diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..d7e7167586afa2a49b59fb3e56602e771d6c2cf2
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,19 @@
+.github
+.DS_Store
+docs
+kubernetes
+node_modules
+/.svelte-kit
+/package
+.env
+.env.*
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+__pycache__
+.idea
+venv
+_old
+uploads
+.ipynb_checkpoints
+**/*.db
+_test
\ No newline at end of file
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000000000000000000000000000000000000..c38bf88bfb96e3a4f87e9f920096a93379a1e677
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,13 @@
+# Ollama URL for the backend to connect
+# The path '/ollama' will be redirected to the specified backend URL
+OLLAMA_BASE_URL='http://localhost:11434'
+
+OPENAI_API_BASE_URL=''
+OPENAI_API_KEY=''
+
+# AUTOMATIC1111_BASE_URL="http://localhost:7860"
+
+# DO NOT TRACK
+SCARF_NO_ANALYTICS=true
+DO_NOT_TRACK=true
+ANONYMIZED_TELEMETRY=false
\ No newline at end of file
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000000000000000000000000000000000000..38972655faff07d2cc0383044bbf9f43b22c2248
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,13 @@
+.DS_Store
+node_modules
+/build
+/.svelte-kit
+/package
+.env
+.env.*
+!.env.example
+
+# Ignore files for PNPM, NPM and YARN
+pnpm-lock.yaml
+package-lock.json
+yarn.lock
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
new file mode 100644
index 0000000000000000000000000000000000000000..cea095ea1aa19e444dc44264c138c95a82dfa04e
--- /dev/null
+++ b/.eslintrc.cjs
@@ -0,0 +1,31 @@
+module.exports = {
+	root: true,
+	extends: [
+		'eslint:recommended',
+		'plugin:@typescript-eslint/recommended',
+		'plugin:svelte/recommended',
+		'plugin:cypress/recommended',
+		'prettier'
+	],
+	parser: '@typescript-eslint/parser',
+	plugins: ['@typescript-eslint'],
+	parserOptions: {
+		sourceType: 'module',
+		ecmaVersion: 2020,
+		extraFileExtensions: ['.svelte']
+	},
+	env: {
+		browser: true,
+		es2017: true,
+		node: true
+	},
+	overrides: [
+		{
+			files: ['*.svelte'],
+			parser: 'svelte-eslint-parser',
+			parserOptions: {
+				parser: '@typescript-eslint/parser'
+			}
+		}
+	]
+};
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..f4382913f4ec5654c4d8c8ba17b25b64afc603d0
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+*.sh text eol=lf
+*.ttf filter=lfs diff=lfs merge=lfs -text
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ef274fa9184f884a8f4af07f0c246d0592eafe42
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: tjbck
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000000000000000000000000000000000000..d0f38c2334e7c2ab89f1274df1ba91e37d2f89ef
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,80 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+---
+
+# Bug Report
+
+## Important Notes
+
+- **Before submitting a bug report**: Please check the Issues or Discussions section to see if a similar issue or feature request has already been posted. It's likely we're already tracking it! If you’re unsure, start a discussion post first. This will help us efficiently focus on improving the project.
+
+- **Collaborate respectfully**: We value a constructive attitude, so please be mindful of your communication. If negativity is part of your approach, our capacity to engage may be limited. We’re here to help if you’re open to learning and communicating positively. Remember, Open WebUI is a volunteer-driven project managed by a single maintainer and supported by contributors who also have full-time jobs. We appreciate your time and ask that you respect ours.
+
+- **Contributing**: If you encounter an issue, we highly encourage you to submit a pull request or fork the project. We actively work to prevent contributor burnout to maintain the quality and continuity of Open WebUI.
+
+- **Bug reproducibility**: If a bug cannot be reproduced with a `:main` or `:dev` Docker setup, or a pip install with Python 3.11, it may require additional help from the community. In such cases, we will move it to the "issues" Discussions section due to our limited resources. We encourage the community to assist with these issues. Remember, it’s not that the issue doesn’t exist; we need your help!
+
+Note: Please remove the notes above when submitting your post. Thank you for your understanding and support!
+
+---
+
+## Installation Method
+
+[Describe the method you used to install the project, e.g., git clone, Docker, pip, etc.]
+
+## Environment
+
+- **Open WebUI Version:** [e.g., v0.3.11]
+- **Ollama (if applicable):** [e.g., v0.2.0, v0.1.32-rc1]
+
+- **Operating System:** [e.g., Windows 10, macOS Big Sur, Ubuntu 20.04]
+- **Browser (if applicable):** [e.g., Chrome 100.0, Firefox 98.0]
+
+**Confirmation:**
+
+- [ ] I have read and followed all the instructions provided in the README.md.
+- [ ] I am on the latest version of both Open WebUI and Ollama.
+- [ ] I have included the browser console logs.
+- [ ] I have included the Docker container logs.
+- [ ] I have provided the exact steps to reproduce the bug in the "Steps to Reproduce" section below.
+
+## Expected Behavior:
+
+[Describe what you expected to happen.]
+
+## Actual Behavior:
+
+[Describe what actually happened.]
+
+## Description
+
+**Bug Summary:**
+[Provide a brief but clear summary of the bug]
+
+## Reproduction Details
+
+**Steps to Reproduce:**
+[Outline the steps to reproduce the bug. Be as detailed as possible.]
+
+## Logs and Screenshots
+
+**Browser Console Logs:**
+[Include relevant browser console logs, if applicable]
+
+**Docker Container Logs:**
+[Include relevant Docker container logs, if applicable]
+
+**Screenshots/Screen Recordings (if applicable):**
+[Attach any relevant screenshots to help illustrate the issue]
+
+## Additional Information
+
+[Include any additional details that may help in understanding and reproducing the issue. This could include specific configurations, error messages, or anything else relevant to the bug.]
+
+## Note
+
+If the bug report is incomplete or does not follow the provided instructions, it may not be addressed. Please ensure that you have followed the steps outlined in the README.md and troubleshooting.md documents, and provide all necessary information for us to reproduce and address the issue. Thank you!
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000000000000000000000000000000000000..5d6e9d708d634b9f5ad6fe066ac15928a6d85a2b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,35 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+---
+
+# Feature Request
+
+## Important Notes
+
+- **Before submitting a report**: Please check the Issues or Discussions section to see if a similar issue or feature request has already been posted. It's likely we're already tracking it! If you’re unsure, start a discussion post first. This will help us efficiently focus on improving the project.
+
+- **Collaborate respectfully**: We value a constructive attitude, so please be mindful of your communication. If negativity is part of your approach, our capacity to engage may be limited. We’re here to help if you’re open to learning and communicating positively. Remember, Open WebUI is a volunteer-driven project managed by a single maintainer and supported by contributors who also have full-time jobs. We appreciate your time and ask that you respect ours.
+
+- **Contributing**: If you encounter an issue, we highly encourage you to submit a pull request or fork the project. We actively work to prevent contributor burnout to maintain the quality and continuity of Open WebUI.
+
+- **Bug reproducibility**: If a bug cannot be reproduced with a `:main` or `:dev` Docker setup, or a pip install with Python 3.11, it may require additional help from the community. In such cases, we will move it to the "issues" Discussions section due to our limited resources. We encourage the community to assist with these issues. Remember, it’s not that the issue doesn’t exist; we need your help!
+
+Note: Please remove the notes above when submitting your post. Thank you for your understanding and support!
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000000000000000000000000000000000..af0a8ed0ee4cd85e0b381dc895baccb90f24f62a
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+version: 2
+updates:
+  - package-ecosystem: pip
+    directory: '/backend'
+    schedule:
+      interval: monthly
+    target-branch: 'dev'
+  - package-ecosystem: 'github-actions'
+    directory: '/'
+    schedule:
+      # Check for updates to GitHub Actions every week
+      interval: monthly
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000000000000000000000000000000000000..2a45c2c16e41cccaea1dd2e67f7568fef1206959
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,72 @@
+# Pull Request Checklist
+
+### Note to first-time contributors: Please open a discussion post in [Discussions](https://github.com/open-webui/open-webui/discussions) and describe your changes before submitting a pull request.
+
+**Before submitting, make sure you've checked the following:**
+
+- [ ] **Target branch:** Please verify that the pull request targets the `dev` branch.
+- [ ] **Description:** Provide a concise description of the changes made in this pull request.
+- [ ] **Changelog:** Ensure a changelog entry following the format of [Keep a Changelog](https://keepachangelog.com/) is added at the bottom of the PR description.
+- [ ] **Documentation:** Have you updated relevant documentation [Open WebUI Docs](https://github.com/open-webui/docs), or other documentation sources?
+- [ ] **Dependencies:** Are there any new dependencies? Have you updated the dependency versions in the documentation?
+- [ ] **Testing:** Have you written and run sufficient tests for validating the changes?
+- [ ] **Code review:** Have you performed a self-review of your code, addressing any coding standard issues and ensuring adherence to the project's coding standards?
+- [ ] **Prefix:** To cleary categorize this pull request, prefix the pull request title, using one of the following:
+  - **BREAKING CHANGE**: Significant changes that may affect compatibility
+  - **build**: Changes that affect the build system or external dependencies
+  - **ci**: Changes to our continuous integration processes or workflows
+  - **chore**: Refactor, cleanup, or other non-functional code changes
+  - **docs**: Documentation update or addition
+  - **feat**: Introduces a new feature or enhancement to the codebase
+  - **fix**: Bug fix or error correction
+  - **i18n**: Internationalization or localization changes
+  - **perf**: Performance improvement
+  - **refactor**: Code restructuring for better maintainability, readability, or scalability
+  - **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc.)
+  - **test**: Adding missing tests or correcting existing tests
+  - **WIP**: Work in progress, a temporary label for incomplete or ongoing work
+
+# Changelog Entry
+
+### Description
+
+- [Concisely describe the changes made in this pull request, including any relevant motivation and impact (e.g., fixing a bug, adding a feature, or improving performance)]
+
+### Added
+
+- [List any new features, functionalities, or additions]
+
+### Changed
+
+- [List any changes, updates, refactorings, or optimizations]
+
+### Deprecated
+
+- [List any deprecated functionality or features that have been removed]
+
+### Removed
+
+- [List any removed features, files, or functionalities]
+
+### Fixed
+
+- [List any fixes, corrections, or bug fixes]
+
+### Security
+
+- [List any new or updated security-related changes, including vulnerability fixes]
+
+### Breaking Changes
+
+- **BREAKING CHANGE**: [List any breaking changes affecting compatibility or functionality]
+
+---
+
+### Additional Information
+
+- [Insert any additional context, notes, or explanations for the changes]
+  - [Reference any related issues, commits, or other relevant information]
+
+### Screenshots or Videos
+
+- [Attach any relevant screenshots or videos demonstrating the changes]
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
new file mode 100644
index 0000000000000000000000000000000000000000..443d904199d7079fe12fc269af45debdc9d316f3
--- /dev/null
+++ b/.github/workflows/build-release.yml
@@ -0,0 +1,72 @@
+name: Release
+
+on:
+  push:
+    branches:
+      - main # or whatever branch you want to use
+
+jobs:
+  release:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Check for changes in package.json
+        run: |
+          git diff --cached --diff-filter=d package.json || {
+            echo "No changes to package.json"
+            exit 1
+          }
+
+      - name: Get version number from package.json
+        id: get_version
+        run: |
+          VERSION=$(jq -r '.version' package.json)
+          echo "::set-output name=version::$VERSION"
+
+      - name: Extract latest CHANGELOG entry
+        id: changelog
+        run: |
+          CHANGELOG_CONTENT=$(awk 'BEGIN {print_section=0;} /^## \[/ {if (print_section == 0) {print_section=1;} else {exit;}} print_section {print;}' CHANGELOG.md)
+          CHANGELOG_ESCAPED=$(echo "$CHANGELOG_CONTENT" | sed ':a;N;$!ba;s/\n/%0A/g')
+          echo "Extracted latest release notes from CHANGELOG.md:" 
+          echo -e "$CHANGELOG_CONTENT" 
+          echo "::set-output name=content::$CHANGELOG_ESCAPED"
+
+      - name: Create GitHub release
+        uses: actions/github-script@v7
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            const changelog = `${{ steps.changelog.outputs.content }}`;
+            const release = await github.rest.repos.createRelease({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              tag_name: `v${{ steps.get_version.outputs.version }}`,
+              name: `v${{ steps.get_version.outputs.version }}`,
+              body: changelog,
+            })
+            console.log(`Created release ${release.data.html_url}`)
+
+      - name: Upload package to GitHub release
+        uses: actions/upload-artifact@v4
+        with:
+          name: package
+          path: |
+            .
+            !.git
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Trigger Docker build workflow
+        uses: actions/github-script@v7
+        with:
+          script: |
+            github.rest.actions.createWorkflowDispatch({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              workflow_id: 'docker-build.yaml',
+              ref: 'v${{ steps.get_version.outputs.version }}',
+            })
diff --git a/.github/workflows/deploy-to-hf-spaces.yml b/.github/workflows/deploy-to-hf-spaces.yml
new file mode 100644
index 0000000000000000000000000000000000000000..aa8bbcfceec2acdb5a617498b369b8c672bf93a9
--- /dev/null
+++ b/.github/workflows/deploy-to-hf-spaces.yml
@@ -0,0 +1,59 @@
+name: Deploy to HuggingFace Spaces
+
+on:
+  push:
+    branches:
+      - dev
+      - main
+  workflow_dispatch:
+
+jobs:
+  check-secret:
+    runs-on: ubuntu-latest
+    outputs:
+      token-set: ${{ steps.check-key.outputs.defined }}
+    steps:
+      - id: check-key
+        env:
+          HF_TOKEN: ${{ secrets.HF_TOKEN }}
+        if: "${{ env.HF_TOKEN != '' }}"
+        run: echo "defined=true" >> $GITHUB_OUTPUT
+
+  deploy:
+    runs-on: ubuntu-latest
+    needs: [check-secret]
+    if: needs.check-secret.outputs.token-set == 'true'
+    env:
+      HF_TOKEN: ${{ secrets.HF_TOKEN }}
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Remove git history
+        run: rm -rf .git
+
+      - name: Prepend YAML front matter to README.md
+        run: |
+          echo "---" > temp_readme.md
+          echo "title: Open WebUI" >> temp_readme.md
+          echo "emoji: 🐳" >> temp_readme.md
+          echo "colorFrom: purple" >> temp_readme.md
+          echo "colorTo: gray" >> temp_readme.md
+          echo "sdk: docker" >> temp_readme.md
+          echo "app_port: 8080" >> temp_readme.md
+          echo "---" >> temp_readme.md
+          cat README.md >> temp_readme.md
+          mv temp_readme.md README.md
+
+      - name: Configure git
+        run: |
+          git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
+          git config --global user.name "github-actions[bot]"
+      - name: Set up Git and push to Space
+        run: |
+          git init --initial-branch=main
+          git lfs track "*.ttf"
+          rm demo.gif
+          git add .
+          git commit -m "GitHub deploy: ${{ github.sha }}"
+          git push --force https://open-webui:${HF_TOKEN}@huggingface.co/spaces/open-webui/open-webui main
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..03dcf845567926f41da3b4c64de42fd94149c3c4
--- /dev/null
+++ b/.github/workflows/docker-build.yaml
@@ -0,0 +1,477 @@
+name: Create and publish Docker images with specific build args
+
+on:
+  workflow_dispatch:
+  push:
+    branches:
+      - main
+      - dev
+    tags:
+      - v*
+
+env:
+  REGISTRY: ghcr.io
+
+jobs:
+  build-main-image:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+    strategy:
+      fail-fast: false
+      matrix:
+        platform:
+          - linux/amd64
+          - linux/arm64
+
+    steps:
+      # GitHub Packages requires the entire repository name to be in lowercase
+      # although the repository owner has a lowercase username, this prevents some people from running actions after forking
+      - name: Set repository and image name to lowercase
+        run: |
+          echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
+          echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
+        env:
+          IMAGE_NAME: '${{ github.repository }}'
+
+      - name: Prepare
+        run: |
+          platform=${{ matrix.platform }}
+          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
+
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Log in to the Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Extract metadata for Docker images (default latest tag)
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.FULL_IMAGE_NAME }}
+          tags: |
+            type=ref,event=branch
+            type=ref,event=tag
+            type=sha,prefix=git-
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
+          flavor: |
+            latest=${{ github.ref == 'refs/heads/main' }}
+
+      - name: Extract metadata for Docker cache
+        id: cache-meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.FULL_IMAGE_NAME }}
+          tags: |
+            type=ref,event=branch
+            ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }}
+          flavor: |
+            prefix=cache-${{ matrix.platform }}-
+            latest=false
+
+      - name: Build Docker image (latest)
+        uses: docker/build-push-action@v5
+        id: build
+        with:
+          context: .
+          push: true
+          platforms: ${{ matrix.platform }}
+          labels: ${{ steps.meta.outputs.labels }}
+          outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
+          cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }}
+          cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
+          build-args: |
+            BUILD_HASH=${{ github.sha }}
+
+      - name: Export digest
+        run: |
+          mkdir -p /tmp/digests
+          digest="${{ steps.build.outputs.digest }}"
+          touch "/tmp/digests/${digest#sha256:}"
+
+      - name: Upload digest
+        uses: actions/upload-artifact@v4
+        with:
+          name: digests-main-${{ env.PLATFORM_PAIR }}
+          path: /tmp/digests/*
+          if-no-files-found: error
+          retention-days: 1
+
+  build-cuda-image:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+    strategy:
+      fail-fast: false
+      matrix:
+        platform:
+          - linux/amd64
+          - linux/arm64
+
+    steps:
+      # GitHub Packages requires the entire repository name to be in lowercase
+      # although the repository owner has a lowercase username, this prevents some people from running actions after forking
+      - name: Set repository and image name to lowercase
+        run: |
+          echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
+          echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
+        env:
+          IMAGE_NAME: '${{ github.repository }}'
+
+      - name: Prepare
+        run: |
+          platform=${{ matrix.platform }}
+          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
+
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Log in to the Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Extract metadata for Docker images (cuda tag)
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.FULL_IMAGE_NAME }}
+          tags: |
+            type=ref,event=branch
+            type=ref,event=tag
+            type=sha,prefix=git-
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
+            type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda
+          flavor: |
+            latest=${{ github.ref == 'refs/heads/main' }}
+            suffix=-cuda,onlatest=true
+
+      - name: Extract metadata for Docker cache
+        id: cache-meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.FULL_IMAGE_NAME }}
+          tags: |
+            type=ref,event=branch
+            ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }}
+          flavor: |
+            prefix=cache-cuda-${{ matrix.platform }}-
+            latest=false
+
+      - name: Build Docker image (cuda)
+        uses: docker/build-push-action@v5
+        id: build
+        with:
+          context: .
+          push: true
+          platforms: ${{ matrix.platform }}
+          labels: ${{ steps.meta.outputs.labels }}
+          outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
+          cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }}
+          cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
+          build-args: |
+            BUILD_HASH=${{ github.sha }}
+            USE_CUDA=true
+
+      - name: Export digest
+        run: |
+          mkdir -p /tmp/digests
+          digest="${{ steps.build.outputs.digest }}"
+          touch "/tmp/digests/${digest#sha256:}"
+
+      - name: Upload digest
+        uses: actions/upload-artifact@v4
+        with:
+          name: digests-cuda-${{ env.PLATFORM_PAIR }}
+          path: /tmp/digests/*
+          if-no-files-found: error
+          retention-days: 1
+
+  build-ollama-image:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+    strategy:
+      fail-fast: false
+      matrix:
+        platform:
+          - linux/amd64
+          - linux/arm64
+
+    steps:
+      # GitHub Packages requires the entire repository name to be in lowercase
+      # although the repository owner has a lowercase username, this prevents some people from running actions after forking
+      - name: Set repository and image name to lowercase
+        run: |
+          echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
+          echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
+        env:
+          IMAGE_NAME: '${{ github.repository }}'
+
+      - name: Prepare
+        run: |
+          platform=${{ matrix.platform }}
+          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
+
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Log in to the Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Extract metadata for Docker images (ollama tag)
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.FULL_IMAGE_NAME }}
+          tags: |
+            type=ref,event=branch
+            type=ref,event=tag
+            type=sha,prefix=git-
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
+            type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=ollama
+          flavor: |
+            latest=${{ github.ref == 'refs/heads/main' }}
+            suffix=-ollama,onlatest=true
+
+      - name: Extract metadata for Docker cache
+        id: cache-meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.FULL_IMAGE_NAME }}
+          tags: |
+            type=ref,event=branch
+            ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }}
+          flavor: |
+            prefix=cache-ollama-${{ matrix.platform }}-
+            latest=false
+
+      - name: Build Docker image (ollama)
+        uses: docker/build-push-action@v5
+        id: build
+        with:
+          context: .
+          push: true
+          platforms: ${{ matrix.platform }}
+          labels: ${{ steps.meta.outputs.labels }}
+          outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
+          cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }}
+          cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
+          build-args: |
+            BUILD_HASH=${{ github.sha }}
+            USE_OLLAMA=true
+
+      - name: Export digest
+        run: |
+          mkdir -p /tmp/digests
+          digest="${{ steps.build.outputs.digest }}"
+          touch "/tmp/digests/${digest#sha256:}"
+
+      - name: Upload digest
+        uses: actions/upload-artifact@v4
+        with:
+          name: digests-ollama-${{ env.PLATFORM_PAIR }}
+          path: /tmp/digests/*
+          if-no-files-found: error
+          retention-days: 1
+
+  merge-main-images:
+    runs-on: ubuntu-latest
+    needs: [build-main-image]
+    steps:
+      # GitHub Packages requires the entire repository name to be in lowercase
+      # although the repository owner has a lowercase username, this prevents some people from running actions after forking
+      - name: Set repository and image name to lowercase
+        run: |
+          echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
+          echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
+        env:
+          IMAGE_NAME: '${{ github.repository }}'
+
+      - name: Download digests
+        uses: actions/download-artifact@v4
+        with:
+          pattern: digests-main-*
+          path: /tmp/digests
+          merge-multiple: true
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Log in to the Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Extract metadata for Docker images (default latest tag)
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.FULL_IMAGE_NAME }}
+          tags: |
+            type=ref,event=branch
+            type=ref,event=tag
+            type=sha,prefix=git-
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
+          flavor: |
+            latest=${{ github.ref == 'refs/heads/main' }}
+
+      - name: Create manifest list and push
+        working-directory: /tmp/digests
+        run: |
+          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+            $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *)
+
+      - name: Inspect image
+        run: |
+          docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }}
+
+  merge-cuda-images:
+    runs-on: ubuntu-latest
+    needs: [build-cuda-image]
+    steps:
+      # GitHub Packages requires the entire repository name to be in lowercase
+      # although the repository owner has a lowercase username, this prevents some people from running actions after forking
+      - name: Set repository and image name to lowercase
+        run: |
+          echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
+          echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
+        env:
+          IMAGE_NAME: '${{ github.repository }}'
+
+      - name: Download digests
+        uses: actions/download-artifact@v4
+        with:
+          pattern: digests-cuda-*
+          path: /tmp/digests
+          merge-multiple: true
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Log in to the Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Extract metadata for Docker images (default latest tag)
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.FULL_IMAGE_NAME }}
+          tags: |
+            type=ref,event=branch
+            type=ref,event=tag
+            type=sha,prefix=git-
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
+            type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda
+          flavor: |
+            latest=${{ github.ref == 'refs/heads/main' }}
+            suffix=-cuda,onlatest=true
+
+      - name: Create manifest list and push
+        working-directory: /tmp/digests
+        run: |
+          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+            $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *)
+
+      - name: Inspect image
+        run: |
+          docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }}
+
+  merge-ollama-images:
+    runs-on: ubuntu-latest
+    needs: [build-ollama-image]
+    steps:
+      # GitHub Packages requires the entire repository name to be in lowercase
+      # although the repository owner has a lowercase username, this prevents some people from running actions after forking
+      - name: Set repository and image name to lowercase
+        run: |
+          echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
+          echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
+        env:
+          IMAGE_NAME: '${{ github.repository }}'
+
+      - name: Download digests
+        uses: actions/download-artifact@v4
+        with:
+          pattern: digests-ollama-*
+          path: /tmp/digests
+          merge-multiple: true
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Log in to the Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Extract metadata for Docker images (default ollama tag)
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.FULL_IMAGE_NAME }}
+          tags: |
+            type=ref,event=branch
+            type=ref,event=tag
+            type=sha,prefix=git-
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
+            type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=ollama
+          flavor: |
+            latest=${{ github.ref == 'refs/heads/main' }}
+            suffix=-ollama,onlatest=true
+
+      - name: Create manifest list and push
+        working-directory: /tmp/digests
+        run: |
+          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+            $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *)
+
+      - name: Inspect image
+        run: |
+          docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }}
diff --git a/.github/workflows/format-backend.yaml b/.github/workflows/format-backend.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..44587669753b484be551c1560075e4b36d39f270
--- /dev/null
+++ b/.github/workflows/format-backend.yaml
@@ -0,0 +1,39 @@
+name: Python CI
+
+on:
+  push:
+    branches:
+      - main
+      - dev
+  pull_request:
+    branches:
+      - main
+      - dev
+
+jobs:
+  build:
+    name: 'Format Backend'
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        python-version: [3.11]
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install black
+
+      - name: Format backend
+        run: npm run format:backend
+
+      - name: Check for changes after format
+        run: git diff --exit-code
diff --git a/.github/workflows/format-build-frontend.yaml b/.github/workflows/format-build-frontend.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..53d3aaa5ec830db736d167992bb2859332446f58
--- /dev/null
+++ b/.github/workflows/format-build-frontend.yaml
@@ -0,0 +1,57 @@
+name: Frontend Build
+
+on:
+  push:
+    branches:
+      - main
+      - dev
+  pull_request:
+    branches:
+      - main
+      - dev
+
+jobs:
+  build:
+    name: 'Format & Build Frontend'
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Repository
+        uses: actions/checkout@v4
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '22' # Or specify any other version you want to use
+
+      - name: Install Dependencies
+        run: npm install
+
+      - name: Format Frontend
+        run: npm run format
+
+      - name: Run i18next
+        run: npm run i18n:parse
+
+      - name: Check for Changes After Format
+        run: git diff --exit-code
+
+      - name: Build Frontend
+        run: npm run build
+
+  test-frontend:
+    name: 'Frontend Unit Tests'
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Repository
+        uses: actions/checkout@v4
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '22'
+
+      - name: Install Dependencies
+        run: npm ci
+
+      - name: Run vitest
+        run: npm run test:frontend
diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cb404f1fc1bd1671393a63f1c38c3da5e416468d
--- /dev/null
+++ b/.github/workflows/integration-test.yml
@@ -0,0 +1,253 @@
+name: Integration Test
+
+on:
+  push:
+    branches:
+      - main
+      - dev
+  pull_request:
+    branches:
+      - main
+      - dev
+
+jobs:
+  cypress-run:
+    name: Run Cypress Integration Tests
+    runs-on: ubuntu-latest
+    steps:
+      - name: Maximize build space
+        uses: AdityaGarg8/remove-unwanted-software@v4.1
+        with:
+          remove-android: 'true'
+          remove-haskell: 'true'
+          remove-codeql: 'true'
+
+      - name: Checkout Repository
+        uses: actions/checkout@v4
+
+      - name: Build and run Compose Stack
+        run: |
+          docker compose \
+            --file docker-compose.yaml \
+            --file docker-compose.api.yaml \
+            --file docker-compose.a1111-test.yaml \
+            up --detach --build
+
+      - name: Delete Docker build cache
+        run: |
+          docker builder prune --all --force
+
+      - name: Wait for Ollama to be up
+        timeout-minutes: 5
+        run: |
+          until curl --output /dev/null --silent --fail http://localhost:11434; do
+            printf '.'
+            sleep 1
+          done
+          echo "Service is up!"
+
+      - name: Preload Ollama model
+        run: |
+          docker exec ollama ollama pull qwen:0.5b-chat-v1.5-q2_K
+
+      - name: Cypress run
+        uses: cypress-io/github-action@v6
+        with:
+          browser: chrome
+          wait-on: 'http://localhost:3000'
+          config: baseUrl=http://localhost:3000
+
+      - uses: actions/upload-artifact@v4
+        if: always()
+        name: Upload Cypress videos
+        with:
+          name: cypress-videos
+          path: cypress/videos
+          if-no-files-found: ignore
+
+      - name: Extract Compose logs
+        if: always()
+        run: |
+          docker compose logs > compose-logs.txt
+
+      - uses: actions/upload-artifact@v4
+        if: always()
+        name: Upload Compose logs
+        with:
+          name: compose-logs
+          path: compose-logs.txt
+          if-no-files-found: ignore
+
+  # pytest:
+  #   name: Run Backend Tests
+  #   runs-on: ubuntu-latest
+  #   steps:
+  #     - uses: actions/checkout@v4
+
+  #     - name: Set up Python
+  #       uses: actions/setup-python@v5
+  #       with:
+  #         python-version: ${{ matrix.python-version }}
+
+  #     - name: Install dependencies
+  #       run: |
+  #         python -m pip install --upgrade pip
+  #         pip install -r backend/requirements.txt
+
+  #     - name: pytest run
+  #       run: |
+  #         ls -al
+  #         cd backend
+  #         PYTHONPATH=. pytest . -o log_cli=true -o log_cli_level=INFO
+
+  migration_test:
+    name: Run Migration Tests
+    runs-on: ubuntu-latest
+    services:
+      postgres:
+        image: postgres
+        env:
+          POSTGRES_PASSWORD: postgres
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+        ports:
+          - 5432:5432
+    #      mysql:
+    #        image: mysql
+    #        env:
+    #          MYSQL_ROOT_PASSWORD: mysql
+    #          MYSQL_DATABASE: mysql
+    #        options: >-
+    #          --health-cmd "mysqladmin ping -h localhost"
+    #          --health-interval 10s
+    #          --health-timeout 5s
+    #          --health-retries 5
+    #        ports:
+    #          - 3306:3306
+    steps:
+      - name: Checkout Repository
+        uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Set up uv
+        uses: yezz123/setup-uv@v4
+        with:
+          uv-venv: venv
+
+      - name: Activate virtualenv
+        run: |
+          . venv/bin/activate
+          echo PATH=$PATH >> $GITHUB_ENV
+
+      - name: Install dependencies
+        run: |
+          uv pip install -r backend/requirements.txt
+
+      - name: Test backend with SQLite
+        id: sqlite
+        env:
+          WEBUI_SECRET_KEY: secret-key
+          GLOBAL_LOG_LEVEL: debug
+        run: |
+          cd backend
+          uvicorn open_webui.main:app --port "8080" --forwarded-allow-ips '*' &
+          UVICORN_PID=$!
+          # Wait up to 40 seconds for the server to start
+          for i in {1..40}; do
+              curl -s http://localhost:8080/api/config > /dev/null && break
+              sleep 1
+              if [ $i -eq 40 ]; then
+                  echo "Server failed to start"
+                  kill -9 $UVICORN_PID
+                  exit 1
+              fi
+          done
+          # Check that the server is still running after 5 seconds
+          sleep 5
+          if ! kill -0 $UVICORN_PID; then
+              echo "Server has stopped"
+              exit 1
+          fi
+
+      - name: Test backend with Postgres
+        if: success() || steps.sqlite.conclusion == 'failure'
+        env:
+          WEBUI_SECRET_KEY: secret-key
+          GLOBAL_LOG_LEVEL: debug
+          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
+          DATABASE_POOL_SIZE: 10
+          DATABASE_POOL_MAX_OVERFLOW: 10
+          DATABASE_POOL_TIMEOUT: 30
+        run: |
+          cd backend
+          uvicorn open_webui.main:app --port "8081" --forwarded-allow-ips '*' &
+          UVICORN_PID=$!
+          # Wait up to 20 seconds for the server to start
+          for i in {1..20}; do
+              curl -s http://localhost:8081/api/config > /dev/null && break
+              sleep 1
+              if [ $i -eq 20 ]; then
+                  echo "Server failed to start"
+                  kill -9 $UVICORN_PID
+                  exit 1
+              fi
+          done
+          # Check that the server is still running after 5 seconds
+          sleep 5
+          if ! kill -0 $UVICORN_PID; then
+              echo "Server has stopped"
+              exit 1
+          fi
+
+          # Check that service will reconnect to postgres when connection will be closed
+          status_code=$(curl --write-out %{http_code} -s --output /dev/null http://localhost:8081/health/db)
+          if [[ "$status_code" -ne 200 ]] ; then
+            echo "Server has failed before postgres reconnect check"
+            exit 1
+          fi
+
+          echo "Terminating all connections to postgres..."
+          python -c "import os, psycopg2 as pg2; \
+            conn = pg2.connect(dsn=os.environ['DATABASE_URL'].replace('+pool', '')); \
+            cur = conn.cursor(); \
+            cur.execute('SELECT pg_terminate_backend(psa.pid) FROM pg_stat_activity psa WHERE datname = current_database() AND pid <> pg_backend_pid();')"
+
+          status_code=$(curl --write-out %{http_code} -s --output /dev/null http://localhost:8081/health/db)
+          if [[ "$status_code" -ne 200 ]] ; then
+            echo "Server has not reconnected to postgres after connection was closed: returned status $status_code"
+            exit 1
+          fi
+
+#      - name: Test backend with MySQL
+#        if: success() || steps.sqlite.conclusion == 'failure' || steps.postgres.conclusion == 'failure'
+#        env:
+#          WEBUI_SECRET_KEY: secret-key
+#          GLOBAL_LOG_LEVEL: debug
+#          DATABASE_URL: mysql://root:mysql@localhost:3306/mysql
+#        run: |
+#          cd backend
+#          uvicorn open_webui.main:app --port "8083" --forwarded-allow-ips '*' &
+#          UVICORN_PID=$!
+#          # Wait up to 20 seconds for the server to start
+#          for i in {1..20}; do
+#              curl -s http://localhost:8083/api/config > /dev/null && break
+#              sleep 1
+#              if [ $i -eq 20 ]; then
+#                  echo "Server failed to start"
+#                  kill -9 $UVICORN_PID
+#                  exit 1
+#              fi
+#          done
+#          # Check that the server is still running after 5 seconds
+#          sleep 5
+#          if ! kill -0 $UVICORN_PID; then
+#              echo "Server has stopped"
+#              exit 1
+#          fi
diff --git a/.github/workflows/lint-backend.disabled b/.github/workflows/lint-backend.disabled
new file mode 100644
index 0000000000000000000000000000000000000000..dd0bdc7fa7bb123de740f6ee2df1d8aaa6dfa312
--- /dev/null
+++ b/.github/workflows/lint-backend.disabled
@@ -0,0 +1,27 @@
+name: Python CI
+on:
+  push:
+    branches: ['main']
+  pull_request:
+jobs:
+  build:
+    name: 'Lint Backend'
+    env:
+      PUBLIC_API_BASE_URL: ''
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version:
+          - latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Use Python
+        uses: actions/setup-python@v5
+      - name: Use Bun
+        uses: oven-sh/setup-bun@v1
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install pylint
+      - name: Lint backend
+        run: bun run lint:backend
diff --git a/.github/workflows/lint-frontend.disabled b/.github/workflows/lint-frontend.disabled
new file mode 100644
index 0000000000000000000000000000000000000000..2c1cd3c5a574989def4319d07405f0bb621d74df
--- /dev/null
+++ b/.github/workflows/lint-frontend.disabled
@@ -0,0 +1,21 @@
+name: Bun CI
+on:
+  push:
+    branches: ['main']
+  pull_request:
+jobs:
+  build:
+    name: 'Lint Frontend'
+    env:
+      PUBLIC_API_BASE_URL: ''
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Use Bun
+        uses: oven-sh/setup-bun@v1
+      - run: bun --version
+      - name: Install frontend dependencies
+        run: bun install --frozen-lockfile
+      - run: bun run lint:frontend
+      - run: bun run lint:types
+        if: success() || failure()
\ No newline at end of file
diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8a2e3438a6dfb242f665602b31da133b39f1f797
--- /dev/null
+++ b/.github/workflows/release-pypi.yml
@@ -0,0 +1,32 @@
+name: Release to PyPI
+
+on:
+  push:
+    branches:
+      - main # or whatever branch you want to use
+      - pypi-release
+
+jobs:
+  release:
+    runs-on: ubuntu-latest
+    environment:
+      name: pypi
+      url: https://pypi.org/p/open-webui
+    permissions:
+      id-token: write
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+      - uses: actions/setup-node@v4
+        with:
+          node-version: 18
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.11
+      - name: Build
+        run: |
+          python -m pip install --upgrade pip
+          pip install build
+          python -m build .
+      - name: Publish package distributions to PyPI
+        uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..32271f8087e213e83089162bd0b1ec99c60d45ca
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,309 @@
+.DS_Store
+node_modules
+/build
+/.svelte-kit
+/package
+.env
+.env.*
+!.env.example
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Pyodide distribution
+static/pyodide/*
+!static/pyodide/pyodide-lock.json
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+# cypress artifacts
+cypress/videos
+cypress/screenshots
+.vscode/settings.json
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000000000000000000000000000000000000..b6f27f135954640c8cc5bfd7b8c9922ca6eb2aad
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+engine-strict=true
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000000000000000000000000000000000000..82c49125724030d1010bb123df7dae758236ff11
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,316 @@
+# Ignore files for PNPM, NPM and YARN
+pnpm-lock.yaml
+package-lock.json
+yarn.lock
+
+kubernetes/
+
+# Copy of .gitignore
+.DS_Store
+node_modules
+/build
+/.svelte-kit
+/package
+.env
+.env.*
+!.env.example
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+# cypress artifacts
+cypress/videos
+cypress/screenshots
+
+
+
+/static/*
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000000000000000000000000000000000000..a77fddea90975988d17a7e8b2f61720939a947f5
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,9 @@
+{
+	"useTabs": true,
+	"singleQuote": true,
+	"trailingComma": "none",
+	"printWidth": 100,
+	"plugins": ["prettier-plugin-svelte"],
+	"pluginSearchDirs": ["."],
+	"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..5ca694e65c772957733e6f5c8a4b62e9c10fbcea
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,1226 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [0.3.35] - 2024-10-26
+
+### Added
+
+- **📁 Robust File Handling**: Enhanced file input handling for chat. If the content extraction fails or is empty, users will now receive a clear warning, preventing silent failures and ensuring you always know what's happening with your uploads.
+- **🌍 New Language Support**: Introduced Hungarian translations and updated French translations, expanding the platform's language accessibility for a more global user base.
+
+### Fixed
+
+- **📚 Knowledge Base Loading Issue**: Resolved a critical bug where the Knowledge Base was not loading, ensuring smooth access to your stored documents and improving information retrieval in RAG-enhanced workflows.
+- **🛠️ Tool Parameters Issue**: Fixed an error where tools were not functioning correctly when required parameters were missing, ensuring reliable tool performance and more efficient task completions.
+- **🔗 Merged Response Loss in Multi-Model Chats**: Addressed an issue where responses in multi-model chat workflows were being deleted after follow-up queries, improving consistency and ensuring smoother interactions across models.
+
+## [0.3.34] - 2024-10-26
+
+### Added
+
+- **🔧 Feedback Export Enhancements**: Feedback history data can now be exported to JSON, allowing for seamless integration in RLHF processing and further analysis.
+- **🗂️ Embedding Model Lazy Loading**: Search functionality for leaderboard reranking is now more efficient, as embedding models are lazy-loaded only when needed, optimizing performance.
+- **🎨 Rich Text Input Toggle**: Users can now switch back to legacy textarea input for chat if they prefer simpler text input, though rich text is still the default until deprecation.
+- **🛠️ Improved Tool Calling Mechanism**: Enhanced method for parsing and calling tools, improving the reliability and robustness of tool function calls.
+- **🌐 Globalization Enhancements**: Updates to internationalization (i18n) support, further refining multi-language compatibility and accuracy.
+
+### Fixed
+
+- **🖥️ Folder Rename Fix for Firefox**: Addressed a persistent issue where users could not rename folders by pressing enter in Firefox, now ensuring seamless folder management across browsers.
+- **🔠 Tiktoken Model Text Splitter Issue**: Resolved an issue where the tiktoken text splitter wasn’t working in Docker installations, restoring full functionality for tokenized text editing.
+- **💼 S3 File Upload Issue**: Fixed a problem affecting S3 file uploads, ensuring smooth operations for those who store files on cloud storage.
+- **🔒 Strict-Transport-Security Crash**: Resolved a crash when setting the Strict-Transport-Security (HSTS) header, improving stability and security enhancements.
+- **🚫 OIDC Boolean Access Fix**: Addressed an issue with boolean values not being accessed correctly during OIDC logins, ensuring login reliability.
+- **⚙️ Rich Text Paste Behavior**: Refined paste behavior in rich text input to make it smoother and more intuitive when pasting various content types.
+- **🔨 Model Exclusion for Arena Fix**: Corrected the filter function that was not properly excluding models from the arena, improving model management.
+- **🏷️ "Tags Generation Prompt" Fix**: Addressed an issue preventing custom "tags generation prompts" from registering properly, ensuring custom prompt work seamlessly.
+
+## [0.3.33] - 2024-10-24
+
+### Added
+
+- **🏆 Evaluation Leaderboard**: Easily track your performance through a new leaderboard system where your ratings contribute to a real-time ranking based on the Elo system. Sibling responses (regenerations, many model chats) are required for your ratings to count in the leaderboard. Additionally, you can opt-in to share your feedback history and be part of the community-wide leaderboard. Expect further improvements as we refine the algorithm—help us build the best community leaderboard!
+- **⚔️ Arena Model Evaluation**: Enable blind A/B testing of models directly from Admin Settings > Evaluation for a true side-by-side comparison. Ideal for pinpointing the best model for your needs.
+- **🎯 Topic-Based Leaderboard**: Discover more accurate rankings with experimental topic-based reranking, which adjusts leaderboard standings based on tag similarity in feedback. Get more relevant insights based on specific topics!
+- **📁 Folders Support for Chats**: Organize your chats better by grouping them into folders. Drag and drop chats between folders and export them seamlessly for easy sharing or analysis.
+- **📤 Easy Chat Import via Drag & Drop**: Save time by simply dragging and dropping chat exports (JSON) directly onto the sidebar to import them into your workspace—streamlined, efficient, and intuitive!
+- **📚 Enhanced Knowledge Collection**: Now, you can reference individual files from a knowledge collection—ideal for more precise Retrieval-Augmented Generations (RAG) queries and document analysis.
+- **🏷️ Enhanced Tagging System**: Tags now take up less space! Utilize the new 'tag:' query system to manage, search, and organize your conversations more effectively without cluttering the interface.
+- **🧠 Auto-Tagging for Chats**: Your conversations are now automatically tagged for improved organization, mirroring the efficiency of auto-generated titles.
+- **🔍 Backend Chat Query System**: Chat filtering has become more efficient, now handled through the backend\*\* instead of your browser, improving search performance and accuracy.
+- **🎮 Revamped Playground**: Experience a refreshed and optimized Playground for smoother testing, tweaks, and experimentation of your models and tools.
+- **🧩 Token-Based Text Splitter**: Introducing token-based text splitting (tiktoken), giving you more precise control over how text is processed. Previously, only character-based splitting was available.
+- **🔢 Ollama Batch Embeddings**: Leverage new batch embedding support for improved efficiency and performance with Ollama embedding models.
+- **🔍 Enhanced Add Text Content Modal**: Enjoy a cleaner, more intuitive workflow for adding and curating knowledge content with an upgraded input modal from our Knowledge workspace.
+- **🖋️ Rich Text Input for Chats**: Make your chat inputs more dynamic with support for rich text formatting. Your conversations just got a lot more polished and professional.
+- **⚡ Faster Whisper Model Configurability**: Customize your local faster whisper model directly from the WebUI.
+- **☁️ Experimental S3 Support**: Enable stateless WebUI instances with S3 support, greatly enhancing scalability and balancing heavy workloads.
+- **🔕 Disable Update Toast**: Now you can streamline your workspace even further—choose to disable update notifications for a more focused experience.
+- **🌟 RAG Citation Relevance Percentage**: Easily assess citation accuracy with the addition of relevance percentages in RAG results.
+- **⚙️ Mermaid Copy Button**: Mermaid diagrams now come with a handy copy button, simplifying the extraction and use of diagram contents directly in your workflow.
+- **🎨 UI Redesign**: Major interface redesign that will make navigation smoother, keep your focus where it matters, and ensure a modern look.
+
+### Fixed
+
+- **🎙️ Voice Note Mic Stopping Issue**: Fixed the issue where the microphone stayed active after ending a voice note recording, ensuring your audio workflow runs smoothly.
+
+### Removed
+
+- **👋 Goodbye Sidebar Tags**: Sidebar tag clutter is gone. We’ve shifted tag buttons to more effective query-based tag filtering for a sleeker, more agile interface.
+
+## [0.3.32] - 2024-10-06
+
+### Added
+
+- **🔢 Workspace Enhancements**: Added a display count for models, prompts, tools, and functions in the workspace, providing a clear overview and easier management.
+
+### Fixed
+
+- **🖥️ Web and YouTube Attachment Fix**: Resolved an issue where attaching web links and YouTube videos was malfunctioning, ensuring seamless integration and display within chats.
+- **📞 Call Mode Activation on Landing Page**: Fixed a bug where call mode was not operational from the landing page.
+
+### Changed
+
+- **🔄 URL Parameter Refinement**: Updated the 'tool_ids' URL parameter to 'tools' or 'tool-ids' for more intuitive and consistent user experience.
+- **🎨 Floating Buttons Styling Update**: Refactored the styling of floating buttons to intelligently adjust to the left side when there isn't enough room on the right, improving interface usability and aesthetic.
+- **🔧 Enhanced Accessibility for Floating Buttons**: Implemented the ability to close floating buttons with the 'Esc' key, making workflow smoother and more efficient for users navigating via keyboard.
+- **🖇️ Updated Information URL**: Information URLs now direct users to a general release page rather than a version-specific URL, ensuring access to the latest and relevant details all in one place.
+- **📦 Library Dependencies Update**: Upgraded dependencies to ensure compatibility and performance optimization for pip installs.
+
+## [0.3.31] - 2024-10-06
+
+### Added
+
+- **📚 Knowledge Feature**: Reimagined documents feature, now more performant with a better UI for enhanced organization; includes streamlined API integration for Retrieval-Augmented Generation (RAG). Detailed documentation forthcoming: https://docs.openwebui.com/
+- **🌐 New Landing Page**: Freshly designed landing page; toggle between the new UI and the classic chat UI from Settings > Interface for a personalized experience.
+- **📁 Full Document Retrieval Mode**: Toggle between full document retrieval or traditional snippets by clicking on the file item. This mode enhances document capabilities and supports comprehensive tasks like summarization by utilizing the entire content instead of RAG.
+- **📄 Extracted File Content Display**: View extracted content directly by clicking on the file item, simplifying file analysis.
+- **🎨 Artifacts Feature**: Render web content and SVGs directly in the interface, supporting quick iterations and live changes.
+- **🖊️ Editable Code Blocks**: Supercharged code blocks now allow live editing directly in the LLM response, with live reloads supported by artifacts.
+- **🔧 Code Block Enhancements**: Introduced a floating copy button in code blocks to facilitate easier code copying without scrolling.
+- **🔍 SVG Pan/Zoom**: Enhanced interaction with SVG images, including Mermaid diagrams, via new pan and zoom capabilities.
+- **🔍 Text Select Quick Actions**: New floating buttons appear when text is highlighted in LLM responses, offering deeper interactions like "Ask a Question" or "Explain".
+- **🗃️ Database Pool Configuration**: Enhanced database handling to support scalable user growth.
+- **🔊 Experimental Audio Compression**: Compress audio files to navigate around the 25MB limit for OpenAI's speech-to-text processing.
+- **🔍 Query Embedding**: Adjusted embedding behavior to enhance system performance by not repeating query embedding.
+- **💾 Lazy Load Optimizations**: Implemented lazy loading of large dependencies to minimize initial memory usage, boosting performance.
+- **🍏 Apple Touch Icon Support**: Optimizes the display of icons for web bookmarks on Apple mobile devices.
+- **🔽 Expandable Content Markdown Support**: Introducing 'details', 'summary' tag support for creating expandable content sections in markdown, facilitating cleaner, organized documentation and interactive content display.
+
+### Fixed
+
+- **🔘 Action Button Issue**: Resolved a bug where action buttons were not functioning, enhancing UI reliability.
+- **🔄 Multi-Model Chat Loop**: Fixed an infinite loop issue in multi-model chat environments, ensuring smoother chat operations.
+- **📄 Chat PDF/TXT Export Issue**: Resolved problems with exporting chat logs to PDF and TXT formats.
+- **🔊 Call to Text-to-Speech Issues**: Rectified problems with text-to-speech functions to improve audio interactions.
+
+### Changed
+
+- **⚙️ Endpoint Renaming**: Renamed 'rag' endpoints to 'retrieval' for clearer function description.
+- **🎨 Styling and Interface Updates**: Multiple refinements across the platform to enhance visual appeal and user interaction.
+
+### Removed
+
+- **🗑️ Deprecated 'DOCS_DIR'**: Removed the outdated 'docs_dir' variable in favor of more direct file management solutions, with direct file directory syncing and API uploads for a more integrated experience.
+
+## [0.3.30] - 2024-09-26
+
+### Fixed
+
+- **🍞 Update Available Toast Dismissal**: Enhanced user experience by ensuring that once the update available notification is dismissed, it won't reappear for 24 hours.
+- **📋 Ollama /embed Form Data**: Adjusted the integration inaccuracies in the /embed form data to ensure it perfectly matches with Ollama's specifications.
+- **🔧 O1 Max Completion Tokens Issue**: Resolved compatibility issues with OpenAI's o1 models max_completion_tokens param to ensure smooth operation.
+- **🔄 Pip Install Database Issue**: Fixed a critical issue where database changes during pip installations were reverting and not saving chat logs, now ensuring data persistence and reliability in chat operations.
+- **🏷️ Chat Rename Tab Update**: Fixed the functionality to change the web browser's tab title simultaneously when a chat is renamed, keeping tab titles consistent.
+
+## [0.3.29] - 2023-09-25
+
+### Fixed
+
+- **🔧 KaTeX Rendering Improvement**: Resolved specific corner cases in KaTeX rendering to enhance the display of complex mathematical notation.
+- **📞 'Call' URL Parameter Fix**: Corrected functionality for 'call' URL search parameter ensuring reliable activation of voice calls through URL triggers.
+- **🔄 Configuration Reset Fix**: Fixed the RESET_CONFIG_ON_START to ensure settings revert to default correctly upon each startup, improving reliability in configuration management.
+- **🌍 Filter Outlet Hook Fix**: Addressed issues in the filter outlet hook, ensuring all filter functions operate as intended.
+
+## [0.3.28] - 2024-09-24
+
+### Fixed
+
+- **🔍 Web Search Functionality**: Corrected an issue where the web search option was not functioning properly.
+
+## [0.3.27] - 2024-09-24
+
+### Fixed
+
+- **🔄 Periodic Cleanup Error Resolved**: Fixed a critical RuntimeError related to the 'periodic_usage_pool_cleanup' coroutine, ensuring smooth and efficient performance post-pip install, correcting a persisting issue from version 0.3.26.
+- **📊 Enhanced LaTeX Rendering**: Improved rendering for LaTeX content, enhancing clarity and visual presentation in documents and mathematical models.
+
+## [0.3.26] - 2024-09-24
+
+### Fixed
+
+- **🔄 Event Loop Error Resolution**: Addressed a critical error where a missing running event loop caused 'periodic_usage_pool_cleanup' to fail with pip installs. This fix ensures smoother and more reliable updates and installations, enhancing overall system stability.
+
+## [0.3.25] - 2024-09-24
+
+### Fixed
+
+- **🖼️ Image Generation Functionality**: Resolved an issue where image generation was not functioning, restoring full capability for visual content creation.
+- **⚖️ Rate Response Corrections**: Addressed a problem where rate responses were not working, ensuring reliable feedback mechanisms are operational.
+
+## [0.3.24] - 2024-09-24
+
+### Added
+
+- **🚀 Rendering Optimization**: Significantly improved message rendering performance, enhancing user experience and webui responsiveness.
+- **💖 Favorite Response Feature in Chat Overview**: Users can now mark responses as favorite directly from the chat overview, enhancing ease of retrieval and organization of preferred responses.
+- **💬 Create Message Pairs with Shortcut**: Implemented creation of new message pairs using Cmd/Ctrl+Shift+Enter, making conversation editing faster and more intuitive.
+- **🌍 Expanded User Prompt Variables**: Added weekday, timezone, and language information variables to user prompts to match system prompt variables.
+- **🎵 Enhanced Audio Support**: Now includes support for 'audio/x-m4a' files, broadening compatibility with audio content within the platform.
+- **🔏 Model URL Search Parameter**: Added an ability to select a model directly via URL parameters, streamlining navigation and model access.
+- **📄 Enhanced PDF Citations**: PDF citations now open at the associated page, streamlining reference checks and document handling.
+- **🔧Use of Redis in Sockets**: Enhanced socket implementation to fully support Redis, enabling effective stateless instances suitable for scalable load balancing.
+- **🌍 Stream Individual Model Responses**: Allows specific models to have individualized streaming settings, enhancing performance and customization.
+- **🕒 Display Model Hash and Last Modified Timestamp for Ollama Models**: Provides critical model details directly in the Models workspace for enhanced tracking.
+- **❗ Update Info Notification for Admins**: Ensures administrators receive immediate updates upon login, keeping them informed of the latest changes and system statuses.
+
+### Fixed
+
+- **🗑️ Temporary File Handling On Windows**: Fixed an issue causing errors when accessing a temporary file being used by another process, Tools & Functions should now work as intended.
+- **🔓 Authentication Toggle Issue**: Resolved the malfunction where setting 'WEBUI_AUTH=False' did not appropriately disable authentication, ensuring that user experience and system security settings function as configured.
+- **🔧 Save As Copy Issue for Many Model Chats**: Resolved an error preventing users from save messages as copies in many model chats.
+- **🔒 Sidebar Closure on Mobile**: Resolved an issue where the mobile sidebar remained open after menu engagement, improving user interface responsivity and comfort.
+- **🛡️ Tooltip XSS Vulnerability**: Resolved a cross-site scripting (XSS) issue within tooltips, ensuring enhanced security and data integrity during user interactions.
+
+### Changed
+
+- **↩️ Deprecated Interface Stream Response Settings**: Moved to advanced parameters to streamline interface settings and enhance user clarity.
+- **⚙️ Renamed 'speedRate' to 'playbackRate'**: Standardizes terminology, improving usability and understanding in media settings.
+
+## [0.3.23] - 2024-09-21
+
+### Added
+
+- **🚀 WebSocket Redis Support**: Enhanced load balancing capabilities for multiple instance setups, promoting better performance and reliability in WebUI.
+- **🔧 Adjustable Chat Controls**: Introduced width-adjustable chat controls, enabling a personalized and more comfortable user interface.
+- **🌎 i18n Updates**: Improved and updated the Chinese translations.
+
+### Fixed
+
+- **🌐 Task Model Unloading Issue**: Modified task handling to use the Ollama /api/chat endpoint instead of OpenAI compatible endpoint, ensuring models stay loaded and ready with custom parameters, thus minimizing delays in task execution.
+- **📝 Title Generation Fix for OpenAI Compatible APIs**: Resolved an issue preventing the generation of titles, enhancing consistency and reliability when using multiple API providers.
+- **🗃️ RAG Duplicate Collection Issue**: Fixed a bug causing repeated processing of the same uploaded file. Now utilizes indexed files to prevent unnecessary duplications, optimizing resource usage.
+- **🖼️ Image Generation Enhancement**: Refactored OpenAI image generation endpoint to be asynchronous, preventing the WebUI from becoming unresponsive during processing, thus enhancing user experience.
+- **🔓 Downgrade Authlib**: Reverted Authlib to version 1.3.1 to address and resolve issues concerning OAuth functionality.
+
+### Changed
+
+- **🔍 Improved Message Interaction**: Enhanced the message node interface to allow for easier focus redirection with a simple click, streamlining user interaction.
+- **✨ Styling Refactor**: Updated WebUI styling for a cleaner, more modern look, enhancing user experience across the platform.
+
+## [0.3.22] - 2024-09-19
+
+### Added
+
+- **⭐ Chat Overview**: Introducing a node-based interactive messages diagram for improved visualization of conversation flows.
+- **🔗 Multiple Vector DB Support**: Now supports multiple vector databases, including the newly added Milvus support. Community contributions for additional database support are highly encouraged!
+- **📡 Experimental Non-Stream Chat Completion**: Experimental feature allowing the use of OpenAI o1 models, which do not support streaming, ensuring more versatile model deployment.
+- **🔍 Experimental Colbert-AI Reranker Integration**: Added support for "jinaai/jina-colbert-v2" as a reranker, enhancing search relevance and accuracy. Note: it may not function at all on low-spec computers.
+- **🕸️ ENABLE_WEBSOCKET_SUPPORT**: Added environment variable for instances to ignore websocket upgrades, stabilizing connections on platforms with websocket issues.
+- **🔊 Azure Speech Service Integration**: Added support for Azure Speech services for Text-to-Speech (TTS).
+- **🎚️ Customizable Playback Speed**: Playback speed control is now available in Call mode settings, allowing users to adjust audio playback speed to their preferences.
+- **🧠 Enhanced Error Messaging**: System now displays helpful error messages directly to users during chat completion issues.
+- **📂 Save Model as Transparent PNG**: Model profile images are now saved as PNGs, supporting transparency and improving visual integration.
+- **📱 iPhone Compatibility Adjustments**: Added padding to accommodate the iPhone navigation bar, improving UI display on these devices.
+- **🔗 Secure Response Headers**: Implemented security response headers, bolstering web application security.
+- **🔧 Enhanced AUTOMATIC1111 Settings**: Users can now configure 'CFG Scale', 'Sampler', and 'Scheduler' parameters directly in the admin settings, enhancing workflow flexibility without source code modifications.
+- **🌍 i18n Updates**: Enhanced translations for Chinese, Ukrainian, Russian, and French, fostering a better localized experience.
+
+### Fixed
+
+- **🛠️ Chat Message Deletion**: Resolved issues with chat message deletion, ensuring a smoother user interaction and system stability.
+- **🔢 Ordered List Numbering**: Fixed the incorrect ordering in lists.
+
+### Changed
+
+- **🎨 Transparent Icon Handling**: Allowed model icons to be displayed on transparent backgrounds, improving UI aesthetics.
+- **📝 Improved RAG Template**: Enhanced Retrieval-Augmented Generation template, optimizing context handling and error checking for more precise operation.
+
+## [0.3.21] - 2024-09-08
+
+### Added
+
+- **📊 Document Count Display**: Now displays the total number of documents directly within the dashboard.
+- **🚀 Ollama Embed API Endpoint**: Enabled /api/embed endpoint proxy support.
+
+### Fixed
+
+- **🐳 Docker Launch Issue**: Resolved the problem preventing Open-WebUI from launching correctly when using Docker.
+
+### Changed
+
+- **🔍 Enhanced Search Prompts**: Improved the search query generation prompts for better accuracy and user interaction, enhancing the overall search experience.
+
+## [0.3.20] - 2024-09-07
+
+### Added
+
+- **🌐 Translation Update**: Updated Catalan translations to improve user experience for Catalan speakers.
+
+### Fixed
+
+- **📄 PDF Download**: Resolved a configuration issue with fonts directory, ensuring PDFs are now downloaded with the correct formatting.
+- **🛠️ Installation of Tools & Functions Requirements**: Fixed a bug where necessary requirements for tools and functions were not properly installing.
+- **🔗 Inline Image Link Rendering**: Enabled rendering of images directly from links in chat.
+- **📞 Post-Call User Interface Cleanup**: Adjusted UI behavior to automatically close chat controls after a voice call ends, reducing screen clutter.
+- **🎙️ Microphone Deactivation Post-Call**: Addressed an issue where the microphone remained active after calls.
+- **✍️ Markdown Spacing Correction**: Corrected spacing in Markdown rendering, ensuring text appears neatly and as expected.
+- **🔄 Message Re-rendering**: Fixed an issue causing all response messages to re-render with each new message, now improving chat performance.
+
+### Changed
+
+- **🌐 Refined Web Search Integration**: Deprecated the Search Query Generation Prompt threshold; introduced a toggle button for "Enable Web Search Query Generation" allowing users to opt-in to using web search more judiciously.
+- **📝 Default Prompt Templates Update**: Emptied environment variable templates for search and title generation now default to the Open WebUI default prompt templates, simplifying configuration efforts.
+
+## [0.3.19] - 2024-09-05
+
+### Added
+
+- **🌐 Translation Update**: Improved Chinese translations.
+
+### Fixed
+
+- **📂 DATA_DIR Overriding**: Fixed an issue to avoid overriding DATA_DIR, preventing errors when directories are set identically, ensuring smoother operation and data management.
+- **🛠️ Frontmatter Extraction**: Fixed the extraction process for frontmatter in tools and functions.
+
+### Changed
+
+- **🎨 UI Styling**: Refined the user interface styling for enhanced visual coherence and user experience.
+
+## [0.3.18] - 2024-09-04
+
+### Added
+
+- **🛠️ Direct Database Execution for Tools & Functions**: Enhanced the execution of Python files for tools and functions, now directly loading from the database for a more streamlined backend process.
+
+### Fixed
+
+- **🔄 Automatic Rewrite of Import Statements in Tools & Functions**: Tool and function scripts that import 'utils', 'apps', 'main', 'config' will now automatically rename these with 'open_webui.', ensuring compatibility and consistency across different modules.
+- **🎨 Styling Adjustments**: Minor fixes in the visual styling to improve user experience and interface consistency.
+
+## [0.3.17] - 2024-09-04
+
+### Added
+
+- **🔄 Import/Export Configuration**: Users can now import and export webui configurations from admin settings > Database, simplifying setup replication across systems.
+- **🌍 Web Search via URL Parameter**: Added support for activating web search directly through URL by setting 'web-search=true'.
+- **🌐 SearchApi Integration**: Added support for SearchApi as an alternative web search provider, enhancing search capabilities within the platform.
+- **🔍 Literal Type Support in Tools**: Tools now support the Literal type.
+- **🌍 Updated Translations**: Improved translations for Chinese, Ukrainian, and Catalan.
+
+### Fixed
+
+- **🔧 Pip Install Issue**: Resolved the issue where pip install failed due to missing 'alembic.ini', ensuring smoother installation processes.
+- **🌃 Automatic Theme Update**: Fixed an issue where the color theme did not update dynamically with system changes.
+- **🛠️ User Agent in ComfyUI**: Added default headers in ComfyUI to fix access issues, improving reliability in network communications.
+- **🔄 Missing Chat Completion Response Headers**: Ensured proper return of proxied response headers during chat completion, improving API reliability.
+- **🔗 Websocket Connection Prioritization**: Modified socket.io configuration to prefer websockets and more reliably fallback to polling, enhancing connection stability.
+- **🎭 Accessibility Enhancements**: Added missing ARIA labels for buttons, improving accessibility for visually impaired users.
+- **⚖️ Advanced Parameter**: Fixed an issue ensuring that advanced parameters are correctly applied in all scenarios, ensuring consistent behavior of user-defined settings.
+
+### Changed
+
+- **🔁 Namespace Reorganization**: Reorganized all Python files under the 'open_webui' namespace to streamline the project structure and improve maintainability. Tools and functions importing from 'utils' should now use 'open_webui.utils'.
+- **🚧 Dependency Updates**: Updated several backend dependencies like 'aiohttp', 'authlib', 'duckduckgo-search', 'flask-cors', and 'langchain' to their latest versions, enhancing performance and security.
+
+## [0.3.16] - 2024-08-27
+
+### Added
+
+- **🚀 Config DB Migration**: Migrated configuration handling from config.json to the database, enabling high-availability setups and load balancing across multiple Open WebUI instances.
+- **🔗 Call Mode Activation via URL**: Added a 'call=true' URL search parameter enabling direct shortcuts to activate call mode, enhancing user interaction on mobile devices.
+- **✨ TTS Content Control**: Added functionality to control how message content is segmented for Text-to-Speech (TTS) generation requests, allowing for more flexible speech output options.
+- **😄 Show Knowledge Search Status**: Enhanced model usage transparency by displaying status when working with knowledge-augmented models, helping users understand the system's state during queries.
+- **👆 Click-to-Copy for Codespan**: Enhanced interactive experience in the WebUI by allowing users to click to copy content from code spans directly.
+- **🚫 API User Blocking via Model Filter**: Introduced the ability to block API users based on customized model filters, enhancing security and control over API access.
+- **🎬 Call Overlay Styling**: Adjusted call overlay styling on large screens to not cover the entire interface, but only the chat control area, for a more unobtrusive interaction experience.
+
+### Fixed
+
+- **🔧 LaTeX Rendering Issue**: Addressed an issue that affected the correct rendering of LaTeX.
+- **📁 File Leak Prevention**: Resolved the issue of uploaded files mistakenly being accessible across user chats.
+- **🔧 Pipe Functions with '**files**' Param**: Fixed issues with '**files**' parameter not functioning correctly in pipe functions.
+- **📝 Markdown Processing for RAG**: Fixed issues with processing Markdown in files.
+- **🚫 Duplicate System Prompts**: Fixed bugs causing system prompts to duplicate.
+
+### Changed
+
+- **🔋 Wakelock Permission**: Optimized the activation of wakelock to only engage during call mode, conserving device resources and improving battery performance during idle periods.
+- **🔍 Content-Type for Ollama Chats**: Added 'application/x-ndjson' content-type to '/api/chat' endpoint responses to match raw Ollama responses.
+- **✋ Disable Signups Conditionally**: Implemented conditional logic to disable sign-ups when 'ENABLE_LOGIN_FORM' is set to false.
+
+## [0.3.15] - 2024-08-21
+
+### Added
+
+- **🔗 Temporary Chat Activation**: Integrated a new URL parameter 'temporary-chat=true' to enable temporary chat sessions directly through the URL.
+- **🌄 ComfyUI Seed Node Support**: Introduced seed node support in ComfyUI for image generation, allowing users to specify node IDs for randomized seed assignment.
+
+### Fixed
+
+- **🛠️ Tools and Functions**: Resolved a critical issue where Tools and Functions were not properly functioning, restoring full capability and reliability to these essential features.
+- **🔘 Chat Action Button in Many Model Chat**: Fixed the malfunctioning of chat action buttons in many model chat environments, ensuring a smoother and more responsive user interaction.
+- **⏪ Many Model Chat Compatibility**: Restored backward compatibility for many model chats.
+
+## [0.3.14] - 2024-08-21
+
+### Added
+
+- **🛠️ Custom ComfyUI Workflow**: Deprecating several older environment variables, this enhancement introduces a new, customizable workflow for a more tailored user experience.
+- **🔀 Merge Responses in Many Model Chat**: Enhances the dialogue by merging responses from multiple models into a single, coherent reply, improving the interaction quality in many model chats.
+- **✅ Multiple Instances of Same Model in Chats**: Enhanced many model chat to support adding multiple instances of the same model.
+- **🔧 Quick Actions in Model Workspace**: Enhanced Shift key quick actions for hiding/unhiding and deleting models, facilitating a smoother workflow.
+- **🗨️ Markdown Rendering in User Messages**: User messages are now rendered in Markdown, enhancing readability and interaction.
+- **💬 Temporary Chat Feature**: Introduced a temporary chat feature, deprecating the old chat history setting to enhance user interaction flexibility.
+- **🖋️ User Message Editing**: Enhanced the user chat editing feature to allow saving changes without sending, providing more flexibility in message management.
+- **🛡️ Security Enhancements**: Various security improvements implemented across the platform to ensure safer user experiences.
+- **🌍 Updated Translations**: Enhanced translations for Chinese, Ukrainian, and Bahasa Malaysia, improving localization and user comprehension.
+
+### Fixed
+
+- **📑 Mermaid Rendering Issue**: Addressed issues with Mermaid chart rendering to ensure clean and clear visual data representation.
+- **🎭 PWA Icon Maskability**: Fixed the Progressive Web App icon to be maskable, ensuring proper display on various device home screens.
+- **🔀 Cloned Model Chat Freezing Issue**: Fixed a bug where cloning many model chats would cause freezing, enhancing stability and responsiveness.
+- **🔍 Generic Error Handling and Refinements**: Various minor fixes and refinements to address previously untracked issues, ensuring smoother operations.
+
+### Changed
+
+- **🖼️ Image Generation Refactor**: Overhauled image generation processes for improved efficiency and quality.
+- **🔨 Refactor Tool and Function Calling**: Refactored tool and function calling mechanisms for improved clarity and maintainability.
+- **🌐 Backend Library Updates**: Updated critical backend libraries including SQLAlchemy, uvicorn[standard], faster-whisper, bcrypt, and boto3 for enhanced performance and security.
+
+### Removed
+
+- **🚫 Deprecated ComfyUI Environment Variables**: Removed several outdated environment variables related to ComfyUI settings, simplifying configuration management.
+
+## [0.3.13] - 2024-08-14
+
+### Added
+
+- **🎨 Enhanced Markdown Rendering**: Significant improvements in rendering markdown, ensuring smooth and reliable display of LaTeX and Mermaid charts, enhancing user experience with more robust visual content.
+- **🔄 Auto-Install Tools & Functions Python Dependencies**: For 'Tools' and 'Functions', Open WebUI now automatically install extra python requirements specified in the frontmatter, streamlining setup processes and customization.
+- **🌀 OAuth Email Claim Customization**: Introduced an 'OAUTH_EMAIL_CLAIM' variable to allow customization of the default "email" claim within OAuth configurations, providing greater flexibility in authentication processes.
+- **📶 Websocket Reconnection**: Enhanced reliability with the capability to automatically reconnect when a websocket is closed, ensuring consistent and stable communication.
+- **🤳 Haptic Feedback on Support Devices**: Android devices now support haptic feedback for an immersive tactile experience during certain interactions.
+
+### Fixed
+
+- **🛠️ ComfyUI Performance Improvement**: Addressed an issue causing FastAPI to stall when ComfyUI image generation was active; now runs in a separate thread to prevent UI unresponsiveness.
+- **🔀 Session Handling**: Fixed an issue mandating session_id on client-side to ensure smoother session management and transitions.
+- **🖋️ Minor Bug Fixes and Format Corrections**: Various minor fixes including typo corrections, backend formatting improvements, and test amendments enhancing overall system stability and performance.
+
+### Changed
+
+- **🚀 Migration to SvelteKit 2**: Upgraded the underlying framework to SvelteKit version 2, offering enhanced speed, better code structure, and improved deployment capabilities.
+- **🧹 General Cleanup and Refactoring**: Performed broad cleanup and refactoring across the platform, improving code efficiency and maintaining high standards of code health.
+- **🚧 Integration Testing Improvements**: Modified how Cypress integration tests detect chat messages and updated sharing tests for better reliability and accuracy.
+- **📁 Standardized '.safetensors' File Extension**: Renamed the '.sft' file extension to '.safetensors' for ComfyUI workflows, standardizing file formats across the platform.
+
+### Removed
+
+- **🗑️ Deprecated Frontend Functions**: Removed frontend functions that were migrated to backend to declutter the codebase and reduce redundancy.
+
+## [0.3.12] - 2024-08-07
+
+### Added
+
+- **🔄 Sidebar Infinite Scroll**: Added an infinite scroll feature in the sidebar for more efficient chat navigation, reducing load times and enhancing user experience.
+- **🚀 Enhanced Markdown Rendering**: Support for rendering all code blocks and making images clickable for preview; codespan styling is also enhanced to improve readability and user interaction.
+- **🔒 Admin Shared Chat Visibility**: Admins no longer have default visibility over shared chats when ENABLE_ADMIN_CHAT_ACCESS is set to false, tightening security and privacy settings for users.
+- **🌍 Language Updates**: Added Malay (Bahasa Malaysia) translation and updated Catalan and Traditional Chinese translations to improve accessibility for more users.
+
+### Fixed
+
+- **📊 Markdown Rendering Issues**: Resolved issues with markdown rendering to ensure consistent and correct display across components.
+- **🛠️ Styling Issues**: Multiple fixes applied to styling throughout the application, improving the overall visual experience and interface consistency.
+- **🗃️ Modal Handling**: Fixed an issue where modals were not closing correctly in various model chat scenarios, enhancing usability and interface reliability.
+- **📄 Missing OpenAI Usage Information**: Resolved issues where usage statistics for OpenAI services were not being correctly displayed, ensuring users have access to crucial data for managing and monitoring their API consumption.
+- **🔧 Non-Streaming Support for Functions Plugin**: Fixed a functionality issue with the Functions plugin where non-streaming operations were not functioning as intended, restoring full capabilities for async and sync integration within the platform.
+- **🔄 Environment Variable Type Correction (COMFYUI_FLUX_FP8_CLIP)**: Corrected the data type of the 'COMFYUI_FLUX_FP8_CLIP' environment variable from string to boolean, ensuring environment settings apply correctly and enhance configuration management.
+
+### Changed
+
+- **🔧 Backend Dependency Updates**: Updated several backend dependencies such as boto3, pypdf, python-pptx, validators, and black, ensuring up-to-date security and performance optimizations.
+
+## [0.3.11] - 2024-08-02
+
+### Added
+
+- **📊 Model Information Display**: Added visuals for model selection, including images next to model names for more intuitive navigation.
+- **🗣 ElevenLabs Voice Adaptations**: Voice enhancements including support for ElevenLabs voice ID by name for personalized vocal interactions.
+- **⌨️ Arrow Keys Model Selection**: Users can now use arrow keys for quicker model selection, enhancing accessibility.
+- **🔍 Fuzzy Search in Model Selector**: Enhanced model selector with fuzzy search to locate models swiftly, including descriptions.
+- **🕹️ ComfyUI Flux Image Generation**: Added support for the new Flux image gen model; introduces environment controls like weight precision and CLIP model options in Settings.
+- **💾 Display File Size for Uploads**: Enhanced file interface now displays file size, preparing for upcoming upload restrictions.
+- **🎚️ Advanced Params "Min P"**: Added 'Min P' parameter in the advanced settings for customized model precision control.
+- **🔒 Enhanced OAuth**: Introduced custom redirect URI support for OAuth behind reverse proxies, enabling safer authentication processes.
+- **🖥 Enhanced Latex Rendering**: Adjustments made to latex rendering processes, now accurately detecting and presenting latex inputs from text.
+- **🌐 Internationalization**: Enhanced with new Romanian and updated Vietnamese and Ukrainian translations, helping broaden accessibility for international users.
+
+### Fixed
+
+- **🔧 Tags Handling in Document Upload**: Tags are now properly sent to the upload document handler, resolving issues with missing metadata.
+- **🖥️ Sensitive Input Fields**: Corrected browser misinterpretation of secure input fields, preventing misclassification as password fields.
+- **📂 Static Path Resolution in PDF Generation**: Fixed static paths that adjust dynamically to prevent issues across various environments.
+
+### Changed
+
+- **🎨 UI/UX Styling Enhancements**: Multiple minor styling updates for a cleaner and more intuitive user interface.
+- **🚧 Refactoring Various Components**: Numerous refactoring changes across styling, file handling, and function simplifications for clarity and performance.
+- **🎛️ User Valves Management**: Moved user valves from settings to direct chat controls for more user-friendly access during interactions.
+
+### Removed
+
+- **⚙️ Health Check Logging**: Removed verbose logging from the health checking processes to declutter logs and improve backend performance.
+
+## [0.3.10] - 2024-07-17
+
+### Fixed
+
+- **🔄 Improved File Upload**: Addressed the issue where file uploads lacked animation.
+- **💬 Chat Continuity**: Fixed a problem where existing chats were not functioning properly in some instances.
+- **🗂️ Chat File Reset**: Resolved the issue of chat files not resetting for new conversations, now ensuring a clean slate for each chat session.
+- **📁 Document Workspace Uploads**: Corrected the handling of document uploads in the workspace using the Files API.
+
+## [0.3.9] - 2024-07-17
+
+### Added
+
+- **📁 Files Chat Controls**: We've reverted to the old file handling behavior where uploaded files are always included. You can now manage files directly within the chat controls section, giving you the ability to remove files as needed.
+- **🔧 "Action" Function Support**: Introducing a new "Action" function to write custom buttons to the message toolbar. This feature enables more interactive messaging, with documentation coming soon.
+- **📜 Citations Handling**: For newly uploaded files in documents workspace, citations will now display the actual filename. Additionally, you can click on these filenames to open the file in a new tab for easier access.
+- **🛠️ Event Emitter and Call Updates**: Enhanced 'event_emitter' to allow message replacement and 'event_call' to support text input for Tools and Functions. Detailed documentation will be provided shortly.
+- **🎨 Styling Refactor**: Various styling updates for a cleaner and more cohesive user interface.
+- **🌐 Enhanced Translations**: Improved translations for Catalan, Ukrainian, and Brazilian Portuguese.
+
+### Fixed
+
+- **🔧 Chat Controls Priority**: Resolved an issue where Chat Controls values were being overridden by model information parameters. The priority is now Chat Controls, followed by Global Settings, then Model Settings.
+- **🪲 Debug Logs**: Fixed an issue where debug logs were not being logged properly.
+- **🔑 Automatic1111 Auth Key**: The auth key for Automatic1111 is no longer required.
+- **📝 Title Generation**: Ensured that the title generation runs only once, even when multiple models are in a chat.
+- **✅ Boolean Values in Params**: Added support for boolean values in parameters.
+- **🖼️ Files Overlay Styling**: Fixed the styling issue with the files overlay.
+
+### Changed
+
+- **⬆️ Dependency Updates**
+  - Upgraded 'pydantic' from version 2.7.1 to 2.8.2.
+  - Upgraded 'sqlalchemy' from version 2.0.30 to 2.0.31.
+  - Upgraded 'unstructured' from version 0.14.9 to 0.14.10.
+  - Upgraded 'chromadb' from version 0.5.3 to 0.5.4.
+
+## [0.3.8] - 2024-07-09
+
+### Added
+
+- **💬 Chat Controls**: Easily adjust parameters for each chat session, offering more precise control over your interactions.
+- **📌 Pinned Chats**: Support for pinned chats, allowing you to keep important conversations easily accessible.
+- **📄 Apache Tika Integration**: Added support for using Apache Tika as a document loader, enhancing document processing capabilities.
+- **🛠️ Custom Environment for OpenID Claims**: Allows setting custom claims for OpenID, providing more flexibility in user authentication.
+- **🔧 Enhanced Tools & Functions API**: Introduced 'event_emitter' and 'event_call', now you can also add citations for better documentation and tracking. Detailed documentation will be provided on our documentation website.
+- **↔️ Sideways Scrolling in Settings**: Settings tabs container now supports horizontal scrolling for easier navigation.
+- **🌑 Darker OLED Theme**: Includes a new, darker OLED theme and improved styling for the light theme, enhancing visual appeal.
+- **🌐 Language Updates**: Updated translations for Indonesian, German, French, and Catalan languages, expanding accessibility.
+
+### Fixed
+
+- **⏰ OpenAI Streaming Timeout**: Resolved issues with OpenAI streaming response using the 'AIOHTTP_CLIENT_TIMEOUT' setting, ensuring reliable performance.
+- **💡 User Valves**: Fixed malfunctioning user valves, ensuring proper functionality.
+- **🔄 Collapsible Components**: Addressed issues with collapsible components not working, restoring expected behavior.
+
+### Changed
+
+- **🗃️ Database Backend**: Switched from Peewee to SQLAlchemy for improved concurrency support, enhancing database performance.
+- **⬆️ ChromaDB Update**: Upgraded to version 0.5.3. Ensure your remote ChromaDB instance matches this version.
+- **🔤 Primary Font Styling**: Updated primary font to Archivo for better visual consistency.
+- **🔄 Font Change for Windows**: Replaced Arimo with Inter font for Windows users, improving readability.
+- **🚀 Lazy Loading**: Implemented lazy loading for 'faster_whisper' and 'sentence_transformers' to reduce startup memory usage.
+- **📋 Task Generation Payload**: Task generations now include only the "task" field in the body instead of "title".
+
+## [0.3.7] - 2024-06-29
+
+### Added
+
+- **🌐 Enhanced Internationalization (i18n)**: Newly introduced Indonesian translation, and updated translations for Turkish, Chinese, and Catalan languages to improve user accessibility.
+
+### Fixed
+
+- **🕵️‍♂️ Browser Language Detection**: Corrected the issue where the application was not properly detecting and adapting to the browser's language settings.
+- **🔐 OIDC Admin Role Assignment**: Fixed a bug where the admin role was not being assigned to the first user who signed up via OpenID Connect (OIDC).
+- **💬 Chat/Completions Endpoint**: Resolved an issue where the chat/completions endpoint was non-functional when the stream option was set to False.
+- **🚫 'WEBUI_AUTH' Configuration**: Addressed the problem where setting 'WEBUI_AUTH' to False was not being applied correctly.
+
+### Changed
+
+- **📦 Dependency Update**: Upgraded 'authlib' from version 1.3.0 to 1.3.1 to ensure better security and performance enhancements.
+
+## [0.3.6] - 2024-06-27
+
+### Added
+
+- **✨ "Functions" Feature**: You can now utilize "Functions" like filters (middleware) and pipe (model) functions directly within the WebUI. While largely compatible with Pipelines, these native functions can be executed easily within Open WebUI. Example use cases for filter functions include usage monitoring, real-time translation, moderation, and automemory. For pipe functions, the scope ranges from Cohere and Anthropic integration directly within Open WebUI, enabling "Valves" for per-user OpenAI API key usage, and much more. If you encounter issues, SAFE_MODE has been introduced.
+- **📁 Files API**: Compatible with OpenAI, this feature allows for custom Retrieval-Augmented Generation (RAG) in conjunction with the Filter Function. More examples will be shared on our community platform and official documentation website.
+- **🛠️ Tool Enhancements**: Tools now support citations and "Valves". Documentation will be available shortly.
+- **🔗 Iframe Support via Files API**: Enables rendering HTML directly into your chat interface using functions and tools. Use cases include playing games like DOOM and Snake, displaying a weather applet, and implementing Anthropic "artifacts"-like features. Stay tuned for updates on our community platform and documentation.
+- **🔒 Experimental OAuth Support**: New experimental OAuth support. Check our documentation for more details.
+- **🖼️ Custom Background Support**: Set a custom background from Settings > Interface to personalize your experience.
+- **🔑 AUTOMATIC1111_API_AUTH Support**: Enhanced security for the AUTOMATIC1111 API.
+- **🎨 Code Highlight Optimization**: Improved code highlighting features.
+- **🎙️ Voice Interruption Feature**: Reintroduced and now toggleable from Settings > Interface.
+- **💤 Wakelock API**: Now in use to prevent screen dimming during important tasks.
+- **🔐 API Key Privacy**: All API keys are now hidden by default for better security.
+- **🔍 New Web Search Provider**: Added jina_search as a new option.
+- **🌐 Enhanced Internationalization (i18n)**: Improved Korean translation and updated Chinese and Ukrainian translations.
+
+### Fixed
+
+- **🔧 Conversation Mode Issue**: Fixed the issue where Conversation Mode remained active after being removed from settings.
+- **📏 Scroll Button Obstruction**: Resolved the issue where the scrollToBottom button container obstructed clicks on buttons beneath it.
+
+### Changed
+
+- **⏲️ AIOHTTP_CLIENT_TIMEOUT**: Now set to 'None' by default for improved configuration flexibility.
+- **📞 Voice Call Enhancements**: Improved by skipping code blocks and expressions during calls.
+- **🚫 Error Message Handling**: Disabled the continuation of operations with error messages.
+- **🗂️ Playground Relocation**: Moved the Playground from the workspace to the user menu for better user experience.
+
+## [0.3.5] - 2024-06-16
+
+### Added
+
+- **📞 Enhanced Voice Call**: Text-to-speech (TTS) callback now operates in real-time for each sentence, reducing latency by not waiting for full completion.
+- **👆 Tap to Interrupt**: During a call, you can now stop the assistant from speaking by simply tapping, instead of using voice. This resolves the issue of the speaker's voice being mistakenly registered as input.
+- **😊 Emoji Call**: Toggle this feature on from the Settings > Interface, allowing LLMs to express emotions using emojis during voice calls for a more dynamic interaction.
+- **🖱️ Quick Archive/Delete**: Use the Shift key + mouseover on the chat list to swiftly archive or delete items.
+- **📝 Markdown Support in Model Descriptions**: You can now format model descriptions with markdown, enabling bold text, links, etc.
+- **🧠 Editable Memories**: Adds the capability to modify memories.
+- **📋 Admin Panel Sorting**: Introduces the ability to sort users/chats within the admin panel.
+- **🌑 Dark Mode for Quick Selectors**: Dark mode now available for chat quick selectors (prompts, models, documents).
+- **🔧 Advanced Parameters**: Adds 'num_keep' and 'num_batch' to advanced parameters for customization.
+- **📅 Dynamic System Prompts**: New variables '{{CURRENT_DATETIME}}', '{{CURRENT_TIME}}', '{{USER_LOCATION}}' added for system prompts. Ensure '{{USER_LOCATION}}' is toggled on from Settings > Interface.
+- **🌐 Tavily Web Search**: Includes Tavily as a web search provider option.
+- **🖊️ Federated Auth Usernames**: Ability to set user names for federated authentication.
+- **🔗 Auto Clean URLs**: When adding connection URLs, trailing slashes are now automatically removed.
+- **🌐 Enhanced Translations**: Improved Chinese and Swedish translations.
+
+### Fixed
+
+- **⏳ AIOHTTP_CLIENT_TIMEOUT**: Introduced a new environment variable 'AIOHTTP_CLIENT_TIMEOUT' for requests to Ollama lasting longer than 5 minutes. Default is 300 seconds; set to blank ('') for no timeout.
+- **❌ Message Delete Freeze**: Resolved an issue where message deletion would sometimes cause the web UI to freeze.
+
+## [0.3.4] - 2024-06-12
+
+### Fixed
+
+- **🔒 Mixed Content with HTTPS Issue**: Resolved a problem where mixed content (HTTP and HTTPS) was causing security warnings and blocking resources on HTTPS sites.
+- **🔍 Web Search Issue**: Addressed the problem where web search functionality was not working correctly. The 'ENABLE_RAG_LOCAL_WEB_FETCH' option has been reintroduced to restore proper web searching capabilities.
+- **💾 RAG Template Not Being Saved**: Fixed an issue where the RAG template was not being saved correctly, ensuring your custom templates are now preserved as expected.
+
+## [0.3.3] - 2024-06-12
+
+### Added
+
+- **🛠️ Native Python Function Calling**: Introducing native Python function calling within Open WebUI. We’ve also included a built-in code editor to seamlessly develop and integrate function code within the 'Tools' workspace. With this, you can significantly enhance your LLM’s capabilities by creating custom RAG pipelines, web search tools, and even agent-like features such as sending Discord messages.
+- **🌐 DuckDuckGo Integration**: Added DuckDuckGo as a web search provider, giving you more search options.
+- **🌏 Enhanced Translations**: Improved translations for Vietnamese and Chinese languages, making the interface more accessible.
+
+### Fixed
+
+- **🔗 Web Search URL Error Handling**: Fixed the issue where a single URL error would disrupt the data loading process in Web Search mode. Now, such errors will be handled gracefully to ensure uninterrupted data loading.
+- **🖥️ Frontend Responsiveness**: Resolved the problem where the frontend would stop responding if the backend encounters an error while downloading a model. Improved error handling to maintain frontend stability.
+- **🔧 Dependency Issues in pip**: Fixed issues related to pip installations, ensuring all dependencies are correctly managed to prevent installation errors.
+
+## [0.3.2] - 2024-06-10
+
+### Added
+
+- **🔍 Web Search Query Status**: The web search query will now persist in the results section to aid in easier debugging and tracking of search queries.
+- **🌐 New Web Search Provider**: We have added Serply as a new option for web search providers, giving you more choices for your search needs.
+- **🌏 Improved Translations**: We've enhanced translations for Chinese and Portuguese.
+
+### Fixed
+
+- **🎤 Audio File Upload Issue**: The bug that prevented audio files from being uploaded in chat input has been fixed, ensuring smooth communication.
+- **💬 Message Input Handling**: Improved the handling of message inputs by instantly clearing images and text after sending, along with immediate visual indications when a response message is loading, enhancing user feedback.
+- **⚙️ Parameter Registration and Validation**: Fixed the issue where parameters were not registering in certain cases and addressed the problem where users were unable to save due to invalid input errors.
+
+## [0.3.1] - 2024-06-09
+
+### Fixed
+
+- **💬 Chat Functionality**: Resolved the issue where chat functionality was not working for specific models.
+
+## [0.3.0] - 2024-06-09
+
+### Added
+
+- **📚 Knowledge Support for Models**: Attach documents directly to models from the models workspace, enhancing the information available to each model.
+- **🎙️ Hands-Free Voice Call Feature**: Initiate voice calls without needing to use your hands, making interactions more seamless.
+- **📹 Video Call Feature**: Enable video calls with supported vision models like Llava and GPT-4o, adding a visual dimension to your communications.
+- **🎛️ Enhanced UI for Voice Recording**: Improved user interface for the voice recording feature, making it more intuitive and user-friendly.
+- **🌐 External STT Support**: Now support for external Speech-To-Text services, providing more flexibility in choosing your STT provider.
+- **⚙️ Unified Settings**: Consolidated settings including document settings under a new admin settings section for easier management.
+- **🌑 Dark Mode Splash Screen**: A new splash screen for dark mode, ensuring a consistent and visually appealing experience for dark mode users.
+- **📥 Upload Pipeline**: Directly upload pipelines from the admin settings > pipelines section, streamlining the pipeline management process.
+- **🌍 Improved Language Support**: Enhanced support for Chinese and Ukrainian languages, better catering to a global user base.
+
+### Fixed
+
+- **🛠️ Playground Issue**: Fixed the playground not functioning properly, ensuring a smoother user experience.
+- **🔥 Temperature Parameter Issue**: Corrected the issue where the temperature value '0' was not being passed correctly.
+- **📝 Prompt Input Clearing**: Resolved prompt input textarea not being cleared right away, ensuring a clean slate for new inputs.
+- **✨ Various UI Styling Issues**: Fixed numerous user interface styling problems for a more cohesive look.
+- **👥 Active Users Display**: Fixed active users showing active sessions instead of actual users, now reflecting accurate user activity.
+- **🌐 Community Platform Compatibility**: The Community Platform is back online and fully compatible with Open WebUI.
+
+### Changed
+
+- **📝 RAG Implementation**: Updated the RAG (Retrieval-Augmented Generation) implementation to use a system prompt for context, instead of overriding the user's prompt.
+- **🔄 Settings Relocation**: Moved Models, Connections, Audio, and Images settings to the admin settings for better organization.
+- **✍️ Improved Title Generation**: Enhanced the default prompt for title generation, yielding better results.
+- **🔧 Backend Task Management**: Tasks like title generation and search query generation are now managed on the backend side and controlled only by the admin.
+- **🔍 Editable Search Query Prompt**: You can now edit the search query generation prompt, offering more control over how queries are generated.
+- **📏 Prompt Length Threshold**: Set the prompt length threshold for search query generation from the admin settings, giving more customization options.
+- **📣 Settings Consolidation**: Merged the Banners admin setting with the Interface admin setting for a more streamlined settings area.
+
+## [0.2.5] - 2024-06-05
+
+### Added
+
+- **👥 Active Users Indicator**: Now you can see how many people are currently active and what they are running. This helps you gauge when performance might slow down due to a high number of users.
+- **🗂️ Create Ollama Modelfile**: The option to create a modelfile for Ollama has been reintroduced in the Settings > Models section, making it easier to manage your models.
+- **⚙️ Default Model Setting**: Added an option to set the default model from Settings > Interface. This feature is now easily accessible, especially convenient for mobile users as it was previously hidden.
+- **🌐 Enhanced Translations**: We've improved the Chinese translations and added support for Turkmen and Norwegian languages to make the interface more accessible globally.
+
+### Fixed
+
+- **📱 Mobile View Improvements**: The UI now uses dvh (dynamic viewport height) instead of vh (viewport height), providing a better and more responsive experience for mobile users.
+
+## [0.2.4] - 2024-06-03
+
+### Added
+
+- **👤 Improved Account Pending Page**: The account pending page now displays admin details by default to avoid confusion. You can disable this feature in the admin settings if needed.
+- **🌐 HTTP Proxy Support**: We have enabled the use of the 'http_proxy' environment variable in OpenAI and Ollama API calls, making it easier to configure network settings.
+- **❓ Quick Access to Documentation**: You can now easily access Open WebUI documents via a question mark button located at the bottom right corner of the screen (available on larger screens like PCs).
+- **🌍 Enhanced Translation**: Improvements have been made to translations.
+
+### Fixed
+
+- **🔍 SearxNG Web Search**: Fixed the issue where the SearxNG web search functionality was not working properly.
+
+## [0.2.3] - 2024-06-03
+
+### Added
+
+- **📁 Export Chat as JSON**: You can now export individual chats as JSON files from the navbar menu by navigating to 'Download > Export Chat'. This makes sharing specific conversations easier.
+- **✏️ Edit Titles with Double Click**: Double-click on titles to rename them quickly and efficiently.
+- **🧩 Batch Multiple Embeddings**: Introduced 'RAG_EMBEDDING_OPENAI_BATCH_SIZE' to process multiple embeddings in a batch, enhancing performance for large datasets.
+- **🌍 Improved Translations**: Enhanced the translation quality across various languages for a better user experience.
+
+### Fixed
+
+- **🛠️ Modelfile Migration Script**: Fixed an issue where the modelfile migration script would fail if an invalid modelfile was encountered.
+- **💬 Zhuyin Input Method on Mac**: Resolved an issue where using the Zhuyin input method in the Web UI on a Mac caused text to send immediately upon pressing the enter key, leading to incorrect input.
+- **🔊 Local TTS Voice Selection**: Fixed the issue where the selected local Text-to-Speech (TTS) voice was not being displayed in settings.
+
+## [0.2.2] - 2024-06-02
+
+### Added
+
+- **🌊 Mermaid Rendering Support**: We've included support for Mermaid rendering. This allows you to create beautiful diagrams and flowcharts directly within Open WebUI.
+- **🔄 New Environment Variable 'RESET_CONFIG_ON_START'**: Introducing a new environment variable: 'RESET_CONFIG_ON_START'. Set this variable to reset your configuration settings upon starting the application, making it easier to revert to default settings.
+
+### Fixed
+
+- **🔧 Pipelines Filter Issue**: We've addressed an issue with the pipelines where filters were not functioning as expected.
+
+## [0.2.1] - 2024-06-02
+
+### Added
+
+- **🖱️ Single Model Export Button**: Easily export models with just one click using the new single model export button.
+- **🖥️ Advanced Parameters Support**: Added support for 'num_thread', 'use_mmap', and 'use_mlock' parameters for Ollama.
+- **🌐 Improved Vietnamese Translation**: Enhanced Vietnamese language support for a better user experience for our Vietnamese-speaking community.
+
+### Fixed
+
+- **🔧 OpenAI URL API Save Issue**: Corrected a problem preventing the saving of OpenAI URL API settings.
+- **🚫 Display Issue with Disabled Ollama API**: Fixed the display bug causing models to appear in settings when the Ollama API was disabled.
+
+### Changed
+
+- **💡 Versioning Update**: As a reminder from our previous update, version 0.2.y will focus primarily on bug fixes, while major updates will be designated as 0.x from now on for better version tracking.
+
+## [0.2.0] - 2024-06-01
+
+### Added
+
+- **🔧 Pipelines Support**: Open WebUI now includes a plugin framework for enhanced customization and functionality (https://github.com/open-webui/pipelines). Easily add custom logic and integrate Python libraries, from AI agents to home automation APIs.
+- **🔗 Function Calling via Pipelines**: Integrate function calling seamlessly through Pipelines.
+- **⚖️ User Rate Limiting via Pipelines**: Implement user-specific rate limits to manage API usage efficiently.
+- **📊 Usage Monitoring with Langfuse**: Track and analyze usage statistics with Langfuse integration through Pipelines.
+- **🕒 Conversation Turn Limits**: Set limits on conversation turns to manage interactions better through Pipelines.
+- **🛡️ Toxic Message Filtering**: Automatically filter out toxic messages to maintain a safe environment using Pipelines.
+- **🔍 Web Search Support**: Introducing built-in web search capabilities via RAG API, allowing users to search using SearXNG, Google Programmatic Search Engine, Brave Search, serpstack, and serper. Activate it effortlessly by adding necessary variables from Document settings > Web Params.
+- **🗂️ Models Workspace**: Create and manage model presets for both Ollama/OpenAI API. Note: The old Modelfiles workspace is deprecated.
+- **🛠️ Model Builder Feature**: Build and edit all models with persistent builder mode.
+- **🏷️ Model Tagging Support**: Organize models with tagging features in the models workspace.
+- **📋 Model Ordering Support**: Effortlessly organize models by dragging and dropping them into the desired positions within the models workspace.
+- **📈 OpenAI Generation Stats**: Access detailed generation statistics for OpenAI models.
+- **📅 System Prompt Variables**: New variables added: '{{CURRENT_DATE}}' and '{{USER_NAME}}' for dynamic prompts.
+- **📢 Global Banner Support**: Manage global banners from admin settings > banners.
+- **🗃️ Enhanced Archived Chats Modal**: Search and export archived chats easily.
+- **📂 Archive All Button**: Quickly archive all chats from settings > chats.
+- **🌐 Improved Translations**: Added and improved translations for French, Croatian, Cebuano, and Vietnamese.
+
+### Fixed
+
+- **🔍 Archived Chats Visibility**: Resolved issue with archived chats not showing in the admin panel.
+- **💬 Message Styling**: Fixed styling issues affecting message appearance.
+- **🔗 Shared Chat Responses**: Corrected the issue where shared chat response messages were not readonly.
+- **🖥️ UI Enhancement**: Fixed the scrollbar overlapping issue with the message box in the user interface.
+
+### Changed
+
+- **💾 User Settings Storage**: User settings are now saved on the backend, ensuring consistency across all devices.
+- **📡 Unified API Requests**: The API request for getting models is now unified to '/api/models' for easier usage.
+- **🔄 Versioning Update**: Our versioning will now follow the format 0.x for major updates and 0.x.y for patches.
+- **📦 Export All Chats (All Users)**: Moved this functionality to the Admin Panel settings for better organization and accessibility.
+
+### Removed
+
+- **🚫 Bundled LiteLLM Support Deprecated**: Migrate your LiteLLM config.yaml to a self-hosted LiteLLM instance. LiteLLM can still be added via OpenAI Connections. Download the LiteLLM config.yaml from admin settings > database > export LiteLLM config.yaml.
+
+## [0.1.125] - 2024-05-19
+
+### Added
+
+- **🔄 Updated UI**: Chat interface revamped with chat bubbles. Easily switch back to the old style via settings > interface > chat bubble UI.
+- **📂 Enhanced Sidebar UI**: Model files, documents, prompts, and playground merged into Workspace for streamlined access.
+- **🚀 Improved Many Model Interaction**: All responses now displayed simultaneously for a smoother experience.
+- **🐍 Python Code Execution**: Execute Python code locally in the browser with libraries like 'requests', 'beautifulsoup4', 'numpy', 'pandas', 'seaborn', 'matplotlib', 'scikit-learn', 'scipy', 'regex'.
+- **🧠 Experimental Memory Feature**: Manually input personal information you want LLMs to remember via settings > personalization > memory.
+- **💾 Persistent Settings**: Settings now saved as config.json for convenience.
+- **🩺 Health Check Endpoint**: Added for Docker deployment.
+- **↕️ RTL Support**: Toggle chat direction via settings > interface > chat direction.
+- **🖥️ PowerPoint Support**: RAG pipeline now supports PowerPoint documents.
+- **🌐 Language Updates**: Ukrainian, Turkish, Arabic, Chinese, Serbian, Vietnamese updated; Punjabi added.
+
+### Changed
+
+- **👤 Shared Chat Update**: Shared chat now includes creator user information.
+
+## [0.1.124] - 2024-05-08
+
+### Added
+
+- **🖼️ Improved Chat Sidebar**: Now conveniently displays time ranges and organizes chats by today, yesterday, and more.
+- **📜 Citations in RAG Feature**: Easily track the context fed to the LLM with added citations in the RAG feature.
+- **🔒 Auth Disable Option**: Introducing the ability to disable authentication. Set 'WEBUI_AUTH' to False to disable authentication. Note: Only applicable for fresh installations without existing users.
+- **📹 Enhanced YouTube RAG Pipeline**: Now supports non-English videos for an enriched experience.
+- **🔊 Specify OpenAI TTS Models**: Customize your TTS experience by specifying OpenAI TTS models.
+- **🔧 Additional Environment Variables**: Discover more environment variables in our comprehensive documentation at Open WebUI Documentation (https://docs.openwebui.com).
+- **🌐 Language Support**: Arabic, Finnish, and Hindi added; Improved support for German, Vietnamese, and Chinese.
+
+### Fixed
+
+- **🛠️ Model Selector Styling**: Addressed styling issues for improved user experience.
+- **⚠️ Warning Messages**: Resolved backend warning messages.
+
+### Changed
+
+- **📝 Title Generation**: Limited output to 50 tokens.
+- **📦 Helm Charts**: Removed Helm charts, now available in a separate repository (https://github.com/open-webui/helm-charts).
+
+## [0.1.123] - 2024-05-02
+
+### Added
+
+- **🎨 New Landing Page Design**: Refreshed design for a more modern look and optimized use of screen space.
+- **📹 Youtube RAG Pipeline**: Introduces dedicated RAG pipeline for Youtube videos, enabling interaction with video transcriptions directly.
+- **🔧 Enhanced Admin Panel**: Streamlined user management with options to add users directly or in bulk via CSV import.
+- **👥 '@' Model Integration**: Easily switch to specific models during conversations; old collaborative chat feature phased out.
+- **🌐 Language Enhancements**: Swedish translation added, plus improvements to German, Spanish, and the addition of Doge translation.
+
+### Fixed
+
+- **🗑️ Delete Chat Shortcut**: Addressed issue where shortcut wasn't functioning.
+- **🖼️ Modal Closing Bug**: Resolved unexpected closure of modal when dragging from within.
+- **✏️ Edit Button Styling**: Fixed styling inconsistency with edit buttons.
+- **🌐 Image Generation Compatibility Issue**: Rectified image generation compatibility issue with third-party APIs.
+- **📱 iOS PWA Icon Fix**: Corrected iOS PWA home screen icon shape.
+- **🔍 Scroll Gesture Bug**: Adjusted gesture sensitivity to prevent accidental activation when scrolling through code on mobile; now requires scrolling from the leftmost side to open the sidebar.
+
+### Changed
+
+- **🔄 Unlimited Context Length**: Advanced settings now allow unlimited max context length (previously limited to 16000).
+- **👑 Super Admin Assignment**: The first signup is automatically assigned a super admin role, unchangeable by other admins.
+- **🛡️ Admin User Restrictions**: User action buttons from the admin panel are now disabled for users with admin roles.
+- **🔝 Default Model Selector**: Set as default model option now exclusively available on the landing page.
+
+## [0.1.122] - 2024-04-27
+
+### Added
+
+- **🌟 Enhanced RAG Pipeline**: Now with hybrid searching via 'BM25', reranking powered by 'CrossEncoder', and configurable relevance score thresholds.
+- **🛢️ External Database Support**: Seamlessly connect to custom SQLite or Postgres databases using the 'DATABASE_URL' environment variable.
+- **🌐 Remote ChromaDB Support**: Introducing the capability to connect to remote ChromaDB servers.
+- **👨‍💼 Improved Admin Panel**: Admins can now conveniently check users' chat lists and last active status directly from the admin panel.
+- **🎨 Splash Screen**: Introducing a loading splash screen for a smoother user experience.
+- **🌍 Language Support Expansion**: Added support for Bangla (bn-BD), along with enhancements to Chinese, Spanish, and Ukrainian translations.
+- **💻 Improved LaTeX Rendering Performance**: Enjoy faster rendering times for LaTeX equations.
+- **🔧 More Environment Variables**: Explore additional environment variables in our documentation (https://docs.openwebui.com), including the 'ENABLE_LITELLM' option to manage memory usage.
+
+### Fixed
+
+- **🔧 Ollama Compatibility**: Resolved errors occurring when Ollama server version isn't an integer, such as SHA builds or RCs.
+- **🐛 Various OpenAI API Issues**: Addressed several issues related to the OpenAI API.
+- **🛑 Stop Sequence Issue**: Fixed the problem where the stop sequence with a backslash '\' was not functioning.
+- **🔤 Font Fallback**: Corrected font fallback issue.
+
+### Changed
+
+- **⌨️ Prompt Input Behavior on Mobile**: Enter key prompt submission disabled on mobile devices for improved user experience.
+
+## [0.1.121] - 2024-04-24
+
+### Fixed
+
+- **🔧 Translation Issues**: Addressed various translation discrepancies.
+- **🔒 LiteLLM Security Fix**: Updated LiteLLM version to resolve a security vulnerability.
+- **🖥️ HTML Tag Display**: Rectified the issue where the '< br >' tag wasn't displaying correctly.
+- **🔗 WebSocket Connection**: Resolved the failure of WebSocket connection under HTTPS security for ComfyUI server.
+- **📜 FileReader Optimization**: Implemented FileReader initialization per image in multi-file drag & drop to ensure reusability.
+- **🏷️ Tag Display**: Corrected tag display inconsistencies.
+- **📦 Archived Chat Styling**: Fixed styling issues in archived chat.
+- **🔖 Safari Copy Button Bug**: Addressed the bug where the copy button failed to copy links in Safari.
+
+## [0.1.120] - 2024-04-20
+
+### Added
+
+- **📦 Archive Chat Feature**: Easily archive chats with a new sidebar button, and access archived chats via the profile button > archived chats.
+- **🔊 Configurable Text-to-Speech Endpoint**: Customize your Text-to-Speech experience with configurable OpenAI endpoints.
+- **🛠️ Improved Error Handling**: Enhanced error message handling for connection failures.
+- **⌨️ Enhanced Shortcut**: When editing messages, use ctrl/cmd+enter to save and submit, and esc to close.
+- **🌐 Language Support**: Added support for Georgian and enhanced translations for Portuguese and Vietnamese.
+
+### Fixed
+
+- **🔧 Model Selector**: Resolved issue where default model selection was not saving.
+- **🔗 Share Link Copy Button**: Fixed bug where the copy button wasn't copying links in Safari.
+- **🎨 Light Theme Styling**: Addressed styling issue with the light theme.
+
+## [0.1.119] - 2024-04-16
+
+### Added
+
+- **🌟 Enhanced RAG Embedding Support**: Ollama, and OpenAI models can now be used for RAG embedding model.
+- **🔄 Seamless Integration**: Copy 'ollama run <model name>' directly from Ollama page to easily select and pull models.
+- **🏷️ Tagging Feature**: Add tags to chats directly via the sidebar chat menu.
+- **📱 Mobile Accessibility**: Swipe left and right on mobile to effortlessly open and close the sidebar.
+- **🔍 Improved Navigation**: Admin panel now supports pagination for user list.
+- **🌍 Additional Language Support**: Added Polish language support.
+
+### Fixed
+
+- **🌍 Language Enhancements**: Vietnamese and Spanish translations have been improved.
+- **🔧 Helm Fixes**: Resolved issues with Helm trailing slash and manifest.json.
+
+### Changed
+
+- **🐳 Docker Optimization**: Updated docker image build process to utilize 'uv' for significantly faster builds compared to 'pip3'.
+
+## [0.1.118] - 2024-04-10
+
+### Added
+
+- **🦙 Ollama and CUDA Images**: Added support for ':ollama' and ':cuda' tagged images.
+- **👍 Enhanced Response Rating**: Now you can annotate your ratings for better feedback.
+- **👤 User Initials Profile Photo**: User initials are now the default profile photo.
+- **🔍 Update RAG Embedding Model**: Customize RAG embedding model directly in document settings.
+- **🌍 Additional Language Support**: Added Turkish language support.
+
+### Fixed
+
+- **🔒 Share Chat Permission**: Resolved issue with chat sharing permissions.
+- **🛠 Modal Close**: Modals can now be closed using the Esc key.
+
+### Changed
+
+- **🎨 Admin Panel Styling**: Refreshed styling for the admin panel.
+- **🐳 Docker Image Build**: Updated docker image build process for improved efficiency.
+
+## [0.1.117] - 2024-04-03
+
+### Added
+
+- 🗨️ **Local Chat Sharing**: Share chat links seamlessly between users.
+- 🔑 **API Key Generation Support**: Generate secret keys to leverage Open WebUI with OpenAI libraries.
+- 📄 **Chat Download as PDF**: Easily download chats in PDF format.
+- 📝 **Improved Logging**: Enhancements to logging functionality.
+- 📧 **Trusted Email Authentication**: Authenticate using a trusted email header.
+
+### Fixed
+
+- 🌷 **Enhanced Dutch Translation**: Improved translation for Dutch users.
+- ⚪ **White Theme Styling**: Resolved styling issue with the white theme.
+- 📜 **LaTeX Chat Screen Overflow**: Fixed screen overflow issue with LaTeX rendering.
+- 🔒 **Security Patches**: Applied necessary security patches.
+
+## [0.1.116] - 2024-03-31
+
+### Added
+
+- **🔄 Enhanced UI**: Model selector now conveniently located in the navbar, enabling seamless switching between multiple models during conversations.
+- **🔍 Improved Model Selector**: Directly pull a model from the selector/Models now display detailed information for better understanding.
+- **💬 Webhook Support**: Now compatible with Google Chat and Microsoft Teams.
+- **🌐 Localization**: Korean translation (I18n) now available.
+- **🌑 Dark Theme**: OLED dark theme introduced for reduced strain during prolonged usage.
+- **🏷️ Tag Autocomplete**: Dropdown feature added for effortless chat tagging.
+
+### Fixed
+
+- **🔽 Auto-Scrolling**: Addressed OpenAI auto-scrolling issue.
+- **🏷️ Tag Validation**: Implemented tag validation to prevent empty string tags.
+- **🚫 Model Whitelisting**: Resolved LiteLLM model whitelisting issue.
+- **✅ Spelling**: Corrected various spelling issues for improved readability.
+
+## [0.1.115] - 2024-03-24
+
+### Added
+
+- **🔍 Custom Model Selector**: Easily find and select custom models with the new search filter feature.
+- **🛑 Cancel Model Download**: Added the ability to cancel model downloads.
+- **🎨 Image Generation ComfyUI**: Image generation now supports ComfyUI.
+- **🌟 Updated Light Theme**: Updated the light theme for a fresh look.
+- **🌍 Additional Language Support**: Now supporting Bulgarian, Italian, Portuguese, Japanese, and Dutch.
+
+### Fixed
+
+- **🔧 Fixed Broken Experimental GGUF Upload**: Resolved issues with experimental GGUF upload functionality.
+
+### Changed
+
+- **🔄 Vector Storage Reset Button**: Moved the reset vector storage button to document settings.
+
+## [0.1.114] - 2024-03-20
+
+### Added
+
+- **🔗 Webhook Integration**: Now you can subscribe to new user sign-up events via webhook. Simply navigate to the admin panel > admin settings > webhook URL.
+- **🛡️ Enhanced Model Filtering**: Alongside Ollama, OpenAI proxy model whitelisting, we've added model filtering functionality for LiteLLM proxy.
+- **🌍 Expanded Language Support**: Spanish, Catalan, and Vietnamese languages are now available, with improvements made to others.
+
+### Fixed
+
+- **🔧 Input Field Spelling**: Resolved issue with spelling mistakes in input fields.
+- **🖊️ Light Mode Styling**: Fixed styling issue with light mode in document adding.
+
+### Changed
+
+- **🔄 Language Sorting**: Languages are now sorted alphabetically by their code for improved organization.
+
+## [0.1.113] - 2024-03-18
+
+### Added
+
+- 🌍 **Localization**: You can now change the UI language in Settings > General. We support Ukrainian, German, Farsi (Persian), Traditional and Simplified Chinese and French translations. You can help us to translate the UI into your language! More info in our [CONTRIBUTION.md](https://github.com/open-webui/open-webui/blob/main/docs/CONTRIBUTING.md#-translations-and-internationalization).
+- 🎨 **System-wide Theme**: Introducing a new system-wide theme for enhanced visual experience.
+
+### Fixed
+
+- 🌑 **Dark Background on Select Fields**: Improved readability by adding a dark background to select fields, addressing issues on certain browsers/devices.
+- **Multiple OPENAI_API_BASE_URLS Issue**: Resolved issue where multiple base URLs caused conflicts when one wasn't functioning.
+- **RAG Encoding Issue**: Fixed encoding problem in RAG.
+- **npm Audit Fix**: Addressed npm audit findings.
+- **Reduced Scroll Threshold**: Improved auto-scroll experience by reducing the scroll threshold from 50px to 5px.
+
+### Changed
+
+- 🔄 **Sidebar UI Update**: Updated sidebar UI to feature a chat menu dropdown, replacing two icons for improved navigation.
+
+## [0.1.112] - 2024-03-15
+
+### Fixed
+
+- 🗨️ Resolved chat malfunction after image generation.
+- 🎨 Fixed various RAG issues.
+- 🧪 Rectified experimental broken GGUF upload logic.
+
+## [0.1.111] - 2024-03-10
+
+### Added
+
+- 🛡️ **Model Whitelisting**: Admins now have the ability to whitelist models for users with the 'user' role.
+- 🔄 **Update All Models**: Added a convenient button to update all models at once.
+- 📄 **Toggle PDF OCR**: Users can now toggle PDF OCR option for improved parsing performance.
+- 🎨 **DALL-E Integration**: Introduced DALL-E integration for image generation alongside automatic1111.
+- 🛠️ **RAG API Refactoring**: Refactored RAG logic and exposed its API, with additional documentation to follow.
+
+### Fixed
+
+- 🔒 **Max Token Settings**: Added max token settings for anthropic/claude-3-sonnet-20240229 (Issue #1094).
+- 🔧 **Misalignment Issue**: Corrected misalignment of Edit and Delete Icons when Chat Title is Empty (Issue #1104).
+- 🔄 **Context Loss Fix**: Resolved RAG losing context on model response regeneration with Groq models via API key (Issue #1105).
+- 📁 **File Handling Bug**: Addressed File Not Found Notification when Dropping a Conversation Element (Issue #1098).
+- 🖱️ **Dragged File Styling**: Fixed dragged file layover styling issue.
+
+## [0.1.110] - 2024-03-06
+
+### Added
+
+- **🌐 Multiple OpenAI Servers Support**: Enjoy seamless integration with multiple OpenAI-compatible APIs, now supported natively.
+
+### Fixed
+
+- **🔍 OCR Issue**: Resolved PDF parsing issue caused by OCR malfunction.
+- **🚫 RAG Issue**: Fixed the RAG functionality, ensuring it operates smoothly.
+- **📄 "Add Docs" Model Button**: Addressed the non-functional behavior of the "Add Docs" model button.
+
+## [0.1.109] - 2024-03-06
+
+### Added
+
+- **🔄 Multiple Ollama Servers Support**: Enjoy enhanced scalability and performance with support for multiple Ollama servers in a single WebUI. Load balancing features are now available, providing improved efficiency (#788, #278).
+- **🔧 Support for Claude 3 and Gemini**: Responding to user requests, we've expanded our toolset to include Claude 3 and Gemini, offering a wider range of functionalities within our platform (#1064).
+- **🔍 OCR Functionality for PDF Loader**: We've augmented our PDF loader with Optical Character Recognition (OCR) capabilities. Now, extract text from scanned documents and images within PDFs, broadening the scope of content processing (#1050).
+
+### Fixed
+
+- **🛠️ RAG Collection**: Implemented a dynamic mechanism to recreate RAG collections, ensuring users have up-to-date and accurate data (#1031).
+- **📝 User Agent Headers**: Fixed issue of RAG web requests being sent with empty user_agent headers, reducing rejections from certain websites. Realistic headers are now utilized for these requests (#1024).
+- **⏹️ Playground Cancel Functionality**: Introducing a new "Cancel" option for stopping Ollama generation in the Playground, enhancing user control and usability (#1006).
+- **🔤 Typographical Error in 'ASSISTANT' Field**: Corrected a typographical error in the 'ASSISTANT' field within the GGUF model upload template for accuracy and consistency (#1061).
+
+### Changed
+
+- **🔄 Refactored Message Deletion Logic**: Streamlined message deletion process for improved efficiency and user experience, simplifying interactions within the platform (#1004).
+- **⚠️ Deprecation of `OLLAMA_API_BASE_URL`**: Deprecated `OLLAMA_API_BASE_URL` environment variable; recommend using `OLLAMA_BASE_URL` instead. Refer to our documentation for further details.
+
+## [0.1.108] - 2024-03-02
+
+### Added
+
+- **🎮 Playground Feature (Beta)**: Explore the full potential of the raw API through an intuitive UI with our new playground feature, accessible to admins. Simply click on the bottom name area of the sidebar to access it. The playground feature offers two modes text completion (notebook) and chat completion. As it's in beta, please report any issues you encounter.
+- **🛠️ Direct Database Download for Admins**: Admins can now download the database directly from the WebUI via the admin settings.
+- **🎨 Additional RAG Settings**: Customize your RAG process with the ability to edit the TOP K value. Navigate to Documents > Settings > General to make changes.
+- **🖥️ UI Improvements**: Tooltips now available in the input area and sidebar handle. More tooltips will be added across other parts of the UI.
+
+### Fixed
+
+- Resolved input autofocus issue on mobile when the sidebar is open, making it easier to use.
+- Corrected numbered list display issue in Safari (#963).
+- Restricted user ability to delete chats without proper permissions (#993).
+
+### Changed
+
+- **Simplified Ollama Settings**: Ollama settings now don't require the `/api` suffix. You can now utilize the Ollama base URL directly, e.g., `http://localhost:11434`. Also, an `OLLAMA_BASE_URL` environment variable has been added.
+- **Database Renaming**: Starting from this release, `ollama.db` will be automatically renamed to `webui.db`.
+
+## [0.1.107] - 2024-03-01
+
+### Added
+
+- **🚀 Makefile and LLM Update Script**: Included Makefile and a script for LLM updates in the repository.
+
+### Fixed
+
+- Corrected issue where links in the settings modal didn't appear clickable (#960).
+- Fixed problem with web UI port not taking effect due to incorrect environment variable name in run-compose.sh (#996).
+- Enhanced user experience by displaying chat in browser title and enabling automatic scrolling to the bottom (#992).
+
+### Changed
+
+- Upgraded toast library from `svelte-french-toast` to `svelte-sonner` for a more polished UI.
+- Enhanced accessibility with the addition of dark mode on the authentication page.
+
+## [0.1.106] - 2024-02-27
+
+### Added
+
+- **🎯 Auto-focus Feature**: The input area now automatically focuses when initiating or opening a chat conversation.
+
+### Fixed
+
+- Corrected typo from "HuggingFace" to "Hugging Face" (Issue #924).
+- Resolved bug causing errors in chat completion API calls to OpenAI due to missing "num_ctx" parameter (Issue #927).
+- Fixed issues preventing text editing, selection, and cursor retention in the input field (Issue #940).
+- Fixed a bug where defining an OpenAI-compatible API server using 'OPENAI_API_BASE_URL' containing 'openai' string resulted in hiding models not containing 'gpt' string from the model menu. (Issue #930)
+
+## [0.1.105] - 2024-02-25
+
+### Added
+
+- **📄 Document Selection**: Now you can select and delete multiple documents at once for easier management.
+
+### Changed
+
+- **🏷️ Document Pre-tagging**: Simply click the "+" button at the top, enter tag names in the popup window, or select from a list of existing tags. Then, upload files with the added tags for streamlined organization.
+
+## [0.1.104] - 2024-02-25
+
+### Added
+
+- **🔄 Check for Updates**: Keep your system current by checking for updates conveniently located in Settings > About.
+- **🗑️ Automatic Tag Deletion**: Unused tags on the sidebar will now be deleted automatically with just a click.
+
+### Changed
+
+- **🎨 Modernized Styling**: Enjoy a refreshed look with updated styling for a more contemporary experience.
+
+## [0.1.103] - 2024-02-25
+
+### Added
+
+- **🔗 Built-in LiteLLM Proxy**: Now includes LiteLLM proxy within Open WebUI for enhanced functionality.
+
+  - Easily integrate existing LiteLLM configurations using `-v /path/to/config.yaml:/app/backend/data/litellm/config.yaml` flag.
+  - When utilizing Docker container to run Open WebUI, ensure connections to localhost use `host.docker.internal`.
+
+- **🖼️ Image Generation Enhancements**: Introducing Advanced Settings with Image Preview Feature.
+  - Customize image generation by setting the number of steps; defaults to A1111 value.
+
+### Fixed
+
+- Resolved issue with RAG scan halting document loading upon encountering unsupported MIME types or exceptions (Issue #866).
+
+### Changed
+
+- Ollama is no longer required to run Open WebUI.
+- Access our comprehensive documentation at [Open WebUI Documentation](https://docs.openwebui.com/).
+
+## [0.1.102] - 2024-02-22
+
+### Added
+
+- **🖼️ Image Generation**: Generate Images using the AUTOMATIC1111/stable-diffusion-webui API. You can set this up in Settings > Images.
+- **📝 Change title generation prompt**: Change the prompt used to generate titles for your chats. You can set this up in the Settings > Interface.
+- **🤖 Change embedding model**: Change the embedding model used to generate embeddings for your chats in the Dockerfile. Use any sentence transformer model from huggingface.co.
+- **📢 CHANGELOG.md/Popup**: This popup will show you the latest changes.
+
+## [0.1.101] - 2024-02-22
+
+### Fixed
+
+- LaTex output formatting issue (#828)
+
+### Changed
+
+- Instead of having the previous 1.0.0-alpha.101, we switched to semantic versioning as a way to respect global conventions.
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000000000000000000000000000000000000..37ac5263cbfdbb5a938da361397352443e57dc33
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,77 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contribute to a positive environment for our community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the overall community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email address, without their explicit permission
+- **Spamming of any kind**
+- Aggressive sales tactics targeting our community members are strictly prohibited. You can mention your product if it's relevant to the discussion, but under no circumstances should you push it forcefully
+- Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies within all community spaces and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, spamming, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at hello@openwebui.com. All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Temporary Ban
+
+**Community Impact**: Any violation of community standards, including but not limited to inappropriate language, unprofessional behavior, harassment, or spamming.
+
+**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
+
+### 2. Permanent Ban
+
+**Community Impact**: Repeated or severe violations of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/Caddyfile.localhost b/Caddyfile.localhost
new file mode 100644
index 0000000000000000000000000000000000000000..80728eedf6ac60dc6454ef6933b490eeed4648be
--- /dev/null
+++ b/Caddyfile.localhost
@@ -0,0 +1,64 @@
+# Run with
+#    caddy run --envfile ./example.env --config ./Caddyfile.localhost
+#
+# This is configured for
+#    - Automatic HTTPS (even for localhost)
+#    - Reverse Proxying to Ollama API Base URL (http://localhost:11434/api)
+#    - CORS
+#    - HTTP Basic Auth API Tokens (uncomment basicauth section)
+
+
+# CORS Preflight (OPTIONS) + Request (GET, POST, PATCH, PUT, DELETE)
+(cors-api) {
+	@match-cors-api-preflight method OPTIONS
+	handle @match-cors-api-preflight {
+		header {
+			Access-Control-Allow-Origin "{http.request.header.origin}"
+			Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
+			Access-Control-Allow-Headers "Origin, Accept, Authorization, Content-Type, X-Requested-With"
+			Access-Control-Allow-Credentials "true"
+			Access-Control-Max-Age "3600"
+			defer
+		}
+		respond "" 204
+	}
+
+	@match-cors-api-request {
+		not {
+			header Origin "{http.request.scheme}://{http.request.host}"
+		}
+		header Origin "{http.request.header.origin}"
+	}
+	handle @match-cors-api-request {
+		header {
+			Access-Control-Allow-Origin "{http.request.header.origin}"
+			Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
+			Access-Control-Allow-Headers "Origin, Accept, Authorization, Content-Type, X-Requested-With"
+			Access-Control-Allow-Credentials "true"
+			Access-Control-Max-Age "3600"
+			defer
+		}
+	}
+}
+
+# replace localhost with example.com or whatever
+localhost {
+	## HTTP Basic Auth
+	## (uncomment to enable)
+	# basicauth {
+	# 	# see .example.env for how to generate tokens
+	# 	{env.OLLAMA_API_ID} {env.OLLAMA_API_TOKEN_DIGEST}
+	# }
+
+	handle /api/* {
+		# Comment to disable CORS
+		import cors-api
+
+		reverse_proxy localhost:11434
+	}
+
+	# Same-Origin Static Web Server
+	file_server {
+		root ./build/
+	}
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..274e23dbfc79c0a2fb5c01da0b26f26ad9f7dec8
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,176 @@
+# syntax=docker/dockerfile:1
+# Initialize device type args
+# use build args in the docker build command with --build-arg="BUILDARG=true"
+ARG USE_CUDA=false
+ARG USE_OLLAMA=false
+# Tested with cu117 for CUDA 11 and cu121 for CUDA 12 (default)
+ARG USE_CUDA_VER=cu121
+# any sentence transformer model; models to use can be found at https://huggingface.co/models?library=sentence-transformers
+# Leaderboard: https://huggingface.co/spaces/mteb/leaderboard 
+# for better performance and multilangauge support use "intfloat/multilingual-e5-large" (~2.5GB) or "intfloat/multilingual-e5-base" (~1.5GB)
+# IMPORTANT: If you change the embedding model (sentence-transformers/all-MiniLM-L6-v2) and vice versa, you aren't able to use RAG Chat with your previous documents loaded in the WebUI! You need to re-embed them.
+ARG USE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
+ARG USE_RERANKING_MODEL=""
+
+# Tiktoken encoding name; models to use can be found at https://huggingface.co/models?library=tiktoken
+ARG USE_TIKTOKEN_ENCODING_NAME="cl100k_base"
+
+ARG BUILD_HASH=dev-build
+# Override at your own risk - non-root configurations are untested
+ARG UID=0
+ARG GID=0
+
+######## WebUI frontend ########
+FROM --platform=$BUILDPLATFORM node:22-alpine3.20 AS build
+ARG BUILD_HASH
+
+WORKDIR /app
+
+COPY package.json package-lock.json ./
+RUN npm ci
+
+COPY . .
+ENV APP_BUILD_HASH=${BUILD_HASH}
+RUN npm run build
+
+######## WebUI backend ########
+FROM python:3.11-slim-bookworm AS base
+
+# Use args
+ARG USE_CUDA
+ARG USE_OLLAMA
+ARG USE_CUDA_VER
+ARG USE_EMBEDDING_MODEL
+ARG USE_RERANKING_MODEL
+ARG UID
+ARG GID
+
+## Basis ##
+ENV ENV=prod \
+    PORT=8080 \
+    # pass build args to the build
+    USE_OLLAMA_DOCKER=${USE_OLLAMA} \
+    USE_CUDA_DOCKER=${USE_CUDA} \
+    USE_CUDA_DOCKER_VER=${USE_CUDA_VER} \
+    USE_EMBEDDING_MODEL_DOCKER=${USE_EMBEDDING_MODEL} \
+    USE_RERANKING_MODEL_DOCKER=${USE_RERANKING_MODEL}
+
+## Basis URL Config ##
+ENV OLLAMA_BASE_URL="/ollama" \
+    OPENAI_API_BASE_URL=""
+
+## API Key and Security Config ##
+ENV OPENAI_API_KEY="" \
+    WEBUI_SECRET_KEY="" \
+    SCARF_NO_ANALYTICS=true \
+    DO_NOT_TRACK=true \
+    ANONYMIZED_TELEMETRY=false
+
+#### Other models #########################################################
+## whisper TTS model settings ##
+ENV WHISPER_MODEL="base" \
+    WHISPER_MODEL_DIR="/app/backend/data/cache/whisper/models"
+
+## RAG Embedding model settings ##
+ENV RAG_EMBEDDING_MODEL="$USE_EMBEDDING_MODEL_DOCKER" \
+    RAG_RERANKING_MODEL="$USE_RERANKING_MODEL_DOCKER" \
+    SENTENCE_TRANSFORMERS_HOME="/app/backend/data/cache/embedding/models"
+
+## Tiktoken model settings ##
+ENV TIKTOKEN_ENCODING_NAME="cl100k_base" \
+    TIKTOKEN_CACHE_DIR="/app/backend/data/cache/tiktoken"
+
+## Hugging Face download cache ##
+ENV HF_HOME="/app/backend/data/cache/embedding/models"
+
+## Torch Extensions ##
+# ENV TORCH_EXTENSIONS_DIR="/.cache/torch_extensions"
+
+#### Other models ##########################################################
+
+WORKDIR /app/backend
+
+ENV HOME=/root
+# Create user and group if not root
+RUN if [ $UID -ne 0 ]; then \
+    if [ $GID -ne 0 ]; then \
+    addgroup --gid $GID app; \
+    fi; \
+    adduser --uid $UID --gid $GID --home $HOME --disabled-password --no-create-home app; \
+    fi
+
+RUN mkdir -p $HOME/.cache/chroma
+RUN echo -n 00000000-0000-0000-0000-000000000000 > $HOME/.cache/chroma/telemetry_user_id
+
+# Make sure the user has access to the app and root directory
+RUN chown -R $UID:$GID /app $HOME
+
+RUN if [ "$USE_OLLAMA" = "true" ]; then \
+    apt-get update && \
+    # Install pandoc and netcat
+    apt-get install -y --no-install-recommends git build-essential pandoc netcat-openbsd curl && \
+    apt-get install -y --no-install-recommends gcc python3-dev && \
+    # for RAG OCR
+    apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
+    # install helper tools
+    apt-get install -y --no-install-recommends curl jq && \
+    # install ollama
+    curl -fsSL https://ollama.com/install.sh | sh && \
+    # cleanup
+    rm -rf /var/lib/apt/lists/*; \
+    else \
+    apt-get update && \
+    # Install pandoc, netcat and gcc
+    apt-get install -y --no-install-recommends git build-essential pandoc gcc netcat-openbsd curl jq && \
+    apt-get install -y --no-install-recommends gcc python3-dev && \
+    # for RAG OCR
+    apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
+    # cleanup
+    rm -rf /var/lib/apt/lists/*; \
+    fi
+
+# install python dependencies
+COPY --chown=$UID:$GID ./backend/requirements.txt ./requirements.txt
+
+RUN pip3 install uv && \
+    if [ "$USE_CUDA" = "true" ]; then \
+    # If you use CUDA the whisper and embedding model will be downloaded on first use
+    pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
+    uv pip install --system -r requirements.txt --no-cache-dir && \
+    python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
+    python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
+    python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
+    else \
+    pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
+    uv pip install --system -r requirements.txt --no-cache-dir && \
+    python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
+    python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
+    python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
+    fi; \
+    chown -R $UID:$GID /app/backend/data/
+
+
+
+# copy embedding weight from build
+# RUN mkdir -p /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2
+# COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onnx
+
+# copy built frontend files
+COPY --chown=$UID:$GID --from=build /app/build /app/build
+COPY --chown=$UID:$GID --from=build /app/CHANGELOG.md /app/CHANGELOG.md
+COPY --chown=$UID:$GID --from=build /app/package.json /app/package.json
+
+# copy backend files
+COPY --chown=$UID:$GID ./backend .
+
+EXPOSE 8080
+
+HEALTHCHECK CMD curl --silent --fail http://localhost:${PORT:-8080}/health | jq -ne 'input.status == true' || exit 1
+
+USER $UID:$GID
+
+ARG BUILD_HASH
+ENV WEBUI_BUILD_VERSION=${BUILD_HASH}
+ENV DOCKER=true
+
+CMD [ "bash", "start.sh"]
diff --git a/INSTALLATION.md b/INSTALLATION.md
new file mode 100644
index 0000000000000000000000000000000000000000..4298b173e9fd5c372a81c86fc4121de6a57fb81b
--- /dev/null
+++ b/INSTALLATION.md
@@ -0,0 +1,35 @@
+### Installing Both Ollama and Open WebUI Using Kustomize
+
+For cpu-only pod
+
+```bash
+kubectl apply -f ./kubernetes/manifest/base
+```
+
+For gpu-enabled pod
+
+```bash
+kubectl apply -k ./kubernetes/manifest
+```
+
+### Installing Both Ollama and Open WebUI Using Helm
+
+Package Helm file first
+
+```bash
+helm package ./kubernetes/helm/
+```
+
+For cpu-only pod
+
+```bash
+helm install ollama-webui ./ollama-webui-*.tgz
+```
+
+For gpu-enabled pod
+
+```bash
+helm install ollama-webui ./ollama-webui-*.tgz --set ollama.resources.limits.nvidia.com/gpu="1"
+```
+
+Check the `kubernetes/helm/values.yaml` file to know which parameters are available for customization
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..515e64df6c00c937379ad187de6204770db0fe18
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Timothy Jaeryang Baek
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4b60b049658173c1e8b64dd79919fdd04d5d4f24
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,33 @@
+
+ifneq ($(shell which docker-compose 2>/dev/null),)
+    DOCKER_COMPOSE := docker-compose
+else
+    DOCKER_COMPOSE := docker compose
+endif
+
+install:
+	$(DOCKER_COMPOSE) up -d
+
+remove:
+	@chmod +x confirm_remove.sh
+	@./confirm_remove.sh
+
+start:
+	$(DOCKER_COMPOSE) start
+startAndBuild: 
+	$(DOCKER_COMPOSE) up -d --build
+
+stop:
+	$(DOCKER_COMPOSE) stop
+
+update:
+	# Calls the LLM update script
+	chmod +x update_ollama_models.sh
+	@./update_ollama_models.sh
+	@git pull
+	$(DOCKER_COMPOSE) down
+	# Make sure the ollama-webui container is stopped before rebuilding
+	@docker stop open-webui || true
+	$(DOCKER_COMPOSE) up --build -d
+	$(DOCKER_COMPOSE) start
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..c5d256b458552131c26f0cfb85298cacc3d5f2fc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,219 @@
+---
+title: Open WebUI
+emoji: 🐳
+colorFrom: purple
+colorTo: gray
+sdk: docker
+app_port: 8080
+---
+# Open WebUI 👋
+
+![GitHub stars](https://img.shields.io/github/stars/open-webui/open-webui?style=social)
+![GitHub forks](https://img.shields.io/github/forks/open-webui/open-webui?style=social)
+![GitHub watchers](https://img.shields.io/github/watchers/open-webui/open-webui?style=social)
+![GitHub repo size](https://img.shields.io/github/repo-size/open-webui/open-webui)
+![GitHub language count](https://img.shields.io/github/languages/count/open-webui/open-webui)
+![GitHub top language](https://img.shields.io/github/languages/top/open-webui/open-webui)
+![GitHub last commit](https://img.shields.io/github/last-commit/open-webui/open-webui?color=red)
+![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Follama-webui%2Follama-wbui&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)
+[![Discord](https://img.shields.io/badge/Discord-Open_WebUI-blue?logo=discord&logoColor=white)](https://discord.gg/5rJgQTnV4s)
+[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/tjbck)
+
+Open WebUI is an [extensible](https://github.com/open-webui/pipelines), feature-rich, and user-friendly self-hosted WebUI designed to operate entirely offline. It supports various LLM runners, including Ollama and OpenAI-compatible APIs. For more information, be sure to check out our [Open WebUI Documentation](https://docs.openwebui.com/).
+
+![Open WebUI Demo](./demo.gif)
+
+## Key Features of Open WebUI ⭐
+
+- 🚀 **Effortless Setup**: Install seamlessly using Docker or Kubernetes (kubectl, kustomize or helm) for a hassle-free experience with support for both `:ollama` and `:cuda` tagged images.
+
+- 🤝 **Ollama/OpenAI API Integration**: Effortlessly integrate OpenAI-compatible APIs for versatile conversations alongside Ollama models. Customize the OpenAI API URL to link with **LMStudio, GroqCloud, Mistral, OpenRouter, and more**.
+
+- 🧩 **Pipelines, Open WebUI Plugin Support**: Seamlessly integrate custom logic and Python libraries into Open WebUI using [Pipelines Plugin Framework](https://github.com/open-webui/pipelines). Launch your Pipelines instance, set the OpenAI URL to the Pipelines URL, and explore endless possibilities. [Examples](https://github.com/open-webui/pipelines/tree/main/examples) include **Function Calling**, User **Rate Limiting** to control access, **Usage Monitoring** with tools like Langfuse, **Live Translation with LibreTranslate** for multilingual support, **Toxic Message Filtering** and much more.
+
+- 📱 **Responsive Design**: Enjoy a seamless experience across Desktop PC, Laptop, and Mobile devices.
+
+- 📱 **Progressive Web App (PWA) for Mobile**: Enjoy a native app-like experience on your mobile device with our PWA, providing offline access on localhost and a seamless user interface.
+
+- ✒️🔢 **Full Markdown and LaTeX Support**: Elevate your LLM experience with comprehensive Markdown and LaTeX capabilities for enriched interaction.
+
+- 🎤📹 **Hands-Free Voice/Video Call**: Experience seamless communication with integrated hands-free voice and video call features, allowing for a more dynamic and interactive chat environment.
+
+- 🛠️ **Model Builder**: Easily create Ollama models via the Web UI. Create and add custom characters/agents, customize chat elements, and import models effortlessly through [Open WebUI Community](https://openwebui.com/) integration.
+
+- 🐍 **Native Python Function Calling Tool**: Enhance your LLMs with built-in code editor support in the tools workspace. Bring Your Own Function (BYOF) by simply adding your pure Python functions, enabling seamless integration with LLMs.
+
+- 📚 **Local RAG Integration**: Dive into the future of chat interactions with groundbreaking Retrieval Augmented Generation (RAG) support. This feature seamlessly integrates document interactions into your chat experience. You can load documents directly into the chat or add files to your document library, effortlessly accessing them using the `#` command before a query.
+
+- 🔍 **Web Search for RAG**: Perform web searches using providers like `SearXNG`, `Google PSE`, `Brave Search`, `serpstack`, `serper`, `Serply`, `DuckDuckGo`, `TavilySearch` and `SearchApi` and inject the results directly into your chat experience.
+
+- 🌐 **Web Browsing Capability**: Seamlessly integrate websites into your chat experience using the `#` command followed by a URL. This feature allows you to incorporate web content directly into your conversations, enhancing the richness and depth of your interactions.
+
+- 🎨 **Image Generation Integration**: Seamlessly incorporate image generation capabilities using options such as AUTOMATIC1111 API or ComfyUI (local), and OpenAI's DALL-E (external), enriching your chat experience with dynamic visual content.
+
+- ⚙️ **Many Models Conversations**: Effortlessly engage with various models simultaneously, harnessing their unique strengths for optimal responses. Enhance your experience by leveraging a diverse set of models in parallel.
+
+- 🔐 **Role-Based Access Control (RBAC)**: Ensure secure access with restricted permissions; only authorized individuals can access your Ollama, and exclusive model creation/pulling rights are reserved for administrators.
+
+- 🌐🌍 **Multilingual Support**: Experience Open WebUI in your preferred language with our internationalization (i18n) support. Join us in expanding our supported languages! We're actively seeking contributors!
+
+- 🌟 **Continuous Updates**: We are committed to improving Open WebUI with regular updates, fixes, and new features.
+
+Want to learn more about Open WebUI's features? Check out our [Open WebUI documentation](https://docs.openwebui.com/features) for a comprehensive overview!
+
+## 🔗 Also Check Out Open WebUI Community!
+
+Don't forget to explore our sibling project, [Open WebUI Community](https://openwebui.com/), where you can discover, download, and explore customized Modelfiles. Open WebUI Community offers a wide range of exciting possibilities for enhancing your chat interactions with Open WebUI! 🚀
+
+## How to Install 🚀
+
+### Installation via Python pip 🐍
+
+Open WebUI can be installed using pip, the Python package installer. Before proceeding, ensure you're using **Python 3.11** to avoid compatibility issues.
+
+1. **Install Open WebUI**:
+   Open your terminal and run the following command to install Open WebUI:
+
+   ```bash
+   pip install open-webui
+   ```
+
+2. **Running Open WebUI**:
+   After installation, you can start Open WebUI by executing:
+
+   ```bash
+   open-webui serve
+   ```
+
+This will start the Open WebUI server, which you can access at [http://localhost:8080](http://localhost:8080)
+
+### Quick Start with Docker 🐳
+
+> [!NOTE]  
+> Please note that for certain Docker environments, additional configurations might be needed. If you encounter any connection issues, our detailed guide on [Open WebUI Documentation](https://docs.openwebui.com/) is ready to assist you.
+
+> [!WARNING]
+> When using Docker to install Open WebUI, make sure to include the `-v open-webui:/app/backend/data` in your Docker command. This step is crucial as it ensures your database is properly mounted and prevents any loss of data.
+
+> [!TIP]  
+> If you wish to utilize Open WebUI with Ollama included or CUDA acceleration, we recommend utilizing our official images tagged with either `:cuda` or `:ollama`. To enable CUDA, you must install the [Nvidia CUDA container toolkit](https://docs.nvidia.com/dgx/nvidia-container-runtime-upgrade/) on your Linux/WSL system.
+
+### Installation with Default Configuration
+
+- **If Ollama is on your computer**, use this command:
+
+  ```bash
+  docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+  ```
+
+- **If Ollama is on a Different Server**, use this command:
+
+  To connect to Ollama on another server, change the `OLLAMA_BASE_URL` to the server's URL:
+
+  ```bash
+  docker run -d -p 3000:8080 -e OLLAMA_BASE_URL=https://example.com -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+  ```
+
+- **To run Open WebUI with Nvidia GPU support**, use this command:
+
+  ```bash
+  docker run -d -p 3000:8080 --gpus all --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:cuda
+  ```
+
+### Installation for OpenAI API Usage Only
+
+- **If you're only using OpenAI API**, use this command:
+
+  ```bash
+  docker run -d -p 3000:8080 -e OPENAI_API_KEY=your_secret_key -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+  ```
+
+### Installing Open WebUI with Bundled Ollama Support
+
+This installation method uses a single container image that bundles Open WebUI with Ollama, allowing for a streamlined setup via a single command. Choose the appropriate command based on your hardware setup:
+
+- **With GPU Support**:
+  Utilize GPU resources by running the following command:
+
+  ```bash
+  docker run -d -p 3000:8080 --gpus=all -v ollama:/root/.ollama -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:ollama
+  ```
+
+- **For CPU Only**:
+  If you're not using a GPU, use this command instead:
+
+  ```bash
+  docker run -d -p 3000:8080 -v ollama:/root/.ollama -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:ollama
+  ```
+
+Both commands facilitate a built-in, hassle-free installation of both Open WebUI and Ollama, ensuring that you can get everything up and running swiftly.
+
+After installation, you can access Open WebUI at [http://localhost:3000](http://localhost:3000). Enjoy! 😄
+
+### Other Installation Methods
+
+We offer various installation alternatives, including non-Docker native installation methods, Docker Compose, Kustomize, and Helm. Visit our [Open WebUI Documentation](https://docs.openwebui.com/getting-started/) or join our [Discord community](https://discord.gg/5rJgQTnV4s) for comprehensive guidance.
+
+### Troubleshooting
+
+Encountering connection issues? Our [Open WebUI Documentation](https://docs.openwebui.com/troubleshooting/) has got you covered. For further assistance and to join our vibrant community, visit the [Open WebUI Discord](https://discord.gg/5rJgQTnV4s).
+
+#### Open WebUI: Server Connection Error
+
+If you're experiencing connection issues, it’s often due to the WebUI docker container not being able to reach the Ollama server at 127.0.0.1:11434 (host.docker.internal:11434) inside the container . Use the `--network=host` flag in your docker command to resolve this. Note that the port changes from 3000 to 8080, resulting in the link: `http://localhost:8080`.
+
+**Example Docker Command**:
+
+```bash
+docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_BASE_URL=http://127.0.0.1:11434 --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+```
+
+### Keeping Your Docker Installation Up-to-Date
+
+In case you want to update your local Docker installation to the latest version, you can do it with [Watchtower](https://containrrr.dev/watchtower/):
+
+```bash
+docker run --rm --volume /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --run-once open-webui
+```
+
+In the last part of the command, replace `open-webui` with your container name if it is different.
+
+Check our Migration Guide available in our [Open WebUI Documentation](https://docs.openwebui.com/tutorials/migration/).
+
+### Using the Dev Branch 🌙
+
+> [!WARNING]
+> The `:dev` branch contains the latest unstable features and changes. Use it at your own risk as it may have bugs or incomplete features.
+
+If you want to try out the latest bleeding-edge features and are okay with occasional instability, you can use the `:dev` tag like this:
+
+```bash
+docker run -d -p 3000:8080 -v open-webui:/app/backend/data --name open-webui --add-host=host.docker.internal:host-gateway --restart always ghcr.io/open-webui/open-webui:dev
+```
+
+## What's Next? 🌟
+
+Discover upcoming features on our roadmap in the [Open WebUI Documentation](https://docs.openwebui.com/roadmap/).
+
+## License 📜
+
+This project is licensed under the [MIT License](LICENSE) - see the [LICENSE](LICENSE) file for details. 📄
+
+## Support 💬
+
+If you have any questions, suggestions, or need assistance, please open an issue or join our
+[Open WebUI Discord community](https://discord.gg/5rJgQTnV4s) to connect with us! 🤝
+
+## Star History
+
+<a href="https://star-history.com/#open-webui/open-webui&Date">
+  <picture>
+    <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=open-webui/open-webui&type=Date&theme=dark" />
+    <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=open-webui/open-webui&type=Date" />
+    <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=open-webui/open-webui&type=Date" />
+  </picture>
+</a>
+
+---
+
+Created by [Timothy Jaeryang Baek](https://github.com/tjbck) - Let's make Open WebUI even more amazing together! 💪
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..83251a3a91ee25b4952e70e335fe7df1dfcbde69
--- /dev/null
+++ b/TROUBLESHOOTING.md
@@ -0,0 +1,36 @@
+# Open WebUI Troubleshooting Guide
+
+## Understanding the Open WebUI Architecture
+
+The Open WebUI system is designed to streamline interactions between the client (your browser) and the Ollama API. At the heart of this design is a backend reverse proxy, enhancing security and resolving CORS issues.
+
+- **How it Works**: The Open WebUI is designed to interact with the Ollama API through a specific route. When a request is made from the WebUI to Ollama, it is not directly sent to the Ollama API. Initially, the request is sent to the Open WebUI backend via `/ollama` route. From there, the backend is responsible for forwarding the request to the Ollama API. This forwarding is accomplished by using the route specified in the `OLLAMA_BASE_URL` environment variable. Therefore, a request made to `/ollama` in the WebUI is effectively the same as making a request to `OLLAMA_BASE_URL` in the backend. For instance, a request to `/ollama/api/tags` in the WebUI is equivalent to `OLLAMA_BASE_URL/api/tags` in the backend.
+
+- **Security Benefits**: This design prevents direct exposure of the Ollama API to the frontend, safeguarding against potential CORS (Cross-Origin Resource Sharing) issues and unauthorized access. Requiring authentication to access the Ollama API further enhances this security layer.
+
+## Open WebUI: Server Connection Error
+
+If you're experiencing connection issues, it’s often due to the WebUI docker container not being able to reach the Ollama server at 127.0.0.1:11434 (host.docker.internal:11434) inside the container . Use the `--network=host` flag in your docker command to resolve this. Note that the port changes from 3000 to 8080, resulting in the link: `http://localhost:8080`.
+
+**Example Docker Command**:
+
+```bash
+docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_BASE_URL=http://127.0.0.1:11434 --name open-webui --restart always ghcr.io/open-webui/open-webui:main
+```
+
+### Error on Slow Responses for Ollama
+
+Open WebUI has a default timeout of 5 minutes for Ollama to finish generating the response. If needed, this can be adjusted via the environment variable AIOHTTP_CLIENT_TIMEOUT, which sets the timeout in seconds.
+
+### General Connection Errors
+
+**Ensure Ollama Version is Up-to-Date**: Always start by checking that you have the latest version of Ollama. Visit [Ollama's official site](https://ollama.com/) for the latest updates.
+
+**Troubleshooting Steps**:
+
+1. **Verify Ollama URL Format**:
+   - When running the Web UI container, ensure the `OLLAMA_BASE_URL` is correctly set. (e.g., `http://192.168.1.1:11434` for different host setups).
+   - In the Open WebUI, navigate to "Settings" > "General".
+   - Confirm that the Ollama Server URL is correctly set to `[OLLAMA URL]` (e.g., `http://localhost:11434`).
+
+By following these enhanced troubleshooting steps, connection issues should be effectively resolved. For further assistance or queries, feel free to reach out to us on our community Discord.
diff --git a/backend/.dockerignore b/backend/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..97ab32835d90e779180b09b866c7eecbdb1433ac
--- /dev/null
+++ b/backend/.dockerignore
@@ -0,0 +1,14 @@
+__pycache__
+.env
+_old
+uploads
+.ipynb_checkpoints
+*.db
+_test
+!/data
+/data/*
+!/data/litellm
+/data/litellm/*
+!data/litellm/config.yaml
+
+!data/config.json
\ No newline at end of file
diff --git a/backend/.gitignore b/backend/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..614a5f7465676c7bdc5d90f117fa6c18c75c4476
--- /dev/null
+++ b/backend/.gitignore
@@ -0,0 +1,12 @@
+__pycache__
+.env
+_old
+uploads
+.ipynb_checkpoints
+*.db
+_test
+Pipfile
+!/data
+/data/*
+/open_webui/data/*
+.webui_secret_key
\ No newline at end of file
diff --git a/backend/dev.sh b/backend/dev.sh
new file mode 100755
index 0000000000000000000000000000000000000000..5449ab77777fa604f6569fa59b68f29756e94a1d
--- /dev/null
+++ b/backend/dev.sh
@@ -0,0 +1,2 @@
+PORT="${PORT:-8080}"
+uvicorn open_webui.main:app --port $PORT --host 0.0.0.0 --forwarded-allow-ips '*' --reload
\ No newline at end of file
diff --git a/backend/open_webui/__init__.py b/backend/open_webui/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..de34a8bc769c70de9a097ad863f1e6a1dbb29dbb
--- /dev/null
+++ b/backend/open_webui/__init__.py
@@ -0,0 +1,77 @@
+import base64
+import os
+import random
+from pathlib import Path
+
+import typer
+import uvicorn
+
+app = typer.Typer()
+
+KEY_FILE = Path.cwd() / ".webui_secret_key"
+
+
+@app.command()
+def serve(
+    host: str = "0.0.0.0",
+    port: int = 8080,
+):
+    os.environ["FROM_INIT_PY"] = "true"
+    if os.getenv("WEBUI_SECRET_KEY") is None:
+        typer.echo(
+            "Loading WEBUI_SECRET_KEY from file, not provided as an environment variable."
+        )
+        if not KEY_FILE.exists():
+            typer.echo(f"Generating a new secret key and saving it to {KEY_FILE}")
+            KEY_FILE.write_bytes(base64.b64encode(random.randbytes(12)))
+        typer.echo(f"Loading WEBUI_SECRET_KEY from {KEY_FILE}")
+        os.environ["WEBUI_SECRET_KEY"] = KEY_FILE.read_text()
+
+    if os.getenv("USE_CUDA_DOCKER", "false") == "true":
+        typer.echo(
+            "CUDA is enabled, appending LD_LIBRARY_PATH to include torch/cudnn & cublas libraries."
+        )
+        LD_LIBRARY_PATH = os.getenv("LD_LIBRARY_PATH", "").split(":")
+        os.environ["LD_LIBRARY_PATH"] = ":".join(
+            LD_LIBRARY_PATH
+            + [
+                "/usr/local/lib/python3.11/site-packages/torch/lib",
+                "/usr/local/lib/python3.11/site-packages/nvidia/cudnn/lib",
+            ]
+        )
+        try:
+            import torch
+
+            assert torch.cuda.is_available(), "CUDA not available"
+            typer.echo("CUDA seems to be working")
+        except Exception as e:
+            typer.echo(
+                "Error when testing CUDA but USE_CUDA_DOCKER is true. "
+                "Resetting USE_CUDA_DOCKER to false and removing "
+                f"LD_LIBRARY_PATH modifications: {e}"
+            )
+            os.environ["USE_CUDA_DOCKER"] = "false"
+            os.environ["LD_LIBRARY_PATH"] = ":".join(LD_LIBRARY_PATH)
+
+    import open_webui.main  # we need set environment variables before importing main
+
+    uvicorn.run(open_webui.main.app, host=host, port=port, forwarded_allow_ips="*")
+
+
+@app.command()
+def dev(
+    host: str = "0.0.0.0",
+    port: int = 8080,
+    reload: bool = True,
+):
+    uvicorn.run(
+        "open_webui.main:app",
+        host=host,
+        port=port,
+        reload=reload,
+        forwarded_allow_ips="*",
+    )
+
+
+if __name__ == "__main__":
+    app()
diff --git a/backend/open_webui/alembic.ini b/backend/open_webui/alembic.ini
new file mode 100644
index 0000000000000000000000000000000000000000..4eff85f0c621c16f5afc80a4d92dc75da1483ac8
--- /dev/null
+++ b/backend/open_webui/alembic.ini
@@ -0,0 +1,114 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts
+script_location = migrations
+
+# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
+# Uncomment the line below if you want the files to be prepended with date and time
+# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
+
+# sys.path path, will be prepended to sys.path if present.
+# defaults to the current working directory.
+prepend_sys_path = .
+
+# timezone to use when rendering the date within the migration file
+# as well as the filename.
+# If specified, requires the python>=3.9 or backports.zoneinfo library.
+# Any required deps can installed by adding `alembic[tz]` to the pip requirements
+# string value is passed to ZoneInfo()
+# leave blank for localtime
+# timezone =
+
+# max length of characters to apply to the
+# "slug" field
+# truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+# version location specification; This defaults
+# to migrations/versions.  When using multiple version
+# directories, initial revisions must be specified with --version-path.
+# The path separator used here should be the separator specified by "version_path_separator" below.
+# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions
+
+# version path separator; As mentioned above, this is the character used to split
+# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
+# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
+# Valid values for version_path_separator are:
+#
+# version_path_separator = :
+# version_path_separator = ;
+# version_path_separator = space
+version_path_separator = os  # Use os.pathsep. Default configuration used for new projects.
+
+# set to 'true' to search source files recursively
+# in each "version_locations" directory
+# new in Alembic version 1.10
+# recursive_version_locations = false
+
+# the output encoding used when revision files
+# are written from script.py.mako
+# output_encoding = utf-8
+
+# sqlalchemy.url = REPLACE_WITH_DATABASE_URL
+
+
+[post_write_hooks]
+# post_write_hooks defines scripts or Python functions that are run
+# on newly generated revision scripts.  See the documentation for further
+# detail and examples
+
+# format using "black" - use the console_scripts runner, against the "black" entrypoint
+# hooks = black
+# black.type = console_scripts
+# black.entrypoint = black
+# black.options = -l 79 REVISION_SCRIPT_FILENAME
+
+# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
+# hooks = ruff
+# ruff.type = exec
+# ruff.executable = %(here)s/.venv/bin/ruff
+# ruff.options = --fix REVISION_SCRIPT_FILENAME
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/backend/open_webui/apps/audio/main.py b/backend/open_webui/apps/audio/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..50ffad12be8307d349df88aafbea13b3cd577192
--- /dev/null
+++ b/backend/open_webui/apps/audio/main.py
@@ -0,0 +1,641 @@
+import hashlib
+import json
+import logging
+import os
+import uuid
+from functools import lru_cache
+from pathlib import Path
+from pydub import AudioSegment
+from pydub.silence import split_on_silence
+
+import requests
+from open_webui.config import (
+    AUDIO_STT_ENGINE,
+    AUDIO_STT_MODEL,
+    AUDIO_STT_OPENAI_API_BASE_URL,
+    AUDIO_STT_OPENAI_API_KEY,
+    AUDIO_TTS_API_KEY,
+    AUDIO_TTS_ENGINE,
+    AUDIO_TTS_MODEL,
+    AUDIO_TTS_OPENAI_API_BASE_URL,
+    AUDIO_TTS_OPENAI_API_KEY,
+    AUDIO_TTS_SPLIT_ON,
+    AUDIO_TTS_VOICE,
+    AUDIO_TTS_AZURE_SPEECH_REGION,
+    AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT,
+    CACHE_DIR,
+    CORS_ALLOW_ORIGIN,
+    WHISPER_MODEL,
+    WHISPER_MODEL_AUTO_UPDATE,
+    WHISPER_MODEL_DIR,
+    AppConfig,
+)
+
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.env import ENV, SRC_LOG_LEVELS, DEVICE_TYPE
+from fastapi import Depends, FastAPI, File, HTTPException, Request, UploadFile, status
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import FileResponse
+from pydantic import BaseModel
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+# Constants
+MAX_FILE_SIZE_MB = 25
+MAX_FILE_SIZE = MAX_FILE_SIZE_MB * 1024 * 1024  # Convert MB to bytes
+
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["AUDIO"])
+
+app = FastAPI(docs_url="/docs" if ENV == "dev" else None, openapi_url="/openapi.json" if ENV == "dev" else None, redoc_url=None)
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=CORS_ALLOW_ORIGIN,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+app.state.config = AppConfig()
+
+app.state.config.STT_OPENAI_API_BASE_URL = AUDIO_STT_OPENAI_API_BASE_URL
+app.state.config.STT_OPENAI_API_KEY = AUDIO_STT_OPENAI_API_KEY
+app.state.config.STT_ENGINE = AUDIO_STT_ENGINE
+app.state.config.STT_MODEL = AUDIO_STT_MODEL
+
+app.state.config.WHISPER_MODEL = WHISPER_MODEL
+app.state.faster_whisper_model = None
+
+app.state.config.TTS_OPENAI_API_BASE_URL = AUDIO_TTS_OPENAI_API_BASE_URL
+app.state.config.TTS_OPENAI_API_KEY = AUDIO_TTS_OPENAI_API_KEY
+app.state.config.TTS_ENGINE = AUDIO_TTS_ENGINE
+app.state.config.TTS_MODEL = AUDIO_TTS_MODEL
+app.state.config.TTS_VOICE = AUDIO_TTS_VOICE
+app.state.config.TTS_API_KEY = AUDIO_TTS_API_KEY
+app.state.config.TTS_SPLIT_ON = AUDIO_TTS_SPLIT_ON
+
+app.state.config.TTS_AZURE_SPEECH_REGION = AUDIO_TTS_AZURE_SPEECH_REGION
+app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT = AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT
+
+# setting device type for whisper model
+whisper_device_type = DEVICE_TYPE if DEVICE_TYPE and DEVICE_TYPE == "cuda" else "cpu"
+log.info(f"whisper_device_type: {whisper_device_type}")
+
+SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
+SPEECH_CACHE_DIR.mkdir(parents=True, exist_ok=True)
+
+
+def set_faster_whisper_model(model: str, auto_update: bool = False):
+    if model and app.state.config.STT_ENGINE == "":
+        from faster_whisper import WhisperModel
+
+        faster_whisper_kwargs = {
+            "model_size_or_path": model,
+            "device": whisper_device_type,
+            "compute_type": "int8",
+            "download_root": WHISPER_MODEL_DIR,
+            "local_files_only": not auto_update,
+        }
+
+        try:
+            app.state.faster_whisper_model = WhisperModel(**faster_whisper_kwargs)
+        except Exception:
+            log.warning(
+                "WhisperModel initialization failed, attempting download with local_files_only=False"
+            )
+            faster_whisper_kwargs["local_files_only"] = False
+            app.state.faster_whisper_model = WhisperModel(**faster_whisper_kwargs)
+
+    else:
+        app.state.faster_whisper_model = None
+
+
+class TTSConfigForm(BaseModel):
+    OPENAI_API_BASE_URL: str
+    OPENAI_API_KEY: str
+    API_KEY: str
+    ENGINE: str
+    MODEL: str
+    VOICE: str
+    SPLIT_ON: str
+    AZURE_SPEECH_REGION: str
+    AZURE_SPEECH_OUTPUT_FORMAT: str
+
+
+class STTConfigForm(BaseModel):
+    OPENAI_API_BASE_URL: str
+    OPENAI_API_KEY: str
+    ENGINE: str
+    MODEL: str
+    WHISPER_MODEL: str
+
+
+class AudioConfigUpdateForm(BaseModel):
+    tts: TTSConfigForm
+    stt: STTConfigForm
+
+
+from pydub import AudioSegment
+from pydub.utils import mediainfo
+
+
+def is_mp4_audio(file_path):
+    """Check if the given file is an MP4 audio file."""
+    if not os.path.isfile(file_path):
+        print(f"File not found: {file_path}")
+        return False
+
+    info = mediainfo(file_path)
+    if (
+        info.get("codec_name") == "aac"
+        and info.get("codec_type") == "audio"
+        and info.get("codec_tag_string") == "mp4a"
+    ):
+        return True
+    return False
+
+
+def convert_mp4_to_wav(file_path, output_path):
+    """Convert MP4 audio file to WAV format."""
+    audio = AudioSegment.from_file(file_path, format="mp4")
+    audio.export(output_path, format="wav")
+    print(f"Converted {file_path} to {output_path}")
+
+
+@app.get("/config")
+async def get_audio_config(user=Depends(get_admin_user)):
+    return {
+        "tts": {
+            "OPENAI_API_BASE_URL": app.state.config.TTS_OPENAI_API_BASE_URL,
+            "OPENAI_API_KEY": app.state.config.TTS_OPENAI_API_KEY,
+            "API_KEY": app.state.config.TTS_API_KEY,
+            "ENGINE": app.state.config.TTS_ENGINE,
+            "MODEL": app.state.config.TTS_MODEL,
+            "VOICE": app.state.config.TTS_VOICE,
+            "SPLIT_ON": app.state.config.TTS_SPLIT_ON,
+            "AZURE_SPEECH_REGION": app.state.config.TTS_AZURE_SPEECH_REGION,
+            "AZURE_SPEECH_OUTPUT_FORMAT": app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT,
+        },
+        "stt": {
+            "OPENAI_API_BASE_URL": app.state.config.STT_OPENAI_API_BASE_URL,
+            "OPENAI_API_KEY": app.state.config.STT_OPENAI_API_KEY,
+            "ENGINE": app.state.config.STT_ENGINE,
+            "MODEL": app.state.config.STT_MODEL,
+            "WHISPER_MODEL": app.state.config.WHISPER_MODEL,
+        },
+    }
+
+
+@app.post("/config/update")
+async def update_audio_config(
+    form_data: AudioConfigUpdateForm, user=Depends(get_admin_user)
+):
+    app.state.config.TTS_OPENAI_API_BASE_URL = form_data.tts.OPENAI_API_BASE_URL
+    app.state.config.TTS_OPENAI_API_KEY = form_data.tts.OPENAI_API_KEY
+    app.state.config.TTS_API_KEY = form_data.tts.API_KEY
+    app.state.config.TTS_ENGINE = form_data.tts.ENGINE
+    app.state.config.TTS_MODEL = form_data.tts.MODEL
+    app.state.config.TTS_VOICE = form_data.tts.VOICE
+    app.state.config.TTS_SPLIT_ON = form_data.tts.SPLIT_ON
+    app.state.config.TTS_AZURE_SPEECH_REGION = form_data.tts.AZURE_SPEECH_REGION
+    app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT = (
+        form_data.tts.AZURE_SPEECH_OUTPUT_FORMAT
+    )
+
+    app.state.config.STT_OPENAI_API_BASE_URL = form_data.stt.OPENAI_API_BASE_URL
+    app.state.config.STT_OPENAI_API_KEY = form_data.stt.OPENAI_API_KEY
+    app.state.config.STT_ENGINE = form_data.stt.ENGINE
+    app.state.config.STT_MODEL = form_data.stt.MODEL
+    app.state.config.WHISPER_MODEL = form_data.stt.WHISPER_MODEL
+    set_faster_whisper_model(form_data.stt.WHISPER_MODEL, WHISPER_MODEL_AUTO_UPDATE)
+
+    return {
+        "tts": {
+            "OPENAI_API_BASE_URL": app.state.config.TTS_OPENAI_API_BASE_URL,
+            "OPENAI_API_KEY": app.state.config.TTS_OPENAI_API_KEY,
+            "API_KEY": app.state.config.TTS_API_KEY,
+            "ENGINE": app.state.config.TTS_ENGINE,
+            "MODEL": app.state.config.TTS_MODEL,
+            "VOICE": app.state.config.TTS_VOICE,
+            "SPLIT_ON": app.state.config.TTS_SPLIT_ON,
+            "AZURE_SPEECH_REGION": app.state.config.TTS_AZURE_SPEECH_REGION,
+            "AZURE_SPEECH_OUTPUT_FORMAT": app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT,
+        },
+        "stt": {
+            "OPENAI_API_BASE_URL": app.state.config.STT_OPENAI_API_BASE_URL,
+            "OPENAI_API_KEY": app.state.config.STT_OPENAI_API_KEY,
+            "ENGINE": app.state.config.STT_ENGINE,
+            "MODEL": app.state.config.STT_MODEL,
+            "WHISPER_MODEL": app.state.config.WHISPER_MODEL,
+        },
+    }
+
+
+@app.post("/speech")
+async def speech(request: Request, user=Depends(get_verified_user)):
+    body = await request.body()
+    name = hashlib.sha256(body).hexdigest()
+
+    file_path = SPEECH_CACHE_DIR.joinpath(f"{name}.mp3")
+    file_body_path = SPEECH_CACHE_DIR.joinpath(f"{name}.json")
+
+    # Check if the file already exists in the cache
+    if file_path.is_file():
+        return FileResponse(file_path)
+
+    if app.state.config.TTS_ENGINE == "openai":
+        headers = {}
+        headers["Authorization"] = f"Bearer {app.state.config.TTS_OPENAI_API_KEY}"
+        headers["Content-Type"] = "application/json"
+
+        try:
+            body = body.decode("utf-8")
+            body = json.loads(body)
+            body["model"] = app.state.config.TTS_MODEL
+            body = json.dumps(body).encode("utf-8")
+        except Exception:
+            pass
+
+        r = None
+        try:
+            r = requests.post(
+                url=f"{app.state.config.TTS_OPENAI_API_BASE_URL}/audio/speech",
+                data=body,
+                headers=headers,
+                stream=True,
+            )
+
+            r.raise_for_status()
+
+            # Save the streaming content to a file
+            with open(file_path, "wb") as f:
+                for chunk in r.iter_content(chunk_size=8192):
+                    f.write(chunk)
+
+            with open(file_body_path, "w") as f:
+                json.dump(json.loads(body.decode("utf-8")), f)
+
+            # Return the saved file
+            return FileResponse(file_path)
+
+        except Exception as e:
+            log.exception(e)
+            error_detail = "Open WebUI: Server Connection Error"
+            if r is not None:
+                try:
+                    res = r.json()
+                    if "error" in res:
+                        error_detail = f"External: {res['error']['message']}"
+                except Exception:
+                    error_detail = f"External: {e}"
+
+            raise HTTPException(
+                status_code=r.status_code if r != None else 500,
+                detail=error_detail,
+            )
+
+    elif app.state.config.TTS_ENGINE == "elevenlabs":
+        payload = None
+        try:
+            payload = json.loads(body.decode("utf-8"))
+        except Exception as e:
+            log.exception(e)
+            raise HTTPException(status_code=400, detail="Invalid JSON payload")
+
+        voice_id = payload.get("voice", "")
+
+        if voice_id not in get_available_voices():
+            raise HTTPException(
+                status_code=400,
+                detail="Invalid voice id",
+            )
+
+        url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}"
+
+        headers = {
+            "Accept": "audio/mpeg",
+            "Content-Type": "application/json",
+            "xi-api-key": app.state.config.TTS_API_KEY,
+        }
+
+        data = {
+            "text": payload["input"],
+            "model_id": app.state.config.TTS_MODEL,
+            "voice_settings": {"stability": 0.5, "similarity_boost": 0.5},
+        }
+
+        try:
+            r = requests.post(url, json=data, headers=headers)
+
+            r.raise_for_status()
+
+            # Save the streaming content to a file
+            with open(file_path, "wb") as f:
+                for chunk in r.iter_content(chunk_size=8192):
+                    f.write(chunk)
+
+            with open(file_body_path, "w") as f:
+                json.dump(json.loads(body.decode("utf-8")), f)
+
+            # Return the saved file
+            return FileResponse(file_path)
+
+        except Exception as e:
+            log.exception(e)
+            error_detail = "Open WebUI: Server Connection Error"
+            if r is not None:
+                try:
+                    res = r.json()
+                    if "error" in res:
+                        error_detail = f"External: {res['error']['message']}"
+                except Exception:
+                    error_detail = f"External: {e}"
+
+            raise HTTPException(
+                status_code=r.status_code if r != None else 500,
+                detail=error_detail,
+            )
+
+    elif app.state.config.TTS_ENGINE == "azure":
+        payload = None
+        try:
+            payload = json.loads(body.decode("utf-8"))
+        except Exception as e:
+            log.exception(e)
+            raise HTTPException(status_code=400, detail="Invalid JSON payload")
+
+        region = app.state.config.TTS_AZURE_SPEECH_REGION
+        language = app.state.config.TTS_VOICE
+        locale = "-".join(app.state.config.TTS_VOICE.split("-")[:1])
+        output_format = app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT
+        url = f"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1"
+
+        headers = {
+            "Ocp-Apim-Subscription-Key": app.state.config.TTS_API_KEY,
+            "Content-Type": "application/ssml+xml",
+            "X-Microsoft-OutputFormat": output_format,
+        }
+
+        data = f"""<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="{locale}">
+                <voice name="{language}">{payload["input"]}</voice>
+            </speak>"""
+
+        response = requests.post(url, headers=headers, data=data)
+
+        if response.status_code == 200:
+            with open(file_path, "wb") as f:
+                f.write(response.content)
+            return FileResponse(file_path)
+        else:
+            log.error(f"Error synthesizing speech - {response.reason}")
+            raise HTTPException(
+                status_code=500, detail=f"Error synthesizing speech - {response.reason}"
+            )
+
+
+def transcribe(file_path):
+    print("transcribe", file_path)
+    filename = os.path.basename(file_path)
+    file_dir = os.path.dirname(file_path)
+    id = filename.split(".")[0]
+
+    if app.state.config.STT_ENGINE == "":
+        if app.state.faster_whisper_model is None:
+            set_faster_whisper_model(app.state.config.WHISPER_MODEL)
+
+        model = app.state.faster_whisper_model
+        segments, info = model.transcribe(file_path, beam_size=5)
+        log.info(
+            "Detected language '%s' with probability %f"
+            % (info.language, info.language_probability)
+        )
+
+        transcript = "".join([segment.text for segment in list(segments)])
+        data = {"text": transcript.strip()}
+
+        # save the transcript to a json file
+        transcript_file = f"{file_dir}/{id}.json"
+        with open(transcript_file, "w") as f:
+            json.dump(data, f)
+
+        log.debug(data)
+        return data
+    elif app.state.config.STT_ENGINE == "openai":
+        if is_mp4_audio(file_path):
+            print("is_mp4_audio")
+            os.rename(file_path, file_path.replace(".wav", ".mp4"))
+            # Convert MP4 audio file to WAV format
+            convert_mp4_to_wav(file_path.replace(".wav", ".mp4"), file_path)
+
+        headers = {"Authorization": f"Bearer {app.state.config.STT_OPENAI_API_KEY}"}
+
+        files = {"file": (filename, open(file_path, "rb"))}
+        data = {"model": app.state.config.STT_MODEL}
+
+        log.debug(files, data)
+
+        r = None
+        try:
+            r = requests.post(
+                url=f"{app.state.config.STT_OPENAI_API_BASE_URL}/audio/transcriptions",
+                headers=headers,
+                files=files,
+                data=data,
+            )
+
+            r.raise_for_status()
+
+            data = r.json()
+
+            # save the transcript to a json file
+            transcript_file = f"{file_dir}/{id}.json"
+            with open(transcript_file, "w") as f:
+                json.dump(data, f)
+
+            print(data)
+            return data
+        except Exception as e:
+            log.exception(e)
+            error_detail = "Open WebUI: Server Connection Error"
+            if r is not None:
+                try:
+                    res = r.json()
+                    if "error" in res:
+                        error_detail = f"External: {res['error']['message']}"
+                except Exception:
+                    error_detail = f"External: {e}"
+
+            raise Exception(error_detail)
+
+
+@app.post("/transcriptions")
+def transcription(
+    file: UploadFile = File(...),
+    user=Depends(get_verified_user),
+):
+    log.info(f"file.content_type: {file.content_type}")
+
+    if file.content_type not in ["audio/mpeg", "audio/wav", "audio/ogg", "audio/x-m4a"]:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.FILE_NOT_SUPPORTED,
+        )
+
+    try:
+        ext = file.filename.split(".")[-1]
+        id = uuid.uuid4()
+
+        filename = f"{id}.{ext}"
+        contents = file.file.read()
+
+        file_dir = f"{CACHE_DIR}/audio/transcriptions"
+        os.makedirs(file_dir, exist_ok=True)
+        file_path = f"{file_dir}/{filename}"
+
+        with open(file_path, "wb") as f:
+            f.write(contents)
+
+        try:
+            if os.path.getsize(file_path) > MAX_FILE_SIZE:  # file is bigger than 25MB
+                log.debug(f"File size is larger than {MAX_FILE_SIZE_MB}MB")
+                audio = AudioSegment.from_file(file_path)
+                audio = audio.set_frame_rate(16000).set_channels(1)  # Compress audio
+                compressed_path = f"{file_dir}/{id}_compressed.opus"
+                audio.export(compressed_path, format="opus", bitrate="32k")
+                log.debug(f"Compressed audio to {compressed_path}")
+                file_path = compressed_path
+
+                if (
+                    os.path.getsize(file_path) > MAX_FILE_SIZE
+                ):  # Still larger than 25MB after compression
+                    log.debug(
+                        f"Compressed file size is still larger than {MAX_FILE_SIZE_MB}MB: {os.path.getsize(file_path)}"
+                    )
+                    raise HTTPException(
+                        status_code=status.HTTP_400_BAD_REQUEST,
+                        detail=ERROR_MESSAGES.FILE_TOO_LARGE(
+                            size=f"{MAX_FILE_SIZE_MB}MB"
+                        ),
+                    )
+
+                data = transcribe(file_path)
+            else:
+                data = transcribe(file_path)
+
+            file_path = file_path.split("/")[-1]
+            return {**data, "filename": file_path}
+        except Exception as e:
+            log.exception(e)
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT(e),
+            )
+
+    except Exception as e:
+        log.exception(e)
+
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+def get_available_models() -> list[dict]:
+    if app.state.config.TTS_ENGINE == "openai":
+        return [{"id": "tts-1"}, {"id": "tts-1-hd"}]
+    elif app.state.config.TTS_ENGINE == "elevenlabs":
+        headers = {
+            "xi-api-key": app.state.config.TTS_API_KEY,
+            "Content-Type": "application/json",
+        }
+
+        try:
+            response = requests.get(
+                "https://api.elevenlabs.io/v1/models", headers=headers, timeout=5
+            )
+            response.raise_for_status()
+            models = response.json()
+            return [
+                {"name": model["name"], "id": model["model_id"]} for model in models
+            ]
+        except requests.RequestException as e:
+            log.error(f"Error fetching voices: {str(e)}")
+    return []
+
+
+@app.get("/models")
+async def get_models(user=Depends(get_verified_user)):
+    return {"models": get_available_models()}
+
+
+def get_available_voices() -> dict:
+    """Returns {voice_id: voice_name} dict"""
+    ret = {}
+    if app.state.config.TTS_ENGINE == "openai":
+        ret = {
+            "alloy": "alloy",
+            "echo": "echo",
+            "fable": "fable",
+            "onyx": "onyx",
+            "nova": "nova",
+            "shimmer": "shimmer",
+        }
+    elif app.state.config.TTS_ENGINE == "elevenlabs":
+        try:
+            ret = get_elevenlabs_voices()
+        except Exception:
+            # Avoided @lru_cache with exception
+            pass
+    elif app.state.config.TTS_ENGINE == "azure":
+        try:
+            region = app.state.config.TTS_AZURE_SPEECH_REGION
+            url = f"https://{region}.tts.speech.microsoft.com/cognitiveservices/voices/list"
+            headers = {"Ocp-Apim-Subscription-Key": app.state.config.TTS_API_KEY}
+
+            response = requests.get(url, headers=headers)
+            response.raise_for_status()
+            voices = response.json()
+            for voice in voices:
+                ret[voice["ShortName"]] = (
+                    f"{voice['DisplayName']} ({voice['ShortName']})"
+                )
+        except requests.RequestException as e:
+            log.error(f"Error fetching voices: {str(e)}")
+
+    return ret
+
+
+@lru_cache
+def get_elevenlabs_voices() -> dict:
+    """
+    Note, set the following in your .env file to use Elevenlabs:
+    AUDIO_TTS_ENGINE=elevenlabs
+    AUDIO_TTS_API_KEY=sk_...  # Your Elevenlabs API key
+    AUDIO_TTS_VOICE=EXAVITQu4vr4xnSDxMaL  # From https://api.elevenlabs.io/v1/voices
+    AUDIO_TTS_MODEL=eleven_multilingual_v2
+    """
+    headers = {
+        "xi-api-key": app.state.config.TTS_API_KEY,
+        "Content-Type": "application/json",
+    }
+    try:
+        # TODO: Add retries
+        response = requests.get("https://api.elevenlabs.io/v1/voices", headers=headers)
+        response.raise_for_status()
+        voices_data = response.json()
+
+        voices = {}
+        for voice in voices_data.get("voices", []):
+            voices[voice["voice_id"]] = voice["name"]
+    except requests.RequestException as e:
+        # Avoid @lru_cache with exception
+        log.error(f"Error fetching voices: {str(e)}")
+        raise RuntimeError(f"Error fetching voices: {str(e)}")
+
+    return voices
+
+
+@app.get("/voices")
+async def get_voices(user=Depends(get_verified_user)):
+    return {"voices": [{"id": k, "name": v} for k, v in get_available_voices().items()]}
diff --git a/backend/open_webui/apps/images/main.py b/backend/open_webui/apps/images/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..56b251ef6f9df63e67c6af71e9637a7fb817a962
--- /dev/null
+++ b/backend/open_webui/apps/images/main.py
@@ -0,0 +1,598 @@
+import asyncio
+import base64
+import json
+import logging
+import mimetypes
+import re
+import uuid
+from pathlib import Path
+from typing import Optional
+
+import requests
+from open_webui.apps.images.utils.comfyui import (
+    ComfyUIGenerateImageForm,
+    ComfyUIWorkflow,
+    comfyui_generate_image,
+)
+from open_webui.config import (
+    AUTOMATIC1111_API_AUTH,
+    AUTOMATIC1111_BASE_URL,
+    AUTOMATIC1111_CFG_SCALE,
+    AUTOMATIC1111_SAMPLER,
+    AUTOMATIC1111_SCHEDULER,
+    CACHE_DIR,
+    COMFYUI_BASE_URL,
+    COMFYUI_WORKFLOW,
+    COMFYUI_WORKFLOW_NODES,
+    CORS_ALLOW_ORIGIN,
+    ENABLE_IMAGE_GENERATION,
+    IMAGE_GENERATION_ENGINE,
+    IMAGE_GENERATION_MODEL,
+    IMAGE_SIZE,
+    IMAGE_STEPS,
+    IMAGES_OPENAI_API_BASE_URL,
+    IMAGES_OPENAI_API_KEY,
+    AppConfig,
+)
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.env import ENV, SRC_LOG_LEVELS
+from fastapi import Depends, FastAPI, HTTPException, Request
+from fastapi.middleware.cors import CORSMiddleware
+from pydantic import BaseModel
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["IMAGES"])
+
+IMAGE_CACHE_DIR = Path(CACHE_DIR).joinpath("./image/generations/")
+IMAGE_CACHE_DIR.mkdir(parents=True, exist_ok=True)
+
+app = FastAPI(docs_url="/docs" if ENV == "dev" else None, openapi_url="/openapi.json" if ENV == "dev" else None, redoc_url=None)
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=CORS_ALLOW_ORIGIN,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+app.state.config = AppConfig()
+
+app.state.config.ENGINE = IMAGE_GENERATION_ENGINE
+app.state.config.ENABLED = ENABLE_IMAGE_GENERATION
+
+app.state.config.OPENAI_API_BASE_URL = IMAGES_OPENAI_API_BASE_URL
+app.state.config.OPENAI_API_KEY = IMAGES_OPENAI_API_KEY
+
+app.state.config.MODEL = IMAGE_GENERATION_MODEL
+
+app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
+app.state.config.AUTOMATIC1111_API_AUTH = AUTOMATIC1111_API_AUTH
+app.state.config.AUTOMATIC1111_CFG_SCALE = AUTOMATIC1111_CFG_SCALE
+app.state.config.AUTOMATIC1111_SAMPLER = AUTOMATIC1111_SAMPLER
+app.state.config.AUTOMATIC1111_SCHEDULER = AUTOMATIC1111_SCHEDULER
+app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL
+app.state.config.COMFYUI_WORKFLOW = COMFYUI_WORKFLOW
+app.state.config.COMFYUI_WORKFLOW_NODES = COMFYUI_WORKFLOW_NODES
+
+app.state.config.IMAGE_SIZE = IMAGE_SIZE
+app.state.config.IMAGE_STEPS = IMAGE_STEPS
+
+
+@app.get("/config")
+async def get_config(request: Request, user=Depends(get_admin_user)):
+    return {
+        "enabled": app.state.config.ENABLED,
+        "engine": app.state.config.ENGINE,
+        "openai": {
+            "OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
+            "OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
+        },
+        "automatic1111": {
+            "AUTOMATIC1111_BASE_URL": app.state.config.AUTOMATIC1111_BASE_URL,
+            "AUTOMATIC1111_API_AUTH": app.state.config.AUTOMATIC1111_API_AUTH,
+            "AUTOMATIC1111_CFG_SCALE": app.state.config.AUTOMATIC1111_CFG_SCALE,
+            "AUTOMATIC1111_SAMPLER": app.state.config.AUTOMATIC1111_SAMPLER,
+            "AUTOMATIC1111_SCHEDULER": app.state.config.AUTOMATIC1111_SCHEDULER,
+        },
+        "comfyui": {
+            "COMFYUI_BASE_URL": app.state.config.COMFYUI_BASE_URL,
+            "COMFYUI_WORKFLOW": app.state.config.COMFYUI_WORKFLOW,
+            "COMFYUI_WORKFLOW_NODES": app.state.config.COMFYUI_WORKFLOW_NODES,
+        },
+    }
+
+
+class OpenAIConfigForm(BaseModel):
+    OPENAI_API_BASE_URL: str
+    OPENAI_API_KEY: str
+
+
+class Automatic1111ConfigForm(BaseModel):
+    AUTOMATIC1111_BASE_URL: str
+    AUTOMATIC1111_API_AUTH: str
+    AUTOMATIC1111_CFG_SCALE: Optional[str]
+    AUTOMATIC1111_SAMPLER: Optional[str]
+    AUTOMATIC1111_SCHEDULER: Optional[str]
+
+
+class ComfyUIConfigForm(BaseModel):
+    COMFYUI_BASE_URL: str
+    COMFYUI_WORKFLOW: str
+    COMFYUI_WORKFLOW_NODES: list[dict]
+
+
+class ConfigForm(BaseModel):
+    enabled: bool
+    engine: str
+    openai: OpenAIConfigForm
+    automatic1111: Automatic1111ConfigForm
+    comfyui: ComfyUIConfigForm
+
+
+@app.post("/config/update")
+async def update_config(form_data: ConfigForm, user=Depends(get_admin_user)):
+    app.state.config.ENGINE = form_data.engine
+    app.state.config.ENABLED = form_data.enabled
+
+    app.state.config.OPENAI_API_BASE_URL = form_data.openai.OPENAI_API_BASE_URL
+    app.state.config.OPENAI_API_KEY = form_data.openai.OPENAI_API_KEY
+
+    app.state.config.AUTOMATIC1111_BASE_URL = (
+        form_data.automatic1111.AUTOMATIC1111_BASE_URL
+    )
+    app.state.config.AUTOMATIC1111_API_AUTH = (
+        form_data.automatic1111.AUTOMATIC1111_API_AUTH
+    )
+
+    app.state.config.AUTOMATIC1111_CFG_SCALE = (
+        float(form_data.automatic1111.AUTOMATIC1111_CFG_SCALE)
+        if form_data.automatic1111.AUTOMATIC1111_CFG_SCALE
+        else None
+    )
+    app.state.config.AUTOMATIC1111_SAMPLER = (
+        form_data.automatic1111.AUTOMATIC1111_SAMPLER
+        if form_data.automatic1111.AUTOMATIC1111_SAMPLER
+        else None
+    )
+    app.state.config.AUTOMATIC1111_SCHEDULER = (
+        form_data.automatic1111.AUTOMATIC1111_SCHEDULER
+        if form_data.automatic1111.AUTOMATIC1111_SCHEDULER
+        else None
+    )
+
+    app.state.config.COMFYUI_BASE_URL = form_data.comfyui.COMFYUI_BASE_URL.strip("/")
+    app.state.config.COMFYUI_WORKFLOW = form_data.comfyui.COMFYUI_WORKFLOW
+    app.state.config.COMFYUI_WORKFLOW_NODES = form_data.comfyui.COMFYUI_WORKFLOW_NODES
+
+    return {
+        "enabled": app.state.config.ENABLED,
+        "engine": app.state.config.ENGINE,
+        "openai": {
+            "OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
+            "OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
+        },
+        "automatic1111": {
+            "AUTOMATIC1111_BASE_URL": app.state.config.AUTOMATIC1111_BASE_URL,
+            "AUTOMATIC1111_API_AUTH": app.state.config.AUTOMATIC1111_API_AUTH,
+            "AUTOMATIC1111_CFG_SCALE": app.state.config.AUTOMATIC1111_CFG_SCALE,
+            "AUTOMATIC1111_SAMPLER": app.state.config.AUTOMATIC1111_SAMPLER,
+            "AUTOMATIC1111_SCHEDULER": app.state.config.AUTOMATIC1111_SCHEDULER,
+        },
+        "comfyui": {
+            "COMFYUI_BASE_URL": app.state.config.COMFYUI_BASE_URL,
+            "COMFYUI_WORKFLOW": app.state.config.COMFYUI_WORKFLOW,
+            "COMFYUI_WORKFLOW_NODES": app.state.config.COMFYUI_WORKFLOW_NODES,
+        },
+    }
+
+
+def get_automatic1111_api_auth():
+    if app.state.config.AUTOMATIC1111_API_AUTH is None:
+        return ""
+    else:
+        auth1111_byte_string = app.state.config.AUTOMATIC1111_API_AUTH.encode("utf-8")
+        auth1111_base64_encoded_bytes = base64.b64encode(auth1111_byte_string)
+        auth1111_base64_encoded_string = auth1111_base64_encoded_bytes.decode("utf-8")
+        return f"Basic {auth1111_base64_encoded_string}"
+
+
+@app.get("/config/url/verify")
+async def verify_url(user=Depends(get_admin_user)):
+    if app.state.config.ENGINE == "automatic1111":
+        try:
+            r = requests.get(
+                url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
+                headers={"authorization": get_automatic1111_api_auth()},
+            )
+            r.raise_for_status()
+            return True
+        except Exception:
+            app.state.config.ENABLED = False
+            raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
+    elif app.state.config.ENGINE == "comfyui":
+        try:
+            r = requests.get(url=f"{app.state.config.COMFYUI_BASE_URL}/object_info")
+            r.raise_for_status()
+            return True
+        except Exception:
+            app.state.config.ENABLED = False
+            raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
+    else:
+        return True
+
+
+def set_image_model(model: str):
+    log.info(f"Setting image model to {model}")
+    app.state.config.MODEL = model
+    if app.state.config.ENGINE in ["", "automatic1111"]:
+        api_auth = get_automatic1111_api_auth()
+        r = requests.get(
+            url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
+            headers={"authorization": api_auth},
+        )
+        options = r.json()
+        if model != options["sd_model_checkpoint"]:
+            options["sd_model_checkpoint"] = model
+            r = requests.post(
+                url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
+                json=options,
+                headers={"authorization": api_auth},
+            )
+    return app.state.config.MODEL
+
+
+def get_image_model():
+    if app.state.config.ENGINE == "openai":
+        return app.state.config.MODEL if app.state.config.MODEL else "dall-e-2"
+    elif app.state.config.ENGINE == "comfyui":
+        return app.state.config.MODEL if app.state.config.MODEL else ""
+    elif app.state.config.ENGINE == "automatic1111" or app.state.config.ENGINE == "":
+        try:
+            r = requests.get(
+                url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
+                headers={"authorization": get_automatic1111_api_auth()},
+            )
+            options = r.json()
+            return options["sd_model_checkpoint"]
+        except Exception as e:
+            app.state.config.ENABLED = False
+            raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
+
+
+class ImageConfigForm(BaseModel):
+    MODEL: str
+    IMAGE_SIZE: str
+    IMAGE_STEPS: int
+
+
+@app.get("/image/config")
+async def get_image_config(user=Depends(get_admin_user)):
+    return {
+        "MODEL": app.state.config.MODEL,
+        "IMAGE_SIZE": app.state.config.IMAGE_SIZE,
+        "IMAGE_STEPS": app.state.config.IMAGE_STEPS,
+    }
+
+
+@app.post("/image/config/update")
+async def update_image_config(form_data: ImageConfigForm, user=Depends(get_admin_user)):
+
+    set_image_model(form_data.MODEL)
+
+    pattern = r"^\d+x\d+$"
+    if re.match(pattern, form_data.IMAGE_SIZE):
+        app.state.config.IMAGE_SIZE = form_data.IMAGE_SIZE
+    else:
+        raise HTTPException(
+            status_code=400,
+            detail=ERROR_MESSAGES.INCORRECT_FORMAT("  (e.g., 512x512)."),
+        )
+
+    if form_data.IMAGE_STEPS >= 0:
+        app.state.config.IMAGE_STEPS = form_data.IMAGE_STEPS
+    else:
+        raise HTTPException(
+            status_code=400,
+            detail=ERROR_MESSAGES.INCORRECT_FORMAT("  (e.g., 50)."),
+        )
+
+    return {
+        "MODEL": app.state.config.MODEL,
+        "IMAGE_SIZE": app.state.config.IMAGE_SIZE,
+        "IMAGE_STEPS": app.state.config.IMAGE_STEPS,
+    }
+
+
+@app.get("/models")
+def get_models(user=Depends(get_verified_user)):
+    try:
+        if app.state.config.ENGINE == "openai":
+            return [
+                {"id": "dall-e-2", "name": "DALL·E 2"},
+                {"id": "dall-e-3", "name": "DALL·E 3"},
+            ]
+        elif app.state.config.ENGINE == "comfyui":
+            # TODO - get models from comfyui
+            r = requests.get(url=f"{app.state.config.COMFYUI_BASE_URL}/object_info")
+            info = r.json()
+
+            workflow = json.loads(app.state.config.COMFYUI_WORKFLOW)
+            model_node_id = None
+
+            for node in app.state.config.COMFYUI_WORKFLOW_NODES:
+                if node["type"] == "model":
+                    if node["node_ids"]:
+                        model_node_id = node["node_ids"][0]
+                    break
+
+            if model_node_id:
+                model_list_key = None
+
+                print(workflow[model_node_id]["class_type"])
+                for key in info[workflow[model_node_id]["class_type"]]["input"][
+                    "required"
+                ]:
+                    if "_name" in key:
+                        model_list_key = key
+                        break
+
+                if model_list_key:
+                    return list(
+                        map(
+                            lambda model: {"id": model, "name": model},
+                            info[workflow[model_node_id]["class_type"]]["input"][
+                                "required"
+                            ][model_list_key][0],
+                        )
+                    )
+            else:
+                return list(
+                    map(
+                        lambda model: {"id": model, "name": model},
+                        info["CheckpointLoaderSimple"]["input"]["required"][
+                            "ckpt_name"
+                        ][0],
+                    )
+                )
+        elif (
+            app.state.config.ENGINE == "automatic1111" or app.state.config.ENGINE == ""
+        ):
+            r = requests.get(
+                url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/sd-models",
+                headers={"authorization": get_automatic1111_api_auth()},
+            )
+            models = r.json()
+            return list(
+                map(
+                    lambda model: {"id": model["title"], "name": model["model_name"]},
+                    models,
+                )
+            )
+    except Exception as e:
+        app.state.config.ENABLED = False
+        raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
+
+
+class GenerateImageForm(BaseModel):
+    model: Optional[str] = None
+    prompt: str
+    size: Optional[str] = None
+    n: int = 1
+    negative_prompt: Optional[str] = None
+
+
+def save_b64_image(b64_str):
+    try:
+        image_id = str(uuid.uuid4())
+
+        if "," in b64_str:
+            header, encoded = b64_str.split(",", 1)
+            mime_type = header.split(";")[0]
+
+            img_data = base64.b64decode(encoded)
+            image_format = mimetypes.guess_extension(mime_type)
+
+            image_filename = f"{image_id}{image_format}"
+            file_path = IMAGE_CACHE_DIR / f"{image_filename}"
+            with open(file_path, "wb") as f:
+                f.write(img_data)
+            return image_filename
+        else:
+            image_filename = f"{image_id}.png"
+            file_path = IMAGE_CACHE_DIR.joinpath(image_filename)
+
+            img_data = base64.b64decode(b64_str)
+
+            # Write the image data to a file
+            with open(file_path, "wb") as f:
+                f.write(img_data)
+            return image_filename
+
+    except Exception as e:
+        log.exception(f"Error saving image: {e}")
+        return None
+
+
+def save_url_image(url):
+    image_id = str(uuid.uuid4())
+    try:
+        r = requests.get(url)
+        r.raise_for_status()
+        if r.headers["content-type"].split("/")[0] == "image":
+            mime_type = r.headers["content-type"]
+            image_format = mimetypes.guess_extension(mime_type)
+
+            if not image_format:
+                raise ValueError("Could not determine image type from MIME type")
+
+            image_filename = f"{image_id}{image_format}"
+
+            file_path = IMAGE_CACHE_DIR.joinpath(f"{image_filename}")
+            with open(file_path, "wb") as image_file:
+                for chunk in r.iter_content(chunk_size=8192):
+                    image_file.write(chunk)
+            return image_filename
+        else:
+            log.error("Url does not point to an image.")
+            return None
+
+    except Exception as e:
+        log.exception(f"Error saving image: {e}")
+        return None
+
+
+@app.post("/generations")
+async def image_generations(
+    form_data: GenerateImageForm,
+    user=Depends(get_verified_user),
+):
+    width, height = tuple(map(int, app.state.config.IMAGE_SIZE.split("x")))
+
+    r = None
+    try:
+        if app.state.config.ENGINE == "openai":
+            headers = {}
+            headers["Authorization"] = f"Bearer {app.state.config.OPENAI_API_KEY}"
+            headers["Content-Type"] = "application/json"
+
+            data = {
+                "model": (
+                    app.state.config.MODEL
+                    if app.state.config.MODEL != ""
+                    else "dall-e-2"
+                ),
+                "prompt": form_data.prompt,
+                "n": form_data.n,
+                "size": (
+                    form_data.size if form_data.size else app.state.config.IMAGE_SIZE
+                ),
+                "response_format": "b64_json",
+            }
+
+            # Use asyncio.to_thread for the requests.post call
+            r = await asyncio.to_thread(
+                requests.post,
+                url=f"{app.state.config.OPENAI_API_BASE_URL}/images/generations",
+                json=data,
+                headers=headers,
+            )
+
+            r.raise_for_status()
+            res = r.json()
+
+            images = []
+
+            for image in res["data"]:
+                image_filename = save_b64_image(image["b64_json"])
+                images.append({"url": f"/cache/image/generations/{image_filename}"})
+                file_body_path = IMAGE_CACHE_DIR.joinpath(f"{image_filename}.json")
+
+                with open(file_body_path, "w") as f:
+                    json.dump(data, f)
+
+            return images
+
+        elif app.state.config.ENGINE == "comfyui":
+            data = {
+                "prompt": form_data.prompt,
+                "width": width,
+                "height": height,
+                "n": form_data.n,
+            }
+
+            if app.state.config.IMAGE_STEPS is not None:
+                data["steps"] = app.state.config.IMAGE_STEPS
+
+            if form_data.negative_prompt is not None:
+                data["negative_prompt"] = form_data.negative_prompt
+
+            form_data = ComfyUIGenerateImageForm(
+                **{
+                    "workflow": ComfyUIWorkflow(
+                        **{
+                            "workflow": app.state.config.COMFYUI_WORKFLOW,
+                            "nodes": app.state.config.COMFYUI_WORKFLOW_NODES,
+                        }
+                    ),
+                    **data,
+                }
+            )
+            res = await comfyui_generate_image(
+                app.state.config.MODEL,
+                form_data,
+                user.id,
+                app.state.config.COMFYUI_BASE_URL,
+            )
+            log.debug(f"res: {res}")
+
+            images = []
+
+            for image in res["data"]:
+                image_filename = save_url_image(image["url"])
+                images.append({"url": f"/cache/image/generations/{image_filename}"})
+                file_body_path = IMAGE_CACHE_DIR.joinpath(f"{image_filename}.json")
+
+                with open(file_body_path, "w") as f:
+                    json.dump(form_data.model_dump(exclude_none=True), f)
+
+            log.debug(f"images: {images}")
+            return images
+        elif (
+            app.state.config.ENGINE == "automatic1111" or app.state.config.ENGINE == ""
+        ):
+            if form_data.model:
+                set_image_model(form_data.model)
+
+            data = {
+                "prompt": form_data.prompt,
+                "batch_size": form_data.n,
+                "width": width,
+                "height": height,
+            }
+
+            if app.state.config.IMAGE_STEPS is not None:
+                data["steps"] = app.state.config.IMAGE_STEPS
+
+            if form_data.negative_prompt is not None:
+                data["negative_prompt"] = form_data.negative_prompt
+
+            if app.state.config.AUTOMATIC1111_CFG_SCALE:
+                data["cfg_scale"] = app.state.config.AUTOMATIC1111_CFG_SCALE
+
+            if app.state.config.AUTOMATIC1111_SAMPLER:
+                data["sampler_name"] = app.state.config.AUTOMATIC1111_SAMPLER
+
+            if app.state.config.AUTOMATIC1111_SCHEDULER:
+                data["scheduler"] = app.state.config.AUTOMATIC1111_SCHEDULER
+
+            # Use asyncio.to_thread for the requests.post call
+            r = await asyncio.to_thread(
+                requests.post,
+                url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/txt2img",
+                json=data,
+                headers={"authorization": get_automatic1111_api_auth()},
+            )
+
+            res = r.json()
+            log.debug(f"res: {res}")
+
+            images = []
+
+            for image in res["images"]:
+                image_filename = save_b64_image(image)
+                images.append({"url": f"/cache/image/generations/{image_filename}"})
+                file_body_path = IMAGE_CACHE_DIR.joinpath(f"{image_filename}.json")
+
+                with open(file_body_path, "w") as f:
+                    json.dump({**data, "info": res["info"]}, f)
+
+            return images
+    except Exception as e:
+        error = e
+        if r != None:
+            data = r.json()
+            if "error" in data:
+                error = data["error"]["message"]
+        raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(error))
diff --git a/backend/open_webui/apps/images/utils/comfyui.py b/backend/open_webui/apps/images/utils/comfyui.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c421d7c5290ba7564acc69303241dcdb67dae18
--- /dev/null
+++ b/backend/open_webui/apps/images/utils/comfyui.py
@@ -0,0 +1,186 @@
+import asyncio
+import json
+import logging
+import random
+import urllib.parse
+import urllib.request
+from typing import Optional
+
+import websocket  # NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
+from open_webui.env import SRC_LOG_LEVELS
+from pydantic import BaseModel
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["COMFYUI"])
+
+default_headers = {"User-Agent": "Mozilla/5.0"}
+
+
+def queue_prompt(prompt, client_id, base_url):
+    log.info("queue_prompt")
+    p = {"prompt": prompt, "client_id": client_id}
+    data = json.dumps(p).encode("utf-8")
+    log.debug(f"queue_prompt data: {data}")
+    try:
+        req = urllib.request.Request(
+            f"{base_url}/prompt", data=data, headers=default_headers
+        )
+        response = urllib.request.urlopen(req).read()
+        return json.loads(response)
+    except Exception as e:
+        log.exception(f"Error while queuing prompt: {e}")
+        raise e
+
+
+def get_image(filename, subfolder, folder_type, base_url):
+    log.info("get_image")
+    data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
+    url_values = urllib.parse.urlencode(data)
+    req = urllib.request.Request(
+        f"{base_url}/view?{url_values}", headers=default_headers
+    )
+    with urllib.request.urlopen(req) as response:
+        return response.read()
+
+
+def get_image_url(filename, subfolder, folder_type, base_url):
+    log.info("get_image")
+    data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
+    url_values = urllib.parse.urlencode(data)
+    return f"{base_url}/view?{url_values}"
+
+
+def get_history(prompt_id, base_url):
+    log.info("get_history")
+
+    req = urllib.request.Request(
+        f"{base_url}/history/{prompt_id}", headers=default_headers
+    )
+    with urllib.request.urlopen(req) as response:
+        return json.loads(response.read())
+
+
+def get_images(ws, prompt, client_id, base_url):
+    prompt_id = queue_prompt(prompt, client_id, base_url)["prompt_id"]
+    output_images = []
+    while True:
+        out = ws.recv()
+        if isinstance(out, str):
+            message = json.loads(out)
+            if message["type"] == "executing":
+                data = message["data"]
+                if data["node"] is None and data["prompt_id"] == prompt_id:
+                    break  # Execution is done
+        else:
+            continue  # previews are binary data
+
+    history = get_history(prompt_id, base_url)[prompt_id]
+    for o in history["outputs"]:
+        for node_id in history["outputs"]:
+            node_output = history["outputs"][node_id]
+            if "images" in node_output:
+                for image in node_output["images"]:
+                    url = get_image_url(
+                        image["filename"], image["subfolder"], image["type"], base_url
+                    )
+                    output_images.append({"url": url})
+    return {"data": output_images}
+
+
+class ComfyUINodeInput(BaseModel):
+    type: Optional[str] = None
+    node_ids: list[str] = []
+    key: Optional[str] = "text"
+    value: Optional[str] = None
+
+
+class ComfyUIWorkflow(BaseModel):
+    workflow: str
+    nodes: list[ComfyUINodeInput]
+
+
+class ComfyUIGenerateImageForm(BaseModel):
+    workflow: ComfyUIWorkflow
+
+    prompt: str
+    negative_prompt: Optional[str] = None
+    width: int
+    height: int
+    n: int = 1
+
+    steps: Optional[int] = None
+    seed: Optional[int] = None
+
+
+async def comfyui_generate_image(
+    model: str, payload: ComfyUIGenerateImageForm, client_id, base_url
+):
+    ws_url = base_url.replace("http://", "ws://").replace("https://", "wss://")
+    workflow = json.loads(payload.workflow.workflow)
+
+    for node in payload.workflow.nodes:
+        if node.type:
+            if node.type == "model":
+                for node_id in node.node_ids:
+                    workflow[node_id]["inputs"][node.key] = model
+            elif node.type == "prompt":
+                for node_id in node.node_ids:
+                    workflow[node_id]["inputs"][
+                        node.key if node.key else "text"
+                    ] = payload.prompt
+            elif node.type == "negative_prompt":
+                for node_id in node.node_ids:
+                    workflow[node_id]["inputs"][
+                        node.key if node.key else "text"
+                    ] = payload.negative_prompt
+            elif node.type == "width":
+                for node_id in node.node_ids:
+                    workflow[node_id]["inputs"][
+                        node.key if node.key else "width"
+                    ] = payload.width
+            elif node.type == "height":
+                for node_id in node.node_ids:
+                    workflow[node_id]["inputs"][
+                        node.key if node.key else "height"
+                    ] = payload.height
+            elif node.type == "n":
+                for node_id in node.node_ids:
+                    workflow[node_id]["inputs"][
+                        node.key if node.key else "batch_size"
+                    ] = payload.n
+            elif node.type == "steps":
+                for node_id in node.node_ids:
+                    workflow[node_id]["inputs"][
+                        node.key if node.key else "steps"
+                    ] = payload.steps
+            elif node.type == "seed":
+                seed = (
+                    payload.seed
+                    if payload.seed
+                    else random.randint(0, 18446744073709551614)
+                )
+                for node_id in node.node_ids:
+                    workflow[node_id]["inputs"][node.key] = seed
+        else:
+            for node_id in node.node_ids:
+                workflow[node_id]["inputs"][node.key] = node.value
+
+    try:
+        ws = websocket.WebSocket()
+        ws.connect(f"{ws_url}/ws?clientId={client_id}")
+        log.info("WebSocket connection established.")
+    except Exception as e:
+        log.exception(f"Failed to connect to WebSocket server: {e}")
+        return None
+
+    try:
+        log.info("Sending workflow to WebSocket server.")
+        log.info(f"Workflow: {workflow}")
+        images = await asyncio.to_thread(get_images, ws, workflow, client_id, base_url)
+    except Exception as e:
+        log.exception(f"Error while receiving images: {e}")
+        images = None
+
+    ws.close()
+
+    return images
diff --git a/backend/open_webui/apps/ollama/main.py b/backend/open_webui/apps/ollama/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..8976d55b4081dc673a08e6cdfef746ec3d160890
--- /dev/null
+++ b/backend/open_webui/apps/ollama/main.py
@@ -0,0 +1,1123 @@
+import asyncio
+import json
+import logging
+import os
+import random
+import re
+import time
+from typing import Optional, Union
+from urllib.parse import urlparse
+
+import aiohttp
+import requests
+from open_webui.apps.webui.models.models import Models
+from open_webui.config import (
+    CORS_ALLOW_ORIGIN,
+    ENABLE_MODEL_FILTER,
+    ENABLE_OLLAMA_API,
+    MODEL_FILTER_LIST,
+    OLLAMA_BASE_URLS,
+    UPLOAD_DIR,
+    AppConfig,
+)
+from open_webui.env import AIOHTTP_CLIENT_TIMEOUT
+
+
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.env import ENV, SRC_LOG_LEVELS
+from fastapi import Depends, FastAPI, File, HTTPException, Request, UploadFile
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import StreamingResponse
+from pydantic import BaseModel, ConfigDict
+from starlette.background import BackgroundTask
+
+
+from open_webui.utils.misc import (
+    calculate_sha256,
+)
+from open_webui.utils.payload import (
+    apply_model_params_to_body_ollama,
+    apply_model_params_to_body_openai,
+    apply_model_system_prompt_to_body,
+)
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["OLLAMA"])
+
+
+app = FastAPI(docs_url="/docs" if ENV == "dev" else None, openapi_url="/openapi.json" if ENV == "dev" else None, redoc_url=None)
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=CORS_ALLOW_ORIGIN,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+app.state.config = AppConfig()
+
+app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
+app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
+
+app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API
+app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
+app.state.MODELS = {}
+
+
+# TODO: Implement a more intelligent load balancing mechanism for distributing requests among multiple backend instances.
+# Current implementation uses a simple round-robin approach (random.choice). Consider incorporating algorithms like weighted round-robin,
+# least connections, or least response time for better resource utilization and performance optimization.
+
+
+@app.middleware("http")
+async def check_url(request: Request, call_next):
+    if len(app.state.MODELS) == 0:
+        await get_all_models()
+    else:
+        pass
+
+    response = await call_next(request)
+    return response
+
+
+@app.head("/")
+@app.get("/")
+async def get_status():
+    return {"status": True}
+
+
+@app.get("/config")
+async def get_config(user=Depends(get_admin_user)):
+    return {"ENABLE_OLLAMA_API": app.state.config.ENABLE_OLLAMA_API}
+
+
+class OllamaConfigForm(BaseModel):
+    enable_ollama_api: Optional[bool] = None
+
+
+@app.post("/config/update")
+async def update_config(form_data: OllamaConfigForm, user=Depends(get_admin_user)):
+    app.state.config.ENABLE_OLLAMA_API = form_data.enable_ollama_api
+    return {"ENABLE_OLLAMA_API": app.state.config.ENABLE_OLLAMA_API}
+
+
+@app.get("/urls")
+async def get_ollama_api_urls(user=Depends(get_admin_user)):
+    return {"OLLAMA_BASE_URLS": app.state.config.OLLAMA_BASE_URLS}
+
+
+class UrlUpdateForm(BaseModel):
+    urls: list[str]
+
+
+@app.post("/urls/update")
+async def update_ollama_api_url(form_data: UrlUpdateForm, user=Depends(get_admin_user)):
+    app.state.config.OLLAMA_BASE_URLS = form_data.urls
+
+    log.info(f"app.state.config.OLLAMA_BASE_URLS: {app.state.config.OLLAMA_BASE_URLS}")
+    return {"OLLAMA_BASE_URLS": app.state.config.OLLAMA_BASE_URLS}
+
+
+async def fetch_url(url):
+    timeout = aiohttp.ClientTimeout(total=3)
+    try:
+        async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
+            async with session.get(url) as response:
+                return await response.json()
+    except Exception as e:
+        # Handle connection error here
+        log.error(f"Connection error: {e}")
+        return None
+
+
+async def cleanup_response(
+    response: Optional[aiohttp.ClientResponse],
+    session: Optional[aiohttp.ClientSession],
+):
+    if response:
+        response.close()
+    if session:
+        await session.close()
+
+
+async def post_streaming_url(
+    url: str, payload: Union[str, bytes], stream: bool = True, content_type=None
+):
+    r = None
+    try:
+        session = aiohttp.ClientSession(
+            trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
+        )
+        r = await session.post(
+            url,
+            data=payload,
+            headers={"Content-Type": "application/json"},
+        )
+        r.raise_for_status()
+
+        if stream:
+            headers = dict(r.headers)
+            if content_type:
+                headers["Content-Type"] = content_type
+            return StreamingResponse(
+                r.content,
+                status_code=r.status,
+                headers=headers,
+                background=BackgroundTask(
+                    cleanup_response, response=r, session=session
+                ),
+            )
+        else:
+            res = await r.json()
+            await cleanup_response(r, session)
+            return res
+
+    except Exception as e:
+        error_detail = "Open WebUI: Server Connection Error"
+        if r is not None:
+            try:
+                res = await r.json()
+                if "error" in res:
+                    error_detail = f"Ollama: {res['error']}"
+            except Exception:
+                error_detail = f"Ollama: {e}"
+
+        raise HTTPException(
+            status_code=r.status if r else 500,
+            detail=error_detail,
+        )
+
+
+def merge_models_lists(model_lists):
+    merged_models = {}
+
+    for idx, model_list in enumerate(model_lists):
+        if model_list is not None:
+            for model in model_list:
+                digest = model["digest"]
+                if digest not in merged_models:
+                    model["urls"] = [idx]
+                    merged_models[digest] = model
+                else:
+                    merged_models[digest]["urls"].append(idx)
+
+    return list(merged_models.values())
+
+
+async def get_all_models():
+    log.info("get_all_models()")
+
+    if app.state.config.ENABLE_OLLAMA_API:
+        tasks = [
+            fetch_url(f"{url}/api/tags") for url in app.state.config.OLLAMA_BASE_URLS
+        ]
+        responses = await asyncio.gather(*tasks)
+
+        models = {
+            "models": merge_models_lists(
+                map(
+                    lambda response: response["models"] if response else None, responses
+                )
+            )
+        }
+
+    else:
+        models = {"models": []}
+
+    app.state.MODELS = {model["model"]: model for model in models["models"]}
+
+    return models
+
+
+@app.get("/api/tags")
+@app.get("/api/tags/{url_idx}")
+async def get_ollama_tags(
+    url_idx: Optional[int] = None, user=Depends(get_verified_user)
+):
+    if url_idx is None:
+        models = await get_all_models()
+
+        if app.state.config.ENABLE_MODEL_FILTER:
+            if user.role == "user":
+                models["models"] = list(
+                    filter(
+                        lambda model: model["name"]
+                        in app.state.config.MODEL_FILTER_LIST,
+                        models["models"],
+                    )
+                )
+                return models
+        return models
+    else:
+        url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+
+        r = None
+        try:
+            r = requests.request(method="GET", url=f"{url}/api/tags")
+            r.raise_for_status()
+
+            return r.json()
+        except Exception as e:
+            log.exception(e)
+            error_detail = "Open WebUI: Server Connection Error"
+            if r is not None:
+                try:
+                    res = r.json()
+                    if "error" in res:
+                        error_detail = f"Ollama: {res['error']}"
+                except Exception:
+                    error_detail = f"Ollama: {e}"
+
+            raise HTTPException(
+                status_code=r.status_code if r else 500,
+                detail=error_detail,
+            )
+
+
+@app.get("/api/version")
+@app.get("/api/version/{url_idx}")
+async def get_ollama_versions(url_idx: Optional[int] = None):
+    if app.state.config.ENABLE_OLLAMA_API:
+        if url_idx is None:
+            # returns lowest version
+            tasks = [
+                fetch_url(f"{url}/api/version")
+                for url in app.state.config.OLLAMA_BASE_URLS
+            ]
+            responses = await asyncio.gather(*tasks)
+            responses = list(filter(lambda x: x is not None, responses))
+
+            if len(responses) > 0:
+                lowest_version = min(
+                    responses,
+                    key=lambda x: tuple(
+                        map(int, re.sub(r"^v|-.*", "", x["version"]).split("."))
+                    ),
+                )
+
+                return {"version": lowest_version["version"]}
+            else:
+                raise HTTPException(
+                    status_code=500,
+                    detail=ERROR_MESSAGES.OLLAMA_NOT_FOUND,
+                )
+        else:
+            url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+
+            r = None
+            try:
+                r = requests.request(method="GET", url=f"{url}/api/version")
+                r.raise_for_status()
+
+                return r.json()
+            except Exception as e:
+                log.exception(e)
+                error_detail = "Open WebUI: Server Connection Error"
+                if r is not None:
+                    try:
+                        res = r.json()
+                        if "error" in res:
+                            error_detail = f"Ollama: {res['error']}"
+                    except Exception:
+                        error_detail = f"Ollama: {e}"
+
+                raise HTTPException(
+                    status_code=r.status_code if r else 500,
+                    detail=error_detail,
+                )
+    else:
+        return {"version": False}
+
+
+class ModelNameForm(BaseModel):
+    name: str
+
+
+@app.post("/api/pull")
+@app.post("/api/pull/{url_idx}")
+async def pull_model(
+    form_data: ModelNameForm, url_idx: int = 0, user=Depends(get_admin_user)
+):
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+    log.info(f"url: {url}")
+
+    # Admin should be able to pull models from any source
+    payload = {**form_data.model_dump(exclude_none=True), "insecure": True}
+
+    return await post_streaming_url(f"{url}/api/pull", json.dumps(payload))
+
+
+class PushModelForm(BaseModel):
+    name: str
+    insecure: Optional[bool] = None
+    stream: Optional[bool] = None
+
+
+@app.delete("/api/push")
+@app.delete("/api/push/{url_idx}")
+async def push_model(
+    form_data: PushModelForm,
+    url_idx: Optional[int] = None,
+    user=Depends(get_admin_user),
+):
+    if url_idx is None:
+        if form_data.name in app.state.MODELS:
+            url_idx = app.state.MODELS[form_data.name]["urls"][0]
+        else:
+            raise HTTPException(
+                status_code=400,
+                detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.name),
+            )
+
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+    log.debug(f"url: {url}")
+
+    return await post_streaming_url(
+        f"{url}/api/push", form_data.model_dump_json(exclude_none=True).encode()
+    )
+
+
+class CreateModelForm(BaseModel):
+    name: str
+    modelfile: Optional[str] = None
+    stream: Optional[bool] = None
+    path: Optional[str] = None
+
+
+@app.post("/api/create")
+@app.post("/api/create/{url_idx}")
+async def create_model(
+    form_data: CreateModelForm, url_idx: int = 0, user=Depends(get_admin_user)
+):
+    log.debug(f"form_data: {form_data}")
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+    log.info(f"url: {url}")
+
+    return await post_streaming_url(
+        f"{url}/api/create", form_data.model_dump_json(exclude_none=True).encode()
+    )
+
+
+class CopyModelForm(BaseModel):
+    source: str
+    destination: str
+
+
+@app.post("/api/copy")
+@app.post("/api/copy/{url_idx}")
+async def copy_model(
+    form_data: CopyModelForm,
+    url_idx: Optional[int] = None,
+    user=Depends(get_admin_user),
+):
+    if url_idx is None:
+        if form_data.source in app.state.MODELS:
+            url_idx = app.state.MODELS[form_data.source]["urls"][0]
+        else:
+            raise HTTPException(
+                status_code=400,
+                detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.source),
+            )
+
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+    log.info(f"url: {url}")
+    r = requests.request(
+        method="POST",
+        url=f"{url}/api/copy",
+        headers={"Content-Type": "application/json"},
+        data=form_data.model_dump_json(exclude_none=True).encode(),
+    )
+
+    try:
+        r.raise_for_status()
+
+        log.debug(f"r.text: {r.text}")
+
+        return True
+    except Exception as e:
+        log.exception(e)
+        error_detail = "Open WebUI: Server Connection Error"
+        if r is not None:
+            try:
+                res = r.json()
+                if "error" in res:
+                    error_detail = f"Ollama: {res['error']}"
+            except Exception:
+                error_detail = f"Ollama: {e}"
+
+        raise HTTPException(
+            status_code=r.status_code if r else 500,
+            detail=error_detail,
+        )
+
+
+@app.delete("/api/delete")
+@app.delete("/api/delete/{url_idx}")
+async def delete_model(
+    form_data: ModelNameForm,
+    url_idx: Optional[int] = None,
+    user=Depends(get_admin_user),
+):
+    if url_idx is None:
+        if form_data.name in app.state.MODELS:
+            url_idx = app.state.MODELS[form_data.name]["urls"][0]
+        else:
+            raise HTTPException(
+                status_code=400,
+                detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.name),
+            )
+
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+    log.info(f"url: {url}")
+
+    r = requests.request(
+        method="DELETE",
+        url=f"{url}/api/delete",
+        headers={"Content-Type": "application/json"},
+        data=form_data.model_dump_json(exclude_none=True).encode(),
+    )
+    try:
+        r.raise_for_status()
+
+        log.debug(f"r.text: {r.text}")
+
+        return True
+    except Exception as e:
+        log.exception(e)
+        error_detail = "Open WebUI: Server Connection Error"
+        if r is not None:
+            try:
+                res = r.json()
+                if "error" in res:
+                    error_detail = f"Ollama: {res['error']}"
+            except Exception:
+                error_detail = f"Ollama: {e}"
+
+        raise HTTPException(
+            status_code=r.status_code if r else 500,
+            detail=error_detail,
+        )
+
+
+@app.post("/api/show")
+async def show_model_info(form_data: ModelNameForm, user=Depends(get_verified_user)):
+    if form_data.name not in app.state.MODELS:
+        raise HTTPException(
+            status_code=400,
+            detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.name),
+        )
+
+    url_idx = random.choice(app.state.MODELS[form_data.name]["urls"])
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+    log.info(f"url: {url}")
+
+    r = requests.request(
+        method="POST",
+        url=f"{url}/api/show",
+        headers={"Content-Type": "application/json"},
+        data=form_data.model_dump_json(exclude_none=True).encode(),
+    )
+    try:
+        r.raise_for_status()
+
+        return r.json()
+    except Exception as e:
+        log.exception(e)
+        error_detail = "Open WebUI: Server Connection Error"
+        if r is not None:
+            try:
+                res = r.json()
+                if "error" in res:
+                    error_detail = f"Ollama: {res['error']}"
+            except Exception:
+                error_detail = f"Ollama: {e}"
+
+        raise HTTPException(
+            status_code=r.status_code if r else 500,
+            detail=error_detail,
+        )
+
+
+class GenerateEmbeddingsForm(BaseModel):
+    model: str
+    prompt: str
+    options: Optional[dict] = None
+    keep_alive: Optional[Union[int, str]] = None
+
+
+class GenerateEmbedForm(BaseModel):
+    model: str
+    input: list[str] | str
+    truncate: Optional[bool] = None
+    options: Optional[dict] = None
+    keep_alive: Optional[Union[int, str]] = None
+
+
+@app.post("/api/embed")
+@app.post("/api/embed/{url_idx}")
+async def generate_embeddings(
+    form_data: GenerateEmbedForm,
+    url_idx: Optional[int] = None,
+    user=Depends(get_verified_user),
+):
+    return generate_ollama_batch_embeddings(form_data, url_idx)
+
+
+@app.post("/api/embeddings")
+@app.post("/api/embeddings/{url_idx}")
+async def generate_embeddings(
+    form_data: GenerateEmbeddingsForm,
+    url_idx: Optional[int] = None,
+    user=Depends(get_verified_user),
+):
+    return generate_ollama_embeddings(form_data=form_data, url_idx=url_idx)
+
+
+def generate_ollama_embeddings(
+    form_data: GenerateEmbeddingsForm,
+    url_idx: Optional[int] = None,
+):
+    log.info(f"generate_ollama_embeddings {form_data}")
+
+    if url_idx is None:
+        model = form_data.model
+
+        if ":" not in model:
+            model = f"{model}:latest"
+
+        if model in app.state.MODELS:
+            url_idx = random.choice(app.state.MODELS[model]["urls"])
+        else:
+            raise HTTPException(
+                status_code=400,
+                detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
+            )
+
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+    log.info(f"url: {url}")
+
+    r = requests.request(
+        method="POST",
+        url=f"{url}/api/embeddings",
+        headers={"Content-Type": "application/json"},
+        data=form_data.model_dump_json(exclude_none=True).encode(),
+    )
+    try:
+        r.raise_for_status()
+
+        data = r.json()
+
+        log.info(f"generate_ollama_embeddings {data}")
+
+        if "embedding" in data:
+            return data
+        else:
+            raise Exception("Something went wrong :/")
+    except Exception as e:
+        log.exception(e)
+        error_detail = "Open WebUI: Server Connection Error"
+        if r is not None:
+            try:
+                res = r.json()
+                if "error" in res:
+                    error_detail = f"Ollama: {res['error']}"
+            except Exception:
+                error_detail = f"Ollama: {e}"
+
+        raise HTTPException(
+            status_code=r.status_code if r else 500,
+            detail=error_detail,
+        )
+
+
+def generate_ollama_batch_embeddings(
+    form_data: GenerateEmbedForm,
+    url_idx: Optional[int] = None,
+):
+    log.info(f"generate_ollama_batch_embeddings {form_data}")
+
+    if url_idx is None:
+        model = form_data.model
+
+        if ":" not in model:
+            model = f"{model}:latest"
+
+        if model in app.state.MODELS:
+            url_idx = random.choice(app.state.MODELS[model]["urls"])
+        else:
+            raise HTTPException(
+                status_code=400,
+                detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
+            )
+
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+    log.info(f"url: {url}")
+
+    r = requests.request(
+        method="POST",
+        url=f"{url}/api/embed",
+        headers={"Content-Type": "application/json"},
+        data=form_data.model_dump_json(exclude_none=True).encode(),
+    )
+    try:
+        r.raise_for_status()
+
+        data = r.json()
+
+        log.info(f"generate_ollama_batch_embeddings {data}")
+
+        if "embeddings" in data:
+            return data
+        else:
+            raise Exception("Something went wrong :/")
+    except Exception as e:
+        log.exception(e)
+        error_detail = "Open WebUI: Server Connection Error"
+        if r is not None:
+            try:
+                res = r.json()
+                if "error" in res:
+                    error_detail = f"Ollama: {res['error']}"
+            except Exception:
+                error_detail = f"Ollama: {e}"
+
+        raise Exception(error_detail)
+
+
+class GenerateCompletionForm(BaseModel):
+    model: str
+    prompt: str
+    images: Optional[list[str]] = None
+    format: Optional[str] = None
+    options: Optional[dict] = None
+    system: Optional[str] = None
+    template: Optional[str] = None
+    context: Optional[str] = None
+    stream: Optional[bool] = True
+    raw: Optional[bool] = None
+    keep_alive: Optional[Union[int, str]] = None
+
+
+@app.post("/api/generate")
+@app.post("/api/generate/{url_idx}")
+async def generate_completion(
+    form_data: GenerateCompletionForm,
+    url_idx: Optional[int] = None,
+    user=Depends(get_verified_user),
+):
+    if url_idx is None:
+        model = form_data.model
+
+        if ":" not in model:
+            model = f"{model}:latest"
+
+        if model in app.state.MODELS:
+            url_idx = random.choice(app.state.MODELS[model]["urls"])
+        else:
+            raise HTTPException(
+                status_code=400,
+                detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
+            )
+
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+    log.info(f"url: {url}")
+
+    return await post_streaming_url(
+        f"{url}/api/generate", form_data.model_dump_json(exclude_none=True).encode()
+    )
+
+
+class ChatMessage(BaseModel):
+    role: str
+    content: str
+    images: Optional[list[str]] = None
+
+
+class GenerateChatCompletionForm(BaseModel):
+    model: str
+    messages: list[ChatMessage]
+    format: Optional[str] = None
+    options: Optional[dict] = None
+    template: Optional[str] = None
+    stream: Optional[bool] = True
+    keep_alive: Optional[Union[int, str]] = None
+
+
+def get_ollama_url(url_idx: Optional[int], model: str):
+    if url_idx is None:
+        if model not in app.state.MODELS:
+            raise HTTPException(
+                status_code=400,
+                detail=ERROR_MESSAGES.MODEL_NOT_FOUND(model),
+            )
+        url_idx = random.choice(app.state.MODELS[model]["urls"])
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+    return url
+
+
+@app.post("/api/chat")
+@app.post("/api/chat/{url_idx}")
+async def generate_chat_completion(
+    form_data: GenerateChatCompletionForm,
+    url_idx: Optional[int] = None,
+    user=Depends(get_verified_user),
+    bypass_filter: Optional[bool] = False,
+):
+    payload = {**form_data.model_dump(exclude_none=True)}
+    log.debug(f"generate_chat_completion() - 1.payload = {payload}")
+    if "metadata" in payload:
+        del payload["metadata"]
+
+    model_id = form_data.model
+
+    if not bypass_filter and app.state.config.ENABLE_MODEL_FILTER:
+        if user.role == "user" and model_id not in app.state.config.MODEL_FILTER_LIST:
+            raise HTTPException(
+                status_code=403,
+                detail="Model not found",
+            )
+
+    model_info = Models.get_model_by_id(model_id)
+
+    if model_info:
+        if model_info.base_model_id:
+            payload["model"] = model_info.base_model_id
+
+        params = model_info.params.model_dump()
+
+        if params:
+            if payload.get("options") is None:
+                payload["options"] = {}
+
+            payload["options"] = apply_model_params_to_body_ollama(
+                params, payload["options"]
+            )
+            payload = apply_model_system_prompt_to_body(params, payload, user)
+
+    if ":" not in payload["model"]:
+        payload["model"] = f"{payload['model']}:latest"
+
+    url = get_ollama_url(url_idx, payload["model"])
+    log.info(f"url: {url}")
+    log.debug(f"generate_chat_completion() - 2.payload = {payload}")
+
+    return await post_streaming_url(
+        f"{url}/api/chat",
+        json.dumps(payload),
+        stream=form_data.stream,
+        content_type="application/x-ndjson",
+    )
+
+
+# TODO: we should update this part once Ollama supports other types
+class OpenAIChatMessageContent(BaseModel):
+    type: str
+    model_config = ConfigDict(extra="allow")
+
+
+class OpenAIChatMessage(BaseModel):
+    role: str
+    content: Union[str, OpenAIChatMessageContent]
+
+    model_config = ConfigDict(extra="allow")
+
+
+class OpenAIChatCompletionForm(BaseModel):
+    model: str
+    messages: list[OpenAIChatMessage]
+
+    model_config = ConfigDict(extra="allow")
+
+
+@app.post("/v1/chat/completions")
+@app.post("/v1/chat/completions/{url_idx}")
+async def generate_openai_chat_completion(
+    form_data: dict,
+    url_idx: Optional[int] = None,
+    user=Depends(get_verified_user),
+):
+    completion_form = OpenAIChatCompletionForm(**form_data)
+    payload = {**completion_form.model_dump(exclude_none=True, exclude=["metadata"])}
+    if "metadata" in payload:
+        del payload["metadata"]
+
+    model_id = completion_form.model
+
+    if app.state.config.ENABLE_MODEL_FILTER:
+        if user.role == "user" and model_id not in app.state.config.MODEL_FILTER_LIST:
+            raise HTTPException(
+                status_code=403,
+                detail="Model not found",
+            )
+
+    model_info = Models.get_model_by_id(model_id)
+
+    if model_info:
+        if model_info.base_model_id:
+            payload["model"] = model_info.base_model_id
+
+        params = model_info.params.model_dump()
+
+        if params:
+            payload = apply_model_params_to_body_openai(params, payload)
+            payload = apply_model_system_prompt_to_body(params, payload, user)
+
+    if ":" not in payload["model"]:
+        payload["model"] = f"{payload['model']}:latest"
+
+    url = get_ollama_url(url_idx, payload["model"])
+    log.info(f"url: {url}")
+
+    return await post_streaming_url(
+        f"{url}/v1/chat/completions",
+        json.dumps(payload),
+        stream=payload.get("stream", False),
+    )
+
+
+@app.get("/v1/models")
+@app.get("/v1/models/{url_idx}")
+async def get_openai_models(
+    url_idx: Optional[int] = None,
+    user=Depends(get_verified_user),
+):
+    if url_idx is None:
+        models = await get_all_models()
+
+        if app.state.config.ENABLE_MODEL_FILTER:
+            if user.role == "user":
+                models["models"] = list(
+                    filter(
+                        lambda model: model["name"]
+                        in app.state.config.MODEL_FILTER_LIST,
+                        models["models"],
+                    )
+                )
+
+        return {
+            "data": [
+                {
+                    "id": model["model"],
+                    "object": "model",
+                    "created": int(time.time()),
+                    "owned_by": "openai",
+                }
+                for model in models["models"]
+            ],
+            "object": "list",
+        }
+
+    else:
+        url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+        try:
+            r = requests.request(method="GET", url=f"{url}/api/tags")
+            r.raise_for_status()
+
+            models = r.json()
+
+            return {
+                "data": [
+                    {
+                        "id": model["model"],
+                        "object": "model",
+                        "created": int(time.time()),
+                        "owned_by": "openai",
+                    }
+                    for model in models["models"]
+                ],
+                "object": "list",
+            }
+
+        except Exception as e:
+            log.exception(e)
+            error_detail = "Open WebUI: Server Connection Error"
+            if r is not None:
+                try:
+                    res = r.json()
+                    if "error" in res:
+                        error_detail = f"Ollama: {res['error']}"
+                except Exception:
+                    error_detail = f"Ollama: {e}"
+
+            raise HTTPException(
+                status_code=r.status_code if r else 500,
+                detail=error_detail,
+            )
+
+
+class UrlForm(BaseModel):
+    url: str
+
+
+class UploadBlobForm(BaseModel):
+    filename: str
+
+
+def parse_huggingface_url(hf_url):
+    try:
+        # Parse the URL
+        parsed_url = urlparse(hf_url)
+
+        # Get the path and split it into components
+        path_components = parsed_url.path.split("/")
+
+        # Extract the desired output
+        model_file = path_components[-1]
+
+        return model_file
+    except ValueError:
+        return None
+
+
+async def download_file_stream(
+    ollama_url, file_url, file_path, file_name, chunk_size=1024 * 1024
+):
+    done = False
+
+    if os.path.exists(file_path):
+        current_size = os.path.getsize(file_path)
+    else:
+        current_size = 0
+
+    headers = {"Range": f"bytes={current_size}-"} if current_size > 0 else {}
+
+    timeout = aiohttp.ClientTimeout(total=600)  # Set the timeout
+
+    async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
+        async with session.get(file_url, headers=headers) as response:
+            total_size = int(response.headers.get("content-length", 0)) + current_size
+
+            with open(file_path, "ab+") as file:
+                async for data in response.content.iter_chunked(chunk_size):
+                    current_size += len(data)
+                    file.write(data)
+
+                    done = current_size == total_size
+                    progress = round((current_size / total_size) * 100, 2)
+
+                    yield f'data: {{"progress": {progress}, "completed": {current_size}, "total": {total_size}}}\n\n'
+
+                if done:
+                    file.seek(0)
+                    hashed = calculate_sha256(file)
+                    file.seek(0)
+
+                    url = f"{ollama_url}/api/blobs/sha256:{hashed}"
+                    response = requests.post(url, data=file)
+
+                    if response.ok:
+                        res = {
+                            "done": done,
+                            "blob": f"sha256:{hashed}",
+                            "name": file_name,
+                        }
+                        os.remove(file_path)
+
+                        yield f"data: {json.dumps(res)}\n\n"
+                    else:
+                        raise "Ollama: Could not create blob, Please try again."
+
+
+# url = "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q2_K.gguf"
+@app.post("/models/download")
+@app.post("/models/download/{url_idx}")
+async def download_model(
+    form_data: UrlForm,
+    url_idx: Optional[int] = None,
+    user=Depends(get_admin_user),
+):
+    allowed_hosts = ["https://huggingface.co/", "https://github.com/"]
+
+    if not any(form_data.url.startswith(host) for host in allowed_hosts):
+        raise HTTPException(
+            status_code=400,
+            detail="Invalid file_url. Only URLs from allowed hosts are permitted.",
+        )
+
+    if url_idx is None:
+        url_idx = 0
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+
+    file_name = parse_huggingface_url(form_data.url)
+
+    if file_name:
+        file_path = f"{UPLOAD_DIR}/{file_name}"
+
+        return StreamingResponse(
+            download_file_stream(url, form_data.url, file_path, file_name),
+        )
+    else:
+        return None
+
+
+@app.post("/models/upload")
+@app.post("/models/upload/{url_idx}")
+def upload_model(
+    file: UploadFile = File(...),
+    url_idx: Optional[int] = None,
+    user=Depends(get_admin_user),
+):
+    if url_idx is None:
+        url_idx = 0
+    ollama_url = app.state.config.OLLAMA_BASE_URLS[url_idx]
+
+    file_path = f"{UPLOAD_DIR}/{file.filename}"
+
+    # Save file in chunks
+    with open(file_path, "wb+") as f:
+        for chunk in file.file:
+            f.write(chunk)
+
+    def file_process_stream():
+        nonlocal ollama_url
+        total_size = os.path.getsize(file_path)
+        chunk_size = 1024 * 1024
+        try:
+            with open(file_path, "rb") as f:
+                total = 0
+                done = False
+
+                while not done:
+                    chunk = f.read(chunk_size)
+                    if not chunk:
+                        done = True
+                        continue
+
+                    total += len(chunk)
+                    progress = round((total / total_size) * 100, 2)
+
+                    res = {
+                        "progress": progress,
+                        "total": total_size,
+                        "completed": total,
+                    }
+                    yield f"data: {json.dumps(res)}\n\n"
+
+                if done:
+                    f.seek(0)
+                    hashed = calculate_sha256(f)
+                    f.seek(0)
+
+                    url = f"{ollama_url}/api/blobs/sha256:{hashed}"
+                    response = requests.post(url, data=f)
+
+                    if response.ok:
+                        res = {
+                            "done": done,
+                            "blob": f"sha256:{hashed}",
+                            "name": file.filename,
+                        }
+                        os.remove(file_path)
+                        yield f"data: {json.dumps(res)}\n\n"
+                    else:
+                        raise Exception(
+                            "Ollama: Could not create blob, Please try again."
+                        )
+
+        except Exception as e:
+            res = {"error": str(e)}
+            yield f"data: {json.dumps(res)}\n\n"
+
+    return StreamingResponse(file_process_stream(), media_type="text/event-stream")
diff --git a/backend/open_webui/apps/openai/main.py b/backend/open_webui/apps/openai/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7e1c7bf8ec15dd062d1cc79728a523f4aa87e9b
--- /dev/null
+++ b/backend/open_webui/apps/openai/main.py
@@ -0,0 +1,557 @@
+import asyncio
+import hashlib
+import json
+import logging
+from pathlib import Path
+from typing import Literal, Optional, overload
+
+import aiohttp
+import requests
+from open_webui.apps.webui.models.models import Models
+from open_webui.config import (
+    CACHE_DIR,
+    CORS_ALLOW_ORIGIN,
+    ENABLE_MODEL_FILTER,
+    ENABLE_OPENAI_API,
+    MODEL_FILTER_LIST,
+    OPENAI_API_BASE_URLS,
+    OPENAI_API_KEYS,
+    AppConfig,
+)
+from open_webui.env import (
+    AIOHTTP_CLIENT_TIMEOUT,
+    AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST,
+)
+
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.env import ENV, SRC_LOG_LEVELS
+from fastapi import Depends, FastAPI, HTTPException, Request
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import FileResponse, StreamingResponse
+from pydantic import BaseModel
+from starlette.background import BackgroundTask
+
+from open_webui.utils.payload import (
+    apply_model_params_to_body_openai,
+    apply_model_system_prompt_to_body,
+)
+
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["OPENAI"])
+
+
+app = FastAPI(docs_url="/docs" if ENV == "dev" else None, openapi_url="/openapi.json" if ENV == "dev" else None, redoc_url=None)
+
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=CORS_ALLOW_ORIGIN,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+app.state.config = AppConfig()
+
+app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
+app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
+
+app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API
+app.state.config.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
+app.state.config.OPENAI_API_KEYS = OPENAI_API_KEYS
+
+app.state.MODELS = {}
+
+
+@app.middleware("http")
+async def check_url(request: Request, call_next):
+    if len(app.state.MODELS) == 0:
+        await get_all_models()
+
+    response = await call_next(request)
+    return response
+
+
+@app.get("/config")
+async def get_config(user=Depends(get_admin_user)):
+    return {"ENABLE_OPENAI_API": app.state.config.ENABLE_OPENAI_API}
+
+
+class OpenAIConfigForm(BaseModel):
+    enable_openai_api: Optional[bool] = None
+
+
+@app.post("/config/update")
+async def update_config(form_data: OpenAIConfigForm, user=Depends(get_admin_user)):
+    app.state.config.ENABLE_OPENAI_API = form_data.enable_openai_api
+    return {"ENABLE_OPENAI_API": app.state.config.ENABLE_OPENAI_API}
+
+
+class UrlsUpdateForm(BaseModel):
+    urls: list[str]
+
+
+class KeysUpdateForm(BaseModel):
+    keys: list[str]
+
+
+@app.get("/urls")
+async def get_openai_urls(user=Depends(get_admin_user)):
+    return {"OPENAI_API_BASE_URLS": app.state.config.OPENAI_API_BASE_URLS}
+
+
+@app.post("/urls/update")
+async def update_openai_urls(form_data: UrlsUpdateForm, user=Depends(get_admin_user)):
+    await get_all_models()
+    app.state.config.OPENAI_API_BASE_URLS = form_data.urls
+    return {"OPENAI_API_BASE_URLS": app.state.config.OPENAI_API_BASE_URLS}
+
+
+@app.get("/keys")
+async def get_openai_keys(user=Depends(get_admin_user)):
+    return {"OPENAI_API_KEYS": app.state.config.OPENAI_API_KEYS}
+
+
+@app.post("/keys/update")
+async def update_openai_key(form_data: KeysUpdateForm, user=Depends(get_admin_user)):
+    app.state.config.OPENAI_API_KEYS = form_data.keys
+    return {"OPENAI_API_KEYS": app.state.config.OPENAI_API_KEYS}
+
+
+@app.post("/audio/speech")
+async def speech(request: Request, user=Depends(get_verified_user)):
+    idx = None
+    try:
+        idx = app.state.config.OPENAI_API_BASE_URLS.index("https://api.openai.com/v1")
+        body = await request.body()
+        name = hashlib.sha256(body).hexdigest()
+
+        SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
+        SPEECH_CACHE_DIR.mkdir(parents=True, exist_ok=True)
+        file_path = SPEECH_CACHE_DIR.joinpath(f"{name}.mp3")
+        file_body_path = SPEECH_CACHE_DIR.joinpath(f"{name}.json")
+
+        # Check if the file already exists in the cache
+        if file_path.is_file():
+            return FileResponse(file_path)
+
+        headers = {}
+        headers["Authorization"] = f"Bearer {app.state.config.OPENAI_API_KEYS[idx]}"
+        headers["Content-Type"] = "application/json"
+        if "openrouter.ai" in app.state.config.OPENAI_API_BASE_URLS[idx]:
+            headers["HTTP-Referer"] = "https://openwebui.com/"
+            headers["X-Title"] = "Open WebUI"
+        r = None
+        try:
+            r = requests.post(
+                url=f"{app.state.config.OPENAI_API_BASE_URLS[idx]}/audio/speech",
+                data=body,
+                headers=headers,
+                stream=True,
+            )
+
+            r.raise_for_status()
+
+            # Save the streaming content to a file
+            with open(file_path, "wb") as f:
+                for chunk in r.iter_content(chunk_size=8192):
+                    f.write(chunk)
+
+            with open(file_body_path, "w") as f:
+                json.dump(json.loads(body.decode("utf-8")), f)
+
+            # Return the saved file
+            return FileResponse(file_path)
+
+        except Exception as e:
+            log.exception(e)
+            error_detail = "Open WebUI: Server Connection Error"
+            if r is not None:
+                try:
+                    res = r.json()
+                    if "error" in res:
+                        error_detail = f"External: {res['error']}"
+                except Exception:
+                    error_detail = f"External: {e}"
+
+            raise HTTPException(
+                status_code=r.status_code if r else 500, detail=error_detail
+            )
+
+    except ValueError:
+        raise HTTPException(status_code=401, detail=ERROR_MESSAGES.OPENAI_NOT_FOUND)
+
+
+async def fetch_url(url, key):
+    timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST)
+    try:
+        headers = {"Authorization": f"Bearer {key}"}
+        async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
+            async with session.get(url, headers=headers) as response:
+                return await response.json()
+    except Exception as e:
+        # Handle connection error here
+        log.error(f"Connection error: {e}")
+        return None
+
+
+async def cleanup_response(
+    response: Optional[aiohttp.ClientResponse],
+    session: Optional[aiohttp.ClientSession],
+):
+    if response:
+        response.close()
+    if session:
+        await session.close()
+
+
+def merge_models_lists(model_lists):
+    log.debug(f"merge_models_lists {model_lists}")
+    merged_list = []
+
+    for idx, models in enumerate(model_lists):
+        if models is not None and "error" not in models:
+            merged_list.extend(
+                [
+                    {
+                        **model,
+                        "name": model.get("name", model["id"]),
+                        "owned_by": "openai",
+                        "openai": model,
+                        "urlIdx": idx,
+                    }
+                    for model in models
+                    if "api.openai.com"
+                    not in app.state.config.OPENAI_API_BASE_URLS[idx]
+                    or not any(
+                        name in model["id"]
+                        for name in [
+                            "babbage",
+                            "dall-e",
+                            "davinci",
+                            "embedding",
+                            "tts",
+                            "whisper",
+                        ]
+                    )
+                ]
+            )
+
+    return merged_list
+
+
+def is_openai_api_disabled():
+    return not app.state.config.ENABLE_OPENAI_API
+
+
+async def get_all_models_raw() -> list:
+    if is_openai_api_disabled():
+        return []
+
+    # Check if API KEYS length is same than API URLS length
+    num_urls = len(app.state.config.OPENAI_API_BASE_URLS)
+    num_keys = len(app.state.config.OPENAI_API_KEYS)
+
+    if num_keys != num_urls:
+        # if there are more keys than urls, remove the extra keys
+        if num_keys > num_urls:
+            new_keys = app.state.config.OPENAI_API_KEYS[:num_urls]
+            app.state.config.OPENAI_API_KEYS = new_keys
+        # if there are more urls than keys, add empty keys
+        else:
+            app.state.config.OPENAI_API_KEYS += [""] * (num_urls - num_keys)
+
+    tasks = [
+        fetch_url(f"{url}/models", app.state.config.OPENAI_API_KEYS[idx])
+        for idx, url in enumerate(app.state.config.OPENAI_API_BASE_URLS)
+    ]
+
+    responses = await asyncio.gather(*tasks)
+    log.debug(f"get_all_models:responses() {responses}")
+
+    return responses
+
+
+@overload
+async def get_all_models(raw: Literal[True]) -> list: ...
+
+
+@overload
+async def get_all_models(raw: Literal[False] = False) -> dict[str, list]: ...
+
+
+async def get_all_models(raw=False) -> dict[str, list] | list:
+    log.info("get_all_models()")
+    if is_openai_api_disabled():
+        return [] if raw else {"data": []}
+
+    responses = await get_all_models_raw()
+    if raw:
+        return responses
+
+    def extract_data(response):
+        if response and "data" in response:
+            return response["data"]
+        if isinstance(response, list):
+            return response
+        return None
+
+    models = {"data": merge_models_lists(map(extract_data, responses))}
+
+    log.debug(f"models: {models}")
+    app.state.MODELS = {model["id"]: model for model in models["data"]}
+
+    return models
+
+
+@app.get("/models")
+@app.get("/models/{url_idx}")
+async def get_models(url_idx: Optional[int] = None, user=Depends(get_verified_user)):
+    if url_idx is None:
+        models = await get_all_models()
+        if app.state.config.ENABLE_MODEL_FILTER:
+            if user.role == "user":
+                models["data"] = list(
+                    filter(
+                        lambda model: model["id"] in app.state.config.MODEL_FILTER_LIST,
+                        models["data"],
+                    )
+                )
+                return models
+        return models
+    else:
+        url = app.state.config.OPENAI_API_BASE_URLS[url_idx]
+        key = app.state.config.OPENAI_API_KEYS[url_idx]
+
+        headers = {}
+        headers["Authorization"] = f"Bearer {key}"
+        headers["Content-Type"] = "application/json"
+
+        r = None
+
+        try:
+            r = requests.request(method="GET", url=f"{url}/models", headers=headers)
+            r.raise_for_status()
+
+            response_data = r.json()
+
+            if "api.openai.com" in url:
+                # Filter the response data
+                response_data["data"] = [
+                    model
+                    for model in response_data["data"]
+                    if not any(
+                        name in model["id"]
+                        for name in [
+                            "babbage",
+                            "dall-e",
+                            "davinci",
+                            "embedding",
+                            "tts",
+                            "whisper",
+                        ]
+                    )
+                ]
+
+            return response_data
+        except Exception as e:
+            log.exception(e)
+            error_detail = "Open WebUI: Server Connection Error"
+            if r is not None:
+                try:
+                    res = r.json()
+                    if "error" in res:
+                        error_detail = f"External: {res['error']}"
+                except Exception:
+                    error_detail = f"External: {e}"
+
+            raise HTTPException(
+                status_code=r.status_code if r else 500,
+                detail=error_detail,
+            )
+
+
+@app.post("/chat/completions")
+@app.post("/chat/completions/{url_idx}")
+async def generate_chat_completion(
+    form_data: dict,
+    url_idx: Optional[int] = None,
+    user=Depends(get_verified_user),
+):
+    idx = 0
+    payload = {**form_data}
+
+    if "metadata" in payload:
+        del payload["metadata"]
+
+    model_id = form_data.get("model")
+    model_info = Models.get_model_by_id(model_id)
+
+    if model_info:
+        if model_info.base_model_id:
+            payload["model"] = model_info.base_model_id
+
+        params = model_info.params.model_dump()
+        payload = apply_model_params_to_body_openai(params, payload)
+        payload = apply_model_system_prompt_to_body(params, payload, user)
+
+    model = app.state.MODELS[payload.get("model")]
+    idx = model["urlIdx"]
+
+    if "pipeline" in model and model.get("pipeline"):
+        payload["user"] = {
+            "name": user.name,
+            "id": user.id,
+            "email": user.email,
+            "role": user.role,
+        }
+
+    url = app.state.config.OPENAI_API_BASE_URLS[idx]
+    key = app.state.config.OPENAI_API_KEYS[idx]
+    is_o1 = payload["model"].lower().startswith("o1-")
+
+    # Change max_completion_tokens to max_tokens (Backward compatible)
+    if "api.openai.com" not in url and not is_o1:
+        if "max_completion_tokens" in payload:
+            # Remove "max_completion_tokens" from the payload
+            payload["max_tokens"] = payload["max_completion_tokens"]
+            del payload["max_completion_tokens"]
+    else:
+        if is_o1 and "max_tokens" in payload:
+            payload["max_completion_tokens"] = payload["max_tokens"]
+            del payload["max_tokens"]
+        if "max_tokens" in payload and "max_completion_tokens" in payload:
+            del payload["max_tokens"]
+
+    # Fix: O1 does not support the "system" parameter, Modify "system" to "user"
+    if is_o1 and payload["messages"][0]["role"] == "system":
+        payload["messages"][0]["role"] = "user"
+
+    # Convert the modified body back to JSON
+    payload = json.dumps(payload)
+
+    log.debug(payload)
+
+    headers = {}
+    headers["Authorization"] = f"Bearer {key}"
+    headers["Content-Type"] = "application/json"
+    if "openrouter.ai" in app.state.config.OPENAI_API_BASE_URLS[idx]:
+        headers["HTTP-Referer"] = "https://openwebui.com/"
+        headers["X-Title"] = "Open WebUI"
+
+    r = None
+    session = None
+    streaming = False
+    response = None
+
+    try:
+        session = aiohttp.ClientSession(
+            trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
+        )
+        r = await session.request(
+            method="POST",
+            url=f"{url}/chat/completions",
+            data=payload,
+            headers=headers,
+        )
+
+        # Check if response is SSE
+        if "text/event-stream" in r.headers.get("Content-Type", ""):
+            streaming = True
+            return StreamingResponse(
+                r.content,
+                status_code=r.status,
+                headers=dict(r.headers),
+                background=BackgroundTask(
+                    cleanup_response, response=r, session=session
+                ),
+            )
+        else:
+            try:
+                response = await r.json()
+            except Exception as e:
+                log.error(e)
+                response = await r.text()
+
+            r.raise_for_status()
+            return response
+    except Exception as e:
+        log.exception(e)
+        error_detail = "Open WebUI: Server Connection Error"
+        if isinstance(response, dict):
+            if "error" in response:
+                error_detail = f"{response['error']['message'] if 'message' in response['error'] else response['error']}"
+        elif isinstance(response, str):
+            error_detail = response
+
+        raise HTTPException(status_code=r.status if r else 500, detail=error_detail)
+    finally:
+        if not streaming and session:
+            if r:
+                r.close()
+            await session.close()
+
+
+@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
+async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
+    idx = 0
+
+    body = await request.body()
+
+    url = app.state.config.OPENAI_API_BASE_URLS[idx]
+    key = app.state.config.OPENAI_API_KEYS[idx]
+
+    target_url = f"{url}/{path}"
+
+    headers = {}
+    headers["Authorization"] = f"Bearer {key}"
+    headers["Content-Type"] = "application/json"
+
+    r = None
+    session = None
+    streaming = False
+
+    try:
+        session = aiohttp.ClientSession(trust_env=True)
+        r = await session.request(
+            method=request.method,
+            url=target_url,
+            data=body,
+            headers=headers,
+        )
+
+        r.raise_for_status()
+
+        # Check if response is SSE
+        if "text/event-stream" in r.headers.get("Content-Type", ""):
+            streaming = True
+            return StreamingResponse(
+                r.content,
+                status_code=r.status,
+                headers=dict(r.headers),
+                background=BackgroundTask(
+                    cleanup_response, response=r, session=session
+                ),
+            )
+        else:
+            response_data = await r.json()
+            return response_data
+    except Exception as e:
+        log.exception(e)
+        error_detail = "Open WebUI: Server Connection Error"
+        if r is not None:
+            try:
+                res = await r.json()
+                print(res)
+                if "error" in res:
+                    error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}"
+            except Exception:
+                error_detail = f"External: {e}"
+        raise HTTPException(status_code=r.status if r else 500, detail=error_detail)
+    finally:
+        if not streaming and session:
+            if r:
+                r.close()
+            await session.close()
diff --git a/backend/open_webui/apps/retrieval/loaders/main.py b/backend/open_webui/apps/retrieval/loaders/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0e8f804eec23e88349a5c4ffe028a41d7002578
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/loaders/main.py
@@ -0,0 +1,190 @@
+import requests
+import logging
+import ftfy
+
+from langchain_community.document_loaders import (
+    BSHTMLLoader,
+    CSVLoader,
+    Docx2txtLoader,
+    OutlookMessageLoader,
+    PyPDFLoader,
+    TextLoader,
+    UnstructuredEPubLoader,
+    UnstructuredExcelLoader,
+    UnstructuredMarkdownLoader,
+    UnstructuredPowerPointLoader,
+    UnstructuredRSTLoader,
+    UnstructuredXMLLoader,
+    YoutubeLoader,
+)
+from langchain_core.documents import Document
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+known_source_ext = [
+    "go",
+    "py",
+    "java",
+    "sh",
+    "bat",
+    "ps1",
+    "cmd",
+    "js",
+    "ts",
+    "css",
+    "cpp",
+    "hpp",
+    "h",
+    "c",
+    "cs",
+    "sql",
+    "log",
+    "ini",
+    "pl",
+    "pm",
+    "r",
+    "dart",
+    "dockerfile",
+    "env",
+    "php",
+    "hs",
+    "hsc",
+    "lua",
+    "nginxconf",
+    "conf",
+    "m",
+    "mm",
+    "plsql",
+    "perl",
+    "rb",
+    "rs",
+    "db2",
+    "scala",
+    "bash",
+    "swift",
+    "vue",
+    "svelte",
+    "msg",
+    "ex",
+    "exs",
+    "erl",
+    "tsx",
+    "jsx",
+    "hs",
+    "lhs",
+]
+
+
+class TikaLoader:
+    def __init__(self, url, file_path, mime_type=None):
+        self.url = url
+        self.file_path = file_path
+        self.mime_type = mime_type
+
+    def load(self) -> list[Document]:
+        with open(self.file_path, "rb") as f:
+            data = f.read()
+
+        if self.mime_type is not None:
+            headers = {"Content-Type": self.mime_type}
+        else:
+            headers = {}
+
+        endpoint = self.url
+        if not endpoint.endswith("/"):
+            endpoint += "/"
+        endpoint += "tika/text"
+
+        r = requests.put(endpoint, data=data, headers=headers)
+
+        if r.ok:
+            raw_metadata = r.json()
+            text = raw_metadata.get("X-TIKA:content", "<No text content found>")
+
+            if "Content-Type" in raw_metadata:
+                headers["Content-Type"] = raw_metadata["Content-Type"]
+
+            log.info("Tika extracted text: %s", text)
+
+            return [Document(page_content=text, metadata=headers)]
+        else:
+            raise Exception(f"Error calling Tika: {r.reason}")
+
+
+class Loader:
+    def __init__(self, engine: str = "", **kwargs):
+        self.engine = engine
+        self.kwargs = kwargs
+
+    def load(
+        self, filename: str, file_content_type: str, file_path: str
+    ) -> list[Document]:
+        loader = self._get_loader(filename, file_content_type, file_path)
+        docs = loader.load()
+
+        return [
+            Document(
+                page_content=ftfy.fix_text(doc.page_content), metadata=doc.metadata
+            )
+            for doc in docs
+        ]
+
+    def _get_loader(self, filename: str, file_content_type: str, file_path: str):
+        file_ext = filename.split(".")[-1].lower()
+
+        if self.engine == "tika" and self.kwargs.get("TIKA_SERVER_URL"):
+            if file_ext in known_source_ext or (
+                file_content_type and file_content_type.find("text/") >= 0
+            ):
+                loader = TextLoader(file_path, autodetect_encoding=True)
+            else:
+                loader = TikaLoader(
+                    url=self.kwargs.get("TIKA_SERVER_URL"),
+                    file_path=file_path,
+                    mime_type=file_content_type,
+                )
+        else:
+            if file_ext == "pdf":
+                loader = PyPDFLoader(
+                    file_path, extract_images=self.kwargs.get("PDF_EXTRACT_IMAGES")
+                )
+            elif file_ext == "csv":
+                loader = CSVLoader(file_path)
+            elif file_ext == "rst":
+                loader = UnstructuredRSTLoader(file_path, mode="elements")
+            elif file_ext == "xml":
+                loader = UnstructuredXMLLoader(file_path)
+            elif file_ext in ["htm", "html"]:
+                loader = BSHTMLLoader(file_path, open_encoding="unicode_escape")
+            elif file_ext == "md":
+                loader = UnstructuredMarkdownLoader(file_path)
+            elif file_content_type == "application/epub+zip":
+                loader = UnstructuredEPubLoader(file_path)
+            elif (
+                file_content_type
+                == "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+                or file_ext == "docx"
+            ):
+                loader = Docx2txtLoader(file_path)
+            elif file_content_type in [
+                "application/vnd.ms-excel",
+                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+            ] or file_ext in ["xls", "xlsx"]:
+                loader = UnstructuredExcelLoader(file_path)
+            elif file_content_type in [
+                "application/vnd.ms-powerpoint",
+                "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+            ] or file_ext in ["ppt", "pptx"]:
+                loader = UnstructuredPowerPointLoader(file_path)
+            elif file_ext == "msg":
+                loader = OutlookMessageLoader(file_path)
+            elif file_ext in known_source_ext or (
+                file_content_type and file_content_type.find("text/") >= 0
+            ):
+                loader = TextLoader(file_path, autodetect_encoding=True)
+            else:
+                loader = TextLoader(file_path, autodetect_encoding=True)
+
+        return loader
diff --git a/backend/open_webui/apps/retrieval/main.py b/backend/open_webui/apps/retrieval/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..85472485d4fdc1d72eec3e37cec348d651b0c21b
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/main.py
@@ -0,0 +1,1332 @@
+# TODO: Merge this with the webui_app and make it a single app
+
+import json
+import logging
+import mimetypes
+import os
+import shutil
+
+import uuid
+from datetime import datetime
+from pathlib import Path
+from typing import Iterator, Optional, Sequence, Union
+
+from fastapi import Depends, FastAPI, File, Form, HTTPException, UploadFile, status
+from fastapi.middleware.cors import CORSMiddleware
+from pydantic import BaseModel
+import tiktoken
+
+
+from open_webui.storage.provider import Storage
+from open_webui.apps.webui.models.knowledge import Knowledges
+from open_webui.apps.retrieval.vector.connector import VECTOR_DB_CLIENT
+
+# Document loaders
+from open_webui.apps.retrieval.loaders.main import Loader
+
+# Web search engines
+from open_webui.apps.retrieval.web.main import SearchResult
+from open_webui.apps.retrieval.web.utils import get_web_loader
+from open_webui.apps.retrieval.web.brave import search_brave
+from open_webui.apps.retrieval.web.duckduckgo import search_duckduckgo
+from open_webui.apps.retrieval.web.google_pse import search_google_pse
+from open_webui.apps.retrieval.web.jina_search import search_jina
+from open_webui.apps.retrieval.web.searchapi import search_searchapi
+from open_webui.apps.retrieval.web.searxng import search_searxng
+from open_webui.apps.retrieval.web.serper import search_serper
+from open_webui.apps.retrieval.web.serply import search_serply
+from open_webui.apps.retrieval.web.serpstack import search_serpstack
+from open_webui.apps.retrieval.web.tavily import search_tavily
+
+
+from open_webui.apps.retrieval.utils import (
+    get_embedding_function,
+    get_model_path,
+    query_collection,
+    query_collection_with_hybrid_search,
+    query_doc,
+    query_doc_with_hybrid_search,
+)
+
+from open_webui.apps.webui.models.files import Files
+from open_webui.config import (
+    BRAVE_SEARCH_API_KEY,
+    TIKTOKEN_ENCODING_NAME,
+    RAG_TEXT_SPLITTER,
+    CHUNK_OVERLAP,
+    CHUNK_SIZE,
+    CONTENT_EXTRACTION_ENGINE,
+    CORS_ALLOW_ORIGIN,
+    ENABLE_RAG_HYBRID_SEARCH,
+    ENABLE_RAG_LOCAL_WEB_FETCH,
+    ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
+    ENABLE_RAG_WEB_SEARCH,
+    ENV,
+    GOOGLE_PSE_API_KEY,
+    GOOGLE_PSE_ENGINE_ID,
+    PDF_EXTRACT_IMAGES,
+    RAG_EMBEDDING_ENGINE,
+    RAG_EMBEDDING_MODEL,
+    RAG_EMBEDDING_MODEL_AUTO_UPDATE,
+    RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE,
+    RAG_EMBEDDING_BATCH_SIZE,
+    RAG_FILE_MAX_COUNT,
+    RAG_FILE_MAX_SIZE,
+    RAG_OPENAI_API_BASE_URL,
+    RAG_OPENAI_API_KEY,
+    RAG_RELEVANCE_THRESHOLD,
+    RAG_RERANKING_MODEL,
+    RAG_RERANKING_MODEL_AUTO_UPDATE,
+    RAG_RERANKING_MODEL_TRUST_REMOTE_CODE,
+    DEFAULT_RAG_TEMPLATE,
+    RAG_TEMPLATE,
+    RAG_TOP_K,
+    RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
+    RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
+    RAG_WEB_SEARCH_ENGINE,
+    RAG_WEB_SEARCH_RESULT_COUNT,
+    SEARCHAPI_API_KEY,
+    SEARCHAPI_ENGINE,
+    SEARXNG_QUERY_URL,
+    SERPER_API_KEY,
+    SERPLY_API_KEY,
+    SERPSTACK_API_KEY,
+    SERPSTACK_HTTPS,
+    TAVILY_API_KEY,
+    TIKA_SERVER_URL,
+    UPLOAD_DIR,
+    YOUTUBE_LOADER_LANGUAGE,
+    AppConfig,
+)
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.env import SRC_LOG_LEVELS, DEVICE_TYPE, DOCKER
+from open_webui.utils.misc import (
+    calculate_sha256,
+    calculate_sha256_string,
+    extract_folders_after_data_docs,
+    sanitize_filename,
+)
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+from langchain.text_splitter import RecursiveCharacterTextSplitter, TokenTextSplitter
+from langchain_community.document_loaders import (
+    YoutubeLoader,
+)
+from langchain_core.documents import Document
+
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+app = FastAPI(docs_url="/docs" if ENV == "dev" else None, openapi_url="/openapi.json" if ENV == "dev" else None, redoc_url=None)
+
+app.state.config = AppConfig()
+
+app.state.config.TOP_K = RAG_TOP_K
+app.state.config.RELEVANCE_THRESHOLD = RAG_RELEVANCE_THRESHOLD
+app.state.config.FILE_MAX_SIZE = RAG_FILE_MAX_SIZE
+app.state.config.FILE_MAX_COUNT = RAG_FILE_MAX_COUNT
+
+app.state.config.ENABLE_RAG_HYBRID_SEARCH = ENABLE_RAG_HYBRID_SEARCH
+app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = (
+    ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION
+)
+
+app.state.config.CONTENT_EXTRACTION_ENGINE = CONTENT_EXTRACTION_ENGINE
+app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL
+
+app.state.config.TEXT_SPLITTER = RAG_TEXT_SPLITTER
+app.state.config.TIKTOKEN_ENCODING_NAME = TIKTOKEN_ENCODING_NAME
+
+app.state.config.CHUNK_SIZE = CHUNK_SIZE
+app.state.config.CHUNK_OVERLAP = CHUNK_OVERLAP
+
+app.state.config.RAG_EMBEDDING_ENGINE = RAG_EMBEDDING_ENGINE
+app.state.config.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL
+app.state.config.RAG_EMBEDDING_BATCH_SIZE = RAG_EMBEDDING_BATCH_SIZE
+app.state.config.RAG_RERANKING_MODEL = RAG_RERANKING_MODEL
+app.state.config.RAG_TEMPLATE = RAG_TEMPLATE
+
+app.state.config.OPENAI_API_BASE_URL = RAG_OPENAI_API_BASE_URL
+app.state.config.OPENAI_API_KEY = RAG_OPENAI_API_KEY
+
+app.state.config.PDF_EXTRACT_IMAGES = PDF_EXTRACT_IMAGES
+
+app.state.config.YOUTUBE_LOADER_LANGUAGE = YOUTUBE_LOADER_LANGUAGE
+app.state.YOUTUBE_LOADER_TRANSLATION = None
+
+
+app.state.config.ENABLE_RAG_WEB_SEARCH = ENABLE_RAG_WEB_SEARCH
+app.state.config.RAG_WEB_SEARCH_ENGINE = RAG_WEB_SEARCH_ENGINE
+app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = RAG_WEB_SEARCH_DOMAIN_FILTER_LIST
+
+app.state.config.SEARXNG_QUERY_URL = SEARXNG_QUERY_URL
+app.state.config.GOOGLE_PSE_API_KEY = GOOGLE_PSE_API_KEY
+app.state.config.GOOGLE_PSE_ENGINE_ID = GOOGLE_PSE_ENGINE_ID
+app.state.config.BRAVE_SEARCH_API_KEY = BRAVE_SEARCH_API_KEY
+app.state.config.SERPSTACK_API_KEY = SERPSTACK_API_KEY
+app.state.config.SERPSTACK_HTTPS = SERPSTACK_HTTPS
+app.state.config.SERPER_API_KEY = SERPER_API_KEY
+app.state.config.SERPLY_API_KEY = SERPLY_API_KEY
+app.state.config.TAVILY_API_KEY = TAVILY_API_KEY
+app.state.config.SEARCHAPI_API_KEY = SEARCHAPI_API_KEY
+app.state.config.SEARCHAPI_ENGINE = SEARCHAPI_ENGINE
+app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = RAG_WEB_SEARCH_RESULT_COUNT
+app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
+
+
+def update_embedding_model(
+    embedding_model: str,
+    auto_update: bool = False,
+):
+    if embedding_model and app.state.config.RAG_EMBEDDING_ENGINE == "":
+        from sentence_transformers import SentenceTransformer
+
+        app.state.sentence_transformer_ef = SentenceTransformer(
+            get_model_path(embedding_model, auto_update),
+            device=DEVICE_TYPE,
+            trust_remote_code=RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE,
+        )
+    else:
+        app.state.sentence_transformer_ef = None
+
+
+def update_reranking_model(
+    reranking_model: str,
+    auto_update: bool = False,
+):
+    if reranking_model:
+        if any(model in reranking_model for model in ["jinaai/jina-colbert-v2"]):
+            try:
+                from open_webui.apps.retrieval.models.colbert import ColBERT
+
+                app.state.sentence_transformer_rf = ColBERT(
+                    get_model_path(reranking_model, auto_update),
+                    env="docker" if DOCKER else None,
+                )
+            except Exception as e:
+                log.error(f"ColBERT: {e}")
+                app.state.sentence_transformer_rf = None
+                app.state.config.ENABLE_RAG_HYBRID_SEARCH = False
+        else:
+            import sentence_transformers
+
+            try:
+                app.state.sentence_transformer_rf = sentence_transformers.CrossEncoder(
+                    get_model_path(reranking_model, auto_update),
+                    device=DEVICE_TYPE,
+                    trust_remote_code=RAG_RERANKING_MODEL_TRUST_REMOTE_CODE,
+                )
+            except:
+                log.error("CrossEncoder error")
+                app.state.sentence_transformer_rf = None
+                app.state.config.ENABLE_RAG_HYBRID_SEARCH = False
+    else:
+        app.state.sentence_transformer_rf = None
+
+
+update_embedding_model(
+    app.state.config.RAG_EMBEDDING_MODEL,
+    RAG_EMBEDDING_MODEL_AUTO_UPDATE,
+)
+
+update_reranking_model(
+    app.state.config.RAG_RERANKING_MODEL,
+    RAG_RERANKING_MODEL_AUTO_UPDATE,
+)
+
+
+app.state.EMBEDDING_FUNCTION = get_embedding_function(
+    app.state.config.RAG_EMBEDDING_ENGINE,
+    app.state.config.RAG_EMBEDDING_MODEL,
+    app.state.sentence_transformer_ef,
+    app.state.config.OPENAI_API_KEY,
+    app.state.config.OPENAI_API_BASE_URL,
+    app.state.config.RAG_EMBEDDING_BATCH_SIZE,
+)
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=CORS_ALLOW_ORIGIN,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+
+class CollectionNameForm(BaseModel):
+    collection_name: Optional[str] = None
+
+
+class ProcessUrlForm(CollectionNameForm):
+    url: str
+
+
+class SearchForm(CollectionNameForm):
+    query: str
+
+
+@app.get("/")
+async def get_status():
+    return {
+        "status": True,
+        "chunk_size": app.state.config.CHUNK_SIZE,
+        "chunk_overlap": app.state.config.CHUNK_OVERLAP,
+        "template": app.state.config.RAG_TEMPLATE,
+        "embedding_engine": app.state.config.RAG_EMBEDDING_ENGINE,
+        "embedding_model": app.state.config.RAG_EMBEDDING_MODEL,
+        "reranking_model": app.state.config.RAG_RERANKING_MODEL,
+        "embedding_batch_size": app.state.config.RAG_EMBEDDING_BATCH_SIZE,
+    }
+
+
+@app.get("/embedding")
+async def get_embedding_config(user=Depends(get_admin_user)):
+    return {
+        "status": True,
+        "embedding_engine": app.state.config.RAG_EMBEDDING_ENGINE,
+        "embedding_model": app.state.config.RAG_EMBEDDING_MODEL,
+        "embedding_batch_size": app.state.config.RAG_EMBEDDING_BATCH_SIZE,
+        "openai_config": {
+            "url": app.state.config.OPENAI_API_BASE_URL,
+            "key": app.state.config.OPENAI_API_KEY,
+        },
+    }
+
+
+@app.get("/reranking")
+async def get_reraanking_config(user=Depends(get_admin_user)):
+    return {
+        "status": True,
+        "reranking_model": app.state.config.RAG_RERANKING_MODEL,
+    }
+
+
+class OpenAIConfigForm(BaseModel):
+    url: str
+    key: str
+
+
+class EmbeddingModelUpdateForm(BaseModel):
+    openai_config: Optional[OpenAIConfigForm] = None
+    embedding_engine: str
+    embedding_model: str
+    embedding_batch_size: Optional[int] = 1
+
+
+@app.post("/embedding/update")
+async def update_embedding_config(
+    form_data: EmbeddingModelUpdateForm, user=Depends(get_admin_user)
+):
+    log.info(
+        f"Updating embedding model: {app.state.config.RAG_EMBEDDING_MODEL} to {form_data.embedding_model}"
+    )
+    try:
+        app.state.config.RAG_EMBEDDING_ENGINE = form_data.embedding_engine
+        app.state.config.RAG_EMBEDDING_MODEL = form_data.embedding_model
+
+        if app.state.config.RAG_EMBEDDING_ENGINE in ["ollama", "openai"]:
+            if form_data.openai_config is not None:
+                app.state.config.OPENAI_API_BASE_URL = form_data.openai_config.url
+                app.state.config.OPENAI_API_KEY = form_data.openai_config.key
+            app.state.config.RAG_EMBEDDING_BATCH_SIZE = form_data.embedding_batch_size
+
+        update_embedding_model(app.state.config.RAG_EMBEDDING_MODEL)
+
+        app.state.EMBEDDING_FUNCTION = get_embedding_function(
+            app.state.config.RAG_EMBEDDING_ENGINE,
+            app.state.config.RAG_EMBEDDING_MODEL,
+            app.state.sentence_transformer_ef,
+            app.state.config.OPENAI_API_KEY,
+            app.state.config.OPENAI_API_BASE_URL,
+            app.state.config.RAG_EMBEDDING_BATCH_SIZE,
+        )
+
+        return {
+            "status": True,
+            "embedding_engine": app.state.config.RAG_EMBEDDING_ENGINE,
+            "embedding_model": app.state.config.RAG_EMBEDDING_MODEL,
+            "embedding_batch_size": app.state.config.RAG_EMBEDDING_BATCH_SIZE,
+            "openai_config": {
+                "url": app.state.config.OPENAI_API_BASE_URL,
+                "key": app.state.config.OPENAI_API_KEY,
+            },
+        }
+    except Exception as e:
+        log.exception(f"Problem updating embedding model: {e}")
+        raise HTTPException(
+            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+class RerankingModelUpdateForm(BaseModel):
+    reranking_model: str
+
+
+@app.post("/reranking/update")
+async def update_reranking_config(
+    form_data: RerankingModelUpdateForm, user=Depends(get_admin_user)
+):
+    log.info(
+        f"Updating reranking model: {app.state.config.RAG_RERANKING_MODEL} to {form_data.reranking_model}"
+    )
+    try:
+        app.state.config.RAG_RERANKING_MODEL = form_data.reranking_model
+
+        update_reranking_model(app.state.config.RAG_RERANKING_MODEL, True)
+
+        return {
+            "status": True,
+            "reranking_model": app.state.config.RAG_RERANKING_MODEL,
+        }
+    except Exception as e:
+        log.exception(f"Problem updating reranking model: {e}")
+        raise HTTPException(
+            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+@app.get("/config")
+async def get_rag_config(user=Depends(get_admin_user)):
+    return {
+        "status": True,
+        "pdf_extract_images": app.state.config.PDF_EXTRACT_IMAGES,
+        "content_extraction": {
+            "engine": app.state.config.CONTENT_EXTRACTION_ENGINE,
+            "tika_server_url": app.state.config.TIKA_SERVER_URL,
+        },
+        "chunk": {
+            "text_splitter": app.state.config.TEXT_SPLITTER,
+            "chunk_size": app.state.config.CHUNK_SIZE,
+            "chunk_overlap": app.state.config.CHUNK_OVERLAP,
+        },
+        "file": {
+            "max_size": app.state.config.FILE_MAX_SIZE,
+            "max_count": app.state.config.FILE_MAX_COUNT,
+        },
+        "youtube": {
+            "language": app.state.config.YOUTUBE_LOADER_LANGUAGE,
+            "translation": app.state.YOUTUBE_LOADER_TRANSLATION,
+        },
+        "web": {
+            "ssl_verification": app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
+            "search": {
+                "enabled": app.state.config.ENABLE_RAG_WEB_SEARCH,
+                "engine": app.state.config.RAG_WEB_SEARCH_ENGINE,
+                "searxng_query_url": app.state.config.SEARXNG_QUERY_URL,
+                "google_pse_api_key": app.state.config.GOOGLE_PSE_API_KEY,
+                "google_pse_engine_id": app.state.config.GOOGLE_PSE_ENGINE_ID,
+                "brave_search_api_key": app.state.config.BRAVE_SEARCH_API_KEY,
+                "serpstack_api_key": app.state.config.SERPSTACK_API_KEY,
+                "serpstack_https": app.state.config.SERPSTACK_HTTPS,
+                "serper_api_key": app.state.config.SERPER_API_KEY,
+                "serply_api_key": app.state.config.SERPLY_API_KEY,
+                "tavily_api_key": app.state.config.TAVILY_API_KEY,
+                "searchapi_api_key": app.state.config.SEARCHAPI_API_KEY,
+                "seaarchapi_engine": app.state.config.SEARCHAPI_ENGINE,
+                "result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+                "concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
+            },
+        },
+    }
+
+
+class FileConfig(BaseModel):
+    max_size: Optional[int] = None
+    max_count: Optional[int] = None
+
+
+class ContentExtractionConfig(BaseModel):
+    engine: str = ""
+    tika_server_url: Optional[str] = None
+
+
+class ChunkParamUpdateForm(BaseModel):
+    text_splitter: Optional[str] = None
+    chunk_size: int
+    chunk_overlap: int
+
+
+class YoutubeLoaderConfig(BaseModel):
+    language: list[str]
+    translation: Optional[str] = None
+
+
+class WebSearchConfig(BaseModel):
+    enabled: bool
+    engine: Optional[str] = None
+    searxng_query_url: Optional[str] = None
+    google_pse_api_key: Optional[str] = None
+    google_pse_engine_id: Optional[str] = None
+    brave_search_api_key: Optional[str] = None
+    serpstack_api_key: Optional[str] = None
+    serpstack_https: Optional[bool] = None
+    serper_api_key: Optional[str] = None
+    serply_api_key: Optional[str] = None
+    tavily_api_key: Optional[str] = None
+    searchapi_api_key: Optional[str] = None
+    searchapi_engine: Optional[str] = None
+    result_count: Optional[int] = None
+    concurrent_requests: Optional[int] = None
+
+
+class WebConfig(BaseModel):
+    search: WebSearchConfig
+    web_loader_ssl_verification: Optional[bool] = None
+
+
+class ConfigUpdateForm(BaseModel):
+    pdf_extract_images: Optional[bool] = None
+    file: Optional[FileConfig] = None
+    content_extraction: Optional[ContentExtractionConfig] = None
+    chunk: Optional[ChunkParamUpdateForm] = None
+    youtube: Optional[YoutubeLoaderConfig] = None
+    web: Optional[WebConfig] = None
+
+
+@app.post("/config/update")
+async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_user)):
+    app.state.config.PDF_EXTRACT_IMAGES = (
+        form_data.pdf_extract_images
+        if form_data.pdf_extract_images is not None
+        else app.state.config.PDF_EXTRACT_IMAGES
+    )
+
+    if form_data.file is not None:
+        app.state.config.FILE_MAX_SIZE = form_data.file.max_size
+        app.state.config.FILE_MAX_COUNT = form_data.file.max_count
+
+    if form_data.content_extraction is not None:
+        log.info(f"Updating text settings: {form_data.content_extraction}")
+        app.state.config.CONTENT_EXTRACTION_ENGINE = form_data.content_extraction.engine
+        app.state.config.TIKA_SERVER_URL = form_data.content_extraction.tika_server_url
+
+    if form_data.chunk is not None:
+        app.state.config.TEXT_SPLITTER = form_data.chunk.text_splitter
+        app.state.config.CHUNK_SIZE = form_data.chunk.chunk_size
+        app.state.config.CHUNK_OVERLAP = form_data.chunk.chunk_overlap
+
+    if form_data.youtube is not None:
+        app.state.config.YOUTUBE_LOADER_LANGUAGE = form_data.youtube.language
+        app.state.YOUTUBE_LOADER_TRANSLATION = form_data.youtube.translation
+
+    if form_data.web is not None:
+        app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = (
+            form_data.web.web_loader_ssl_verification
+        )
+
+        app.state.config.ENABLE_RAG_WEB_SEARCH = form_data.web.search.enabled
+        app.state.config.RAG_WEB_SEARCH_ENGINE = form_data.web.search.engine
+        app.state.config.SEARXNG_QUERY_URL = form_data.web.search.searxng_query_url
+        app.state.config.GOOGLE_PSE_API_KEY = form_data.web.search.google_pse_api_key
+        app.state.config.GOOGLE_PSE_ENGINE_ID = (
+            form_data.web.search.google_pse_engine_id
+        )
+        app.state.config.BRAVE_SEARCH_API_KEY = (
+            form_data.web.search.brave_search_api_key
+        )
+        app.state.config.SERPSTACK_API_KEY = form_data.web.search.serpstack_api_key
+        app.state.config.SERPSTACK_HTTPS = form_data.web.search.serpstack_https
+        app.state.config.SERPER_API_KEY = form_data.web.search.serper_api_key
+        app.state.config.SERPLY_API_KEY = form_data.web.search.serply_api_key
+        app.state.config.TAVILY_API_KEY = form_data.web.search.tavily_api_key
+        app.state.config.SEARCHAPI_API_KEY = form_data.web.search.searchapi_api_key
+        app.state.config.SEARCHAPI_ENGINE = form_data.web.search.searchapi_engine
+        app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = form_data.web.search.result_count
+        app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = (
+            form_data.web.search.concurrent_requests
+        )
+
+    return {
+        "status": True,
+        "pdf_extract_images": app.state.config.PDF_EXTRACT_IMAGES,
+        "file": {
+            "max_size": app.state.config.FILE_MAX_SIZE,
+            "max_count": app.state.config.FILE_MAX_COUNT,
+        },
+        "content_extraction": {
+            "engine": app.state.config.CONTENT_EXTRACTION_ENGINE,
+            "tika_server_url": app.state.config.TIKA_SERVER_URL,
+        },
+        "chunk": {
+            "text_splitter": app.state.config.TEXT_SPLITTER,
+            "chunk_size": app.state.config.CHUNK_SIZE,
+            "chunk_overlap": app.state.config.CHUNK_OVERLAP,
+        },
+        "youtube": {
+            "language": app.state.config.YOUTUBE_LOADER_LANGUAGE,
+            "translation": app.state.YOUTUBE_LOADER_TRANSLATION,
+        },
+        "web": {
+            "ssl_verification": app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
+            "search": {
+                "enabled": app.state.config.ENABLE_RAG_WEB_SEARCH,
+                "engine": app.state.config.RAG_WEB_SEARCH_ENGINE,
+                "searxng_query_url": app.state.config.SEARXNG_QUERY_URL,
+                "google_pse_api_key": app.state.config.GOOGLE_PSE_API_KEY,
+                "google_pse_engine_id": app.state.config.GOOGLE_PSE_ENGINE_ID,
+                "brave_search_api_key": app.state.config.BRAVE_SEARCH_API_KEY,
+                "serpstack_api_key": app.state.config.SERPSTACK_API_KEY,
+                "serpstack_https": app.state.config.SERPSTACK_HTTPS,
+                "serper_api_key": app.state.config.SERPER_API_KEY,
+                "serply_api_key": app.state.config.SERPLY_API_KEY,
+                "serachapi_api_key": app.state.config.SEARCHAPI_API_KEY,
+                "searchapi_engine": app.state.config.SEARCHAPI_ENGINE,
+                "tavily_api_key": app.state.config.TAVILY_API_KEY,
+                "result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+                "concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
+            },
+        },
+    }
+
+
+@app.get("/template")
+async def get_rag_template(user=Depends(get_verified_user)):
+    return {
+        "status": True,
+        "template": app.state.config.RAG_TEMPLATE,
+    }
+
+
+@app.get("/query/settings")
+async def get_query_settings(user=Depends(get_admin_user)):
+    return {
+        "status": True,
+        "template": app.state.config.RAG_TEMPLATE,
+        "k": app.state.config.TOP_K,
+        "r": app.state.config.RELEVANCE_THRESHOLD,
+        "hybrid": app.state.config.ENABLE_RAG_HYBRID_SEARCH,
+    }
+
+
+class QuerySettingsForm(BaseModel):
+    k: Optional[int] = None
+    r: Optional[float] = None
+    template: Optional[str] = None
+    hybrid: Optional[bool] = None
+
+
+@app.post("/query/settings/update")
+async def update_query_settings(
+    form_data: QuerySettingsForm, user=Depends(get_admin_user)
+):
+    app.state.config.RAG_TEMPLATE = form_data.template
+    app.state.config.TOP_K = form_data.k if form_data.k else 4
+    app.state.config.RELEVANCE_THRESHOLD = form_data.r if form_data.r else 0.0
+
+    app.state.config.ENABLE_RAG_HYBRID_SEARCH = (
+        form_data.hybrid if form_data.hybrid else False
+    )
+
+    return {
+        "status": True,
+        "template": app.state.config.RAG_TEMPLATE,
+        "k": app.state.config.TOP_K,
+        "r": app.state.config.RELEVANCE_THRESHOLD,
+        "hybrid": app.state.config.ENABLE_RAG_HYBRID_SEARCH,
+    }
+
+
+####################################
+#
+# Document process and retrieval
+#
+####################################
+
+
+def save_docs_to_vector_db(
+    docs,
+    collection_name,
+    metadata: Optional[dict] = None,
+    overwrite: bool = False,
+    split: bool = True,
+    add: bool = False,
+) -> bool:
+    log.info(f"save_docs_to_vector_db {docs} {collection_name}")
+
+    # Check if entries with the same hash (metadata.hash) already exist
+    if metadata and "hash" in metadata:
+        result = VECTOR_DB_CLIENT.query(
+            collection_name=collection_name,
+            filter={"hash": metadata["hash"]},
+        )
+
+        if result is not None:
+            existing_doc_ids = result.ids[0]
+            if existing_doc_ids:
+                log.info(f"Document with hash {metadata['hash']} already exists")
+                raise ValueError(ERROR_MESSAGES.DUPLICATE_CONTENT)
+
+    if split:
+        if app.state.config.TEXT_SPLITTER in ["", "character"]:
+            text_splitter = RecursiveCharacterTextSplitter(
+                chunk_size=app.state.config.CHUNK_SIZE,
+                chunk_overlap=app.state.config.CHUNK_OVERLAP,
+                add_start_index=True,
+            )
+        elif app.state.config.TEXT_SPLITTER == "token":
+            log.info(
+                f"Using token text splitter: {app.state.config.TIKTOKEN_ENCODING_NAME}"
+            )
+
+            tiktoken.get_encoding(str(app.state.config.TIKTOKEN_ENCODING_NAME))
+            text_splitter = TokenTextSplitter(
+                encoding_name=str(app.state.config.TIKTOKEN_ENCODING_NAME),
+                chunk_size=app.state.config.CHUNK_SIZE,
+                chunk_overlap=app.state.config.CHUNK_OVERLAP,
+                add_start_index=True,
+            )
+        else:
+            raise ValueError(ERROR_MESSAGES.DEFAULT("Invalid text splitter"))
+
+        docs = text_splitter.split_documents(docs)
+
+    if len(docs) == 0:
+        raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)
+
+    texts = [doc.page_content for doc in docs]
+    metadatas = [
+        {
+            **doc.metadata,
+            **(metadata if metadata else {}),
+            "embedding_config": json.dumps(
+                {
+                    "engine": app.state.config.RAG_EMBEDDING_ENGINE,
+                    "model": app.state.config.RAG_EMBEDDING_MODEL,
+                }
+            ),
+        }
+        for doc in docs
+    ]
+
+    # ChromaDB does not like datetime formats
+    # for meta-data so convert them to string.
+    for metadata in metadatas:
+        for key, value in metadata.items():
+            if isinstance(value, datetime):
+                metadata[key] = str(value)
+
+    try:
+        if VECTOR_DB_CLIENT.has_collection(collection_name=collection_name):
+            log.info(f"collection {collection_name} already exists")
+
+            if overwrite:
+                VECTOR_DB_CLIENT.delete_collection(collection_name=collection_name)
+                log.info(f"deleting existing collection {collection_name}")
+            elif add is False:
+                log.info(
+                    f"collection {collection_name} already exists, overwrite is False and add is False"
+                )
+                return True
+
+        log.info(f"adding to collection {collection_name}")
+        embedding_function = get_embedding_function(
+            app.state.config.RAG_EMBEDDING_ENGINE,
+            app.state.config.RAG_EMBEDDING_MODEL,
+            app.state.sentence_transformer_ef,
+            app.state.config.OPENAI_API_KEY,
+            app.state.config.OPENAI_API_BASE_URL,
+            app.state.config.RAG_EMBEDDING_BATCH_SIZE,
+        )
+
+        embeddings = embedding_function(
+            list(map(lambda x: x.replace("\n", " "), texts))
+        )
+
+        items = [
+            {
+                "id": str(uuid.uuid4()),
+                "text": text,
+                "vector": embeddings[idx],
+                "metadata": metadatas[idx],
+            }
+            for idx, text in enumerate(texts)
+        ]
+
+        VECTOR_DB_CLIENT.insert(
+            collection_name=collection_name,
+            items=items,
+        )
+
+        return True
+    except Exception as e:
+        log.exception(e)
+        return False
+
+
+class ProcessFileForm(BaseModel):
+    file_id: str
+    content: Optional[str] = None
+    collection_name: Optional[str] = None
+
+
+@app.post("/process/file")
+def process_file(
+    form_data: ProcessFileForm,
+    user=Depends(get_verified_user),
+):
+    try:
+        file = Files.get_file_by_id(form_data.file_id)
+
+        collection_name = form_data.collection_name
+
+        if collection_name is None:
+            collection_name = f"file-{file.id}"
+
+        if form_data.content:
+            # Update the content in the file
+            # Usage: /files/{file_id}/data/content/update
+
+            VECTOR_DB_CLIENT.delete(
+                collection_name=f"file-{file.id}",
+                filter={"file_id": file.id},
+            )
+
+            docs = [
+                Document(
+                    page_content=form_data.content,
+                    metadata={
+                        "name": file.meta.get("name", file.filename),
+                        "created_by": file.user_id,
+                        "file_id": file.id,
+                        **file.meta,
+                    },
+                )
+            ]
+
+            text_content = form_data.content
+        elif form_data.collection_name:
+            # Check if the file has already been processed and save the content
+            # Usage: /knowledge/{id}/file/add, /knowledge/{id}/file/update
+
+            result = VECTOR_DB_CLIENT.query(
+                collection_name=f"file-{file.id}", filter={"file_id": file.id}
+            )
+
+            if result is not None and len(result.ids[0]) > 0:
+                docs = [
+                    Document(
+                        page_content=result.documents[0][idx],
+                        metadata=result.metadatas[0][idx],
+                    )
+                    for idx, id in enumerate(result.ids[0])
+                ]
+            else:
+                docs = [
+                    Document(
+                        page_content=file.data.get("content", ""),
+                        metadata={
+                            "name": file.meta.get("name", file.filename),
+                            "created_by": file.user_id,
+                            "file_id": file.id,
+                            **file.meta,
+                        },
+                    )
+                ]
+
+            text_content = file.data.get("content", "")
+        else:
+            # Process the file and save the content
+            # Usage: /files/
+            file_path = file.path
+            if file_path:
+                file_path = Storage.get_file(file_path)
+                loader = Loader(
+                    engine=app.state.config.CONTENT_EXTRACTION_ENGINE,
+                    TIKA_SERVER_URL=app.state.config.TIKA_SERVER_URL,
+                    PDF_EXTRACT_IMAGES=app.state.config.PDF_EXTRACT_IMAGES,
+                )
+                docs = loader.load(
+                    file.filename, file.meta.get("content_type"), file_path
+                )
+            else:
+                docs = [
+                    Document(
+                        page_content=file.data.get("content", ""),
+                        metadata={
+                            "name": file.filename,
+                            "created_by": file.user_id,
+                            "file_id": file.id,
+                            **file.meta,
+                        },
+                    )
+                ]
+            text_content = " ".join([doc.page_content for doc in docs])
+
+        log.debug(f"text_content: {text_content}")
+        Files.update_file_data_by_id(
+            file.id,
+            {"content": text_content},
+        )
+
+        hash = calculate_sha256_string(text_content)
+        Files.update_file_hash_by_id(file.id, hash)
+
+        try:
+            result = save_docs_to_vector_db(
+                docs=docs,
+                collection_name=collection_name,
+                metadata={
+                    "file_id": file.id,
+                    "name": file.meta.get("name", file.filename),
+                    "hash": hash,
+                },
+                add=(True if form_data.collection_name else False),
+            )
+
+            if result:
+                Files.update_file_metadata_by_id(
+                    file.id,
+                    {
+                        "collection_name": collection_name,
+                    },
+                )
+
+                return {
+                    "status": True,
+                    "collection_name": collection_name,
+                    "filename": file.meta.get("name", file.filename),
+                    "content": text_content,
+                }
+        except Exception as e:
+            raise e
+    except Exception as e:
+        log.exception(e)
+        if "No pandoc was found" in str(e):
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.PANDOC_NOT_INSTALLED,
+            )
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=str(e),
+            )
+
+
+class ProcessTextForm(BaseModel):
+    name: str
+    content: str
+    collection_name: Optional[str] = None
+
+
+@app.post("/process/text")
+def process_text(
+    form_data: ProcessTextForm,
+    user=Depends(get_verified_user),
+):
+    collection_name = form_data.collection_name
+    if collection_name is None:
+        collection_name = calculate_sha256_string(form_data.content)
+
+    docs = [
+        Document(
+            page_content=form_data.content,
+            metadata={"name": form_data.name, "created_by": user.id},
+        )
+    ]
+    text_content = form_data.content
+    log.debug(f"text_content: {text_content}")
+
+    result = save_docs_to_vector_db(docs, collection_name)
+
+    if result:
+        return {
+            "status": True,
+            "collection_name": collection_name,
+            "content": text_content,
+        }
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+            detail=ERROR_MESSAGES.DEFAULT(),
+        )
+
+
+@app.post("/process/youtube")
+def process_youtube_video(form_data: ProcessUrlForm, user=Depends(get_verified_user)):
+    try:
+        collection_name = form_data.collection_name
+        if not collection_name:
+            collection_name = calculate_sha256_string(form_data.url)[:63]
+
+        loader = YoutubeLoader.from_youtube_url(
+            form_data.url,
+            add_video_info=True,
+            language=app.state.config.YOUTUBE_LOADER_LANGUAGE,
+            translation=app.state.YOUTUBE_LOADER_TRANSLATION,
+        )
+        docs = loader.load()
+        content = " ".join([doc.page_content for doc in docs])
+        log.debug(f"text_content: {content}")
+        save_docs_to_vector_db(docs, collection_name, overwrite=True)
+
+        return {
+            "status": True,
+            "collection_name": collection_name,
+            "filename": form_data.url,
+            "file": {
+                "data": {
+                    "content": content,
+                },
+                "meta": {
+                    "name": form_data.url,
+                },
+            },
+        }
+    except Exception as e:
+        log.exception(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+@app.post("/process/web")
+def process_web(form_data: ProcessUrlForm, user=Depends(get_verified_user)):
+    try:
+        collection_name = form_data.collection_name
+        if not collection_name:
+            collection_name = calculate_sha256_string(form_data.url)[:63]
+
+        loader = get_web_loader(
+            form_data.url,
+            verify_ssl=app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
+            requests_per_second=app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
+        )
+        docs = loader.load()
+        content = " ".join([doc.page_content for doc in docs])
+        log.debug(f"text_content: {content}")
+        save_docs_to_vector_db(docs, collection_name, overwrite=True)
+
+        return {
+            "status": True,
+            "collection_name": collection_name,
+            "filename": form_data.url,
+            "file": {
+                "data": {
+                    "content": content,
+                },
+                "meta": {
+                    "name": form_data.url,
+                },
+            },
+        }
+    except Exception as e:
+        log.exception(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+def search_web(engine: str, query: str) -> list[SearchResult]:
+    """Search the web using a search engine and return the results as a list of SearchResult objects.
+    Will look for a search engine API key in environment variables in the following order:
+    - SEARXNG_QUERY_URL
+    - GOOGLE_PSE_API_KEY + GOOGLE_PSE_ENGINE_ID
+    - BRAVE_SEARCH_API_KEY
+    - SERPSTACK_API_KEY
+    - SERPER_API_KEY
+    - SERPLY_API_KEY
+    - TAVILY_API_KEY
+    - SEARCHAPI_API_KEY + SEARCHAPI_ENGINE (by default `google`)
+    Args:
+        query (str): The query to search for
+    """
+
+    # TODO: add playwright to search the web
+    if engine == "searxng":
+        if app.state.config.SEARXNG_QUERY_URL:
+            return search_searxng(
+                app.state.config.SEARXNG_QUERY_URL,
+                query,
+                app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+                app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
+            )
+        else:
+            raise Exception("No SEARXNG_QUERY_URL found in environment variables")
+    elif engine == "google_pse":
+        if (
+            app.state.config.GOOGLE_PSE_API_KEY
+            and app.state.config.GOOGLE_PSE_ENGINE_ID
+        ):
+            return search_google_pse(
+                app.state.config.GOOGLE_PSE_API_KEY,
+                app.state.config.GOOGLE_PSE_ENGINE_ID,
+                query,
+                app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+                app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
+            )
+        else:
+            raise Exception(
+                "No GOOGLE_PSE_API_KEY or GOOGLE_PSE_ENGINE_ID found in environment variables"
+            )
+    elif engine == "brave":
+        if app.state.config.BRAVE_SEARCH_API_KEY:
+            return search_brave(
+                app.state.config.BRAVE_SEARCH_API_KEY,
+                query,
+                app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+                app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
+            )
+        else:
+            raise Exception("No BRAVE_SEARCH_API_KEY found in environment variables")
+    elif engine == "serpstack":
+        if app.state.config.SERPSTACK_API_KEY:
+            return search_serpstack(
+                app.state.config.SERPSTACK_API_KEY,
+                query,
+                app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+                app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
+                https_enabled=app.state.config.SERPSTACK_HTTPS,
+            )
+        else:
+            raise Exception("No SERPSTACK_API_KEY found in environment variables")
+    elif engine == "serper":
+        if app.state.config.SERPER_API_KEY:
+            return search_serper(
+                app.state.config.SERPER_API_KEY,
+                query,
+                app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+                app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
+            )
+        else:
+            raise Exception("No SERPER_API_KEY found in environment variables")
+    elif engine == "serply":
+        if app.state.config.SERPLY_API_KEY:
+            return search_serply(
+                app.state.config.SERPLY_API_KEY,
+                query,
+                app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+                app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
+            )
+        else:
+            raise Exception("No SERPLY_API_KEY found in environment variables")
+    elif engine == "duckduckgo":
+        return search_duckduckgo(
+            query,
+            app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+            app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
+        )
+    elif engine == "tavily":
+        if app.state.config.TAVILY_API_KEY:
+            return search_tavily(
+                app.state.config.TAVILY_API_KEY,
+                query,
+                app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+            )
+        else:
+            raise Exception("No TAVILY_API_KEY found in environment variables")
+    elif engine == "searchapi":
+        if app.state.config.SEARCHAPI_API_KEY:
+            return search_searchapi(
+                app.state.config.SEARCHAPI_API_KEY,
+                app.state.config.SEARCHAPI_ENGINE,
+                query,
+                app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+                app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
+            )
+        else:
+            raise Exception("No SEARCHAPI_API_KEY found in environment variables")
+    elif engine == "jina":
+        return search_jina(query, app.state.config.RAG_WEB_SEARCH_RESULT_COUNT)
+    else:
+        raise Exception("No search engine API key found in environment variables")
+
+
+@app.post("/process/web/search")
+def process_web_search(form_data: SearchForm, user=Depends(get_verified_user)):
+    try:
+        logging.info(
+            f"trying to web search with {app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query}"
+        )
+        web_results = search_web(
+            app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query
+        )
+    except Exception as e:
+        log.exception(e)
+
+        print(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.WEB_SEARCH_ERROR(e),
+        )
+
+    try:
+        collection_name = form_data.collection_name
+        if collection_name == "":
+            collection_name = calculate_sha256_string(form_data.query)[:63]
+
+        urls = [result.link for result in web_results]
+
+        loader = get_web_loader(urls)
+        docs = loader.load()
+
+        save_docs_to_vector_db(docs, collection_name, overwrite=True)
+
+        return {
+            "status": True,
+            "collection_name": collection_name,
+            "filenames": urls,
+        }
+    except Exception as e:
+        log.exception(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+class QueryDocForm(BaseModel):
+    collection_name: str
+    query: str
+    k: Optional[int] = None
+    r: Optional[float] = None
+    hybrid: Optional[bool] = None
+
+
+@app.post("/query/doc")
+def query_doc_handler(
+    form_data: QueryDocForm,
+    user=Depends(get_verified_user),
+):
+    try:
+        if app.state.config.ENABLE_RAG_HYBRID_SEARCH:
+            return query_doc_with_hybrid_search(
+                collection_name=form_data.collection_name,
+                query=form_data.query,
+                embedding_function=app.state.EMBEDDING_FUNCTION,
+                k=form_data.k if form_data.k else app.state.config.TOP_K,
+                reranking_function=app.state.sentence_transformer_rf,
+                r=(
+                    form_data.r if form_data.r else app.state.config.RELEVANCE_THRESHOLD
+                ),
+            )
+        else:
+            return query_doc(
+                collection_name=form_data.collection_name,
+                query=form_data.query,
+                embedding_function=app.state.EMBEDDING_FUNCTION,
+                k=form_data.k if form_data.k else app.state.config.TOP_K,
+            )
+    except Exception as e:
+        log.exception(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+class QueryCollectionsForm(BaseModel):
+    collection_names: list[str]
+    query: str
+    k: Optional[int] = None
+    r: Optional[float] = None
+    hybrid: Optional[bool] = None
+
+
+@app.post("/query/collection")
+def query_collection_handler(
+    form_data: QueryCollectionsForm,
+    user=Depends(get_verified_user),
+):
+    try:
+        if app.state.config.ENABLE_RAG_HYBRID_SEARCH:
+            return query_collection_with_hybrid_search(
+                collection_names=form_data.collection_names,
+                query=form_data.query,
+                embedding_function=app.state.EMBEDDING_FUNCTION,
+                k=form_data.k if form_data.k else app.state.config.TOP_K,
+                reranking_function=app.state.sentence_transformer_rf,
+                r=(
+                    form_data.r if form_data.r else app.state.config.RELEVANCE_THRESHOLD
+                ),
+            )
+        else:
+            return query_collection(
+                collection_names=form_data.collection_names,
+                query=form_data.query,
+                embedding_function=app.state.EMBEDDING_FUNCTION,
+                k=form_data.k if form_data.k else app.state.config.TOP_K,
+            )
+
+    except Exception as e:
+        log.exception(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+####################################
+#
+# Vector DB operations
+#
+####################################
+
+
+class DeleteForm(BaseModel):
+    collection_name: str
+    file_id: str
+
+
+@app.post("/delete")
+def delete_entries_from_collection(form_data: DeleteForm, user=Depends(get_admin_user)):
+    try:
+        if VECTOR_DB_CLIENT.has_collection(collection_name=form_data.collection_name):
+            file = Files.get_file_by_id(form_data.file_id)
+            hash = file.hash
+
+            VECTOR_DB_CLIENT.delete(
+                collection_name=form_data.collection_name,
+                metadata={"hash": hash},
+            )
+            return {"status": True}
+        else:
+            return {"status": False}
+    except Exception as e:
+        log.exception(e)
+        return {"status": False}
+
+
+@app.post("/reset/db")
+def reset_vector_db(user=Depends(get_admin_user)):
+    VECTOR_DB_CLIENT.reset()
+    Knowledges.delete_all_knowledge()
+
+
+@app.post("/reset/uploads")
+def reset_upload_dir(user=Depends(get_admin_user)) -> bool:
+    folder = f"{UPLOAD_DIR}"
+    try:
+        # Check if the directory exists
+        if os.path.exists(folder):
+            # Iterate over all the files and directories in the specified directory
+            for filename in os.listdir(folder):
+                file_path = os.path.join(folder, filename)
+                try:
+                    if os.path.isfile(file_path) or os.path.islink(file_path):
+                        os.unlink(file_path)  # Remove the file or link
+                    elif os.path.isdir(file_path):
+                        shutil.rmtree(file_path)  # Remove the directory
+                except Exception as e:
+                    print(f"Failed to delete {file_path}. Reason: {e}")
+        else:
+            print(f"The directory {folder} does not exist")
+    except Exception as e:
+        print(f"Failed to process the directory {folder}. Reason: {e}")
+    return True
+
+
+if ENV == "dev":
+
+    @app.get("/ef")
+    async def get_embeddings():
+        return {"result": app.state.EMBEDDING_FUNCTION("hello world")}
+
+    @app.get("/ef/{text}")
+    async def get_embeddings_text(text: str):
+        return {"result": app.state.EMBEDDING_FUNCTION(text)}
diff --git a/backend/open_webui/apps/retrieval/models/colbert.py b/backend/open_webui/apps/retrieval/models/colbert.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea3204cb8bf91d7dbae4370d0f3628de7b6e031b
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/models/colbert.py
@@ -0,0 +1,81 @@
+import os
+import torch
+import numpy as np
+from colbert.infra import ColBERTConfig
+from colbert.modeling.checkpoint import Checkpoint
+
+
+class ColBERT:
+    def __init__(self, name, **kwargs) -> None:
+        print("ColBERT: Loading model", name)
+        self.device = "cuda" if torch.cuda.is_available() else "cpu"
+
+        DOCKER = kwargs.get("env") == "docker"
+        if DOCKER:
+            # This is a workaround for the issue with the docker container
+            # where the torch extension is not loaded properly
+            # and the following error is thrown:
+            # /root/.cache/torch_extensions/py311_cpu/segmented_maxsim_cpp/segmented_maxsim_cpp.so: cannot open shared object file: No such file or directory
+
+            lock_file = (
+                "/root/.cache/torch_extensions/py311_cpu/segmented_maxsim_cpp/lock"
+            )
+            if os.path.exists(lock_file):
+                os.remove(lock_file)
+
+        self.ckpt = Checkpoint(
+            name,
+            colbert_config=ColBERTConfig(model_name=name),
+        ).to(self.device)
+        pass
+
+    def calculate_similarity_scores(self, query_embeddings, document_embeddings):
+
+        query_embeddings = query_embeddings.to(self.device)
+        document_embeddings = document_embeddings.to(self.device)
+
+        # Validate dimensions to ensure compatibility
+        if query_embeddings.dim() != 3:
+            raise ValueError(
+                f"Expected query embeddings to have 3 dimensions, but got {query_embeddings.dim()}."
+            )
+        if document_embeddings.dim() != 3:
+            raise ValueError(
+                f"Expected document embeddings to have 3 dimensions, but got {document_embeddings.dim()}."
+            )
+        if query_embeddings.size(0) not in [1, document_embeddings.size(0)]:
+            raise ValueError(
+                "There should be either one query or queries equal to the number of documents."
+            )
+
+        # Transpose the query embeddings to align for matrix multiplication
+        transposed_query_embeddings = query_embeddings.permute(0, 2, 1)
+        # Compute similarity scores using batch matrix multiplication
+        computed_scores = torch.matmul(document_embeddings, transposed_query_embeddings)
+        # Apply max pooling to extract the highest semantic similarity across each document's sequence
+        maximum_scores = torch.max(computed_scores, dim=1).values
+
+        # Sum up the maximum scores across features to get the overall document relevance scores
+        final_scores = maximum_scores.sum(dim=1)
+
+        normalized_scores = torch.softmax(final_scores, dim=0)
+
+        return normalized_scores.detach().cpu().numpy().astype(np.float32)
+
+    def predict(self, sentences):
+
+        query = sentences[0][0]
+        docs = [i[1] for i in sentences]
+
+        # Embedding the documents
+        embedded_docs = self.ckpt.docFromText(docs, bsize=32)[0]
+        # Embedding the queries
+        embedded_queries = self.ckpt.queryFromText([query], bsize=32)
+        embedded_query = embedded_queries[0]
+
+        # Calculate retrieval scores for the query against all documents
+        scores = self.calculate_similarity_scores(
+            embedded_query.unsqueeze(0), embedded_docs
+        )
+
+        return scores
diff --git a/backend/open_webui/apps/retrieval/utils.py b/backend/open_webui/apps/retrieval/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..153bd804ff7346cd88ee706c0769ae39700aa60f
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/utils.py
@@ -0,0 +1,573 @@
+import logging
+import os
+import uuid
+from typing import Optional, Union
+
+import requests
+
+from huggingface_hub import snapshot_download
+from langchain.retrievers import ContextualCompressionRetriever, EnsembleRetriever
+from langchain_community.retrievers import BM25Retriever
+from langchain_core.documents import Document
+
+
+from open_webui.apps.ollama.main import (
+    GenerateEmbedForm,
+    generate_ollama_batch_embeddings,
+)
+from open_webui.apps.retrieval.vector.connector import VECTOR_DB_CLIENT
+from open_webui.utils.misc import get_last_user_message
+
+from open_webui.env import SRC_LOG_LEVELS
+from open_webui.config import DEFAULT_RAG_TEMPLATE
+
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+from typing import Any
+
+from langchain_core.callbacks import CallbackManagerForRetrieverRun
+from langchain_core.retrievers import BaseRetriever
+
+
+class VectorSearchRetriever(BaseRetriever):
+    collection_name: Any
+    embedding_function: Any
+    top_k: int
+
+    def _get_relevant_documents(
+        self,
+        query: str,
+        *,
+        run_manager: CallbackManagerForRetrieverRun,
+    ) -> list[Document]:
+        result = VECTOR_DB_CLIENT.search(
+            collection_name=self.collection_name,
+            vectors=[self.embedding_function(query)],
+            limit=self.top_k,
+        )
+
+        ids = result.ids[0]
+        metadatas = result.metadatas[0]
+        documents = result.documents[0]
+
+        results = []
+        for idx in range(len(ids)):
+            results.append(
+                Document(
+                    metadata=metadatas[idx],
+                    page_content=documents[idx],
+                )
+            )
+        return results
+
+
+def query_doc(
+    collection_name: str,
+    query_embedding: list[float],
+    k: int,
+):
+    try:
+        result = VECTOR_DB_CLIENT.search(
+            collection_name=collection_name,
+            vectors=[query_embedding],
+            limit=k,
+        )
+
+        log.info(f"query_doc:result {result}")
+        return result
+    except Exception as e:
+        print(e)
+        raise e
+
+
+def query_doc_with_hybrid_search(
+    collection_name: str,
+    query: str,
+    embedding_function,
+    k: int,
+    reranking_function,
+    r: float,
+) -> dict:
+    try:
+        result = VECTOR_DB_CLIENT.get(collection_name=collection_name)
+
+        bm25_retriever = BM25Retriever.from_texts(
+            texts=result.documents[0],
+            metadatas=result.metadatas[0],
+        )
+        bm25_retriever.k = k
+
+        vector_search_retriever = VectorSearchRetriever(
+            collection_name=collection_name,
+            embedding_function=embedding_function,
+            top_k=k,
+        )
+
+        ensemble_retriever = EnsembleRetriever(
+            retrievers=[bm25_retriever, vector_search_retriever], weights=[0.5, 0.5]
+        )
+        compressor = RerankCompressor(
+            embedding_function=embedding_function,
+            top_n=k,
+            reranking_function=reranking_function,
+            r_score=r,
+        )
+
+        compression_retriever = ContextualCompressionRetriever(
+            base_compressor=compressor, base_retriever=ensemble_retriever
+        )
+
+        result = compression_retriever.invoke(query)
+        result = {
+            "distances": [[d.metadata.get("score") for d in result]],
+            "documents": [[d.page_content for d in result]],
+            "metadatas": [[d.metadata for d in result]],
+        }
+
+        log.info(f"query_doc_with_hybrid_search:result {result}")
+        return result
+    except Exception as e:
+        raise e
+
+
+def merge_and_sort_query_results(
+    query_results: list[dict], k: int, reverse: bool = False
+) -> list[dict]:
+    # Initialize lists to store combined data
+    combined_distances = []
+    combined_documents = []
+    combined_metadatas = []
+
+    for data in query_results:
+        combined_distances.extend(data["distances"][0])
+        combined_documents.extend(data["documents"][0])
+        combined_metadatas.extend(data["metadatas"][0])
+
+    # Create a list of tuples (distance, document, metadata)
+    combined = list(zip(combined_distances, combined_documents, combined_metadatas))
+
+    # Sort the list based on distances
+    combined.sort(key=lambda x: x[0], reverse=reverse)
+
+    # We don't have anything :-(
+    if not combined:
+        sorted_distances = []
+        sorted_documents = []
+        sorted_metadatas = []
+    else:
+        # Unzip the sorted list
+        sorted_distances, sorted_documents, sorted_metadatas = zip(*combined)
+
+        # Slicing the lists to include only k elements
+        sorted_distances = list(sorted_distances)[:k]
+        sorted_documents = list(sorted_documents)[:k]
+        sorted_metadatas = list(sorted_metadatas)[:k]
+
+    # Create the output dictionary
+    result = {
+        "distances": [sorted_distances],
+        "documents": [sorted_documents],
+        "metadatas": [sorted_metadatas],
+    }
+
+    return result
+
+
+def query_collection(
+    collection_names: list[str],
+    query: str,
+    embedding_function,
+    k: int,
+) -> dict:
+
+    results = []
+    query_embedding = embedding_function(query)
+
+    for collection_name in collection_names:
+        if collection_name:
+            try:
+                result = query_doc(
+                    collection_name=collection_name,
+                    k=k,
+                    query_embedding=query_embedding,
+                )
+                if result is not None:
+                    results.append(result.model_dump())
+            except Exception as e:
+                log.exception(f"Error when querying the collection: {e}")
+        else:
+            pass
+
+    return merge_and_sort_query_results(results, k=k)
+
+
+def query_collection_with_hybrid_search(
+    collection_names: list[str],
+    query: str,
+    embedding_function,
+    k: int,
+    reranking_function,
+    r: float,
+) -> dict:
+    results = []
+    error = False
+    for collection_name in collection_names:
+        try:
+            result = query_doc_with_hybrid_search(
+                collection_name=collection_name,
+                query=query,
+                embedding_function=embedding_function,
+                k=k,
+                reranking_function=reranking_function,
+                r=r,
+            )
+            results.append(result)
+        except Exception as e:
+            log.exception(
+                "Error when querying the collection with " f"hybrid_search: {e}"
+            )
+            error = True
+
+    if error:
+        raise Exception(
+            "Hybrid search failed for all collections. Using Non hybrid search as fallback."
+        )
+
+    return merge_and_sort_query_results(results, k=k, reverse=True)
+
+
+def rag_template(template: str, context: str, query: str):
+    if template == "":
+        template = DEFAULT_RAG_TEMPLATE
+
+    if "[context]" not in template and "{{CONTEXT}}" not in template:
+        log.debug(
+            "WARNING: The RAG template does not contain the '[context]' or '{{CONTEXT}}' placeholder."
+        )
+
+    if "<context>" in context and "</context>" in context:
+        log.debug(
+            "WARNING: Potential prompt injection attack: the RAG "
+            "context contains '<context>' and '</context>'. This might be "
+            "nothing, or the user might be trying to hack something."
+        )
+
+    query_placeholders = []
+    if "[query]" in context:
+        query_placeholder = "{{QUERY" + str(uuid.uuid4()) + "}}"
+        template = template.replace("[query]", query_placeholder)
+        query_placeholders.append(query_placeholder)
+
+    if "{{QUERY}}" in context:
+        query_placeholder = "{{QUERY" + str(uuid.uuid4()) + "}}"
+        template = template.replace("{{QUERY}}", query_placeholder)
+        query_placeholders.append(query_placeholder)
+
+    template = template.replace("[context]", context)
+    template = template.replace("{{CONTEXT}}", context)
+    template = template.replace("[query]", query)
+    template = template.replace("{{QUERY}}", query)
+
+    for query_placeholder in query_placeholders:
+        template = template.replace(query_placeholder, query)
+
+    return template
+
+
+def get_embedding_function(
+    embedding_engine,
+    embedding_model,
+    embedding_function,
+    openai_key,
+    openai_url,
+    embedding_batch_size,
+):
+    if embedding_engine == "":
+        return lambda query: embedding_function.encode(query).tolist()
+    elif embedding_engine in ["ollama", "openai"]:
+        func = lambda query: generate_embeddings(
+            engine=embedding_engine,
+            model=embedding_model,
+            text=query,
+            key=openai_key if embedding_engine == "openai" else "",
+            url=openai_url if embedding_engine == "openai" else "",
+        )
+
+        def generate_multiple(query, func):
+            if isinstance(query, list):
+                embeddings = []
+                for i in range(0, len(query), embedding_batch_size):
+                    embeddings.extend(func(query[i : i + embedding_batch_size]))
+                return embeddings
+            else:
+                return func(query)
+
+        return lambda query: generate_multiple(query, func)
+
+
+def get_rag_context(
+    files,
+    messages,
+    embedding_function,
+    k,
+    reranking_function,
+    r,
+    hybrid_search,
+):
+    log.debug(f"files: {files} {messages} {embedding_function} {reranking_function}")
+    query = get_last_user_message(messages)
+
+    extracted_collections = []
+    relevant_contexts = []
+
+    for file in files:
+        if file.get("context") == "full":
+            context = {
+                "documents": [[file.get("file").get("data", {}).get("content")]],
+                "metadatas": [[{"file_id": file.get("id"), "name": file.get("name")}]],
+            }
+        else:
+            context = None
+
+            collection_names = []
+            if file.get("type") == "collection":
+                if file.get("legacy"):
+                    collection_names = file.get("collection_names", [])
+                else:
+                    collection_names.append(file["id"])
+            elif file.get("collection_name"):
+                collection_names.append(file["collection_name"])
+            elif file.get("id"):
+                if file.get("legacy"):
+                    collection_names.append(f"{file['id']}")
+                else:
+                    collection_names.append(f"file-{file['id']}")
+
+            collection_names = set(collection_names).difference(extracted_collections)
+            if not collection_names:
+                log.debug(f"skipping {file} as it has already been extracted")
+                continue
+
+            try:
+                context = None
+                if file.get("type") == "text":
+                    context = file["content"]
+                else:
+                    if hybrid_search:
+                        try:
+                            context = query_collection_with_hybrid_search(
+                                collection_names=collection_names,
+                                query=query,
+                                embedding_function=embedding_function,
+                                k=k,
+                                reranking_function=reranking_function,
+                                r=r,
+                            )
+                        except Exception as e:
+                            log.debug(
+                                "Error when using hybrid search, using"
+                                " non hybrid search as fallback."
+                            )
+
+                    if (not hybrid_search) or (context is None):
+                        context = query_collection(
+                            collection_names=collection_names,
+                            query=query,
+                            embedding_function=embedding_function,
+                            k=k,
+                        )
+            except Exception as e:
+                log.exception(e)
+
+            extracted_collections.extend(collection_names)
+
+        if context:
+            if "data" in file:
+                del file["data"]
+            relevant_contexts.append({**context, "file": file})
+
+    contexts = []
+    citations = []
+    for context in relevant_contexts:
+        try:
+            if "documents" in context:
+                file_names = list(
+                    set(
+                        [
+                            metadata["name"]
+                            for metadata in context["metadatas"][0]
+                            if metadata is not None and "name" in metadata
+                        ]
+                    )
+                )
+                contexts.append(
+                    ((", ".join(file_names) + ":\n\n") if file_names else "")
+                    + "\n\n".join(
+                        [text for text in context["documents"][0] if text is not None]
+                    )
+                )
+
+                if "metadatas" in context:
+                    citation = {
+                        "source": context["file"],
+                        "document": context["documents"][0],
+                        "metadata": context["metadatas"][0],
+                    }
+                    if "distances" in context and context["distances"]:
+                        citation["distances"] = context["distances"][0]
+                    citations.append(citation)
+        except Exception as e:
+            log.exception(e)
+
+    print("contexts", contexts)
+    print("citations", citations)
+
+    return contexts, citations
+
+
+def get_model_path(model: str, update_model: bool = False):
+    # Construct huggingface_hub kwargs with local_files_only to return the snapshot path
+    cache_dir = os.getenv("SENTENCE_TRANSFORMERS_HOME")
+
+    local_files_only = not update_model
+
+    snapshot_kwargs = {
+        "cache_dir": cache_dir,
+        "local_files_only": local_files_only,
+    }
+
+    log.debug(f"model: {model}")
+    log.debug(f"snapshot_kwargs: {snapshot_kwargs}")
+
+    # Inspiration from upstream sentence_transformers
+    if (
+        os.path.exists(model)
+        or ("\\" in model or model.count("/") > 1)
+        and local_files_only
+    ):
+        # If fully qualified path exists, return input, else set repo_id
+        return model
+    elif "/" not in model:
+        # Set valid repo_id for model short-name
+        model = "sentence-transformers" + "/" + model
+
+    snapshot_kwargs["repo_id"] = model
+
+    # Attempt to query the huggingface_hub library to determine the local path and/or to update
+    try:
+        model_repo_path = snapshot_download(**snapshot_kwargs)
+        log.debug(f"model_repo_path: {model_repo_path}")
+        return model_repo_path
+    except Exception as e:
+        log.exception(f"Cannot determine model snapshot path: {e}")
+        return model
+
+
+def generate_openai_batch_embeddings(
+    model: str, texts: list[str], key: str, url: str = "https://api.openai.com/v1"
+) -> Optional[list[list[float]]]:
+    try:
+        r = requests.post(
+            f"{url}/embeddings",
+            headers={
+                "Content-Type": "application/json",
+                "Authorization": f"Bearer {key}",
+            },
+            json={"input": texts, "model": model},
+        )
+        r.raise_for_status()
+        data = r.json()
+        if "data" in data:
+            return [elem["embedding"] for elem in data["data"]]
+        else:
+            raise "Something went wrong :/"
+    except Exception as e:
+        print(e)
+        return None
+
+
+def generate_embeddings(engine: str, model: str, text: Union[str, list[str]], **kwargs):
+    if engine == "ollama":
+        if isinstance(text, list):
+            embeddings = generate_ollama_batch_embeddings(
+                GenerateEmbedForm(**{"model": model, "input": text})
+            )
+        else:
+            embeddings = generate_ollama_batch_embeddings(
+                GenerateEmbedForm(**{"model": model, "input": [text]})
+            )
+        return (
+            embeddings["embeddings"][0]
+            if isinstance(text, str)
+            else embeddings["embeddings"]
+        )
+    elif engine == "openai":
+        key = kwargs.get("key", "")
+        url = kwargs.get("url", "https://api.openai.com/v1")
+
+        if isinstance(text, list):
+            embeddings = generate_openai_batch_embeddings(model, text, key, url)
+        else:
+            embeddings = generate_openai_batch_embeddings(model, [text], key, url)
+
+        return embeddings[0] if isinstance(text, str) else embeddings
+
+
+import operator
+from typing import Optional, Sequence
+
+from langchain_core.callbacks import Callbacks
+from langchain_core.documents import BaseDocumentCompressor, Document
+
+
+class RerankCompressor(BaseDocumentCompressor):
+    embedding_function: Any
+    top_n: int
+    reranking_function: Any
+    r_score: float
+
+    class Config:
+        extra = "forbid"
+        arbitrary_types_allowed = True
+
+    def compress_documents(
+        self,
+        documents: Sequence[Document],
+        query: str,
+        callbacks: Optional[Callbacks] = None,
+    ) -> Sequence[Document]:
+        reranking = self.reranking_function is not None
+
+        if reranking:
+            scores = self.reranking_function.predict(
+                [(query, doc.page_content) for doc in documents]
+            )
+        else:
+            from sentence_transformers import util
+
+            query_embedding = self.embedding_function(query)
+            document_embedding = self.embedding_function(
+                [doc.page_content for doc in documents]
+            )
+            scores = util.cos_sim(query_embedding, document_embedding)[0]
+
+        docs_with_scores = list(zip(documents, scores.tolist()))
+        if self.r_score:
+            docs_with_scores = [
+                (d, s) for d, s in docs_with_scores if s >= self.r_score
+            ]
+
+        result = sorted(docs_with_scores, key=operator.itemgetter(1), reverse=True)
+        final_results = []
+        for doc, doc_score in result[: self.top_n]:
+            metadata = doc.metadata
+            metadata["score"] = doc_score
+            doc = Document(
+                page_content=doc.page_content,
+                metadata=metadata,
+            )
+            final_results.append(doc)
+        return final_results
diff --git a/backend/open_webui/apps/retrieval/vector/connector.py b/backend/open_webui/apps/retrieval/vector/connector.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7f00f5fd1632719c22d883b9f66194b56a2dcf2
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/vector/connector.py
@@ -0,0 +1,14 @@
+from open_webui.config import VECTOR_DB
+
+if VECTOR_DB == "milvus":
+    from open_webui.apps.retrieval.vector.dbs.milvus import MilvusClient
+
+    VECTOR_DB_CLIENT = MilvusClient()
+elif VECTOR_DB == "qdrant":
+    from open_webui.apps.retrieval.vector.dbs.qdrant import QdrantClient
+
+    VECTOR_DB_CLIENT = QdrantClient()
+else:
+    from open_webui.apps.retrieval.vector.dbs.chroma import ChromaClient
+
+    VECTOR_DB_CLIENT = ChromaClient()
diff --git a/backend/open_webui/apps/retrieval/vector/dbs/chroma.py b/backend/open_webui/apps/retrieval/vector/dbs/chroma.py
new file mode 100644
index 0000000000000000000000000000000000000000..7782671a23e0622e0b9de1b206908b00565a8532
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/vector/dbs/chroma.py
@@ -0,0 +1,161 @@
+import chromadb
+from chromadb import Settings
+from chromadb.utils.batch_utils import create_batches
+
+from typing import Optional
+
+from open_webui.apps.retrieval.vector.main import VectorItem, SearchResult, GetResult
+from open_webui.config import (
+    CHROMA_DATA_PATH,
+    CHROMA_HTTP_HOST,
+    CHROMA_HTTP_PORT,
+    CHROMA_HTTP_HEADERS,
+    CHROMA_HTTP_SSL,
+    CHROMA_TENANT,
+    CHROMA_DATABASE,
+)
+
+
+class ChromaClient:
+    def __init__(self):
+        if CHROMA_HTTP_HOST != "":
+            self.client = chromadb.HttpClient(
+                host=CHROMA_HTTP_HOST,
+                port=CHROMA_HTTP_PORT,
+                headers=CHROMA_HTTP_HEADERS,
+                ssl=CHROMA_HTTP_SSL,
+                tenant=CHROMA_TENANT,
+                database=CHROMA_DATABASE,
+                settings=Settings(allow_reset=True, anonymized_telemetry=False),
+            )
+        else:
+            self.client = chromadb.PersistentClient(
+                path=CHROMA_DATA_PATH,
+                settings=Settings(allow_reset=True, anonymized_telemetry=False),
+                tenant=CHROMA_TENANT,
+                database=CHROMA_DATABASE,
+            )
+
+    def has_collection(self, collection_name: str) -> bool:
+        # Check if the collection exists based on the collection name.
+        collections = self.client.list_collections()
+        return collection_name in [collection.name for collection in collections]
+
+    def delete_collection(self, collection_name: str):
+        # Delete the collection based on the collection name.
+        return self.client.delete_collection(name=collection_name)
+
+    def search(
+        self, collection_name: str, vectors: list[list[float | int]], limit: int
+    ) -> Optional[SearchResult]:
+        # Search for the nearest neighbor items based on the vectors and return 'limit' number of results.
+        try:
+            collection = self.client.get_collection(name=collection_name)
+            if collection:
+                result = collection.query(
+                    query_embeddings=vectors,
+                    n_results=limit,
+                )
+
+                return SearchResult(
+                    **{
+                        "ids": result["ids"],
+                        "distances": result["distances"],
+                        "documents": result["documents"],
+                        "metadatas": result["metadatas"],
+                    }
+                )
+            return None
+        except Exception as e:
+            return None
+
+    def query(
+        self, collection_name: str, filter: dict, limit: Optional[int] = None
+    ) -> Optional[GetResult]:
+        # Query the items from the collection based on the filter.
+        try:
+            collection = self.client.get_collection(name=collection_name)
+            if collection:
+                result = collection.get(
+                    where=filter,
+                    limit=limit,
+                )
+
+                return GetResult(
+                    **{
+                        "ids": [result["ids"]],
+                        "documents": [result["documents"]],
+                        "metadatas": [result["metadatas"]],
+                    }
+                )
+            return None
+        except Exception as e:
+            print(e)
+            return None
+
+    def get(self, collection_name: str) -> Optional[GetResult]:
+        # Get all the items in the collection.
+        collection = self.client.get_collection(name=collection_name)
+        if collection:
+            result = collection.get()
+            return GetResult(
+                **{
+                    "ids": [result["ids"]],
+                    "documents": [result["documents"]],
+                    "metadatas": [result["metadatas"]],
+                }
+            )
+        return None
+
+    def insert(self, collection_name: str, items: list[VectorItem]):
+        # Insert the items into the collection, if the collection does not exist, it will be created.
+        collection = self.client.get_or_create_collection(
+            name=collection_name, metadata={"hnsw:space": "cosine"}
+        )
+
+        ids = [item["id"] for item in items]
+        documents = [item["text"] for item in items]
+        embeddings = [item["vector"] for item in items]
+        metadatas = [item["metadata"] for item in items]
+
+        for batch in create_batches(
+            api=self.client,
+            documents=documents,
+            embeddings=embeddings,
+            ids=ids,
+            metadatas=metadatas,
+        ):
+            collection.add(*batch)
+
+    def upsert(self, collection_name: str, items: list[VectorItem]):
+        # Update the items in the collection, if the items are not present, insert them. If the collection does not exist, it will be created.
+        collection = self.client.get_or_create_collection(
+            name=collection_name, metadata={"hnsw:space": "cosine"}
+        )
+
+        ids = [item["id"] for item in items]
+        documents = [item["text"] for item in items]
+        embeddings = [item["vector"] for item in items]
+        metadatas = [item["metadata"] for item in items]
+
+        collection.upsert(
+            ids=ids, documents=documents, embeddings=embeddings, metadatas=metadatas
+        )
+
+    def delete(
+        self,
+        collection_name: str,
+        ids: Optional[list[str]] = None,
+        filter: Optional[dict] = None,
+    ):
+        # Delete the items from the collection based on the ids.
+        collection = self.client.get_collection(name=collection_name)
+        if collection:
+            if ids:
+                collection.delete(ids=ids)
+            elif filter:
+                collection.delete(where=filter)
+
+    def reset(self):
+        # Resets the database. This will delete all collections and item entries.
+        return self.client.reset()
diff --git a/backend/open_webui/apps/retrieval/vector/dbs/milvus.py b/backend/open_webui/apps/retrieval/vector/dbs/milvus.py
new file mode 100644
index 0000000000000000000000000000000000000000..5351f860e045fc55b93a8524fc1a4e5697f8142d
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/vector/dbs/milvus.py
@@ -0,0 +1,286 @@
+from pymilvus import MilvusClient as Client
+from pymilvus import FieldSchema, DataType
+import json
+
+from typing import Optional
+
+from open_webui.apps.retrieval.vector.main import VectorItem, SearchResult, GetResult
+from open_webui.config import (
+    MILVUS_URI,
+)
+
+
+class MilvusClient:
+    def __init__(self):
+        self.collection_prefix = "open_webui"
+        self.client = Client(uri=MILVUS_URI)
+
+    def _result_to_get_result(self, result) -> GetResult:
+        ids = []
+        documents = []
+        metadatas = []
+
+        for match in result:
+            _ids = []
+            _documents = []
+            _metadatas = []
+            for item in match:
+                _ids.append(item.get("id"))
+                _documents.append(item.get("data", {}).get("text"))
+                _metadatas.append(item.get("metadata"))
+
+            ids.append(_ids)
+            documents.append(_documents)
+            metadatas.append(_metadatas)
+
+        return GetResult(
+            **{
+                "ids": ids,
+                "documents": documents,
+                "metadatas": metadatas,
+            }
+        )
+
+    def _result_to_search_result(self, result) -> SearchResult:
+        ids = []
+        distances = []
+        documents = []
+        metadatas = []
+
+        for match in result:
+            _ids = []
+            _distances = []
+            _documents = []
+            _metadatas = []
+
+            for item in match:
+                _ids.append(item.get("id"))
+                _distances.append(item.get("distance"))
+                _documents.append(item.get("entity", {}).get("data", {}).get("text"))
+                _metadatas.append(item.get("entity", {}).get("metadata"))
+
+            ids.append(_ids)
+            distances.append(_distances)
+            documents.append(_documents)
+            metadatas.append(_metadatas)
+
+        return SearchResult(
+            **{
+                "ids": ids,
+                "distances": distances,
+                "documents": documents,
+                "metadatas": metadatas,
+            }
+        )
+
+    def _create_collection(self, collection_name: str, dimension: int):
+        schema = self.client.create_schema(
+            auto_id=False,
+            enable_dynamic_field=True,
+        )
+        schema.add_field(
+            field_name="id",
+            datatype=DataType.VARCHAR,
+            is_primary=True,
+            max_length=65535,
+        )
+        schema.add_field(
+            field_name="vector",
+            datatype=DataType.FLOAT_VECTOR,
+            dim=dimension,
+            description="vector",
+        )
+        schema.add_field(field_name="data", datatype=DataType.JSON, description="data")
+        schema.add_field(
+            field_name="metadata", datatype=DataType.JSON, description="metadata"
+        )
+
+        index_params = self.client.prepare_index_params()
+        index_params.add_index(
+            field_name="vector",
+            index_type="HNSW",
+            metric_type="COSINE",
+            params={"M": 16, "efConstruction": 100},
+        )
+
+        self.client.create_collection(
+            collection_name=f"{self.collection_prefix}_{collection_name}",
+            schema=schema,
+            index_params=index_params,
+        )
+
+    def has_collection(self, collection_name: str) -> bool:
+        # Check if the collection exists based on the collection name.
+        collection_name = collection_name.replace("-", "_")
+        return self.client.has_collection(
+            collection_name=f"{self.collection_prefix}_{collection_name}"
+        )
+
+    def delete_collection(self, collection_name: str):
+        # Delete the collection based on the collection name.
+        collection_name = collection_name.replace("-", "_")
+        return self.client.drop_collection(
+            collection_name=f"{self.collection_prefix}_{collection_name}"
+        )
+
+    def search(
+        self, collection_name: str, vectors: list[list[float | int]], limit: int
+    ) -> Optional[SearchResult]:
+        # Search for the nearest neighbor items based on the vectors and return 'limit' number of results.
+        collection_name = collection_name.replace("-", "_")
+        result = self.client.search(
+            collection_name=f"{self.collection_prefix}_{collection_name}",
+            data=vectors,
+            limit=limit,
+            output_fields=["data", "metadata"],
+        )
+
+        return self._result_to_search_result(result)
+
+    def query(self, collection_name: str, filter: dict, limit: Optional[int] = None):
+        # Construct the filter string for querying
+        collection_name = collection_name.replace("-", "_")
+        if not self.has_collection(collection_name):
+            return None
+
+        filter_string = " && ".join(
+            [
+                f'metadata["{key}"] == {json.dumps(value)}'
+                for key, value in filter.items()
+            ]
+        )
+
+        max_limit = 16383  # The maximum number of records per request
+        all_results = []
+
+        if limit is None:
+            limit = float("inf")  # Use infinity as a placeholder for no limit
+
+        # Initialize offset and remaining to handle pagination
+        offset = 0
+        remaining = limit
+
+        try:
+            # Loop until there are no more items to fetch or the desired limit is reached
+            while remaining > 0:
+                print("remaining", remaining)
+                current_fetch = min(
+                    max_limit, remaining
+                )  # Determine how many items to fetch in this iteration
+
+                results = self.client.query(
+                    collection_name=f"{self.collection_prefix}_{collection_name}",
+                    filter=filter_string,
+                    output_fields=["*"],
+                    limit=current_fetch,
+                    offset=offset,
+                )
+
+                if not results:
+                    break
+
+                all_results.extend(results)
+                results_count = len(results)
+                remaining -= (
+                    results_count  # Decrease remaining by the number of items fetched
+                )
+                offset += results_count
+
+                # Break the loop if the results returned are less than the requested fetch count
+                if results_count < current_fetch:
+                    break
+
+            print(all_results)
+            return self._result_to_get_result([all_results])
+        except Exception as e:
+            print(e)
+            return None
+
+    def get(self, collection_name: str) -> Optional[GetResult]:
+        # Get all the items in the collection.
+        collection_name = collection_name.replace("-", "_")
+        result = self.client.query(
+            collection_name=f"{self.collection_prefix}_{collection_name}",
+            filter='id != ""',
+        )
+        return self._result_to_get_result([result])
+
+    def insert(self, collection_name: str, items: list[VectorItem]):
+        # Insert the items into the collection, if the collection does not exist, it will be created.
+        collection_name = collection_name.replace("-", "_")
+        if not self.client.has_collection(
+            collection_name=f"{self.collection_prefix}_{collection_name}"
+        ):
+            self._create_collection(
+                collection_name=collection_name, dimension=len(items[0]["vector"])
+            )
+
+        return self.client.insert(
+            collection_name=f"{self.collection_prefix}_{collection_name}",
+            data=[
+                {
+                    "id": item["id"],
+                    "vector": item["vector"],
+                    "data": {"text": item["text"]},
+                    "metadata": item["metadata"],
+                }
+                for item in items
+            ],
+        )
+
+    def upsert(self, collection_name: str, items: list[VectorItem]):
+        # Update the items in the collection, if the items are not present, insert them. If the collection does not exist, it will be created.
+        collection_name = collection_name.replace("-", "_")
+        if not self.client.has_collection(
+            collection_name=f"{self.collection_prefix}_{collection_name}"
+        ):
+            self._create_collection(
+                collection_name=collection_name, dimension=len(items[0]["vector"])
+            )
+
+        return self.client.upsert(
+            collection_name=f"{self.collection_prefix}_{collection_name}",
+            data=[
+                {
+                    "id": item["id"],
+                    "vector": item["vector"],
+                    "data": {"text": item["text"]},
+                    "metadata": item["metadata"],
+                }
+                for item in items
+            ],
+        )
+
+    def delete(
+        self,
+        collection_name: str,
+        ids: Optional[list[str]] = None,
+        filter: Optional[dict] = None,
+    ):
+        # Delete the items from the collection based on the ids.
+        collection_name = collection_name.replace("-", "_")
+        if ids:
+            return self.client.delete(
+                collection_name=f"{self.collection_prefix}_{collection_name}",
+                ids=ids,
+            )
+        elif filter:
+            # Convert the filter dictionary to a string using JSON_CONTAINS.
+            filter_string = " && ".join(
+                [
+                    f'metadata["{key}"] == {json.dumps(value)}'
+                    for key, value in filter.items()
+                ]
+            )
+
+            return self.client.delete(
+                collection_name=f"{self.collection_prefix}_{collection_name}",
+                filter=filter_string,
+            )
+
+    def reset(self):
+        # Resets the database. This will delete all collections and item entries.
+        collection_names = self.client.list_collections()
+        for collection_name in collection_names:
+            if collection_name.startswith(self.collection_prefix):
+                self.client.drop_collection(collection_name=collection_name)
diff --git a/backend/open_webui/apps/retrieval/vector/dbs/qdrant.py b/backend/open_webui/apps/retrieval/vector/dbs/qdrant.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1e06872f9c207a9a11c4577b374ec5af4fb1d35
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/vector/dbs/qdrant.py
@@ -0,0 +1,179 @@
+from typing import Optional
+
+from qdrant_client import QdrantClient as Qclient
+from qdrant_client.http.models import PointStruct
+from qdrant_client.models import models
+
+from open_webui.apps.retrieval.vector.main import VectorItem, SearchResult, GetResult
+from open_webui.config import QDRANT_URI
+
+NO_LIMIT = 999999999
+
+
+class QdrantClient:
+    def __init__(self):
+        self.collection_prefix = "open-webui"
+        self.QDRANT_URI = QDRANT_URI
+        self.client = Qclient(url=self.QDRANT_URI) if self.QDRANT_URI else None
+
+    def _result_to_get_result(self, points) -> GetResult:
+        ids = []
+        documents = []
+        metadatas = []
+
+        for point in points:
+            payload = point.payload
+            ids.append(point.id)
+            documents.append(payload["text"])
+            metadatas.append(payload["metadata"])
+
+        return GetResult(
+            **{
+                "ids": [ids],
+                "documents": [documents],
+                "metadatas": [metadatas],
+            }
+        )
+
+    def _create_collection(self, collection_name: str, dimension: int):
+        collection_name_with_prefix = f"{self.collection_prefix}_{collection_name}"
+        self.client.create_collection(
+            collection_name=collection_name_with_prefix,
+            vectors_config=models.VectorParams(
+                size=dimension, distance=models.Distance.COSINE
+            ),
+        )
+
+        print(f"collection {collection_name_with_prefix} successfully created!")
+
+    def _create_collection_if_not_exists(self, collection_name, dimension):
+        if not self.has_collection(collection_name=collection_name):
+            self._create_collection(
+                collection_name=collection_name, dimension=dimension
+            )
+
+    def _create_points(self, items: list[VectorItem]):
+        return [
+            PointStruct(
+                id=item["id"],
+                vector=item["vector"],
+                payload={"text": item["text"], "metadata": item["metadata"]},
+            )
+            for item in items
+        ]
+
+    def has_collection(self, collection_name: str) -> bool:
+        return self.client.collection_exists(
+            f"{self.collection_prefix}_{collection_name}"
+        )
+
+    def delete_collection(self, collection_name: str):
+        return self.client.delete_collection(
+            collection_name=f"{self.collection_prefix}_{collection_name}"
+        )
+
+    def search(
+        self, collection_name: str, vectors: list[list[float | int]], limit: int
+    ) -> Optional[SearchResult]:
+        # Search for the nearest neighbor items based on the vectors and return 'limit' number of results.
+        if limit is None:
+            limit = NO_LIMIT  # otherwise qdrant would set limit to 10!
+
+        query_response = self.client.query_points(
+            collection_name=f"{self.collection_prefix}_{collection_name}",
+            query=vectors[0],
+            limit=limit,
+        )
+        get_result = self._result_to_get_result(query_response.points)
+        return SearchResult(
+            ids=get_result.ids,
+            documents=get_result.documents,
+            metadatas=get_result.metadatas,
+            distances=[[point.score for point in query_response.points]],
+        )
+
+    def query(self, collection_name: str, filter: dict, limit: Optional[int] = None):
+        # Construct the filter string for querying
+        if not self.has_collection(collection_name):
+            return None
+        try:
+            if limit is None:
+                limit = NO_LIMIT  # otherwise qdrant would set limit to 10!
+
+            field_conditions = []
+            for key, value in filter.items():
+                field_conditions.append(
+                    models.FieldCondition(
+                        key=f"metadata.{key}", match=models.MatchValue(value=value)
+                    )
+                )
+
+            points = self.client.query_points(
+                collection_name=f"{self.collection_prefix}_{collection_name}",
+                query_filter=models.Filter(should=field_conditions),
+                limit=limit,
+            )
+            return self._result_to_get_result(points.points)
+        except Exception as e:
+            print(e)
+            return None
+
+    def get(self, collection_name: str) -> Optional[GetResult]:
+        # Get all the items in the collection.
+        points = self.client.query_points(
+            collection_name=f"{self.collection_prefix}_{collection_name}",
+            limit=NO_LIMIT,  # otherwise qdrant would set limit to 10!
+        )
+        return self._result_to_get_result(points.points)
+
+    def insert(self, collection_name: str, items: list[VectorItem]):
+        # Insert the items into the collection, if the collection does not exist, it will be created.
+        self._create_collection_if_not_exists(collection_name, len(items[0]["vector"]))
+        points = self._create_points(items)
+        self.client.upload_points(f"{self.collection_prefix}_{collection_name}", points)
+
+    def upsert(self, collection_name: str, items: list[VectorItem]):
+        # Update the items in the collection, if the items are not present, insert them. If the collection does not exist, it will be created.
+        self._create_collection_if_not_exists(collection_name, len(items[0]["vector"]))
+        points = self._create_points(items)
+        return self.client.upsert(f"{self.collection_prefix}_{collection_name}", points)
+
+    def delete(
+        self,
+        collection_name: str,
+        ids: Optional[list[str]] = None,
+        filter: Optional[dict] = None,
+    ):
+        # Delete the items from the collection based on the ids.
+        field_conditions = []
+
+        if ids:
+            for id_value in ids:
+                field_conditions.append(
+                    models.FieldCondition(
+                        key="metadata.id",
+                        match=models.MatchValue(value=id_value),
+                    ),
+                ),
+        elif filter:
+            for key, value in filter.items():
+                field_conditions.append(
+                    models.FieldCondition(
+                        key=f"metadata.{key}",
+                        match=models.MatchValue(value=value),
+                    ),
+                ),
+
+        return self.client.delete(
+            collection_name=f"{self.collection_prefix}_{collection_name}",
+            points_selector=models.FilterSelector(
+                filter=models.Filter(must=field_conditions)
+            ),
+        )
+
+    def reset(self):
+        # Resets the database. This will delete all collections and item entries.
+        collection_names = self.client.get_collections().collections
+        for collection_name in collection_names:
+            if collection_name.name.startswith(self.collection_prefix):
+                self.client.delete_collection(collection_name=collection_name.name)
diff --git a/backend/open_webui/apps/retrieval/vector/main.py b/backend/open_webui/apps/retrieval/vector/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0cf0c03876c1f803ddc0d6e3f0903026df50a12
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/vector/main.py
@@ -0,0 +1,19 @@
+from pydantic import BaseModel
+from typing import Optional, List, Any
+
+
+class VectorItem(BaseModel):
+    id: str
+    text: str
+    vector: List[float | int]
+    metadata: Any
+
+
+class GetResult(BaseModel):
+    ids: Optional[List[List[str]]]
+    documents: Optional[List[List[str]]]
+    metadatas: Optional[List[List[Any]]]
+
+
+class SearchResult(GetResult):
+    distances: Optional[List[List[float | int]]]
diff --git a/backend/open_webui/apps/retrieval/web/brave.py b/backend/open_webui/apps/retrieval/web/brave.py
new file mode 100644
index 0000000000000000000000000000000000000000..f988b3b08ef6f432dd2205263cbbf662a117fcfc
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/brave.py
@@ -0,0 +1,42 @@
+import logging
+from typing import Optional
+
+import requests
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_brave(
+    api_key: str, query: str, count: int, filter_list: Optional[list[str]] = None
+) -> list[SearchResult]:
+    """Search using Brave's Search API and return the results as a list of SearchResult objects.
+
+    Args:
+        api_key (str): A Brave Search API key
+        query (str): The query to search for
+    """
+    url = "https://api.search.brave.com/res/v1/web/search"
+    headers = {
+        "Accept": "application/json",
+        "Accept-Encoding": "gzip",
+        "X-Subscription-Token": api_key,
+    }
+    params = {"q": query, "count": count}
+
+    response = requests.get(url, headers=headers, params=params)
+    response.raise_for_status()
+
+    json_response = response.json()
+    results = json_response.get("web", {}).get("results", [])
+    if filter_list:
+        results = get_filtered_results(results, filter_list)
+
+    return [
+        SearchResult(
+            link=result["url"], title=result.get("title"), snippet=result.get("snippet")
+        )
+        for result in results[:count]
+    ]
diff --git a/backend/open_webui/apps/retrieval/web/duckduckgo.py b/backend/open_webui/apps/retrieval/web/duckduckgo.py
new file mode 100644
index 0000000000000000000000000000000000000000..11e5122964761b37f992e51325aaf793e951377b
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/duckduckgo.py
@@ -0,0 +1,50 @@
+import logging
+from typing import Optional
+
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
+from duckduckgo_search import DDGS
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_duckduckgo(
+    query: str, count: int, filter_list: Optional[list[str]] = None
+) -> list[SearchResult]:
+    """
+    Search using DuckDuckGo's Search API and return the results as a list of SearchResult objects.
+    Args:
+        query (str): The query to search for
+        count (int): The number of results to return
+
+    Returns:
+        list[SearchResult]: A list of search results
+    """
+    # Use the DDGS context manager to create a DDGS object
+    with DDGS() as ddgs:
+        # Use the ddgs.text() method to perform the search
+        ddgs_gen = ddgs.text(
+            query, safesearch="moderate", max_results=count, backend="api"
+        )
+        # Check if there are search results
+        if ddgs_gen:
+            # Convert the search results into a list
+            search_results = [r for r in ddgs_gen]
+
+    # Create an empty list to store the SearchResult objects
+    results = []
+    # Iterate over each search result
+    for result in search_results:
+        # Create a SearchResult object and append it to the results list
+        results.append(
+            SearchResult(
+                link=result["href"],
+                title=result.get("title"),
+                snippet=result.get("body"),
+            )
+        )
+    if filter_list:
+        results = get_filtered_results(results, filter_list)
+    # Return the list of search results
+    return results
diff --git a/backend/open_webui/apps/retrieval/web/google_pse.py b/backend/open_webui/apps/retrieval/web/google_pse.py
new file mode 100644
index 0000000000000000000000000000000000000000..61b919583ceba95eb02fddd4db41feeef6e5b2ea
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/google_pse.py
@@ -0,0 +1,50 @@
+import logging
+from typing import Optional
+
+import requests
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_google_pse(
+    api_key: str,
+    search_engine_id: str,
+    query: str,
+    count: int,
+    filter_list: Optional[list[str]] = None,
+) -> list[SearchResult]:
+    """Search using Google's Programmable Search Engine API and return the results as a list of SearchResult objects.
+
+    Args:
+        api_key (str): A Programmable Search Engine API key
+        search_engine_id (str): A Programmable Search Engine ID
+        query (str): The query to search for
+    """
+    url = "https://www.googleapis.com/customsearch/v1"
+
+    headers = {"Content-Type": "application/json"}
+    params = {
+        "cx": search_engine_id,
+        "q": query,
+        "key": api_key,
+        "num": count,
+    }
+
+    response = requests.request("GET", url, headers=headers, params=params)
+    response.raise_for_status()
+
+    json_response = response.json()
+    results = json_response.get("items", [])
+    if filter_list:
+        results = get_filtered_results(results, filter_list)
+    return [
+        SearchResult(
+            link=result["link"],
+            title=result.get("title"),
+            snippet=result.get("snippet"),
+        )
+        for result in results
+    ]
diff --git a/backend/open_webui/apps/retrieval/web/jina_search.py b/backend/open_webui/apps/retrieval/web/jina_search.py
new file mode 100644
index 0000000000000000000000000000000000000000..487bbc9483685d9ee109735c587a9da4e2489d24
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/jina_search.py
@@ -0,0 +1,41 @@
+import logging
+
+import requests
+from open_webui.apps.retrieval.web.main import SearchResult
+from open_webui.env import SRC_LOG_LEVELS
+from yarl import URL
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_jina(query: str, count: int) -> list[SearchResult]:
+    """
+    Search using Jina's Search API and return the results as a list of SearchResult objects.
+    Args:
+        query (str): The query to search for
+        count (int): The number of results to return
+
+    Returns:
+        list[SearchResult]: A list of search results
+    """
+    jina_search_endpoint = "https://s.jina.ai/"
+    headers = {
+        "Accept": "application/json",
+    }
+    url = str(URL(jina_search_endpoint + query))
+    response = requests.get(url, headers=headers)
+    response.raise_for_status()
+    data = response.json()
+
+    results = []
+    for result in data["data"][:count]:
+        results.append(
+            SearchResult(
+                link=result["url"],
+                title=result.get("title"),
+                snippet=result.get("content"),
+            )
+        )
+
+    return results
diff --git a/backend/open_webui/apps/retrieval/web/main.py b/backend/open_webui/apps/retrieval/web/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..1af8a70aa15fa97ef5d5bff02ef33b5cc86c42b0
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/main.py
@@ -0,0 +1,22 @@
+from typing import Optional
+from urllib.parse import urlparse
+
+from pydantic import BaseModel
+
+
+def get_filtered_results(results, filter_list):
+    if not filter_list:
+        return results
+    filtered_results = []
+    for result in results:
+        url = result.get("url") or result.get("link", "")
+        domain = urlparse(url).netloc
+        if any(domain.endswith(filtered_domain) for filtered_domain in filter_list):
+            filtered_results.append(result)
+    return filtered_results
+
+
+class SearchResult(BaseModel):
+    link: str
+    title: Optional[str]
+    snippet: Optional[str]
diff --git a/backend/open_webui/apps/retrieval/web/searchapi.py b/backend/open_webui/apps/retrieval/web/searchapi.py
new file mode 100644
index 0000000000000000000000000000000000000000..412dc6b6955c691e0e8050b1cdb71269cc27bce6
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/searchapi.py
@@ -0,0 +1,48 @@
+import logging
+from typing import Optional
+from urllib.parse import urlencode
+
+import requests
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_searchapi(
+    api_key: str,
+    engine: str,
+    query: str,
+    count: int,
+    filter_list: Optional[list[str]] = None,
+) -> list[SearchResult]:
+    """Search using searchapi.io's API and return the results as a list of SearchResult objects.
+
+    Args:
+      api_key (str): A searchapi.io API key
+      query (str): The query to search for
+    """
+    url = "https://www.searchapi.io/api/v1/search"
+
+    engine = engine or "google"
+
+    payload = {"engine": engine, "q": query, "api_key": api_key}
+
+    url = f"{url}?{urlencode(payload)}"
+    response = requests.request("GET", url)
+
+    json_response = response.json()
+    log.info(f"results from searchapi search: {json_response}")
+
+    results = sorted(
+        json_response.get("organic_results", []), key=lambda x: x.get("position", 0)
+    )
+    if filter_list:
+        results = get_filtered_results(results, filter_list)
+    return [
+        SearchResult(
+            link=result["link"], title=result["title"], snippet=result["snippet"]
+        )
+        for result in results[:count]
+    ]
diff --git a/backend/open_webui/apps/retrieval/web/searxng.py b/backend/open_webui/apps/retrieval/web/searxng.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb1eaf91d03f88299e70b8e304b1a97a09aeff38
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/searxng.py
@@ -0,0 +1,91 @@
+import logging
+from typing import Optional
+
+import requests
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_searxng(
+    query_url: str,
+    query: str,
+    count: int,
+    filter_list: Optional[list[str]] = None,
+    **kwargs,
+) -> list[SearchResult]:
+    """
+    Search a SearXNG instance for a given query and return the results as a list of SearchResult objects.
+
+    The function allows passing additional parameters such as language or time_range to tailor the search result.
+
+    Args:
+        query_url (str): The base URL of the SearXNG server.
+        query (str): The search term or question to find in the SearXNG database.
+        count (int): The maximum number of results to retrieve from the search.
+
+    Keyword Args:
+        language (str): Language filter for the search results; e.g., "en-US". Defaults to an empty string.
+        safesearch (int): Safe search filter for safer web results; 0 = off, 1 = moderate, 2 = strict. Defaults to 1 (moderate).
+        time_range (str): Time range for filtering results by date; e.g., "2023-04-05..today" or "all-time". Defaults to ''.
+        categories: (Optional[list[str]]): Specific categories within which the search should be performed, defaulting to an empty string if not provided.
+
+    Returns:
+        list[SearchResult]: A list of SearchResults sorted by relevance score in descending order.
+
+    Raise:
+        requests.exceptions.RequestException: If a request error occurs during the search process.
+    """
+
+    # Default values for optional parameters are provided as empty strings or None when not specified.
+    language = kwargs.get("language", "en-US")
+    safesearch = kwargs.get("safesearch", "1")
+    time_range = kwargs.get("time_range", "")
+    categories = "".join(kwargs.get("categories", []))
+
+    params = {
+        "q": query,
+        "format": "json",
+        "pageno": 1,
+        "safesearch": safesearch,
+        "language": language,
+        "time_range": time_range,
+        "categories": categories,
+        "theme": "simple",
+        "image_proxy": 0,
+    }
+
+    # Legacy query format
+    if "<query>" in query_url:
+        # Strip all query parameters from the URL
+        query_url = query_url.split("?")[0]
+
+    log.debug(f"searching {query_url}")
+
+    response = requests.get(
+        query_url,
+        headers={
+            "User-Agent": "Open WebUI (https://github.com/open-webui/open-webui) RAG Bot",
+            "Accept": "text/html",
+            "Accept-Encoding": "gzip, deflate",
+            "Accept-Language": "en-US,en;q=0.5",
+            "Connection": "keep-alive",
+        },
+        params=params,
+    )
+
+    response.raise_for_status()  # Raise an exception for HTTP errors.
+
+    json_response = response.json()
+    results = json_response.get("results", [])
+    sorted_results = sorted(results, key=lambda x: x.get("score", 0), reverse=True)
+    if filter_list:
+        sorted_results = get_filtered_results(sorted_results, filter_list)
+    return [
+        SearchResult(
+            link=result["url"], title=result.get("title"), snippet=result.get("content")
+        )
+        for result in sorted_results[:count]
+    ]
diff --git a/backend/open_webui/apps/retrieval/web/serper.py b/backend/open_webui/apps/retrieval/web/serper.py
new file mode 100644
index 0000000000000000000000000000000000000000..436fa167e989ad5aba782f57e2cf36ba1e8bb619
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/serper.py
@@ -0,0 +1,43 @@
+import json
+import logging
+from typing import Optional
+
+import requests
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_serper(
+    api_key: str, query: str, count: int, filter_list: Optional[list[str]] = None
+) -> list[SearchResult]:
+    """Search using serper.dev's API and return the results as a list of SearchResult objects.
+
+    Args:
+        api_key (str): A serper.dev API key
+        query (str): The query to search for
+    """
+    url = "https://google.serper.dev/search"
+
+    payload = json.dumps({"q": query})
+    headers = {"X-API-KEY": api_key, "Content-Type": "application/json"}
+
+    response = requests.request("POST", url, headers=headers, data=payload)
+    response.raise_for_status()
+
+    json_response = response.json()
+    results = sorted(
+        json_response.get("organic", []), key=lambda x: x.get("position", 0)
+    )
+    if filter_list:
+        results = get_filtered_results(results, filter_list)
+    return [
+        SearchResult(
+            link=result["link"],
+            title=result.get("title"),
+            snippet=result.get("description"),
+        )
+        for result in results[:count]
+    ]
diff --git a/backend/open_webui/apps/retrieval/web/serply.py b/backend/open_webui/apps/retrieval/web/serply.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c2521c47ab894fe1a8d8eca73af8a272af2c96e
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/serply.py
@@ -0,0 +1,69 @@
+import logging
+from typing import Optional
+from urllib.parse import urlencode
+
+import requests
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_serply(
+    api_key: str,
+    query: str,
+    count: int,
+    hl: str = "us",
+    limit: int = 10,
+    device_type: str = "desktop",
+    proxy_location: str = "US",
+    filter_list: Optional[list[str]] = None,
+) -> list[SearchResult]:
+    """Search using serper.dev's API and return the results as a list of SearchResult objects.
+
+    Args:
+        api_key (str): A serply.io API key
+        query (str): The query to search for
+        hl (str): Host Language code to display results in (reference https://developers.google.com/custom-search/docs/xml_results?hl=en#wsInterfaceLanguages)
+        limit (int): The maximum number of results to return [10-100, defaults to 10]
+    """
+    log.info("Searching with Serply")
+
+    url = "https://api.serply.io/v1/search/"
+
+    query_payload = {
+        "q": query,
+        "language": "en",
+        "num": limit,
+        "gl": proxy_location.upper(),
+        "hl": hl.lower(),
+    }
+
+    url = f"{url}{urlencode(query_payload)}"
+    headers = {
+        "X-API-KEY": api_key,
+        "X-User-Agent": device_type,
+        "User-Agent": "open-webui",
+        "X-Proxy-Location": proxy_location,
+    }
+
+    response = requests.request("GET", url, headers=headers)
+    response.raise_for_status()
+
+    json_response = response.json()
+    log.info(f"results from serply search: {json_response}")
+
+    results = sorted(
+        json_response.get("results", []), key=lambda x: x.get("realPosition", 0)
+    )
+    if filter_list:
+        results = get_filtered_results(results, filter_list)
+    return [
+        SearchResult(
+            link=result["link"],
+            title=result.get("title"),
+            snippet=result.get("description"),
+        )
+        for result in results[:count]
+    ]
diff --git a/backend/open_webui/apps/retrieval/web/serpstack.py b/backend/open_webui/apps/retrieval/web/serpstack.py
new file mode 100644
index 0000000000000000000000000000000000000000..b655934de5a692faa01e4d28ebb7c41ecda85df3
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/serpstack.py
@@ -0,0 +1,48 @@
+import logging
+from typing import Optional
+
+import requests
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_serpstack(
+    api_key: str,
+    query: str,
+    count: int,
+    filter_list: Optional[list[str]] = None,
+    https_enabled: bool = True,
+) -> list[SearchResult]:
+    """Search using serpstack.com's and return the results as a list of SearchResult objects.
+
+    Args:
+        api_key (str): A serpstack.com API key
+        query (str): The query to search for
+        https_enabled (bool): Whether to use HTTPS or HTTP for the API request
+    """
+    url = f"{'https' if https_enabled else 'http'}://api.serpstack.com/search"
+
+    headers = {"Content-Type": "application/json"}
+    params = {
+        "access_key": api_key,
+        "query": query,
+    }
+
+    response = requests.request("POST", url, headers=headers, params=params)
+    response.raise_for_status()
+
+    json_response = response.json()
+    results = sorted(
+        json_response.get("organic_results", []), key=lambda x: x.get("position", 0)
+    )
+    if filter_list:
+        results = get_filtered_results(results, filter_list)
+    return [
+        SearchResult(
+            link=result["url"], title=result.get("title"), snippet=result.get("snippet")
+        )
+        for result in results[:count]
+    ]
diff --git a/backend/open_webui/apps/retrieval/web/tavily.py b/backend/open_webui/apps/retrieval/web/tavily.py
new file mode 100644
index 0000000000000000000000000000000000000000..03b0be75ac027053a5d7dd3d2f6a2fc05db52af0
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/tavily.py
@@ -0,0 +1,38 @@
+import logging
+
+import requests
+from open_webui.apps.retrieval.web.main import SearchResult
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_tavily(api_key: str, query: str, count: int) -> list[SearchResult]:
+    """Search using Tavily's Search API and return the results as a list of SearchResult objects.
+
+    Args:
+        api_key (str): A Tavily Search API key
+        query (str): The query to search for
+
+    Returns:
+        list[SearchResult]: A list of search results
+    """
+    url = "https://api.tavily.com/search"
+    data = {"query": query, "api_key": api_key}
+
+    response = requests.post(url, json=data)
+    response.raise_for_status()
+
+    json_response = response.json()
+
+    raw_search_results = json_response.get("results", [])
+
+    return [
+        SearchResult(
+            link=result["url"],
+            title=result.get("title", ""),
+            snippet=result.get("content"),
+        )
+        for result in raw_search_results[:count]
+    ]
diff --git a/backend/open_webui/apps/retrieval/web/testdata/brave.json b/backend/open_webui/apps/retrieval/web/testdata/brave.json
new file mode 100644
index 0000000000000000000000000000000000000000..38487390d9067236e8d5bd6afcedfd08ae9e8368
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/testdata/brave.json
@@ -0,0 +1,998 @@
+{
+	"query": {
+		"original": "python",
+		"show_strict_warning": false,
+		"is_navigational": true,
+		"is_news_breaking": false,
+		"spellcheck_off": true,
+		"country": "us",
+		"bad_results": false,
+		"should_fallback": false,
+		"postal_code": "",
+		"city": "",
+		"header_country": "",
+		"more_results_available": true,
+		"state": ""
+	},
+	"mixed": {
+		"type": "mixed",
+		"main": [
+			{
+				"type": "web",
+				"index": 0,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 1,
+				"all": false
+			},
+			{
+				"type": "news",
+				"all": true
+			},
+			{
+				"type": "web",
+				"index": 2,
+				"all": false
+			},
+			{
+				"type": "videos",
+				"all": true
+			},
+			{
+				"type": "web",
+				"index": 3,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 4,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 5,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 6,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 7,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 8,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 9,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 10,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 11,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 12,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 13,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 14,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 15,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 16,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 17,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 18,
+				"all": false
+			},
+			{
+				"type": "web",
+				"index": 19,
+				"all": false
+			}
+		],
+		"top": [],
+		"side": []
+	},
+	"news": {
+		"type": "news",
+		"results": [
+			{
+				"title": "Google lays off staff from Flutter, Dart and Python teams weeks before its developer conference | TechCrunch",
+				"url": "https://techcrunch.com/2024/05/01/google-lays-off-staff-from-flutter-dart-python-weeks-before-its-developer-conference/",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "Google told TechCrunch that Flutter will have new updates to share at I/O this year.",
+				"page_age": "2024-05-02T17:40:05",
+				"family_friendly": true,
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "techcrunch.com",
+					"hostname": "techcrunch.com",
+					"favicon": "https://imgs.search.brave.com/N6VSEVahheQOb7lqfb47dhUOB4XD-6sfQOP94sCe3Oo/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvZGI5Njk0Yzlk/YWM3ZWMwZjg1MTM1/NmIyMWEyNzBjZDZj/ZDQyNmFlNGU0NDRi/MDgyYjQwOGU0Y2Qy/ZWMwNWQ2ZC90ZWNo/Y3J1bmNoLmNvbS8",
+					"path": "› 2024  › 05  › 01  › google-lays-off-staff-from-flutter-dart-python-weeks-before-its-developer-conference"
+				},
+				"breaking": false,
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/gCI5UG8muOEOZDAx9vpu6L6r6R00mD7jOF08-biFoyQ/rs:fit:200:200:1/g:ce/aHR0cHM6Ly90ZWNo/Y3J1bmNoLmNvbS93/cC1jb250ZW50L3Vw/bG9hZHMvMjAxOC8x/MS9HZXR0eUltYWdl/cy0xMDAyNDg0NzQ2/LmpwZz9yZXNpemU9/MTIwMCw4MDA"
+				},
+				"age": "3 days ago",
+				"extra_snippets": [
+					"Ahead of Google’s annual I/O developer conference in May, the tech giant has laid off staff across key teams like Flutter, Dart, Python and others, according to reports from affected employees shared on social media. Google confirmed the layoffs to TechCrunch, but not the specific teams, roles or how many people were let go.",
+					"In a separate post on Reddit, another commenter noted the Python team affected by the layoffs were those who managed the internal Python runtimes and toolchains and worked with OSS Python. Included in this group were “multiple current and former core devs and steering council members,” they said.",
+					"Meanwhile, others shared on Y Combinator’s Hacker News, where a Python team member detailed their specific duties on the technical front and noted that, for years, much of the work was done with fewer than 10 people. Another Hacker News commenter said their early years on the Python team were spent paying down internal technical debt accumulated from not having a strong Python strategy.",
+					"CNBC reports that a total of 200 people were let go across Google’s “Core” teams, which included those working on Python, app platforms, and other engineering roles. Some jobs were being shifted to India and Mexico, it said, citing internal documents."
+				]
+			}
+		],
+		"mutated_by_goggles": false
+	},
+	"type": "search",
+	"videos": {
+		"type": "videos",
+		"results": [
+			{
+				"type": "video_result",
+				"url": "https://www.youtube.com/watch?v=b093aqAZiPU",
+				"title": "👩‍💻 Python for Beginners Tutorial - YouTube",
+				"description": "In this step-by-step Python for beginner's tutorial, learn how you can get started programming in Python. In this video, I assume that you are completely new...",
+				"age": "March 25, 2021",
+				"page_age": "2021-03-25T10:00:08",
+				"video": {},
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "youtube.com",
+					"hostname": "www.youtube.com",
+					"favicon": "https://imgs.search.brave.com/Ux4Hee4evZhvjuTKwtapBycOGjGDci2Gvn2pbSzvbC0/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTkyZTZiMWU3/YzU3Nzc5YjExYzUy/N2VhZTIxOWNlYjM5/ZGVjN2MyZDY4Nzdh/ZDYzMTYxNmI5N2Rk/Y2Q3N2FkNy93d3cu/eW91dHViZS5jb20v",
+					"path": "› watch"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/tZI4Do4_EYcTCsD_MvE3Jx8FzjIXwIJ5ZuKhwiWTyZs/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9pLnl0/aW1nLmNvbS92aS9i/MDkzYXFBWmlQVS9t/YXhyZXNkZWZhdWx0/LmpwZw"
+				}
+			},
+			{
+				"type": "video_result",
+				"url": "https://www.youtube.com/watch?v=rfscVS0vtbw",
+				"title": "Learn Python - Full Course for Beginners [Tutorial] - YouTube",
+				"description": "This course will give you a full introduction into all of the core concepts in python. Follow along with the videos and you'll be a python programmer in no t...",
+				"age": "July 11, 2018",
+				"page_age": "2018-07-11T18:00:42",
+				"video": {},
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "youtube.com",
+					"hostname": "www.youtube.com",
+					"favicon": "https://imgs.search.brave.com/Ux4Hee4evZhvjuTKwtapBycOGjGDci2Gvn2pbSzvbC0/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTkyZTZiMWU3/YzU3Nzc5YjExYzUy/N2VhZTIxOWNlYjM5/ZGVjN2MyZDY4Nzdh/ZDYzMTYxNmI5N2Rk/Y2Q3N2FkNy93d3cu/eW91dHViZS5jb20v",
+					"path": "› watch"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/65zkx_kPU_zJb-4nmvvY-q5-ZZwzceChz-N00V8cqvk/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9pLnl0/aW1nLmNvbS92aS9y/ZnNjVlMwdnRidy9t/YXhyZXNkZWZhdWx0/LmpwZw"
+				}
+			},
+			{
+				"type": "video_result",
+				"url": "https://www.youtube.com/watch?v=_uQrJ0TkZlc",
+				"title": "Python Tutorial - Python Full Course for Beginners - YouTube",
+				"description": "Become a Python pro! 🚀 This comprehensive tutorial takes you from beginner to hero, covering the basics, machine learning, and web development projects.🚀 W...",
+				"age": "February 18, 2019",
+				"page_age": "2019-02-18T15:00:08",
+				"video": {},
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "youtube.com",
+					"hostname": "www.youtube.com",
+					"favicon": "https://imgs.search.brave.com/Ux4Hee4evZhvjuTKwtapBycOGjGDci2Gvn2pbSzvbC0/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTkyZTZiMWU3/YzU3Nzc5YjExYzUy/N2VhZTIxOWNlYjM5/ZGVjN2MyZDY4Nzdh/ZDYzMTYxNmI5N2Rk/Y2Q3N2FkNy93d3cu/eW91dHViZS5jb20v",
+					"path": "› watch"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/Djiv1pXLq1ClqBSE_86jQnEYR8bW8UJP6Cs7LrgyQzQ/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9pLnl0/aW1nLmNvbS92aS9f/dVFySjBUa1psYy9t/YXhyZXNkZWZhdWx0/LmpwZw"
+				}
+			},
+			{
+				"type": "video_result",
+				"url": "https://www.youtube.com/watch?v=wRKgzC-MhIc",
+				"title": "[] and {} vs list() and dict(), which is better?",
+				"description": "Enjoy the videos and music you love, upload original content, and share it all with friends, family, and the world on YouTube.",
+				"video": {},
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "youtube.com",
+					"hostname": "www.youtube.com",
+					"favicon": "https://imgs.search.brave.com/Ux4Hee4evZhvjuTKwtapBycOGjGDci2Gvn2pbSzvbC0/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTkyZTZiMWU3/YzU3Nzc5YjExYzUy/N2VhZTIxOWNlYjM5/ZGVjN2MyZDY4Nzdh/ZDYzMTYxNmI5N2Rk/Y2Q3N2FkNy93d3cu/eW91dHViZS5jb20v",
+					"path": "› watch"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/Hw9ep2Pio13X1VZjRw_h9R2VH_XvZFOuGlQJVnVkeq0/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9pLnl0/aW1nLmNvbS92aS93/UktnekMtTWhJYy9o/cWRlZmF1bHQuanBn"
+				}
+			},
+			{
+				"type": "video_result",
+				"url": "https://www.youtube.com/watch?v=LWdsF79H1Pg",
+				"title": "print() vs. return in Python Functions - YouTube",
+				"description": "In this video, you will learn the differences between the return statement and the print function when they are used inside Python functions. We will see an ...",
+				"age": "June 11, 2022",
+				"page_age": "2022-06-11T21:33:26",
+				"video": {},
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "youtube.com",
+					"hostname": "www.youtube.com",
+					"favicon": "https://imgs.search.brave.com/Ux4Hee4evZhvjuTKwtapBycOGjGDci2Gvn2pbSzvbC0/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTkyZTZiMWU3/YzU3Nzc5YjExYzUy/N2VhZTIxOWNlYjM5/ZGVjN2MyZDY4Nzdh/ZDYzMTYxNmI5N2Rk/Y2Q3N2FkNy93d3cu/eW91dHViZS5jb20v",
+					"path": "› watch"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/ebglnr5_jwHHpvon3WU-5hzt0eHdTZSVGg3Ts6R38xY/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9pLnl0/aW1nLmNvbS92aS9M/V2RzRjc5SDFQZy9t/YXhyZXNkZWZhdWx0/LmpwZw"
+				}
+			},
+			{
+				"type": "video_result",
+				"url": "https://www.youtube.com/watch?v=AovxLr8jUH4",
+				"title": "Python Tutorial for Beginners 5 - Python print() and input() Function ...",
+				"description": "In this Video I am going to show How to use print() Function and input() Function in Python. In python The print() function is used to print the specified ...",
+				"age": "August 28, 2018",
+				"page_age": "2018-08-28T20:11:09",
+				"video": {},
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "youtube.com",
+					"hostname": "www.youtube.com",
+					"favicon": "https://imgs.search.brave.com/Ux4Hee4evZhvjuTKwtapBycOGjGDci2Gvn2pbSzvbC0/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTkyZTZiMWU3/YzU3Nzc5YjExYzUy/N2VhZTIxOWNlYjM5/ZGVjN2MyZDY4Nzdh/ZDYzMTYxNmI5N2Rk/Y2Q3N2FkNy93d3cu/eW91dHViZS5jb20v",
+					"path": "› watch"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/nCoLEcWkKtiecprWbS6nufwGCaSbPH7o0-sMeIkFmjI/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9pLnl0/aW1nLmNvbS92aS9B/b3Z4THI4alVINC9o/cWRlZmF1bHQuanBn"
+				}
+			}
+		],
+		"mutated_by_goggles": false
+	},
+	"web": {
+		"type": "search",
+		"results": [
+			{
+				"title": "Welcome to Python.org",
+				"url": "https://www.python.org",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "The official home of the <strong>Python</strong> Programming Language",
+				"page_age": "2023-09-09T15:55:05",
+				"profile": {
+					"name": "Python",
+					"url": "https://www.python.org",
+					"long_name": "python.org",
+					"img": "https://imgs.search.brave.com/vBaRH-v6oPS4csO4cdvuKhZ7-xDVvydin3oe3zXYxAI/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvNTJjMzZjNDBj/MmIzODgwMGUyOTRj/Y2E5MjM3YjRkYTZj/YWI1Yzk1NTlmYTgw/ZDBjNzM0MGMxZjQz/YWFjNTczYy93d3cu/cHl0aG9uLm9yZy8"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "python.org",
+					"hostname": "www.python.org",
+					"favicon": "https://imgs.search.brave.com/vBaRH-v6oPS4csO4cdvuKhZ7-xDVvydin3oe3zXYxAI/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvNTJjMzZjNDBj/MmIzODgwMGUyOTRj/Y2E5MjM3YjRkYTZj/YWI1Yzk1NTlmYTgw/ZDBjNzM0MGMxZjQz/YWFjNTczYy93d3cu/cHl0aG9uLm9yZy8",
+					"path": ""
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/GGfNfe5rxJ8QWEoxXniSLc0-POLU3qPyTIpuqPdbmXk/rs:fit:200:200:1/g:ce/aHR0cHM6Ly93d3cu/cHl0aG9uLm9yZy9z/dGF0aWMvb3Blbmdy/YXBoLWljb24tMjAw/eDIwMC5wbmc",
+					"original": "https://www.python.org/static/opengraph-icon-200x200.png",
+					"logo": false
+				},
+				"age": "September 9, 2023",
+				"cluster_type": "generic",
+				"cluster": [
+					{
+						"title": "Downloads",
+						"url": "https://www.python.org/downloads/",
+						"is_source_local": false,
+						"is_source_both": false,
+						"description": "The official home of the <strong>Python</strong> Programming Language",
+						"family_friendly": true
+					},
+					{
+						"title": "Macos",
+						"url": "https://www.python.org/downloads/macos/",
+						"is_source_local": false,
+						"is_source_both": false,
+						"description": "The official home of the <strong>Python</strong> Programming Language",
+						"family_friendly": true
+					},
+					{
+						"title": "Windows",
+						"url": "https://www.python.org/downloads/windows/",
+						"is_source_local": false,
+						"is_source_both": false,
+						"description": "The official home of the <strong>Python</strong> Programming Language",
+						"family_friendly": true
+					},
+					{
+						"title": "Getting Started",
+						"url": "https://www.python.org/about/gettingstarted/",
+						"is_source_local": false,
+						"is_source_both": false,
+						"description": "The official home of the <strong>Python</strong> Programming Language",
+						"family_friendly": true
+					}
+				],
+				"extra_snippets": [
+					"Calculations are simple with Python, and expression syntax is straightforward: the operators +, -, * and / work as expected; parentheses () can be used for grouping. More about simple math functions in Python 3.",
+					"The core of extensible programming is defining functions. Python allows mandatory and optional arguments, keyword arguments, and even arbitrary argument lists. More about defining functions in Python 3",
+					"Lists (known as arrays in other languages) are one of the compound data types that Python understands. Lists can be indexed, sliced and manipulated with other built-in functions. More about lists in Python 3",
+					"# Python 3: Simple output (with Unicode) >>> print(\"Hello, I'm Python!\") Hello, I'm Python! # Input, assignment >>> name = input('What is your name?\\n') >>> print('Hi, %s.' % name) What is your name? Python Hi, Python."
+				]
+			},
+			{
+				"title": "Python (programming language) - Wikipedia",
+				"url": "https://en.wikipedia.org/wiki/Python_(programming_language)",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "<strong>Python</strong> is a high-level, general-purpose programming language. Its design philosophy emphasizes code readability with the use of significant indentation. <strong>Python</strong> is dynamically typed and garbage-collected. It supports multiple programming paradigms, including structured (particularly procedural), ...",
+				"page_age": "2024-05-01T12:54:03",
+				"profile": {
+					"name": "Wikipedia",
+					"url": "https://en.wikipedia.org/wiki/Python_(programming_language)",
+					"long_name": "en.wikipedia.org",
+					"img": "https://imgs.search.brave.com/0kxnVOiqv-faZvOJc7zpym4Zin1CTs1f1svfNZSzmfU/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvNjQwNGZhZWY0/ZTQ1YWUzYzQ3MDUw/MmMzMGY3NTQ0ZjNj/NDUwMDk5ZTI3MWRk/NWYyNTM4N2UwOTE0/NTI3ZDQzNy9lbi53/aWtpcGVkaWEub3Jn/Lw"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "en.wikipedia.org",
+					"hostname": "en.wikipedia.org",
+					"favicon": "https://imgs.search.brave.com/0kxnVOiqv-faZvOJc7zpym4Zin1CTs1f1svfNZSzmfU/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvNjQwNGZhZWY0/ZTQ1YWUzYzQ3MDUw/MmMzMGY3NTQ0ZjNj/NDUwMDk5ZTI3MWRk/NWYyNTM4N2UwOTE0/NTI3ZDQzNy9lbi53/aWtpcGVkaWEub3Jn/Lw",
+					"path": "› wiki  › Python_(programming_language)"
+				},
+				"age": "4 days ago",
+				"extra_snippets": [
+					"Python is dynamically typed and garbage-collected. It supports multiple programming paradigms, including structured (particularly procedural), object-oriented and functional programming. It is often described as a \"batteries included\" language due to its comprehensive standard library.",
+					"Guido van Rossum began working on Python in the late 1980s as a successor to the ABC programming language and first released it in 1991 as Python 0.9.0. Python 2.0 was released in 2000. Python 3.0, released in 2008, was a major revision not completely backward-compatible with earlier versions. Python 2.7.18, released in 2020, was the last release of Python 2.",
+					"Python was invented in the late 1980s by Guido van Rossum at Centrum Wiskunde & Informatica (CWI) in the Netherlands as a successor to the ABC programming language, which was inspired by SETL, capable of exception handling and interfacing with the Amoeba operating system.",
+					"Python consistently ranks as one of the most popular programming languages, and has gained widespread use in the machine learning community."
+				]
+			},
+			{
+				"title": "Python Tutorial",
+				"url": "https://www.w3schools.com/python/",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, <strong>Python</strong>, SQL, Java, and many, many more.",
+				"page_age": "2017-12-07T00:00:00",
+				"profile": {
+					"name": "W3Schools",
+					"url": "https://www.w3schools.com/python/",
+					"long_name": "w3schools.com",
+					"img": "https://imgs.search.brave.com/JwO5r7z3HTBkU29vgNH_4rrSWLf2M4-8FMWNvbxrKX8/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYjVlMGVkZDVj/ZGMyZWRmMzAwODRi/ZDAwZGE4NWI3NmU4/MjRhNjEzOGFhZWY3/ZGViMjY1OWY2ZDYw/YTZiOGUyZS93d3cu/dzNzY2hvb2xzLmNv/bS8"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "w3schools.com",
+					"hostname": "www.w3schools.com",
+					"favicon": "https://imgs.search.brave.com/JwO5r7z3HTBkU29vgNH_4rrSWLf2M4-8FMWNvbxrKX8/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYjVlMGVkZDVj/ZGMyZWRmMzAwODRi/ZDAwZGE4NWI3NmU4/MjRhNjEzOGFhZWY3/ZGViMjY1OWY2ZDYw/YTZiOGUyZS93d3cu/dzNzY2hvb2xzLmNv/bS8",
+					"path": "› python"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/EMfp8dodbJehmj0yCJh8317RHuaumsddnHI4bujvFcg/rs:fit:200:200:1/g:ce/aHR0cHM6Ly93d3cu/dzNzY2hvb2xzLmNv/bS9pbWFnZXMvdzNz/Y2hvb2xzX2xvZ29f/NDM2XzIucG5n",
+					"original": "https://www.w3schools.com/images/w3schools_logo_436_2.png",
+					"logo": true
+				},
+				"age": "December 7, 2017",
+				"extra_snippets": [
+					"Well organized and easy to understand Web building tutorials with lots of examples of how to use HTML, CSS, JavaScript, SQL, Python, PHP, Bootstrap, Java, XML and more.",
+					"HTML CSS JAVASCRIPT SQL PYTHON JAVA PHP HOW TO W3.CSS C C++ C# BOOTSTRAP REACT MYSQL JQUERY EXCEL XML DJANGO NUMPY PANDAS NODEJS R TYPESCRIPT ANGULAR GIT POSTGRESQL MONGODB ASP AI GO KOTLIN SASS VUE DSA GEN AI SCIPY AWS CYBERSECURITY DATA SCIENCE",
+					"Python Variables Variable Names Assign Multiple Values Output Variables Global Variables Variable Exercises Python Data Types Python Numbers Python Casting Python Strings",
+					"Python Strings Slicing Strings Modify Strings Concatenate Strings Format Strings Escape Characters String Methods String Exercises Python Booleans Python Operators Python Lists"
+				]
+			},
+			{
+				"title": "Online Python - IDE, Editor, Compiler, Interpreter",
+				"url": "https://www.online-python.com/",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "Build and Run your <strong>Python</strong> code instantly. Online-<strong>Python</strong> is a quick and easy tool that helps you to build, compile, test your <strong>python</strong> programs.",
+				"profile": {
+					"name": "Online-python",
+					"url": "https://www.online-python.com/",
+					"long_name": "online-python.com",
+					"img": "https://imgs.search.brave.com/kfaEvapwHxSsRObO52-I-otYFPHpG1h7UXJyUqDM2Ec/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvZGYxODdjNWQ0/NjZjZTNiMjk5NDY1/MWI5MTgyYjU3Y2Q3/MTI3NGM5MjUzY2Fi/OGQ3MTQ4MmIxMTQx/ZTcxNWFhMC93d3cu/b25saW5lLXB5dGhv/bi5jb20v"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "online-python.com",
+					"hostname": "www.online-python.com",
+					"favicon": "https://imgs.search.brave.com/kfaEvapwHxSsRObO52-I-otYFPHpG1h7UXJyUqDM2Ec/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvZGYxODdjNWQ0/NjZjZTNiMjk5NDY1/MWI5MTgyYjU3Y2Q3/MTI3NGM5MjUzY2Fi/OGQ3MTQ4MmIxMTQx/ZTcxNWFhMC93d3cu/b25saW5lLXB5dGhv/bi5jb20v",
+					"path": ""
+				},
+				"extra_snippets": [
+					"Build, run, and share Python code online for free with the help of online-integrated python's development environment (IDE). It is one of the most efficient, dependable, and potent online compilers for the Python programming language. It is not necessary for you to bother about establishing a Python environment in your local.",
+					"It is one of the most efficient, dependable, and potent online compilers for the Python programming language. It is not necessary for you to bother about establishing a Python environment in your local. Now You can immediately execute the Python code in the web browser of your choice.",
+					"It is not necessary for you to bother about establishing a Python environment in your local. Now You can immediately execute the Python code in the web browser of your choice. Using this Python editor is simple and quick to get up and running with. Simply type in the programme, and then press the RUN button!",
+					"Now You can immediately execute the Python code in the web browser of your choice. Using this Python editor is simple and quick to get up and running with. Simply type in the programme, and then press the RUN button! The code can be saved online by choosing the SHARE option, which also gives you the ability to access your code from any location providing you have internet access."
+				]
+			},
+			{
+				"title": "Python · GitHub",
+				"url": "https://github.com/python",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "Repositories related to the <strong>Python</strong> Programming language - <strong>Python</strong>",
+				"page_age": "2023-03-06T00:00:00",
+				"profile": {
+					"name": "GitHub",
+					"url": "https://github.com/python",
+					"long_name": "github.com",
+					"img": "https://imgs.search.brave.com/v8685zI4XInM0zxlNI2s7oE_2Sb-EL7lAy81WXbkQD8/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYWQyNWM1NjA5/ZjZmZjNlYzI2MDNk/N2VkNmJhYjE2MzZl/MDY5ZTMxMDUzZmY1/NmU3NWIzNWVmMjk0/NTBjMjJjZi9naXRo/dWIuY29tLw"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "github.com",
+					"hostname": "github.com",
+					"favicon": "https://imgs.search.brave.com/v8685zI4XInM0zxlNI2s7oE_2Sb-EL7lAy81WXbkQD8/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYWQyNWM1NjA5/ZjZmZjNlYzI2MDNk/N2VkNmJhYjE2MzZl/MDY5ZTMxMDUzZmY1/NmU3NWIzNWVmMjk0/NTBjMjJjZi9naXRo/dWIuY29tLw",
+					"path": "› python"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/POoaRfu_7gfp-D_O3qMNJrwDqJNbiDu1HuBpNJ_MpVQ/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9hdmF0/YXJzLmdpdGh1YnVz/ZXJjb250ZW50LmNv/bS91LzE1MjU5ODE_/cz0yMDAmYW1wO3Y9/NA",
+					"original": "https://avatars.githubusercontent.com/u/1525981?s=200&amp;v=4",
+					"logo": false
+				},
+				"age": "March 6, 2023",
+				"extra_snippets": ["Configuration for Python planets (e.g. http://planetpython.org)"]
+			},
+			{
+				"title": "Online Python Compiler (Interpreter)",
+				"url": "https://www.programiz.com/python-programming/online-compiler/",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "Write and run <strong>Python</strong> code using our online compiler (interpreter). You can use <strong>Python</strong> Shell like IDLE, and take inputs from the user in our <strong>Python</strong> compiler.",
+				"page_age": "2020-06-02T00:00:00",
+				"profile": {
+					"name": "Programiz",
+					"url": "https://www.programiz.com/python-programming/online-compiler/",
+					"long_name": "programiz.com",
+					"img": "https://imgs.search.brave.com/ozj4JFayZ3Fs5c9eTp7M5g12azQ_Hblgu4dpTuHRz6U/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvMGJlN2U1YjVi/Y2M3ZDU5OGMwMWNi/M2Q3YjhjOTM1ZTFk/Y2NkZjE4NGQwOGIx/MTQ4NjI2YmNhODVj/MzFkMmJhYy93d3cu/cHJvZ3JhbWl6LmNv/bS8"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "programiz.com",
+					"hostname": "www.programiz.com",
+					"favicon": "https://imgs.search.brave.com/ozj4JFayZ3Fs5c9eTp7M5g12azQ_Hblgu4dpTuHRz6U/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvMGJlN2U1YjVi/Y2M3ZDU5OGMwMWNi/M2Q3YjhjOTM1ZTFk/Y2NkZjE4NGQwOGIx/MTQ4NjI2YmNhODVj/MzFkMmJhYy93d3cu/cHJvZ3JhbWl6LmNv/bS8",
+					"path": "› python-programming  › online-compiler"
+				},
+				"age": "June 2, 2020",
+				"extra_snippets": [
+					"Python Online Compiler Online R Compiler SQL Online Editor Online HTML/CSS Editor Online Java Compiler C Online Compiler C++ Online Compiler C# Online Compiler JavaScript Online Compiler Online GoLang Compiler Online PHP Compiler Online Swift Compiler Online Rust Compiler",
+					"# Online Python compiler (interpreter) to run Python online. # Write Python 3 code in this online editor and run it. print(\"Try programiz.pro\")"
+				]
+			},
+			{
+				"title": "Python Developer",
+				"url": "https://twitter.com/Python_Dv/status/1786763460992544791",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "<strong>Python</strong> Developer",
+				"page_age": "2024-05-04T14:30:03",
+				"profile": {
+					"name": "X",
+					"url": "https://twitter.com/Python_Dv/status/1786763460992544791",
+					"long_name": "twitter.com",
+					"img": "https://imgs.search.brave.com/Zq483bGX0GnSgym-1P7iyOyEDX3PkDZSNT8m56F862A/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvN2MxOTUxNzhj/OTY1ZTQ3N2I0MjJk/MTY5NGM0MTRlYWVi/MjU1YWE2NDUwYmQ2/YTA2MDFhMDlkZDEx/NTAzZGNiNi90d2l0/dGVyLmNvbS8"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "twitter.com",
+					"hostname": "twitter.com",
+					"favicon": "https://imgs.search.brave.com/Zq483bGX0GnSgym-1P7iyOyEDX3PkDZSNT8m56F862A/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvN2MxOTUxNzhj/OTY1ZTQ3N2I0MjJk/MTY5NGM0MTRlYWVi/MjU1YWE2NDUwYmQ2/YTA2MDFhMDlkZDEx/NTAzZGNiNi90d2l0/dGVyLmNvbS8",
+					"path": "› Python_Dv  › status  › 1786763460992544791"
+				},
+				"age": "20 hours ago"
+			},
+			{
+				"title": "input table name? - python script - KNIME Extensions - KNIME Community Forum",
+				"url": "https://forum.knime.com/t/input-table-name-python-script/78978",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "Hi, when running a <strong>python</strong> script node, I get the error seen on the screenshot Same happens with this code too: The script input is output from the csv reader node. How can I get the right name for that table? Best wishes, Dario",
+				"page_age": "2024-05-04T09:20:44",
+				"profile": {
+					"name": "Knime",
+					"url": "https://forum.knime.com/t/input-table-name-python-script/78978",
+					"long_name": "forum.knime.com",
+					"img": "https://imgs.search.brave.com/WQoOhAD5i6uEhJ-qXvlWMJwbGA52f2Ycc_ns36EK698/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTAxNzMxNjFl/MzJjNzU5NzRkOTMz/Mjg4NDU2OWUxM2Rj/YzVkOGM3MzIwNzI2/YTY1NzYxNzA1MDE5/NzQzOWU3NC9mb3J1/bS5rbmltZS5jb20v"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "article",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "forum.knime.com",
+					"hostname": "forum.knime.com",
+					"favicon": "https://imgs.search.brave.com/WQoOhAD5i6uEhJ-qXvlWMJwbGA52f2Ycc_ns36EK698/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTAxNzMxNjFl/MzJjNzU5NzRkOTMz/Mjg4NDU2OWUxM2Rj/YzVkOGM3MzIwNzI2/YTY1NzYxNzA1MDE5/NzQzOWU3NC9mb3J1/bS5rbmltZS5jb20v",
+					"path": "  › knime extensions"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/DtEl38dcvuM1kGfhN0T5HfOrsMJcztWNyriLvtDJmKI/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9mb3J1/bS1jZG4ua25pbWUu/Y29tL3VwbG9hZHMv/ZGVmYXVsdC9vcmln/aW5hbC8zWC9lLzYv/ZTY0M2M2NzFlNzAz/MDg2MjkwMWY2YzJh/OWFjOWI5ZmEwM2M3/ZjMwZi5wbmc",
+					"original": "https://forum-cdn.knime.com/uploads/default/original/3X/e/6/e643c671e7030862901f6c2a9ac9b9fa03c7f30f.png",
+					"logo": false
+				},
+				"age": "1 day ago",
+				"extra_snippets": [
+					"Hi, when running a python script node, I get the error seen on the screenshot Same happens with this code too: The script input is output from the csv reader node. How can I get the right name for that table? …"
+				]
+			},
+			{
+				"title": "What does the Double Star operator mean in Python? - GeeksforGeeks",
+				"url": "https://www.geeksforgeeks.org/what-does-the-double-star-operator-mean-in-python/",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.",
+				"page_age": "2023-03-14T17:15:04",
+				"profile": {
+					"name": "GeeksforGeeks",
+					"url": "https://www.geeksforgeeks.org/what-does-the-double-star-operator-mean-in-python/",
+					"long_name": "geeksforgeeks.org",
+					"img": "https://imgs.search.brave.com/fhzcfv5xltx6-YBvJI9RZgS7xZo0dPNaASsrB8YOsCs/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYjBhOGQ3MmNi/ZWE5N2EwMmZjYzA1/ZTI0ZTFhMGUyMTE0/MGM0ZTBmMWZlM2Y2/Yzk2ODMxZTRhYTBi/NDdjYTE0OS93d3cu/Z2Vla3Nmb3JnZWVr/cy5vcmcv"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "article",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "geeksforgeeks.org",
+					"hostname": "www.geeksforgeeks.org",
+					"favicon": "https://imgs.search.brave.com/fhzcfv5xltx6-YBvJI9RZgS7xZo0dPNaASsrB8YOsCs/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYjBhOGQ3MmNi/ZWE5N2EwMmZjYzA1/ZTI0ZTFhMGUyMTE0/MGM0ZTBmMWZlM2Y2/Yzk2ODMxZTRhYTBi/NDdjYTE0OS93d3cu/Z2Vla3Nmb3JnZWVr/cy5vcmcv",
+					"path": "› what-does-the-double-star-operator-mean-in-python"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/GcR-j_dLbyHkbHEI3ffLMi6xpXGhF_2Z8POIoqtokhM/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9tZWRp/YS5nZWVrc2Zvcmdl/ZWtzLm9yZy93cC1j/b250ZW50L3VwbG9h/ZHMvZ2ZnXzIwMFgy/MDAtMTAweDEwMC5w/bmc",
+					"original": "https://media.geeksforgeeks.org/wp-content/uploads/gfg_200X200-100x100.png",
+					"logo": false
+				},
+				"age": "March 14, 2023",
+				"extra_snippets": [
+					"Difference between / vs. // operator in Python",
+					"Double Star or (**) is one of the Arithmetic Operator (Like +, -, *, **, /, //, %) in Python Language. It is also known as Power Operator.",
+					"The time complexity of the given Python program is O(n), where n is the number of key-value pairs in the input dictionary.",
+					"Inplace Operators in Python | Set 2 (ixor(), iand(), ipow(),…)"
+				]
+			},
+			{
+				"title": "r/Python",
+				"url": "https://www.reddit.com/r/Python/",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "The official <strong>Python</strong> community for Reddit! Stay up to date with the latest news, packages, and meta information relating to the <strong>Python</strong> programming language. --- If you have questions or are new to <strong>Python</strong> use r/LearnPython",
+				"page_age": "2022-12-30T16:25:02",
+				"profile": {
+					"name": "Reddit",
+					"url": "https://www.reddit.com/r/Python/",
+					"long_name": "reddit.com",
+					"img": "https://imgs.search.brave.com/mAZYEK9Wi13WLDUge7XZ8YuDTwm6DP6gBjvz1GdYZVY/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvN2ZiNTU0M2Nj/MTFhZjRiYWViZDlk/MjJiMjBjMzFjMDRk/Y2IzYWI0MGI0MjVk/OGY5NzQzOGQ5NzQ5/NWJhMWI0NC93d3cu/cmVkZGl0LmNvbS8"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "reddit.com",
+					"hostname": "www.reddit.com",
+					"favicon": "https://imgs.search.brave.com/mAZYEK9Wi13WLDUge7XZ8YuDTwm6DP6gBjvz1GdYZVY/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvN2ZiNTU0M2Nj/MTFhZjRiYWViZDlk/MjJiMjBjMzFjMDRk/Y2IzYWI0MGI0MjVk/OGY5NzQzOGQ5NzQ5/NWJhMWI0NC93d3cu/cmVkZGl0LmNvbS8",
+					"path": "› r  › Python"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/zWd10t3zg34ciHiAB-K5WWK3h_H4LedeDot9BVX7Ydo/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9zdHls/ZXMucmVkZGl0bWVk/aWEuY29tL3Q1XzJx/aDB5L3N0eWxlcy9j/b21tdW5pdHlJY29u/X2NpZmVobDR4dDdu/YzEucG5n",
+					"original": "https://styles.redditmedia.com/t5_2qh0y/styles/communityIcon_cifehl4xt7nc1.png",
+					"logo": false
+				},
+				"age": "December 30, 2022",
+				"extra_snippets": [
+					"r/Python: The official Python community for Reddit! Stay up to date with the latest news, packages, and meta information relating to the Python…",
+					"By default, Python allows you to import and use anything, anywhere. Over time, this results in modules that were intended to be separate getting tightly coupled together, and domain boundaries breaking down. We experienced this first-hand at a unicorn startup, where the eng team paused development for over a year in an attempt to split up packages into independent services.",
+					"Hello r/Python! It's time to share what you've been working on! Whether it's a work-in-progress, a completed masterpiece, or just a rough idea, let us know what you're up to!",
+					"Whether it's your job, your hobby, or your passion project, all Python-related work is welcome here."
+				]
+			},
+			{
+				"title": "GitHub - python/cpython: The Python programming language",
+				"url": "https://github.com/python/cpython",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "The <strong>Python</strong> programming language. Contribute to <strong>python</strong>/cpython development by creating an account on GitHub.",
+				"page_age": "2022-10-29T00:00:00",
+				"profile": {
+					"name": "GitHub",
+					"url": "https://github.com/python/cpython",
+					"long_name": "github.com",
+					"img": "https://imgs.search.brave.com/v8685zI4XInM0zxlNI2s7oE_2Sb-EL7lAy81WXbkQD8/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYWQyNWM1NjA5/ZjZmZjNlYzI2MDNk/N2VkNmJhYjE2MzZl/MDY5ZTMxMDUzZmY1/NmU3NWIzNWVmMjk0/NTBjMjJjZi9naXRo/dWIuY29tLw"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "software",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "github.com",
+					"hostname": "github.com",
+					"favicon": "https://imgs.search.brave.com/v8685zI4XInM0zxlNI2s7oE_2Sb-EL7lAy81WXbkQD8/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYWQyNWM1NjA5/ZjZmZjNlYzI2MDNk/N2VkNmJhYjE2MzZl/MDY5ZTMxMDUzZmY1/NmU3NWIzNWVmMjk0/NTBjMjJjZi9naXRo/dWIuY29tLw",
+					"path": "› python  › cpython"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/BJbWFRUqgP-tKIyGK9ByXjuYjHO2mtYigUOEFNz_gXk/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9vcGVu/Z3JhcGguZ2l0aHVi/YXNzZXRzLmNvbS82/MTY5YmJkNTQ0YzAy/NDg0MGU4NDdjYTU1/YTU3ZGZmMDA2ZDAw/YWQ1NDIzOTFmYTQ3/YmJjODg3OWM0NWYw/MTZhL3B5dGhvbi9j/cHl0aG9u",
+					"original": "https://opengraph.githubassets.com/6169bbd544c024840e847ca55a57dff006d00ad542391fa47bbc8879c45f016a/python/cpython",
+					"logo": false
+				},
+				"age": "October 29, 2022",
+				"extra_snippets": [
+					"You can pass many options to the configure script; run ./configure --help to find out more. On macOS case-insensitive file systems and on Cygwin, the executable is called python.exe; elsewhere it's just python.",
+					"Building a complete Python installation requires the use of various additional third-party libraries, depending on your build platform and configure options. Not all standard library modules are buildable or useable on all platforms. Refer to the Install dependencies section of the Developer Guide for current detailed information on dependencies for various Linux distributions and macOS.",
+					"To get an optimized build of Python, configure --enable-optimizations before you run make. This sets the default make targets up to enable Profile Guided Optimization (PGO) and may be used to auto-enable Link Time Optimization (LTO) on some platforms. For more details, see the sections below.",
+					"Copyright © 2001-2024 Python Software Foundation. All rights reserved."
+				]
+			},
+			{
+				"title": "5. Data Structures — Python 3.12.3 documentation",
+				"url": "https://docs.python.org/3/tutorial/datastructures.html",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "This chapter describes some things you’ve learned about already in more detail, and adds some new things as well. More on Lists: The list data type has some more methods. Here are all of the method...",
+				"page_age": "2023-07-04T00:00:00",
+				"profile": {
+					"name": "Python documentation",
+					"url": "https://docs.python.org/3/tutorial/datastructures.html",
+					"long_name": "docs.python.org",
+					"img": "https://imgs.search.brave.com/F5Ym7eSElhGdGUFKLRxDj9Z_tc180ldpeMvQ2Q6ARbA/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvMTUzOTFjOGVi/YTcyOTVmODA3ODIy/YjE2NzFjY2ViMjhl/NzRlY2JhYTc5YjNm/ZjhmODAyZWI2OGUw/ZjU4NDVlNy9kb2Nz/LnB5dGhvbi5vcmcv"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "docs.python.org",
+					"hostname": "docs.python.org",
+					"favicon": "https://imgs.search.brave.com/F5Ym7eSElhGdGUFKLRxDj9Z_tc180ldpeMvQ2Q6ARbA/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvMTUzOTFjOGVi/YTcyOTVmODA3ODIy/YjE2NzFjY2ViMjhl/NzRlY2JhYTc5YjNm/ZjhmODAyZWI2OGUw/ZjU4NDVlNy9kb2Nz/LnB5dGhvbi5vcmcv",
+					"path": "› 3  › tutorial  › datastructures.html"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/Y7GrMRF8WorDIMLuOl97XC8ltYpoOCqNwWF2pQIIKls/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9kb2Nz/LnB5dGhvbi5vcmcv/My9fc3RhdGljL29n/LWltYWdlLnBuZw",
+					"original": "https://docs.python.org/3/_static/og-image.png",
+					"logo": false
+				},
+				"age": "July 4, 2023",
+				"extra_snippets": [
+					"You might have noticed that methods like insert, remove or sort that only modify the list have no return value printed – they return the default None. [1] This is a design principle for all mutable data structures in Python.",
+					"We saw that lists and strings have many common properties, such as indexing and slicing operations. They are two examples of sequence data types (see Sequence Types — list, tuple, range). Since Python is an evolving language, other sequence data types may be added. There is also another standard sequence data type: the tuple.",
+					"Python also includes a data type for sets. A set is an unordered collection with no duplicate elements. Basic uses include membership testing and eliminating duplicate entries. Set objects also support mathematical operations like union, intersection, difference, and symmetric difference.",
+					"Another useful data type built into Python is the dictionary (see Mapping Types — dict). Dictionaries are sometimes found in other languages as “associative memories” or “associative arrays”. Unlike sequences, which are indexed by a range of numbers, dictionaries are indexed by keys, which can be any immutable type; strings and numbers can always be keys."
+				]
+			},
+			{
+				"title": "Something wrong with python packages / AUR Issues, Discussion & PKGBUILD Requests / Arch Linux Forums",
+				"url": "https://bbs.archlinux.org/viewtopic.php?id=295466",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "Big <strong>Python</strong> updates require <strong>Python</strong> packages to be rebuild. For some reason they didn&#x27;t think a bump that made it necessary to rebuild half the official repo was a news post.",
+				"page_age": "2024-05-04T08:30:02",
+				"profile": {
+					"name": "Archlinux",
+					"url": "https://bbs.archlinux.org/viewtopic.php?id=295466",
+					"long_name": "bbs.archlinux.org",
+					"img": "https://imgs.search.brave.com/3au9oqkzSri_aLEec3jo-0bFgLuICkydrWfjFcC8lkI/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvNWNkODM1MWJl/ZmJhMzkzNzYzMDkz/NmEyMWMxNjI5MjNk/NGJmZjFhNTBlZDNl/Mzk5MzJjOGZkYjZl/MjNmY2IzNS9iYnMu/YXJjaGxpbnV4Lm9y/Zy8"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "bbs.archlinux.org",
+					"hostname": "bbs.archlinux.org",
+					"favicon": "https://imgs.search.brave.com/3au9oqkzSri_aLEec3jo-0bFgLuICkydrWfjFcC8lkI/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvNWNkODM1MWJl/ZmJhMzkzNzYzMDkz/NmEyMWMxNjI5MjNk/NGJmZjFhNTBlZDNl/Mzk5MzJjOGZkYjZl/MjNmY2IzNS9iYnMu/YXJjaGxpbnV4Lm9y/Zy8",
+					"path": "› viewtopic.php"
+				},
+				"age": "1 day ago",
+				"extra_snippets": [
+					"Traceback (most recent call last): File \"/usr/lib/python3.12/importlib/metadata/__init__.py\", line 397, in from_name return next(cls.discover(name=name)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ StopIteration During handling of the above exception, another exception occurred: Traceback (most recent call last): File \"/usr/bin/informant\", line 33, in <module> sys.exit(load_entry_point('informant==0.5.0', 'console_scripts', 'informant')()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File \"/usr/bin/informant\", line 22, in importlib_load_entry_point for entry_point in distribution(dis"
+				]
+			},
+			{
+				"title": "Introduction to Python",
+				"url": "https://www.w3schools.com/python/python_intro.asp",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, <strong>Python</strong>, SQL, Java, and many, many more.",
+				"profile": {
+					"name": "W3Schools",
+					"url": "https://www.w3schools.com/python/python_intro.asp",
+					"long_name": "w3schools.com",
+					"img": "https://imgs.search.brave.com/JwO5r7z3HTBkU29vgNH_4rrSWLf2M4-8FMWNvbxrKX8/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYjVlMGVkZDVj/ZGMyZWRmMzAwODRi/ZDAwZGE4NWI3NmU4/MjRhNjEzOGFhZWY3/ZGViMjY1OWY2ZDYw/YTZiOGUyZS93d3cu/dzNzY2hvb2xzLmNv/bS8"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "w3schools.com",
+					"hostname": "www.w3schools.com",
+					"favicon": "https://imgs.search.brave.com/JwO5r7z3HTBkU29vgNH_4rrSWLf2M4-8FMWNvbxrKX8/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYjVlMGVkZDVj/ZGMyZWRmMzAwODRi/ZDAwZGE4NWI3NmU4/MjRhNjEzOGFhZWY3/ZGViMjY1OWY2ZDYw/YTZiOGUyZS93d3cu/dzNzY2hvb2xzLmNv/bS8",
+					"path": "› python  › python_intro.asp"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/EMfp8dodbJehmj0yCJh8317RHuaumsddnHI4bujvFcg/rs:fit:200:200:1/g:ce/aHR0cHM6Ly93d3cu/dzNzY2hvb2xzLmNv/bS9pbWFnZXMvdzNz/Y2hvb2xzX2xvZ29f/NDM2XzIucG5n",
+					"original": "https://www.w3schools.com/images/w3schools_logo_436_2.png",
+					"logo": true
+				},
+				"extra_snippets": [
+					"Well organized and easy to understand Web building tutorials with lots of examples of how to use HTML, CSS, JavaScript, SQL, Python, PHP, Bootstrap, Java, XML and more.",
+					"HTML CSS JAVASCRIPT SQL PYTHON JAVA PHP HOW TO W3.CSS C C++ C# BOOTSTRAP REACT MYSQL JQUERY EXCEL XML DJANGO NUMPY PANDAS NODEJS R TYPESCRIPT ANGULAR GIT POSTGRESQL MONGODB ASP AI GO KOTLIN SASS VUE DSA GEN AI SCIPY AWS CYBERSECURITY DATA SCIENCE",
+					"Python Variables Variable Names Assign Multiple Values Output Variables Global Variables Variable Exercises Python Data Types Python Numbers Python Casting Python Strings",
+					"Python Strings Slicing Strings Modify Strings Concatenate Strings Format Strings Escape Characters String Methods String Exercises Python Booleans Python Operators Python Lists"
+				]
+			},
+			{
+				"title": "bug: AUR package wants to use python but does not find any preset version · Issue #1740 · asdf-vm/asdf",
+				"url": "https://github.com/asdf-vm/asdf/issues/1740",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "Describe the Bug I am not sure why this is happening, I am trying to install tlpui from AUR and it fails, here are some logs to help: ==&gt; Making package: tlpui 2:1.6.5-1 (Mi 10 apr 2024 23:19:15 +0...",
+				"page_age": "2024-05-04T06:45:04",
+				"profile": {
+					"name": "GitHub",
+					"url": "https://github.com/asdf-vm/asdf/issues/1740",
+					"long_name": "github.com",
+					"img": "https://imgs.search.brave.com/v8685zI4XInM0zxlNI2s7oE_2Sb-EL7lAy81WXbkQD8/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYWQyNWM1NjA5/ZjZmZjNlYzI2MDNk/N2VkNmJhYjE2MzZl/MDY5ZTMxMDUzZmY1/NmU3NWIzNWVmMjk0/NTBjMjJjZi9naXRo/dWIuY29tLw"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "software",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "github.com",
+					"hostname": "github.com",
+					"favicon": "https://imgs.search.brave.com/v8685zI4XInM0zxlNI2s7oE_2Sb-EL7lAy81WXbkQD8/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYWQyNWM1NjA5/ZjZmZjNlYzI2MDNk/N2VkNmJhYjE2MzZl/MDY5ZTMxMDUzZmY1/NmU3NWIzNWVmMjk0/NTBjMjJjZi9naXRo/dWIuY29tLw",
+					"path": "› asdf-vm  › asdf  › issues  › 1740"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/KrLW5s_2n4jyP8XLbc3ZPVBaLD963tQgWzG9EWPZlQs/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9vcGVu/Z3JhcGguZ2l0aHVi/YXNzZXRzLmNvbS81/MTE0ZTdkOGIwODM2/YmQ2MTY3NzQ1ZGI4/MmZjMGE3OGUyMjcw/MGFlY2ZjMWZkODBl/MDYzZTNiN2ZjOWNj/NzYyL2FzZGYtdm0v/YXNkZi9pc3N1ZXMv/MTc0MA",
+					"original": "https://opengraph.githubassets.com/5114e7d8b0836bd6167745db82fc0a78e22700aecfc1fd80e063e3b7fc9cc762/asdf-vm/asdf/issues/1740",
+					"logo": false
+				},
+				"age": "1 day ago",
+				"extra_snippets": [
+					"==> Starting build()... No preset version installed for command python Please install a version by running one of the following: asdf install python 3.8 or add one of the following versions in your config file at /home/ferret/.tool-versions python 3.11.0 python 3.12.1 python 3.12.3 ==> ERROR: A failure occurred in build(). Aborting...",
+					"-> error making: tlpui-exit status 4 -> Failed to install the following packages. Manual intervention is required: tlpui - exit status 4 ferret@FX505DT in ~ $ cat /home/ferret/.tool-versions nodejs 21.6.0 python 3.12.3 ferret@FX505DT in ~ $ python -V Python 3.12.3 ferret@FX505DT in ~ $ which python /home/ferret/.asdf/shims/python",
+					"Describe the Bug I am not sure why this is happening, I am trying to install tlpui from AUR and it fails, here are some logs to help: ==> Making package: tlpui 2:1.6.5-1 (Mi 10 apr 2024 23:19:15 +0300) ==> Retrieving sources... -> Found ..."
+				]
+			},
+			{
+				"title": "What are python.exe and python3.exe, and why do they appear to point to App Installer? | Windows 11 Forum",
+				"url": "https://www.elevenforum.com/t/what-are-python-exe-and-python3-exe-and-why-do-they-appear-to-point-to-app-installer.24886/",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "I was looking at App execution aliases (Settings &gt; Apps &gt; Advanced app settings &gt; App execution aliases) on my new computer -- my first Windows 11 computer. Why are <strong>python</strong>.exe and python3.exe listed as App Installer? I assume that App Installer refers to installation of Microsoft Store / UWP...",
+				"page_age": "2024-05-03T17:30:04",
+				"profile": {
+					"name": "Windows 11 Forum",
+					"url": "https://www.elevenforum.com/t/what-are-python-exe-and-python3-exe-and-why-do-they-appear-to-point-to-app-installer.24886/",
+					"long_name": "elevenforum.com",
+					"img": "https://imgs.search.brave.com/XVRAYMEj6Im8i7jV5RxeTwpiRPtY9IWg4wRIuh-WhEw/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvZjk5MDZkMDIw/M2U1OWIwNjM5Y2U1/M2U2NzNiNzVkNTA5/NzA5OTI1ZTFmOTc4/MzU3OTlhYzU5OTVi/ZGNjNTY4MS93d3cu/ZWxldmVuZm9ydW0u/Y29tLw"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "elevenforum.com",
+					"hostname": "www.elevenforum.com",
+					"favicon": "https://imgs.search.brave.com/XVRAYMEj6Im8i7jV5RxeTwpiRPtY9IWg4wRIuh-WhEw/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvZjk5MDZkMDIw/M2U1OWIwNjM5Y2U1/M2U2NzNiNzVkNTA5/NzA5OTI1ZTFmOTc4/MzU3OTlhYzU5OTVi/ZGNjNTY4MS93d3cu/ZWxldmVuZm9ydW0u/Y29tLw",
+					"path": "  › windows support forums  › apps and software"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/DVoFcE6d_-lx3BVGNS-RZK_lZzxQ8VhwZVf3AVqEJFA/rs:fit:200:200:1/g:ce/aHR0cHM6Ly93d3cu/ZWxldmVuZm9ydW0u/Y29tL2RhdGEvYXNz/ZXRzL2xvZ28vbWV0/YTEtMjAxLnBuZw",
+					"original": "https://www.elevenforum.com/data/assets/logo/meta1-201.png",
+					"logo": true
+				},
+				"age": "2 days ago",
+				"extra_snippets": [
+					"Why are python.exe and python3.exe listed as App Installer? I assume that App Installer refers to installation of Microsoft Store / UWP apps, but if that's the case, then why are they called python.exe and python3.exe? Or are python.exe and python3.exe simply serving as aliases / pointers pointing to App Installer, which is itself a Microsoft Store App?",
+					"Or are python.exe and python3.exe simply serving as aliases / pointers pointing to App Installer, which is itself a Microsoft Store App? I wish to soon install Python, along with an integrated development editor (IDE), on my machine, so that I can code in Python.",
+					"I wish to soon install Python, along with an integrated development editor (IDE), on my machine, so that I can code in Python. But is a Python interpreter already on my computer as suggested, if obliquely, by the presence of python.exe and python3.exe? I kind of doubt it."
+				]
+			},
+			{
+				"title": "How to Watermark Your Images Using Python OpenCV in ...",
+				"url": "https://medium.com/@daily_data_prep/how-to-watermark-your-images-using-python-opencv-in-bulk-e472085389a1",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "Medium is an open platform where readers find dynamic thinking, and where expert and undiscovered voices can share their writing on any topic.",
+				"page_age": "2024-05-03T14:05:06",
+				"profile": {
+					"name": "Medium",
+					"url": "https://medium.com/@daily_data_prep/how-to-watermark-your-images-using-python-opencv-in-bulk-e472085389a1",
+					"long_name": "medium.com",
+					"img": "https://imgs.search.brave.com/qvE2kIQCiAsnPv2C6P9xM5J2VVWdm55g-A-2Q_yIJ0g/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTZhYmQ1N2Q4/NDg4ZDcyODIyMDZi/MzFmOWNhNjE3Y2E4/Y2YzMThjNjljNDIx/ZjllZmNhYTcwODhl/YTcwNDEzYy9tZWRp/dW0uY29tLw"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "medium.com",
+					"hostname": "medium.com",
+					"favicon": "https://imgs.search.brave.com/qvE2kIQCiAsnPv2C6P9xM5J2VVWdm55g-A-2Q_yIJ0g/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTZhYmQ1N2Q4/NDg4ZDcyODIyMDZi/MzFmOWNhNjE3Y2E4/Y2YzMThjNjljNDIx/ZjllZmNhYTcwODhl/YTcwNDEzYy9tZWRp/dW0uY29tLw",
+					"path": "› @daily_data_prep  › how-to-watermark-your-images-using-python-opencv-in-bulk-e472085389a1"
+				},
+				"age": "2 days ago"
+			},
+			{
+				"title": "Increment and Decrement Operators in Python?",
+				"url": "https://www.tutorialspoint.com/increment-and-decrement-operators-in-python",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "Increment and Decrement Operators in <strong>Python</strong> - <strong>Python</strong> does not have unary increment/decrement operator (++/--). Instead to increment a value, usea += 1to decrement a value, use −a -= 1Example&gt;&gt;&gt; a = 0 &gt;&gt;&gt; &gt;&gt;&gt; #Increment &gt;&gt;&gt; a +=1 &gt;&gt;&gt; &gt;&gt;&gt; #Decrement &gt;&gt;&gt; a -= 1 &gt;&gt;&gt; &gt;&gt;&gt; #value of a &gt;&gt;&gt; a 0Python ...",
+				"page_age": "2023-08-23T00:00:00",
+				"profile": {
+					"name": "Tutorialspoint",
+					"url": "https://www.tutorialspoint.com/increment-and-decrement-operators-in-python",
+					"long_name": "tutorialspoint.com",
+					"img": "https://imgs.search.brave.com/Wt8BSkivPlFwcU5yBtf7YzuvTuRExyd_502cdABCS5c/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYjcyYjAzYmVl/ODU4MzZiMjJiYTFh/MjJhZDNmNWE4YzA5/MDgyYTZhMDg3NTYw/M2NiY2NiZTUxN2I5/MjU1MWFmMS93d3cu/dHV0b3JpYWxzcG9p/bnQuY29tLw"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "tutorialspoint.com",
+					"hostname": "www.tutorialspoint.com",
+					"favicon": "https://imgs.search.brave.com/Wt8BSkivPlFwcU5yBtf7YzuvTuRExyd_502cdABCS5c/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYjcyYjAzYmVl/ODU4MzZiMjJiYTFh/MjJhZDNmNWE4YzA5/MDgyYTZhMDg3NTYw/M2NiY2NiZTUxN2I5/MjU1MWFmMS93d3cu/dHV0b3JpYWxzcG9p/bnQuY29tLw",
+					"path": "› increment-and-decrement-operators-in-python"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/ddG5vyZGLVudvecEbQJPeG8tGuaZ7g3Xz6Gyjdl5WA8/rs:fit:200:200:1/g:ce/aHR0cHM6Ly93d3cu/dHV0b3JpYWxzcG9p/bnQuY29tL2ltYWdl/cy90cF9sb2dvXzQz/Ni5wbmc",
+					"original": "https://www.tutorialspoint.com/images/tp_logo_436.png",
+					"logo": true
+				},
+				"age": "August 23, 2023",
+				"extra_snippets": [
+					"Increment and Decrement Operators in Python - Python does not have unary increment/decrement operator (++/--). Instead to increment a value, usea += 1to decrement a value, use −a -= 1Example>>> a = 0 >>> >>> #Increment >>> a +=1 >>> >>> #Decrement >>> a -= 1 >>> >>> #value of a >>> a 0Python does not provide multiple ways to do the same thing",
+					"So what above statement means in python is: create an object of type int having value 1 and give the name a to it. The object is an instance of int having value 1 and the name a refers to it. The assigned name a and the object to which it refers are distinct.",
+					"Python does not provide multiple ways to do the same thing .",
+					"However, be careful if you are coming from a language like C, Python doesn’t have \"variables\" in the sense that C does, instead python uses names and objects and in python integers (int’s) are immutable."
+				]
+			},
+			{
+				"title": "Gumroad – How not to suck at Python / SideFX Houdini | CG Persia",
+				"url": "https://cgpersia.com/2024/05/gumroad-how-not-to-suck-at-python-sidefx-houdini-195370.html",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "Info: This course is made for artists or TD (technical director) willing to learn <strong>Python</strong> to improve their workflows inside SideFX Houdini, get faster in production and develop all the tools you always wished you had.",
+				"page_age": "2024-05-03T08:35:03",
+				"profile": {
+					"name": "Cgpersia",
+					"url": "https://cgpersia.com/2024/05/gumroad-how-not-to-suck-at-python-sidefx-houdini-195370.html",
+					"long_name": "cgpersia.com",
+					"img": "https://imgs.search.brave.com/VjyaopAm-M9sWvM7n-KnGZ3T5swIOwwE80iF5QVqQPg/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYmE0MzQ4NmI2/NjFhMTA1ZDBiN2Iw/ZWNiNDUxNjUwYjdh/MGE5ZjQ0ZjIxNzll/NmVkZDE2YzYyMDBh/NDNiMDgwMy9jZ3Bl/cnNpYS5jb20v"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "cgpersia.com",
+					"hostname": "cgpersia.com",
+					"favicon": "https://imgs.search.brave.com/VjyaopAm-M9sWvM7n-KnGZ3T5swIOwwE80iF5QVqQPg/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvYmE0MzQ4NmI2/NjFhMTA1ZDBiN2Iw/ZWNiNDUxNjUwYjdh/MGE5ZjQ0ZjIxNzll/NmVkZDE2YzYyMDBh/NDNiMDgwMy9jZ3Bl/cnNpYS5jb20v",
+					"path": "› 2024  › 05  › gumroad-how-not-to-suck-at-python-sidefx-houdini-195370.html"
+				},
+				"age": "2 days ago",
+				"extra_snippets": [
+					"Posted in: 2D, CG Releases, Downloads, Learning, Tutorials, Videos. Tagged: Gumroad, Python, Sidefx. Leave a Comment",
+					"01 – Python – Fundamentals Get the Fundamentals of python before starting the fun stuff ! 02 – Python Construction Part02 digging further into python concepts 03 – Houdini – Python Basics Applying some basic python in Houdini and starting to make tools !",
+					"02 – Python Construction Part02 digging further into python concepts 03 – Houdini – Python Basics Applying some basic python in Houdini and starting to make tools ! 04 – Houdini – Python Intermediate Applying some more advanced python in Houdini to make tools ! 05 – Houdini – Python Expert Using QtDesigner in combinaison with Houdini Python/Pyside to create advanced tools."
+				]
+			},
+			{
+				"title": "How to install Python: The complete Python programmer’s guide",
+				"url": "https://www.pluralsight.com/resources/blog/software-development/python-installation-guide",
+				"is_source_local": false,
+				"is_source_both": false,
+				"description": "An easy guide on how set up your operating system so you can program in <strong>Python</strong>, and how to update or uninstall it. For Linux, Windows, and macOS.",
+				"page_age": "2024-05-02T07:30:02",
+				"profile": {
+					"name": "Pluralsight",
+					"url": "https://www.pluralsight.com/resources/blog/software-development/python-installation-guide",
+					"long_name": "pluralsight.com",
+					"img": "https://imgs.search.brave.com/zvwQNSVu9-jR2CRlNcsTzxjaXKPlXNuh-Jo9-0yA1OE/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvMTNkNWQyNjk3/M2Q0NzYyMmUyNDc3/ZjYwMWFlZDI5YTI4/ODhmYzc2MDkzMjAy/MjNkMWY1MDE3NTQw/MzI5NWVkZS93d3cu/cGx1cmFsc2lnaHQu/Y29tLw"
+				},
+				"language": "en",
+				"family_friendly": true,
+				"type": "search_result",
+				"subtype": "generic",
+				"meta_url": {
+					"scheme": "https",
+					"netloc": "pluralsight.com",
+					"hostname": "www.pluralsight.com",
+					"favicon": "https://imgs.search.brave.com/zvwQNSVu9-jR2CRlNcsTzxjaXKPlXNuh-Jo9-0yA1OE/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvMTNkNWQyNjk3/M2Q0NzYyMmUyNDc3/ZjYwMWFlZDI5YTI4/ODhmYzc2MDkzMjAy/MjNkMWY1MDE3NTQw/MzI5NWVkZS93d3cu/cGx1cmFsc2lnaHQu/Y29tLw",
+					"path": "  › blog  › blog"
+				},
+				"thumbnail": {
+					"src": "https://imgs.search.brave.com/xrv5PHH2Bzmq2rcIYzk__8h5RqCj6kS3I6SGCNw5dZM/rs:fit:200:200:1/g:ce/aHR0cHM6Ly93d3cu/cGx1cmFsc2lnaHQu/Y29tL2NvbnRlbnQv/ZGFtL3BzL2ltYWdl/cy9yZXNvdXJjZS1j/ZW50ZXIvYmxvZy9o/ZWFkZXItaGVyby1p/bWFnZXMvUHl0aG9u/LndlYnA",
+					"original": "https://www.pluralsight.com/content/dam/ps/images/resource-center/blog/header-hero-images/Python.webp",
+					"logo": false
+				},
+				"age": "3 days ago",
+				"extra_snippets": [
+					"Whether it’s your first time programming or you’re a seasoned programmer, you’ll have to install or update Python every now and then --- or if necessary, uninstall it. In this article, you'll learn how to do just that.",
+					"Some systems come with Python, so to start off, we’ll first check to see if it’s installed on your system before we proceed. To do that, we’ll need to open a terminal. Since you might be new to programming, let’s go over how to open a terminal for Linux, Windows, and macOS.",
+					"Before we dive into setting up your system so you can program in Python, let’s talk terminal basics and benefits.",
+					"However, let’s focus on why we need it for working with Python. We use a terminal, or command line, to:"
+				]
+			}
+		],
+		"family_friendly": true
+	}
+}
diff --git a/backend/open_webui/apps/retrieval/web/testdata/google_pse.json b/backend/open_webui/apps/retrieval/web/testdata/google_pse.json
new file mode 100644
index 0000000000000000000000000000000000000000..15da9729cde30eee86d0302f662c5ca37d71f65b
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/testdata/google_pse.json
@@ -0,0 +1,442 @@
+{
+	"kind": "customsearch#search",
+	"url": {
+		"type": "application/json",
+		"template": "https://www.googleapis.com/customsearch/v1?q={searchTerms}&num={count?}&start={startIndex?}&lr={language?}&safe={safe?}&cx={cx?}&sort={sort?}&filter={filter?}&gl={gl?}&cr={cr?}&googlehost={googleHost?}&c2coff={disableCnTwTranslation?}&hq={hq?}&hl={hl?}&siteSearch={siteSearch?}&siteSearchFilter={siteSearchFilter?}&exactTerms={exactTerms?}&excludeTerms={excludeTerms?}&linkSite={linkSite?}&orTerms={orTerms?}&dateRestrict={dateRestrict?}&lowRange={lowRange?}&highRange={highRange?}&searchType={searchType}&fileType={fileType?}&rights={rights?}&imgSize={imgSize?}&imgType={imgType?}&imgColorType={imgColorType?}&imgDominantColor={imgDominantColor?}&alt=json"
+	},
+	"queries": {
+		"request": [
+			{
+				"title": "Google Custom Search - lectures",
+				"totalResults": "2450000000",
+				"searchTerms": "lectures",
+				"count": 10,
+				"startIndex": 1,
+				"inputEncoding": "utf8",
+				"outputEncoding": "utf8",
+				"safe": "off",
+				"cx": "0473ef98502d44e18"
+			}
+		],
+		"nextPage": [
+			{
+				"title": "Google Custom Search - lectures",
+				"totalResults": "2450000000",
+				"searchTerms": "lectures",
+				"count": 10,
+				"startIndex": 11,
+				"inputEncoding": "utf8",
+				"outputEncoding": "utf8",
+				"safe": "off",
+				"cx": "0473ef98502d44e18"
+			}
+		]
+	},
+	"context": {
+		"title": "LLM Search"
+	},
+	"searchInformation": {
+		"searchTime": 0.445959,
+		"formattedSearchTime": "0.45",
+		"totalResults": "2450000000",
+		"formattedTotalResults": "2,450,000,000"
+	},
+	"items": [
+		{
+			"kind": "customsearch#result",
+			"title": "The Feynman Lectures on Physics",
+			"htmlTitle": "The Feynman \u003cb\u003eLectures\u003c/b\u003e on Physics",
+			"link": "https://www.feynmanlectures.caltech.edu/",
+			"displayLink": "www.feynmanlectures.caltech.edu",
+			"snippet": "This edition has been designed for ease of reading on devices of any size or shape; text, figures and equations can all be zoomed without degradation.",
+			"htmlSnippet": "This edition has been designed for ease of reading on devices of any size or shape; text, figures and equations can all be zoomed without degradation.",
+			"cacheId": "CyXMWYWs9UEJ",
+			"formattedUrl": "https://www.feynmanlectures.caltech.edu/",
+			"htmlFormattedUrl": "https://www.feynman\u003cb\u003electures\u003c/b\u003e.caltech.edu/",
+			"pagemap": {
+				"metatags": [
+					{
+						"viewport": "width=device-width, initial-scale=1.0"
+					}
+				]
+			}
+		},
+		{
+			"kind": "customsearch#result",
+			"title": "Video Lectures",
+			"htmlTitle": "Video \u003cb\u003eLectures\u003c/b\u003e",
+			"link": "https://www.reddit.com/r/lectures/",
+			"displayLink": "www.reddit.com",
+			"snippet": "r/lectures: This subreddit is all about video lectures, talks and interesting public speeches. The topics include mathematics, physics, computer…",
+			"htmlSnippet": "r/\u003cb\u003electures\u003c/b\u003e: This subreddit is all about video \u003cb\u003electures\u003c/b\u003e, talks and interesting public speeches. The topics include mathematics, physics, computer…",
+			"formattedUrl": "https://www.reddit.com/r/lectures/",
+			"htmlFormattedUrl": "https://www.reddit.com/r/\u003cb\u003electures\u003c/b\u003e/",
+			"pagemap": {
+				"cse_thumbnail": [
+					{
+						"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTZtOjhfkgUKQbL3DZxe5F6OVsgeDNffleObjJ7n9RllKQTSsimax7VIaY&s",
+						"width": "192",
+						"height": "192"
+					}
+				],
+				"metatags": [
+					{
+						"og:image": "https://www.redditstatic.com/shreddit/assets/favicon/192x192.png",
+						"theme-color": "#000000",
+						"og:image:width": "256",
+						"og:type": "website",
+						"twitter:card": "summary",
+						"twitter:title": "r/lectures",
+						"og:site_name": "Reddit",
+						"og:title": "r/lectures",
+						"og:image:height": "256",
+						"bingbot": "noarchive",
+						"msapplication-navbutton-color": "#000000",
+						"og:description": "This subreddit is all about video lectures, talks and interesting public speeches.\n\nThe topics include mathematics, physics, computer science, programming, engineering, biology, medicine, economics, politics, social sciences, and any other subjects!",
+						"twitter:image": "https://www.redditstatic.com/shreddit/assets/favicon/192x192.png",
+						"apple-mobile-web-app-status-bar-style": "black",
+						"twitter:site": "@reddit",
+						"viewport": "width=device-width, initial-scale=1, viewport-fit=cover",
+						"apple-mobile-web-app-capable": "yes",
+						"og:ttl": "600",
+						"og:url": "https://www.reddit.com/r/lectures/"
+					}
+				],
+				"cse_image": [
+					{
+						"src": "https://www.redditstatic.com/shreddit/assets/favicon/192x192.png"
+					}
+				]
+			}
+		},
+		{
+			"kind": "customsearch#result",
+			"title": "Lectures & Discussions | Flint Institute of Arts",
+			"htmlTitle": "\u003cb\u003eLectures\u003c/b\u003e &amp; Discussions | Flint Institute of Arts",
+			"link": "https://flintarts.org/events/lectures",
+			"displayLink": "flintarts.org",
+			"snippet": "It will trace the intricate relationship between jewelry, attire, and the expression of personal identity, social hierarchy, and spiritual belief systems that ...",
+			"htmlSnippet": "It will trace the intricate relationship between jewelry, attire, and the expression of personal identity, social hierarchy, and spiritual belief systems that&nbsp;...",
+			"cacheId": "jvpb9DxrfxoJ",
+			"formattedUrl": "https://flintarts.org/events/lectures",
+			"htmlFormattedUrl": "https://flintarts.org/events/\u003cb\u003electures\u003c/b\u003e",
+			"pagemap": {
+				"cse_thumbnail": [
+					{
+						"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS23tMtAeNhJbOWdGxShYsmnyzFdzOC9Hb7lRykA9Pw72z1IlKTkjTdZw&s",
+						"width": "447",
+						"height": "113"
+					}
+				],
+				"metatags": [
+					{
+						"og:image": "https://flintarts.org/uploads/images/page-headers/_headerImage/nightshot.jpg",
+						"og:type": "website",
+						"viewport": "width=device-width, initial-scale=1",
+						"og:title": "Lectures & Discussions | Flint Institute of Arts",
+						"og:description": "The Flint Institute of Arts is the second largest art museum in Michigan and one of the largest museum art schools in the nation."
+					}
+				],
+				"cse_image": [
+					{
+						"src": "https://flintarts.org/uploads/images/page-headers/_headerImage/nightshot.jpg"
+					}
+				]
+			}
+		},
+		{
+			"kind": "customsearch#result",
+			"title": "Mandel Lectures | Mandel Center for the Humanities ... - Waltham",
+			"htmlTitle": "Mandel \u003cb\u003eLectures\u003c/b\u003e | Mandel Center for the Humanities ... - Waltham",
+			"link": "https://www.brandeis.edu/mandel-center-humanities/mandel-lectures.html",
+			"displayLink": "www.brandeis.edu",
+			"snippet": "Past Lectures · Lecture 1: \"Invisible Music: The Sonic Idea of Black Revolution From Captivity to Reconstruction\" · Lecture 2: \"Solidarity in Sound: Grassroots ...",
+			"htmlSnippet": "Past \u003cb\u003eLectures\u003c/b\u003e &middot; \u003cb\u003eLecture\u003c/b\u003e 1: &quot;Invisible Music: The Sonic Idea of Black Revolution From Captivity to Reconstruction&quot; &middot; \u003cb\u003eLecture\u003c/b\u003e 2: &quot;Solidarity in Sound: Grassroots&nbsp;...",
+			"cacheId": "cQLOZr0kgEEJ",
+			"formattedUrl": "https://www.brandeis.edu/mandel-center-humanities/mandel-lectures.html",
+			"htmlFormattedUrl": "https://www.brandeis.edu/mandel-center-humanities/mandel-\u003cb\u003electures\u003c/b\u003e.html",
+			"pagemap": {
+				"cse_thumbnail": [
+					{
+						"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQWlU7bcJ5pIHk7RBCk2QKE-48ejF7hyPV0pr-20_cBt2BGdfKtiYXBuyw&s",
+						"width": "275",
+						"height": "183"
+					}
+				],
+				"metatags": [
+					{
+						"og:image": "https://www.brandeis.edu/mandel-center-humanities/events/events-images/mlhzumba",
+						"twitter:card": "summary_large_image",
+						"viewport": "width=device-width,initial-scale=1,minimum-scale=1",
+						"og:title": "Mandel Lectures in the Humanities",
+						"og:url": "https://www.brandeis.edu/mandel-center-humanities/mandel-lectures.html",
+						"og:description": "Annual Lecture Series",
+						"twitter:image": "https://www.brandeis.edu/mandel-center-humanities/events/events-images/mlhzumba"
+					}
+				],
+				"cse_image": [
+					{
+						"src": "https://www.brandeis.edu/mandel-center-humanities/events/events-images/mlhzumba"
+					}
+				]
+			}
+		},
+		{
+			"kind": "customsearch#result",
+			"title": "Brian Douglas - YouTube",
+			"htmlTitle": "Brian Douglas - YouTube",
+			"link": "https://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg",
+			"displayLink": "www.youtube.com",
+			"snippet": "Welcome to Control Systems Lectures! This collection of videos is intended to supplement a first year controls class, not replace it.",
+			"htmlSnippet": "Welcome to Control Systems \u003cb\u003eLectures\u003c/b\u003e! This collection of videos is intended to supplement a first year controls class, not replace it.",
+			"cacheId": "NEROyBHolL0J",
+			"formattedUrl": "https://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg",
+			"htmlFormattedUrl": "https://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg",
+			"pagemap": {
+				"hcard": [
+					{
+						"fn": "Brian Douglas",
+						"url": "https://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg"
+					}
+				],
+				"cse_thumbnail": [
+					{
+						"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR7G0CeCBz_wVTZgjnhEr2QbiKP7f3uYzKitZYn74Mi32cDmVxvsegJoLI&s",
+						"width": "225",
+						"height": "225"
+					}
+				],
+				"imageobject": [
+					{
+						"width": "900",
+						"url": "https://yt3.googleusercontent.com/ytc/AIdro_nLo68wetImbwGUYP3stve_iKmAEccjhqB-q4o79xdInN4=s900-c-k-c0x00ffffff-no-rj",
+						"height": "900"
+					}
+				],
+				"person": [
+					{
+						"name": "Brian Douglas",
+						"url": "https://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg"
+					}
+				],
+				"metatags": [
+					{
+						"apple-itunes-app": "app-id=544007664, app-argument=https://m.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg?referring_app=com.apple.mobilesafari-smartbanner, affiliate-data=ct=smart_app_banner_polymer&pt=9008",
+						"og:image": "https://yt3.googleusercontent.com/ytc/AIdro_nLo68wetImbwGUYP3stve_iKmAEccjhqB-q4o79xdInN4=s900-c-k-c0x00ffffff-no-rj",
+						"twitter:app:url:iphone": "vnd.youtube://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg",
+						"twitter:app:id:googleplay": "com.google.android.youtube",
+						"theme-color": "rgb(255, 255, 255)",
+						"og:image:width": "900",
+						"twitter:card": "summary",
+						"og:site_name": "YouTube",
+						"twitter:url": "https://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg",
+						"twitter:app:url:ipad": "vnd.youtube://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg",
+						"al:android:package": "com.google.android.youtube",
+						"twitter:app:name:googleplay": "YouTube",
+						"al:ios:url": "vnd.youtube://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg",
+						"twitter:app:id:iphone": "544007664",
+						"og:description": "Welcome to Control Systems Lectures!  This collection of videos is intended to supplement a first year controls class, not replace it.  My goal is to take specific concepts in controls and expand on them in order to provide an intuitive understanding which will ultimately make you a better controls engineer.  \n\nI'm glad you made it to my channel and I hope you find it useful.\n\nShoot me a message at controlsystemlectures@gmail.com, leave a comment or question and I'll get back to you if I can. Don't forget to subscribe!\n \nTwitter: @BrianBDouglas for engineering tweets and announcement of new videos.\nWebpage: http://engineeringmedia.com\n\nHere is the hardware/software I use: http://www.youtube.com/watch?v=m-M5_mIyHe4\n\nHere's a list of my favorite references: http://bit.ly/2skvmWd\n\n--Brian",
+						"al:ios:app_store_id": "544007664",
+						"twitter:image": "https://yt3.googleusercontent.com/ytc/AIdro_nLo68wetImbwGUYP3stve_iKmAEccjhqB-q4o79xdInN4=s900-c-k-c0x00ffffff-no-rj",
+						"twitter:site": "@youtube",
+						"og:type": "profile",
+						"twitter:title": "Brian Douglas",
+						"al:ios:app_name": "YouTube",
+						"og:title": "Brian Douglas",
+						"og:image:height": "900",
+						"twitter:app:id:ipad": "544007664",
+						"al:web:url": "https://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg?feature=applinks",
+						"al:android:url": "https://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg?feature=applinks",
+						"fb:app_id": "87741124305",
+						"twitter:app:url:googleplay": "https://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg",
+						"twitter:app:name:ipad": "YouTube",
+						"viewport": "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,",
+						"twitter:description": "Welcome to Control Systems Lectures!  This collection of videos is intended to supplement a first year controls class, not replace it.  My goal is to take specific concepts in controls and expand on them in order to provide an intuitive understanding which will ultimately make you a better controls engineer.  \n\nI'm glad you made it to my channel and I hope you find it useful.\n\nShoot me a message at controlsystemlectures@gmail.com, leave a comment or question and I'll get back to you if I can. Don't forget to subscribe!\n \nTwitter: @BrianBDouglas for engineering tweets and announcement of new videos.\nWebpage: http://engineeringmedia.com\n\nHere is the hardware/software I use: http://www.youtube.com/watch?v=m-M5_mIyHe4\n\nHere's a list of my favorite references: http://bit.ly/2skvmWd\n\n--Brian",
+						"og:url": "https://www.youtube.com/channel/UCq0imsn84ShAe9PBOFnoIrg",
+						"al:android:app_name": "YouTube",
+						"twitter:app:name:iphone": "YouTube"
+					}
+				],
+				"cse_image": [
+					{
+						"src": "https://yt3.googleusercontent.com/ytc/AIdro_nLo68wetImbwGUYP3stve_iKmAEccjhqB-q4o79xdInN4=s900-c-k-c0x00ffffff-no-rj"
+					}
+				]
+			}
+		},
+		{
+			"kind": "customsearch#result",
+			"title": "Lecture - Wikipedia",
+			"htmlTitle": "\u003cb\u003eLecture\u003c/b\u003e - Wikipedia",
+			"link": "https://en.wikipedia.org/wiki/Lecture",
+			"displayLink": "en.wikipedia.org",
+			"snippet": "Lecture ... For the academic rank, see Lecturer. A lecture (from Latin: lēctūra 'reading') is an oral presentation intended to present information or teach people ...",
+			"htmlSnippet": "\u003cb\u003eLecture\u003c/b\u003e ... For the academic rank, see \u003cb\u003eLecturer\u003c/b\u003e. A \u003cb\u003electure\u003c/b\u003e (from Latin: lēctūra &#39;reading&#39;) is an oral presentation intended to present information or teach people&nbsp;...",
+			"cacheId": "d9Pjta02fmgJ",
+			"formattedUrl": "https://en.wikipedia.org/wiki/Lecture",
+			"htmlFormattedUrl": "https://en.wikipedia.org/wiki/Lecture",
+			"pagemap": {
+				"metatags": [
+					{
+						"referrer": "origin",
+						"og:image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/ADFA_Lecture_Theatres.jpg/1200px-ADFA_Lecture_Theatres.jpg",
+						"theme-color": "#eaecf0",
+						"og:image:width": "1200",
+						"og:type": "website",
+						"viewport": "width=device-width, initial-scale=1.0, user-scalable=yes, minimum-scale=0.25, maximum-scale=5.0",
+						"og:title": "Lecture - Wikipedia",
+						"og:image:height": "799",
+						"format-detection": "telephone=no"
+					}
+				]
+			}
+		},
+		{
+			"kind": "customsearch#result",
+			"title": "Mount Wilson Observatory | Lectures",
+			"htmlTitle": "Mount Wilson Observatory | \u003cb\u003eLectures\u003c/b\u003e",
+			"link": "https://www.mtwilson.edu/lectures/",
+			"displayLink": "www.mtwilson.edu",
+			"snippet": "Talks & Telescopes: August 24, 2024 – Panel: The Triumph of Hubble ... Compelling talks followed by picnicking and convivial stargazing through both the big ...",
+			"htmlSnippet": "Talks &amp; Telescopes: August 24, 2024 – Panel: The Triumph of Hubble ... Compelling talks followed by picnicking and convivial stargazing through both the big&nbsp;...",
+			"cacheId": "wdXI0azqx5UJ",
+			"formattedUrl": "https://www.mtwilson.edu/lectures/",
+			"htmlFormattedUrl": "https://www.mtwilson.edu/\u003cb\u003electures\u003c/b\u003e/",
+			"pagemap": {
+				"metatags": [
+					{
+						"viewport": "width=device-width,initial-scale=1,user-scalable=no"
+					}
+				],
+				"webpage": [
+					{
+						"image": "http://www.mtwilson.edu/wp-content/uploads/2016/09/Logo.jpg",
+						"url": "https://www.facebook.com/WilsonObs"
+					}
+				]
+			}
+		},
+		{
+			"kind": "customsearch#result",
+			"title": "Lectures | NBER",
+			"htmlTitle": "\u003cb\u003eLectures\u003c/b\u003e | NBER",
+			"link": "https://www.nber.org/research/lectures",
+			"displayLink": "www.nber.org",
+			"snippet": "Results 1 - 50 of 354 ... Among featured events at the NBER Summer Institute are the Martin Feldstein Lecture, which examines a current issue involving economic ...",
+			"htmlSnippet": "Results 1 - 50 of 354 \u003cb\u003e...\u003c/b\u003e Among featured events at the NBER Summer Institute are the Martin Feldstein \u003cb\u003eLecture\u003c/b\u003e, which examines a current issue involving economic&nbsp;...",
+			"cacheId": "CvvP3U3nb44J",
+			"formattedUrl": "https://www.nber.org/research/lectures",
+			"htmlFormattedUrl": "https://www.nber.org/research/\u003cb\u003electures\u003c/b\u003e",
+			"pagemap": {
+				"cse_thumbnail": [
+					{
+						"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTmeViEZyV1YmFEFLhcA6WdgAG3v3RV6tB93ncyxSJ5JPst_p2aWrL7D1k&s",
+						"width": "310",
+						"height": "163"
+					}
+				],
+				"metatags": [
+					{
+						"og:image": "https://www.nber.org/sites/default/files/2022-06/NBER-FB-Share-Tile-1200.jpg",
+						"og:site_name": "NBER",
+						"handheldfriendly": "true",
+						"viewport": "width=device-width, initial-scale=1.0",
+						"og:title": "Lectures",
+						"mobileoptimized": "width",
+						"og:url": "https://www.nber.org/research/lectures"
+					}
+				],
+				"cse_image": [
+					{
+						"src": "https://www.nber.org/sites/default/files/2022-06/NBER-FB-Share-Tile-1200.jpg"
+					}
+				]
+			}
+		},
+		{
+			"kind": "customsearch#result",
+			"title": "STUDENTS CANNOT ACCESS RECORDED LECTURES ... - Solved",
+			"htmlTitle": "STUDENTS CANNOT ACCESS RECORDED LECTURES ... - Solved",
+			"link": "https://community.canvaslms.com/t5/Canvas-Question-Forum/STUDENTS-CANNOT-ACCESS-RECORDED-LECTURES/td-p/190358",
+			"displayLink": "community.canvaslms.com",
+			"snippet": "Mar 19, 2020 ... I believe the issue is that students were not invited. Are you trying to capture your screen? If not, there is an option to just record your web ...",
+			"htmlSnippet": "Mar 19, 2020 \u003cb\u003e...\u003c/b\u003e I believe the issue is that students were not invited. Are you trying to capture your screen? If not, there is an option to just record your web&nbsp;...",
+			"cacheId": "wqrynQXX61sJ",
+			"formattedUrl": "https://community.canvaslms.com/t5/Canvas...LECTURES/td-p/190358",
+			"htmlFormattedUrl": "https://community.canvaslms.com/t5/Canvas...\u003cb\u003eLECTURES\u003c/b\u003e/td-p/190358",
+			"pagemap": {
+				"cse_thumbnail": [
+					{
+						"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRUqXau3N8LfKgSD7OJOvV7xzGarLKRU-ckWXy1ZQ1p4CLPsedvLKmLMhk&s",
+						"width": "310",
+						"height": "163"
+					}
+				],
+				"metatags": [
+					{
+						"og:image": "https://community.canvaslms.com/html/@6A1FDD4D5FF35E4BBB4083A1022FA0DB/assets/CommunityPreview23.png",
+						"og:type": "article",
+						"article:section": "Canvas Question Forum",
+						"article:published_time": "2020-03-19T15:50:03.409Z",
+						"og:site_name": "Instructure Community",
+						"article:modified_time": "2020-03-19T13:55:53-07:00",
+						"viewport": "width=device-width, initial-scale=1.0, user-scalable=yes",
+						"og:title": "STUDENTS CANNOT ACCESS RECORDED LECTURES",
+						"og:url": "https://community.canvaslms.com/t5/Canvas-Question-Forum/STUDENTS-CANNOT-ACCESS-RECORDED-LECTURES/m-p/190358#M93667",
+						"og:description": "I can access and see my recorded lectures but my students can't. They have an error message when they try to open the recorded presentation or notes.",
+						"article:author": "https://community.canvaslms.com/t5/user/viewprofilepage/user-id/794287",
+						"twitter:image": "https://community.canvaslms.com/html/@6A1FDD4D5FF35E4BBB4083A1022FA0DB/assets/CommunityPreview23.png"
+					}
+				],
+				"cse_image": [
+					{
+						"src": "https://community.canvaslms.com/html/@6A1FDD4D5FF35E4BBB4083A1022FA0DB/assets/CommunityPreview23.png"
+					}
+				]
+			}
+		},
+		{
+			"kind": "customsearch#result",
+			"title": "Public Lecture Series - Sam Fox School of Design & Visual Arts",
+			"htmlTitle": "Public \u003cb\u003eLecture\u003c/b\u003e Series - Sam Fox School of Design &amp; Visual Arts",
+			"link": "https://samfoxschool.wustl.edu/calendar/series/2-public-lecture-series",
+			"displayLink": "samfoxschool.wustl.edu",
+			"snippet": "The Sam Fox School's Spring 2024 Public Lecture Series highlights design and art as catalysts for change. Renowned speakers will delve into themes like ...",
+			"htmlSnippet": "The Sam Fox School&#39;s Spring 2024 Public \u003cb\u003eLecture\u003c/b\u003e Series highlights design and art as catalysts for change. Renowned speakers will delve into themes like&nbsp;...",
+			"cacheId": "B-cgQG0j6tUJ",
+			"formattedUrl": "https://samfoxschool.wustl.edu/calendar/series/2-public-lecture-series",
+			"htmlFormattedUrl": "https://samfoxschool.wustl.edu/calendar/series/2-public-lecture-series",
+			"pagemap": {
+				"cse_thumbnail": [
+					{
+						"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQSmHaGianm-64m-qauYjkPK_Q0JKWe-7yom4m1ogFYTmpWArA7k6dmk0sR&s",
+						"width": "307",
+						"height": "164"
+					}
+				],
+				"website": [
+					{
+						"name": "Public Lecture Series - Sam Fox School of Design & Visual Arts — Washington University in St. Louis"
+					}
+				],
+				"metatags": [
+					{
+						"og:image": "https://dvsp0hlm0xrn3.cloudfront.net/assets/default_og_image-44e73dee4b9d1e2c6a6295901371270c8ec5899eaed48ee8167a9b12f1b0f8b3.jpg",
+						"og:type": "website",
+						"og:site_name": "Sam Fox School of Design & Visual Arts — Washington University in St. Louis",
+						"viewport": "width=device-width, initial-scale=1.0",
+						"og:title": "Public Lecture Series - Sam Fox School of Design & Visual Arts — Washington University in St. Louis",
+						"csrf-token": "jBQsfZGY3RH8NVs0-KVDBYB-2N2kib4UYZHYdrShfTdLkvzfSvGeOaMrRKTRdYBPRKzdcGIuP7zwm9etqX_uvg",
+						"csrf-param": "authenticity_token",
+						"og:description": "The Sam Fox School's Spring 2024 Public Lecture Series highlights design and art as catalysts for change. Renowned speakers will delve into themes like social equity, resilient cities, and the impact of emerging technologies on contemporary life. Speakers include artists, architects, designers, and critics of the highest caliber, widely recognized for their research-based practices and multidisciplinary approaches to their fields."
+					}
+				],
+				"cse_image": [
+					{
+						"src": "https://dvsp0hlm0xrn3.cloudfront.net/assets/default_og_image-44e73dee4b9d1e2c6a6295901371270c8ec5899eaed48ee8167a9b12f1b0f8b3.jpg"
+					}
+				]
+			}
+		}
+	]
+}
diff --git a/backend/open_webui/apps/retrieval/web/testdata/searchapi.json b/backend/open_webui/apps/retrieval/web/testdata/searchapi.json
new file mode 100644
index 0000000000000000000000000000000000000000..fa3d1c3d74097aaef3d975ff6af845b6c1370b17
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/testdata/searchapi.json
@@ -0,0 +1,357 @@
+{
+	"search_metadata": {
+		"id": "search_VW19X7MebbAtdMwoQe68NbDz",
+		"status": "Success",
+		"created_at": "2024-08-27T13:43:20Z",
+		"request_time_taken": 0.6,
+		"parsing_time_taken": 0.72,
+		"total_time_taken": 1.32,
+		"request_url": "https://www.google.com/search?q=chatgpt&oq=chatgpt&gl=us&hl=en&ie=UTF-8",
+		"html_url": "https://www.searchapi.io/api/v1/searches/search_VW19X7MebbAtdMwoQe68NbDz.html",
+		"json_url": "https://www.searchapi.io/api/v1/searches/search_VW19X7MebbAtdMwoQe68NbDz"
+	},
+	"search_parameters": {
+		"engine": "google",
+		"q": "chatgpt",
+		"device": "desktop",
+		"google_domain": "google.com",
+		"hl": "en",
+		"gl": "us"
+	},
+	"search_information": {
+		"query_displayed": "chatgpt",
+		"total_results": 1010000000,
+		"time_taken_displayed": 0.37,
+		"detected_location": "United States"
+	},
+	"knowledge_graph": {
+		"kgmid": "/g/11khcfz0y2",
+		"knowledge_graph_type": "Kp3 verticals",
+		"title": "ChatGPT",
+		"type": "Software",
+		"description": "ChatGPT is a chatbot and virtual assistant developed by OpenAI and launched on November 30, 2022. Based on large language models, it enables users to refine and steer a conversation towards a desired length, format, style, level of detail, and language.",
+		"source": {
+			"name": "Wikipedia",
+			"link": "https://en.wikipedia.org/wiki/ChatGPT"
+		},
+		"developer": "OpenAI, Microsoft",
+		"developer_links": [
+			{
+				"text": "OpenAI",
+				"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=OpenAI&si=ACC90nwLLwns5sISZcdzuISy7t-NHozt8Cbt6G3WNQfC9ekAgItjbuK5dmA2L3ta2Ero3Ypd_sib6W4Pr5sCi7O_W3yzdqxwyrjzsYeYOtNg2ogL1xVq9TKwgD48tL7rygfkRfNyy4k-R5yQgywoFukoCUths6NdRX69gl50cvd6dpZcMzVelCxT7mxXlRchl6XkueG326znDiZL-ODNOysdnCc4XoeAQUFtbaVjja6Vc7WkQF4X8rUdbDKPVU9WyLOV765d8Y777kMI7-nXGGyD7xXJX5E3HA%3D%3D&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQmxMoAHoECD0QAg"
+			},
+			{
+				"text": "Microsoft",
+				"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=Microsoft&si=ACC90nyvvWro6QmnyY1IfSdgk5wwjB1r8BGd_IWRjXqmKPQqm-SdjhIP74XAMBYys4zy1Z9yzXEom04F9Qy-tMOt2d-L6jIC5cXse6I528G870-4sF-DZYAPj0F1HoGTUOqpWuP7jbEPm3w_-mCH0wVgBHBGCgxRrCaUn8_k2-aga9V9JD6hkq2kM8zVmERCqCM8rqo3bNfbPdJ-baTq4w8Pkxdan3K--CfOtXX--lTjJtO6BnfG2RdpY_jBfy3uZZ7DeAE4-P4rvKuty6UL6le4dqqDt-kLQA%3D%3D&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQmxMoAXoECD0QAw"
+			}
+		],
+		"initial_release_date": "November 30, 2022",
+		"programming_language": "Python",
+		"programming_language_links": [
+			{
+				"text": "Python",
+				"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=Python&si=ACC90nyvvWro6QmnyY1IfSdgk5wwjB1r8BGd_IWRjXqmKPQqmwbtPHPEcZi5JOYKaqe_iu1m4TVPotntrDVKbuXCkoFhx-K-Dp6PbewOILPFWjhDofHha-WRuSQCgY7LnBkzXtVH7pxiRdHONv3wpVsflGBg_EdTHCxOnyWt1nDgBmCjsfchXU7DKtJq159-V0-seE_cp7VV&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQmxMoAHoECDYQAg"
+			}
+		],
+		"engine": "GPT-4; GPT-4o; GPT-4o mini",
+		"engine_links": [
+			{
+				"text": "GPT-4",
+				"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=GPT-4&stick=H4sIAAAAAAAAAONgVuLVT9c3NMy2TI_PNUtOX8TK6h4QomsCAKiBOxkZAAAA&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQmxMoAHoECDUQAg"
+			},
+			{
+				"text": "GPT-4o",
+				"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=GPT-4o&stick=H4sIAAAAAAAAAONgVuLVT9c3NCyryEg3rMooWMTK5h4QomuSDwC3NAfvGgAAAA&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQmxMoAXoECDUQAw"
+			},
+			{
+				"text": "GPT-4o",
+				"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=GPT-4o&stick=H4sIAAAAAAAAAONgVuLVT9c3NCyryEg3rMooWMTK5h4QomuSDwC3NAfvGgAAAA&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQmxMoAnoECDUQBA"
+			}
+		],
+		"license": "Proprietary",
+		"platform": "Cloud computing platforms",
+		"platform_links": [
+			{
+				"text": "Cloud computing",
+				"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=Cloud+computing&stick=H4sIAAAAAAAAAONgVuLSz9U3MKqMt8w1XsTK75yTX5qikJyfW1BakpmXDgB-4JvxIAAAAA&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQmxMoAHoECDcQAg"
+			}
+		],
+		"stable_release": "July 18, 2024; 40 days ago",
+		"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALoAAAC6CAMAAAAu0KfDAAAAaVBMVEX///8AAAD7+/uysrJ5eXmoqKiYmJhZWVn4+PiVlZXw8PDj4+P09PTr6+vNzc2vr6/V1dWMjIxmZmY7OzvExMSCgoIgICBMTEygoKBUVFQUFBQxMTG4uLhDQ0O+vr7b29snJydubm4LCwtts+PWAAAPGElEQVR4nM1d6YKiMAxWBKlcIocoooDv/5CrjjpNmrTlcNz82x2Oz5KmX46mi8VMIoI42ZbZ+VRfl9e6891108aRM9fjPyVOmHhZvVSl2hVp8G10Gom3uyMB+yldf0i+jZCRcOdfeeAPOfUr8W2YqoTZ3oD7IfXJ+zZSKE7Y2+B+SvMfKX24oWYmL8fL/2JvPM3cpKV2w2+DvkvgmiYnJefLt3EvFsl5BPC77L6tNNuRwG/Sf1VpxFqHrd53WoN5TL+HPNgxoPb9ZnspiiRJiqLx2CU2/9rqGpUkoNMhDQN50RRxeMlo7MV/hHxfxszlTUUY/y5ZOEIEaXJZeQfvkrRRJD4/fR0CuX/QUZTEPamfyCsr+D9VuUrC6KPQ1RlaHUxvTGgVUxWp9JLP8bQVft11Z2HsxKWioBJy6g8fMp6J8u0LOx0NXEvsd/SfAB/76DVH+3sLa+y3ydPOPmk38A373YB720HUoZx53Urg4+uD/diEh2EEedkdOHM7SpCia00iFM92mkriz7hwHeCj7bWlPY0hyMvrYS7kYQce7Frf14/B/ZBqJqWB60pmqS3hwcrvZqSbZbamwEKcW6ubxErnBea+65al2x9VpvDGPodbBTS9totNJBmr5P4hScM4iKIoiOMwLdbMb9xPxx4CG5HZ3OL0DPBrVqjBSEc0NPpmKvQLGAqL6RN4NO668thpEmwoIzrRSArgNJjtIjeEy2ylXceiA+YaN5nmVQXgWUZenTChjvxi/F7hocN3WdoEUkTRy49aGy4PSsZkrK04oULyltk4++6EG0ScrnoAYsvQlcyazB7wrSOiN6L1VMK3061GIs1p4Mchsy3BDxlqZsRlp+jdTXSWNmV8ObMXCKXFSjMsTHxxyS9f8ZMm2jFDbqfksmCFt6ZMNwl9hnyU7C0rxpuo4hFOTwCnem2tMoKLcN1oOn2Hw0VQzyOXlBg+z7ezMk7BfPibnOgFomWU/LweHZ5I4bhbsfdooyGqZ+rXxx41n+/e64TVZNEAGGeL+aIuCbJQQYAtt+on0zx7+CW3xutTveuukkbnzKxBxeRQFvyEpnFoDK67Qr1C+rpuMxX3TVLwSIOR2Zr8MUxgBKle+1Kv5CIpisTCZoCg2Vl7qRJTvEsuT3XsH12o35oZDGJR3u6qs8aoUS14um6iFoS25KtQ/u14sihU6WY/Ddnd+MUs697kNjtgfdH4CQrrufu1An62FbpHjVrvDHxDDoRdS8PcAwFaPsSphgXz9f3JzgDoe8OqJwq8Aug/EXg3sxzeVqIeI3db9XY99MzgwCdEsNq/6FTek/0tbkXFtGX/CipaQz+t9EMebshFt3Y13mcsKzFD/VL0wNN7AG2hl4almnBbXu/SzA/Z+NKenoOMnEQZ7KD7BrpC2ABJui2nNXJQhE65IuZ3lp5kBd3XmgonNGZkzikNPpYvohZUlCU6yl9mOvRwbRM03dFWXr6EoGARHJQKPGQy9ANill1PY89JlZdnyFp9C0xSofjqROgpZjl+G4XMjM0JjZBVuVS88wgm85H5nARdyQzsf/jNhQkznRWtkXlVr3wWmODq0V+10F9ZPRp6jIOm53fuLMKhqffIIlMla8RRsY7gk17xLxsNXQ2abmRYbUlHJrstANhKf8rx0gHsjwJutMIkuFg2Q2bZKRhf0pdVXvZlThg6sOmVok7jRl30yCB2BFcRDY29Pv5i1EF3ALNQF6wx0APM4zsuPlzSwYRl+RpCHXTgjqpzeAR0pRTj5PI0oaUDhMvTU+Vl6FjXZWRXRdNHQFcKYPQRMMEUWi2zh8rL5g9lUkHcRbU+g6EHO8Vkn3Z6Nhxvaa2p7x9Lng4Z1IlCfhPFiIdB90i6YkpXxkzgr96BehAXrqbyhNpTDGgAdKEW+7wfbSiOCo+0ytfyyrUBxgAEdU/UQ+2hc5mB55AZUnErbcTwIZA5xvIcwRxgEPRgg7yJHP3bqPIen3L/EcgL25z90xDomRo03cQxqltaVoaYZ6gvzkMhAUC9SBfRCnqHE83VwwSHyn8b/MBWVxDhQ7MOqDr5NCvoGOGbhTQYPFuJ+hRNNQeyf3JBdzUT9Fyu3IoPSOX3nh58wGXTsCMh20Y6TzYYOnYyUxzi8Q1WXrnhR5Cqg4AkHRMfCL1ScxlOgrTm6usDpXTmGMUchS6MOwI6lyVUUg6uQeWJSD9iMKKX/ka/dgD0nM/mC6zBV6PKK8QGUhgAnaCNg6AzkZSnqCqvDZTejJ/CKEF9wowKczSVcIsE2T1TbkBZ0sDrAXR64ttBr7cWFQwRrjjpDMn/Ffa8JZVx5B9GB4GtoB8tSy8ivOBc9dsicS5UJoiyXSfZlyX9skOOAic/t2oTrDjXIvm4cnApnwDdMjHtUMxWO71TuCacfq+dicNMgr7M1xp9Q+5L+f5GgDnSWYPPQ79nLPl74Fyt33QA6BJp2D8DPeuRsTnz2xxgru3toAIviax4/Qz0w6JBH+DKlqA4cG16aTvgX93fQV8TdPjMqTysoXgbQjmOXFOeL4COVXIKdMKh85nEK4wDvtYlMIHJxU1+Ps7TT4N+3w2BwNOVnAGw7i9yHsgqV1E3AiZxhgvIVOg3dotTBCQdATa8e/1vaboPjQzYNDQd+iJeo5GnWCCwJvUrhglKSciEMNqQfFr/XjQDdCX0pCQA7gJSO2+yBSg9GZRVUkLvgZkF+kIUQGuoWDwIi/gvSwRcADWr8bgRs/6unRP6AuUYqTvJ8hJYWM9UbihB8J+GBrNBl5WStHNAr99TsgeYmAILsUamIL/vDZ0NuqyTtP8h6/XbB4YFJRmHot0gn+W4fa8VfwAdxHVfhgKVCLA7GhwcQqyzF5I/gC57Fr/7NxLA4qiE0ku4BPkfQJfTu9c3YxHQsOq6FERY5f8MOigv+bXhqOK60nlcLRUR/AvoMs+UaCAq+tOWXFMJ8sFu9QjovXSJbMJRlEy/j0k0OD4yOJgxArqs1XJCDK5LN+Ohmat3wRHBoSGkEdDlS0BZqrLfb6MPZ6Y4QT4scDcCumwdXRB9VFJQuAgEI1G4wZBw6UToPRimQAms6kon7xLjEGLObxXDQeo5R/2GRN3uuTdkC2Ncv2idGhgBXWZoJRojKneWM6WTL2l9BMomITMOuuxoohwEk+A3dYHwsJU3psFGQpe/sCaZJwtdOvkrMa7G0CcfR0OXlQIpMp/lPhqMvFITokn5jocu3w790FBXGXE0JcgtE+3H10o2HHokPwdVC+jrIkwd6mzKG26+ycsCD4cu76rP4UgyhXtv6UzZQlNRSb4JpjBHeZaimIu5/ZtvUHl9Kc/DUk2ALisFtI2OzHuZuVUbVF5TQHX6YTjjoYPtVdA2Br30p/Vix2zGMyXI6bK196o8Hrr8RU/QwAAK09xLJ+lS//3WlCAnigXfC8No6LFsBXy40IAdwQ/bw5ZO6hPkBXZeS8mSjYa+krUAhcdC2R78GMJ4S6uuLkEe4sZwR/BDx0IH6ow5Xii/8mXDudLJPbexfY3LKVAHgbHQQaKxQwSPhM6XTi6JnUTigoDvXXzRSOgBMHl43BjoCyXT9hIfd4lL0NyoCTdrJHS45mA7AXatgEWfVXmwraLF+zd9aivsOOhwX4MSOAE+EhouMmp0k/PmpcnOQUkJ0anvUdB78GTlW4I5rKz43Eb35y7DAq8BXH/AUdChI5Gpm5NkekPsBW6Y6sNzGCmNRfiCujHQYS8EakuGzFCpYk2FGb5HGNmVo6ZJxAjo6IO7RKgKzGLyvUzpJJS9vqXKYOgx9APIXcqAr9PeqDD0dri/NtRv3xwKPUBvJNdCQFg5Zu6smM04P2LqH/w7Kyyh442Ge/Kx5vKShxDM8CVnQ62uXM1gB11JojD+gsxXzpr4RULvJOo2eu81Xsk4bKBHa/wizgDI5rPWRezEheAGpq0YiEJbQE975R0c24blJdrZFmAfvDO0VIlxbyUjdLFRFPPIf1bZDLGtEp7ilNLHrA3tfoju4QboolF9tFwTQQQv2JgSW+mr8Kwz7GEIGmJi4x8LtgCEK2L163SDCUqs9sY+Rk5zV999aWqpQvmJSn9M0AaCsmGGrpTgt/Idvt4S31vU6HNIbUl650rzU9NC3Zma8oCr52gOTbfJIJ5t6JHQmdqHCWBH66m4BdOJuj6rX0q/i01jW14C03hDmk6r4mCH742DYhnaE0ZseutCyjCpX2u7o0NQdNSVa4rwuINo2kAIdEjy0d3ShOLwPYXpTYVjCZJUls1LUa+148jzdpQI2FPOXAM2ZkPcTewbjqMsYTXi7AKHyGL+AGeD3IIxMB2xTZ0X9JDhPU9D5rSTfMcPA+3CdO6wjooxut++XepDxIEZcm0Ok8qonDaWJxn8Cm65pq3rwVIwRk6fOY5UffEbvadIiqp3hjzMryiVnE+p2VZHP9Ki6ysvGNdPUe3o2FspPKfkp9I010A82Z3SBjJV8xKG7N1N4gsdprma3Cc8vaadf0Zwj8zTBylW3Kpv7h8IjVpvsegPxH71efDCo07ouYtnMYYpKCSbfLIDnf/NCketGIkKrrv9tbfSWvC9LDiiETvTqivzkjaMgyASURCHbeIxinLvbm9HPiBtmuOcQh0h8nu3LMs+04XxjpYnPcJt1KY+A3aSakN0Btnbnh6EdnFOcxHeEms76mnF/uQgqG/dXEfHOLtxR6ic7L86WrlnPJGTa2+lk8r+/bhtN73hdaTESsTSIPWAI7IE/qozH5FH9RvlxVRoJYuDI8amAxgGi5MOOQ/U3hOPlO13sx7v9JSwN/XL+RXXjj4JpROzLho6Cfwa5+lYqWyOj01VNZx8WgwrUeGVloa+NE23eK0+abZDqWj0YbJ10eJflUTzpdzVffwIdwS9y0zLqE4cEUVtUjTbbXNJQiGEs0gIVaorjryGZGGW2trzb4Q5VqEvwhgU9wdhwkTztEekfFTY/sHHndc8Tmu9f6cNe2qVJkLzcUm1J93tO/0huQMOA/yAcGEMGzGf3fBZUXepWMqgCNWHpBl1mPW3TAuUdvgR4gMI8mdFXIYd3F4bqgr+VJQEvE76EQHRj8q6s1Kb/cDg95+I2Bopcu3bHF/8FUnWuDeyLP6u+YRTMZfE6aWkjGXXr5L/dcB/RURxu1q7WV5fl9f6VPW7bRIHs/Gsf2zY1viSH96vAAAAAElFTkSuQmCC"
+	},
+	"organic_results": [
+		{
+			"position": 1,
+			"title": "ChatGPT | OpenAI",
+			"link": "https://openai.com/chatgpt/",
+			"source": "OpenAI",
+			"domain": "openai.com",
+			"displayed_link": "https://openai.com › chatgpt",
+			"snippet": "ChatGPT helps you get answers, find inspiration and be more productive. It is free to use and easy to try. Just ask and ChatGPT can help with writing, ...",
+			"snippet_highlighted_words": ["ChatGPT", "ChatGPT"],
+			"sitelinks": {
+				"expanded": [
+					{
+						"title": "Introducing ChatGPT",
+						"link": "https://openai.com/index/chatgpt/",
+						"snippet": "We've trained a model called ChatGPT which interacts in a ..."
+					},
+					{
+						"title": "Download ChatGPT",
+						"link": "https://openai.com/chatgpt/download/",
+						"snippet": "Download ChatGPT Use ChatGPT your way. Talk to type or have a ..."
+					},
+					{
+						"title": "Pricing",
+						"link": "https://openai.com/chatgpt/pricing/",
+						"snippet": "Pricing · $25per user / month billed annually · $30per user / month ..."
+					},
+					{
+						"title": "“What is ChatGPT?” article",
+						"link": "https://help.openai.com/en/articles/6783457-what-is-chatgpt",
+						"snippet": "How does ChatGPT work? ChatGPT is fine-tuned from ..."
+					},
+					{
+						"title": "For Teams",
+						"link": "https://openai.com/chatgpt/team/",
+						"snippet": "ChatGPT simplifies lead qualification and forecasting ..."
+					}
+				]
+			},
+			"favicon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAAAAABWESUoAAABQElEQVR4Ac3PIYyDMBiG4VefPDtxEj0xM39qZl40mcPhMzONOjWNrqxA4UgmqklweBQKVfFdGhbSZZvfY5qmb35++DAbO4XQF7xjpN42s1oyXtlr2gN4SRpynnTaANtesy1tkOOR8aoAJ12J6ngmGkknCqn5gv0y8Jv03eYy+PEAu07jCQ66sDqqpohBCVb2PMtvSbeoxRJcLlIFVFKVBuOwBDdNxkzjEbKbVDwHvgZw8j+Qq2fVhhjkxB2g7JwqKJMRhUqo5Lol8OTxMbSsehXw45e9ao+J92EkGaFbBscxLqnbPRhYOVXr/53L+wTVaUDmNZ+tLNyDWgdWl3gxo7otHMYY5DYdwLc6gB18tVLBSVJD6qr6fsoBVt7wyCm4PxfiRyBTx5N8kCQP8DtrzysZrebG9ZLhnaILYbIbPss/4c/row+G/FAAAAAASUVORK5CYII="
+		},
+		{
+			"position": 2,
+			"title": "ChatGPT",
+			"link": "https://chatgpt.com/",
+			"source": "ChatGPT",
+			"domain": "chatgpt.com",
+			"displayed_link": "https://chatgpt.com",
+			"snippet": "ChatGPT helps you get answers, find inspiration and be more productive. It is free to use and easy to try. Just ask and ChatGPT can help with writing, learning,",
+			"snippet_highlighted_words": ["ChatGPT", "ChatGPT"],
+			"favicon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAAAAABXZoBIAAABGElEQVR4Aa3SIWzCUBSF4d8rDA6LnMfiMPjU18xiJjHPCxzBVePqaqsrK6sqK5qgnmjybzShzQKb4tjv3mvuwX/yHhya9i8cDgCXlziwKm99TnIM5RN+rlQvkO5Z97+wP1FpAbkadwwzWgAOW4L2rcppxoZLjc2i1xMEzZYzblMrbBILzpaQV0wYqUfcbNNk3+kZPibsaEek1oqjxj3DA6W8Y5uobs7kuggTphvNOKWq6/HQlQl70sF4oNaS2NNaMzxQ4Krt9rBPliMW82akubKqDFSuR9x9TiiF8QsybfnBLtDNePhQm3ifSOyAyhlvpKoZy0pzsuiM2kKSwlWNhKd/FiHsFsXtVrB5XbAAEHyN2jTv7+1TvgE1rn+XcUk3JAAAAABJRU5ErkJggg=="
+		},
+		{
+			"position": 3,
+			"title": "OpenAI",
+			"link": "https://openai.com/",
+			"source": "OpenAI",
+			"domain": "openai.com",
+			"displayed_link": "https://openai.com",
+			"snippet": "ChatGPT on your desktop. Chat about email, screenshots, files, and anything on your screen. Chat about email, screenshots, files ...",
+			"snippet_highlighted_words": ["ChatGPT"],
+			"sitelinks": {
+				"inline": [
+					{
+						"title": "ChatGPT",
+						"link": "https://openai.com/chatgpt/"
+					},
+					{
+						"title": "Introducing ChatGPT",
+						"link": "https://openai.com/index/chatgpt/"
+					},
+					{
+						"title": "Download ChatGPT",
+						"link": "https://openai.com/chatgpt/download/"
+					},
+					{
+						"title": "ChatGPT for teams",
+						"link": "https://openai.com/chatgpt/team/"
+					}
+				]
+			},
+			"favicon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAAAAABWESUoAAABQElEQVR4Ac3PIYyDMBiG4VefPDtxEj0xM39qZl40mcPhMzONOjWNrqxA4UgmqklweBQKVfFdGhbSZZvfY5qmb35++DAbO4XQF7xjpN42s1oyXtlr2gN4SRpynnTaANtesy1tkOOR8aoAJ12J6ngmGkknCqn5gv0y8Jv03eYy+PEAu07jCQ66sDqqpohBCVb2PMtvSbeoxRJcLlIFVFKVBuOwBDdNxkzjEbKbVDwHvgZw8j+Qq2fVhhjkxB2g7JwqKJMRhUqo5Lol8OTxMbSsehXw45e9ao+J92EkGaFbBscxLqnbPRhYOVXr/53L+wTVaUDmNZ+tLNyDWgdWl3gxo7otHMYY5DYdwLc6gB18tVLBSVJD6qr6fsoBVt7wyCm4PxfiRyBTx5N8kCQP8DtrzysZrebG9ZLhnaILYbIbPss/4c/row+G/FAAAAAASUVORK5CYII="
+		},
+		{
+			"position": 4,
+			"title": "ChatGPT - Apps on Google Play",
+			"link": "https://play.google.com/store/apps/details?id=com.openai.chatgpt&hl=en_US",
+			"source": "Google Play",
+			"domain": "play.google.com",
+			"displayed_link": "https://play.google.com › store › apps › details › id=com...",
+			"snippet": "With the official ChatGPT app, get instant answers and inspiration wherever you are. This app is free and brings you the newest model improvements from ...",
+			"snippet_highlighted_words": ["ChatGPT"],
+			"rich_snippet": {
+				"detected_extensions": {
+					"rating": 4.8,
+					"reviews": 3113820
+				},
+				"extensions": ["Rating: 4.8", "3,113,820 votes", "Free", "Android", "Business/Productivity"]
+			},
+			"favicon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADNklEQVR4AcXUA4wdURiG4VPbtu32mmvUtm3bts2gnNq2bbtBbSzn3n79M8nZrvdcZZO8yUTzPUMGIFmLOlBJTXhtqcPUUaoDxRwp16aObORuHcNpxmwnUjM5/uICam0PyKza2miJSmoGOlH0HlIGjwPUm9tOqrXdD6qt9RANEb39VEGPAXym/5YMK9ehxu5ahKgbH4I3m0rjdoDfBEj+EwDDyrUiiO9UR7ffAd8pMvzHAyJ3Ivr74R7AtD8SIRATURO1ttWBakuCCN4BqrDLAApRiAmAcflm1NilJUSwCAJqqcm8nBs7pRu4y+gCIAoRqdwJ07LtdCfUSSLUlEZqjBQbevwctVvX1QUA7zd8pkYIIfh41o2d0Xh7ICJOpAFOsic0ZHYBIIZQKynjaLQtCPLJVKCrRyQhaAjUYaqIMCBxxA5CqAgRRIjmUMapzBu7oHG0cZmPx2welU4AkARi6T7U3KWHanuAgshCV952hy/sJ1MmNs77Q5mcAHBEuIKwLD6Mmrv1yCS1QMftfsApJjLO+0A1cxjAEb5TwxRE/uX30PmkFrjAgJPRn7lQklMAXwL4TAtB8UVA927PIA8sBNxikC+mhHzMgwA+7j0tFEWXAN1GXkGksSzkMpXxdWBZ/L3LYLvMRBHfqLbCAD7uNT0MJRYDfYedQ4S5FOymonjvbcD7Slb86Fsa9psM9itJIuxUsPhLGG282BJg8ODjgL4QbKbieO+nxyc/NT55a/CughXfu5dLCrGGyir2GcYzPoTGYSiESFNJGtfhk69aSUH4qPG+YoKIc1Q58R+R+Hj8iJ5lYbuUArazKd/QkL9Tv2Lfqb9hpfGiSYzHh3hXxjv05/Di/e23GB8TB/Bxy4xw5YUbMfAwjRdJajw6Yun7KpaM37uWY/YbBDjpIICP023HxH47AV0ehJtLi4wfp0pR7H01M6OvwnGA/9Rf0v/xHTSeF2GWMviQ+PhzykoxJVcA1eZBKh5jvGxi47+oHnzULYCacyFN7is0Po9KRzG3Am7XbzopxFoeoZZyCY0foIrwIbcDZFPxzN+9qy2JZ/whZeADngJEP0k76gB1kOooOuwKIFn7B3LHHIJtp64TAAAAAElFTkSuQmCC",
+			"thumbnail": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5OjcBCgoKDQwNGg8PGjclHyU3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3N//AABEIAFwAXAMBIgACEQEDEQH/xAAbAAABBQEBAAAAAAAAAAAAAAAAAgMEBQYBB//EADQQAAIBAwMDAgQEBAcAAAAAAAECAwAEEQUSIQYTMSJBFCMyUTNhgZFCUnHSFTRTVGJysf/EABkBAQADAQEAAAAAAAAAAAAAAAABAgMEBf/EACURAAICAAQFBQAAAAAAAAAAAAABAhEDEhMhIjFRUoFBYcHh8P/aAAwDAQACEQMRAD8A8sooorc8s0+l6ppbaRp1tqF3qNlNplxNNH8CvqnEgHh8jY4K43c8Grew6u0sx2UGoid4LWGwEfyEcxyRKwlbJ859PPuB7cVzo3pe21jpW9ke2jkvLh3W2nPcIhCBM5K8KctkL5cAiq/o/TkudMubmDT4b++F5FC0c8DzLbwMGJk7aEE+oBc+35VxT03m9vk64uaSLxuutNhuFltxMwWe0nkjW3C/EMgdJDksSDyjAknOwA481BseoOn9JtoILY3F40XxO6SSBkE3ciZQHXuHLZI3MNvHjxVje6Z05odvIur21msTXt1GQI5ZZWARCixSAjbgt5aqS+Syvun9FJttF0qW9inkmue1IvMcu0KuC2Mjzke3tVIxw5ck6+izc16ogw9RiebUZb+GKIS6K2m20VtGQkY3KVHJJxw3OTUrovqXT+m7aZ5rWe4uLi4jEoRwii3UHI5B3ZJOV4zgc1anpHSptJ0w28q3Dqly872MyyTXrJGj9uMcgEEsPGcAZGTWS6m0yPR9Xks4XkZBHG+2bHcjLIG2PjjcM4Nax0sS4L9RlJzjxMuX6pt7GwsbDR4I2WGK4tnuriI94QvMzAL6scoVzkHnPNUfUl7DqXUGo39tu7NzcvKm8YOCcjIqtoreOFGLtGcsSUlTCiiitDMKKKKAUruoIVmAJBIBxzXUZw2ULA4wSpxxXFXJp1Fz44pQboTtJ+piR/Wu7RgDJOPHNPrH9h+ppez/AJAUKOZFAIIKswKnKkHwfypDK2SSc5OSfvUwxk/Y006UolTsi0U4y5596boWCiiigCiilL5oBaj2qRGv7UzH71IHipM5sft0E1xFE0ixK7qpkbwgJxk/kK37QaN0wz20+LcveSJI17Zx3Us8KKgwFyBGjlnIb7Ac159BKYZ4pgquY3D7WGQ2DnB/Kt4uqaFrqqZjGiw3Bl7er3mwW0bnLrAEX5gGOFY/YBearKzTArfqUvV2i2mlJA9tHcW0jyvG1vcSrIXUYKyoygZRgft5BrNMM/1rQ9U6/barEttYxz9lby4ue5ORnMjcKij6UAA48kk5rO5qY3Rni1n4RiQc5phxzmpUnJqO3ihaLG6KKKFgpS+aTXV80A8nvV/0qmktqZm16VFsIYyzRszZlYkKFAX1H6t3A/h54rPKcGtBp+mG9jYW6WG+NYsLcSyK8pePecYYDjn9BRiMW5WWT2WiW+rW9vHcWVyqafKQ7XHyZrlWkCdxgRtBAU+QPHIzUqXTem7oRRteWlnc7i0ht70GH8SFSuXBPh3YHwNp+oc1XHprUBjNnpWSSAPjGOcDP+pjxSX6fu07bG303Y8ywlhJL6WL7PBbPk1XyaqL7S5TR+lBG8IvY3f6e82pQowy0ByM+n0q0wzyDtYecYhDSOmorSV/8UW5lNs5hBuo4w8mzcDjymGyu1/q8ik3fTEkd0sNqtjL3ACm8TKSTkY4c8cYz4JKjyRTMfTV60gR4NJjJGctcv8AoOH8nn9qjyS4vtMw58VHPipd2UKwSJGI+5HuKqSQDuYcZJPsPeoh8VcwSoRRRRQkKKKKAUDUxb3KqJbaCRlULuYNkgDA8EewA/SoVGaAnC6j/wBlbfs/91KF2i+LO3Hvxv8A7qghjXd1BbLCbUWnk7txDFLJ/PI0jN+5amjdRY/yVr+z/wB1Qy1czmlC2PXE5nZSURFVdqqg4AyT/wCkn9aYJozXKAKKKKAKKK7QFpHoVydCk1iXK2wC9rau7eS5Q5/lA2nk/dceeIkGn39yiPbWN3Mj52NFAzBsecEDnHvWv6mJXobTFHhfg88fVut3fn+ngY9sZzgYXply9j0dp93B+NbyzXClmYhnVJtmRnwp5AGBkknOTmmbY6NNZqMX8FeB4ozaXIeb8Jey2ZP+ox6v0rq2N42zbZ3LdyQxJiFjuceVHHLDB4816DDqD3g0SSeJGkkgGXDOCO8q274O70+hARjGGyacj6gur97dZooQLzUZ7GXZvHywJgNvq9LfPf1DngfnlmZKwY9TzcW1wySOtvMUjYI7CMkIxOACccHPsa7LZ3cIlM1pcRiFgkpeJl7bHwGyOCfsa9CXqK5srW+1OC3txcfFx3JBDFC8sy7sjdz+CmPcc85NNa/rVxFpms2sccOyxmis4S4MmUKxElwxIc/KXlgfJ+9TmZGjGuZidG05tV1KKxVzG82VVthbDAEjIHOOOT7eccUzf2c+n3b2l2mydApdc5xuUMB+xFWel302rdaWd/cduOa81OJn7Uaqql5BnC4x7++c+TkkmpnX57mq2U2MGWxRio8D5ki8Z5/hzySck1N7lHFZbMxRRRUmR//Z"
+		},
+		{
+			"position": 5,
+			"title": "ChatGPT on the App Store - Apple",
+			"link": "https://apps.apple.com/us/app/chatgpt/id6448311069",
+			"source": "Apple",
+			"domain": "apps.apple.com",
+			"displayed_link": "https://apps.apple.com › app › chatgpt",
+			"snippet": "This official app is free, syncs your history across devices, and brings you the newest model improvements from OpenAI. With ChatGPT in your pocket ...",
+			"snippet_highlighted_words": ["ChatGPT"],
+			"rich_snippet": {
+				"detected_extensions": {
+					"rating": 4.9,
+					"reviews": 1026513
+				},
+				"extensions": ["Rating: 4.9", "1,026,513 reviews", "Free", "iOS", "Business/Productivity"]
+			},
+			"favicon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAC5UlEQVR4Aa1XQ3hkQRjc+ynX2OZtbfu+tm3b1nlt27a9O4qNS5xxbdd+cTKvXydT31fJoPuvmvf6/ejw86dBlX6CwwQXCq6t5cLaz/xV4+ld6F8r9NdgsCAjIwf5+UUoLCwBydf8jN+JNQbBddzjDQM+gocErRSyWm2QgWu4lntq9/q01UAfwYKCgmK43W6ognu4lzEE+6oamCboLC0tR3vBGIwlOF2vgZm5uQWoqamBXrhcLpw5cxZ79uxFKxCxrGBMxpYZ6Eu33KAXNDp+/AQEBgbzv8Y6Kxi7+e1ofuAKVS/7zp27KE7i6dNnem5HAbVaM3CYh0YF/PWRkdEUpxHoQe3BPNTcQJCgTc9pT0tLh8VigdPpBLFv3368evVKBC7A16/fkJmZKX06qCXo39jAej67Wnjx4iVGjBiJ0NBwBAeHYsCAgTh48BCuXLmCKVOmIioqBrwS4eGRGDduPMxmMzyBWtRsbMCglWSePXuOkJAwCuhmnz79YLVaPSUrGjDWGQhgCvWEyspKdOrURUk8JiYO799/0Exg1KQ2DQxjHveEO3fuKomTPBcyUJPaNLCQxcQTNm3arGzAYDBABmoK7UU0sE7rAC5dukxJPCgoRPy6DMhATWpLDWzbtl35Cty//0DBgOQW3LhxU9nAsGEj4HA4dN0CySHkwvy6bKfECRMmISsrS34IZY8hMXnyFAZV5rFjx6WPoa5E9PnzZ2XxpKQUlJaWaiUik1IqXrBgkZKB06fPwBOKiv4fwA3Ni5FdK3NVVFSgd+++usRnzJilXIzII7JynJOTAxaa7t17Yt68+bh37z6+fPmKCxcuYvToMejVqzdWrVrNMi0rx4cVGxIFKDQkCi2ZAhRaMklTavWqeF6epCltxuneasvLyurb8lmqg0lfLw4m/dozmh0RtBUV6R/NuJZ7avf6eGs4ZeIwMoVmZrYcTvkZv+MarlUZTlUZIDi8diRfX8uFtZ8FqMb7Bx+2VJbBTrlcAAAAAElFTkSuQmCC"
+		},
+		{
+			"position": 6,
+			"title": "What is ChatGPT and why does it matter? Here's what you ...",
+			"link": "https://www.zdnet.com/article/what-is-chatgpt-and-why-does-it-matter-heres-everything-you-need-to-know/",
+			"source": "ZDNET",
+			"domain": "www.zdnet.com",
+			"displayed_link": "https://www.zdnet.com › ... › Artificial Intelligence",
+			"snippet": "ChatGPT is an AI chatbot with natural language processing (NLP) that allows you to have human-like conversations to complete various tasks. The ...",
+			"snippet_highlighted_words": ["ChatGPT"],
+			"date": "Jun 17, 2024",
+			"favicon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAAMFBMVEXQ/0rQ/0vM+0oDAxDV/0yixjxlfCpTZiV/nDK75USTtTg7SR/F8Uiu1UAfJhhyjC65DF56AAAAAXRSTlP3Yz+/2QAAANRJREFUKJHdkkGSxCAIRaOAAore/7YDJNPVSVfPAeYtdPEK5FMex1/U8pV/JutMatwVH9JaouTH1ok3SWsEjWGNBXve5JQ5qS+XJLJB8V3iZK8AZhBEAb5JBVjNEFPaU65tIibR1saiZ2XgbzpDH1H2ZtWttB316SSpZ5TeWymNtSfK501X2wFWo23mVR7DE49f2VYrkdPONVaEXmq9JHVIGUQSl/ia1jxFyOYT2YeUletTIyJ5SEIf4WoLfJNCE73EhJKoJHv9hHjFyQNzeefp8jvHD3ZbC4DWezICAAAAAElFTkSuQmCC"
+		}
+	],
+	"inline_images": {
+		"images": [
+			{
+				"title": "upload.wikimedia.org/wikipedia/commons/e/ef/ChatGP...",
+				"source": {
+					"name": "en.wikipedia.org",
+					"link": "https://en.wikipedia.org/wiki/ChatGPT"
+				},
+				"original": {
+					"link": "https://upload.wikimedia.org/wikipedia/commons/e/ef/ChatGPT-Logo.svg",
+					"height": 800,
+					"width": 800,
+					"size": "1KB"
+				},
+				"thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALoAAAC6CAMAAAAu0KfDAAAAaVBMVEX///8AAAD7+/uysrJ5eXmoqKiYmJhZWVn4+PiVlZXw8PDj4+P09PTr6+vNzc2vr6/V1dWMjIxmZmY7OzvExMSCgoIgICBMTEygoKBUVFQUFBQxMTG4uLhDQ0O+vr7b29snJydubm4LCwtts+PWAAAPGElEQVR4nM1d6YKiMAxWBKlcIocoooDv/5CrjjpNmrTlcNz82x2Oz5KmX46mi8VMIoI42ZbZ+VRfl9e6891108aRM9fjPyVOmHhZvVSl2hVp8G10Gom3uyMB+yldf0i+jZCRcOdfeeAPOfUr8W2YqoTZ3oD7IfXJ+zZSKE7Y2+B+SvMfKX24oWYmL8fL/2JvPM3cpKV2w2+DvkvgmiYnJefLt3EvFsl5BPC77L6tNNuRwG/Sf1VpxFqHrd53WoN5TL+HPNgxoPb9ZnspiiRJiqLx2CU2/9rqGpUkoNMhDQN50RRxeMlo7MV/hHxfxszlTUUY/y5ZOEIEaXJZeQfvkrRRJD4/fR0CuX/QUZTEPamfyCsr+D9VuUrC6KPQ1RlaHUxvTGgVUxWp9JLP8bQVft11Z2HsxKWioBJy6g8fMp6J8u0LOx0NXEvsd/SfAB/76DVH+3sLa+y3ydPOPmk38A373YB720HUoZx53Urg4+uD/diEh2EEedkdOHM7SpCia00iFM92mkriz7hwHeCj7bWlPY0hyMvrYS7kYQce7Frf14/B/ZBqJqWB60pmqS3hwcrvZqSbZbamwEKcW6ubxErnBea+65al2x9VpvDGPodbBTS9totNJBmr5P4hScM4iKIoiOMwLdbMb9xPxx4CG5HZ3OL0DPBrVqjBSEc0NPpmKvQLGAqL6RN4NO668thpEmwoIzrRSArgNJjtIjeEy2ylXceiA+YaN5nmVQXgWUZenTChjvxi/F7hocN3WdoEUkTRy49aGy4PSsZkrK04oULyltk4++6EG0ScrnoAYsvQlcyazB7wrSOiN6L1VMK3061GIs1p4Mchsy3BDxlqZsRlp+jdTXSWNmV8ObMXCKXFSjMsTHxxyS9f8ZMm2jFDbqfksmCFt6ZMNwl9hnyU7C0rxpuo4hFOTwCnem2tMoKLcN1oOn2Hw0VQzyOXlBg+z7ezMk7BfPibnOgFomWU/LweHZ5I4bhbsfdooyGqZ+rXxx41n+/e64TVZNEAGGeL+aIuCbJQQYAtt+on0zx7+CW3xutTveuukkbnzKxBxeRQFvyEpnFoDK67Qr1C+rpuMxX3TVLwSIOR2Zr8MUxgBKle+1Kv5CIpisTCZoCg2Vl7qRJTvEsuT3XsH12o35oZDGJR3u6qs8aoUS14um6iFoS25KtQ/u14sihU6WY/Ddnd+MUs697kNjtgfdH4CQrrufu1An62FbpHjVrvDHxDDoRdS8PcAwFaPsSphgXz9f3JzgDoe8OqJwq8Aug/EXg3sxzeVqIeI3db9XY99MzgwCdEsNq/6FTek/0tbkXFtGX/CipaQz+t9EMebshFt3Y13mcsKzFD/VL0wNN7AG2hl4almnBbXu/SzA/Z+NKenoOMnEQZ7KD7BrpC2ABJui2nNXJQhE65IuZ3lp5kBd3XmgonNGZkzikNPpYvohZUlCU6yl9mOvRwbRM03dFWXr6EoGARHJQKPGQy9ANill1PY89JlZdnyFp9C0xSofjqROgpZjl+G4XMjM0JjZBVuVS88wgm85H5nARdyQzsf/jNhQkznRWtkXlVr3wWmODq0V+10F9ZPRp6jIOm53fuLMKhqffIIlMla8RRsY7gk17xLxsNXQ2abmRYbUlHJrstANhKf8rx0gHsjwJutMIkuFg2Q2bZKRhf0pdVXvZlThg6sOmVok7jRl30yCB2BFcRDY29Pv5i1EF3ALNQF6wx0APM4zsuPlzSwYRl+RpCHXTgjqpzeAR0pRTj5PI0oaUDhMvTU+Vl6FjXZWRXRdNHQFcKYPQRMMEUWi2zh8rL5g9lUkHcRbU+g6EHO8Vkn3Z6Nhxvaa2p7x9Lng4Z1IlCfhPFiIdB90i6YkpXxkzgr96BehAXrqbyhNpTDGgAdKEW+7wfbSiOCo+0ytfyyrUBxgAEdU/UQ+2hc5mB55AZUnErbcTwIZA5xvIcwRxgEPRgg7yJHP3bqPIen3L/EcgL25z90xDomRo03cQxqltaVoaYZ6gvzkMhAUC9SBfRCnqHE83VwwSHyn8b/MBWVxDhQ7MOqDr5NCvoGOGbhTQYPFuJ+hRNNQeyf3JBdzUT9Fyu3IoPSOX3nh58wGXTsCMh20Y6TzYYOnYyUxzi8Q1WXrnhR5Cqg4AkHRMfCL1ScxlOgrTm6usDpXTmGMUchS6MOwI6lyVUUg6uQeWJSD9iMKKX/ka/dgD0nM/mC6zBV6PKK8QGUhgAnaCNg6AzkZSnqCqvDZTejJ/CKEF9wowKczSVcIsE2T1TbkBZ0sDrAXR64ttBr7cWFQwRrjjpDMn/Ffa8JZVx5B9GB4GtoB8tSy8ivOBc9dsicS5UJoiyXSfZlyX9skOOAic/t2oTrDjXIvm4cnApnwDdMjHtUMxWO71TuCacfq+dicNMgr7M1xp9Q+5L+f5GgDnSWYPPQ79nLPl74Fyt33QA6BJp2D8DPeuRsTnz2xxgru3toAIviax4/Qz0w6JBH+DKlqA4cG16aTvgX93fQV8TdPjMqTysoXgbQjmOXFOeL4COVXIKdMKh85nEK4wDvtYlMIHJxU1+Ps7TT4N+3w2BwNOVnAGw7i9yHsgqV1E3AiZxhgvIVOg3dotTBCQdATa8e/1vaboPjQzYNDQd+iJeo5GnWCCwJvUrhglKSciEMNqQfFr/XjQDdCX0pCQA7gJSO2+yBSg9GZRVUkLvgZkF+kIUQGuoWDwIi/gvSwRcADWr8bgRs/6unRP6AuUYqTvJ8hJYWM9UbihB8J+GBrNBl5WStHNAr99TsgeYmAILsUamIL/vDZ0NuqyTtP8h6/XbB4YFJRmHot0gn+W4fa8VfwAdxHVfhgKVCLA7GhwcQqyzF5I/gC57Fr/7NxLA4qiE0ku4BPkfQJfTu9c3YxHQsOq6FERY5f8MOigv+bXhqOK60nlcLRUR/AvoMs+UaCAq+tOWXFMJ8sFu9QjovXSJbMJRlEy/j0k0OD4yOJgxArqs1XJCDK5LN+Ohmat3wRHBoSGkEdDlS0BZqrLfb6MPZ6Y4QT4scDcCumwdXRB9VFJQuAgEI1G4wZBw6UToPRimQAms6kon7xLjEGLObxXDQeo5R/2GRN3uuTdkC2Ncv2idGhgBXWZoJRojKneWM6WTL2l9BMomITMOuuxoohwEk+A3dYHwsJU3psFGQpe/sCaZJwtdOvkrMa7G0CcfR0OXlQIpMp/lPhqMvFITokn5jocu3w790FBXGXE0JcgtE+3H10o2HHokPwdVC+jrIkwd6mzKG26+ycsCD4cu76rP4UgyhXtv6UzZQlNRSb4JpjBHeZaimIu5/ZtvUHl9Kc/DUk2ALisFtI2OzHuZuVUbVF5TQHX6YTjjoYPtVdA2Br30p/Vix2zGMyXI6bK196o8Hrr8RU/QwAAK09xLJ+lS//3WlCAnigXfC8No6LFsBXy40IAdwQ/bw5ZO6hPkBXZeS8mSjYa+krUAhcdC2R78GMJ4S6uuLkEe4sZwR/BDx0IH6ow5Xii/8mXDudLJPbexfY3LKVAHgbHQQaKxQwSPhM6XTi6JnUTigoDvXXzRSOgBMHl43BjoCyXT9hIfd4lL0NyoCTdrJHS45mA7AXatgEWfVXmwraLF+zd9aivsOOhwX4MSOAE+EhouMmp0k/PmpcnOQUkJ0anvUdB78GTlW4I5rKz43Eb35y7DAq8BXH/AUdChI5Gpm5NkekPsBW6Y6sNzGCmNRfiCujHQYS8EakuGzFCpYk2FGb5HGNmVo6ZJxAjo6IO7RKgKzGLyvUzpJJS9vqXKYOgx9APIXcqAr9PeqDD0dri/NtRv3xwKPUBvJNdCQFg5Zu6smM04P2LqH/w7Kyyh442Ge/Kx5vKShxDM8CVnQ62uXM1gB11JojD+gsxXzpr4RULvJOo2eu81Xsk4bKBHa/wizgDI5rPWRezEheAGpq0YiEJbQE975R0c24blJdrZFmAfvDO0VIlxbyUjdLFRFPPIf1bZDLGtEp7ilNLHrA3tfoju4QboolF9tFwTQQQv2JgSW+mr8Kwz7GEIGmJi4x8LtgCEK2L163SDCUqs9sY+Rk5zV999aWqpQvmJSn9M0AaCsmGGrpTgt/Idvt4S31vU6HNIbUl650rzU9NC3Zma8oCr52gOTbfJIJ5t6JHQmdqHCWBH66m4BdOJuj6rX0q/i01jW14C03hDmk6r4mCH742DYhnaE0ZseutCyjCpX2u7o0NQdNSVa4rwuINo2kAIdEjy0d3ShOLwPYXpTYVjCZJUls1LUa+148jzdpQI2FPOXAM2ZkPcTewbjqMsYTXi7AKHyGL+AGeD3IIxMB2xTZ0X9JDhPU9D5rSTfMcPA+3CdO6wjooxut++XepDxIEZcm0Ok8qonDaWJxn8Cm65pq3rwVIwRk6fOY5UffEbvadIiqp3hjzMryiVnE+p2VZHP9Ki6ysvGNdPUe3o2FspPKfkp9I010A82Z3SBjJV8xKG7N1N4gsdprma3Cc8vaadf0Zwj8zTBylW3Kpv7h8IjVpvsegPxH71efDCo07ouYtnMYYpKCSbfLIDnf/NCketGIkKrrv9tbfSWvC9LDiiETvTqivzkjaMgyASURCHbeIxinLvbm9HPiBtmuOcQh0h8nu3LMs+04XxjpYnPcJt1KY+A3aSakN0Btnbnh6EdnFOcxHeEms76mnF/uQgqG/dXEfHOLtxR6ic7L86WrlnPJGTa2+lk8r+/bhtN73hdaTESsTSIPWAI7IE/qozH5FH9RvlxVRoJYuDI8amAxgGi5MOOQ/U3hOPlO13sx7v9JSwN/XL+RXXjj4JpROzLho6Cfwa5+lYqWyOj01VNZx8WgwrUeGVloa+NE23eK0+abZDqWj0YbJ10eJflUTzpdzVffwIdwS9y0zLqE4cEUVtUjTbbXNJQiGEs0gIVaorjryGZGGW2trzb4Q5VqEvwhgU9wdhwkTztEekfFTY/sHHndc8Tmu9f6cNe2qVJkLzcUm1J93tO/0huQMOA/yAcGEMGzGf3fBZUXepWMqgCNWHpBl1mPW3TAuUdvgR4gMI8mdFXIYd3F4bqgr+VJQEvE76EQHRj8q6s1Kb/cDg95+I2Bopcu3bHF/8FUnWuDeyLP6u+YRTMZfE6aWkjGXXr5L/dcB/RURxu1q7WV5fl9f6VPW7bRIHs/Gsf2zY1viSH96vAAAAAElFTkSuQmCC"
+			},
+			{
+				"title": "Introducing ChatGPT | OpenAI",
+				"source": {
+					"name": "OpenAI",
+					"link": "https://openai.com/index/chatgpt/"
+				},
+				"original": {
+					"link": "https://images.ctfassets.net/kftzwdyauwt9/40in10B8KtAGrQvwRv5cop/8241bb17c283dced48ea034a41d7464a/chatgpt_diagram_light.png?w=3840&q=90&fm=webp",
+					"height": 1153,
+					"width": 1940,
+					"size": "93KB"
+				},
+				"thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALgAAABtCAMAAAAlHltpAAAAwFBMVEX////P6t38/Pz4+Pj09PTe8Ofr6+vw8PDJyczExMTk5OTY2NjMzMz1+vjM6dvV1dW7u7ve3t61tbWtra2lpaXg4OQAAACfn5/W7eGNjY3p9O+VlZV3d3fI4dSCgoJiYmKpvLO1yb9ra2tXV1dJSUmisqrB2MzR0ddBQUFrbXU0NDSPkJaIlo9venSQn5fb9+klKCZJUExZYl5+i4N3eIJQUVs5PEdARlOUlKWioa9YW2esrLk3PU7BwMkiKT6BgomhiaRPAAAOkElEQVR4nO1ci3bbNhKlAAQwgKnxjBmWsmI5kRPXjpPdJn2k7eb//2oHpB58SSalxGnPKWxRMs3BXAKDwZ0BqCw7upDjRZP0aeJZZhzWIfgxqtOBGU4IcdPlK+DEcIrazRHaM+mtzbk6QrICriAEa/R0+Qo4lTL6QO0R2jNGmVBHdXslQxiljgg6XZqstXNO2BHaTyjf2cZTc/MKRqsqQte176+//o8mGSftu6Cbt/XJwQattXFeq9peQsXmE6tU7O+MYCxEb3xU0ctgJUQ8eluClYHm1ke7T7bSrKWWBUhbcCm9jNLiG75kJDH4YIU0uSn3AmcSFcgQAS/lEquQNkgwylqw3gMOv0LKPeMH0fqwyBG+LfAYvQ0IOs8Ru8HbyUPhDgFXKJ8HlFIIFj/KcuFCIUtpZF7YuUv/C3uBU8Sdm9xDLhcuiduYW2lhEfJFrsocdJSFHlZPGSOEEsFwoKBrwherXuk3EzT9+xDwTJAkj1fhOCNECTzS9Gkti5XRA6bChMMLKFaQFLokzBQjwooEAq+hGT0wGBxHM2u54mqgp1slbq/DqCrEG85afVn7ZEX5I66iPTj1+sgSiiRJJR4keGP3NHi2yMuFl3M/L6O6dvMcXubF3JcF/ixMXOSxiMUwhkqzmdt5EXUBi6K4xv4tc/kyLopwvdhnYU3gIl4XKDMPZSzLIs7RWENRxqIIMA9zWdp5sPmeCiwoabhUAQSP3IJZGKmdwrFpJFgADSYOD4+6ry0Pkimv8DojrTPAU4UgwR32d7WNo2JrrDFGC5A4zgwi8EblORqLVQb7b2+LH1++sx8/QfVp0hVX0RqcidppZRwoUMbjm8EfCdxQMKCctsFzeMTwpqk+TbqycftygePJXnv/amHn87iwMF+8Qn+6mIcFvI45vtky5otwDA3cp/o06ZrqVIVWvxldf6LVKbDoqiOvT5JDTnGy6tOk/9E2/n3Kv8CfuvwL/BsU8myw/Fj/81HxH4bFn40EflD74fLsfFNmWLZ/nP84Dviw+Gz2wzjgbfFNDbPzHx4H/mK2Kcv7+3fvt381gG9ZL2neTA3r2UbjbLV6uNpKn++At3ln52Z22mezq8uO9pHA3z/M3i9nbdFac+QepMSAK3CwVgPyQgzdgLaBP6DqIeDIQjBe00YmKRvanKQB/N3Fh/+8u2yIpyBCMMGV4FoIxgTlXDFOQQjFG6IvVsvZuyHgyEu91kYjZIUvBA/GOoBOiz+8mN1f9oFToxXeKVIq5FY6ANdmAPjl/X8v3r172xLHYBmM93i/MSAVxpv23uRBY6xqDW3c8+X91WoI+MGyA37182onfj5k4wO1NbRjd93ctIFjjMeZIgwwWhSCc8YdtjgVPMWcjc56/35nZA3gQjptkH1qriwwAIZ/boncDvj72Yud+A44QUIrMfhQGMhoJ+Ue4Jezhu4N8IqVZSlIJpwwTgSe4DvRm9XN1dXyCg/4drPEw3LZMhVXJSSQNYNHQ5Ua7X1rp89m56vlcrVKgisUT9KryyZwa71Kxm25dLAH+Orn1f1qdXX/sFoPkxq4tXjLvspjRK6jt1HGuBO9vFo+oNIK/VV1WN60TaXipSkaFylSrzhoo8WvUCWCrwSX6cNVE3iG7YV9nnJCFasdBH5+ieUGf2bLZos7Zwg4rpmyDvs5FaX4VvQGJZaXeLy8XKYKzm9uWsCpUk6hoQgzkA14NkPpVC5vZrV4qqU5OEFzivEOViFMLyRfA8e7Xl5drlLzNYEfLC/Qfz9gk2OjLx+wux8esOOvmsCVjyCDDcEMhCjPZlcP99hPxQ2qXd3cr36+XN0vG8BZSnQZrCKitYiu+Bp46unKVpfL8cDPN6OjktgOj0aLcxzJGj2qGshhPJs1xTdvTVMRhGMNPDkF1rvzjfbmRLR1DYfLj7PB8qI1S04Xnzzlt8qLx3EjTRostc4R9G5YvOrqMezwgPiE0ksQPCWtJc+P1/SmV9koMXGX7y6Ej7CVnga8p318ORL43ZdPZ5vP4hf761Z6EnD62/TFmnV5/ntXdJxm+fl2m3qkX7582UpPa/Gzx6/ZU+gfvcrGqdQN82Swo+/ThsjRDY793IP0uAwDA+m3YrnGSKPwc+2v/+bBMpEAWtfsnDLGEqGpG28ScB0lHJs87K/hjtLMEbYaunK9sqyqg6jXPZCpDZsEd0i2J4DdFWWVkKYNYBRw6pAO7AWeYbgUeB68dmmtLpee24GWTSs99KgWFzILBVPQOvk1JiDFnMXQhUjuBHJ5ACEHAF4jzw4vj9HiM35x4TNodeRIr3LWCCP5jgLWplItADfTBUOVMgd5bhuraGRs6wudkeu3DunzdOD+9vOWLNDb4rYN3EifHE0qPh1UAOhVkdaiDEZXWzh//IYG5bhyGmN8JJeaO8dxHGjHjeIN98dMVrye/4TebTrwePvnVor9mn9qAafSSLRukB40hm5Oc4Duci55ZWlW5hjmvd2eo9lZivKD9D4EBxjFYQyo8dZDem8I2+yni4sLLltYxwGnoRFJqnLrm3bukGy2DJBsZziNYjzg/XB8a5w8S2EyVywNfSZSdJ+oPVOJ2TcuEyDLwqv2hphRflxxjO1qk7RoCqnHPdkCJ05wlwI3jBZBpBjQDKxDaxWsDG3tZ2gqhDBFGWcUQ/yMoYnwPiQusebORp5RLa6Ucaq2Oup0Arm2wdpUMDYHnFykDGjcIG3keR84KT2aRXsrw1mGguByn1JBEidlGaT2Q76e96aRccBBm7X/r+JqBM52wDPBFE6n1aQqhGCEiqF13YBmq9u2f5YpnCJU6k7hNMXOwhGihqYvknd3Tk3340zKLa4TucpZ2g5DKxSPrGBb2Tv1aO2H9l2cDNzakNKrEg6vmRN/ROj2XNFNyVLyZ13eVJt/pgHvTjlnGWicZtHf6H2bBihnhCUrzFjKU4mGG3tc3fNNefNm+/H5zquMLr2Yc0RgATHyKK2x6ACQEsF25E7SrLpxyMTQ7Y/O5aMiIsZ1Jgh6TEFFo88maX5+GnDy+1Tgypq0ZpBWDLhPmdkdl5ikuZen+tamosApQDqD9EUgl9GA/HOTpp2iuQ/lVK9ygupJV/dyBPUERI+pK3s64GnfUttYauDohTUDB84oCsoZZAUWu/fxCicA75H38cCJZcJk7ZmiBq65jBwplHWel947nFOQfTTmusau3Za+CcCHM1nIJQW6+LQAQwiStEyIvvUanr3FAEo2zWVzFU4NaSM3slONU3i1+7FJEHXi2lrinSHrbmy9PBm4WKiQI5v3XuTRh7RPNcTugho6og8XAZliFzhRCp0sQztRe6IxhZGNcCmuwU9IkTdVjwfO/hpkh4xXbBXjpkwZZPFaCNUlDxjxqTKGjDX/UQOPUoE3Hm934m6x8cD5b90z7RupMyLDdg9kjgGUM81k2LrFGRGMK41BzDTHcrKpKGlyJKvYcviGQ8ta6G/NdDr/8OEn14r8vrcf50aakFbwmUFLxAlKgulD0tY5097PPRF49+rRwIngvUl7imLobgP/9uywLiaYbvKw0uwkkgEwJuzJA7OIKlhAv0LrTxuNJ65IjAbOXC8fWbtDjN1T+G4HM5tZ9uXuT27uPv7PubvbX7S7u/u0dh9TVySOobVZSpfiYTDpSVp/9Ur55ROn+cdPCg+f0+Hl2uKeqMXDK070RTu3P0ozk3p9oNtDLf00g9PwPC9Je5L4zrR2M3Fsk1+kXyVBcgfKgG8/jnCS5knAlbUd13CWyWikMukxGB64SFujVOh64vmCZPAa5S+ayL8CcAI7PPXzV2zwyR/R86ZnGU4c0qUERRA27XXBEK2XQqEgXdXiLelpbqHLRTYrEr7gZR5sDBaQq4WY99PMQysSI22cafAxZ9OTntuyBzin3JGUSNNccZpSdK63updlL5FFxPaKxFkWNZIfT3FKVmnbibPaM4eEvu21/bVj5qcjvMoWeJcybmy8P3H19rgLBXm07X0lZ5n31rOAh6CiZU5JHAd4B52HEqocLxyxeLWtoEsuaxuXENLAx6jHCul8/cRZ916shPTIS8uAJ5As1evs8bLYan91pSvgSMp0WsDFl5K8WlLRHddAX1uSlSXi/9A4e5aeqGQCOTFnzIgqw68EGyAeIu919hTgvWc2189IjKjH2fWKRBMA2nhexjJYi28LXuQL8D7PfV88HpGtPVTqPVlpg2jQWhgm08NLw5dqHpMltxoumQrvjYahJ3nFcYn9daGqm8paL15p4OiBkwV7iaR+WLoIxoWideqp8iouhk6Prb0Ky+pHCGnG+xsPNyWluF3bCkYB1zj008vJ0PTkf/8UnBZgNUbjou2SJmiWAb3SonXqKYAbinF4WsNVvOkSJ2j2Od5zeUrM2Z1Px+XHTXrY0YDAaaCpOh0o3yLYj0RpiCZv72F4qphzqLJ0EOCNoR7DeK6dSHtzcQrpOBBIO9oDcbYZmD7FisSeUre4Q0qMPMFXcae1QSL4DsErIs/ky4zBdcPNfusViUOV7SCkvddkvShP+7wJjJUMef9Q0nOkrq9uKmkPtWNKpLVdJbhSUpi0H6OdxAQZooU2m//OmSyVvgIAXSVyI29zGzTGIdaa3LYoWV5gCPWqBbUmWQGUokpxgfesFU1v477742TgBFkZzURyLmklPn2oHrBgbd+XjKTD5itxDAJSBBRjETxEnfuy9AMhUK+4p1sD6klPEm8/K+72fa3DSNVjLjK3dzj1poO7vSPZ83SopSuSVX/fhko7TdKowLE9vMC97+H8o8oo4Lf+kzEf45/g5vkncB/D7brtalobvbGLvCx8ASEvdRl9HgZshah9xPGbAfefP2fEfv5M8XDLMnl7KxrAM8qIMBjK0LT4w4jBscEGV1V68dwJZZyRVptfdge+AdAMlln6IoTThsyU8hUGJ5U++rRKGOIRX0N0tOrTpOtgWRlhPCOcdAnONyzfNZA4SfVp0v8Cn676NOl/KvBTy/8BsqneOZjJbOsAAAAASUVORK5CYII="
+			},
+			{
+				"title": "What Is ChatGPT? Everything You Need to Know | TechTarget",
+				"source": {
+					"name": "TechTarget",
+					"link": "https://www.techtarget.com/whatis/definition/ChatGPT"
+				},
+				"original": {
+					"link": "https://cdn.ttgtmedia.com/rms/onlineimages/chatgpt_screenshot-f_mobile.jpg",
+					"height": 252,
+					"width": 559,
+					"size": "12KB"
+				},
+				"thumbnail": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5OjcBCgoKDQwNGg8PGjclHyU3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3N//AABEIAFwAzAMBIgACEQEDEQH/xAAcAAEAAgMBAQEAAAAAAAAAAAAAAwQBAgcIBgX/xABBEAABAgQCBAoGBwkBAAAAAAABAAIDBBESITETFEFRBQciNmF0gaGy0SMkMnGR8AZSVGKSscEVQkNEU3KCouEz/8QAFwEBAQEBAAAAAAAAAAAAAAAAAAEDAv/EABsRAQACAgMAAAAAAAAAAAAAAAABEQJBEyFR/9oADAMBAAIRAxEAPwDmMJzGua6JB0gDiXC4tuFMq7FvEfBdDc2HLOa40tcYpNOynzVTwp2CxkNroLXFuZNRd0Lcz8scpUD/ACd5rpH51rli11cu5fp/tCU+xj8bvNYbPy7X3GXBF1aVNKUySh3LiN5hQutx/EugLn3Es+76DQ3NwBm4/jX3d7t6ipkUN7t6Xu3qD8v6RjhayD+x7rqu0ltnRT2u1X6TdkPRmCHWi68HPsVbhThaHwaIZjMivMS6mjAOWeZG9WNchAML4rWXtDmhxpgrpljEcmUxPfjJbPZNdLUpmWuz+PuWSJ63B0tXH913ZtWNbhUrp2U31CxrsE5TEM7PaCjVmk9X2pbH7rsO9bUm/rQMsqHP5oo9egCtZhmGeIWwnINK6wyn9wQbMbOXN0joFv7wa0+asqmJ2C6lJhhrliMUM7BH8xD/ABBBcRUxOQjSkdhqaDEJrsEgETDCD94ILiKprcM5RmYGhxQTkEkDTsqcsRigtooDEo4NL8TlgjYhcKgmnSEE6KG929L3b0EyKG929L3b0HkDaibUVQQ5IhyQegeJHmFB61H8a+9XwXEjzCg9aj+NfeooiIgrzkjKTwaJyXhxg2tt4rSua0miIdGMjNggMwrCuoAraqzbnAgMix2YZQoV1e4olRdohFLhc2ba4bfQHE9HetdNgQZlhycPVzln8VtpH3O9YmwTiGmBliMsMUue1xrMTZG7QfraisOmDUgTbWkF2BgHGmzsWNMQL9ba3AAky5zph+q2cYrWD1maJBpXQAn4UWXOeORrMy0jC7QZmvu6UGrYjza0zUK53s+rkCtUEyCQTNtpQV9XOa2D3gubrM0SRWur5dyGI4EERpugOWr16fq9iDR8UtFHzjeSaOpLkrbWKHGbZS3CkE49KzV5qNYmwbqD0GXdj71qYrqYzM20DGplqfm1AEY0I1xl2dTLmtKblu1zosQMhTLCa1oYOGGeKwHuMeG3WJoH6pg0DqZ40VxgLWAOcXEZk7UBgcGARHBzt4FFsiICIiAiIg8g7UTaiIIckRB6C4kGk/QGFT7XH8a++scvg+IzmDC63H8a6CoqKxyWOUqIIrHJY5SogiscmjO4KVEEVjkscpUQRWO6Esd0KVEEVjk0blKiCKx3QljlKiCKxyWOUqIIrHJY5SogiscljlKiDx1tRDnmE7QqgidoTtCD0JxGcwYXW4/jXQVz7iN5hQutx/GugqKIiICIiAiIgIiICIiAiIgIiICIiAiIgIiIPIkKNNNhQmQ4QcxsVzmcgGrqCvvw2LcxZkEuMlDx3yopl7lFCZGOjdDjNYXOIb6W20gZ9HvVh8KfcHF06x1uJ9caTls5W7BVEUzGjhphR5aFCNP6AaezBWODvo5wzwnK61wfwfEjy5cWh7XsFSMwAXAn4L858R8SmkiOfTAXEmitS3CvCUnA0EpwhNQIOJ0cOK5ranPAKTelh3jiM5gwqfao/jXQVz7iM5gwutx/GugoCIiAiIgIiICItHutpgTiBgg3RVROA/wY46NEcFnWxWmijYiv/mUFlFrDde0OoRXY4UK2QEREBERAREQEREHkBjZctbpIr2uLzfRlQ1v640UhgyVR6287/Qf9ViRlIMcyjYjT6WK8OIOwB1PyCscOcGS0jLh8APuMcs5Tq4Cvkqj8mO2CxwECI6I2mJcy0hRoiD0JxGcwYXW4/jXQVz7iM5gwutx/GugqKIiINIhOFBXL81m1qpPjRNO5t2AJwoFpp4to5QrX6oQfoWtS1qpse90MOL8SDsHksh78eX3DyQW7Wpa35KqGI8QwQ7Gh2BHOeGe33DyQW7W/JS1vyVUc54pyzj0DyS9+PL7h5ILdrfkpa1VL309vZuHklz6e3/qPJBbtalrVWufaTecxsHktQ593tn4DyQWYnJY4saHOAwBNKrZip3vuIv2DYPJSQXvMQAuqN1AgtIiICIiD/9k="
+			},
+			{
+				"title": "ChatGPT Tutorial - A Crash Course on Chat GPT for Beginners",
+				"source": {
+					"name": "YouTube",
+					"link": "https://m.youtube.com/watch?v=JTxsNm9IdYU"
+				},
+				"original": {
+					"link": "https://i.ytimg.com/vi/JTxsNm9IdYU/maxresdefault.jpg",
+					"height": 720,
+					"width": 1280,
+					"size": "134KB"
+				},
+				"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRIDD6PSH-o5_a4uY4vMZypbGD47mIWLL6VsTXNuADpOw&s"
+			},
+			{
+				"title": "Introducing ChatGPT and Whisper APIs | OpenAI",
+				"source": {
+					"name": "OpenAI",
+					"link": "https://openai.com/index/introducing-chatgpt-and-whisper-apis/"
+				},
+				"original": {
+					"link": "https://images.ctfassets.net/kftzwdyauwt9/44fefabe-41f8-4dbf-d80656c1f876/8dec20d14a894ae52ae07449452a89c5/introducing-chatgpt-and-whisper-apis.jpg?w=3840&q=90&fm=webp",
+					"height": 2048,
+					"width": 2048,
+					"size": "93KB"
+				},
+				"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ_IQLO0924Gl1jYnj0yWaeKwSWj8tbTbk0Jc6cAvQv6A&s"
+			}
+		]
+	},
+	"inline_videos": [
+		{
+			"position": 1,
+			"title": "2 MINUTES AGO: OpenAI Just Released the Most Powerful ...",
+			"link": "https://www.youtube.com/watch?v=7idowVzHZ9g",
+			"source": "YouTube",
+			"channel": "AI Uncovered",
+			"date": "1 day ago",
+			"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5OjcBCgoKDQwNGg8PGjclHyU3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3N//AABEIAFMAlAMBIgACEQEDEQH/xAAbAAABBQEBAAAAAAAAAAAAAAAAAgMEBQYHAf/EADgQAAEDAgQEAwYFBAIDAAAAAAECAwQAEQUGEiETMUFRFCKRBzJSYXGBFiNCobEVwdHwYsIzcpL/xAAZAQADAQEBAAAAAAAAAAAAAAAAAQIDBAX/xAAkEQACAQMEAgIDAAAAAAAAAAAAAQIDERIEFCExQVEyUhMi8P/aAAwDAQACEQMRAD8A4hatfh0cQsneIt+ZNdUb90pNgPpe9ZK1b7GUpZyhggQk6lRUkC3cmrghNmGWfOSefWrPBITsyU220nU4s2HyqHIirYcSh4WVbUodq6N7PILDbfiVutF5z9OoXSKmbxRcVdmsy9lmPEZYWHEeLadQ5ckcwb2+lbDEcNYxXDXI0+CH2lo82koIHzBJqHChx1XdWVKIOrTq2va3L6Vo4jbQQOGAFWt9rk/3NTTlKxFVPLo4U1HeyvjsjBJQUWffjqXbds/4rF5qiJjYw7oACHPMK7D7ZMMUIcHFkjzRpAbWQP0L2/kiuVZqPHahyD7xBSa18CTuZu1FhS7V5ppFCKLUu1FqAG6LVYYThMrFpRYiBsFKdS3HXAhDabgXJPzIHck1rMTyFHjQnHhNfiFh7hKemMlTT3TiflhRaRflr5gg/KkBgrUGpEyK7ClvxJKdD7DimnE3vZSSQR6imKAE2ry1LtXhFIBs0V6edFADlq6zliO3jDGFYVOUlbsRJWCAf/Gi10knqL27WFcq03BHeuhZYmON4iiY2gFC4irrH6VFCwR63/f7VdpoaipQk/KMnmV0KxaQ5YFOvl/anYmHScQjeJRhxaQlN0rYQbk/QG/3t0NTsHjxpmKOonpCm1kix610nL2VsEhJ48dpSjzCFLNqzqSSfJpTi2uCg9mmMTfGjCZTjq9Q1ILir+Ujoe24NXftBxjEIuIogwn5qefkiAhSu+43pgltjPMF5ASHF3SQkAWFdDxHL0PGwhcgLStN9LrStKhfmL9qxU/CNZU18mcMxjHYy8NmwJQnmWWtSHVylqBIIO4OxFUmJL4mFM9w8f4NdazrkXDYOXcZmcZ+TKEVS0LeXq0afNYdq488rVhbXzdP8VvTfZhUK/TRal2pIUg7BSfWtDMTavLU62W9aC4RwyoX35jrWjcdyih2MpuM+pPHCXkOKc0qbsvzAhQOq5QLf8b9TQBGyXLjRcQlNzC1wpEUoSh5zhtqcSpK0BS/07p2PK9r7XrqOJ4uP6a/ILr8JbkNLwmLUXYsY2IToUkFJUoqAIbvq0m4AukczjO5W8JEEqOpuVpWX1qLpa1EHSLBVykG3Ig8ue9lrdym9FRHaW83dbxSTxVcG5c0KUNVjtwvdHRV6kZTZilR5+PYhMhpUGH5C3EahYm5uTbpc3Nul7VWkVqVOZRD7auG4I916kK4vFH5t03OrSRw9tt786J7uTlQJpgNSG5BQkx+KpZso2JSPNbbcEnnsR1FO4jKkV5anPKQSFCw570m6SbBQ9aAGiN6KcKd6KAJATWkyxP4ALDly2pKuXMG19vrVAE1Mgu8BwLtyJP7H/NUibnSM25djYXlmHLw0hxyyH+MOuqx/g03lXGy80QsgHqO1Ly8XsdyO0yErddgFUc6VAHQmykEi4vsSLC/u1jIji4GIutFZa1AlJIvY1nWjdXNKErNlw5GzJiGb+PhkY2bXqQ4SAlKQN73+9dxwkYgzhpVibjKpJUb8H3bdK4DgmISXpC+Ni05p5J8vBZuL9q6rluVOEUlzEVSo36eOyULH71y85HdJJU73K/2sY0uLlp+I0fzpygzf/gd1ftcfeuNSUcOJGa+qj+1bPPuKJxbHUx2llTUYlPy19fSw/esfiBCpFh7qEgCu5RsjznK/JZZAjMSs74IxKbS4yuWnUhYuFWBIB+4FTcbzJmnEyYWMKeELxSboVBS0kEL2GoIB/eq1nCXWsD/AKwh9xp9EkJabQhSV7AHWFdLX5j1vXuIZmx7EovhcQxiZJY1JVw3XCRcG4P2NSuehvjs6NIw/BEe0DNL7GLLdxAwZeuCYJSlv8oXs5qsbbdOtVfs3gt4LlV7Hp0Rh9jEZAiv8ZaUlqELh1xIO58xFwOib1lGW8VfQ7jjeJOmTKSUSFg/mELcDVib7hW//wA2pUjBZk6W1hkueVIgsjhiQkaWWiTewCiAAdI576vlV/ikYvU0le76EJexfImap8GBK4TzTvh1rLSF8RrUFJPmBG40m471Z+1nGsRmZpxLCZD6VQIUq8dkNIToOgdQLn3jzNUU9udMxxqLPmLdl3aYLrhuUGwGn56SbfanJECTPdenYrPUh9xpLzjklPmUpWqyTdQN7I6A/TuKDY3WgvJq8oSX8KyTBk4fIdgmXizzc2XHgJlOBKWroTpIO17epNTnGcUi5rxGevNjjHDwVmW7OXhDalllShZHCuACL3vz6VlMKGOYP4ZvBsYmRfGrQHEtakJBKAvVa5CrJ5nY7WqLMexFx/GXsQxqUuRwgh9RBX4loqSEgkq2SSUm1qX45Aq9N9P+6LjN2bWnxgb+F4orEMXw9x1xeJrw5EfZVtKOHuDbfnUjPWa8bfy5gEdcxJaxTCuJMSGGxxVcQi99O2wHK1Y7FMLXhojFxSjx29YBQBbl2Ub8/lUaRKkSW47ch9x1EZvhspUbhtF76R2FzSxsXGakrogqTvRTpG9FMZKCaUBagKR8Q9a91I+JPrTuSarDJMqB7PsTlwnVtOR57B1oNj5goH/p6VSz8YRinEXKCI8xl0J8SE+RwG+6kjkduY2+laRDkKFk6RgUh1BkyQZMgJWDoUQNCfqEpST8ya5vIbdYWWyUqSuytuRHT6VMmOJ1PJ2cWMJU23MYRu4ga0eZCwVDe49auM953bdirZwIfmqA1vkW0BVx5e58tcnixvCJZlNJIeT5hrKVJ325W++/7VdsxC/lSRjC30lbs9KeFsNKUoI1fcqT6VMUkVKWXJDgWSpxw7htBJJ6kmoqSOMlaxcagVD7082tKYjoCk3UpI59N6Z1J+JPrWhJuH5MdmGHw07LugLLigG0aCCT1KieXasJptT/AImVYNeJVwdGyL7W5elN3T3HrUQp4dl1KmdhPmta5ta1r16tS1qKlrUokWJUq5tXupPcetF09x61pczEWN73N73vQsrWSVrUonmVG96Xt3HrXm3cUXAQVLUEhS1kJFkgqJsOwpNj3PID7U5t3FFh8qLgIVqUEpUtRCRZIJuEj5Ugop2vDSGMFO9FOkb0UcAWwy5F7D0pwZdhgXUAB8xVO5jcsYopDDyXGS6EoSEixF+htepuaMQiKirhNuqL4WNQCdtuhP8AjtXbudPZ2gLF+ycMuQ+iU+lLGXInwj0qtyliDq1Ox5LwLTbadF7DTbaw/wB6VF/EU2JPkFRQ+2XCEpJ8oSD+m3y60bqhZPAMX7L38NxPgHpXv4aiXvoTf6UziuML/orUiEoMyHSk6SQVJTvew/3aoUDNS3ElmWhCDwyEvpvsq3Mj69qb1ND6CxZPcyvFUbpVoPyFM/h0Mq8yEOt9wLEVFy5mFDDLzeJuurULrStR1bW9369qaxnMAnwGjFU5FdS9uhLm5FtjcVD1FFq6hyPFlzDyjPkKKDhz4cYKhJQU2KEb6fryGwvXv4bifCn0qEvM6WUwklhtwuNpU6pKvcv2+fWokrHkRsdXLZU5IZU0EBsnSBy5fz9zRHUU49xuLFlwctRPhT6V5+GYnwj0qnnY6nEXIrjJei8F7zDXcKTtvtbfblTsPNYTPkeMKlRN+EEI8wsdvUd6vc0b/AMWWYyzE+FPpQcsRLck+lZ5OY5cfEX3m3i8yteyHL2032sOhtT+OZi8ayz4B2RH0qPERfSTysbj70t3Qt8B4P2XH4YifCn0rw5Yi/Cn0qtjZpkCE+X20KeSkFoi4B3A3/mohzDNkty9UgMktAtpTtZQKb2PzGo091Q+gsZF0csR+gTSDlljsKoVZhnKVHUXCCz71iQHf/YU/NzC/KhAIcLD6XQfyyRqTY/36UbnTc/oPGXsthldk9vWiq05slAJDbSOQvqvuaKe40n1DGRnlc6DubkkmiivKND0gWrznz3oooAdC1kC61GwsLnpSLUUVohBbY0miipGKVva9JtRRSYhaORpCqKKb6GAFOJSNNeUU4gB3NulJIsdq9opMBFza19qORoopAKtfnRRRQB//9k="
+		},
+		{
+			"position": 2,
+			"title": "OpenAI Secretly Released a NEW ChatGPT Model and It’s ...",
+			"link": "https://www.youtube.com/watch?v=uh4baKXL6K4",
+			"source": "YouTube",
+			"channel": "Unveiling AI News",
+			"date": "21 hours ago",
+			"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5OjcBCgoKDQwNGg8PGjclHyU3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3N//AABEIAFMAlAMBIgACEQEDEQH/xAAbAAABBQEBAAAAAAAAAAAAAAAGAAIDBAUBB//EADYQAAIBAwMCAwYFAwQDAAAAAAECAwAEEQUSIQYxE0FRFCIyYXGBQpGhscEjYnIHFlLwFTOC/8QAGgEAAgMBAQAAAAAAAAAAAAAAAgQAAQMFBv/EACkRAAICAQQBAQgDAAAAAAAAAAABAhEDBBIhMUFRBRMiMnGh0fAzYZH/2gAMAwEAAhEDEQA/APJJDU0EWxdxHvH9KjRd8yqe2c1eVUz75GcE4IOPvVisSuFmnbbCNi+cjfwPOro0SAKpuJp5GYZBDADFMaW4RhIjbSoGCF5+lQzXFyyLJk7QCBxwKzcmNRh6l9NLsYoxzOAT73vZ2/PtWXr+lXFmFmWRJrRvgdDyP8h5VH7dMMNubag4GfOprXVpFlXeVCtw4PIP1qk5BNR8GDSrYu1t0nYQhRGeVB8h6VWZUP4VrQAogkHI71Nv3L+9OdUHkBUR2jtjmoQaeK7mmkj1pZqFDs13NNzXQahKHU4VHkeop4PlUBZIlWI6rqyjuQPvViLnGKsykWVHFKur2pVZhZy0GZ2YjIRSTWtpVq093vlGQVzgeX3qvp8aRl1fksvPzq1FO8NyFQ4JA3DyrOY1iXJr2FkuqTMkUAeNfiHY961LHpcSPiVO3ljgfLFaX+nyNEJGdC2Rgue3/eaML24trRN8rxx57FiBSk2zpQS9Dzu+6QgEp8BAuTnFYOodMojf+sJx5DvXo8+rWk91EltLFK/Jbw2zxig3U+o7GS9KmRs84jA+GghKW7g1lGFcgBrVsbbw0f4kZ0J+QwR+9ZdG3VEcLWceoRHKOwV/kw4/Yj8qGJdqjLDj6U+ujnNU6NnoLwvatT8bZj2Ndu/Hf2iHt9s0Ua9eWqWfU+t6PLFa3630cMscWAVkWRx4ifJ1OT/cG9RQZ07pdvrV7NbyTeAqW7Sh9owCCAM/LmteDo6EK/tbXIljtopHijMakOzOpGWIGBt9aJRbFsmqxY3UnyEUmuy/7n1prvULgW9rokbwtAVLRuy2xYpnjcTnP3pt/NPd2hu+mZJJNYuLG1ZJiEW7kh3TiUjb+PcIgdvO0D50N2PSdpfoZIbmdI1M0bB9pKyqyhF4453Dt9qoDp6NuoItJFwy7YBJcuVzsOzewA88dqm1lLVYm2r67DZm1Q6fqH/iGH+4Rb2IvjbFQxk3T7s44zs8Pd8+/NZ/UGnNrWnXVlosFvc6hFfwyXcdoUCh2tkWRlxxs8UPkjgH5UO2mhadqonOk3VwxS2aURXCqhVwwABb4cEHPB4q1a9IJJqiWlzcPHELOKWWQbW2ySEAKMcEZPf0FTayS1eKN26oKdRmvbq7E3Rc1v4LancnUZEZPDP9TCGX1h2dvw/F51WsrrTLTQ5XvIra4tJNOWKYQKB7rahcAtGDyCBhl+goXTRNOOmNLLJereJdCyaPamwTEHz77cj61Yl6as/EaO3mvGaC8jtZ5niXw2ZmCttI5GNw796m1lPVY7oLbu0a0jjtun7nxtSigsUkn0/aZntNjbmhyfNthODn4c8ZoU6zt1t+oHxKsjSwxSyMqovvlBuyEJXdnvg4zmp7jpbTba7tYLiW+j9puTbojpHuzkAP/gc/WsS/itbe9lhsjMY42KkygAlgSD28qumgI6iGX5RL2pUl7UqgA57hormEICxLDgdznjFalxYXFjOBcJtZuQQQQR9RxWJLKYNQgm4/pujjPyOf4r2a70uG7hezigVLe3gz4oH485GP1/KsMsttHR0+NStmjpaBNJg8GP4YwQvqaFtZ0bVtWc3F47JHvwkCjOU+fzPpRhp06qiKQFAUDAq9K6CMkNxilV6j9eAV6K6Xj0yaW7kQqWO1UfnA+lDvVfS9rFrk0iRYE+JUOcDPmKM11SUQzzRWs08aDEaQ4y7feg3qXqK4lu7eG5szA4ALLkNtPOeRwfKri3douSXTMq/0uReltTjUDaoWYDPYq2Tj7bqCISJYSjeXH2r0m5vFktJYsZSSJlI9cggivOLeMRQ734JH6U1ibcbEsySlwR2t1PYtcCEgGaFoJMjPunGf2rVHU+pOreP7NcBoo4XE8AcMqElcg+eSeay7lOzj71CnB+tapsWljhLmSNW01nUhIbayESe0XUcqwxRhV8RSNoA7AZAqZoeoINUutXMEi3UFxiZ8AgO/G3b55zjAz3rP02ZLXU7O5lzshnSRsDJwGBOKMZurrIxs1tBK0rSxTOGUAOyuvz/4oPvV8eWL5VOM0sWO77/foZl6nU2Utn0+KFLlGtY4YERUGTuYAA+6xxnn0pA9WIY7CGJ4ZGhTa0JVS0cQ2j3wcYGeefOren6tp2mXLPbNdzpc3guJTJGAYwA3A55OW703TtZ03TYI7FDPJbLHPumkt1b3pNoA2ZwVGOcnmpuj6gSxZ1H+L7P9/Jlay+ueDctqEKxo10k8rJt4lKkL2PmM01+qNSkBz7MHdkeSRYFDSshDKWI7nIq9Lf6JPZXdlLczRpNNHKrwWKoAVUgjYDjzHNDLhRIwjJZATtJGCRUYeOEZqpw6/r6FuK+nTUhqAI9oE3jZxxuznt6ZrrStNK8r/E7Fmx6mqi1OlUaOKRaU8V2o1PFKrMqHX8W9NwHbg/SvQen+v9Pj6e8HU5ZE1CKPw8bCRNgYU5H65oGicSrg43eYqrPZ85i7elBKKkuRnHlcHwe0Wd0Lq0hni4EqBgD5ZqxNI7QHc+0fib0FB/RWpSSaH4UwO6zbw2/w/C37j/5oklmM0e2Mggjn6UjOO10dCGTdGyOPVLmS0C2emS+Go90uVTI9QM80Ma1c3nhqj6csabi3vSKzE/nRncQXE1qqRssYxgMPKgnWNN8CUFr1pWPbK4/mih2G5raZssjHTpnlG0tGw257ZGP5oRun3NsXsO/1rf1y4MVuIA2Xc+XoKH1QscDvTkVSOdOVsfF78RB8uKrlMHB8qvbQiY8hVZhuJNEDuGqM4rd6esDeXCxRwmWZyFjT1JrGjWiroy6Wy1a2uJDiNJV8QkZ9wnDfoTS+p+Q7PsRKWouraTr/AAK4Ok7DwzHMZHm8MHxrYo6FyiuFVce8u10y7Oi5YAH1G+qNBGmxLlTtkTfGzxGJxg4Ksp7EEHzI7EE16GIfBgilC3YggYiMowZZv6aoqnbu8b3UXbhV4zuweSLdfT2/hCyhlWV7Zn4TO2BTt/pD1wwY8cDdgUq4qNNHoIZZ5d0Zcpp3x1S+3g8wlXaxFMAqece+ajxXQj0eJytKbo4KlSmAU5aIyZODxXKaDxXagFFgqVOVODU0MhdirDnHepZI6jhikaZfCRnb0UVZjGYWf6fLMdUulWMtAYMSt5Kcjbn68/rRLc2E0Mu62baP+PlTdBiGn9OaaYlCrPPmdh+JyWXB+mBW867/AC7UrlXxD+GXwgdf6xqULm3KhPQt/FDmpXM+4vPNub5Uaa1bvcTLGF4HnjtWVL02kRFxqHNuoyEB5kPkPp61UOXQU5cGHf29vP0/psdxhblzLIr+ag7cZ+WAPzoc9me3ciZcN5fT5Vt6tO1xfAD4IxgAeX/eKsxFvZ9pOR5ZFN7aQlKdvgF5Pe4HambKJ1MUhxcW0cnzK811tL0+Ye4skJ9VbI/I1e0H3gMhMCrNrO8DZU/WtWTQZMZt545f7T7pP8frWdNZzQNtmiZD/cKCcFJUzbBrJ4JqcHTLttrl5a7/AGWeSHeMP4Tsu4ehweapXN7JLHs4C/Ko/CrnhVktPBeDo5PbuqyRcXLvsqMuTmm7KuGKmGOtqOX7y3ZV20sVYMdMK1CbhlKnYpVC7N5UV5kVhkFgDW94McERSJAi/KlSrSBzW+At6ekaPpU7Dx4xGCM8cetasBy7JxtC9sV2lWWXyO4W6RRviVKbSRuYg4rI1QYgcc/F60qVTH0iZHywQMaeOfdHxH96vxRJ4ZG3ilSrfwJ2yMxpub3R3rqoo8qVKrBseow3HFTFiwKNyvoe1KlVA2Dd2ipcyoi4UOQBUJA9KVKszdDSBTGA9KVKqDRGwFRMBSpVDSJGaVKlUCP/2Q=="
+		},
+		{
+			"position": 3,
+			"title": "OpenAI's ChatGPT Does Research… And Breaks Itself!",
+			"link": "https://www.youtube.com/watch?v=iC-wRBsAhEs",
+			"source": "YouTube",
+			"channel": "Two Minute Papers",
+			"date": "2 days ago",
+			"image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5OjcBCgoKDQwNGg8PGjclHyU3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3N//AABEIAFMAlAMBIgACEQEDEQH/xAAcAAACAgMBAQAAAAAAAAAAAAAEBQMGAAECBwj/xAA+EAACAQMCBAMEBwYEBwAAAAABAgMABBEFEgYTITEiQVFhcZGhBxSBkrHB0RUjMjNCYhZScoNzgpOiwuHw/8QAGwEAAgMBAQEAAAAAAAAAAAAAAgQBAwUGAAf/xAAsEQACAgEDAgUDBAMAAAAAAAABAgADEQQhMRJBBRMiUZEUMkJxgcHRFVJh/9oADAMBAAIRAxEAPwCsoGA7CpEVicEYqYReA49KVcLcNrrPCfEGry313HNpUIeKKMjbIdpOGz18vKsjT1+bnBjdjdMtUMMkSqGBHTvW74MY+/lVd/w8q/R6eJWu7s3AvBbC3HVSCwGfXPWrA3AGkW93ZaJrHEl1bcQ3kQaOKGLfDGTnCsfPqCO65x5dK16mIG8TdczvQI2+uwHPXePxr2b93PbmOTBBGCDXz7BwVcpofEk8t3cftfQ7pYjbKw5cqHb4s9+oLEe4e2rZN9HPI4n03Sm1O75F1Zyzyy5GVaMqGA8sZdPnRs+YKpiWXVtOOnz+XKY+FvyoWMBuxBrx+4eKaRooruZld2EZzk7euCfsq2fQ8qniG7gvGaWPl7cFjjIJpivXZPSRFtR4KyoLQdj+/wA+0vG01mKf6lovKHNtQzIe69ytJmjwadSxXGRMW2l6mw0hxmtFaLtrdp5UjUdWOBTC80KWFC8TCVQOuO4+yvNYqnBM8tLupZRsIjK1rFTvESpA6Ejv6VHBbmONUZy5H9R7mizA6RjOd5xtruAYnjPtphbaXc3C7ooyV9ewqW60e4swkj7WXIyVPagNicZlqUWfdjaJp9Eiutd+v3CgrGPAD6+tb4k1KTTNNkngTc4GAPStcR65HprxQIMzSMAB6VDxbHv0RjjJIpGbQE8gvbu6vbl7iZ2Z2PU5rKbQaHcSJvOEyegNZVfmCNDT2EcRqQRG3THTzrX0Za/HofB3FTRahb2uptEjWSSOm+RwrY2q38XXHTBqG6iaUjaxBrjTuFrSTxSRgBcE4JGBkD19SKxabtPRX1AnJ5EZdLXbBG0eanxZLq/0Y51TUrafWY9RR1gYokhVXBB2Ljp7cUz1OXhzX+KtJ4vbiXT7O2gWKSezuJAs4eMlgoXueuB09Omc1ltwTojACeBnX/iMCPnUXEXAeiW1gs1skmSem5yfzprS6qu84QyqyspzBuCOL9MvvpE4ludQnhtdM1eIFGupBGDyiqoDnzKljin/APjmwuOHOIbuW7t11K2kvINPRpVDyxvgoUHcjO0f8tU3ROEdOur6GKRDtZgDgn9avMf0b8PyZ2hmwcEiRunzp0iUg5nhVpLy4Z9jpGyrtGe/2Vc/onVjq0rLnwqCce+rDrv0aWGm2s10h3JuyAScgfGj/o90e00m4Nyi/wA4FCMk9B1qAMHMse4sip2E9UiYMoHspRqWlGW6VoEAEn8XoDTOGZWAx06VKTt3OWJU+XpRq7IciL2VrauGgtjp0VtGAUVpAc78daG1KeWyYcoArID38jRSXqOTtIwDjrQOsTLM0caEHbknFWVgs+WlVxWuohNojZMmu7eyluZOXCwVz2LdhT2z0pOWHuM5P9PpRjCG1DTOyJEo6ZAG2rrNSMECK0aJshmk6BYkVFwAowBVY4u1vkW4SDDAyBCR65pPf65Nc67Fy5GWFiVCA9MVH9VF1ZOs7MMTluvc9aVTB3mlapT0mL+INJm1HVLV41GEALOewqXiC4/dxwA5XHWmd3eRW9vzLqQQxAeZ6mkvEKtfaUt1YMAAuV6dxUvkjaeoKq4LcRI80aNhnUH0JrKFsNKjltw902+UnqSa3VPkmOHXL7SRIx6L8abaNbm6uWtBtDTxMiEt/WPEv/copWq0fpzPFdRSRna6OGU+hHauRFgVgTxHCpIwJcIbO7EEcvIZkKg7o/EPlQPFFyg0+ODcOZ32nvTqRJGiGqaWziKYlpokPWN/6unpnv8AHsaovFcg1K/2uxklzgsp6gYrV0CJTewGcYz+o/5FrA1qiTcMZXVLYv0BbOTR1ldS2XE9xcLeMbOSQl0HUA1XdOE1ruVnZtgO0kYrvS5S9vnPdjWh4heaVDVyjTVdZw0vGp6iNZ0e4hiULMpyFJ/iANc8LoW0tjJECgYjcO6mkulIHSUOzYC5yKN0DUZYbF4IlyGDsW9Kq0Ore/qDdpOopFeMRsutpHNyUJO04z61ZBzJbH9ywLMK8hvtTa2k3Rgc1mPX0x7Ptoix4y1G16R3BA8wUU/lW/VpLLUDjAmLdrK6XKEEz0G4tZosGQEZ86YaZZhBzpMEnsPSqHHx9dSJsnWCQepUg01suNkmIh2pGzdh5H7aK3T3hNx8Sqi/TeZnJ/eXOWQL2NI+KZkfTo0H8bSqo9maHXVZZbWWdXQFThapWq8Wm6cxiIPErZVm7k+uB+FZ1hRB6pvaSi3UP6BxC9VENvqtkkJBKthqMvbyU21x9RTmXCHaAe2aqh1pWkDmJd4OQdgPWi4OIShYgJ4jlspjPwxVH1AAOOZoWeE3MROuKbeXUbG3iJHPQeMZ86MtbiKLRYrORxzQgXHqaBF8L7fJtCndggUs1SV0MLIcESDrSFPiFxvFbgRW/RisEdxGdlp11HEw8IBckVlF20VzLCr81uvsrdboVscTJNiA4zFSrR1iv75ffXSabJ5zQfeo20shHKpe5gAz18VfPnbI2nRgiHaNJe6Jqtzewy8+yuSGmsyMMGAxuU9s4H2/CncuicM8Vs95akC5U4kltn2SI396+vvFA7LVQCL2A59DVTvOFbqbWZtR0/VY7WRmyssUpRx7Mg9q1PD9a6Dy7/tHErKKW6kbBno0PDEMdtyJZ+euMBpIxux7SK6veE9Lu4QDFyJtoBmgwhJ9SOxqoWtxxdZLg8T2Nwo7C5twxH2rg06i4zGnaWv7Xlhu9SZ2CR2cbKrjyJznHfv8q2k1Wnt2BidiWA5JkUnD11o9vcyB4bqLYcu3gZR7c9PnVNs9WSxRWMmN2cAdc05u3uOIWMuuXciw947OBSEX0z6n2n5UHJw/ZyQLAMyRjtvGCPdij0aV/UM2DgxfUs/lYU7yr3V8PrsV2kUdyEYOYpBhX8WSp99LxdiSWRnEULu7PyUbpGCchR7BU+pwJZX8tpHnbEdoyaWT2MU8xlfuRg104BrQPWM7cTAHTY5S04yeYwa4WJeY4LIvVgPTzoyO8trnUTLpqTJaGQ8kTEb9oHnjp3zSK1sFguOaHOAMBc9Kc6ZY3uoTtHpygyxpv6vtwMgd/tosu6l29OxGIJWut+hfVvnP8S5QXnL0udGbxLE7Y+w1Q4tReKOa1a0jdZihW5Y9YsZyB78j/wC7WzUdOudL0UXWo9biX9zhD4PFkDr7B1qiXEIubZomOM4wfSuauyjYadr4anmVFlz2Px2jBXBqVtTZbb9nJYRs8kyyC7OdyoO6+mO/x92EA019uFxn/V0pnYQ/VLZYt24jJJ9tL4VN+Zq5t1GFYFcb5jvT54khZGciRjkD5U5fTrazjWfVG3OOqQg/jQGi6aZPq93G8Ql7KJUYqDuPXtim1zw5f3UjSS39m7HvmQ/pTWi0ulqIv1DAE8DM5rxvVW23NVplO3J/qJ7nWbhpTym5aDoFXyrKYHg6+JyLmz/6v/qsrc/yWk7WCcv9Bqf9DIFiizjmZPoJP0FbFuFPikY+zdj8qiAkJG4x7fRutTJjPdcegFYf0tA/AfE6HzXP5TsRR46Nj/cP6VJEFVsB8/7proSZx5++o3uYkYBpcN5KDk/CpXT0k7IPieZ3A5hgAVclEHtbLH8K6DIXGAGI7ELj8qEWbf8A1Ig9XcE/AfrU8EqIMfWQfbuA/CmlRV4EoLE8mGb2QFXTDAdtrAn4ipYH65xj4n8qCNzbxRl5LhFUf31pBcXy5PMt7U+WcSSe/wDyj5+6jGYBlf46hW/vbc2c9tzVBSYlwpU9MFie/u7+yj14e4alRQuozxuAASkwIJ9eqmpLqwh3AJDGqr2AUYFaSIKOiL92rQ7gYBlfQh3Ilf4s0y20W3gmsL+S65jlWVkHhGM56fpV14M02Cx0sTrMr3N3GjSElSE89o6+3vVe1eFXtJGZBlFJU47HFPOHbmOK2RRNGqFQR46lrLCvSTBFVYOQI/nEF5by2tzGGjOC8UoDKfQjyPXzrznjPSorLUrZ7KLlQTsBJyznDZ9M4GR28ulegyXcIjyLqLPoZBVT150uG8XLnRTnYJOo/wBPX5UtZX1jGI7pb2pfqBixdA0/cR+15VHkeUrf+QpNJbNFxCNNacywlwFmRcFlI/iAP2/A07i0+3u4edbeNB0JBOVPoR5VCdHCSiQSOHHY7u1UNQvtNGrxG8cvmW+wuY7GC3t4lVoIlIwRhjn1JyO/WiP2hFv6xgIR1zhjn7CtVmOJSuHLFvNg5U/EVJh0OUmyP8snX5j881eACMETMbqzkneWNmil8aSIinyMTfqfxrKrpuCOhRyf7WUj5kfhWVHlV+0jqb3gcSgkEjJ9tGRAZrVZQjeGdpxIeZLy36p3xUn1eA4zDGfeorVZRn7oH4yaO3gHaGP7ooqG3hJ6wx/dFarKsEAwJYo59YZZY1IhGY8Ljb8KblfD/E/3zWVlT3g9oDcIM92++aFaMerfeNZWUcAxTrWVMIVnAOcgMetXDSUURphQOg8qysqPeePaNJQNnYUjvANx6DvWVlRCEWrBENVs5OWpYzJnIyG6+Y7H7adcb6bZWuqMtvbRxqUDEKOmTWVlVNzGElZjgi3fy0+FF/V4cfyY/uitVleWFZODbw5/kx/dFZWVlFKp/9k="
+		}
+	],
+	"inline_videos_more_link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&tbm=vid&q=chatgpt&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQ8ccDegQIIhAH",
+	"related_searches": [
+		{
+			"query": "ChatGPT login",
+			"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=ChatGPT+login&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQ1QJ6BAhUEAE"
+		},
+		{
+			"query": "ChatGPT free",
+			"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=ChatGPT+free&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQ1QJ6BAhXEAE"
+		},
+		{
+			"query": "ChatGPT 4",
+			"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=ChatGPT+4&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQ1QJ6BAhREAE"
+		},
+		{
+			"query": "ChatGPT app",
+			"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=ChatGPT+app&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQ1QJ6BAhQEAE"
+		},
+		{
+			"query": "ChatGPT download",
+			"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=ChatGPT+download&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQ1QJ6BAhPEAE"
+		},
+		{
+			"query": "ChatGPT OpenAI",
+			"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=ChatGPT+OpenAI&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQ1QJ6BAhOEAE"
+		},
+		{
+			"query": "ChatGPT website",
+			"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=ChatGPT+website&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQ1QJ6BAhVEAE"
+		},
+		{
+			"query": "ChatGPT free online",
+			"link": "https://www.google.com/search?sca_esv=acb05f42373aaad6&gl=us&hl=en&q=ChatGPT+free+online&sa=X&ved=2ahUKEwi17_rnppWIAxX2rokEHfAoEzYQ1QJ6BAhWEAE"
+		}
+	],
+	"pagination": {
+		"current": 1,
+		"next": "https://www.google.com/search?q=chatgpt&oq=chatgpt&gl=us&hl=en&start=10&ie=UTF-8"
+	}
+}
diff --git a/backend/open_webui/apps/retrieval/web/testdata/searxng.json b/backend/open_webui/apps/retrieval/web/testdata/searxng.json
new file mode 100644
index 0000000000000000000000000000000000000000..0e6952baa807842cf130bd0232eab6fe55f1ffba
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/testdata/searxng.json
@@ -0,0 +1,476 @@
+{
+	"query": "python",
+	"number_of_results": 116000000,
+	"results": [
+		{
+			"url": "https://www.python.org/",
+			"title": "Welcome to Python.org",
+			"content": "Python is a versatile and powerful language that lets you work quickly and integrate systems more effectively. Learn how to get started, download the latest version, access documentation, find jobs, and join the Python community.",
+			"engine": "bing",
+			"parsed_url": ["https", "www.python.org", "/", "", "", ""],
+			"template": "default.html",
+			"engines": ["bing", "qwant", "duckduckgo"],
+			"positions": [1, 1, 1],
+			"score": 9.0,
+			"category": "general"
+		},
+		{
+			"url": "https://wiki.nerdvpn.de/wiki/Python_(programming_language)",
+			"title": "Python (programming language) - Wikipedia",
+			"content": "Python is a high-level, general-purpose programming language. Its design philosophy emphasizes code readability with the use of significant indentation. Python is dynamically typed and garbage-collected. It supports multiple programming paradigms, including structured (particularly procedural), object-oriented and functional programming.",
+			"engine": "bing",
+			"parsed_url": ["https", "wiki.nerdvpn.de", "/wiki/Python_(programming_language)", "", "", ""],
+			"template": "default.html",
+			"engines": ["bing", "qwant", "duckduckgo"],
+			"positions": [4, 3, 2],
+			"score": 3.25,
+			"category": "general"
+		},
+		{
+			"url": "https://docs.python.org/3/tutorial/index.html",
+			"title": "The Python Tutorial \u2014 Python 3.12.3 documentation",
+			"content": "3 days ago \u00b7 Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a simple but effective approach to object-oriented programming. Python\u2019s elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many \u2026",
+			"engine": "bing",
+			"parsed_url": ["https", "docs.python.org", "/3/tutorial/index.html", "", "", ""],
+			"template": "default.html",
+			"engines": ["bing", "qwant", "duckduckgo"],
+			"positions": [5, 5, 3],
+			"score": 2.2,
+			"category": "general"
+		},
+		{
+			"url": "https://www.python.org/downloads/",
+			"title": "Download Python | Python.org",
+			"content": "Python is a popular programming language for various purposes. Find the latest version of Python for different operating systems, download release notes, and learn about the development process.",
+			"engine": "bing",
+			"parsed_url": ["https", "www.python.org", "/downloads/", "", "", ""],
+			"template": "default.html",
+			"engines": ["bing", "duckduckgo"],
+			"positions": [2, 2],
+			"score": 2.0,
+			"category": "general"
+		},
+		{
+			"url": "https://www.python.org/about/gettingstarted/",
+			"title": "Python For Beginners | Python.org",
+			"content": "Learn the basics of Python, a popular and easy-to-use programming language, from installing it to using it for various purposes. Find out how to access online documentation, tutorials, books, code samples, and more resources to help you get started with Python.",
+			"engine": "bing",
+			"parsed_url": ["https", "www.python.org", "/about/gettingstarted/", "", "", ""],
+			"template": "default.html",
+			"engines": ["bing", "qwant", "duckduckgo"],
+			"positions": [9, 4, 4],
+			"score": 1.8333333333333333,
+			"category": "general"
+		},
+		{
+			"url": "https://www.python.org/shell/",
+			"title": "Welcome to Python.org",
+			"content": "Python is a versatile and easy-to-use programming language that lets you work quickly. Learn more about Python, download the latest version, access documentation, find jobs, and join the community.",
+			"engine": "bing",
+			"parsed_url": ["https", "www.python.org", "/shell/", "", "", ""],
+			"template": "default.html",
+			"engines": ["bing", "qwant", "duckduckgo"],
+			"positions": [3, 10, 8],
+			"score": 1.675,
+			"category": "general"
+		},
+		{
+			"url": "https://realpython.com/",
+			"title": "Python Tutorials \u2013 Real Python",
+			"content": "Real Python offers comprehensive and up-to-date tutorials, books, and courses for Python developers of all skill levels. Whether you want to learn Python basics, web development, data science, machine learning, or more, you can find clear and practical guides and code examples here.",
+			"engine": "bing",
+			"parsed_url": ["https", "realpython.com", "/", "", "", ""],
+			"template": "default.html",
+			"engines": ["bing", "qwant", "duckduckgo"],
+			"positions": [6, 6, 5],
+			"score": 1.6,
+			"category": "general"
+		},
+		{
+			"url": "https://wiki.nerdvpn.de/wiki/Python",
+			"title": "Python",
+			"content": "Topics referred to by the same term",
+			"engine": "wikipedia",
+			"parsed_url": ["https", "wiki.nerdvpn.de", "/wiki/Python", "", "", ""],
+			"template": "default.html",
+			"engines": ["wikipedia"],
+			"positions": [1],
+			"score": 1.0,
+			"category": "general"
+		},
+		{
+			"title": "Online Python - IDE, Editor, Compiler, Interpreter",
+			"content": "Online Python IDE is a free online tool that lets you write, execute, and share Python code in the web browser. Learn about Python, its features, and its popularity as a general-purpose programming language for web development, data science, and more.",
+			"url": "https://www.online-python.com/",
+			"engine": "duckduckgo",
+			"parsed_url": ["https", "www.online-python.com", "/", "", "", ""],
+			"template": "default.html",
+			"engines": ["qwant", "duckduckgo"],
+			"positions": [8, 6],
+			"score": 0.5833333333333333,
+			"category": "general"
+		},
+		{
+			"url": "https://micropython.org/",
+			"title": "MicroPython - Python for microcontrollers",
+			"content": "MicroPython is a full Python compiler and runtime that runs on the bare-metal. You get an interactive prompt (the REPL) to execute commands immediately, along ...",
+			"img_src": null,
+			"engine": "google",
+			"parsed_url": ["https", "micropython.org", "/", "", "", ""],
+			"template": "default.html",
+			"engines": ["google"],
+			"positions": [1],
+			"score": 1.0,
+			"category": "general"
+		},
+		{
+			"url": "https://dictionary.cambridge.org/uk/dictionary/english/python",
+			"title": "PYTHON | \u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0432 \u0430\u043d\u0433\u043b\u0456\u0439\u0441\u044c\u043a\u0456\u0439 \u043c\u043e\u0432\u0456 - Cambridge Dictionary",
+			"content": "Apr 17, 2024 \u2014 \u0412\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f PYTHON: 1. a very large snake that kills animals for food by wrapping itself around them and crushing them\u2026. \u0414\u0456\u0437\u043d\u0430\u0439\u0442\u0435\u0441\u044f \u0431\u0456\u043b\u044c\u0448\u0435.",
+			"img_src": null,
+			"engine": "google",
+			"parsed_url": [
+				"https",
+				"dictionary.cambridge.org",
+				"/uk/dictionary/english/python",
+				"",
+				"",
+				""
+			],
+			"template": "default.html",
+			"engines": ["google"],
+			"positions": [2],
+			"score": 0.5,
+			"category": "general"
+		},
+		{
+			"url": "https://www.codetoday.co.uk/code",
+			"title": "Web-based Python Editor (with Turtle graphics)",
+			"content": "Quick way of starting to write Python code, including drawing with Turtle, provided by CodeToday using Trinket.io Ideal for young children to start ...",
+			"img_src": null,
+			"engine": "google",
+			"parsed_url": ["https", "www.codetoday.co.uk", "/code", "", "", ""],
+			"template": "default.html",
+			"engines": ["google"],
+			"positions": [3],
+			"score": 0.3333333333333333,
+			"category": "general"
+		},
+		{
+			"url": "https://snapcraft.io/docs/python-plugin",
+			"title": "The python plugin | Snapcraft documentation",
+			"content": "The python plugin can be used by either Python 2 or Python 3 based parts using a setup.py script for building the project, or using a package published to ...",
+			"img_src": null,
+			"engine": "google",
+			"parsed_url": ["https", "snapcraft.io", "/docs/python-plugin", "", "", ""],
+			"template": "default.html",
+			"engines": ["google"],
+			"positions": [4],
+			"score": 0.25,
+			"category": "general"
+		},
+		{
+			"url": "https://www.developer-tech.com/categories/developer-languages/developer-languages-python/",
+			"title": "Latest Python Developer News",
+			"content": "Python's status as the primary language for AI and machine learning projects, from its extensive data-handling capabilities to its flexibility and ...",
+			"img_src": null,
+			"engine": "google",
+			"parsed_url": [
+				"https",
+				"www.developer-tech.com",
+				"/categories/developer-languages/developer-languages-python/",
+				"",
+				"",
+				""
+			],
+			"template": "default.html",
+			"engines": ["google"],
+			"positions": [5],
+			"score": 0.2,
+			"category": "general"
+		},
+		{
+			"url": "https://subjectguides.york.ac.uk/coding/python",
+			"title": "Coding: a Practical Guide - Python - Subject Guides",
+			"content": "Python is a coding language used for a wide range of things, including working with data, building systems and software, and even creating games.",
+			"img_src": null,
+			"engine": "google",
+			"parsed_url": ["https", "subjectguides.york.ac.uk", "/coding/python", "", "", ""],
+			"template": "default.html",
+			"engines": ["google"],
+			"positions": [6],
+			"score": 0.16666666666666666,
+			"category": "general"
+		},
+		{
+			"url": "https://hub.salford.ac.uk/psytech/python/getting-started-python/",
+			"title": "Getting Started - Python - Salford PsyTech Home - The Hub",
+			"content": "Python in itself is a very friendly programming language, when we get to grips with writing code, once you grasp the logic, it will become very intuitive.",
+			"img_src": null,
+			"engine": "google",
+			"parsed_url": [
+				"https",
+				"hub.salford.ac.uk",
+				"/psytech/python/getting-started-python/",
+				"",
+				"",
+				""
+			],
+			"template": "default.html",
+			"engines": ["google"],
+			"positions": [7],
+			"score": 0.14285714285714285,
+			"category": "general"
+		},
+		{
+			"url": "https://snapcraft.io/docs/python-apps",
+			"title": "Python apps | Snapcraft documentation",
+			"content": "Snapcraft can be used to package and distribute Python applications in a way that enables convenient installation by users. The process of creating a snap ...",
+			"img_src": null,
+			"engine": "google",
+			"parsed_url": ["https", "snapcraft.io", "/docs/python-apps", "", "", ""],
+			"template": "default.html",
+			"engines": ["google"],
+			"positions": [8],
+			"score": 0.125,
+			"category": "general"
+		},
+		{
+			"url": "https://anvil.works/",
+			"title": "Anvil | Build Web Apps with Nothing but Python",
+			"content": "Anvil is a free Python-based drag-and-drop web app builder.\u200eSign Up \u00b7 \u200eSign in \u00b7 \u200ePricing \u00b7 \u200eForum",
+			"img_src": null,
+			"engine": "google",
+			"parsed_url": ["https", "anvil.works", "/", "", "", ""],
+			"template": "default.html",
+			"engines": ["google"],
+			"positions": [9],
+			"score": 0.1111111111111111,
+			"category": "general"
+		},
+		{
+			"url": "https://docs.python.org/",
+			"title": "Python 3.12.3 documentation",
+			"content": "3 days ago \u00b7 This is the official documentation for Python 3.12.3. Documentation sections: What's new in Python 3.12? Or all \"What's new\" documents since Python 2.0. Tutorial. Start here: a tour of Python's syntax and features. Library reference. Standard library and builtins. Language reference.",
+			"engine": "bing",
+			"parsed_url": ["https", "docs.python.org", "/", "", "", ""],
+			"template": "default.html",
+			"engines": ["bing", "duckduckgo"],
+			"positions": [7, 13],
+			"score": 0.43956043956043955,
+			"category": "general"
+		},
+		{
+			"title": "How to Use Python: Your First Steps - Real Python",
+			"content": "Learn the basics of Python syntax, installation, error handling, and more in this tutorial. You'll also code your first Python program and test your knowledge with a quiz.",
+			"url": "https://realpython.com/python-first-steps/",
+			"engine": "duckduckgo",
+			"parsed_url": ["https", "realpython.com", "/python-first-steps/", "", "", ""],
+			"template": "default.html",
+			"engines": ["qwant", "duckduckgo"],
+			"positions": [14, 7],
+			"score": 0.42857142857142855,
+			"category": "general"
+		},
+		{
+			"title": "The Python Tutorial \u2014 Python 3.11.8 documentation",
+			"content": "This tutorial introduces the reader informally to the basic concepts and features of the Python language and system. It helps to have a Python interpreter handy for hands-on experience, but all examples are self-contained, so the tutorial can be read off-line as well. For a description of standard objects and modules, see The Python Standard ...",
+			"url": "https://docs.python.org/3.11/tutorial/",
+			"engine": "duckduckgo",
+			"parsed_url": ["https", "docs.python.org", "/3.11/tutorial/", "", "", ""],
+			"template": "default.html",
+			"engines": ["duckduckgo"],
+			"positions": [7],
+			"score": 0.14285714285714285,
+			"category": "general"
+		},
+		{
+			"url": "https://realpython.com/python-introduction/",
+			"title": "Introduction to Python 3 \u2013 Real Python",
+			"content": "Python programming language, including a brief history of the development of Python and reasons why you might select Python as your language of choice.",
+			"engine": "bing",
+			"parsed_url": ["https", "realpython.com", "/python-introduction/", "", "", ""],
+			"template": "default.html",
+			"engines": ["bing"],
+			"positions": [8],
+			"score": 0.125,
+			"category": "general"
+		},
+		{
+			"title": "Our Documentation | Python.org",
+			"content": "Find online or download Python's documentation, tutorials, and guides for beginners and advanced users. Learn how to port from Python 2 to Python 3, contribute to Python, and access Python videos and books.",
+			"url": "https://www.python.org/doc/",
+			"engine": "duckduckgo",
+			"parsed_url": ["https", "www.python.org", "/doc/", "", "", ""],
+			"template": "default.html",
+			"engines": ["duckduckgo"],
+			"positions": [9],
+			"score": 0.1111111111111111,
+			"category": "general"
+		},
+		{
+			"title": "Welcome to Python.org",
+			"url": "http://www.get-python.org/shell/",
+			"content": "The mission of the Python Software Foundation is to promote, protect, and advance the Python programming language, and to support and facilitate the growth of a diverse and international community of Python programmers. Learn more. Become a Member Donate to the PSF.",
+			"engine": "qwant",
+			"parsed_url": ["http", "www.get-python.org", "/shell/", "", "", ""],
+			"template": "default.html",
+			"engines": ["qwant"],
+			"positions": [9],
+			"score": 0.1111111111111111,
+			"category": "general"
+		},
+		{
+			"title": "About Python\u2122 | Python.org",
+			"content": "Python is a powerful, fast, and versatile programming language that runs on various platforms and is easy to learn. Learn how to get started, explore the applications, and join the community of Python programmers and users.",
+			"url": "https://www.python.org/about/",
+			"engine": "duckduckgo",
+			"parsed_url": ["https", "www.python.org", "/about/", "", "", ""],
+			"template": "default.html",
+			"engines": ["duckduckgo"],
+			"positions": [11],
+			"score": 0.09090909090909091,
+			"category": "general"
+		},
+		{
+			"title": "Online Python Compiler (Interpreter) - Programiz",
+			"content": "Write and run Python code using this online tool. You can use Python Shell like IDLE, and take inputs from the user in our Python compiler.",
+			"url": "https://www.programiz.com/python-programming/online-compiler/",
+			"engine": "duckduckgo",
+			"parsed_url": [
+				"https",
+				"www.programiz.com",
+				"/python-programming/online-compiler/",
+				"",
+				"",
+				""
+			],
+			"template": "default.html",
+			"engines": ["duckduckgo"],
+			"positions": [12],
+			"score": 0.08333333333333333,
+			"category": "general"
+		},
+		{
+			"title": "Welcome to Python.org",
+			"content": "Python is a versatile and powerful language that lets you work quickly and integrate systems more effectively. Download the latest version, read the documentation, find jobs, events, success stories, and more on Python.org.",
+			"url": "https://www.python.org/?downloads",
+			"engine": "duckduckgo",
+			"parsed_url": ["https", "www.python.org", "/", "", "downloads", ""],
+			"template": "default.html",
+			"engines": ["duckduckgo"],
+			"positions": [15],
+			"score": 0.06666666666666667,
+			"category": "general"
+		},
+		{
+			"url": "https://www.matillion.com/blog/the-importance-of-python-and-its-growing-influence-on-data-productivty-a-matillion-perspective",
+			"title": "The Importance of Python and its Growing Influence on ...",
+			"content": "Jan 30, 2024 \u2014 The synergy of low-code functionality with Python's versatility empowers data professionals to orchestrate complex transformations seamlessly.",
+			"img_src": null,
+			"engine": "google",
+			"parsed_url": [
+				"https",
+				"www.matillion.com",
+				"/blog/the-importance-of-python-and-its-growing-influence-on-data-productivty-a-matillion-perspective",
+				"",
+				"",
+				""
+			],
+			"template": "default.html",
+			"engines": ["google"],
+			"positions": [10],
+			"score": 0.1,
+			"category": "general"
+		},
+		{
+			"title": "BeginnersGuide - Python Wiki",
+			"content": "This is the program that reads Python programs and carries out their instructions; you need it before you can do any Python programming. Mac and Linux distributions may include an outdated version of Python (Python 2), but you should install an updated one (Python 3). See BeginnersGuide/Download for instructions to download the correct version ...",
+			"url": "https://wiki.python.org/moin/BeginnersGuide",
+			"engine": "duckduckgo",
+			"parsed_url": ["https", "wiki.python.org", "/moin/BeginnersGuide", "", "", ""],
+			"template": "default.html",
+			"engines": ["duckduckgo"],
+			"positions": [16],
+			"score": 0.0625,
+			"category": "general"
+		},
+		{
+			"title": "Learn Python - Free Interactive Python Tutorial",
+			"content": "Learn Python from scratch or improve your skills with this website that offers tutorials, exercises, tests and certification. Explore topics such as basics, data science, advanced features and more with DataCamp.",
+			"url": "https://www.learnpython.org/",
+			"engine": "duckduckgo",
+			"parsed_url": ["https", "www.learnpython.org", "/", "", "", ""],
+			"template": "default.html",
+			"engines": ["duckduckgo"],
+			"positions": [17],
+			"score": 0.058823529411764705,
+			"category": "general"
+		}
+	],
+	"answers": [],
+	"corrections": [],
+	"infoboxes": [
+		{
+			"infobox": "Python",
+			"id": "https://en.wikipedia.org/wiki/Python_(programming_language)",
+			"content": "general-purpose programming language",
+			"img_src": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/.PY_file_recreation.png/500px-.PY_file_recreation.png",
+			"urls": [
+				{
+					"title": "Official website",
+					"url": "https://www.python.org/",
+					"official": true
+				},
+				{
+					"title": "Wikipedia (en)",
+					"url": "https://en.wikipedia.org/wiki/Python_(programming_language)"
+				},
+				{
+					"title": "Wikidata",
+					"url": "http://www.wikidata.org/entity/Q28865"
+				}
+			],
+			"attributes": [
+				{
+					"label": "Inception",
+					"value": "Wednesday, February 20, 1991",
+					"entity": "P571"
+				},
+				{
+					"label": "Developer",
+					"value": "Python Software Foundation, Guido van Rossum",
+					"entity": "P178"
+				},
+				{
+					"label": "Copyright license",
+					"value": "Python Software Foundation License",
+					"entity": "P275"
+				},
+				{
+					"label": "Programmed in",
+					"value": "C, Python",
+					"entity": "P277"
+				},
+				{
+					"label": "Software version identifier",
+					"value": "3.12.3, 3.13.0a6",
+					"entity": "P348"
+				}
+			],
+			"engine": "wikidata",
+			"engines": ["wikidata"]
+		}
+	],
+	"suggestions": [
+		"python turtle",
+		"micro python tutorial",
+		"python docs",
+		"python compiler",
+		"snapcraft python",
+		"micropython vs python",
+		"python online",
+		"python download"
+	],
+	"unresponsive_engines": []
+}
diff --git a/backend/open_webui/apps/retrieval/web/testdata/serper.json b/backend/open_webui/apps/retrieval/web/testdata/serper.json
new file mode 100644
index 0000000000000000000000000000000000000000..b269eaf5b34fe64234ba6e7ffb27fd3fbbaa3fe0
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/testdata/serper.json
@@ -0,0 +1,190 @@
+{
+	"searchParameters": {
+		"q": "apple inc",
+		"gl": "us",
+		"hl": "en",
+		"autocorrect": true,
+		"page": 1,
+		"type": "search"
+	},
+	"knowledgeGraph": {
+		"title": "Apple",
+		"type": "Technology company",
+		"website": "http://www.apple.com/",
+		"imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQwGQRv5TjjkycpctY66mOg_e2-npacrmjAb6_jAWhzlzkFE3OTjxyzbA&s=0",
+		"description": "Apple Inc. is an American multinational technology company specializing in consumer electronics, software and online services headquartered in Cupertino, California, United States.",
+		"descriptionSource": "Wikipedia",
+		"descriptionLink": "https://en.wikipedia.org/wiki/Apple_Inc.",
+		"attributes": {
+			"Headquarters": "Cupertino, CA",
+			"CEO": "Tim Cook (Aug 24, 2011–)",
+			"Founded": "April 1, 1976, Los Altos, CA",
+			"Sales": "1 (800) 692-7753",
+			"Products": "iPhone, Apple Watch, iPad, and more",
+			"Founders": "Steve Jobs, Steve Wozniak, and Ronald Wayne",
+			"Subsidiaries": "Apple Store, Beats Electronics, Beddit, and more"
+		}
+	},
+	"organic": [
+		{
+			"title": "Apple",
+			"link": "https://www.apple.com/",
+			"snippet": "Discover the innovative world of Apple and shop everything iPhone, iPad, Apple Watch, Mac, and Apple TV, plus explore accessories, entertainment, ...",
+			"sitelinks": [
+				{
+					"title": "Support",
+					"link": "https://support.apple.com/"
+				},
+				{
+					"title": "iPhone",
+					"link": "https://www.apple.com/iphone/"
+				},
+				{
+					"title": "Apple makes business better.",
+					"link": "https://www.apple.com/business/"
+				},
+				{
+					"title": "Mac",
+					"link": "https://www.apple.com/mac/"
+				}
+			],
+			"position": 1
+		},
+		{
+			"title": "Apple Inc. - Wikipedia",
+			"link": "https://en.wikipedia.org/wiki/Apple_Inc.",
+			"snippet": "Apple Inc. is an American multinational technology company specializing in consumer electronics, software and online services headquartered in Cupertino, ...",
+			"attributes": {
+				"Products": "AirPods; Apple Watch; iPad; iPhone; Mac",
+				"Founders": "Steve Jobs; Steve Wozniak; Ronald Wayne",
+				"Founded": "April 1, 1976; 46 years ago in Los Altos, California, U.S",
+				"Industry": "Consumer electronics; Software services; Online services"
+			},
+			"sitelinks": [
+				{
+					"title": "History",
+					"link": "https://en.wikipedia.org/wiki/History_of_Apple_Inc."
+				},
+				{
+					"title": "Timeline of Apple Inc. products",
+					"link": "https://en.wikipedia.org/wiki/Timeline_of_Apple_Inc._products"
+				},
+				{
+					"title": "List of software by Apple Inc.",
+					"link": "https://en.wikipedia.org/wiki/List_of_software_by_Apple_Inc."
+				},
+				{
+					"title": "Apple Store",
+					"link": "https://en.wikipedia.org/wiki/Apple_Store"
+				}
+			],
+			"position": 2
+		},
+		{
+			"title": "Apple Inc. | History, Products, Headquarters, & Facts | Britannica",
+			"link": "https://www.britannica.com/topic/Apple-Inc",
+			"snippet": "Apple Inc., formerly Apple Computer, Inc., American manufacturer of personal computers, smartphones, tablet computers, computer peripherals, ...",
+			"date": "Aug 31, 2022",
+			"attributes": {
+				"Related People": "Steve Jobs Steve Wozniak Jony Ive Tim Cook Angela Ahrendts",
+				"Date": "1976 - present",
+				"Areas Of Involvement": "peripheral device"
+			},
+			"position": 3
+		},
+		{
+			"title": "AAPL: Apple Inc Stock Price Quote - NASDAQ GS - Bloomberg.com",
+			"link": "https://www.bloomberg.com/quote/AAPL:US",
+			"snippet": "Stock analysis for Apple Inc (AAPL:NASDAQ GS) including stock price, stock chart, company news, key statistics, fundamentals and company profile.",
+			"position": 4
+		},
+		{
+			"title": "Apple Inc. (AAPL) Company Profile & Facts - Yahoo Finance",
+			"link": "https://finance.yahoo.com/quote/AAPL/profile/",
+			"snippet": "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide. It also sells various related ...",
+			"position": 5
+		},
+		{
+			"title": "AAPL | Apple Inc. Stock Price & News - WSJ",
+			"link": "https://www.wsj.com/market-data/quotes/AAPL",
+			"snippet": "Apple, Inc. engages in the design, manufacture, and sale of smartphones, personal computers, tablets, wearables and accessories, and other varieties of ...",
+			"position": 6
+		},
+		{
+			"title": "Apple Inc Company Profile - Apple Inc Overview - GlobalData",
+			"link": "https://www.globaldata.com/company-profile/apple-inc/",
+			"snippet": "Apple Inc (Apple) designs, manufactures, and markets smartphones, tablets, personal computers (PCs), portable and wearable devices. The company also offers ...",
+			"position": 7
+		},
+		{
+			"title": "Apple Inc (AAPL) Stock Price & News - Google Finance",
+			"link": "https://www.google.com/finance/quote/AAPL:NASDAQ?hl=en",
+			"snippet": "Get the latest Apple Inc (AAPL) real-time quote, historical performance, charts, and other financial information to help you make more informed trading and ...",
+			"position": 8
+		}
+	],
+	"peopleAlsoAsk": [
+		{
+			"question": "What does Apple Inc mean?",
+			"snippet": "Apple Inc., formerly Apple Computer, Inc., American manufacturer of personal\ncomputers, smartphones, tablet computers, computer peripherals, and computer\nsoftware. It was the first successful personal computer company and the\npopularizer of the graphical user interface.\nAug 31, 2022",
+			"title": "Apple Inc. | History, Products, Headquarters, & Facts | Britannica",
+			"link": "https://www.britannica.com/topic/Apple-Inc"
+		},
+		{
+			"question": "Is Apple and Apple Inc same?",
+			"snippet": "Apple was founded as Apple Computer Company on April 1, 1976, by Steve Jobs,\nSteve Wozniak and Ronald Wayne to develop and sell Wozniak's Apple I personal\ncomputer. It was incorporated by Jobs and Wozniak as Apple Computer, Inc.",
+			"title": "Apple Inc. - Wikipedia",
+			"link": "https://en.wikipedia.org/wiki/Apple_Inc."
+		},
+		{
+			"question": "Who owns Apple Inc?",
+			"snippet": "Apple Inc. is owned by two main institutional investors (Vanguard Group and\nBlackRock, Inc). While its major individual shareholders comprise people like\nArt Levinson, Tim Cook, Bruce Sewell, Al Gore, Johny Sroujli, and others.",
+			"title": "Who Owns Apple In 2022? - FourWeekMBA",
+			"link": "https://fourweekmba.com/who-owns-apple/"
+		},
+		{
+			"question": "What products does Apple Inc offer?",
+			"snippet": "APPLE FOOTER\nStore.\nMac.\niPad.\niPhone.\nWatch.\nAirPods.\nTV & Home.\nAirTag.",
+			"title": "More items...",
+			"link": "https://www.apple.com/business/"
+		}
+	],
+	"relatedSearches": [
+		{
+			"query": "Who invented the iPhone"
+		},
+		{
+			"query": "Apple Inc competitors"
+		},
+		{
+			"query": "Apple iPad"
+		},
+		{
+			"query": "iPhones"
+		},
+		{
+			"query": "Apple Inc us"
+		},
+		{
+			"query": "Apple company history"
+		},
+		{
+			"query": "Apple Store"
+		},
+		{
+			"query": "Apple customer service"
+		},
+		{
+			"query": "Apple Watch"
+		},
+		{
+			"query": "Apple Inc Industry"
+		},
+		{
+			"query": "Apple Inc registered address"
+		},
+		{
+			"query": "Apple Inc Bloomberg"
+		}
+	]
+}
diff --git a/backend/open_webui/apps/retrieval/web/testdata/serply.json b/backend/open_webui/apps/retrieval/web/testdata/serply.json
new file mode 100644
index 0000000000000000000000000000000000000000..0fc2a31e4d63cefba8aa96cad147208d596060c4
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/testdata/serply.json
@@ -0,0 +1,206 @@
+{
+	"ads": [],
+	"ads_count": 0,
+	"answers": [],
+	"results": [
+		{
+			"title": "Apple",
+			"link": "https://www.apple.com/",
+			"description": "Discover the innovative world of Apple and shop everything iPhone, iPad, Apple Watch, Mac, and Apple TV, plus explore accessories, entertainment, ...",
+			"additional_links": [
+				{
+					"text": "AppleApplehttps://www.apple.com",
+					"href": "https://www.apple.com/"
+				}
+			],
+			"cite": {},
+			"subdomains": [
+				{
+					"title": "Support",
+					"link": "https://support.apple.com/",
+					"description": "SupportContact - iPhone Support - Billing and Subscriptions - Apple Repair"
+				},
+				{
+					"title": "Store",
+					"link": "https://www.apple.com/store",
+					"description": "StoreShop iPhone - Shop iPad - App Store - Shop Mac - ..."
+				},
+				{
+					"title": "Mac",
+					"link": "https://www.apple.com/mac/",
+					"description": "MacMacBook Air - MacBook Pro - iMac - Compare Mac models - Mac mini"
+				},
+				{
+					"title": "iPad",
+					"link": "https://www.apple.com/ipad/",
+					"description": "iPadShop iPad - iPad Pro - iPad Air - Compare iPad models - ..."
+				},
+				{
+					"title": "Watch",
+					"link": "https://www.apple.com/watch/",
+					"description": "WatchShop Apple Watch - Series 9 - SE - Ultra 2 - Nike - Hermès - ..."
+				}
+			],
+			"realPosition": 1
+		},
+		{
+			"title": "Apple",
+			"link": "https://www.apple.com/",
+			"description": "Discover the innovative world of Apple and shop everything iPhone, iPad, Apple Watch, Mac, and Apple TV, plus explore accessories, entertainment, ...",
+			"additional_links": [
+				{
+					"text": "AppleApplehttps://www.apple.com",
+					"href": "https://www.apple.com/"
+				}
+			],
+			"cite": {},
+			"realPosition": 2
+		},
+		{
+			"title": "Apple Inc.",
+			"link": "https://en.wikipedia.org/wiki/Apple_Inc.",
+			"description": "Apple Inc. (formerly Apple Computer, Inc.) is an American multinational corporation and technology company headquartered in Cupertino, California, ...",
+			"additional_links": [
+				{
+					"text": "Apple Inc.Wikipediahttps://en.wikipedia.org › wiki › Apple_Inc",
+					"href": "https://en.wikipedia.org/wiki/Apple_Inc."
+				},
+				{
+					"text": "",
+					"href": "https://en.wikipedia.org/wiki/Apple_Inc."
+				},
+				{
+					"text": "History",
+					"href": "https://en.wikipedia.org/wiki/History_of_Apple_Inc."
+				},
+				{
+					"text": "List of Apple products",
+					"href": "https://en.wikipedia.org/wiki/List_of_Apple_products"
+				},
+				{
+					"text": "Litigation involving Apple Inc.",
+					"href": "https://en.wikipedia.org/wiki/Litigation_involving_Apple_Inc."
+				},
+				{
+					"text": "Apple Park",
+					"href": "https://en.wikipedia.org/wiki/Apple_Park"
+				}
+			],
+			"cite": {
+				"domain": "https://en.wikipedia.org › wiki › Apple_Inc",
+				"span": " › wiki › Apple_Inc"
+			},
+			"realPosition": 3
+		},
+		{
+			"title": "Apple Inc. (AAPL) Company Profile & Facts",
+			"link": "https://finance.yahoo.com/quote/AAPL/profile/",
+			"description": "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide. The company offers iPhone, a line ...",
+			"additional_links": [
+				{
+					"text": "Apple Inc. (AAPL) Company Profile & FactsYahoo Financehttps://finance.yahoo.com › quote › AAPL › profile",
+					"href": "https://finance.yahoo.com/quote/AAPL/profile/"
+				}
+			],
+			"cite": {
+				"domain": "https://finance.yahoo.com › quote › AAPL › profile",
+				"span": " › quote › AAPL › profile"
+			},
+			"realPosition": 4
+		},
+		{
+			"title": "Apple Inc - Company Profile and News",
+			"link": "https://www.bloomberg.com/profile/company/AAPL:US",
+			"description": "Apple Inc. Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables and accessories, and sells a variety of related ...",
+			"additional_links": [
+				{
+					"text": "Apple Inc - Company Profile and NewsBloomberghttps://www.bloomberg.com › company › AAPL:US",
+					"href": "https://www.bloomberg.com/profile/company/AAPL:US"
+				},
+				{
+					"text": "",
+					"href": "https://www.bloomberg.com/profile/company/AAPL:US"
+				}
+			],
+			"cite": {
+				"domain": "https://www.bloomberg.com › company › AAPL:US",
+				"span": " › company › AAPL:US"
+			},
+			"realPosition": 5
+		},
+		{
+			"title": "Apple Inc. | History, Products, Headquarters, & Facts",
+			"link": "https://www.britannica.com/money/Apple-Inc",
+			"description": "May 22, 2024 — Apple Inc. is an American multinational technology company that revolutionized the technology sector through its innovation of computer ...",
+			"additional_links": [
+				{
+					"text": "Apple Inc. | History, Products, Headquarters, & FactsBritannicahttps://www.britannica.com › money › Apple-Inc",
+					"href": "https://www.britannica.com/money/Apple-Inc"
+				},
+				{
+					"text": "",
+					"href": "https://www.britannica.com/money/Apple-Inc"
+				}
+			],
+			"cite": {
+				"domain": "https://www.britannica.com › money › Apple-Inc",
+				"span": " › money › Apple-Inc"
+			},
+			"realPosition": 6
+		}
+	],
+	"shopping_ads": [],
+	"places": [
+		{
+			"title": "Apple Inc."
+		},
+		{
+			"title": "Apple Inc"
+		},
+		{
+			"title": "Apple Inc"
+		}
+	],
+	"related_searches": {
+		"images": [],
+		"text": [
+			{
+				"title": "apple inc full form",
+				"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+Inc+full+form&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhPEAE"
+			},
+			{
+				"title": "apple company history",
+				"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+company+history&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhOEAE"
+			},
+			{
+				"title": "apple store",
+				"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+Store&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhQEAE"
+			},
+			{
+				"title": "apple id",
+				"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+id&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhSEAE"
+			},
+			{
+				"title": "apple inc industry",
+				"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+Inc+industry&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhREAE"
+			},
+			{
+				"title": "apple login",
+				"link": "https://www.google.com/search?sca_esv=6b6df170a5c9891b&sca_upv=1&q=Apple+login&sa=X&ved=2ahUKEwjLxuSJwM-GAxUHODQIHYuJBhgQ1QJ6BAhTEAE"
+			}
+		]
+	},
+	"image_results": [],
+	"carousel": [],
+	"total": 2450000000,
+	"knowledge_graph": "",
+	"related_questions": [
+		"What does the Apple Inc do?",
+		"Why did Apple change to Apple Inc?",
+		"Who owns Apple Inc.?",
+		"What is Apple Inc best known for?"
+	],
+	"carousel_count": 0,
+	"ts": 2.491065263748169,
+	"device_type": null
+}
diff --git a/backend/open_webui/apps/retrieval/web/testdata/serpstack.json b/backend/open_webui/apps/retrieval/web/testdata/serpstack.json
new file mode 100644
index 0000000000000000000000000000000000000000..a82f689d8b2293586d6b94974e018f74e49d1013
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/testdata/serpstack.json
@@ -0,0 +1,276 @@
+{
+	"request": {
+		"success": true,
+		"total_time_taken": 3.4,
+		"processed_timestamp": 1714968442,
+		"search_url": "http://www.google.com/search?q=mcdonalds\u0026gl=us\u0026hl=en\u0026safe=0\u0026num=10"
+	},
+	"search_parameters": {
+		"engine": "google",
+		"type": "web",
+		"device": "desktop",
+		"auto_location": "1",
+		"google_domain": "google.com",
+		"gl": "us",
+		"hl": "en",
+		"safe": "0",
+		"news_type": "all",
+		"exclude_autocorrected_results": "0",
+		"images_color": "any",
+		"page": "1",
+		"num": "10",
+		"output": "json",
+		"csv_fields": "search_parameters.query,organic_results.position,organic_results.title,organic_results.url,organic_results.domain",
+		"query": "mcdonalds",
+		"action": "search",
+		"access_key": "aac48e007e15c532bb94ffb34532a4b2",
+		"error": {}
+	},
+	"search_information": {
+		"total_results": 1170000000,
+		"time_taken_displayed": 0.49,
+		"detected_location": {},
+		"did_you_mean": {},
+		"no_results_for_original_query": false,
+		"showing_results_for": {}
+	},
+	"organic_results": [
+		{
+			"position": 1,
+			"title": "Our Full McDonald\u0027s Food Menu",
+			"snippet": "",
+			"prerender": false,
+			"cached_page_url": {},
+			"related_pages_url": {},
+			"url": "https://www.mcdonalds.com/us/en-us/full-menu.html",
+			"domain": "www.mcdonalds.com",
+			"displayed_url": "https://www.mcdonalds.com \u203a en-us \u203a full-menu"
+		},
+		{
+			"position": 2,
+			"title": "McDonald\u0027s",
+			"snippet": "McDonald\u0027s is the world\u0027s largest fast food restaurant chain, serving over 69 million customers daily in over 100 countries in more than 40,000 outlets as of\u00a0...",
+			"prerender": false,
+			"cached_page_url": {},
+			"related_pages_url": {},
+			"url": "https://en.wikipedia.org/wiki/McDonald%27s",
+			"domain": "en.wikipedia.org",
+			"displayed_url": "https://en.wikipedia.org \u203a wiki \u203a McDonald\u0027s"
+		},
+		{
+			"position": 3,
+			"title": "Restaurants Near Me: Nearby McDonald\u0027s Locations",
+			"snippet": "",
+			"prerender": false,
+			"cached_page_url": {},
+			"related_pages_url": {},
+			"url": "https://www.mcdonalds.com/us/en-us/restaurant-locator.html",
+			"domain": "www.mcdonalds.com",
+			"displayed_url": "https://www.mcdonalds.com \u203a en-us \u203a restaurant-locator"
+		},
+		{
+			"position": 4,
+			"title": "Download the McDonald\u0027s App: Deals, Promotions \u0026 ...",
+			"snippet": "Download the McDonald\u0027s app for Mobile Order \u0026 Pay, exclusive deals and coupons, menu information and special promotions.",
+			"prerender": false,
+			"cached_page_url": {},
+			"related_pages_url": {},
+			"url": "https://www.mcdonalds.com/us/en-us/download-app.html",
+			"domain": "www.mcdonalds.com",
+			"displayed_url": "https://www.mcdonalds.com \u203a en-us \u203a download-app"
+		},
+		{
+			"position": 5,
+			"title": "McDonald\u0027s Restaurant Careers in the US",
+			"snippet": "McDonald\u0027s restaurant jobs are one-of-a-kind \u2013 just like you. Restaurants are hiring across all levels, from Crew team to Management. Apply today!",
+			"prerender": false,
+			"cached_page_url": {},
+			"related_pages_url": {},
+			"url": "https://jobs.mchire.com/",
+			"domain": "jobs.mchire.com",
+			"displayed_url": "https://jobs.mchire.com"
+		}
+	],
+	"inline_images": [
+		{
+			"image_url": "https://serpstack-assets.apilayer.net/2418910010831954152.png",
+			"title": ""
+		}
+	],
+	"local_results": [
+		{
+			"position": 1,
+			"title": "McDonald\u0027s",
+			"coordinates": {
+				"latitude": 0,
+				"longitude": 0
+			},
+			"address": "",
+			"rating": 0,
+			"reviews": 0,
+			"type": "",
+			"price": {},
+			"url": 0
+		},
+		{
+			"position": 2,
+			"title": "McDonald\u0027s",
+			"coordinates": {
+				"latitude": 0,
+				"longitude": 0
+			},
+			"address": "",
+			"rating": 0,
+			"reviews": 0,
+			"type": "",
+			"price": {},
+			"url": 0
+		},
+		{
+			"position": 3,
+			"title": "McDonald\u0027s",
+			"coordinates": {
+				"latitude": 0,
+				"longitude": 0
+			},
+			"address": "",
+			"rating": 0,
+			"reviews": 0,
+			"type": "",
+			"price": {},
+			"url": 0
+		}
+	],
+	"top_stories": [
+		{
+			"block_position": 1,
+			"title": "Menu nutrition",
+			"url": "/search?safe=0\u0026sca_esv=c9c7fd42856085e2\u0026sca_upv=1\u0026gl=us\u0026hl=en\u0026q=mcdonald%27s+double+quarter+pounder+with+cheese\u0026stick=H4sIAAAAAAAAAONgFuLUz9U3ME-vLDBX4tVP1zc0TCsuNE0ytjTTUs5OttJPy89P0c9NzSuNLyjKL8tMSS2yAvNS80qKMlOLF7Hq5ian5Ocl5qSoFyuk5Jcm5aQqFJYmFpWkFikU5JfmATUolGeWZCgkZ6SmFqcCAM4ilJtxAAAA\u0026sa=X\u0026ved=2ahUKEwjF55alk_iFAxXlamwGHbqgAs4Qri56BAh0EAM",
+			"source": "",
+			"uploaded": "",
+			"uploaded_utc": "2024-05-06T04:07:22.082Z"
+		},
+		{
+			"block_position": 2,
+			"title": "Profiles",
+			"url": "https://www.instagram.com/McDonalds",
+			"source": "",
+			"uploaded": "",
+			"uploaded_utc": "2024-05-06T04:07:22.082Z"
+		},
+		{
+			"block_position": 3,
+			"title": "People also search for",
+			"url": "/search?safe=0\u0026sca_esv=c9c7fd42856085e2\u0026sca_upv=1\u0026gl=us\u0026hl=en\u0026si=ACC90nzx_D3_zUKRnpAjmO0UBLNxnt7EyN4YYdru6U3bxLI-L5Wg8IL2sxPFxxcDEhVbocy-LJPZIvZySijw0ho2hfZ-KtV-sSEEJ9lw7JuEkXHDnRK5y4Dm8aqbiLwugbLbslwjG3hO_gpDTFZK2VoUGZPy2nrmOBCy0G3PoOfoiEtct2GSZlUz0uufG-xP8emtNzQKQpvjkAm5Zmi57iVZueiD62upz7-x2N3dAbwtm6FkInAPRw1yR91zuT7F3lEaPblTW3LaRwCDC0bvaRCh9x4N9zHgY1OOQa_rzts2jf5WpXcuw4Y%3D\u0026q=Burger+King\u0026sa=X\u0026ved=2ahUKEwjF55alk_iFAxXlamwGHbqgAs4Qs9oBKAB6BAhzEAI",
+			"source": "",
+			"uploaded": "",
+			"uploaded_utc": "2024-05-06T04:07:22.082Z"
+		}
+	],
+	"related_questions": [
+		{
+			"question": "What\u0027s a number 7 at McDonald\u0027s?What\u0027s a number 7 at McDonald\u0027s?What\u0027s a number 7 at McDonald\u0027s?",
+			"answer": "",
+			"title": "",
+			"displayed_url": ""
+		},
+		{
+			"question": "Why is McDonald\u0027s changing their name?Why is McDonald\u0027s changing their name?Why is McDonald\u0027s changing their name?",
+			"answer": "",
+			"title": "",
+			"displayed_url": ""
+		},
+		{
+			"question": "What is the oldest still running Mcdonalds?What is the oldest still running Mcdonalds?What is the oldest still running Mcdonalds?",
+			"answer": "",
+			"title": "",
+			"displayed_url": ""
+		},
+		{
+			"question": "Why is McDonald\u0027s now WcDonald\u0027s?Why is McDonald\u0027s now WcDonald\u0027s?Why is McDonald\u0027s now WcDonald\u0027s?",
+			"answer": "",
+			"title": "",
+			"displayed_url": ""
+		}
+	],
+	"knowledge_graph": {
+		"title": "",
+		"type": "Fast-food restaurant company",
+		"image_urls": ["https://serpstack-assets.apilayer.net/2418910010831954152.png"],
+		"description": "McDonald\u0027s Corporation is an American multinational fast food chain, founded in 1940 as a restaurant operated by Richard and Maurice McDonald, in San Bernardino, California, United States.",
+		"source": {
+			"name": "Wikipedia",
+			"url": "https://en.wikipedia.org/wiki/McDonald\u0027s"
+		},
+		"people_also_search_for": [],
+		"known_attributes": [
+			{
+				"attribute": "kc:/business/business_operation:founder",
+				"link": "http://www.google.com/search?safe=0\u0026sca_esv=c9c7fd42856085e2\u0026sca_upv=1\u0026gl=us\u0026hl=en\u0026q=Ray+Kroc\u0026si=ACC90nzx_D3_zUKRnpAjmO0UBLNxnt7EyN4YYdru6U3bxLI-LxARWRdbk5SkoY2sDn5Qq7yOmqYGei6qZ7sfJhsjZXBPgjMlLbS7824rpJOm69GzqVWMdoNIZiFX2T4A2td14sZOn4a1BexZLtZXHU7NZdF6VsWbGMVuiSYtXdev7uaUjEJKumiwlqTAATTebOriYTEBuSzC\u0026sa=X\u0026ved=2ahUKEwjF55alk_iFAxXlamwGHbqgAs4QmxMoAHoECHgQAg",
+				"name": "Founder: ",
+				"value": "Ray Kroc"
+			},
+			{
+				"attribute": "kc:/organization/organization:ceo",
+				"link": "http://www.google.com/search?safe=0\u0026sca_esv=c9c7fd42856085e2\u0026sca_upv=1\u0026gl=us\u0026hl=en\u0026q=Chris+Kempczinski\u0026si=ACC90nwLLwns5sISZcdzuISy7t-NHozt8Cbt6G3WNQfC9ekAgKFbjdEFCDgxLbt57EDZGosYDGiZuq1AcBhA6IhTOSZxfVSySuGQ3VDwmmTA7Z93n3K3596jAuZH9VVv5h8PyvKJSuGuSsQWviJTl3eKj2UL1ZIWuDgkjyVMnC47rN7j0G9PlHRCCLdQF7VDQ1gubTiC4onXqLRBTbwAj6a--PD6Jv_NoA%3D%3D\u0026sa=X\u0026ved=2ahUKEwjF55alk_iFAxXlamwGHbqgAs4QmxMoAHoECHUQAg",
+				"name": "CEO: ",
+				"value": "Chris Kempczinski (Nov 1, 2019\u2013)"
+			},
+			{
+				"attribute": "kc:/business/employer:revenue",
+				"link": "",
+				"name": "Revenue: ",
+				"value": "25.49\u00a0billion USD (2023)"
+			},
+			{
+				"attribute": "kc:/organization/organization:founded",
+				"link": "http://www.google.com/search?safe=0\u0026sca_esv=c9c7fd42856085e2\u0026sca_upv=1\u0026gl=us\u0026hl=en\u0026q=Des+Plaines\u0026si=ACC90nyvvWro6QmnyY1IfSdgk5wwjB1r8BGd_IWRjXqmKPQqm_yqLtI_DBi5PXGOtg_Z3qrzzEP6mcih1nN7h5A7v6OefnEJiC7a8dBR-v9LxlRubfyR6vlMr3fZ3TmVKWwz9FRpvZb1eYNt-RM7KIDKQlwGEIgINvzhxjUrv6uxSmceduzxd8W7Pkz71XGwxF0F8OlSzHlx\u0026sa=X\u0026ved=2ahUKEwjF55alk_iFAxXlamwGHbqgAs4QmxMoAHoECG4QAg",
+				"name": "Founded: ",
+				"value": "April 15, 1955, Des Plaines, IL"
+			},
+			{
+				"attribute": "kc:/organization/organization:headquarters",
+				"link": "http://www.google.com/search?safe=0\u0026sca_esv=c9c7fd42856085e2\u0026sca_upv=1\u0026gl=us\u0026hl=en\u0026q=Chicago\u0026si=ACC90nyvvWro6QmnyY1IfSdgk5wwjB1r8BGd_IWRjXqmKPQqm-46AEJ_kJbUIEvsvEEZqteiYJvXVXs2ScRNDvFFpjfeAaW3dxtpTGCgcsf5RMdi6IdzOdtjJMN3ZaFwqZOmdi7tC6r0Mh1O9bnP3HrVDB9hH02m7aA6f70dCAfTdpOFnGxDU6wVMAI5MxWBE3wTugtUDOK-\u0026sa=X\u0026ved=2ahUKEwjF55alk_iFAxXlamwGHbqgAs4QmxMoAHoECHYQAg",
+				"name": "Headquarters: ",
+				"value": "Chicago, IL"
+			},
+			{
+				"attribute": "kc:/organization/organization:president",
+				"link": "http://www.google.com/search?safe=0\u0026sca_esv=c9c7fd42856085e2\u0026sca_upv=1\u0026gl=us\u0026hl=en\u0026q=Chris+Kempczinski\u0026si=ACC90nwLLwns5sISZcdzuISy7t-NHozt8Cbt6G3WNQfC9ekAgKFbjdEFCDgxLbt57EDZGosYDGiZuq1AcBhA6IhTOSZxfVSySuGQ3VDwmmTA7Z93n3K3596jAuZH9VVv5h8PyvKJSuGuSsQWviJTl3eKj2UL1ZIWuDgkjyVMnC47rN7j0G9PlHRCCLdQF7VDQ1gubTiC4onXqLRBTbwAj6a--PD6Jv_NoA%3D%3D\u0026sa=X\u0026ved=2ahUKEwjF55alk_iFAxXlamwGHbqgAs4QmxMoAHoECHEQAg",
+				"name": "President: ",
+				"value": "Chris Kempczinski"
+			}
+		],
+		"website": "https://www.mcdonalds.com/us/en-us.html",
+		"profiles": [
+			{
+				"name": "Instagram",
+				"url": "https://www.instagram.com/McDonalds"
+			},
+			{
+				"name": "X (Twitter)",
+				"url": "https://twitter.com/McDonalds"
+			},
+			{
+				"name": "Facebook",
+				"url": "https://www.facebook.com/McDonaldsUS"
+			},
+			{
+				"name": "YouTube",
+				"url": "https://www.youtube.com/user/McDonaldsUS"
+			},
+			{
+				"name": "Pinterest",
+				"url": "https://www.pinterest.com/mcdonalds"
+			}
+		],
+		"founded": "April 15, 1955, Des Plaines, IL",
+		"headquarters": "Chicago, IL",
+		"founders": [
+			{
+				"name": "Ray Kroc",
+				"link": "http://www.google.com/search?safe=0\u0026sca_esv=c9c7fd42856085e2\u0026sca_upv=1\u0026gl=us\u0026hl=en\u0026q=Ray+Kroc\u0026si=ACC90nzx_D3_zUKRnpAjmO0UBLNxnt7EyN4YYdru6U3bxLI-LxARWRdbk5SkoY2sDn5Qq7yOmqYGei6qZ7sfJhsjZXBPgjMlLbS7824rpJOm69GzqVWMdoNIZiFX2T4A2td14sZOn4a1BexZLtZXHU7NZdF6VsWbGMVuiSYtXdev7uaUjEJKumiwlqTAATTebOriYTEBuSzC\u0026sa=X\u0026ved=2ahUKEwjF55alk_iFAxXlamwGHbqgAs4QmxMoAHoECHgQAg"
+			}
+		]
+	}
+}
diff --git a/backend/open_webui/apps/retrieval/web/utils.py b/backend/open_webui/apps/retrieval/web/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..2df98b33c8564d39892a25cd29ecbc916fcc8618
--- /dev/null
+++ b/backend/open_webui/apps/retrieval/web/utils.py
@@ -0,0 +1,97 @@
+import socket
+import urllib.parse
+import validators
+from typing import Union, Sequence, Iterator
+
+from langchain_community.document_loaders import (
+    WebBaseLoader,
+)
+from langchain_core.documents import Document
+
+
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.config import ENABLE_RAG_LOCAL_WEB_FETCH
+from open_webui.env import SRC_LOG_LEVELS
+
+import logging
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def validate_url(url: Union[str, Sequence[str]]):
+    if isinstance(url, str):
+        if isinstance(validators.url(url), validators.ValidationError):
+            raise ValueError(ERROR_MESSAGES.INVALID_URL)
+        if not ENABLE_RAG_LOCAL_WEB_FETCH:
+            # Local web fetch is disabled, filter out any URLs that resolve to private IP addresses
+            parsed_url = urllib.parse.urlparse(url)
+            # Get IPv4 and IPv6 addresses
+            ipv4_addresses, ipv6_addresses = resolve_hostname(parsed_url.hostname)
+            # Check if any of the resolved addresses are private
+            # This is technically still vulnerable to DNS rebinding attacks, as we don't control WebBaseLoader
+            for ip in ipv4_addresses:
+                if validators.ipv4(ip, private=True):
+                    raise ValueError(ERROR_MESSAGES.INVALID_URL)
+            for ip in ipv6_addresses:
+                if validators.ipv6(ip, private=True):
+                    raise ValueError(ERROR_MESSAGES.INVALID_URL)
+        return True
+    elif isinstance(url, Sequence):
+        return all(validate_url(u) for u in url)
+    else:
+        return False
+
+
+def resolve_hostname(hostname):
+    # Get address information
+    addr_info = socket.getaddrinfo(hostname, None)
+
+    # Extract IP addresses from address information
+    ipv4_addresses = [info[4][0] for info in addr_info if info[0] == socket.AF_INET]
+    ipv6_addresses = [info[4][0] for info in addr_info if info[0] == socket.AF_INET6]
+
+    return ipv4_addresses, ipv6_addresses
+
+
+class SafeWebBaseLoader(WebBaseLoader):
+    """WebBaseLoader with enhanced error handling for URLs."""
+
+    def lazy_load(self) -> Iterator[Document]:
+        """Lazy load text from the url(s) in web_path with error handling."""
+        for path in self.web_paths:
+            try:
+                soup = self._scrape(path, bs_kwargs=self.bs_kwargs)
+                text = soup.get_text(**self.bs_get_text_kwargs)
+
+                # Build metadata
+                metadata = {"source": path}
+                if title := soup.find("title"):
+                    metadata["title"] = title.get_text()
+                if description := soup.find("meta", attrs={"name": "description"}):
+                    metadata["description"] = description.get(
+                        "content", "No description found."
+                    )
+                if html := soup.find("html"):
+                    metadata["language"] = html.get("lang", "No language found.")
+
+                yield Document(page_content=text, metadata=metadata)
+            except Exception as e:
+                # Log the error and continue with the next URL
+                log.error(f"Error loading {path}: {e}")
+
+
+def get_web_loader(
+    url: Union[str, Sequence[str]],
+    verify_ssl: bool = True,
+    requests_per_second: int = 2,
+):
+    # Check if the URL is valid
+    if not validate_url(url):
+        raise ValueError(ERROR_MESSAGES.INVALID_URL)
+    return SafeWebBaseLoader(
+        url,
+        verify_ssl=verify_ssl,
+        requests_per_second=requests_per_second,
+        continue_on_failure=True,
+    )
diff --git a/backend/open_webui/apps/socket/main.py b/backend/open_webui/apps/socket/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..fca268a6b4c0d5014d2a2d3ca45b0736c35e1fad
--- /dev/null
+++ b/backend/open_webui/apps/socket/main.py
@@ -0,0 +1,219 @@
+import asyncio
+import socketio
+import logging
+import sys
+import time
+
+from open_webui.apps.webui.models.users import Users
+from open_webui.env import (
+    ENABLE_WEBSOCKET_SUPPORT,
+    WEBSOCKET_MANAGER,
+    WEBSOCKET_REDIS_URL,
+)
+from open_webui.utils.utils import decode_token
+from open_webui.apps.socket.utils import RedisDict
+
+from open_webui.env import (
+    GLOBAL_LOG_LEVEL,
+    SRC_LOG_LEVELS,
+)
+
+
+logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["SOCKET"])
+
+
+if WEBSOCKET_MANAGER == "redis":
+    mgr = socketio.AsyncRedisManager(WEBSOCKET_REDIS_URL)
+    sio = socketio.AsyncServer(
+        cors_allowed_origins=[],
+        async_mode="asgi",
+        transports=(
+            ["polling", "websocket"] if ENABLE_WEBSOCKET_SUPPORT else ["polling"]
+        ),
+        allow_upgrades=ENABLE_WEBSOCKET_SUPPORT,
+        always_connect=True,
+        client_manager=mgr,
+    )
+else:
+    sio = socketio.AsyncServer(
+        cors_allowed_origins=[],
+        async_mode="asgi",
+        transports=(
+            ["polling", "websocket"] if ENABLE_WEBSOCKET_SUPPORT else ["polling"]
+        ),
+        allow_upgrades=ENABLE_WEBSOCKET_SUPPORT,
+        always_connect=True,
+    )
+
+
+# Dictionary to maintain the user pool
+
+if WEBSOCKET_MANAGER == "redis":
+    SESSION_POOL = RedisDict("open-webui:session_pool", redis_url=WEBSOCKET_REDIS_URL)
+    USER_POOL = RedisDict("open-webui:user_pool", redis_url=WEBSOCKET_REDIS_URL)
+    USAGE_POOL = RedisDict("open-webui:usage_pool", redis_url=WEBSOCKET_REDIS_URL)
+else:
+    SESSION_POOL = {}
+    USER_POOL = {}
+    USAGE_POOL = {}
+
+
+# Timeout duration in seconds
+TIMEOUT_DURATION = 3
+
+
+async def periodic_usage_pool_cleanup():
+    while True:
+        now = int(time.time())
+        for model_id, connections in list(USAGE_POOL.items()):
+            # Creating a list of sids to remove if they have timed out
+            expired_sids = [
+                sid
+                for sid, details in connections.items()
+                if now - details["updated_at"] > TIMEOUT_DURATION
+            ]
+
+            for sid in expired_sids:
+                del connections[sid]
+
+            if not connections:
+                log.debug(f"Cleaning up model {model_id} from usage pool")
+                del USAGE_POOL[model_id]
+            else:
+                USAGE_POOL[model_id] = connections
+
+            # Emit updated usage information after cleaning
+            await sio.emit("usage", {"models": get_models_in_use()})
+
+        await asyncio.sleep(TIMEOUT_DURATION)
+
+
+app = socketio.ASGIApp(
+    sio,
+    socketio_path="/ws/socket.io",
+)
+
+
+def get_models_in_use():
+    # List models that are currently in use
+    models_in_use = list(USAGE_POOL.keys())
+    return models_in_use
+
+
+@sio.on("usage")
+async def usage(sid, data):
+    model_id = data["model"]
+    # Record the timestamp for the last update
+    current_time = int(time.time())
+
+    # Store the new usage data and task
+    USAGE_POOL[model_id] = {
+        **(USAGE_POOL[model_id] if model_id in USAGE_POOL else {}),
+        sid: {"updated_at": current_time},
+    }
+
+    # Broadcast the usage data to all clients
+    await sio.emit("usage", {"models": get_models_in_use()})
+
+
+@sio.event
+async def connect(sid, environ, auth):
+    user = None
+    if auth and "token" in auth:
+        data = decode_token(auth["token"])
+
+        if data is not None and "id" in data:
+            user = Users.get_user_by_id(data["id"])
+
+        if user:
+            SESSION_POOL[sid] = user.id
+            if user.id in USER_POOL:
+                USER_POOL[user.id].append(sid)
+            else:
+                USER_POOL[user.id] = [sid]
+
+            # print(f"user {user.name}({user.id}) connected with session ID {sid}")
+            await sio.emit("user-count", {"count": len(USER_POOL.items())})
+            await sio.emit("usage", {"models": get_models_in_use()})
+
+
+@sio.on("user-join")
+async def user_join(sid, data):
+    # print("user-join", sid, data)
+
+    auth = data["auth"] if "auth" in data else None
+    if not auth or "token" not in auth:
+        return
+
+    data = decode_token(auth["token"])
+    if data is None or "id" not in data:
+        return
+
+    user = Users.get_user_by_id(data["id"])
+    if not user:
+        return
+
+    SESSION_POOL[sid] = user.id
+    if user.id in USER_POOL:
+        USER_POOL[user.id].append(sid)
+    else:
+        USER_POOL[user.id] = [sid]
+
+    # print(f"user {user.name}({user.id}) connected with session ID {sid}")
+
+    await sio.emit("user-count", {"count": len(USER_POOL.items())})
+
+
+@sio.on("user-count")
+async def user_count(sid):
+    await sio.emit("user-count", {"count": len(USER_POOL.items())})
+
+
+@sio.event
+async def disconnect(sid):
+    if sid in SESSION_POOL:
+        user_id = SESSION_POOL[sid]
+        del SESSION_POOL[sid]
+
+        USER_POOL[user_id] = [_sid for _sid in USER_POOL[user_id] if _sid != sid]
+
+        if len(USER_POOL[user_id]) == 0:
+            del USER_POOL[user_id]
+
+        await sio.emit("user-count", {"count": len(USER_POOL)})
+    else:
+        pass
+        # print(f"Unknown session ID {sid} disconnected")
+
+
+def get_event_emitter(request_info):
+    async def __event_emitter__(event_data):
+        await sio.emit(
+            "chat-events",
+            {
+                "chat_id": request_info["chat_id"],
+                "message_id": request_info["message_id"],
+                "data": event_data,
+            },
+            to=request_info["session_id"],
+        )
+
+    return __event_emitter__
+
+
+def get_event_call(request_info):
+    async def __event_call__(event_data):
+        response = await sio.call(
+            "chat-events",
+            {
+                "chat_id": request_info["chat_id"],
+                "message_id": request_info["message_id"],
+                "data": event_data,
+            },
+            to=request_info["session_id"],
+        )
+        return response
+
+    return __event_call__
diff --git a/backend/open_webui/apps/socket/utils.py b/backend/open_webui/apps/socket/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..1862ff439e8f8f6489ddc31a72f90abfa877ed72
--- /dev/null
+++ b/backend/open_webui/apps/socket/utils.py
@@ -0,0 +1,59 @@
+import json
+import redis
+
+
+class RedisDict:
+    def __init__(self, name, redis_url):
+        self.name = name
+        self.redis = redis.Redis.from_url(redis_url, decode_responses=True)
+
+    def __setitem__(self, key, value):
+        serialized_value = json.dumps(value)
+        self.redis.hset(self.name, key, serialized_value)
+
+    def __getitem__(self, key):
+        value = self.redis.hget(self.name, key)
+        if value is None:
+            raise KeyError(key)
+        return json.loads(value)
+
+    def __delitem__(self, key):
+        result = self.redis.hdel(self.name, key)
+        if result == 0:
+            raise KeyError(key)
+
+    def __contains__(self, key):
+        return self.redis.hexists(self.name, key)
+
+    def __len__(self):
+        return self.redis.hlen(self.name)
+
+    def keys(self):
+        return self.redis.hkeys(self.name)
+
+    def values(self):
+        return [json.loads(v) for v in self.redis.hvals(self.name)]
+
+    def items(self):
+        return [(k, json.loads(v)) for k, v in self.redis.hgetall(self.name).items()]
+
+    def get(self, key, default=None):
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
+    def clear(self):
+        self.redis.delete(self.name)
+
+    def update(self, other=None, **kwargs):
+        if other is not None:
+            for k, v in other.items() if hasattr(other, "items") else other:
+                self[k] = v
+        for k, v in kwargs.items():
+            self[k] = v
+
+    def setdefault(self, key, default=None):
+        if key not in self:
+            self[key] = default
+        return self[key]
diff --git a/backend/open_webui/apps/webui/internal/db.py b/backend/open_webui/apps/webui/internal/db.py
new file mode 100644
index 0000000000000000000000000000000000000000..bcf913e6fd6e474f59e9581bc944cec2b66415d9
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/db.py
@@ -0,0 +1,114 @@
+import json
+import logging
+from contextlib import contextmanager
+from typing import Any, Optional
+
+from open_webui.apps.webui.internal.wrappers import register_connection
+from open_webui.env import (
+    OPEN_WEBUI_DIR,
+    DATABASE_URL,
+    SRC_LOG_LEVELS,
+    DATABASE_POOL_MAX_OVERFLOW,
+    DATABASE_POOL_RECYCLE,
+    DATABASE_POOL_SIZE,
+    DATABASE_POOL_TIMEOUT,
+)
+from peewee_migrate import Router
+from sqlalchemy import Dialect, create_engine, types
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import scoped_session, sessionmaker
+from sqlalchemy.pool import QueuePool, NullPool
+from sqlalchemy.sql.type_api import _T
+from typing_extensions import Self
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["DB"])
+
+
+class JSONField(types.TypeDecorator):
+    impl = types.Text
+    cache_ok = True
+
+    def process_bind_param(self, value: Optional[_T], dialect: Dialect) -> Any:
+        return json.dumps(value)
+
+    def process_result_value(self, value: Optional[_T], dialect: Dialect) -> Any:
+        if value is not None:
+            return json.loads(value)
+
+    def copy(self, **kw: Any) -> Self:
+        return JSONField(self.impl.length)
+
+    def db_value(self, value):
+        return json.dumps(value)
+
+    def python_value(self, value):
+        if value is not None:
+            return json.loads(value)
+
+
+# Workaround to handle the peewee migration
+# This is required to ensure the peewee migration is handled before the alembic migration
+def handle_peewee_migration(DATABASE_URL):
+    # db = None
+    try:
+        # Replace the postgresql:// with postgres:// to handle the peewee migration
+        db = register_connection(DATABASE_URL.replace("postgresql://", "postgres://"))
+        migrate_dir = OPEN_WEBUI_DIR / "apps" / "webui" / "internal" / "migrations"
+        router = Router(db, logger=log, migrate_dir=migrate_dir)
+        router.run()
+        db.close()
+
+    except Exception as e:
+        log.error(f"Failed to initialize the database connection: {e}")
+        raise
+    finally:
+        # Properly closing the database connection
+        if db and not db.is_closed():
+            db.close()
+
+        # Assert if db connection has been closed
+        assert db.is_closed(), "Database connection is still open."
+
+
+handle_peewee_migration(DATABASE_URL)
+
+
+SQLALCHEMY_DATABASE_URL = DATABASE_URL
+if "sqlite" in SQLALCHEMY_DATABASE_URL:
+    engine = create_engine(
+        SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+    )
+else:
+    if DATABASE_POOL_SIZE > 0:
+        engine = create_engine(
+            SQLALCHEMY_DATABASE_URL,
+            pool_size=DATABASE_POOL_SIZE,
+            max_overflow=DATABASE_POOL_MAX_OVERFLOW,
+            pool_timeout=DATABASE_POOL_TIMEOUT,
+            pool_recycle=DATABASE_POOL_RECYCLE,
+            pool_pre_ping=True,
+            poolclass=QueuePool,
+        )
+    else:
+        engine = create_engine(
+            SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, poolclass=NullPool
+        )
+
+
+SessionLocal = sessionmaker(
+    autocommit=False, autoflush=False, bind=engine, expire_on_commit=False
+)
+Base = declarative_base()
+Session = scoped_session(SessionLocal)
+
+
+def get_session():
+    db = SessionLocal()
+    try:
+        yield db
+    finally:
+        db.close()
+
+
+get_db = contextmanager(get_session)
diff --git a/backend/open_webui/apps/webui/internal/migrations/001_initial_schema.py b/backend/open_webui/apps/webui/internal/migrations/001_initial_schema.py
new file mode 100644
index 0000000000000000000000000000000000000000..93f278f15b842306c6d7e3367c696272c5e9da69
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/001_initial_schema.py
@@ -0,0 +1,254 @@
+"""Peewee migrations -- 001_initial_schema.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    # We perform different migrations for SQLite and other databases
+    # This is because SQLite is very loose with enforcing its schema, and trying to migrate other databases like SQLite
+    # will require per-database SQL queries.
+    # Instead, we assume that because external DB support was added at a later date, it is safe to assume a newer base
+    # schema instead of trying to migrate from an older schema.
+    if isinstance(database, pw.SqliteDatabase):
+        migrate_sqlite(migrator, database, fake=fake)
+    else:
+        migrate_external(migrator, database, fake=fake)
+
+
+def migrate_sqlite(migrator: Migrator, database: pw.Database, *, fake=False):
+    @migrator.create_model
+    class Auth(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        email = pw.CharField(max_length=255)
+        password = pw.CharField(max_length=255)
+        active = pw.BooleanField()
+
+        class Meta:
+            table_name = "auth"
+
+    @migrator.create_model
+    class Chat(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        user_id = pw.CharField(max_length=255)
+        title = pw.CharField()
+        chat = pw.TextField()
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "chat"
+
+    @migrator.create_model
+    class ChatIdTag(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        tag_name = pw.CharField(max_length=255)
+        chat_id = pw.CharField(max_length=255)
+        user_id = pw.CharField(max_length=255)
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "chatidtag"
+
+    @migrator.create_model
+    class Document(pw.Model):
+        id = pw.AutoField()
+        collection_name = pw.CharField(max_length=255, unique=True)
+        name = pw.CharField(max_length=255, unique=True)
+        title = pw.CharField()
+        filename = pw.CharField()
+        content = pw.TextField(null=True)
+        user_id = pw.CharField(max_length=255)
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "document"
+
+    @migrator.create_model
+    class Modelfile(pw.Model):
+        id = pw.AutoField()
+        tag_name = pw.CharField(max_length=255, unique=True)
+        user_id = pw.CharField(max_length=255)
+        modelfile = pw.TextField()
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "modelfile"
+
+    @migrator.create_model
+    class Prompt(pw.Model):
+        id = pw.AutoField()
+        command = pw.CharField(max_length=255, unique=True)
+        user_id = pw.CharField(max_length=255)
+        title = pw.CharField()
+        content = pw.TextField()
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "prompt"
+
+    @migrator.create_model
+    class Tag(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        name = pw.CharField(max_length=255)
+        user_id = pw.CharField(max_length=255)
+        data = pw.TextField(null=True)
+
+        class Meta:
+            table_name = "tag"
+
+    @migrator.create_model
+    class User(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        name = pw.CharField(max_length=255)
+        email = pw.CharField(max_length=255)
+        role = pw.CharField(max_length=255)
+        profile_image_url = pw.CharField(max_length=255)
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "user"
+
+
+def migrate_external(migrator: Migrator, database: pw.Database, *, fake=False):
+    @migrator.create_model
+    class Auth(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        email = pw.CharField(max_length=255)
+        password = pw.TextField()
+        active = pw.BooleanField()
+
+        class Meta:
+            table_name = "auth"
+
+    @migrator.create_model
+    class Chat(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        user_id = pw.CharField(max_length=255)
+        title = pw.TextField()
+        chat = pw.TextField()
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "chat"
+
+    @migrator.create_model
+    class ChatIdTag(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        tag_name = pw.CharField(max_length=255)
+        chat_id = pw.CharField(max_length=255)
+        user_id = pw.CharField(max_length=255)
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "chatidtag"
+
+    @migrator.create_model
+    class Document(pw.Model):
+        id = pw.AutoField()
+        collection_name = pw.CharField(max_length=255, unique=True)
+        name = pw.CharField(max_length=255, unique=True)
+        title = pw.TextField()
+        filename = pw.TextField()
+        content = pw.TextField(null=True)
+        user_id = pw.CharField(max_length=255)
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "document"
+
+    @migrator.create_model
+    class Modelfile(pw.Model):
+        id = pw.AutoField()
+        tag_name = pw.CharField(max_length=255, unique=True)
+        user_id = pw.CharField(max_length=255)
+        modelfile = pw.TextField()
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "modelfile"
+
+    @migrator.create_model
+    class Prompt(pw.Model):
+        id = pw.AutoField()
+        command = pw.CharField(max_length=255, unique=True)
+        user_id = pw.CharField(max_length=255)
+        title = pw.TextField()
+        content = pw.TextField()
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "prompt"
+
+    @migrator.create_model
+    class Tag(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        name = pw.CharField(max_length=255)
+        user_id = pw.CharField(max_length=255)
+        data = pw.TextField(null=True)
+
+        class Meta:
+            table_name = "tag"
+
+    @migrator.create_model
+    class User(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        name = pw.CharField(max_length=255)
+        email = pw.CharField(max_length=255)
+        role = pw.CharField(max_length=255)
+        profile_image_url = pw.TextField()
+        timestamp = pw.BigIntegerField()
+
+        class Meta:
+            table_name = "user"
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_model("user")
+
+    migrator.remove_model("tag")
+
+    migrator.remove_model("prompt")
+
+    migrator.remove_model("modelfile")
+
+    migrator.remove_model("document")
+
+    migrator.remove_model("chatidtag")
+
+    migrator.remove_model("chat")
+
+    migrator.remove_model("auth")
diff --git a/backend/open_webui/apps/webui/internal/migrations/002_add_local_sharing.py b/backend/open_webui/apps/webui/internal/migrations/002_add_local_sharing.py
new file mode 100644
index 0000000000000000000000000000000000000000..e93501aeec522fc102a3ce26112b2edd0e518455
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/002_add_local_sharing.py
@@ -0,0 +1,48 @@
+"""Peewee migrations -- 002_add_local_sharing.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    migrator.add_fields(
+        "chat", share_id=pw.CharField(max_length=255, null=True, unique=True)
+    )
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_fields("chat", "share_id")
diff --git a/backend/open_webui/apps/webui/internal/migrations/003_add_auth_api_key.py b/backend/open_webui/apps/webui/internal/migrations/003_add_auth_api_key.py
new file mode 100644
index 0000000000000000000000000000000000000000..07144f3aca6688a960f7036bcd2c20470da0881c
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/003_add_auth_api_key.py
@@ -0,0 +1,48 @@
+"""Peewee migrations -- 002_add_local_sharing.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    migrator.add_fields(
+        "user", api_key=pw.CharField(max_length=255, null=True, unique=True)
+    )
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_fields("user", "api_key")
diff --git a/backend/open_webui/apps/webui/internal/migrations/004_add_archived.py b/backend/open_webui/apps/webui/internal/migrations/004_add_archived.py
new file mode 100644
index 0000000000000000000000000000000000000000..d01c06b4e665a61c709a8c662387e0c0755efa9a
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/004_add_archived.py
@@ -0,0 +1,46 @@
+"""Peewee migrations -- 002_add_local_sharing.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    migrator.add_fields("chat", archived=pw.BooleanField(default=False))
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_fields("chat", "archived")
diff --git a/backend/open_webui/apps/webui/internal/migrations/005_add_updated_at.py b/backend/open_webui/apps/webui/internal/migrations/005_add_updated_at.py
new file mode 100644
index 0000000000000000000000000000000000000000..950866ef024e80fa1b1af6e296d89feb50a5f5f9
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/005_add_updated_at.py
@@ -0,0 +1,130 @@
+"""Peewee migrations -- 002_add_local_sharing.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    if isinstance(database, pw.SqliteDatabase):
+        migrate_sqlite(migrator, database, fake=fake)
+    else:
+        migrate_external(migrator, database, fake=fake)
+
+
+def migrate_sqlite(migrator: Migrator, database: pw.Database, *, fake=False):
+    # Adding fields created_at and updated_at to the 'chat' table
+    migrator.add_fields(
+        "chat",
+        created_at=pw.DateTimeField(null=True),  # Allow null for transition
+        updated_at=pw.DateTimeField(null=True),  # Allow null for transition
+    )
+
+    # Populate the new fields from an existing 'timestamp' field
+    migrator.sql(
+        "UPDATE chat SET created_at = timestamp, updated_at = timestamp WHERE timestamp IS NOT NULL"
+    )
+
+    # Now that the data has been copied, remove the original 'timestamp' field
+    migrator.remove_fields("chat", "timestamp")
+
+    # Update the fields to be not null now that they are populated
+    migrator.change_fields(
+        "chat",
+        created_at=pw.DateTimeField(null=False),
+        updated_at=pw.DateTimeField(null=False),
+    )
+
+
+def migrate_external(migrator: Migrator, database: pw.Database, *, fake=False):
+    # Adding fields created_at and updated_at to the 'chat' table
+    migrator.add_fields(
+        "chat",
+        created_at=pw.BigIntegerField(null=True),  # Allow null for transition
+        updated_at=pw.BigIntegerField(null=True),  # Allow null for transition
+    )
+
+    # Populate the new fields from an existing 'timestamp' field
+    migrator.sql(
+        "UPDATE chat SET created_at = timestamp, updated_at = timestamp WHERE timestamp IS NOT NULL"
+    )
+
+    # Now that the data has been copied, remove the original 'timestamp' field
+    migrator.remove_fields("chat", "timestamp")
+
+    # Update the fields to be not null now that they are populated
+    migrator.change_fields(
+        "chat",
+        created_at=pw.BigIntegerField(null=False),
+        updated_at=pw.BigIntegerField(null=False),
+    )
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    if isinstance(database, pw.SqliteDatabase):
+        rollback_sqlite(migrator, database, fake=fake)
+    else:
+        rollback_external(migrator, database, fake=fake)
+
+
+def rollback_sqlite(migrator: Migrator, database: pw.Database, *, fake=False):
+    # Recreate the timestamp field initially allowing null values for safe transition
+    migrator.add_fields("chat", timestamp=pw.DateTimeField(null=True))
+
+    # Copy the earliest created_at date back into the new timestamp field
+    # This assumes created_at was originally a copy of timestamp
+    migrator.sql("UPDATE chat SET timestamp = created_at")
+
+    # Remove the created_at and updated_at fields
+    migrator.remove_fields("chat", "created_at", "updated_at")
+
+    # Finally, alter the timestamp field to not allow nulls if that was the original setting
+    migrator.change_fields("chat", timestamp=pw.DateTimeField(null=False))
+
+
+def rollback_external(migrator: Migrator, database: pw.Database, *, fake=False):
+    # Recreate the timestamp field initially allowing null values for safe transition
+    migrator.add_fields("chat", timestamp=pw.BigIntegerField(null=True))
+
+    # Copy the earliest created_at date back into the new timestamp field
+    # This assumes created_at was originally a copy of timestamp
+    migrator.sql("UPDATE chat SET timestamp = created_at")
+
+    # Remove the created_at and updated_at fields
+    migrator.remove_fields("chat", "created_at", "updated_at")
+
+    # Finally, alter the timestamp field to not allow nulls if that was the original setting
+    migrator.change_fields("chat", timestamp=pw.BigIntegerField(null=False))
diff --git a/backend/open_webui/apps/webui/internal/migrations/006_migrate_timestamps_and_charfields.py b/backend/open_webui/apps/webui/internal/migrations/006_migrate_timestamps_and_charfields.py
new file mode 100644
index 0000000000000000000000000000000000000000..caca14d323e1fad148e7e14bae207c7e1b8896a9
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/006_migrate_timestamps_and_charfields.py
@@ -0,0 +1,130 @@
+"""Peewee migrations -- 006_migrate_timestamps_and_charfields.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    # Alter the tables with timestamps
+    migrator.change_fields(
+        "chatidtag",
+        timestamp=pw.BigIntegerField(),
+    )
+    migrator.change_fields(
+        "document",
+        timestamp=pw.BigIntegerField(),
+    )
+    migrator.change_fields(
+        "modelfile",
+        timestamp=pw.BigIntegerField(),
+    )
+    migrator.change_fields(
+        "prompt",
+        timestamp=pw.BigIntegerField(),
+    )
+    migrator.change_fields(
+        "user",
+        timestamp=pw.BigIntegerField(),
+    )
+    # Alter the tables with varchar to text where necessary
+    migrator.change_fields(
+        "auth",
+        password=pw.TextField(),
+    )
+    migrator.change_fields(
+        "chat",
+        title=pw.TextField(),
+    )
+    migrator.change_fields(
+        "document",
+        title=pw.TextField(),
+        filename=pw.TextField(),
+    )
+    migrator.change_fields(
+        "prompt",
+        title=pw.TextField(),
+    )
+    migrator.change_fields(
+        "user",
+        profile_image_url=pw.TextField(),
+    )
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    if isinstance(database, pw.SqliteDatabase):
+        # Alter the tables with timestamps
+        migrator.change_fields(
+            "chatidtag",
+            timestamp=pw.DateField(),
+        )
+        migrator.change_fields(
+            "document",
+            timestamp=pw.DateField(),
+        )
+        migrator.change_fields(
+            "modelfile",
+            timestamp=pw.DateField(),
+        )
+        migrator.change_fields(
+            "prompt",
+            timestamp=pw.DateField(),
+        )
+        migrator.change_fields(
+            "user",
+            timestamp=pw.DateField(),
+        )
+    migrator.change_fields(
+        "auth",
+        password=pw.CharField(max_length=255),
+    )
+    migrator.change_fields(
+        "chat",
+        title=pw.CharField(),
+    )
+    migrator.change_fields(
+        "document",
+        title=pw.CharField(),
+        filename=pw.CharField(),
+    )
+    migrator.change_fields(
+        "prompt",
+        title=pw.CharField(),
+    )
+    migrator.change_fields(
+        "user",
+        profile_image_url=pw.CharField(),
+    )
diff --git a/backend/open_webui/apps/webui/internal/migrations/007_add_user_last_active_at.py b/backend/open_webui/apps/webui/internal/migrations/007_add_user_last_active_at.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd176ba73e51b15f74f45b839d0eb8e72fc63ecf
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/007_add_user_last_active_at.py
@@ -0,0 +1,79 @@
+"""Peewee migrations -- 002_add_local_sharing.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    # Adding fields created_at and updated_at to the 'user' table
+    migrator.add_fields(
+        "user",
+        created_at=pw.BigIntegerField(null=True),  # Allow null for transition
+        updated_at=pw.BigIntegerField(null=True),  # Allow null for transition
+        last_active_at=pw.BigIntegerField(null=True),  # Allow null for transition
+    )
+
+    # Populate the new fields from an existing 'timestamp' field
+    migrator.sql(
+        'UPDATE "user" SET created_at = timestamp, updated_at = timestamp, last_active_at = timestamp WHERE timestamp IS NOT NULL'
+    )
+
+    # Now that the data has been copied, remove the original 'timestamp' field
+    migrator.remove_fields("user", "timestamp")
+
+    # Update the fields to be not null now that they are populated
+    migrator.change_fields(
+        "user",
+        created_at=pw.BigIntegerField(null=False),
+        updated_at=pw.BigIntegerField(null=False),
+        last_active_at=pw.BigIntegerField(null=False),
+    )
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    # Recreate the timestamp field initially allowing null values for safe transition
+    migrator.add_fields("user", timestamp=pw.BigIntegerField(null=True))
+
+    # Copy the earliest created_at date back into the new timestamp field
+    # This assumes created_at was originally a copy of timestamp
+    migrator.sql('UPDATE "user" SET timestamp = created_at')
+
+    # Remove the created_at and updated_at fields
+    migrator.remove_fields("user", "created_at", "updated_at", "last_active_at")
+
+    # Finally, alter the timestamp field to not allow nulls if that was the original setting
+    migrator.change_fields("user", timestamp=pw.BigIntegerField(null=False))
diff --git a/backend/open_webui/apps/webui/internal/migrations/008_add_memory.py b/backend/open_webui/apps/webui/internal/migrations/008_add_memory.py
new file mode 100644
index 0000000000000000000000000000000000000000..9307aa4d5c933cf0ee98c0932ab0e48bc4cecbc6
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/008_add_memory.py
@@ -0,0 +1,53 @@
+"""Peewee migrations -- 002_add_local_sharing.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    @migrator.create_model
+    class Memory(pw.Model):
+        id = pw.CharField(max_length=255, unique=True)
+        user_id = pw.CharField(max_length=255)
+        content = pw.TextField(null=False)
+        updated_at = pw.BigIntegerField(null=False)
+        created_at = pw.BigIntegerField(null=False)
+
+        class Meta:
+            table_name = "memory"
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_model("memory")
diff --git a/backend/open_webui/apps/webui/internal/migrations/009_add_models.py b/backend/open_webui/apps/webui/internal/migrations/009_add_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..548ec7cdcabbc620f8c2c79b65709a6b08d9a11c
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/009_add_models.py
@@ -0,0 +1,61 @@
+"""Peewee migrations -- 009_add_models.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    @migrator.create_model
+    class Model(pw.Model):
+        id = pw.TextField(unique=True)
+        user_id = pw.TextField()
+        base_model_id = pw.TextField(null=True)
+
+        name = pw.TextField()
+
+        meta = pw.TextField()
+        params = pw.TextField()
+
+        created_at = pw.BigIntegerField(null=False)
+        updated_at = pw.BigIntegerField(null=False)
+
+        class Meta:
+            table_name = "model"
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_model("model")
diff --git a/backend/open_webui/apps/webui/internal/migrations/010_migrate_modelfiles_to_models.py b/backend/open_webui/apps/webui/internal/migrations/010_migrate_modelfiles_to_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..322ddd44ec9e810cdb351b903af5cbb8831e72e7
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/010_migrate_modelfiles_to_models.py
@@ -0,0 +1,130 @@
+"""Peewee migrations -- 009_add_models.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+import json
+
+from open_webui.utils.misc import parse_ollama_modelfile
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    # Fetch data from 'modelfile' table and insert into 'model' table
+    migrate_modelfile_to_model(migrator, database)
+    # Drop the 'modelfile' table
+    migrator.remove_model("modelfile")
+
+
+def migrate_modelfile_to_model(migrator: Migrator, database: pw.Database):
+    ModelFile = migrator.orm["modelfile"]
+    Model = migrator.orm["model"]
+
+    modelfiles = ModelFile.select()
+
+    for modelfile in modelfiles:
+        # Extract and transform data in Python
+
+        modelfile.modelfile = json.loads(modelfile.modelfile)
+        meta = json.dumps(
+            {
+                "description": modelfile.modelfile.get("desc"),
+                "profile_image_url": modelfile.modelfile.get("imageUrl"),
+                "ollama": {"modelfile": modelfile.modelfile.get("content")},
+                "suggestion_prompts": modelfile.modelfile.get("suggestionPrompts"),
+                "categories": modelfile.modelfile.get("categories"),
+                "user": {**modelfile.modelfile.get("user", {}), "community": True},
+            }
+        )
+
+        info = parse_ollama_modelfile(modelfile.modelfile.get("content"))
+
+        # Insert the processed data into the 'model' table
+        Model.create(
+            id=f"ollama-{modelfile.tag_name}",
+            user_id=modelfile.user_id,
+            base_model_id=info.get("base_model_id"),
+            name=modelfile.modelfile.get("title"),
+            meta=meta,
+            params=json.dumps(info.get("params", {})),
+            created_at=modelfile.timestamp,
+            updated_at=modelfile.timestamp,
+        )
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    recreate_modelfile_table(migrator, database)
+    move_data_back_to_modelfile(migrator, database)
+    migrator.remove_model("model")
+
+
+def recreate_modelfile_table(migrator: Migrator, database: pw.Database):
+    query = """
+    CREATE TABLE IF NOT EXISTS modelfile (
+        user_id TEXT,
+        tag_name TEXT,
+        modelfile JSON,
+        timestamp BIGINT
+    )
+    """
+    migrator.sql(query)
+
+
+def move_data_back_to_modelfile(migrator: Migrator, database: pw.Database):
+    Model = migrator.orm["model"]
+    Modelfile = migrator.orm["modelfile"]
+
+    models = Model.select()
+
+    for model in models:
+        # Extract and transform data in Python
+        meta = json.loads(model.meta)
+
+        modelfile_data = {
+            "title": model.name,
+            "desc": meta.get("description"),
+            "imageUrl": meta.get("profile_image_url"),
+            "content": meta.get("ollama", {}).get("modelfile"),
+            "suggestionPrompts": meta.get("suggestion_prompts"),
+            "categories": meta.get("categories"),
+            "user": {k: v for k, v in meta.get("user", {}).items() if k != "community"},
+        }
+
+        # Insert the processed data back into the 'modelfile' table
+        Modelfile.create(
+            user_id=model.user_id,
+            tag_name=model.id,
+            modelfile=modelfile_data,
+            timestamp=model.created_at,
+        )
diff --git a/backend/open_webui/apps/webui/internal/migrations/011_add_user_settings.py b/backend/open_webui/apps/webui/internal/migrations/011_add_user_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1620dcadae41891922e324a9a6b152b81ea0ec4
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/011_add_user_settings.py
@@ -0,0 +1,48 @@
+"""Peewee migrations -- 002_add_local_sharing.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    # Adding fields settings to the 'user' table
+    migrator.add_fields("user", settings=pw.TextField(null=True))
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    # Remove the settings field
+    migrator.remove_fields("user", "settings")
diff --git a/backend/open_webui/apps/webui/internal/migrations/012_add_tools.py b/backend/open_webui/apps/webui/internal/migrations/012_add_tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a68eea552e4fb9b5d5f64f55e7b7966f342435b
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/012_add_tools.py
@@ -0,0 +1,61 @@
+"""Peewee migrations -- 009_add_models.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    @migrator.create_model
+    class Tool(pw.Model):
+        id = pw.TextField(unique=True)
+        user_id = pw.TextField()
+
+        name = pw.TextField()
+        content = pw.TextField()
+        specs = pw.TextField()
+
+        meta = pw.TextField()
+
+        created_at = pw.BigIntegerField(null=False)
+        updated_at = pw.BigIntegerField(null=False)
+
+        class Meta:
+            table_name = "tool"
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_model("tool")
diff --git a/backend/open_webui/apps/webui/internal/migrations/013_add_user_info.py b/backend/open_webui/apps/webui/internal/migrations/013_add_user_info.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f68669cca869fcd5537d1a0600ed45155056437
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/013_add_user_info.py
@@ -0,0 +1,48 @@
+"""Peewee migrations -- 002_add_local_sharing.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    # Adding fields info to the 'user' table
+    migrator.add_fields("user", info=pw.TextField(null=True))
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    # Remove the settings field
+    migrator.remove_fields("user", "info")
diff --git a/backend/open_webui/apps/webui/internal/migrations/014_add_files.py b/backend/open_webui/apps/webui/internal/migrations/014_add_files.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e1acf0ad8b9510b8d090c6458f292a124dd73cd
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/014_add_files.py
@@ -0,0 +1,55 @@
+"""Peewee migrations -- 009_add_models.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    @migrator.create_model
+    class File(pw.Model):
+        id = pw.TextField(unique=True)
+        user_id = pw.TextField()
+        filename = pw.TextField()
+        meta = pw.TextField()
+        created_at = pw.BigIntegerField(null=False)
+
+        class Meta:
+            table_name = "file"
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_model("file")
diff --git a/backend/open_webui/apps/webui/internal/migrations/015_add_functions.py b/backend/open_webui/apps/webui/internal/migrations/015_add_functions.py
new file mode 100644
index 0000000000000000000000000000000000000000..8316a9333bad45eccf7d6708016ef5c45b208360
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/015_add_functions.py
@@ -0,0 +1,61 @@
+"""Peewee migrations -- 009_add_models.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    @migrator.create_model
+    class Function(pw.Model):
+        id = pw.TextField(unique=True)
+        user_id = pw.TextField()
+
+        name = pw.TextField()
+        type = pw.TextField()
+
+        content = pw.TextField()
+        meta = pw.TextField()
+
+        created_at = pw.BigIntegerField(null=False)
+        updated_at = pw.BigIntegerField(null=False)
+
+        class Meta:
+            table_name = "function"
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_model("function")
diff --git a/backend/open_webui/apps/webui/internal/migrations/016_add_valves_and_is_active.py b/backend/open_webui/apps/webui/internal/migrations/016_add_valves_and_is_active.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3af521b7e841ee01b226ad867ae509e42e5eb4f
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/016_add_valves_and_is_active.py
@@ -0,0 +1,50 @@
+"""Peewee migrations -- 009_add_models.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    migrator.add_fields("tool", valves=pw.TextField(null=True))
+    migrator.add_fields("function", valves=pw.TextField(null=True))
+    migrator.add_fields("function", is_active=pw.BooleanField(default=False))
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_fields("tool", "valves")
+    migrator.remove_fields("function", "valves")
+    migrator.remove_fields("function", "is_active")
diff --git a/backend/open_webui/apps/webui/internal/migrations/017_add_user_oauth_sub.py b/backend/open_webui/apps/webui/internal/migrations/017_add_user_oauth_sub.py
new file mode 100644
index 0000000000000000000000000000000000000000..eaa3fa5fe54bd37691593ba6fc2840b8653d534d
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/017_add_user_oauth_sub.py
@@ -0,0 +1,45 @@
+"""Peewee migrations -- 017_add_user_oauth_sub.py.
+Some examples (model - class or model name)::
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    migrator.add_fields(
+        "user",
+        oauth_sub=pw.TextField(null=True, unique=True),
+    )
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_fields("user", "oauth_sub")
diff --git a/backend/open_webui/apps/webui/internal/migrations/018_add_function_is_global.py b/backend/open_webui/apps/webui/internal/migrations/018_add_function_is_global.py
new file mode 100644
index 0000000000000000000000000000000000000000..04cdab705986a36227a7f73e800d6739f00033ce
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/migrations/018_add_function_is_global.py
@@ -0,0 +1,49 @@
+"""Peewee migrations -- 017_add_user_oauth_sub.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    migrator.add_fields(
+        "function",
+        is_global=pw.BooleanField(default=False),
+    )
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_fields("function", "is_global")
diff --git a/backend/open_webui/apps/webui/internal/wrappers.py b/backend/open_webui/apps/webui/internal/wrappers.py
new file mode 100644
index 0000000000000000000000000000000000000000..ccc62b9a5741c52f5a84ade064c6e935fb66dd8a
--- /dev/null
+++ b/backend/open_webui/apps/webui/internal/wrappers.py
@@ -0,0 +1,66 @@
+import logging
+from contextvars import ContextVar
+
+from open_webui.env import SRC_LOG_LEVELS
+from peewee import *
+from peewee import InterfaceError as PeeWeeInterfaceError
+from peewee import PostgresqlDatabase
+from playhouse.db_url import connect, parse
+from playhouse.shortcuts import ReconnectMixin
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["DB"])
+
+db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None}
+db_state = ContextVar("db_state", default=db_state_default.copy())
+
+
+class PeeweeConnectionState(object):
+    def __init__(self, **kwargs):
+        super().__setattr__("_state", db_state)
+        super().__init__(**kwargs)
+
+    def __setattr__(self, name, value):
+        self._state.get()[name] = value
+
+    def __getattr__(self, name):
+        value = self._state.get()[name]
+        return value
+
+
+class CustomReconnectMixin(ReconnectMixin):
+    reconnect_errors = (
+        # psycopg2
+        (OperationalError, "termin"),
+        (InterfaceError, "closed"),
+        # peewee
+        (PeeWeeInterfaceError, "closed"),
+    )
+
+
+class ReconnectingPostgresqlDatabase(CustomReconnectMixin, PostgresqlDatabase):
+    pass
+
+
+def register_connection(db_url):
+    db = connect(db_url, unquote_password=True)
+    if isinstance(db, PostgresqlDatabase):
+        # Enable autoconnect for SQLite databases, managed by Peewee
+        db.autoconnect = True
+        db.reuse_if_open = True
+        log.info("Connected to PostgreSQL database")
+
+        # Get the connection details
+        connection = parse(db_url, unquote_password=True)
+
+        # Use our custom database class that supports reconnection
+        db = ReconnectingPostgresqlDatabase(**connection)
+        db.connect(reuse_if_open=True)
+    elif isinstance(db, SqliteDatabase):
+        # Enable autoconnect for SQLite databases, managed by Peewee
+        db.autoconnect = True
+        db.reuse_if_open = True
+        log.info("Connected to SQLite database")
+    else:
+        raise ValueError("Unsupported database connection")
+    return db
diff --git a/backend/open_webui/apps/webui/main.py b/backend/open_webui/apps/webui/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..b32c83f88ca579abf0b2a4cbef7b47d4a8611e8e
--- /dev/null
+++ b/backend/open_webui/apps/webui/main.py
@@ -0,0 +1,458 @@
+import inspect
+import json
+import logging
+import time
+from typing import AsyncGenerator, Generator, Iterator
+
+from open_webui.apps.socket.main import get_event_call, get_event_emitter
+from open_webui.apps.webui.models.functions import Functions
+from open_webui.apps.webui.models.models import Models
+from open_webui.apps.webui.routers import (
+    auths,
+    chats,
+    folders,
+    configs,
+    files,
+    functions,
+    memories,
+    models,
+    knowledge,
+    prompts,
+    evaluations,
+    tools,
+    users,
+    utils,
+)
+from open_webui.apps.webui.utils import load_function_module_by_id
+from open_webui.config import (
+    ADMIN_EMAIL,
+    CORS_ALLOW_ORIGIN,
+    DEFAULT_MODELS,
+    DEFAULT_PROMPT_SUGGESTIONS,
+    DEFAULT_USER_ROLE,
+    ENABLE_COMMUNITY_SHARING,
+    ENABLE_LOGIN_FORM,
+    ENABLE_MESSAGE_RATING,
+    ENABLE_SIGNUP,
+    ENABLE_EVALUATION_ARENA_MODELS,
+    EVALUATION_ARENA_MODELS,
+    DEFAULT_ARENA_MODEL,
+    JWT_EXPIRES_IN,
+    ENABLE_OAUTH_ROLE_MANAGEMENT,
+    OAUTH_ROLES_CLAIM,
+    OAUTH_EMAIL_CLAIM,
+    OAUTH_PICTURE_CLAIM,
+    OAUTH_USERNAME_CLAIM,
+    OAUTH_ALLOWED_ROLES,
+    OAUTH_ADMIN_ROLES,
+    SHOW_ADMIN_DETAILS,
+    USER_PERMISSIONS,
+    WEBHOOK_URL,
+    WEBUI_AUTH,
+    WEBUI_BANNERS,
+    AppConfig,
+)
+from open_webui.env import (
+    ENV,
+    WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
+    WEBUI_AUTH_TRUSTED_NAME_HEADER,
+)
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import StreamingResponse
+from pydantic import BaseModel
+from open_webui.utils.misc import (
+    openai_chat_chunk_message_template,
+    openai_chat_completion_message_template,
+)
+from open_webui.utils.payload import (
+    apply_model_params_to_body_openai,
+    apply_model_system_prompt_to_body,
+)
+
+
+from open_webui.utils.tools import get_tools
+
+app = FastAPI(docs_url="/docs" if ENV == "dev" else None, openapi_url="/openapi.json" if ENV == "dev" else None, redoc_url=None)
+
+log = logging.getLogger(__name__)
+
+app.state.config = AppConfig()
+
+app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP
+app.state.config.ENABLE_LOGIN_FORM = ENABLE_LOGIN_FORM
+app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN
+app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
+app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER
+
+
+app.state.config.SHOW_ADMIN_DETAILS = SHOW_ADMIN_DETAILS
+app.state.config.ADMIN_EMAIL = ADMIN_EMAIL
+
+
+app.state.config.DEFAULT_MODELS = DEFAULT_MODELS
+app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
+app.state.config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
+app.state.config.USER_PERMISSIONS = USER_PERMISSIONS
+app.state.config.WEBHOOK_URL = WEBHOOK_URL
+app.state.config.BANNERS = WEBUI_BANNERS
+
+app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING
+app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING
+
+app.state.config.ENABLE_EVALUATION_ARENA_MODELS = ENABLE_EVALUATION_ARENA_MODELS
+app.state.config.EVALUATION_ARENA_MODELS = EVALUATION_ARENA_MODELS
+
+app.state.config.OAUTH_USERNAME_CLAIM = OAUTH_USERNAME_CLAIM
+app.state.config.OAUTH_PICTURE_CLAIM = OAUTH_PICTURE_CLAIM
+app.state.config.OAUTH_EMAIL_CLAIM = OAUTH_EMAIL_CLAIM
+
+app.state.config.ENABLE_OAUTH_ROLE_MANAGEMENT = ENABLE_OAUTH_ROLE_MANAGEMENT
+app.state.config.OAUTH_ROLES_CLAIM = OAUTH_ROLES_CLAIM
+app.state.config.OAUTH_ALLOWED_ROLES = OAUTH_ALLOWED_ROLES
+app.state.config.OAUTH_ADMIN_ROLES = OAUTH_ADMIN_ROLES
+
+app.state.MODELS = {}
+app.state.TOOLS = {}
+app.state.FUNCTIONS = {}
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=CORS_ALLOW_ORIGIN,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+
+app.include_router(configs.router, prefix="/configs", tags=["configs"])
+
+app.include_router(auths.router, prefix="/auths", tags=["auths"])
+app.include_router(users.router, prefix="/users", tags=["users"])
+
+app.include_router(chats.router, prefix="/chats", tags=["chats"])
+
+app.include_router(models.router, prefix="/models", tags=["models"])
+app.include_router(knowledge.router, prefix="/knowledge", tags=["knowledge"])
+app.include_router(prompts.router, prefix="/prompts", tags=["prompts"])
+app.include_router(tools.router, prefix="/tools", tags=["tools"])
+app.include_router(functions.router, prefix="/functions", tags=["functions"])
+
+app.include_router(memories.router, prefix="/memories", tags=["memories"])
+app.include_router(evaluations.router, prefix="/evaluations", tags=["evaluations"])
+
+app.include_router(folders.router, prefix="/folders", tags=["folders"])
+app.include_router(files.router, prefix="/files", tags=["files"])
+
+app.include_router(utils.router, prefix="/utils", tags=["utils"])
+
+
+@app.get("/")
+async def get_status():
+    return {
+        "status": True,
+        "auth": WEBUI_AUTH,
+        "default_models": app.state.config.DEFAULT_MODELS,
+        "default_prompt_suggestions": app.state.config.DEFAULT_PROMPT_SUGGESTIONS,
+    }
+
+
+async def get_all_models():
+    models = []
+    pipe_models = await get_pipe_models()
+    models = models + pipe_models
+
+    if app.state.config.ENABLE_EVALUATION_ARENA_MODELS:
+        arena_models = []
+        if len(app.state.config.EVALUATION_ARENA_MODELS) > 0:
+            arena_models = [
+                {
+                    "id": model["id"],
+                    "name": model["name"],
+                    "info": {
+                        "meta": model["meta"],
+                    },
+                    "object": "model",
+                    "created": int(time.time()),
+                    "owned_by": "arena",
+                    "arena": True,
+                }
+                for model in app.state.config.EVALUATION_ARENA_MODELS
+            ]
+        else:
+            # Add default arena model
+            arena_models = [
+                {
+                    "id": DEFAULT_ARENA_MODEL["id"],
+                    "name": DEFAULT_ARENA_MODEL["name"],
+                    "info": {
+                        "meta": DEFAULT_ARENA_MODEL["meta"],
+                    },
+                    "object": "model",
+                    "created": int(time.time()),
+                    "owned_by": "arena",
+                    "arena": True,
+                }
+            ]
+        models = models + arena_models
+    return models
+
+
+def get_function_module(pipe_id: str):
+    # Check if function is already loaded
+    if pipe_id not in app.state.FUNCTIONS:
+        function_module, _, _ = load_function_module_by_id(pipe_id)
+        app.state.FUNCTIONS[pipe_id] = function_module
+    else:
+        function_module = app.state.FUNCTIONS[pipe_id]
+
+    if hasattr(function_module, "valves") and hasattr(function_module, "Valves"):
+        valves = Functions.get_function_valves_by_id(pipe_id)
+        function_module.valves = function_module.Valves(**(valves if valves else {}))
+    return function_module
+
+
+async def get_pipe_models():
+    pipes = Functions.get_functions_by_type("pipe", active_only=True)
+    pipe_models = []
+
+    for pipe in pipes:
+        function_module = get_function_module(pipe.id)
+
+        # Check if function is a manifold
+        if hasattr(function_module, "pipes"):
+            sub_pipes = []
+
+            # Check if pipes is a function or a list
+
+            try:
+                if callable(function_module.pipes):
+                    sub_pipes = function_module.pipes()
+                else:
+                    sub_pipes = function_module.pipes
+            except Exception as e:
+                log.exception(e)
+                sub_pipes = []
+
+            print(sub_pipes)
+
+            for p in sub_pipes:
+                sub_pipe_id = f'{pipe.id}.{p["id"]}'
+                sub_pipe_name = p["name"]
+
+                if hasattr(function_module, "name"):
+                    sub_pipe_name = f"{function_module.name}{sub_pipe_name}"
+
+                pipe_flag = {"type": pipe.type}
+                pipe_models.append(
+                    {
+                        "id": sub_pipe_id,
+                        "name": sub_pipe_name,
+                        "object": "model",
+                        "created": pipe.created_at,
+                        "owned_by": "openai",
+                        "pipe": pipe_flag,
+                    }
+                )
+        else:
+            pipe_flag = {"type": "pipe"}
+
+            pipe_models.append(
+                {
+                    "id": pipe.id,
+                    "name": pipe.name,
+                    "object": "model",
+                    "created": pipe.created_at,
+                    "owned_by": "openai",
+                    "pipe": pipe_flag,
+                }
+            )
+
+    return pipe_models
+
+
+async def execute_pipe(pipe, params):
+    if inspect.iscoroutinefunction(pipe):
+        return await pipe(**params)
+    else:
+        return pipe(**params)
+
+
+async def get_message_content(res: str | Generator | AsyncGenerator) -> str:
+    if isinstance(res, str):
+        return res
+    if isinstance(res, Generator):
+        return "".join(map(str, res))
+    if isinstance(res, AsyncGenerator):
+        return "".join([str(stream) async for stream in res])
+
+
+def process_line(form_data: dict, line):
+    if isinstance(line, BaseModel):
+        line = line.model_dump_json()
+        line = f"data: {line}"
+    if isinstance(line, dict):
+        line = f"data: {json.dumps(line)}"
+
+    try:
+        line = line.decode("utf-8")
+    except Exception:
+        pass
+
+    if line.startswith("data:"):
+        return f"{line}\n\n"
+    else:
+        line = openai_chat_chunk_message_template(form_data["model"], line)
+        return f"data: {json.dumps(line)}\n\n"
+
+
+def get_pipe_id(form_data: dict) -> str:
+    pipe_id = form_data["model"]
+    if "." in pipe_id:
+        pipe_id, _ = pipe_id.split(".", 1)
+    print(pipe_id)
+    return pipe_id
+
+
+def get_function_params(function_module, form_data, user, extra_params=None):
+    if extra_params is None:
+        extra_params = {}
+
+    pipe_id = get_pipe_id(form_data)
+
+    # Get the signature of the function
+    sig = inspect.signature(function_module.pipe)
+    params = {"body": form_data} | {
+        k: v for k, v in extra_params.items() if k in sig.parameters
+    }
+
+    if "__user__" in params and hasattr(function_module, "UserValves"):
+        user_valves = Functions.get_user_valves_by_id_and_user_id(pipe_id, user.id)
+        try:
+            params["__user__"]["valves"] = function_module.UserValves(**user_valves)
+        except Exception as e:
+            log.exception(e)
+            params["__user__"]["valves"] = function_module.UserValves()
+
+    return params
+
+
+async def generate_function_chat_completion(form_data, user):
+    model_id = form_data.get("model")
+    model_info = Models.get_model_by_id(model_id)
+
+    metadata = form_data.pop("metadata", {})
+
+    files = metadata.get("files", [])
+    tool_ids = metadata.get("tool_ids", [])
+    # Check if tool_ids is None
+    if tool_ids is None:
+        tool_ids = []
+
+    __event_emitter__ = None
+    __event_call__ = None
+    __task__ = None
+    __task_body__ = None
+
+    if metadata:
+        if all(k in metadata for k in ("session_id", "chat_id", "message_id")):
+            __event_emitter__ = get_event_emitter(metadata)
+            __event_call__ = get_event_call(metadata)
+        __task__ = metadata.get("task", None)
+        __task_body__ = metadata.get("task_body", None)
+
+    extra_params = {
+        "__event_emitter__": __event_emitter__,
+        "__event_call__": __event_call__,
+        "__task__": __task__,
+        "__task_body__": __task_body__,
+        "__files__": files,
+        "__user__": {
+            "id": user.id,
+            "email": user.email,
+            "name": user.name,
+            "role": user.role,
+        },
+    }
+    extra_params["__tools__"] = get_tools(
+        app,
+        tool_ids,
+        user,
+        {
+            **extra_params,
+            "__model__": app.state.MODELS[form_data["model"]],
+            "__messages__": form_data["messages"],
+            "__files__": files,
+        },
+    )
+
+    if model_info:
+        if model_info.base_model_id:
+            form_data["model"] = model_info.base_model_id
+
+        params = model_info.params.model_dump()
+        form_data = apply_model_params_to_body_openai(params, form_data)
+        form_data = apply_model_system_prompt_to_body(params, form_data, user)
+
+    pipe_id = get_pipe_id(form_data)
+    function_module = get_function_module(pipe_id)
+
+    pipe = function_module.pipe
+    params = get_function_params(function_module, form_data, user, extra_params)
+
+    if form_data.get("stream", False):
+
+        async def stream_content():
+            try:
+                res = await execute_pipe(pipe, params)
+
+                # Directly return if the response is a StreamingResponse
+                if isinstance(res, StreamingResponse):
+                    async for data in res.body_iterator:
+                        yield data
+                    return
+                if isinstance(res, dict):
+                    yield f"data: {json.dumps(res)}\n\n"
+                    return
+
+            except Exception as e:
+                print(f"Error: {e}")
+                yield f"data: {json.dumps({'error': {'detail':str(e)}})}\n\n"
+                return
+
+            if isinstance(res, str):
+                message = openai_chat_chunk_message_template(form_data["model"], res)
+                yield f"data: {json.dumps(message)}\n\n"
+
+            if isinstance(res, Iterator):
+                for line in res:
+                    yield process_line(form_data, line)
+
+            if isinstance(res, AsyncGenerator):
+                async for line in res:
+                    yield process_line(form_data, line)
+
+            if isinstance(res, str) or isinstance(res, Generator):
+                finish_message = openai_chat_chunk_message_template(
+                    form_data["model"], ""
+                )
+                finish_message["choices"][0]["finish_reason"] = "stop"
+                yield f"data: {json.dumps(finish_message)}\n\n"
+                yield "data: [DONE]"
+
+        return StreamingResponse(stream_content(), media_type="text/event-stream")
+    else:
+        try:
+            res = await execute_pipe(pipe, params)
+
+        except Exception as e:
+            print(f"Error: {e}")
+            return {"error": {"detail": str(e)}}
+
+        if isinstance(res, StreamingResponse) or isinstance(res, dict):
+            return res
+        if isinstance(res, BaseModel):
+            return res.model_dump()
+
+        message = await get_message_content(res)
+        return openai_chat_completion_message_template(form_data["model"], message)
diff --git a/backend/open_webui/apps/webui/models/auths.py b/backend/open_webui/apps/webui/models/auths.py
new file mode 100644
index 0000000000000000000000000000000000000000..167b9f6dcb17abf173f8fc82b755bbe18f42d45f
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/auths.py
@@ -0,0 +1,201 @@
+import logging
+import uuid
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, get_db
+from open_webui.apps.webui.models.users import UserModel, Users
+from open_webui.env import SRC_LOG_LEVELS
+from pydantic import BaseModel
+from sqlalchemy import Boolean, Column, String, Text
+from open_webui.utils.utils import verify_password
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+####################
+# DB MODEL
+####################
+
+
+class Auth(Base):
+    __tablename__ = "auth"
+
+    id = Column(String, primary_key=True)
+    email = Column(String)
+    password = Column(Text)
+    active = Column(Boolean)
+
+
+class AuthModel(BaseModel):
+    id: str
+    email: str
+    password: str
+    active: bool = True
+
+
+####################
+# Forms
+####################
+
+
+class Token(BaseModel):
+    token: str
+    token_type: str
+
+
+class ApiKey(BaseModel):
+    api_key: Optional[str] = None
+
+
+class UserResponse(BaseModel):
+    id: str
+    email: str
+    name: str
+    role: str
+    profile_image_url: str
+
+
+class SigninResponse(Token, UserResponse):
+    pass
+
+
+class SigninForm(BaseModel):
+    email: str
+    password: str
+
+
+class ProfileImageUrlForm(BaseModel):
+    profile_image_url: str
+
+
+class UpdateProfileForm(BaseModel):
+    profile_image_url: str
+    name: str
+
+
+class UpdatePasswordForm(BaseModel):
+    password: str
+    new_password: str
+
+
+class SignupForm(BaseModel):
+    name: str
+    email: str
+    password: str
+    profile_image_url: Optional[str] = "/user.png"
+
+
+class AddUserForm(SignupForm):
+    role: Optional[str] = "pending"
+
+
+class AuthsTable:
+    def insert_new_auth(
+        self,
+        email: str,
+        password: str,
+        name: str,
+        profile_image_url: str = "/user.png",
+        role: str = "pending",
+        oauth_sub: Optional[str] = None,
+    ) -> Optional[UserModel]:
+        with get_db() as db:
+            log.info("insert_new_auth")
+
+            id = str(uuid.uuid4())
+
+            auth = AuthModel(
+                **{"id": id, "email": email, "password": password, "active": True}
+            )
+            result = Auth(**auth.model_dump())
+            db.add(result)
+
+            user = Users.insert_new_user(
+                id, name, email, profile_image_url, role, oauth_sub
+            )
+
+            db.commit()
+            db.refresh(result)
+
+            if result and user:
+                return user
+            else:
+                return None
+
+    def authenticate_user(self, email: str, password: str) -> Optional[UserModel]:
+        log.info(f"authenticate_user: {email}")
+        try:
+            with get_db() as db:
+                auth = db.query(Auth).filter_by(email=email, active=True).first()
+                if auth:
+                    if verify_password(password, auth.password):
+                        user = Users.get_user_by_id(auth.id)
+                        return user
+                    else:
+                        return None
+                else:
+                    return None
+        except Exception:
+            return None
+
+    def authenticate_user_by_api_key(self, api_key: str) -> Optional[UserModel]:
+        log.info(f"authenticate_user_by_api_key: {api_key}")
+        # if no api_key, return None
+        if not api_key:
+            return None
+
+        try:
+            user = Users.get_user_by_api_key(api_key)
+            return user if user else None
+        except Exception:
+            return False
+
+    def authenticate_user_by_trusted_header(self, email: str) -> Optional[UserModel]:
+        log.info(f"authenticate_user_by_trusted_header: {email}")
+        try:
+            with get_db() as db:
+                auth = db.query(Auth).filter_by(email=email, active=True).first()
+                if auth:
+                    user = Users.get_user_by_id(auth.id)
+                    return user
+        except Exception:
+            return None
+
+    def update_user_password_by_id(self, id: str, new_password: str) -> bool:
+        try:
+            with get_db() as db:
+                result = (
+                    db.query(Auth).filter_by(id=id).update({"password": new_password})
+                )
+                db.commit()
+                return True if result == 1 else False
+        except Exception:
+            return False
+
+    def update_email_by_id(self, id: str, email: str) -> bool:
+        try:
+            with get_db() as db:
+                result = db.query(Auth).filter_by(id=id).update({"email": email})
+                db.commit()
+                return True if result == 1 else False
+        except Exception:
+            return False
+
+    def delete_auth_by_id(self, id: str) -> bool:
+        try:
+            with get_db() as db:
+                # Delete User
+                result = Users.delete_user_by_id(id)
+
+                if result:
+                    db.query(Auth).filter_by(id=id).delete()
+                    db.commit()
+
+                    return True
+                else:
+                    return False
+        except Exception:
+            return False
+
+
+Auths = AuthsTable()
diff --git a/backend/open_webui/apps/webui/models/chats.py b/backend/open_webui/apps/webui/models/chats.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6a1e45483cf4d8d45914a28b0448a7a0cdef299
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/chats.py
@@ -0,0 +1,805 @@
+import json
+import time
+import uuid
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, get_db
+from open_webui.apps.webui.models.tags import TagModel, Tag, Tags
+
+
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Boolean, Column, String, Text, JSON
+from sqlalchemy import or_, func, select, and_, text
+from sqlalchemy.sql import exists
+
+####################
+# Chat DB Schema
+####################
+
+
+class Chat(Base):
+    __tablename__ = "chat"
+
+    id = Column(String, primary_key=True)
+    user_id = Column(String)
+    title = Column(Text)
+    chat = Column(JSON)
+
+    created_at = Column(BigInteger)
+    updated_at = Column(BigInteger)
+
+    share_id = Column(Text, unique=True, nullable=True)
+    archived = Column(Boolean, default=False)
+    pinned = Column(Boolean, default=False, nullable=True)
+
+    meta = Column(JSON, server_default="{}")
+    folder_id = Column(Text, nullable=True)
+
+
+class ChatModel(BaseModel):
+    model_config = ConfigDict(from_attributes=True)
+
+    id: str
+    user_id: str
+    title: str
+    chat: dict
+
+    created_at: int  # timestamp in epoch
+    updated_at: int  # timestamp in epoch
+
+    share_id: Optional[str] = None
+    archived: bool = False
+    pinned: Optional[bool] = False
+
+    meta: dict = {}
+    folder_id: Optional[str] = None
+
+
+####################
+# Forms
+####################
+
+
+class ChatForm(BaseModel):
+    chat: dict
+
+
+class ChatImportForm(ChatForm):
+    meta: Optional[dict] = {}
+    pinned: Optional[bool] = False
+    folder_id: Optional[str] = None
+
+
+class ChatTitleMessagesForm(BaseModel):
+    title: str
+    messages: list[dict]
+
+
+class ChatTitleForm(BaseModel):
+    title: str
+
+
+class ChatResponse(BaseModel):
+    id: str
+    user_id: str
+    title: str
+    chat: dict
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+    share_id: Optional[str] = None  # id of the chat to be shared
+    archived: bool
+    pinned: Optional[bool] = False
+    meta: dict = {}
+    folder_id: Optional[str] = None
+
+
+class ChatTitleIdResponse(BaseModel):
+    id: str
+    title: str
+    updated_at: int
+    created_at: int
+
+
+class ChatTable:
+    def insert_new_chat(self, user_id: str, form_data: ChatForm) -> Optional[ChatModel]:
+        with get_db() as db:
+            id = str(uuid.uuid4())
+            chat = ChatModel(
+                **{
+                    "id": id,
+                    "user_id": user_id,
+                    "title": (
+                        form_data.chat["title"]
+                        if "title" in form_data.chat
+                        else "New Chat"
+                    ),
+                    "chat": form_data.chat,
+                    "created_at": int(time.time()),
+                    "updated_at": int(time.time()),
+                }
+            )
+
+            result = Chat(**chat.model_dump())
+            db.add(result)
+            db.commit()
+            db.refresh(result)
+            return ChatModel.model_validate(result) if result else None
+
+    def import_chat(
+        self, user_id: str, form_data: ChatImportForm
+    ) -> Optional[ChatModel]:
+        with get_db() as db:
+            id = str(uuid.uuid4())
+            chat = ChatModel(
+                **{
+                    "id": id,
+                    "user_id": user_id,
+                    "title": (
+                        form_data.chat["title"]
+                        if "title" in form_data.chat
+                        else "New Chat"
+                    ),
+                    "chat": form_data.chat,
+                    "meta": form_data.meta,
+                    "pinned": form_data.pinned,
+                    "folder_id": form_data.folder_id,
+                    "created_at": int(time.time()),
+                    "updated_at": int(time.time()),
+                }
+            )
+
+            result = Chat(**chat.model_dump())
+            db.add(result)
+            db.commit()
+            db.refresh(result)
+            return ChatModel.model_validate(result) if result else None
+
+    def update_chat_by_id(self, id: str, chat: dict) -> Optional[ChatModel]:
+        try:
+            with get_db() as db:
+                chat_item = db.get(Chat, id)
+                chat_item.chat = chat
+                chat_item.title = chat["title"] if "title" in chat else "New Chat"
+                chat_item.updated_at = int(time.time())
+                db.commit()
+                db.refresh(chat_item)
+
+                return ChatModel.model_validate(chat_item)
+        except Exception:
+            return None
+
+    def insert_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]:
+        with get_db() as db:
+            # Get the existing chat to share
+            chat = db.get(Chat, chat_id)
+            # Check if the chat is already shared
+            if chat.share_id:
+                return self.get_chat_by_id_and_user_id(chat.share_id, "shared")
+            # Create a new chat with the same data, but with a new ID
+            shared_chat = ChatModel(
+                **{
+                    "id": str(uuid.uuid4()),
+                    "user_id": f"shared-{chat_id}",
+                    "title": chat.title,
+                    "chat": chat.chat,
+                    "created_at": chat.created_at,
+                    "updated_at": int(time.time()),
+                }
+            )
+            shared_result = Chat(**shared_chat.model_dump())
+            db.add(shared_result)
+            db.commit()
+            db.refresh(shared_result)
+
+            # Update the original chat with the share_id
+            result = (
+                db.query(Chat)
+                .filter_by(id=chat_id)
+                .update({"share_id": shared_chat.id})
+            )
+            db.commit()
+            return shared_chat if (shared_result and result) else None
+
+    def update_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]:
+        try:
+            with get_db() as db:
+                print("update_shared_chat_by_id")
+                chat = db.get(Chat, chat_id)
+                print(chat)
+                chat.title = chat.title
+                chat.chat = chat.chat
+                db.commit()
+                db.refresh(chat)
+
+                return self.get_chat_by_id(chat.share_id)
+        except Exception:
+            return None
+
+    def delete_shared_chat_by_chat_id(self, chat_id: str) -> bool:
+        try:
+            with get_db() as db:
+                db.query(Chat).filter_by(user_id=f"shared-{chat_id}").delete()
+                db.commit()
+
+                return True
+        except Exception:
+            return False
+
+    def update_chat_share_id_by_id(
+        self, id: str, share_id: Optional[str]
+    ) -> Optional[ChatModel]:
+        try:
+            with get_db() as db:
+                chat = db.get(Chat, id)
+                chat.share_id = share_id
+                db.commit()
+                db.refresh(chat)
+                return ChatModel.model_validate(chat)
+        except Exception:
+            return None
+
+    def toggle_chat_pinned_by_id(self, id: str) -> Optional[ChatModel]:
+        try:
+            with get_db() as db:
+                chat = db.get(Chat, id)
+                chat.pinned = not chat.pinned
+                chat.updated_at = int(time.time())
+                db.commit()
+                db.refresh(chat)
+                return ChatModel.model_validate(chat)
+        except Exception:
+            return None
+
+    def toggle_chat_archive_by_id(self, id: str) -> Optional[ChatModel]:
+        try:
+            with get_db() as db:
+                chat = db.get(Chat, id)
+                chat.archived = not chat.archived
+                chat.updated_at = int(time.time())
+                db.commit()
+                db.refresh(chat)
+                return ChatModel.model_validate(chat)
+        except Exception:
+            return None
+
+    def archive_all_chats_by_user_id(self, user_id: str) -> bool:
+        try:
+            with get_db() as db:
+                db.query(Chat).filter_by(user_id=user_id).update({"archived": True})
+                db.commit()
+                return True
+        except Exception:
+            return False
+
+    def get_archived_chat_list_by_user_id(
+        self, user_id: str, skip: int = 0, limit: int = 50
+    ) -> list[ChatModel]:
+        with get_db() as db:
+            all_chats = (
+                db.query(Chat)
+                .filter_by(user_id=user_id, archived=True)
+                .order_by(Chat.updated_at.desc())
+                # .limit(limit).offset(skip)
+                .all()
+            )
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def get_chat_list_by_user_id(
+        self,
+        user_id: str,
+        include_archived: bool = False,
+        skip: int = 0,
+        limit: int = 50,
+    ) -> list[ChatModel]:
+        with get_db() as db:
+            query = db.query(Chat).filter_by(user_id=user_id).filter_by(folder_id=None)
+            if not include_archived:
+                query = query.filter_by(archived=False)
+
+            query = query.order_by(Chat.updated_at.desc())
+
+            if skip:
+                query = query.offset(skip)
+            if limit:
+                query = query.limit(limit)
+
+            all_chats = query.all()
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def get_chat_title_id_list_by_user_id(
+        self,
+        user_id: str,
+        include_archived: bool = False,
+        skip: Optional[int] = None,
+        limit: Optional[int] = None,
+    ) -> list[ChatTitleIdResponse]:
+        with get_db() as db:
+            query = db.query(Chat).filter_by(user_id=user_id).filter_by(folder_id=None)
+            query = query.filter(or_(Chat.pinned == False, Chat.pinned == None))
+
+            if not include_archived:
+                query = query.filter_by(archived=False)
+
+            query = query.order_by(Chat.updated_at.desc()).with_entities(
+                Chat.id, Chat.title, Chat.updated_at, Chat.created_at
+            )
+
+            if skip:
+                query = query.offset(skip)
+            if limit:
+                query = query.limit(limit)
+
+            all_chats = query.all()
+
+            # result has to be destrctured from sqlalchemy `row` and mapped to a dict since the `ChatModel`is not the returned dataclass.
+            return [
+                ChatTitleIdResponse.model_validate(
+                    {
+                        "id": chat[0],
+                        "title": chat[1],
+                        "updated_at": chat[2],
+                        "created_at": chat[3],
+                    }
+                )
+                for chat in all_chats
+            ]
+
+    def get_chat_list_by_chat_ids(
+        self, chat_ids: list[str], skip: int = 0, limit: int = 50
+    ) -> list[ChatModel]:
+        with get_db() as db:
+            all_chats = (
+                db.query(Chat)
+                .filter(Chat.id.in_(chat_ids))
+                .filter_by(archived=False)
+                .order_by(Chat.updated_at.desc())
+                .all()
+            )
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def get_chat_by_id(self, id: str) -> Optional[ChatModel]:
+        try:
+            with get_db() as db:
+                chat = db.get(Chat, id)
+                return ChatModel.model_validate(chat)
+        except Exception:
+            return None
+
+    def get_chat_by_share_id(self, id: str) -> Optional[ChatModel]:
+        try:
+            with get_db() as db:
+                chat = db.query(Chat).filter_by(share_id=id).first()
+
+                if chat:
+                    return self.get_chat_by_id(id)
+                else:
+                    return None
+        except Exception:
+            return None
+
+    def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]:
+        try:
+            with get_db() as db:
+                chat = db.query(Chat).filter_by(id=id, user_id=user_id).first()
+                return ChatModel.model_validate(chat)
+        except Exception:
+            return None
+
+    def get_chats(self, skip: int = 0, limit: int = 50) -> list[ChatModel]:
+        with get_db() as db:
+            all_chats = (
+                db.query(Chat)
+                # .limit(limit).offset(skip)
+                .order_by(Chat.updated_at.desc())
+            )
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def get_chats_by_user_id(self, user_id: str) -> list[ChatModel]:
+        with get_db() as db:
+            all_chats = (
+                db.query(Chat)
+                .filter_by(user_id=user_id)
+                .order_by(Chat.updated_at.desc())
+            )
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def get_pinned_chats_by_user_id(self, user_id: str) -> list[ChatModel]:
+        with get_db() as db:
+            all_chats = (
+                db.query(Chat)
+                .filter_by(user_id=user_id, pinned=True, archived=False)
+                .order_by(Chat.updated_at.desc())
+            )
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def get_archived_chats_by_user_id(self, user_id: str) -> list[ChatModel]:
+        with get_db() as db:
+            all_chats = (
+                db.query(Chat)
+                .filter_by(user_id=user_id, archived=True)
+                .order_by(Chat.updated_at.desc())
+            )
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def get_chats_by_user_id_and_search_text(
+        self,
+        user_id: str,
+        search_text: str,
+        include_archived: bool = False,
+        skip: int = 0,
+        limit: int = 60,
+    ) -> list[ChatModel]:
+        """
+        Filters chats based on a search query using Python, allowing pagination using skip and limit.
+        """
+        search_text = search_text.lower().strip()
+
+        if not search_text:
+            return self.get_chat_list_by_user_id(user_id, include_archived, skip, limit)
+
+        search_text_words = search_text.split(" ")
+
+        # search_text might contain 'tag:tag_name' format so we need to extract the tag_name, split the search_text and remove the tags
+        tag_ids = [
+            word.replace("tag:", "").replace(" ", "_").lower()
+            for word in search_text_words
+            if word.startswith("tag:")
+        ]
+
+        search_text_words = [
+            word for word in search_text_words if not word.startswith("tag:")
+        ]
+
+        search_text = " ".join(search_text_words)
+
+        with get_db() as db:
+            query = db.query(Chat).filter(Chat.user_id == user_id)
+
+            if not include_archived:
+                query = query.filter(Chat.archived == False)
+
+            query = query.order_by(Chat.updated_at.desc())
+
+            # Check if the database dialect is either 'sqlite' or 'postgresql'
+            dialect_name = db.bind.dialect.name
+            if dialect_name == "sqlite":
+                # SQLite case: using JSON1 extension for JSON searching
+                query = query.filter(
+                    (
+                        Chat.title.ilike(
+                            f"%{search_text}%"
+                        )  # Case-insensitive search in title
+                        | text(
+                            """
+                            EXISTS (
+                                SELECT 1 
+                                FROM json_each(Chat.chat, '$.messages') AS message 
+                                WHERE LOWER(message.value->>'content') LIKE '%' || :search_text || '%'
+                            )
+                            """
+                        )
+                    ).params(search_text=search_text)
+                )
+
+                # Check if there are any tags to filter, it should have all the tags
+                if "none" in tag_ids:
+                    query = query.filter(
+                        text(
+                            """
+                            NOT EXISTS (
+                                SELECT 1
+                                FROM json_each(Chat.meta, '$.tags') AS tag
+                            )
+                            """
+                        )
+                    )
+                elif tag_ids:
+                    query = query.filter(
+                        and_(
+                            *[
+                                text(
+                                    f"""
+                                    EXISTS (
+                                        SELECT 1
+                                        FROM json_each(Chat.meta, '$.tags') AS tag
+                                        WHERE tag.value = :tag_id_{tag_idx}
+                                    )
+                                    """
+                                ).params(**{f"tag_id_{tag_idx}": tag_id})
+                                for tag_idx, tag_id in enumerate(tag_ids)
+                            ]
+                        )
+                    )
+
+            elif dialect_name == "postgresql":
+                # PostgreSQL relies on proper JSON query for search
+                query = query.filter(
+                    (
+                        Chat.title.ilike(
+                            f"%{search_text}%"
+                        )  # Case-insensitive search in title
+                        | text(
+                            """
+                            EXISTS (
+                                SELECT 1
+                                FROM json_array_elements(Chat.chat->'messages') AS message
+                                WHERE LOWER(message->>'content') LIKE '%' || :search_text || '%'
+                            )
+                            """
+                        )
+                    ).params(search_text=search_text)
+                )
+
+                # Check if there are any tags to filter, it should have all the tags
+                if "none" in tag_ids:
+                    query = query.filter(
+                        text(
+                            """
+                            NOT EXISTS (
+                                SELECT 1
+                                FROM json_array_elements_text(Chat.meta->'tags') AS tag
+                            )
+                            """
+                        )
+                    )
+                elif tag_ids:
+                    query = query.filter(
+                        and_(
+                            *[
+                                text(
+                                    f"""
+                                    EXISTS (
+                                        SELECT 1
+                                        FROM json_array_elements_text(Chat.meta->'tags') AS tag
+                                        WHERE tag = :tag_id_{tag_idx}
+                                    )
+                                    """
+                                ).params(**{f"tag_id_{tag_idx}": tag_id})
+                                for tag_idx, tag_id in enumerate(tag_ids)
+                            ]
+                        )
+                    )
+            else:
+                raise NotImplementedError(
+                    f"Unsupported dialect: {db.bind.dialect.name}"
+                )
+
+            # Perform pagination at the SQL level
+            all_chats = query.offset(skip).limit(limit).all()
+
+            print(len(all_chats))
+
+            # Validate and return chats
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def get_chats_by_folder_id_and_user_id(
+        self, folder_id: str, user_id: str
+    ) -> list[ChatModel]:
+        with get_db() as db:
+            query = db.query(Chat).filter_by(folder_id=folder_id, user_id=user_id)
+            query = query.filter(or_(Chat.pinned == False, Chat.pinned == None))
+            query = query.filter_by(archived=False)
+
+            query = query.order_by(Chat.updated_at.desc())
+
+            all_chats = query.all()
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def get_chats_by_folder_ids_and_user_id(
+        self, folder_ids: list[str], user_id: str
+    ) -> list[ChatModel]:
+        with get_db() as db:
+            query = db.query(Chat).filter(
+                Chat.folder_id.in_(folder_ids), Chat.user_id == user_id
+            )
+            query = query.filter(or_(Chat.pinned == False, Chat.pinned == None))
+            query = query.filter_by(archived=False)
+
+            query = query.order_by(Chat.updated_at.desc())
+
+            all_chats = query.all()
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def update_chat_folder_id_by_id_and_user_id(
+        self, id: str, user_id: str, folder_id: str
+    ) -> Optional[ChatModel]:
+        try:
+            with get_db() as db:
+                chat = db.get(Chat, id)
+                chat.folder_id = folder_id
+                chat.updated_at = int(time.time())
+                chat.pinned = False
+                db.commit()
+                db.refresh(chat)
+                return ChatModel.model_validate(chat)
+        except Exception:
+            return None
+
+    def get_chat_tags_by_id_and_user_id(self, id: str, user_id: str) -> list[TagModel]:
+        with get_db() as db:
+            chat = db.get(Chat, id)
+            tags = chat.meta.get("tags", [])
+            return [Tags.get_tag_by_name_and_user_id(tag, user_id) for tag in tags]
+
+    def get_chat_list_by_user_id_and_tag_name(
+        self, user_id: str, tag_name: str, skip: int = 0, limit: int = 50
+    ) -> list[ChatModel]:
+        with get_db() as db:
+            query = db.query(Chat).filter_by(user_id=user_id)
+            tag_id = tag_name.replace(" ", "_").lower()
+
+            print(db.bind.dialect.name)
+            if db.bind.dialect.name == "sqlite":
+                # SQLite JSON1 querying for tags within the meta JSON field
+                query = query.filter(
+                    text(
+                        f"EXISTS (SELECT 1 FROM json_each(Chat.meta, '$.tags') WHERE json_each.value = :tag_id)"
+                    )
+                ).params(tag_id=tag_id)
+            elif db.bind.dialect.name == "postgresql":
+                # PostgreSQL JSON query for tags within the meta JSON field (for `json` type)
+                query = query.filter(
+                    text(
+                        "EXISTS (SELECT 1 FROM json_array_elements_text(Chat.meta->'tags') elem WHERE elem = :tag_id)"
+                    )
+                ).params(tag_id=tag_id)
+            else:
+                raise NotImplementedError(
+                    f"Unsupported dialect: {db.bind.dialect.name}"
+                )
+
+            all_chats = query.all()
+            print("all_chats", all_chats)
+            return [ChatModel.model_validate(chat) for chat in all_chats]
+
+    def add_chat_tag_by_id_and_user_id_and_tag_name(
+        self, id: str, user_id: str, tag_name: str
+    ) -> Optional[ChatModel]:
+        tag = Tags.get_tag_by_name_and_user_id(tag_name, user_id)
+        if tag is None:
+            tag = Tags.insert_new_tag(tag_name, user_id)
+        try:
+            with get_db() as db:
+                chat = db.get(Chat, id)
+
+                tag_id = tag.id
+                if tag_id not in chat.meta.get("tags", []):
+                    chat.meta = {
+                        **chat.meta,
+                        "tags": list(set(chat.meta.get("tags", []) + [tag_id])),
+                    }
+
+                db.commit()
+                db.refresh(chat)
+                return ChatModel.model_validate(chat)
+        except Exception:
+            return None
+
+    def count_chats_by_tag_name_and_user_id(self, tag_name: str, user_id: str) -> int:
+        with get_db() as db:  # Assuming `get_db()` returns a session object
+            query = db.query(Chat).filter_by(user_id=user_id, archived=False)
+
+            # Normalize the tag_name for consistency
+            tag_id = tag_name.replace(" ", "_").lower()
+
+            if db.bind.dialect.name == "sqlite":
+                # SQLite JSON1 support for querying the tags inside the `meta` JSON field
+                query = query.filter(
+                    text(
+                        f"EXISTS (SELECT 1 FROM json_each(Chat.meta, '$.tags') WHERE json_each.value = :tag_id)"
+                    )
+                ).params(tag_id=tag_id)
+
+            elif db.bind.dialect.name == "postgresql":
+                # PostgreSQL JSONB support for querying the tags inside the `meta` JSON field
+                query = query.filter(
+                    text(
+                        "EXISTS (SELECT 1 FROM json_array_elements_text(Chat.meta->'tags') elem WHERE elem = :tag_id)"
+                    )
+                ).params(tag_id=tag_id)
+
+            else:
+                raise NotImplementedError(
+                    f"Unsupported dialect: {db.bind.dialect.name}"
+                )
+
+            # Get the count of matching records
+            count = query.count()
+
+            # Debugging output for inspection
+            print(f"Count of chats for tag '{tag_name}':", count)
+
+            return count
+
+    def delete_tag_by_id_and_user_id_and_tag_name(
+        self, id: str, user_id: str, tag_name: str
+    ) -> bool:
+        try:
+            with get_db() as db:
+                chat = db.get(Chat, id)
+                tags = chat.meta.get("tags", [])
+                tag_id = tag_name.replace(" ", "_").lower()
+
+                tags = [tag for tag in tags if tag != tag_id]
+                chat.meta = {
+                    **chat.meta,
+                    "tags": list(set(tags)),
+                }
+                db.commit()
+                return True
+        except Exception:
+            return False
+
+    def delete_all_tags_by_id_and_user_id(self, id: str, user_id: str) -> bool:
+        try:
+            with get_db() as db:
+                chat = db.get(Chat, id)
+                chat.meta = {
+                    **chat.meta,
+                    "tags": [],
+                }
+                db.commit()
+
+                return True
+        except Exception:
+            return False
+
+    def delete_chat_by_id(self, id: str) -> bool:
+        try:
+            with get_db() as db:
+                db.query(Chat).filter_by(id=id).delete()
+                db.commit()
+
+                return True and self.delete_shared_chat_by_chat_id(id)
+        except Exception:
+            return False
+
+    def delete_chat_by_id_and_user_id(self, id: str, user_id: str) -> bool:
+        try:
+            with get_db() as db:
+                db.query(Chat).filter_by(id=id, user_id=user_id).delete()
+                db.commit()
+
+                return True and self.delete_shared_chat_by_chat_id(id)
+        except Exception:
+            return False
+
+    def delete_chats_by_user_id(self, user_id: str) -> bool:
+        try:
+            with get_db() as db:
+                self.delete_shared_chats_by_user_id(user_id)
+
+                db.query(Chat).filter_by(user_id=user_id).delete()
+                db.commit()
+
+                return True
+        except Exception:
+            return False
+
+    def delete_chats_by_user_id_and_folder_id(
+        self, user_id: str, folder_id: str
+    ) -> bool:
+        try:
+            with get_db() as db:
+                db.query(Chat).filter_by(user_id=user_id, folder_id=folder_id).delete()
+                db.commit()
+
+                return True
+        except Exception:
+            return False
+
+    def delete_shared_chats_by_user_id(self, user_id: str) -> bool:
+        try:
+            with get_db() as db:
+                chats_by_user = db.query(Chat).filter_by(user_id=user_id).all()
+                shared_chat_ids = [f"shared-{chat.id}" for chat in chats_by_user]
+
+                db.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete()
+                db.commit()
+
+                return True
+        except Exception:
+            return False
+
+
+Chats = ChatTable()
diff --git a/backend/open_webui/apps/webui/models/documents.py b/backend/open_webui/apps/webui/models/documents.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b96c257442a52e1b65f7ddd12a84dc7baecb6eb
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/documents.py
@@ -0,0 +1,157 @@
+import json
+import logging
+import time
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, get_db
+from open_webui.env import SRC_LOG_LEVELS
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, String, Text
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+####################
+# Documents DB Schema
+####################
+
+
+class Document(Base):
+    __tablename__ = "document"
+
+    collection_name = Column(String, primary_key=True)
+    name = Column(String, unique=True)
+    title = Column(Text)
+    filename = Column(Text)
+    content = Column(Text, nullable=True)
+    user_id = Column(String)
+    timestamp = Column(BigInteger)
+
+
+class DocumentModel(BaseModel):
+    model_config = ConfigDict(from_attributes=True)
+
+    collection_name: str
+    name: str
+    title: str
+    filename: str
+    content: Optional[str] = None
+    user_id: str
+    timestamp: int  # timestamp in epoch
+
+
+####################
+# Forms
+####################
+
+
+class DocumentResponse(BaseModel):
+    collection_name: str
+    name: str
+    title: str
+    filename: str
+    content: Optional[dict] = None
+    user_id: str
+    timestamp: int  # timestamp in epoch
+
+
+class DocumentUpdateForm(BaseModel):
+    name: str
+    title: str
+
+
+class DocumentForm(DocumentUpdateForm):
+    collection_name: str
+    filename: str
+    content: Optional[str] = None
+
+
+class DocumentsTable:
+    def insert_new_doc(
+        self, user_id: str, form_data: DocumentForm
+    ) -> Optional[DocumentModel]:
+        with get_db() as db:
+            document = DocumentModel(
+                **{
+                    **form_data.model_dump(),
+                    "user_id": user_id,
+                    "timestamp": int(time.time()),
+                }
+            )
+
+            try:
+                result = Document(**document.model_dump())
+                db.add(result)
+                db.commit()
+                db.refresh(result)
+                if result:
+                    return DocumentModel.model_validate(result)
+                else:
+                    return None
+            except Exception:
+                return None
+
+    def get_doc_by_name(self, name: str) -> Optional[DocumentModel]:
+        try:
+            with get_db() as db:
+                document = db.query(Document).filter_by(name=name).first()
+                return DocumentModel.model_validate(document) if document else None
+        except Exception:
+            return None
+
+    def get_docs(self) -> list[DocumentModel]:
+        with get_db() as db:
+            return [
+                DocumentModel.model_validate(doc) for doc in db.query(Document).all()
+            ]
+
+    def update_doc_by_name(
+        self, name: str, form_data: DocumentUpdateForm
+    ) -> Optional[DocumentModel]:
+        try:
+            with get_db() as db:
+                db.query(Document).filter_by(name=name).update(
+                    {
+                        "title": form_data.title,
+                        "name": form_data.name,
+                        "timestamp": int(time.time()),
+                    }
+                )
+                db.commit()
+                return self.get_doc_by_name(form_data.name)
+        except Exception as e:
+            log.exception(e)
+            return None
+
+    def update_doc_content_by_name(
+        self, name: str, updated: dict
+    ) -> Optional[DocumentModel]:
+        try:
+            doc = self.get_doc_by_name(name)
+            doc_content = json.loads(doc.content if doc.content else "{}")
+            doc_content = {**doc_content, **updated}
+
+            with get_db() as db:
+                db.query(Document).filter_by(name=name).update(
+                    {
+                        "content": json.dumps(doc_content),
+                        "timestamp": int(time.time()),
+                    }
+                )
+                db.commit()
+                return self.get_doc_by_name(name)
+        except Exception as e:
+            log.exception(e)
+            return None
+
+    def delete_doc_by_name(self, name: str) -> bool:
+        try:
+            with get_db() as db:
+                db.query(Document).filter_by(name=name).delete()
+                db.commit()
+                return True
+        except Exception:
+            return False
+
+
+Documents = DocumentsTable()
diff --git a/backend/open_webui/apps/webui/models/feedbacks.py b/backend/open_webui/apps/webui/models/feedbacks.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2356dfd86051d1b5713f891c6ec03bde05d0328
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/feedbacks.py
@@ -0,0 +1,254 @@
+import logging
+import time
+import uuid
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, get_db
+from open_webui.apps.webui.models.chats import Chats
+
+from open_webui.env import SRC_LOG_LEVELS
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, Text, JSON, Boolean
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+
+####################
+# Feedback DB Schema
+####################
+
+
+class Feedback(Base):
+    __tablename__ = "feedback"
+    id = Column(Text, primary_key=True)
+    user_id = Column(Text)
+    version = Column(BigInteger, default=0)
+    type = Column(Text)
+    data = Column(JSON, nullable=True)
+    meta = Column(JSON, nullable=True)
+    snapshot = Column(JSON, nullable=True)
+    created_at = Column(BigInteger)
+    updated_at = Column(BigInteger)
+
+
+class FeedbackModel(BaseModel):
+    id: str
+    user_id: str
+    version: int
+    type: str
+    data: Optional[dict] = None
+    meta: Optional[dict] = None
+    snapshot: Optional[dict] = None
+    created_at: int
+    updated_at: int
+
+    model_config = ConfigDict(from_attributes=True)
+
+
+####################
+# Forms
+####################
+
+
+class FeedbackResponse(BaseModel):
+    id: str
+    user_id: str
+    version: int
+    type: str
+    data: Optional[dict] = None
+    meta: Optional[dict] = None
+    created_at: int
+    updated_at: int
+
+
+class RatingData(BaseModel):
+    rating: Optional[str | int] = None
+    model_id: Optional[str] = None
+    sibling_model_ids: Optional[list[str]] = None
+    reason: Optional[str] = None
+    comment: Optional[str] = None
+    model_config = ConfigDict(extra="allow", protected_namespaces=())
+
+
+class MetaData(BaseModel):
+    arena: Optional[bool] = None
+    chat_id: Optional[str] = None
+    message_id: Optional[str] = None
+    tags: Optional[list[str]] = None
+    model_config = ConfigDict(extra="allow")
+
+
+class SnapshotData(BaseModel):
+    chat: Optional[dict] = None
+    model_config = ConfigDict(extra="allow")
+
+
+class FeedbackForm(BaseModel):
+    type: str
+    data: Optional[RatingData] = None
+    meta: Optional[dict] = None
+    snapshot: Optional[SnapshotData] = None
+    model_config = ConfigDict(extra="allow")
+
+
+class FeedbackTable:
+    def insert_new_feedback(
+        self, user_id: str, form_data: FeedbackForm
+    ) -> Optional[FeedbackModel]:
+        with get_db() as db:
+            id = str(uuid.uuid4())
+            feedback = FeedbackModel(
+                **{
+                    "id": id,
+                    "user_id": user_id,
+                    "version": 0,
+                    **form_data.model_dump(),
+                    "created_at": int(time.time()),
+                    "updated_at": int(time.time()),
+                }
+            )
+            try:
+                result = Feedback(**feedback.model_dump())
+                db.add(result)
+                db.commit()
+                db.refresh(result)
+                if result:
+                    return FeedbackModel.model_validate(result)
+                else:
+                    return None
+            except Exception as e:
+                print(e)
+                return None
+
+    def get_feedback_by_id(self, id: str) -> Optional[FeedbackModel]:
+        try:
+            with get_db() as db:
+                feedback = db.query(Feedback).filter_by(id=id).first()
+                if not feedback:
+                    return None
+                return FeedbackModel.model_validate(feedback)
+        except Exception:
+            return None
+
+    def get_feedback_by_id_and_user_id(
+        self, id: str, user_id: str
+    ) -> Optional[FeedbackModel]:
+        try:
+            with get_db() as db:
+                feedback = db.query(Feedback).filter_by(id=id, user_id=user_id).first()
+                if not feedback:
+                    return None
+                return FeedbackModel.model_validate(feedback)
+        except Exception:
+            return None
+
+    def get_all_feedbacks(self) -> list[FeedbackModel]:
+        with get_db() as db:
+            return [
+                FeedbackModel.model_validate(feedback)
+                for feedback in db.query(Feedback)
+                .order_by(Feedback.updated_at.desc())
+                .all()
+            ]
+
+    def get_feedbacks_by_type(self, type: str) -> list[FeedbackModel]:
+        with get_db() as db:
+            return [
+                FeedbackModel.model_validate(feedback)
+                for feedback in db.query(Feedback)
+                .filter_by(type=type)
+                .order_by(Feedback.updated_at.desc())
+                .all()
+            ]
+
+    def get_feedbacks_by_user_id(self, user_id: str) -> list[FeedbackModel]:
+        with get_db() as db:
+            return [
+                FeedbackModel.model_validate(feedback)
+                for feedback in db.query(Feedback)
+                .filter_by(user_id=user_id)
+                .order_by(Feedback.updated_at.desc())
+                .all()
+            ]
+
+    def update_feedback_by_id(
+        self, id: str, form_data: FeedbackForm
+    ) -> Optional[FeedbackModel]:
+        with get_db() as db:
+            feedback = db.query(Feedback).filter_by(id=id).first()
+            if not feedback:
+                return None
+
+            if form_data.data:
+                feedback.data = form_data.data.model_dump()
+            if form_data.meta:
+                feedback.meta = form_data.meta
+            if form_data.snapshot:
+                feedback.snapshot = form_data.snapshot.model_dump()
+
+            feedback.updated_at = int(time.time())
+
+            db.commit()
+            return FeedbackModel.model_validate(feedback)
+
+    def update_feedback_by_id_and_user_id(
+        self, id: str, user_id: str, form_data: FeedbackForm
+    ) -> Optional[FeedbackModel]:
+        with get_db() as db:
+            feedback = db.query(Feedback).filter_by(id=id, user_id=user_id).first()
+            if not feedback:
+                return None
+
+            if form_data.data:
+                feedback.data = form_data.data.model_dump()
+            if form_data.meta:
+                feedback.meta = form_data.meta
+            if form_data.snapshot:
+                feedback.snapshot = form_data.snapshot.model_dump()
+
+            feedback.updated_at = int(time.time())
+
+            db.commit()
+            return FeedbackModel.model_validate(feedback)
+
+    def delete_feedback_by_id(self, id: str) -> bool:
+        with get_db() as db:
+            feedback = db.query(Feedback).filter_by(id=id).first()
+            if not feedback:
+                return False
+            db.delete(feedback)
+            db.commit()
+            return True
+
+    def delete_feedback_by_id_and_user_id(self, id: str, user_id: str) -> bool:
+        with get_db() as db:
+            feedback = db.query(Feedback).filter_by(id=id, user_id=user_id).first()
+            if not feedback:
+                return False
+            db.delete(feedback)
+            db.commit()
+            return True
+
+    def delete_feedbacks_by_user_id(self, user_id: str) -> bool:
+        with get_db() as db:
+            feedbacks = db.query(Feedback).filter_by(user_id=user_id).all()
+            if not feedbacks:
+                return False
+            for feedback in feedbacks:
+                db.delete(feedback)
+            db.commit()
+            return True
+
+    def delete_all_feedbacks(self) -> bool:
+        with get_db() as db:
+            feedbacks = db.query(Feedback).all()
+            if not feedbacks:
+                return False
+            for feedback in feedbacks:
+                db.delete(feedback)
+            db.commit()
+            return True
+
+
+Feedbacks = FeedbackTable()
diff --git a/backend/open_webui/apps/webui/models/files.py b/backend/open_webui/apps/webui/models/files.py
new file mode 100644
index 0000000000000000000000000000000000000000..31c9164b60c1822732a6fae4261b4c7103c7082b
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/files.py
@@ -0,0 +1,230 @@
+import logging
+import time
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, JSONField, get_db
+from open_webui.env import SRC_LOG_LEVELS
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, String, Text, JSON
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+####################
+# Files DB Schema
+####################
+
+
+class File(Base):
+    __tablename__ = "file"
+    id = Column(String, primary_key=True)
+    user_id = Column(String)
+    hash = Column(Text, nullable=True)
+
+    filename = Column(Text)
+    path = Column(Text, nullable=True)
+
+    data = Column(JSON, nullable=True)
+    meta = Column(JSON, nullable=True)
+
+    created_at = Column(BigInteger)
+    updated_at = Column(BigInteger)
+
+
+class FileModel(BaseModel):
+    model_config = ConfigDict(from_attributes=True)
+
+    id: str
+    user_id: str
+    hash: Optional[str] = None
+
+    filename: str
+    path: Optional[str] = None
+
+    data: Optional[dict] = None
+    meta: Optional[dict] = None
+
+    created_at: Optional[int]  # timestamp in epoch
+    updated_at: Optional[int]  # timestamp in epoch
+
+
+####################
+# Forms
+####################
+
+
+class FileMeta(BaseModel):
+    name: Optional[str] = None
+    content_type: Optional[str] = None
+    size: Optional[int] = None
+
+    model_config = ConfigDict(extra="allow")
+
+
+class FileModelResponse(BaseModel):
+    id: str
+    user_id: str
+    hash: Optional[str] = None
+
+    filename: str
+    data: Optional[dict] = None
+    meta: FileMeta
+
+    created_at: int  # timestamp in epoch
+    updated_at: int  # timestamp in epoch
+
+    model_config = ConfigDict(extra="allow")
+
+
+class FileMetadataResponse(BaseModel):
+    id: str
+    meta: dict
+    created_at: int  # timestamp in epoch
+    updated_at: int  # timestamp in epoch
+
+
+class FileForm(BaseModel):
+    id: str
+    hash: Optional[str] = None
+    filename: str
+    path: str
+    data: dict = {}
+    meta: dict = {}
+
+
+class FilesTable:
+    def insert_new_file(self, user_id: str, form_data: FileForm) -> Optional[FileModel]:
+        with get_db() as db:
+            file = FileModel(
+                **{
+                    **form_data.model_dump(),
+                    "user_id": user_id,
+                    "created_at": int(time.time()),
+                    "updated_at": int(time.time()),
+                }
+            )
+
+            try:
+                result = File(**file.model_dump())
+                db.add(result)
+                db.commit()
+                db.refresh(result)
+                if result:
+                    return FileModel.model_validate(result)
+                else:
+                    return None
+            except Exception as e:
+                print(f"Error creating tool: {e}")
+                return None
+
+    def get_file_by_id(self, id: str) -> Optional[FileModel]:
+        with get_db() as db:
+            try:
+                file = db.get(File, id)
+                return FileModel.model_validate(file)
+            except Exception:
+                return None
+
+    def get_file_metadata_by_id(self, id: str) -> Optional[FileMetadataResponse]:
+        with get_db() as db:
+            try:
+                file = db.get(File, id)
+                return FileMetadataResponse(
+                    id=file.id,
+                    meta=file.meta,
+                    created_at=file.created_at,
+                    updated_at=file.updated_at,
+                )
+            except Exception:
+                return None
+
+    def get_files(self) -> list[FileModel]:
+        with get_db() as db:
+            return [FileModel.model_validate(file) for file in db.query(File).all()]
+
+    def get_files_by_ids(self, ids: list[str]) -> list[FileModel]:
+        with get_db() as db:
+            return [
+                FileModel.model_validate(file)
+                for file in db.query(File)
+                .filter(File.id.in_(ids))
+                .order_by(File.updated_at.desc())
+                .all()
+            ]
+
+    def get_file_metadatas_by_ids(self, ids: list[str]) -> list[FileMetadataResponse]:
+        with get_db() as db:
+            return [
+                FileMetadataResponse(
+                    id=file.id,
+                    meta=file.meta,
+                    created_at=file.created_at,
+                    updated_at=file.updated_at,
+                )
+                for file in db.query(File)
+                .filter(File.id.in_(ids))
+                .order_by(File.updated_at.desc())
+                .all()
+            ]
+
+    def get_files_by_user_id(self, user_id: str) -> list[FileModel]:
+        with get_db() as db:
+            return [
+                FileModel.model_validate(file)
+                for file in db.query(File).filter_by(user_id=user_id).all()
+            ]
+
+    def update_file_hash_by_id(self, id: str, hash: str) -> Optional[FileModel]:
+        with get_db() as db:
+            try:
+                file = db.query(File).filter_by(id=id).first()
+                file.hash = hash
+                db.commit()
+
+                return FileModel.model_validate(file)
+            except Exception:
+                return None
+
+    def update_file_data_by_id(self, id: str, data: dict) -> Optional[FileModel]:
+        with get_db() as db:
+            try:
+                file = db.query(File).filter_by(id=id).first()
+                file.data = {**(file.data if file.data else {}), **data}
+                db.commit()
+                return FileModel.model_validate(file)
+            except Exception as e:
+
+                return None
+
+    def update_file_metadata_by_id(self, id: str, meta: dict) -> Optional[FileModel]:
+        with get_db() as db:
+            try:
+                file = db.query(File).filter_by(id=id).first()
+                file.meta = {**(file.meta if file.meta else {}), **meta}
+                db.commit()
+                return FileModel.model_validate(file)
+            except Exception:
+                return None
+
+    def delete_file_by_id(self, id: str) -> bool:
+        with get_db() as db:
+            try:
+                db.query(File).filter_by(id=id).delete()
+                db.commit()
+
+                return True
+            except Exception:
+                return False
+
+    def delete_all_files(self) -> bool:
+        with get_db() as db:
+            try:
+                db.query(File).delete()
+                db.commit()
+
+                return True
+            except Exception:
+                return False
+
+
+Files = FilesTable()
diff --git a/backend/open_webui/apps/webui/models/folders.py b/backend/open_webui/apps/webui/models/folders.py
new file mode 100644
index 0000000000000000000000000000000000000000..90e8880aaddf0b98710f3a4b9987b59408a9aa86
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/folders.py
@@ -0,0 +1,271 @@
+import logging
+import time
+import uuid
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, get_db
+from open_webui.apps.webui.models.chats import Chats
+
+from open_webui.env import SRC_LOG_LEVELS
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, Text, JSON, Boolean
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+
+####################
+# Folder DB Schema
+####################
+
+
+class Folder(Base):
+    __tablename__ = "folder"
+    id = Column(Text, primary_key=True)
+    parent_id = Column(Text, nullable=True)
+    user_id = Column(Text)
+    name = Column(Text)
+    items = Column(JSON, nullable=True)
+    meta = Column(JSON, nullable=True)
+    is_expanded = Column(Boolean, default=False)
+    created_at = Column(BigInteger)
+    updated_at = Column(BigInteger)
+
+
+class FolderModel(BaseModel):
+    id: str
+    parent_id: Optional[str] = None
+    user_id: str
+    name: str
+    items: Optional[dict] = None
+    meta: Optional[dict] = None
+    is_expanded: bool = False
+    created_at: int
+    updated_at: int
+
+    model_config = ConfigDict(from_attributes=True)
+
+
+####################
+# Forms
+####################
+
+
+class FolderForm(BaseModel):
+    name: str
+    model_config = ConfigDict(extra="allow")
+
+
+class FolderTable:
+    def insert_new_folder(
+        self, user_id: str, name: str, parent_id: Optional[str] = None
+    ) -> Optional[FolderModel]:
+        with get_db() as db:
+            id = str(uuid.uuid4())
+            folder = FolderModel(
+                **{
+                    "id": id,
+                    "user_id": user_id,
+                    "name": name,
+                    "parent_id": parent_id,
+                    "created_at": int(time.time()),
+                    "updated_at": int(time.time()),
+                }
+            )
+            try:
+                result = Folder(**folder.model_dump())
+                db.add(result)
+                db.commit()
+                db.refresh(result)
+                if result:
+                    return FolderModel.model_validate(result)
+                else:
+                    return None
+            except Exception as e:
+                print(e)
+                return None
+
+    def get_folder_by_id_and_user_id(
+        self, id: str, user_id: str
+    ) -> Optional[FolderModel]:
+        try:
+            with get_db() as db:
+                folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
+
+                if not folder:
+                    return None
+
+                return FolderModel.model_validate(folder)
+        except Exception:
+            return None
+
+    def get_children_folders_by_id_and_user_id(
+        self, id: str, user_id: str
+    ) -> Optional[FolderModel]:
+        try:
+            with get_db() as db:
+                folders = []
+
+                def get_children(folder):
+                    children = self.get_folders_by_parent_id_and_user_id(
+                        folder.id, user_id
+                    )
+                    for child in children:
+                        get_children(child)
+                        folders.append(child)
+
+                folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
+                if not folder:
+                    return None
+
+                get_children(folder)
+                return folders
+        except Exception:
+            return None
+
+    def get_folders_by_user_id(self, user_id: str) -> list[FolderModel]:
+        with get_db() as db:
+            return [
+                FolderModel.model_validate(folder)
+                for folder in db.query(Folder).filter_by(user_id=user_id).all()
+            ]
+
+    def get_folder_by_parent_id_and_user_id_and_name(
+        self, parent_id: Optional[str], user_id: str, name: str
+    ) -> Optional[FolderModel]:
+        try:
+            with get_db() as db:
+                # Check if folder exists
+                folder = (
+                    db.query(Folder)
+                    .filter_by(parent_id=parent_id, user_id=user_id)
+                    .filter(Folder.name.ilike(name))
+                    .first()
+                )
+
+                if not folder:
+                    return None
+
+                return FolderModel.model_validate(folder)
+        except Exception as e:
+            log.error(f"get_folder_by_parent_id_and_user_id_and_name: {e}")
+            return None
+
+    def get_folders_by_parent_id_and_user_id(
+        self, parent_id: Optional[str], user_id: str
+    ) -> list[FolderModel]:
+        with get_db() as db:
+            return [
+                FolderModel.model_validate(folder)
+                for folder in db.query(Folder)
+                .filter_by(parent_id=parent_id, user_id=user_id)
+                .all()
+            ]
+
+    def update_folder_parent_id_by_id_and_user_id(
+        self,
+        id: str,
+        user_id: str,
+        parent_id: str,
+    ) -> Optional[FolderModel]:
+        try:
+            with get_db() as db:
+                folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
+
+                if not folder:
+                    return None
+
+                folder.parent_id = parent_id
+                folder.updated_at = int(time.time())
+
+                db.commit()
+
+                return FolderModel.model_validate(folder)
+        except Exception as e:
+            log.error(f"update_folder: {e}")
+            return
+
+    def update_folder_name_by_id_and_user_id(
+        self, id: str, user_id: str, name: str
+    ) -> Optional[FolderModel]:
+        try:
+            with get_db() as db:
+                folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
+
+                if not folder:
+                    return None
+
+                existing_folder = (
+                    db.query(Folder)
+                    .filter_by(name=name, parent_id=folder.parent_id, user_id=user_id)
+                    .first()
+                )
+
+                if existing_folder:
+                    return None
+
+                folder.name = name
+                folder.updated_at = int(time.time())
+
+                db.commit()
+
+                return FolderModel.model_validate(folder)
+        except Exception as e:
+            log.error(f"update_folder: {e}")
+            return
+
+    def update_folder_is_expanded_by_id_and_user_id(
+        self, id: str, user_id: str, is_expanded: bool
+    ) -> Optional[FolderModel]:
+        try:
+            with get_db() as db:
+                folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
+
+                if not folder:
+                    return None
+
+                folder.is_expanded = is_expanded
+                folder.updated_at = int(time.time())
+
+                db.commit()
+
+                return FolderModel.model_validate(folder)
+        except Exception as e:
+            log.error(f"update_folder: {e}")
+            return
+
+    def delete_folder_by_id_and_user_id(self, id: str, user_id: str) -> bool:
+        try:
+            with get_db() as db:
+                folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
+                if not folder:
+                    return False
+
+                # Delete all chats in the folder
+                Chats.delete_chats_by_user_id_and_folder_id(user_id, folder.id)
+
+                # Delete all children folders
+                def delete_children(folder):
+                    folder_children = self.get_folders_by_parent_id_and_user_id(
+                        folder.id, user_id
+                    )
+                    for folder_child in folder_children:
+                        Chats.delete_chats_by_user_id_and_folder_id(
+                            user_id, folder_child.id
+                        )
+                        delete_children(folder_child)
+
+                        folder = db.query(Folder).filter_by(id=folder_child.id).first()
+                        db.delete(folder)
+                        db.commit()
+
+                delete_children(folder)
+                db.delete(folder)
+                db.commit()
+                return True
+        except Exception as e:
+            log.error(f"delete_folder: {e}")
+            return False
+
+
+Folders = FolderTable()
diff --git a/backend/open_webui/apps/webui/models/functions.py b/backend/open_webui/apps/webui/models/functions.py
new file mode 100644
index 0000000000000000000000000000000000000000..fda15507506cdb984afcb05b7b14fa7b7016af6a
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/functions.py
@@ -0,0 +1,270 @@
+import logging
+import time
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, JSONField, get_db
+from open_webui.apps.webui.models.users import Users
+from open_webui.env import SRC_LOG_LEVELS
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Boolean, Column, String, Text
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+####################
+# Functions DB Schema
+####################
+
+
+class Function(Base):
+    __tablename__ = "function"
+
+    id = Column(String, primary_key=True)
+    user_id = Column(String)
+    name = Column(Text)
+    type = Column(Text)
+    content = Column(Text)
+    meta = Column(JSONField)
+    valves = Column(JSONField)
+    is_active = Column(Boolean)
+    is_global = Column(Boolean)
+    updated_at = Column(BigInteger)
+    created_at = Column(BigInteger)
+
+
+class FunctionMeta(BaseModel):
+    description: Optional[str] = None
+    manifest: Optional[dict] = {}
+
+
+class FunctionModel(BaseModel):
+    id: str
+    user_id: str
+    name: str
+    type: str
+    content: str
+    meta: FunctionMeta
+    is_active: bool = False
+    is_global: bool = False
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+    model_config = ConfigDict(from_attributes=True)
+
+
+####################
+# Forms
+####################
+
+
+class FunctionResponse(BaseModel):
+    id: str
+    user_id: str
+    type: str
+    name: str
+    meta: FunctionMeta
+    is_active: bool
+    is_global: bool
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+
+class FunctionForm(BaseModel):
+    id: str
+    name: str
+    content: str
+    meta: FunctionMeta
+
+
+class FunctionValves(BaseModel):
+    valves: Optional[dict] = None
+
+
+class FunctionsTable:
+    def insert_new_function(
+        self, user_id: str, type: str, form_data: FunctionForm
+    ) -> Optional[FunctionModel]:
+        function = FunctionModel(
+            **{
+                **form_data.model_dump(),
+                "user_id": user_id,
+                "type": type,
+                "updated_at": int(time.time()),
+                "created_at": int(time.time()),
+            }
+        )
+
+        try:
+            with get_db() as db:
+                result = Function(**function.model_dump())
+                db.add(result)
+                db.commit()
+                db.refresh(result)
+                if result:
+                    return FunctionModel.model_validate(result)
+                else:
+                    return None
+        except Exception as e:
+            print(f"Error creating tool: {e}")
+            return None
+
+    def get_function_by_id(self, id: str) -> Optional[FunctionModel]:
+        try:
+            with get_db() as db:
+                function = db.get(Function, id)
+                return FunctionModel.model_validate(function)
+        except Exception:
+            return None
+
+    def get_functions(self, active_only=False) -> list[FunctionModel]:
+        with get_db() as db:
+            if active_only:
+                return [
+                    FunctionModel.model_validate(function)
+                    for function in db.query(Function).filter_by(is_active=True).all()
+                ]
+            else:
+                return [
+                    FunctionModel.model_validate(function)
+                    for function in db.query(Function).all()
+                ]
+
+    def get_functions_by_type(
+        self, type: str, active_only=False
+    ) -> list[FunctionModel]:
+        with get_db() as db:
+            if active_only:
+                return [
+                    FunctionModel.model_validate(function)
+                    for function in db.query(Function)
+                    .filter_by(type=type, is_active=True)
+                    .all()
+                ]
+            else:
+                return [
+                    FunctionModel.model_validate(function)
+                    for function in db.query(Function).filter_by(type=type).all()
+                ]
+
+    def get_global_filter_functions(self) -> list[FunctionModel]:
+        with get_db() as db:
+            return [
+                FunctionModel.model_validate(function)
+                for function in db.query(Function)
+                .filter_by(type="filter", is_active=True, is_global=True)
+                .all()
+            ]
+
+    def get_global_action_functions(self) -> list[FunctionModel]:
+        with get_db() as db:
+            return [
+                FunctionModel.model_validate(function)
+                for function in db.query(Function)
+                .filter_by(type="action", is_active=True, is_global=True)
+                .all()
+            ]
+
+    def get_function_valves_by_id(self, id: str) -> Optional[dict]:
+        with get_db() as db:
+            try:
+                function = db.get(Function, id)
+                return function.valves if function.valves else {}
+            except Exception as e:
+                print(f"An error occurred: {e}")
+                return None
+
+    def update_function_valves_by_id(
+        self, id: str, valves: dict
+    ) -> Optional[FunctionValves]:
+        with get_db() as db:
+            try:
+                function = db.get(Function, id)
+                function.valves = valves
+                function.updated_at = int(time.time())
+                db.commit()
+                db.refresh(function)
+                return self.get_function_by_id(id)
+            except Exception:
+                return None
+
+    def get_user_valves_by_id_and_user_id(
+        self, id: str, user_id: str
+    ) -> Optional[dict]:
+        try:
+            user = Users.get_user_by_id(user_id)
+            user_settings = user.settings.model_dump() if user.settings else {}
+
+            # Check if user has "functions" and "valves" settings
+            if "functions" not in user_settings:
+                user_settings["functions"] = {}
+            if "valves" not in user_settings["functions"]:
+                user_settings["functions"]["valves"] = {}
+
+            return user_settings["functions"]["valves"].get(id, {})
+        except Exception as e:
+            print(f"An error occurred: {e}")
+            return None
+
+    def update_user_valves_by_id_and_user_id(
+        self, id: str, user_id: str, valves: dict
+    ) -> Optional[dict]:
+        try:
+            user = Users.get_user_by_id(user_id)
+            user_settings = user.settings.model_dump() if user.settings else {}
+
+            # Check if user has "functions" and "valves" settings
+            if "functions" not in user_settings:
+                user_settings["functions"] = {}
+            if "valves" not in user_settings["functions"]:
+                user_settings["functions"]["valves"] = {}
+
+            user_settings["functions"]["valves"][id] = valves
+
+            # Update the user settings in the database
+            Users.update_user_by_id(user_id, {"settings": user_settings})
+
+            return user_settings["functions"]["valves"][id]
+        except Exception as e:
+            print(f"An error occurred: {e}")
+            return None
+
+    def update_function_by_id(self, id: str, updated: dict) -> Optional[FunctionModel]:
+        with get_db() as db:
+            try:
+                db.query(Function).filter_by(id=id).update(
+                    {
+                        **updated,
+                        "updated_at": int(time.time()),
+                    }
+                )
+                db.commit()
+                return self.get_function_by_id(id)
+            except Exception:
+                return None
+
+    def deactivate_all_functions(self) -> Optional[bool]:
+        with get_db() as db:
+            try:
+                db.query(Function).update(
+                    {
+                        "is_active": False,
+                        "updated_at": int(time.time()),
+                    }
+                )
+                db.commit()
+                return True
+            except Exception:
+                return None
+
+    def delete_function_by_id(self, id: str) -> bool:
+        with get_db() as db:
+            try:
+                db.query(Function).filter_by(id=id).delete()
+                db.commit()
+
+                return True
+            except Exception:
+                return False
+
+
+Functions = FunctionsTable()
diff --git a/backend/open_webui/apps/webui/models/knowledge.py b/backend/open_webui/apps/webui/models/knowledge.py
new file mode 100644
index 0000000000000000000000000000000000000000..269ad8cc3cf83d46eb95d346b2bb4fda04ae293f
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/knowledge.py
@@ -0,0 +1,168 @@
+import json
+import logging
+import time
+from typing import Optional
+import uuid
+
+from open_webui.apps.webui.internal.db import Base, get_db
+from open_webui.env import SRC_LOG_LEVELS
+
+from open_webui.apps.webui.models.files import FileMetadataResponse
+
+
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, String, Text, JSON
+
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+####################
+# Knowledge DB Schema
+####################
+
+
+class Knowledge(Base):
+    __tablename__ = "knowledge"
+
+    id = Column(Text, unique=True, primary_key=True)
+    user_id = Column(Text)
+
+    name = Column(Text)
+    description = Column(Text)
+
+    data = Column(JSON, nullable=True)
+    meta = Column(JSON, nullable=True)
+
+    created_at = Column(BigInteger)
+    updated_at = Column(BigInteger)
+
+
+class KnowledgeModel(BaseModel):
+    model_config = ConfigDict(from_attributes=True)
+
+    id: str
+    user_id: str
+
+    name: str
+    description: str
+
+    data: Optional[dict] = None
+    meta: Optional[dict] = None
+
+    created_at: int  # timestamp in epoch
+    updated_at: int  # timestamp in epoch
+
+
+####################
+# Forms
+####################
+
+
+class KnowledgeResponse(BaseModel):
+    id: str
+    name: str
+    description: str
+    data: Optional[dict] = None
+    meta: Optional[dict] = None
+    created_at: int  # timestamp in epoch
+    updated_at: int  # timestamp in epoch
+
+    files: Optional[list[FileMetadataResponse | dict]] = None
+
+
+class KnowledgeForm(BaseModel):
+    name: str
+    description: str
+    data: Optional[dict] = None
+
+
+class KnowledgeUpdateForm(BaseModel):
+    name: Optional[str] = None
+    description: Optional[str] = None
+    data: Optional[dict] = None
+
+
+class KnowledgeTable:
+    def insert_new_knowledge(
+        self, user_id: str, form_data: KnowledgeForm
+    ) -> Optional[KnowledgeModel]:
+        with get_db() as db:
+            knowledge = KnowledgeModel(
+                **{
+                    **form_data.model_dump(),
+                    "id": str(uuid.uuid4()),
+                    "user_id": user_id,
+                    "created_at": int(time.time()),
+                    "updated_at": int(time.time()),
+                }
+            )
+
+            try:
+                result = Knowledge(**knowledge.model_dump())
+                db.add(result)
+                db.commit()
+                db.refresh(result)
+                if result:
+                    return KnowledgeModel.model_validate(result)
+                else:
+                    return None
+            except Exception:
+                return None
+
+    def get_knowledge_items(self) -> list[KnowledgeModel]:
+        with get_db() as db:
+            return [
+                KnowledgeModel.model_validate(knowledge)
+                for knowledge in db.query(Knowledge)
+                .order_by(Knowledge.updated_at.desc())
+                .all()
+            ]
+
+    def get_knowledge_by_id(self, id: str) -> Optional[KnowledgeModel]:
+        try:
+            with get_db() as db:
+                knowledge = db.query(Knowledge).filter_by(id=id).first()
+                return KnowledgeModel.model_validate(knowledge) if knowledge else None
+        except Exception:
+            return None
+
+    def update_knowledge_by_id(
+        self, id: str, form_data: KnowledgeUpdateForm, overwrite: bool = False
+    ) -> Optional[KnowledgeModel]:
+        try:
+            with get_db() as db:
+                knowledge = self.get_knowledge_by_id(id=id)
+                db.query(Knowledge).filter_by(id=id).update(
+                    {
+                        **form_data.model_dump(exclude_none=True),
+                        "updated_at": int(time.time()),
+                    }
+                )
+                db.commit()
+                return self.get_knowledge_by_id(id=id)
+        except Exception as e:
+            log.exception(e)
+            return None
+
+    def delete_knowledge_by_id(self, id: str) -> bool:
+        try:
+            with get_db() as db:
+                db.query(Knowledge).filter_by(id=id).delete()
+                db.commit()
+                return True
+        except Exception:
+            return False
+
+    def delete_all_knowledge(self) -> bool:
+        with get_db() as db:
+            try:
+                db.query(Knowledge).delete()
+                db.commit()
+
+                return True
+            except Exception:
+                return False
+
+
+Knowledges = KnowledgeTable()
diff --git a/backend/open_webui/apps/webui/models/memories.py b/backend/open_webui/apps/webui/models/memories.py
new file mode 100644
index 0000000000000000000000000000000000000000..6686058d368937e9c359964b7a42297346c891fe
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/memories.py
@@ -0,0 +1,137 @@
+import time
+import uuid
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, get_db
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, String, Text
+
+####################
+# Memory DB Schema
+####################
+
+
+class Memory(Base):
+    __tablename__ = "memory"
+
+    id = Column(String, primary_key=True)
+    user_id = Column(String)
+    content = Column(Text)
+    updated_at = Column(BigInteger)
+    created_at = Column(BigInteger)
+
+
+class MemoryModel(BaseModel):
+    id: str
+    user_id: str
+    content: str
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+    model_config = ConfigDict(from_attributes=True)
+
+
+####################
+# Forms
+####################
+
+
+class MemoriesTable:
+    def insert_new_memory(
+        self,
+        user_id: str,
+        content: str,
+    ) -> Optional[MemoryModel]:
+        with get_db() as db:
+            id = str(uuid.uuid4())
+
+            memory = MemoryModel(
+                **{
+                    "id": id,
+                    "user_id": user_id,
+                    "content": content,
+                    "created_at": int(time.time()),
+                    "updated_at": int(time.time()),
+                }
+            )
+            result = Memory(**memory.model_dump())
+            db.add(result)
+            db.commit()
+            db.refresh(result)
+            if result:
+                return MemoryModel.model_validate(result)
+            else:
+                return None
+
+    def update_memory_by_id(
+        self,
+        id: str,
+        content: str,
+    ) -> Optional[MemoryModel]:
+        with get_db() as db:
+            try:
+                db.query(Memory).filter_by(id=id).update(
+                    {"content": content, "updated_at": int(time.time())}
+                )
+                db.commit()
+                return self.get_memory_by_id(id)
+            except Exception:
+                return None
+
+    def get_memories(self) -> list[MemoryModel]:
+        with get_db() as db:
+            try:
+                memories = db.query(Memory).all()
+                return [MemoryModel.model_validate(memory) for memory in memories]
+            except Exception:
+                return None
+
+    def get_memories_by_user_id(self, user_id: str) -> list[MemoryModel]:
+        with get_db() as db:
+            try:
+                memories = db.query(Memory).filter_by(user_id=user_id).all()
+                return [MemoryModel.model_validate(memory) for memory in memories]
+            except Exception:
+                return None
+
+    def get_memory_by_id(self, id: str) -> Optional[MemoryModel]:
+        with get_db() as db:
+            try:
+                memory = db.get(Memory, id)
+                return MemoryModel.model_validate(memory)
+            except Exception:
+                return None
+
+    def delete_memory_by_id(self, id: str) -> bool:
+        with get_db() as db:
+            try:
+                db.query(Memory).filter_by(id=id).delete()
+                db.commit()
+
+                return True
+
+            except Exception:
+                return False
+
+    def delete_memories_by_user_id(self, user_id: str) -> bool:
+        with get_db() as db:
+            try:
+                db.query(Memory).filter_by(user_id=user_id).delete()
+                db.commit()
+
+                return True
+            except Exception:
+                return False
+
+    def delete_memory_by_id_and_user_id(self, id: str, user_id: str) -> bool:
+        with get_db() as db:
+            try:
+                db.query(Memory).filter_by(id=id, user_id=user_id).delete()
+                db.commit()
+
+                return True
+            except Exception:
+                return False
+
+
+Memories = MemoriesTable()
diff --git a/backend/open_webui/apps/webui/models/models.py b/backend/open_webui/apps/webui/models/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..9bdffb9bcc66e3fee92447b6004388416ff65488
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/models.py
@@ -0,0 +1,179 @@
+import logging
+import time
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, JSONField, get_db
+from open_webui.env import SRC_LOG_LEVELS
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, Text
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+
+####################
+# Models DB Schema
+####################
+
+
+# ModelParams is a model for the data stored in the params field of the Model table
+class ModelParams(BaseModel):
+    model_config = ConfigDict(extra="allow")
+    pass
+
+
+# ModelMeta is a model for the data stored in the meta field of the Model table
+class ModelMeta(BaseModel):
+    profile_image_url: Optional[str] = "/static/favicon.png"
+
+    description: Optional[str] = None
+    """
+        User-facing description of the model.
+    """
+
+    capabilities: Optional[dict] = None
+
+    model_config = ConfigDict(extra="allow")
+
+    pass
+
+
+class Model(Base):
+    __tablename__ = "model"
+
+    id = Column(Text, primary_key=True)
+    """
+        The model's id as used in the API. If set to an existing model, it will override the model.
+    """
+    user_id = Column(Text)
+
+    base_model_id = Column(Text, nullable=True)
+    """
+        An optional pointer to the actual model that should be used when proxying requests.
+    """
+
+    name = Column(Text)
+    """
+        The human-readable display name of the model.
+    """
+
+    params = Column(JSONField)
+    """
+        Holds a JSON encoded blob of parameters, see `ModelParams`.
+    """
+
+    meta = Column(JSONField)
+    """
+        Holds a JSON encoded blob of metadata, see `ModelMeta`.
+    """
+
+    updated_at = Column(BigInteger)
+    created_at = Column(BigInteger)
+
+
+class ModelModel(BaseModel):
+    id: str
+    user_id: str
+    base_model_id: Optional[str] = None
+
+    name: str
+    params: ModelParams
+    meta: ModelMeta
+
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+    model_config = ConfigDict(from_attributes=True)
+
+
+####################
+# Forms
+####################
+
+
+class ModelResponse(BaseModel):
+    id: str
+    name: str
+    meta: ModelMeta
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+
+class ModelForm(BaseModel):
+    id: str
+    base_model_id: Optional[str] = None
+    name: str
+    meta: ModelMeta
+    params: ModelParams
+
+
+class ModelsTable:
+    def insert_new_model(
+        self, form_data: ModelForm, user_id: str
+    ) -> Optional[ModelModel]:
+        model = ModelModel(
+            **{
+                **form_data.model_dump(),
+                "user_id": user_id,
+                "created_at": int(time.time()),
+                "updated_at": int(time.time()),
+            }
+        )
+        try:
+            with get_db() as db:
+                result = Model(**model.model_dump())
+                db.add(result)
+                db.commit()
+                db.refresh(result)
+
+                if result:
+                    return ModelModel.model_validate(result)
+                else:
+                    return None
+        except Exception as e:
+            print(e)
+            return None
+
+    def get_all_models(self) -> list[ModelModel]:
+        with get_db() as db:
+            return [ModelModel.model_validate(model) for model in db.query(Model).all()]
+
+    def get_model_by_id(self, id: str) -> Optional[ModelModel]:
+        try:
+            with get_db() as db:
+                model = db.get(Model, id)
+                return ModelModel.model_validate(model)
+        except Exception:
+            return None
+
+    def update_model_by_id(self, id: str, model: ModelForm) -> Optional[ModelModel]:
+        try:
+            with get_db() as db:
+                # update only the fields that are present in the model
+                result = (
+                    db.query(Model)
+                    .filter_by(id=id)
+                    .update(model.model_dump(exclude={"id"}, exclude_none=True))
+                )
+                db.commit()
+
+                model = db.get(Model, id)
+                db.refresh(model)
+                return ModelModel.model_validate(model)
+        except Exception as e:
+            print(e)
+
+            return None
+
+    def delete_model_by_id(self, id: str) -> bool:
+        try:
+            with get_db() as db:
+                db.query(Model).filter_by(id=id).delete()
+                db.commit()
+
+                return True
+        except Exception:
+            return False
+
+
+Models = ModelsTable()
diff --git a/backend/open_webui/apps/webui/models/prompts.py b/backend/open_webui/apps/webui/models/prompts.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b98e5c5350cf45702167bba283ade9b9af8f2bc
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/prompts.py
@@ -0,0 +1,110 @@
+import time
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, get_db
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, String, Text
+
+####################
+# Prompts DB Schema
+####################
+
+
+class Prompt(Base):
+    __tablename__ = "prompt"
+
+    command = Column(String, primary_key=True)
+    user_id = Column(String)
+    title = Column(Text)
+    content = Column(Text)
+    timestamp = Column(BigInteger)
+
+
+class PromptModel(BaseModel):
+    command: str
+    user_id: str
+    title: str
+    content: str
+    timestamp: int  # timestamp in epoch
+
+    model_config = ConfigDict(from_attributes=True)
+
+
+####################
+# Forms
+####################
+
+
+class PromptForm(BaseModel):
+    command: str
+    title: str
+    content: str
+
+
+class PromptsTable:
+    def insert_new_prompt(
+        self, user_id: str, form_data: PromptForm
+    ) -> Optional[PromptModel]:
+        prompt = PromptModel(
+            **{
+                "user_id": user_id,
+                "command": form_data.command,
+                "title": form_data.title,
+                "content": form_data.content,
+                "timestamp": int(time.time()),
+            }
+        )
+
+        try:
+            with get_db() as db:
+                result = Prompt(**prompt.dict())
+                db.add(result)
+                db.commit()
+                db.refresh(result)
+                if result:
+                    return PromptModel.model_validate(result)
+                else:
+                    return None
+        except Exception:
+            return None
+
+    def get_prompt_by_command(self, command: str) -> Optional[PromptModel]:
+        try:
+            with get_db() as db:
+                prompt = db.query(Prompt).filter_by(command=command).first()
+                return PromptModel.model_validate(prompt)
+        except Exception:
+            return None
+
+    def get_prompts(self) -> list[PromptModel]:
+        with get_db() as db:
+            return [
+                PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all()
+            ]
+
+    def update_prompt_by_command(
+        self, command: str, form_data: PromptForm
+    ) -> Optional[PromptModel]:
+        try:
+            with get_db() as db:
+                prompt = db.query(Prompt).filter_by(command=command).first()
+                prompt.title = form_data.title
+                prompt.content = form_data.content
+                prompt.timestamp = int(time.time())
+                db.commit()
+                return PromptModel.model_validate(prompt)
+        except Exception:
+            return None
+
+    def delete_prompt_by_command(self, command: str) -> bool:
+        try:
+            with get_db() as db:
+                db.query(Prompt).filter_by(command=command).delete()
+                db.commit()
+
+                return True
+        except Exception:
+            return False
+
+
+Prompts = PromptsTable()
diff --git a/backend/open_webui/apps/webui/models/tags.py b/backend/open_webui/apps/webui/models/tags.py
new file mode 100644
index 0000000000000000000000000000000000000000..7424a26604047257a9e0f189ab92da0ca4d04654
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/tags.py
@@ -0,0 +1,109 @@
+import logging
+import time
+import uuid
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, get_db
+
+
+from open_webui.env import SRC_LOG_LEVELS
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, String, JSON, PrimaryKeyConstraint
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+
+####################
+# Tag DB Schema
+####################
+class Tag(Base):
+    __tablename__ = "tag"
+    id = Column(String)
+    name = Column(String)
+    user_id = Column(String)
+    meta = Column(JSON, nullable=True)
+
+    # Unique constraint ensuring (id, user_id) is unique, not just the `id` column
+    __table_args__ = (PrimaryKeyConstraint("id", "user_id", name="pk_id_user_id"),)
+
+
+class TagModel(BaseModel):
+    id: str
+    name: str
+    user_id: str
+    meta: Optional[dict] = None
+    model_config = ConfigDict(from_attributes=True)
+
+
+####################
+# Forms
+####################
+
+
+class TagChatIdForm(BaseModel):
+    name: str
+    chat_id: str
+
+
+class TagTable:
+    def insert_new_tag(self, name: str, user_id: str) -> Optional[TagModel]:
+        with get_db() as db:
+            id = name.replace(" ", "_").lower()
+            tag = TagModel(**{"id": id, "user_id": user_id, "name": name})
+            try:
+                result = Tag(**tag.model_dump())
+                db.add(result)
+                db.commit()
+                db.refresh(result)
+                if result:
+                    return TagModel.model_validate(result)
+                else:
+                    return None
+            except Exception as e:
+                print(e)
+                return None
+
+    def get_tag_by_name_and_user_id(
+        self, name: str, user_id: str
+    ) -> Optional[TagModel]:
+        try:
+            id = name.replace(" ", "_").lower()
+            with get_db() as db:
+                tag = db.query(Tag).filter_by(id=id, user_id=user_id).first()
+                return TagModel.model_validate(tag)
+        except Exception:
+            return None
+
+    def get_tags_by_user_id(self, user_id: str) -> list[TagModel]:
+        with get_db() as db:
+            return [
+                TagModel.model_validate(tag)
+                for tag in (db.query(Tag).filter_by(user_id=user_id).all())
+            ]
+
+    def get_tags_by_ids_and_user_id(
+        self, ids: list[str], user_id: str
+    ) -> list[TagModel]:
+        with get_db() as db:
+            return [
+                TagModel.model_validate(tag)
+                for tag in (
+                    db.query(Tag).filter(Tag.id.in_(ids), Tag.user_id == user_id).all()
+                )
+            ]
+
+    def delete_tag_by_name_and_user_id(self, name: str, user_id: str) -> bool:
+        try:
+            with get_db() as db:
+                id = name.replace(" ", "_").lower()
+                res = db.query(Tag).filter_by(id=id, user_id=user_id).delete()
+                log.debug(f"res: {res}")
+                db.commit()
+                return True
+        except Exception as e:
+            log.error(f"delete_tag: {e}")
+            return False
+
+
+Tags = TagTable()
diff --git a/backend/open_webui/apps/webui/models/tools.py b/backend/open_webui/apps/webui/models/tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..e06f83452b2af8b7f540ddf2f412833ea6f8c609
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/tools.py
@@ -0,0 +1,202 @@
+import logging
+import time
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, JSONField, get_db
+from open_webui.apps.webui.models.users import Users
+from open_webui.env import SRC_LOG_LEVELS
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, String, Text
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+####################
+# Tools DB Schema
+####################
+
+
+class Tool(Base):
+    __tablename__ = "tool"
+
+    id = Column(String, primary_key=True)
+    user_id = Column(String)
+    name = Column(Text)
+    content = Column(Text)
+    specs = Column(JSONField)
+    meta = Column(JSONField)
+    valves = Column(JSONField)
+    updated_at = Column(BigInteger)
+    created_at = Column(BigInteger)
+
+
+class ToolMeta(BaseModel):
+    description: Optional[str] = None
+    manifest: Optional[dict] = {}
+
+
+class ToolModel(BaseModel):
+    id: str
+    user_id: str
+    name: str
+    content: str
+    specs: list[dict]
+    meta: ToolMeta
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+    model_config = ConfigDict(from_attributes=True)
+
+
+####################
+# Forms
+####################
+
+
+class ToolResponse(BaseModel):
+    id: str
+    user_id: str
+    name: str
+    meta: ToolMeta
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+
+class ToolForm(BaseModel):
+    id: str
+    name: str
+    content: str
+    meta: ToolMeta
+
+
+class ToolValves(BaseModel):
+    valves: Optional[dict] = None
+
+
+class ToolsTable:
+    def insert_new_tool(
+        self, user_id: str, form_data: ToolForm, specs: list[dict]
+    ) -> Optional[ToolModel]:
+        with get_db() as db:
+            tool = ToolModel(
+                **{
+                    **form_data.model_dump(),
+                    "specs": specs,
+                    "user_id": user_id,
+                    "updated_at": int(time.time()),
+                    "created_at": int(time.time()),
+                }
+            )
+
+            try:
+                result = Tool(**tool.model_dump())
+                db.add(result)
+                db.commit()
+                db.refresh(result)
+                if result:
+                    return ToolModel.model_validate(result)
+                else:
+                    return None
+            except Exception as e:
+                print(f"Error creating tool: {e}")
+                return None
+
+    def get_tool_by_id(self, id: str) -> Optional[ToolModel]:
+        try:
+            with get_db() as db:
+                tool = db.get(Tool, id)
+                return ToolModel.model_validate(tool)
+        except Exception:
+            return None
+
+    def get_tools(self) -> list[ToolModel]:
+        with get_db() as db:
+            return [ToolModel.model_validate(tool) for tool in db.query(Tool).all()]
+
+    def get_tool_valves_by_id(self, id: str) -> Optional[dict]:
+        try:
+            with get_db() as db:
+                tool = db.get(Tool, id)
+                return tool.valves if tool.valves else {}
+        except Exception as e:
+            print(f"An error occurred: {e}")
+            return None
+
+    def update_tool_valves_by_id(self, id: str, valves: dict) -> Optional[ToolValves]:
+        try:
+            with get_db() as db:
+                db.query(Tool).filter_by(id=id).update(
+                    {"valves": valves, "updated_at": int(time.time())}
+                )
+                db.commit()
+                return self.get_tool_by_id(id)
+        except Exception:
+            return None
+
+    def get_user_valves_by_id_and_user_id(
+        self, id: str, user_id: str
+    ) -> Optional[dict]:
+        try:
+            user = Users.get_user_by_id(user_id)
+            user_settings = user.settings.model_dump() if user.settings else {}
+
+            # Check if user has "tools" and "valves" settings
+            if "tools" not in user_settings:
+                user_settings["tools"] = {}
+            if "valves" not in user_settings["tools"]:
+                user_settings["tools"]["valves"] = {}
+
+            return user_settings["tools"]["valves"].get(id, {})
+        except Exception as e:
+            print(f"An error occurred: {e}")
+            return None
+
+    def update_user_valves_by_id_and_user_id(
+        self, id: str, user_id: str, valves: dict
+    ) -> Optional[dict]:
+        try:
+            user = Users.get_user_by_id(user_id)
+            user_settings = user.settings.model_dump() if user.settings else {}
+
+            # Check if user has "tools" and "valves" settings
+            if "tools" not in user_settings:
+                user_settings["tools"] = {}
+            if "valves" not in user_settings["tools"]:
+                user_settings["tools"]["valves"] = {}
+
+            user_settings["tools"]["valves"][id] = valves
+
+            # Update the user settings in the database
+            Users.update_user_by_id(user_id, {"settings": user_settings})
+
+            return user_settings["tools"]["valves"][id]
+        except Exception as e:
+            print(f"An error occurred: {e}")
+            return None
+
+    def update_tool_by_id(self, id: str, updated: dict) -> Optional[ToolModel]:
+        try:
+            with get_db() as db:
+                db.query(Tool).filter_by(id=id).update(
+                    {**updated, "updated_at": int(time.time())}
+                )
+                db.commit()
+
+                tool = db.query(Tool).get(id)
+                db.refresh(tool)
+                return ToolModel.model_validate(tool)
+        except Exception:
+            return None
+
+    def delete_tool_by_id(self, id: str) -> bool:
+        try:
+            with get_db() as db:
+                db.query(Tool).filter_by(id=id).delete()
+                db.commit()
+
+                return True
+        except Exception:
+            return False
+
+
+Tools = ToolsTable()
diff --git a/backend/open_webui/apps/webui/models/users.py b/backend/open_webui/apps/webui/models/users.py
new file mode 100644
index 0000000000000000000000000000000000000000..328618a671a117a483c0d730b805ccc78391b9f6
--- /dev/null
+++ b/backend/open_webui/apps/webui/models/users.py
@@ -0,0 +1,261 @@
+import time
+from typing import Optional
+
+from open_webui.apps.webui.internal.db import Base, JSONField, get_db
+from open_webui.apps.webui.models.chats import Chats
+from pydantic import BaseModel, ConfigDict
+from sqlalchemy import BigInteger, Column, String, Text
+
+####################
+# User DB Schema
+####################
+
+
+class User(Base):
+    __tablename__ = "user"
+
+    id = Column(String, primary_key=True)
+    name = Column(String)
+    email = Column(String)
+    role = Column(String)
+    profile_image_url = Column(Text)
+
+    last_active_at = Column(BigInteger)
+    updated_at = Column(BigInteger)
+    created_at = Column(BigInteger)
+
+    api_key = Column(String, nullable=True, unique=True)
+    settings = Column(JSONField, nullable=True)
+    info = Column(JSONField, nullable=True)
+
+    oauth_sub = Column(Text, unique=True)
+
+
+class UserSettings(BaseModel):
+    ui: Optional[dict] = {}
+    model_config = ConfigDict(extra="allow")
+    pass
+
+
+class UserModel(BaseModel):
+    id: str
+    name: str
+    email: str
+    role: str = "pending"
+    profile_image_url: str
+
+    last_active_at: int  # timestamp in epoch
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+    api_key: Optional[str] = None
+    settings: Optional[UserSettings] = None
+    info: Optional[dict] = None
+
+    oauth_sub: Optional[str] = None
+
+    model_config = ConfigDict(from_attributes=True)
+
+
+####################
+# Forms
+####################
+
+
+class UserRoleUpdateForm(BaseModel):
+    id: str
+    role: str
+
+
+class UserUpdateForm(BaseModel):
+    name: str
+    email: str
+    profile_image_url: str
+    password: Optional[str] = None
+
+
+class UsersTable:
+    def insert_new_user(
+        self,
+        id: str,
+        name: str,
+        email: str,
+        profile_image_url: str = "/user.png",
+        role: str = "pending",
+        oauth_sub: Optional[str] = None,
+    ) -> Optional[UserModel]:
+        with get_db() as db:
+            user = UserModel(
+                **{
+                    "id": id,
+                    "name": name,
+                    "email": email,
+                    "role": role,
+                    "profile_image_url": profile_image_url,
+                    "last_active_at": int(time.time()),
+                    "created_at": int(time.time()),
+                    "updated_at": int(time.time()),
+                    "oauth_sub": oauth_sub,
+                }
+            )
+            result = User(**user.model_dump())
+            db.add(result)
+            db.commit()
+            db.refresh(result)
+            if result:
+                return user
+            else:
+                return None
+
+    def get_user_by_id(self, id: str) -> Optional[UserModel]:
+        try:
+            with get_db() as db:
+                user = db.query(User).filter_by(id=id).first()
+                return UserModel.model_validate(user)
+        except Exception:
+            return None
+
+    def get_user_by_api_key(self, api_key: str) -> Optional[UserModel]:
+        try:
+            with get_db() as db:
+                user = db.query(User).filter_by(api_key=api_key).first()
+                return UserModel.model_validate(user)
+        except Exception:
+            return None
+
+    def get_user_by_email(self, email: str) -> Optional[UserModel]:
+        try:
+            with get_db() as db:
+                user = db.query(User).filter_by(email=email).first()
+                return UserModel.model_validate(user)
+        except Exception:
+            return None
+
+    def get_user_by_oauth_sub(self, sub: str) -> Optional[UserModel]:
+        try:
+            with get_db() as db:
+                user = db.query(User).filter_by(oauth_sub=sub).first()
+                return UserModel.model_validate(user)
+        except Exception:
+            return None
+
+    def get_users(self, skip: int = 0, limit: int = 50) -> list[UserModel]:
+        with get_db() as db:
+            users = (
+                db.query(User)
+                # .offset(skip).limit(limit)
+                .all()
+            )
+            return [UserModel.model_validate(user) for user in users]
+
+    def get_num_users(self) -> Optional[int]:
+        with get_db() as db:
+            return db.query(User).count()
+
+    def get_first_user(self) -> UserModel:
+        try:
+            with get_db() as db:
+                user = db.query(User).order_by(User.created_at).first()
+                return UserModel.model_validate(user)
+        except Exception:
+            return None
+
+    def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]:
+        try:
+            with get_db() as db:
+                db.query(User).filter_by(id=id).update({"role": role})
+                db.commit()
+                user = db.query(User).filter_by(id=id).first()
+                return UserModel.model_validate(user)
+        except Exception:
+            return None
+
+    def update_user_profile_image_url_by_id(
+        self, id: str, profile_image_url: str
+    ) -> Optional[UserModel]:
+        try:
+            with get_db() as db:
+                db.query(User).filter_by(id=id).update(
+                    {"profile_image_url": profile_image_url}
+                )
+                db.commit()
+
+                user = db.query(User).filter_by(id=id).first()
+                return UserModel.model_validate(user)
+        except Exception:
+            return None
+
+    def update_user_last_active_by_id(self, id: str) -> Optional[UserModel]:
+        try:
+            with get_db() as db:
+                db.query(User).filter_by(id=id).update(
+                    {"last_active_at": int(time.time())}
+                )
+                db.commit()
+
+                user = db.query(User).filter_by(id=id).first()
+                return UserModel.model_validate(user)
+        except Exception:
+            return None
+
+    def update_user_oauth_sub_by_id(
+        self, id: str, oauth_sub: str
+    ) -> Optional[UserModel]:
+        try:
+            with get_db() as db:
+                db.query(User).filter_by(id=id).update({"oauth_sub": oauth_sub})
+                db.commit()
+
+                user = db.query(User).filter_by(id=id).first()
+                return UserModel.model_validate(user)
+        except Exception:
+            return None
+
+    def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]:
+        try:
+            with get_db() as db:
+                db.query(User).filter_by(id=id).update(updated)
+                db.commit()
+
+                user = db.query(User).filter_by(id=id).first()
+                return UserModel.model_validate(user)
+                # return UserModel(**user.dict())
+        except Exception:
+            return None
+
+    def delete_user_by_id(self, id: str) -> bool:
+        try:
+            # Delete User Chats
+            result = Chats.delete_chats_by_user_id(id)
+
+            if result:
+                with get_db() as db:
+                    # Delete User
+                    db.query(User).filter_by(id=id).delete()
+                    db.commit()
+
+                return True
+            else:
+                return False
+        except Exception:
+            return False
+
+    def update_user_api_key_by_id(self, id: str, api_key: str) -> str:
+        try:
+            with get_db() as db:
+                result = db.query(User).filter_by(id=id).update({"api_key": api_key})
+                db.commit()
+                return True if result == 1 else False
+        except Exception:
+            return False
+
+    def get_user_api_key_by_id(self, id: str) -> Optional[str]:
+        try:
+            with get_db() as db:
+                user = db.query(User).filter_by(id=id).first()
+                return user.api_key
+        except Exception:
+            return None
+
+
+Users = UsersTable()
diff --git a/backend/open_webui/apps/webui/routers/auths.py b/backend/open_webui/apps/webui/routers/auths.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef0a0d445b46bce7dd7748220aa1efc4722145fc
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/auths.py
@@ -0,0 +1,497 @@
+import re
+import uuid
+import time
+import datetime
+
+from open_webui.apps.webui.models.auths import (
+    AddUserForm,
+    ApiKey,
+    Auths,
+    Token,
+    SigninForm,
+    SigninResponse,
+    SignupForm,
+    UpdatePasswordForm,
+    UpdateProfileForm,
+    UserResponse,
+)
+from open_webui.apps.webui.models.users import Users
+from open_webui.config import WEBUI_AUTH
+from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
+from open_webui.env import (
+    WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
+    WEBUI_AUTH_TRUSTED_NAME_HEADER,
+    WEBUI_SESSION_COOKIE_SAME_SITE,
+    WEBUI_SESSION_COOKIE_SECURE,
+)
+from fastapi import APIRouter, Depends, HTTPException, Request, status
+from fastapi.responses import Response
+from pydantic import BaseModel
+from open_webui.utils.misc import parse_duration, validate_email_format
+from open_webui.utils.utils import (
+    create_api_key,
+    create_token,
+    get_admin_user,
+    get_verified_user,
+    get_current_user,
+    get_password_hash,
+)
+from open_webui.utils.webhook import post_webhook
+from typing import Optional
+
+router = APIRouter()
+
+############################
+# GetSessionUser
+############################
+
+
+class SessionUserResponse(Token, UserResponse):
+    expires_at: Optional[int] = None
+
+
+@router.get("/", response_model=SessionUserResponse)
+async def get_session_user(
+    request: Request, response: Response, user=Depends(get_current_user)
+):
+    expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
+    expires_at = None
+    if expires_delta:
+        expires_at = int(time.time()) + int(expires_delta.total_seconds())
+
+    token = create_token(
+        data={"id": user.id},
+        expires_delta=expires_delta,
+    )
+
+    datetime_expires_at = (
+        datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc)
+        if expires_at
+        else None
+    )
+
+    # Set the cookie token
+    response.set_cookie(
+        key="token",
+        value=token,
+        expires=datetime_expires_at,
+        httponly=True,  # Ensures the cookie is not accessible via JavaScript
+        samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
+        secure=WEBUI_SESSION_COOKIE_SECURE,
+    )
+
+    return {
+        "token": token,
+        "token_type": "Bearer",
+        "expires_at": expires_at,
+        "id": user.id,
+        "email": user.email,
+        "name": user.name,
+        "role": user.role,
+        "profile_image_url": user.profile_image_url,
+    }
+
+
+############################
+# Update Profile
+############################
+
+
+@router.post("/update/profile", response_model=UserResponse)
+async def update_profile(
+    form_data: UpdateProfileForm, session_user=Depends(get_verified_user)
+):
+    if session_user:
+        user = Users.update_user_by_id(
+            session_user.id,
+            {"profile_image_url": form_data.profile_image_url, "name": form_data.name},
+        )
+        if user:
+            return user
+        else:
+            raise HTTPException(400, detail=ERROR_MESSAGES.DEFAULT())
+    else:
+        raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
+
+
+############################
+# Update Password
+############################
+
+
+@router.post("/update/password", response_model=bool)
+async def update_password(
+    form_data: UpdatePasswordForm, session_user=Depends(get_current_user)
+):
+    if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
+        raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED)
+    if session_user:
+        user = Auths.authenticate_user(session_user.email, form_data.password)
+
+        if user:
+            hashed = get_password_hash(form_data.new_password)
+            return Auths.update_user_password_by_id(user.id, hashed)
+        else:
+            raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_PASSWORD)
+    else:
+        raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
+
+
+############################
+# SignIn
+############################
+
+
+@router.post("/signin", response_model=SessionUserResponse)
+async def signin(request: Request, response: Response, form_data: SigninForm):
+    if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
+        if WEBUI_AUTH_TRUSTED_EMAIL_HEADER not in request.headers:
+            raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
+
+        trusted_email = request.headers[WEBUI_AUTH_TRUSTED_EMAIL_HEADER].lower()
+        trusted_name = trusted_email
+        if WEBUI_AUTH_TRUSTED_NAME_HEADER:
+            trusted_name = request.headers.get(
+                WEBUI_AUTH_TRUSTED_NAME_HEADER, trusted_email
+            )
+        if not Users.get_user_by_email(trusted_email.lower()):
+            await signup(
+                request,
+                response,
+                SignupForm(
+                    email=trusted_email, password=str(uuid.uuid4()), name=trusted_name
+                ),
+            )
+        user = Auths.authenticate_user_by_trusted_header(trusted_email)
+    elif WEBUI_AUTH == False:
+        admin_email = "admin@localhost"
+        admin_password = "admin"
+
+        if Users.get_user_by_email(admin_email.lower()):
+            user = Auths.authenticate_user(admin_email.lower(), admin_password)
+        else:
+            if Users.get_num_users() != 0:
+                raise HTTPException(400, detail=ERROR_MESSAGES.EXISTING_USERS)
+
+            await signup(
+                request,
+                response,
+                SignupForm(email=admin_email, password=admin_password, name="User"),
+            )
+
+            user = Auths.authenticate_user(admin_email.lower(), admin_password)
+    else:
+        user = Auths.authenticate_user(form_data.email.lower(), form_data.password)
+
+    if user:
+
+        expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
+        expires_at = None
+        if expires_delta:
+            expires_at = int(time.time()) + int(expires_delta.total_seconds())
+
+        token = create_token(
+            data={"id": user.id},
+            expires_delta=expires_delta,
+        )
+
+        datetime_expires_at = (
+            datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc)
+            if expires_at
+            else None
+        )
+
+        # Set the cookie token
+        response.set_cookie(
+            key="token",
+            value=token,
+            expires=datetime_expires_at,
+            httponly=True,  # Ensures the cookie is not accessible via JavaScript
+            samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
+            secure=WEBUI_SESSION_COOKIE_SECURE,
+        )
+
+        return {
+            "token": token,
+            "token_type": "Bearer",
+            "expires_at": expires_at,
+            "id": user.id,
+            "email": user.email,
+            "name": user.name,
+            "role": user.role,
+            "profile_image_url": user.profile_image_url,
+        }
+    else:
+        raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
+
+
+############################
+# SignUp
+############################
+
+
+@router.post("/signup", response_model=SessionUserResponse)
+async def signup(request: Request, response: Response, form_data: SignupForm):
+    if WEBUI_AUTH:
+        if (
+            not request.app.state.config.ENABLE_SIGNUP
+            or not request.app.state.config.ENABLE_LOGIN_FORM
+        ):
+            raise HTTPException(
+                status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
+            )
+    else:
+        if Users.get_num_users() != 0:
+            raise HTTPException(
+                status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
+            )
+
+    if not validate_email_format(form_data.email.lower()):
+        raise HTTPException(
+            status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
+        )
+
+    if Users.get_user_by_email(form_data.email.lower()):
+        raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
+
+    try:
+        role = (
+            "admin"
+            if Users.get_num_users() == 0
+            else request.app.state.config.DEFAULT_USER_ROLE
+        )
+        hashed = get_password_hash(form_data.password)
+        user = Auths.insert_new_auth(
+            form_data.email.lower(),
+            hashed,
+            form_data.name,
+            form_data.profile_image_url,
+            role,
+        )
+
+        if user:
+            expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
+            expires_at = None
+            if expires_delta:
+                expires_at = int(time.time()) + int(expires_delta.total_seconds())
+
+            token = create_token(
+                data={"id": user.id},
+                expires_delta=expires_delta,
+            )
+
+            datetime_expires_at = (
+                datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc)
+                if expires_at
+                else None
+            )
+
+            # Set the cookie token
+            response.set_cookie(
+                key="token",
+                value=token,
+                expires=datetime_expires_at,
+                httponly=True,  # Ensures the cookie is not accessible via JavaScript
+                samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
+                secure=WEBUI_SESSION_COOKIE_SECURE,
+            )
+
+            if request.app.state.config.WEBHOOK_URL:
+                post_webhook(
+                    request.app.state.config.WEBHOOK_URL,
+                    WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
+                    {
+                        "action": "signup",
+                        "message": WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
+                        "user": user.model_dump_json(exclude_none=True),
+                    },
+                )
+
+            return {
+                "token": token,
+                "token_type": "Bearer",
+                "expires_at": expires_at,
+                "id": user.id,
+                "email": user.email,
+                "name": user.name,
+                "role": user.role,
+                "profile_image_url": user.profile_image_url,
+            }
+        else:
+            raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
+    except Exception as err:
+        raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
+
+
+@router.get("/signout")
+async def signout(response: Response):
+    response.delete_cookie("token")
+    return {"status": True}
+
+
+############################
+# AddUser
+############################
+
+
+@router.post("/add", response_model=SigninResponse)
+async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)):
+    if not validate_email_format(form_data.email.lower()):
+        raise HTTPException(
+            status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
+        )
+
+    if Users.get_user_by_email(form_data.email.lower()):
+        raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
+
+    try:
+        print(form_data)
+        hashed = get_password_hash(form_data.password)
+        user = Auths.insert_new_auth(
+            form_data.email.lower(),
+            hashed,
+            form_data.name,
+            form_data.profile_image_url,
+            form_data.role,
+        )
+
+        if user:
+            token = create_token(data={"id": user.id})
+            return {
+                "token": token,
+                "token_type": "Bearer",
+                "id": user.id,
+                "email": user.email,
+                "name": user.name,
+                "role": user.role,
+                "profile_image_url": user.profile_image_url,
+            }
+        else:
+            raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
+    except Exception as err:
+        raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
+
+
+############################
+# GetAdminDetails
+############################
+
+
+@router.get("/admin/details")
+async def get_admin_details(request: Request, user=Depends(get_current_user)):
+    if request.app.state.config.SHOW_ADMIN_DETAILS:
+        admin_email = request.app.state.config.ADMIN_EMAIL
+        admin_name = None
+
+        print(admin_email, admin_name)
+
+        if admin_email:
+            admin = Users.get_user_by_email(admin_email)
+            if admin:
+                admin_name = admin.name
+        else:
+            admin = Users.get_first_user()
+            if admin:
+                admin_email = admin.email
+                admin_name = admin.name
+
+        return {
+            "name": admin_name,
+            "email": admin_email,
+        }
+    else:
+        raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED)
+
+
+############################
+# ToggleSignUp
+############################
+
+
+@router.get("/admin/config")
+async def get_admin_config(request: Request, user=Depends(get_admin_user)):
+    return {
+        "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS,
+        "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP,
+        "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE,
+        "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN,
+        "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING,
+        "ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING,
+    }
+
+
+class AdminConfig(BaseModel):
+    SHOW_ADMIN_DETAILS: bool
+    ENABLE_SIGNUP: bool
+    DEFAULT_USER_ROLE: str
+    JWT_EXPIRES_IN: str
+    ENABLE_COMMUNITY_SHARING: bool
+    ENABLE_MESSAGE_RATING: bool
+
+
+@router.post("/admin/config")
+async def update_admin_config(
+    request: Request, form_data: AdminConfig, user=Depends(get_admin_user)
+):
+    request.app.state.config.SHOW_ADMIN_DETAILS = form_data.SHOW_ADMIN_DETAILS
+    request.app.state.config.ENABLE_SIGNUP = form_data.ENABLE_SIGNUP
+
+    if form_data.DEFAULT_USER_ROLE in ["pending", "user", "admin"]:
+        request.app.state.config.DEFAULT_USER_ROLE = form_data.DEFAULT_USER_ROLE
+
+    pattern = r"^(-1|0|(-?\d+(\.\d+)?)(ms|s|m|h|d|w))$"
+
+    # Check if the input string matches the pattern
+    if re.match(pattern, form_data.JWT_EXPIRES_IN):
+        request.app.state.config.JWT_EXPIRES_IN = form_data.JWT_EXPIRES_IN
+
+    request.app.state.config.ENABLE_COMMUNITY_SHARING = (
+        form_data.ENABLE_COMMUNITY_SHARING
+    )
+    request.app.state.config.ENABLE_MESSAGE_RATING = form_data.ENABLE_MESSAGE_RATING
+
+    return {
+        "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS,
+        "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP,
+        "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE,
+        "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN,
+        "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING,
+        "ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING,
+    }
+
+
+############################
+# API Key
+############################
+
+
+# create api key
+@router.post("/api_key", response_model=ApiKey)
+async def create_api_key_(user=Depends(get_current_user)):
+    api_key = create_api_key()
+    success = Users.update_user_api_key_by_id(user.id, api_key)
+    if success:
+        return {
+            "api_key": api_key,
+        }
+    else:
+        raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_API_KEY_ERROR)
+
+
+# delete api key
+@router.delete("/api_key", response_model=bool)
+async def delete_api_key(user=Depends(get_current_user)):
+    success = Users.update_user_api_key_by_id(user.id, None)
+    return success
+
+
+# get api key
+@router.get("/api_key", response_model=ApiKey)
+async def get_api_key(user=Depends(get_current_user)):
+    api_key = Users.get_user_api_key_by_id(user.id)
+    if api_key:
+        return {
+            "api_key": api_key,
+        }
+    else:
+        raise HTTPException(404, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)
diff --git a/backend/open_webui/apps/webui/routers/chats.py b/backend/open_webui/apps/webui/routers/chats.py
new file mode 100644
index 0000000000000000000000000000000000000000..b149b2eb48b50bd730e870446be3501d62a1ad59
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/chats.py
@@ -0,0 +1,665 @@
+import json
+import logging
+from typing import Optional
+
+from open_webui.apps.webui.models.chats import (
+    ChatForm,
+    ChatImportForm,
+    ChatResponse,
+    Chats,
+    ChatTitleIdResponse,
+)
+from open_webui.apps.webui.models.tags import TagModel, Tags
+from open_webui.apps.webui.models.folders import Folders
+
+from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.env import SRC_LOG_LEVELS
+from fastapi import APIRouter, Depends, HTTPException, Request, status
+from pydantic import BaseModel
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+router = APIRouter()
+
+############################
+# GetChatList
+############################
+
+
+@router.get("/", response_model=list[ChatTitleIdResponse])
+@router.get("/list", response_model=list[ChatTitleIdResponse])
+async def get_session_user_chat_list(
+    user=Depends(get_verified_user), page: Optional[int] = None
+):
+    if page is not None:
+        limit = 60
+        skip = (page - 1) * limit
+
+        return Chats.get_chat_title_id_list_by_user_id(user.id, skip=skip, limit=limit)
+    else:
+        return Chats.get_chat_title_id_list_by_user_id(user.id)
+
+
+############################
+# DeleteAllChats
+############################
+
+
+@router.delete("/", response_model=bool)
+async def delete_all_user_chats(request: Request, user=Depends(get_verified_user)):
+    if user.role == "user" and not request.app.state.config.USER_PERMISSIONS.get(
+        "chat", {}
+    ).get("deletion", {}):
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+
+    result = Chats.delete_chats_by_user_id(user.id)
+    return result
+
+
+############################
+# GetUserChatList
+############################
+
+
+@router.get("/list/user/{user_id}", response_model=list[ChatTitleIdResponse])
+async def get_user_chat_list_by_user_id(
+    user_id: str,
+    user=Depends(get_admin_user),
+    skip: int = 0,
+    limit: int = 50,
+):
+    if not ENABLE_ADMIN_CHAT_ACCESS:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+    return Chats.get_chat_list_by_user_id(
+        user_id, include_archived=True, skip=skip, limit=limit
+    )
+
+
+############################
+# CreateNewChat
+############################
+
+
+@router.post("/new", response_model=Optional[ChatResponse])
+async def create_new_chat(form_data: ChatForm, user=Depends(get_verified_user)):
+    try:
+        chat = Chats.insert_new_chat(user.id, form_data)
+        return ChatResponse(**chat.model_dump())
+    except Exception as e:
+        log.exception(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
+        )
+
+
+############################
+# ImportChat
+############################
+
+
+@router.post("/import", response_model=Optional[ChatResponse])
+async def import_chat(form_data: ChatImportForm, user=Depends(get_verified_user)):
+    try:
+        chat = Chats.import_chat(user.id, form_data)
+        if chat:
+            tags = chat.meta.get("tags", [])
+            for tag_id in tags:
+                tag_id = tag_id.replace(" ", "_").lower()
+                tag_name = " ".join([word.capitalize() for word in tag_id.split("_")])
+                if (
+                    tag_id != "none"
+                    and Tags.get_tag_by_name_and_user_id(tag_name, user.id) is None
+                ):
+                    Tags.insert_new_tag(tag_name, user.id)
+
+        return ChatResponse(**chat.model_dump())
+    except Exception as e:
+        log.exception(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
+        )
+
+
+############################
+# GetChats
+############################
+
+
+@router.get("/search", response_model=list[ChatTitleIdResponse])
+async def search_user_chats(
+    text: str, page: Optional[int] = None, user=Depends(get_verified_user)
+):
+    if page is None:
+        page = 1
+
+    limit = 60
+    skip = (page - 1) * limit
+
+    chat_list = [
+        ChatTitleIdResponse(**chat.model_dump())
+        for chat in Chats.get_chats_by_user_id_and_search_text(
+            user.id, text, skip=skip, limit=limit
+        )
+    ]
+
+    # Delete tag if no chat is found
+    words = text.strip().split(" ")
+    if page == 1 and len(words) == 1 and words[0].startswith("tag:"):
+        tag_id = words[0].replace("tag:", "")
+        if len(chat_list) == 0:
+            if Tags.get_tag_by_name_and_user_id(tag_id, user.id):
+                log.debug(f"deleting tag: {tag_id}")
+                Tags.delete_tag_by_name_and_user_id(tag_id, user.id)
+
+    return chat_list
+
+
+############################
+# GetChatsByFolderId
+############################
+
+
+@router.get("/folder/{folder_id}", response_model=list[ChatResponse])
+async def get_chats_by_folder_id(folder_id: str, user=Depends(get_verified_user)):
+    folder_ids = [folder_id]
+    children_folders = Folders.get_children_folders_by_id_and_user_id(
+        folder_id, user.id
+    )
+    if children_folders:
+        folder_ids.extend([folder.id for folder in children_folders])
+
+    return [
+        ChatResponse(**chat.model_dump())
+        for chat in Chats.get_chats_by_folder_ids_and_user_id(folder_ids, user.id)
+    ]
+
+
+############################
+# GetPinnedChats
+############################
+
+
+@router.get("/pinned", response_model=list[ChatResponse])
+async def get_user_pinned_chats(user=Depends(get_verified_user)):
+    return [
+        ChatResponse(**chat.model_dump())
+        for chat in Chats.get_pinned_chats_by_user_id(user.id)
+    ]
+
+
+############################
+# GetChats
+############################
+
+
+@router.get("/all", response_model=list[ChatResponse])
+async def get_user_chats(user=Depends(get_verified_user)):
+    return [
+        ChatResponse(**chat.model_dump())
+        for chat in Chats.get_chats_by_user_id(user.id)
+    ]
+
+
+############################
+# GetArchivedChats
+############################
+
+
+@router.get("/all/archived", response_model=list[ChatResponse])
+async def get_user_archived_chats(user=Depends(get_verified_user)):
+    return [
+        ChatResponse(**chat.model_dump())
+        for chat in Chats.get_archived_chats_by_user_id(user.id)
+    ]
+
+
+############################
+# GetAllTags
+############################
+
+
+@router.get("/all/tags", response_model=list[TagModel])
+async def get_all_user_tags(user=Depends(get_verified_user)):
+    try:
+        tags = Tags.get_tags_by_user_id(user.id)
+        return tags
+    except Exception as e:
+        log.exception(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
+        )
+
+
+############################
+# GetAllChatsInDB
+############################
+
+
+@router.get("/all/db", response_model=list[ChatResponse])
+async def get_all_user_chats_in_db(user=Depends(get_admin_user)):
+    if not ENABLE_ADMIN_EXPORT:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+    return [ChatResponse(**chat.model_dump()) for chat in Chats.get_chats()]
+
+
+############################
+# GetArchivedChats
+############################
+
+
+@router.get("/archived", response_model=list[ChatTitleIdResponse])
+async def get_archived_session_user_chat_list(
+    user=Depends(get_verified_user), skip: int = 0, limit: int = 50
+):
+    return Chats.get_archived_chat_list_by_user_id(user.id, skip, limit)
+
+
+############################
+# ArchiveAllChats
+############################
+
+
+@router.post("/archive/all", response_model=bool)
+async def archive_all_chats(user=Depends(get_verified_user)):
+    return Chats.archive_all_chats_by_user_id(user.id)
+
+
+############################
+# GetSharedChatById
+############################
+
+
+@router.get("/share/{share_id}", response_model=Optional[ChatResponse])
+async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user)):
+    if user.role == "pending":
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
+        )
+
+    if user.role == "user" or (user.role == "admin" and not ENABLE_ADMIN_CHAT_ACCESS):
+        chat = Chats.get_chat_by_share_id(share_id)
+    elif user.role == "admin" and ENABLE_ADMIN_CHAT_ACCESS:
+        chat = Chats.get_chat_by_id(share_id)
+
+    if chat:
+        return ChatResponse(**chat.model_dump())
+
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
+        )
+
+
+############################
+# GetChatsByTags
+############################
+
+
+class TagForm(BaseModel):
+    name: str
+
+
+class TagFilterForm(TagForm):
+    skip: Optional[int] = 0
+    limit: Optional[int] = 50
+
+
+@router.post("/tags", response_model=list[ChatTitleIdResponse])
+async def get_user_chat_list_by_tag_name(
+    form_data: TagFilterForm, user=Depends(get_verified_user)
+):
+    chats = Chats.get_chat_list_by_user_id_and_tag_name(
+        user.id, form_data.name, form_data.skip, form_data.limit
+    )
+    if len(chats) == 0:
+        Tags.delete_tag_by_name_and_user_id(form_data.name, user.id)
+
+    return chats
+
+
+############################
+# GetChatById
+############################
+
+
+@router.get("/{id}", response_model=Optional[ChatResponse])
+async def get_chat_by_id(id: str, user=Depends(get_verified_user)):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+
+    if chat:
+        return ChatResponse(**chat.model_dump())
+
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
+        )
+
+
+############################
+# UpdateChatById
+############################
+
+
+@router.post("/{id}", response_model=Optional[ChatResponse])
+async def update_chat_by_id(
+    id: str, form_data: ChatForm, user=Depends(get_verified_user)
+):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        updated_chat = {**chat.chat, **form_data.chat}
+        chat = Chats.update_chat_by_id(id, updated_chat)
+        return ChatResponse(**chat.model_dump())
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+
+
+############################
+# DeleteChatById
+############################
+
+
+@router.delete("/{id}", response_model=bool)
+async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified_user)):
+    if user.role == "admin":
+        chat = Chats.get_chat_by_id(id)
+        for tag in chat.meta.get("tags", []):
+            if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 1:
+                Tags.delete_tag_by_name_and_user_id(tag, user.id)
+
+        result = Chats.delete_chat_by_id(id)
+
+        return result
+    else:
+        if not request.app.state.config.USER_PERMISSIONS.get("chat", {}).get(
+            "deletion", {}
+        ):
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+            )
+
+        chat = Chats.get_chat_by_id(id)
+        for tag in chat.meta.get("tags", []):
+            if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 1:
+                Tags.delete_tag_by_name_and_user_id(tag, user.id)
+
+        result = Chats.delete_chat_by_id_and_user_id(id, user.id)
+        return result
+
+
+############################
+# GetPinnedStatusById
+############################
+
+
+@router.get("/{id}/pinned", response_model=Optional[bool])
+async def get_pinned_status_by_id(id: str, user=Depends(get_verified_user)):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        return chat.pinned
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
+        )
+
+
+############################
+# PinChatById
+############################
+
+
+@router.post("/{id}/pin", response_model=Optional[ChatResponse])
+async def pin_chat_by_id(id: str, user=Depends(get_verified_user)):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        chat = Chats.toggle_chat_pinned_by_id(id)
+        return chat
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
+        )
+
+
+############################
+# CloneChat
+############################
+
+
+@router.post("/{id}/clone", response_model=Optional[ChatResponse])
+async def clone_chat_by_id(id: str, user=Depends(get_verified_user)):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        updated_chat = {
+            **chat.chat,
+            "originalChatId": chat.id,
+            "branchPointMessageId": chat.chat["history"]["currentId"],
+            "title": f"Clone of {chat.title}",
+        }
+
+        chat = Chats.insert_new_chat(user.id, ChatForm(**{"chat": updated_chat}))
+        return ChatResponse(**chat.model_dump())
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
+        )
+
+
+############################
+# ArchiveChat
+############################
+
+
+@router.post("/{id}/archive", response_model=Optional[ChatResponse])
+async def archive_chat_by_id(id: str, user=Depends(get_verified_user)):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        chat = Chats.toggle_chat_archive_by_id(id)
+
+        # Delete tags if chat is archived
+        if chat.archived:
+            for tag_id in chat.meta.get("tags", []):
+                if Chats.count_chats_by_tag_name_and_user_id(tag_id, user.id) == 0:
+                    log.debug(f"deleting tag: {tag_id}")
+                    Tags.delete_tag_by_name_and_user_id(tag_id, user.id)
+        else:
+            for tag_id in chat.meta.get("tags", []):
+                tag = Tags.get_tag_by_name_and_user_id(tag_id, user.id)
+                if tag is None:
+                    log.debug(f"inserting tag: {tag_id}")
+                    tag = Tags.insert_new_tag(tag_id, user.id)
+
+        return ChatResponse(**chat.model_dump())
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
+        )
+
+
+############################
+# ShareChatById
+############################
+
+
+@router.post("/{id}/share", response_model=Optional[ChatResponse])
+async def share_chat_by_id(id: str, user=Depends(get_verified_user)):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        if chat.share_id:
+            shared_chat = Chats.update_shared_chat_by_chat_id(chat.id)
+            return ChatResponse(**shared_chat.model_dump())
+
+        shared_chat = Chats.insert_shared_chat_by_chat_id(chat.id)
+        if not shared_chat:
+            raise HTTPException(
+                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+                detail=ERROR_MESSAGES.DEFAULT(),
+            )
+        return ChatResponse(**shared_chat.model_dump())
+
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+
+
+############################
+# DeletedSharedChatById
+############################
+
+
+@router.delete("/{id}/share", response_model=Optional[bool])
+async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user)):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        if not chat.share_id:
+            return False
+
+        result = Chats.delete_shared_chat_by_chat_id(id)
+        update_result = Chats.update_chat_share_id_by_id(id, None)
+
+        return result and update_result != None
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+
+
+############################
+# UpdateChatFolderIdById
+############################
+
+
+class ChatFolderIdForm(BaseModel):
+    folder_id: Optional[str] = None
+
+
+@router.post("/{id}/folder", response_model=Optional[ChatResponse])
+async def update_chat_folder_id_by_id(
+    id: str, form_data: ChatFolderIdForm, user=Depends(get_verified_user)
+):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        chat = Chats.update_chat_folder_id_by_id_and_user_id(
+            id, user.id, form_data.folder_id
+        )
+        return ChatResponse(**chat.model_dump())
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
+        )
+
+
+############################
+# GetChatTagsById
+############################
+
+
+@router.get("/{id}/tags", response_model=list[TagModel])
+async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user)):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        tags = chat.meta.get("tags", [])
+        return Tags.get_tags_by_ids_and_user_id(tags, user.id)
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
+        )
+
+
+############################
+# AddChatTagById
+############################
+
+
+@router.post("/{id}/tags", response_model=list[TagModel])
+async def add_tag_by_id_and_tag_name(
+    id: str, form_data: TagForm, user=Depends(get_verified_user)
+):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        tags = chat.meta.get("tags", [])
+        tag_id = form_data.name.replace(" ", "_").lower()
+
+        if tag_id == "none":
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Tag name cannot be 'None'"),
+            )
+
+        print(tags, tag_id)
+        if tag_id not in tags:
+            Chats.add_chat_tag_by_id_and_user_id_and_tag_name(
+                id, user.id, form_data.name
+            )
+
+        chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+        tags = chat.meta.get("tags", [])
+        return Tags.get_tags_by_ids_and_user_id(tags, user.id)
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
+        )
+
+
+############################
+# DeleteChatTagById
+############################
+
+
+@router.delete("/{id}/tags", response_model=list[TagModel])
+async def delete_tag_by_id_and_tag_name(
+    id: str, form_data: TagForm, user=Depends(get_verified_user)
+):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        Chats.delete_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name)
+
+        if Chats.count_chats_by_tag_name_and_user_id(form_data.name, user.id) == 0:
+            Tags.delete_tag_by_name_and_user_id(form_data.name, user.id)
+
+        chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+        tags = chat.meta.get("tags", [])
+        return Tags.get_tags_by_ids_and_user_id(tags, user.id)
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
+        )
+
+
+############################
+# DeleteAllTagsById
+############################
+
+
+@router.delete("/{id}/tags/all", response_model=Optional[bool])
+async def delete_all_tags_by_id(id: str, user=Depends(get_verified_user)):
+    chat = Chats.get_chat_by_id_and_user_id(id, user.id)
+    if chat:
+        Chats.delete_all_tags_by_id_and_user_id(id, user.id)
+
+        for tag in chat.meta.get("tags", []):
+            if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 0:
+                Tags.delete_tag_by_name_and_user_id(tag, user.id)
+
+        return True
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
+        )
diff --git a/backend/open_webui/apps/webui/routers/configs.py b/backend/open_webui/apps/webui/routers/configs.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c30b0b3bb85f76a01920d59353e544f23d8dbe2
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/configs.py
@@ -0,0 +1,99 @@
+from open_webui.config import BannerModel
+from fastapi import APIRouter, Depends, Request
+from pydantic import BaseModel
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+
+from open_webui.config import get_config, save_config
+
+router = APIRouter()
+
+
+############################
+# ImportConfig
+############################
+
+
+class ImportConfigForm(BaseModel):
+    config: dict
+
+
+@router.post("/import", response_model=dict)
+async def import_config(form_data: ImportConfigForm, user=Depends(get_admin_user)):
+    save_config(form_data.config)
+    return get_config()
+
+
+############################
+# ExportConfig
+############################
+
+
+@router.get("/export", response_model=dict)
+async def export_config(user=Depends(get_admin_user)):
+    return get_config()
+
+
+class SetDefaultModelsForm(BaseModel):
+    models: str
+
+
+class PromptSuggestion(BaseModel):
+    title: list[str]
+    content: str
+
+
+class SetDefaultSuggestionsForm(BaseModel):
+    suggestions: list[PromptSuggestion]
+
+
+############################
+# SetDefaultModels
+############################
+
+
+@router.post("/default/models", response_model=str)
+async def set_global_default_models(
+    request: Request, form_data: SetDefaultModelsForm, user=Depends(get_admin_user)
+):
+    request.app.state.config.DEFAULT_MODELS = form_data.models
+    return request.app.state.config.DEFAULT_MODELS
+
+
+@router.post("/default/suggestions", response_model=list[PromptSuggestion])
+async def set_global_default_suggestions(
+    request: Request,
+    form_data: SetDefaultSuggestionsForm,
+    user=Depends(get_admin_user),
+):
+    data = form_data.model_dump()
+    request.app.state.config.DEFAULT_PROMPT_SUGGESTIONS = data["suggestions"]
+    return request.app.state.config.DEFAULT_PROMPT_SUGGESTIONS
+
+
+############################
+# SetBanners
+############################
+
+
+class SetBannersForm(BaseModel):
+    banners: list[BannerModel]
+
+
+@router.post("/banners", response_model=list[BannerModel])
+async def set_banners(
+    request: Request,
+    form_data: SetBannersForm,
+    user=Depends(get_admin_user),
+):
+    data = form_data.model_dump()
+    request.app.state.config.BANNERS = data["banners"]
+    return request.app.state.config.BANNERS
+
+
+@router.get("/banners", response_model=list[BannerModel])
+async def get_banners(
+    request: Request,
+    user=Depends(get_verified_user),
+):
+    return request.app.state.config.BANNERS
diff --git a/backend/open_webui/apps/webui/routers/documents.py b/backend/open_webui/apps/webui/routers/documents.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8f27852f4e25dcce304f1f95e26cfd5c63a3c4b
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/documents.py
@@ -0,0 +1,155 @@
+import json
+from typing import Optional
+
+from open_webui.apps.webui.models.documents import (
+    DocumentForm,
+    DocumentResponse,
+    Documents,
+    DocumentUpdateForm,
+)
+from open_webui.constants import ERROR_MESSAGES
+from fastapi import APIRouter, Depends, HTTPException, status
+from pydantic import BaseModel
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+router = APIRouter()
+
+############################
+# GetDocuments
+############################
+
+
+@router.get("/", response_model=list[DocumentResponse])
+async def get_documents(user=Depends(get_verified_user)):
+    docs = [
+        DocumentResponse(
+            **{
+                **doc.model_dump(),
+                "content": json.loads(doc.content if doc.content else "{}"),
+            }
+        )
+        for doc in Documents.get_docs()
+    ]
+    return docs
+
+
+############################
+# CreateNewDoc
+############################
+
+
+@router.post("/create", response_model=Optional[DocumentResponse])
+async def create_new_doc(form_data: DocumentForm, user=Depends(get_admin_user)):
+    doc = Documents.get_doc_by_name(form_data.name)
+    if doc is None:
+        doc = Documents.insert_new_doc(user.id, form_data)
+
+        if doc:
+            return DocumentResponse(
+                **{
+                    **doc.model_dump(),
+                    "content": json.loads(doc.content if doc.content else "{}"),
+                }
+            )
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.FILE_EXISTS,
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.NAME_TAG_TAKEN,
+        )
+
+
+############################
+# GetDocByName
+############################
+
+
+@router.get("/doc", response_model=Optional[DocumentResponse])
+async def get_doc_by_name(name: str, user=Depends(get_verified_user)):
+    doc = Documents.get_doc_by_name(name)
+
+    if doc:
+        return DocumentResponse(
+            **{
+                **doc.model_dump(),
+                "content": json.loads(doc.content if doc.content else "{}"),
+            }
+        )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# TagDocByName
+############################
+
+
+class TagItem(BaseModel):
+    name: str
+
+
+class TagDocumentForm(BaseModel):
+    name: str
+    tags: list[dict]
+
+
+@router.post("/doc/tags", response_model=Optional[DocumentResponse])
+async def tag_doc_by_name(form_data: TagDocumentForm, user=Depends(get_verified_user)):
+    doc = Documents.update_doc_content_by_name(form_data.name, {"tags": form_data.tags})
+
+    if doc:
+        return DocumentResponse(
+            **{
+                **doc.model_dump(),
+                "content": json.loads(doc.content if doc.content else "{}"),
+            }
+        )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# UpdateDocByName
+############################
+
+
+@router.post("/doc/update", response_model=Optional[DocumentResponse])
+async def update_doc_by_name(
+    name: str,
+    form_data: DocumentUpdateForm,
+    user=Depends(get_admin_user),
+):
+    doc = Documents.update_doc_by_name(name, form_data)
+    if doc:
+        return DocumentResponse(
+            **{
+                **doc.model_dump(),
+                "content": json.loads(doc.content if doc.content else "{}"),
+            }
+        )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.NAME_TAG_TAKEN,
+        )
+
+
+############################
+# DeleteDocByName
+############################
+
+
+@router.delete("/doc/delete", response_model=bool)
+async def delete_doc_by_name(name: str, user=Depends(get_admin_user)):
+    result = Documents.delete_doc_by_name(name)
+    return result
diff --git a/backend/open_webui/apps/webui/routers/evaluations.py b/backend/open_webui/apps/webui/routers/evaluations.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9e3bff29bfc9da372e3b5ac52d1cf3892181a99
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/evaluations.py
@@ -0,0 +1,159 @@
+from typing import Optional
+from fastapi import APIRouter, Depends, HTTPException, status, Request
+from pydantic import BaseModel
+
+from open_webui.apps.webui.models.users import Users, UserModel
+from open_webui.apps.webui.models.feedbacks import (
+    FeedbackModel,
+    FeedbackResponse,
+    FeedbackForm,
+    Feedbacks,
+)
+
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+router = APIRouter()
+
+
+############################
+# GetConfig
+############################
+
+
+@router.get("/config")
+async def get_config(request: Request, user=Depends(get_admin_user)):
+    return {
+        "ENABLE_EVALUATION_ARENA_MODELS": request.app.state.config.ENABLE_EVALUATION_ARENA_MODELS,
+        "EVALUATION_ARENA_MODELS": request.app.state.config.EVALUATION_ARENA_MODELS,
+    }
+
+
+############################
+# UpdateConfig
+############################
+
+
+class UpdateConfigForm(BaseModel):
+    ENABLE_EVALUATION_ARENA_MODELS: Optional[bool] = None
+    EVALUATION_ARENA_MODELS: Optional[list[dict]] = None
+
+
+@router.post("/config")
+async def update_config(
+    request: Request,
+    form_data: UpdateConfigForm,
+    user=Depends(get_admin_user),
+):
+    config = request.app.state.config
+    if form_data.ENABLE_EVALUATION_ARENA_MODELS is not None:
+        config.ENABLE_EVALUATION_ARENA_MODELS = form_data.ENABLE_EVALUATION_ARENA_MODELS
+    if form_data.EVALUATION_ARENA_MODELS is not None:
+        config.EVALUATION_ARENA_MODELS = form_data.EVALUATION_ARENA_MODELS
+    return {
+        "ENABLE_EVALUATION_ARENA_MODELS": config.ENABLE_EVALUATION_ARENA_MODELS,
+        "EVALUATION_ARENA_MODELS": config.EVALUATION_ARENA_MODELS,
+    }
+
+
+class FeedbackUserResponse(FeedbackResponse):
+    user: Optional[UserModel] = None
+
+
+@router.get("/feedbacks/all", response_model=list[FeedbackUserResponse])
+async def get_all_feedbacks(user=Depends(get_admin_user)):
+    feedbacks = Feedbacks.get_all_feedbacks()
+    return [
+        FeedbackUserResponse(
+            **feedback.model_dump(), user=Users.get_user_by_id(feedback.user_id)
+        )
+        for feedback in feedbacks
+    ]
+
+
+@router.delete("/feedbacks/all")
+async def delete_all_feedbacks(user=Depends(get_admin_user)):
+    success = Feedbacks.delete_all_feedbacks()
+    return success
+
+
+@router.get("/feedbacks/all/export", response_model=list[FeedbackModel])
+async def get_all_feedbacks(user=Depends(get_admin_user)):
+    feedbacks = Feedbacks.get_all_feedbacks()
+    return [
+        FeedbackModel(
+            **feedback.model_dump(), user=Users.get_user_by_id(feedback.user_id)
+        )
+        for feedback in feedbacks
+    ]
+
+
+@router.get("/feedbacks/user", response_model=list[FeedbackUserResponse])
+async def get_feedbacks(user=Depends(get_verified_user)):
+    feedbacks = Feedbacks.get_feedbacks_by_user_id(user.id)
+    return feedbacks
+
+
+@router.delete("/feedbacks", response_model=bool)
+async def delete_feedbacks(user=Depends(get_verified_user)):
+    success = Feedbacks.delete_feedbacks_by_user_id(user.id)
+    return success
+
+
+@router.post("/feedback", response_model=FeedbackModel)
+async def create_feedback(
+    request: Request,
+    form_data: FeedbackForm,
+    user=Depends(get_verified_user),
+):
+    feedback = Feedbacks.insert_new_feedback(user_id=user.id, form_data=form_data)
+    if not feedback:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(),
+        )
+
+    return feedback
+
+
+@router.get("/feedback/{id}", response_model=FeedbackModel)
+async def get_feedback_by_id(id: str, user=Depends(get_verified_user)):
+    feedback = Feedbacks.get_feedback_by_id_and_user_id(id=id, user_id=user.id)
+
+    if not feedback:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
+        )
+
+    return feedback
+
+
+@router.post("/feedback/{id}", response_model=FeedbackModel)
+async def update_feedback_by_id(
+    id: str, form_data: FeedbackForm, user=Depends(get_verified_user)
+):
+    feedback = Feedbacks.update_feedback_by_id_and_user_id(
+        id=id, user_id=user.id, form_data=form_data
+    )
+
+    if not feedback:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
+        )
+
+    return feedback
+
+
+@router.delete("/feedback/{id}")
+async def delete_feedback_by_id(id: str, user=Depends(get_verified_user)):
+    if user.role == "admin":
+        success = Feedbacks.delete_feedback_by_id(id=id)
+    else:
+        success = Feedbacks.delete_feedback_by_id_and_user_id(id=id, user_id=user.id)
+
+    if not success:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
+        )
+
+    return success
diff --git a/backend/open_webui/apps/webui/routers/files.py b/backend/open_webui/apps/webui/routers/files.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8695eb67255afd27588d40363e957b97c92920d
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/files.py
@@ -0,0 +1,349 @@
+import logging
+import os
+import uuid
+from pathlib import Path
+from typing import Optional
+from pydantic import BaseModel
+import mimetypes
+
+from open_webui.storage.provider import Storage
+
+from open_webui.apps.webui.models.files import (
+    FileForm,
+    FileModel,
+    FileModelResponse,
+    Files,
+)
+from open_webui.apps.retrieval.main import process_file, ProcessFileForm
+
+from open_webui.config import UPLOAD_DIR
+from open_webui.env import SRC_LOG_LEVELS
+from open_webui.constants import ERROR_MESSAGES
+
+
+from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
+from fastapi.responses import FileResponse, StreamingResponse
+
+
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+
+router = APIRouter()
+
+############################
+# Upload File
+############################
+
+
+@router.post("/", response_model=FileModelResponse)
+def upload_file(file: UploadFile = File(...), user=Depends(get_verified_user)):
+    log.info(f"file.content_type: {file.content_type}")
+    try:
+        unsanitized_filename = file.filename
+        filename = os.path.basename(unsanitized_filename)
+
+        # replace filename with uuid
+        id = str(uuid.uuid4())
+        name = filename
+        filename = f"{id}_{filename}"
+        contents, file_path = Storage.upload_file(file.file, filename)
+
+        file_item = Files.insert_new_file(
+            user.id,
+            FileForm(
+                **{
+                    "id": id,
+                    "filename": filename,
+                    "path": file_path,
+                    "meta": {
+                        "name": name,
+                        "content_type": file.content_type,
+                        "size": len(contents),
+                    },
+                }
+            ),
+        )
+
+        try:
+            process_file(ProcessFileForm(file_id=id))
+            file_item = Files.get_file_by_id(id=id)
+        except Exception as e:
+            log.exception(e)
+            log.error(f"Error processing file: {file_item.id}")
+            file_item = FileModelResponse(
+                **{
+                    **file_item.model_dump(),
+                    "error": str(e.detail) if hasattr(e, "detail") else str(e),
+                }
+            )
+
+        if file_item:
+            return file_item
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error uploading file"),
+            )
+
+    except Exception as e:
+        log.exception(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+############################
+# List Files
+############################
+
+
+@router.get("/", response_model=list[FileModelResponse])
+async def list_files(user=Depends(get_verified_user)):
+    if user.role == "admin":
+        files = Files.get_files()
+    else:
+        files = Files.get_files_by_user_id(user.id)
+    return files
+
+
+############################
+# Delete All Files
+############################
+
+
+@router.delete("/all")
+async def delete_all_files(user=Depends(get_admin_user)):
+    result = Files.delete_all_files()
+    if result:
+        try:
+            Storage.delete_all_files()
+        except Exception as e:
+            log.exception(e)
+            log.error(f"Error deleting files")
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error deleting files"),
+            )
+        return {"message": "All files deleted successfully"}
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT("Error deleting files"),
+        )
+
+
+############################
+# Get File By Id
+############################
+
+
+@router.get("/{id}", response_model=Optional[FileModel])
+async def get_file_by_id(id: str, user=Depends(get_verified_user)):
+    file = Files.get_file_by_id(id)
+
+    if file and (file.user_id == user.id or user.role == "admin"):
+        return file
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# Get File Data Content By Id
+############################
+
+
+@router.get("/{id}/data/content")
+async def get_file_data_content_by_id(id: str, user=Depends(get_verified_user)):
+    file = Files.get_file_by_id(id)
+
+    if file and (file.user_id == user.id or user.role == "admin"):
+        return {"content": file.data.get("content", "")}
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# Update File Data Content By Id
+############################
+
+
+class ContentForm(BaseModel):
+    content: str
+
+
+@router.post("/{id}/data/content/update")
+async def update_file_data_content_by_id(
+    id: str, form_data: ContentForm, user=Depends(get_verified_user)
+):
+    file = Files.get_file_by_id(id)
+
+    if file and (file.user_id == user.id or user.role == "admin"):
+        try:
+            process_file(ProcessFileForm(file_id=id, content=form_data.content))
+            file = Files.get_file_by_id(id=id)
+        except Exception as e:
+            log.exception(e)
+            log.error(f"Error processing file: {file.id}")
+
+        return {"content": file.data.get("content", "")}
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# Get File Content By Id
+############################
+
+
+@router.get("/{id}/content")
+async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
+    file = Files.get_file_by_id(id)
+    if file and (file.user_id == user.id or user.role == "admin"):
+        try:
+            file_path = Storage.get_file(file.path)
+            file_path = Path(file_path)
+
+            # Check if the file already exists in the cache
+            if file_path.is_file():
+                print(f"file_path: {file_path}")
+                headers = {
+                    "Content-Disposition": f'attachment; filename="{file.meta.get("name", file.filename)}"'
+                }
+                return FileResponse(file_path, headers=headers)
+            else:
+                raise HTTPException(
+                    status_code=status.HTTP_404_NOT_FOUND,
+                    detail=ERROR_MESSAGES.NOT_FOUND,
+                )
+        except Exception as e:
+            log.exception(e)
+            log.error(f"Error getting file content")
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error getting file content"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+@router.get("/{id}/content/html")
+async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user)):
+    file = Files.get_file_by_id(id)
+    if file and (file.user_id == user.id or user.role == "admin"):
+        try:
+            file_path = Storage.get_file(file.path)
+            file_path = Path(file_path)
+
+            # Check if the file already exists in the cache
+            if file_path.is_file():
+                print(f"file_path: {file_path}")
+                return FileResponse(file_path)
+            else:
+                raise HTTPException(
+                    status_code=status.HTTP_404_NOT_FOUND,
+                    detail=ERROR_MESSAGES.NOT_FOUND,
+                )
+        except Exception as e:
+            log.exception(e)
+            log.error(f"Error getting file content")
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error getting file content"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+@router.get("/{id}/content/{file_name}")
+async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
+    file = Files.get_file_by_id(id)
+
+    if file and (file.user_id == user.id or user.role == "admin"):
+        file_path = file.path
+        if file_path:
+            file_path = Storage.get_file(file_path)
+            file_path = Path(file_path)
+
+            # Check if the file already exists in the cache
+            if file_path.is_file():
+                print(f"file_path: {file_path}")
+                headers = {
+                    "Content-Disposition": f'attachment; filename="{file.meta.get("name", file.filename)}"'
+                }
+                return FileResponse(file_path, headers=headers)
+            else:
+                raise HTTPException(
+                    status_code=status.HTTP_404_NOT_FOUND,
+                    detail=ERROR_MESSAGES.NOT_FOUND,
+                )
+        else:
+            # File path doesn’t exist, return the content as .txt if possible
+            file_content = file.content.get("content", "")
+            file_name = file.filename
+
+            # Create a generator that encodes the file content
+            def generator():
+                yield file_content.encode("utf-8")
+
+            return StreamingResponse(
+                generator(),
+                media_type="text/plain",
+                headers={"Content-Disposition": f"attachment; filename={file_name}"},
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# Delete File By Id
+############################
+
+
+@router.delete("/{id}")
+async def delete_file_by_id(id: str, user=Depends(get_verified_user)):
+    file = Files.get_file_by_id(id)
+    if file and (file.user_id == user.id or user.role == "admin"):
+        result = Files.delete_file_by_id(id)
+        if result:
+            try:
+                Storage.delete_file(file.filename)
+            except Exception as e:
+                log.exception(e)
+                log.error(f"Error deleting files")
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.DEFAULT("Error deleting files"),
+                )
+            return {"message": "File deleted successfully"}
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error deleting file"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
diff --git a/backend/open_webui/apps/webui/routers/folders.py b/backend/open_webui/apps/webui/routers/folders.py
new file mode 100644
index 0000000000000000000000000000000000000000..36075c357bfc6d6892f284823e378eb6e49b989f
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/folders.py
@@ -0,0 +1,251 @@
+import logging
+import os
+import shutil
+import uuid
+from pathlib import Path
+from typing import Optional
+from pydantic import BaseModel
+import mimetypes
+
+
+from open_webui.apps.webui.models.folders import (
+    FolderForm,
+    FolderModel,
+    Folders,
+)
+from open_webui.apps.webui.models.chats import Chats
+
+from open_webui.config import UPLOAD_DIR
+from open_webui.env import SRC_LOG_LEVELS
+from open_webui.constants import ERROR_MESSAGES
+
+
+from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
+from fastapi.responses import FileResponse, StreamingResponse
+
+
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+
+router = APIRouter()
+
+
+############################
+# Get Folders
+############################
+
+
+@router.get("/", response_model=list[FolderModel])
+async def get_folders(user=Depends(get_verified_user)):
+    folders = Folders.get_folders_by_user_id(user.id)
+
+    return [
+        {
+            **folder.model_dump(),
+            "items": {
+                "chats": [
+                    {"title": chat.title, "id": chat.id}
+                    for chat in Chats.get_chats_by_folder_id_and_user_id(
+                        folder.id, user.id
+                    )
+                ]
+            },
+        }
+        for folder in folders
+    ]
+
+
+############################
+# Create Folder
+############################
+
+
+@router.post("/")
+def create_folder(form_data: FolderForm, user=Depends(get_verified_user)):
+    folder = Folders.get_folder_by_parent_id_and_user_id_and_name(
+        None, user.id, form_data.name
+    )
+
+    if folder:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT("Folder already exists"),
+        )
+
+    try:
+        folder = Folders.insert_new_folder(user.id, form_data.name)
+        return folder
+    except Exception as e:
+        log.exception(e)
+        log.error("Error creating folder")
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT("Error creating folder"),
+        )
+
+
+############################
+# Get Folders By Id
+############################
+
+
+@router.get("/{id}", response_model=Optional[FolderModel])
+async def get_folder_by_id(id: str, user=Depends(get_verified_user)):
+    folder = Folders.get_folder_by_id_and_user_id(id, user.id)
+    if folder:
+        return folder
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# Update Folder Name By Id
+############################
+
+
+@router.post("/{id}/update")
+async def update_folder_name_by_id(
+    id: str, form_data: FolderForm, user=Depends(get_verified_user)
+):
+    folder = Folders.get_folder_by_id_and_user_id(id, user.id)
+    if folder:
+        existing_folder = Folders.get_folder_by_parent_id_and_user_id_and_name(
+            folder.parent_id, user.id, form_data.name
+        )
+        if existing_folder:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Folder already exists"),
+            )
+
+        try:
+            folder = Folders.update_folder_name_by_id_and_user_id(
+                id, user.id, form_data.name
+            )
+
+            return folder
+        except Exception as e:
+            log.exception(e)
+            log.error(f"Error updating folder: {id}")
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating folder"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# Update Folder Parent Id By Id
+############################
+
+
+class FolderParentIdForm(BaseModel):
+    parent_id: Optional[str] = None
+
+
+@router.post("/{id}/update/parent")
+async def update_folder_parent_id_by_id(
+    id: str, form_data: FolderParentIdForm, user=Depends(get_verified_user)
+):
+    folder = Folders.get_folder_by_id_and_user_id(id, user.id)
+    if folder:
+        existing_folder = Folders.get_folder_by_parent_id_and_user_id_and_name(
+            form_data.parent_id, user.id, folder.name
+        )
+
+        if existing_folder:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Folder already exists"),
+            )
+
+        try:
+            folder = Folders.update_folder_parent_id_by_id_and_user_id(
+                id, user.id, form_data.parent_id
+            )
+            return folder
+        except Exception as e:
+            log.exception(e)
+            log.error(f"Error updating folder: {id}")
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating folder"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# Update Folder Is Expanded By Id
+############################
+
+
+class FolderIsExpandedForm(BaseModel):
+    is_expanded: bool
+
+
+@router.post("/{id}/update/expanded")
+async def update_folder_is_expanded_by_id(
+    id: str, form_data: FolderIsExpandedForm, user=Depends(get_verified_user)
+):
+    folder = Folders.get_folder_by_id_and_user_id(id, user.id)
+    if folder:
+        try:
+            folder = Folders.update_folder_is_expanded_by_id_and_user_id(
+                id, user.id, form_data.is_expanded
+            )
+            return folder
+        except Exception as e:
+            log.exception(e)
+            log.error(f"Error updating folder: {id}")
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating folder"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# Delete Folder By Id
+############################
+
+
+@router.delete("/{id}")
+async def delete_folder_by_id(id: str, user=Depends(get_verified_user)):
+    folder = Folders.get_folder_by_id_and_user_id(id, user.id)
+    if folder:
+        try:
+            result = Folders.delete_folder_by_id_and_user_id(id, user.id)
+            if result:
+                return result
+            else:
+                raise Exception("Error deleting folder")
+        except Exception as e:
+            log.exception(e)
+            log.error(f"Error deleting folder: {id}")
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error deleting folder"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
diff --git a/backend/open_webui/apps/webui/routers/functions.py b/backend/open_webui/apps/webui/routers/functions.py
new file mode 100644
index 0000000000000000000000000000000000000000..aeaceecfb101e103cad71c693b404c744ecf733f
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/functions.py
@@ -0,0 +1,405 @@
+import os
+from pathlib import Path
+from typing import Optional
+
+from open_webui.apps.webui.models.functions import (
+    FunctionForm,
+    FunctionModel,
+    FunctionResponse,
+    Functions,
+)
+from open_webui.apps.webui.utils import load_function_module_by_id, replace_imports
+from open_webui.config import CACHE_DIR
+from open_webui.constants import ERROR_MESSAGES
+from fastapi import APIRouter, Depends, HTTPException, Request, status
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+router = APIRouter()
+
+############################
+# GetFunctions
+############################
+
+
+@router.get("/", response_model=list[FunctionResponse])
+async def get_functions(user=Depends(get_verified_user)):
+    return Functions.get_functions()
+
+
+############################
+# ExportFunctions
+############################
+
+
+@router.get("/export", response_model=list[FunctionModel])
+async def get_functions(user=Depends(get_admin_user)):
+    return Functions.get_functions()
+
+
+############################
+# CreateNewFunction
+############################
+
+
+@router.post("/create", response_model=Optional[FunctionResponse])
+async def create_new_function(
+    request: Request, form_data: FunctionForm, user=Depends(get_admin_user)
+):
+    if not form_data.id.isidentifier():
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail="Only alphanumeric characters and underscores are allowed in the id",
+        )
+
+    form_data.id = form_data.id.lower()
+
+    function = Functions.get_function_by_id(form_data.id)
+    if function is None:
+        try:
+            form_data.content = replace_imports(form_data.content)
+            function_module, function_type, frontmatter = load_function_module_by_id(
+                form_data.id,
+                content=form_data.content,
+            )
+            form_data.meta.manifest = frontmatter
+
+            FUNCTIONS = request.app.state.FUNCTIONS
+            FUNCTIONS[form_data.id] = function_module
+
+            function = Functions.insert_new_function(user.id, function_type, form_data)
+
+            function_cache_dir = Path(CACHE_DIR) / "functions" / form_data.id
+            function_cache_dir.mkdir(parents=True, exist_ok=True)
+
+            if function:
+                return function
+            else:
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.DEFAULT("Error creating function"),
+                )
+        except Exception as e:
+            print(e)
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT(e),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.ID_TAKEN,
+        )
+
+
+############################
+# GetFunctionById
+############################
+
+
+@router.get("/id/{id}", response_model=Optional[FunctionModel])
+async def get_function_by_id(id: str, user=Depends(get_admin_user)):
+    function = Functions.get_function_by_id(id)
+
+    if function:
+        return function
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# ToggleFunctionById
+############################
+
+
+@router.post("/id/{id}/toggle", response_model=Optional[FunctionModel])
+async def toggle_function_by_id(id: str, user=Depends(get_admin_user)):
+    function = Functions.get_function_by_id(id)
+    if function:
+        function = Functions.update_function_by_id(
+            id, {"is_active": not function.is_active}
+        )
+
+        if function:
+            return function
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating function"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# ToggleGlobalById
+############################
+
+
+@router.post("/id/{id}/toggle/global", response_model=Optional[FunctionModel])
+async def toggle_global_by_id(id: str, user=Depends(get_admin_user)):
+    function = Functions.get_function_by_id(id)
+    if function:
+        function = Functions.update_function_by_id(
+            id, {"is_global": not function.is_global}
+        )
+
+        if function:
+            return function
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating function"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# UpdateFunctionById
+############################
+
+
+@router.post("/id/{id}/update", response_model=Optional[FunctionModel])
+async def update_function_by_id(
+    request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user)
+):
+    try:
+        form_data.content = replace_imports(form_data.content)
+        function_module, function_type, frontmatter = load_function_module_by_id(
+            id, content=form_data.content
+        )
+        form_data.meta.manifest = frontmatter
+
+        FUNCTIONS = request.app.state.FUNCTIONS
+        FUNCTIONS[id] = function_module
+
+        updated = {**form_data.model_dump(exclude={"id"}), "type": function_type}
+        print(updated)
+
+        function = Functions.update_function_by_id(id, updated)
+
+        if function:
+            return function
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating function"),
+            )
+
+    except Exception as e:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+############################
+# DeleteFunctionById
+############################
+
+
+@router.delete("/id/{id}/delete", response_model=bool)
+async def delete_function_by_id(
+    request: Request, id: str, user=Depends(get_admin_user)
+):
+    result = Functions.delete_function_by_id(id)
+
+    if result:
+        FUNCTIONS = request.app.state.FUNCTIONS
+        if id in FUNCTIONS:
+            del FUNCTIONS[id]
+
+    return result
+
+
+############################
+# GetFunctionValves
+############################
+
+
+@router.get("/id/{id}/valves", response_model=Optional[dict])
+async def get_function_valves_by_id(id: str, user=Depends(get_admin_user)):
+    function = Functions.get_function_by_id(id)
+    if function:
+        try:
+            valves = Functions.get_function_valves_by_id(id)
+            return valves
+        except Exception as e:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT(e),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# GetFunctionValvesSpec
+############################
+
+
+@router.get("/id/{id}/valves/spec", response_model=Optional[dict])
+async def get_function_valves_spec_by_id(
+    request: Request, id: str, user=Depends(get_admin_user)
+):
+    function = Functions.get_function_by_id(id)
+    if function:
+        if id in request.app.state.FUNCTIONS:
+            function_module = request.app.state.FUNCTIONS[id]
+        else:
+            function_module, function_type, frontmatter = load_function_module_by_id(id)
+            request.app.state.FUNCTIONS[id] = function_module
+
+        if hasattr(function_module, "Valves"):
+            Valves = function_module.Valves
+            return Valves.schema()
+        return None
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# UpdateFunctionValves
+############################
+
+
+@router.post("/id/{id}/valves/update", response_model=Optional[dict])
+async def update_function_valves_by_id(
+    request: Request, id: str, form_data: dict, user=Depends(get_admin_user)
+):
+    function = Functions.get_function_by_id(id)
+    if function:
+        if id in request.app.state.FUNCTIONS:
+            function_module = request.app.state.FUNCTIONS[id]
+        else:
+            function_module, function_type, frontmatter = load_function_module_by_id(id)
+            request.app.state.FUNCTIONS[id] = function_module
+
+        if hasattr(function_module, "Valves"):
+            Valves = function_module.Valves
+
+            try:
+                form_data = {k: v for k, v in form_data.items() if v is not None}
+                valves = Valves(**form_data)
+                Functions.update_function_valves_by_id(id, valves.model_dump())
+                return valves.model_dump()
+            except Exception as e:
+                print(e)
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.DEFAULT(e),
+                )
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.NOT_FOUND,
+            )
+
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# FunctionUserValves
+############################
+
+
+@router.get("/id/{id}/valves/user", response_model=Optional[dict])
+async def get_function_user_valves_by_id(id: str, user=Depends(get_verified_user)):
+    function = Functions.get_function_by_id(id)
+    if function:
+        try:
+            user_valves = Functions.get_user_valves_by_id_and_user_id(id, user.id)
+            return user_valves
+        except Exception as e:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT(e),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+@router.get("/id/{id}/valves/user/spec", response_model=Optional[dict])
+async def get_function_user_valves_spec_by_id(
+    request: Request, id: str, user=Depends(get_verified_user)
+):
+    function = Functions.get_function_by_id(id)
+    if function:
+        if id in request.app.state.FUNCTIONS:
+            function_module = request.app.state.FUNCTIONS[id]
+        else:
+            function_module, function_type, frontmatter = load_function_module_by_id(id)
+            request.app.state.FUNCTIONS[id] = function_module
+
+        if hasattr(function_module, "UserValves"):
+            UserValves = function_module.UserValves
+            return UserValves.schema()
+        return None
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+@router.post("/id/{id}/valves/user/update", response_model=Optional[dict])
+async def update_function_user_valves_by_id(
+    request: Request, id: str, form_data: dict, user=Depends(get_verified_user)
+):
+    function = Functions.get_function_by_id(id)
+
+    if function:
+        if id in request.app.state.FUNCTIONS:
+            function_module = request.app.state.FUNCTIONS[id]
+        else:
+            function_module, function_type, frontmatter = load_function_module_by_id(id)
+            request.app.state.FUNCTIONS[id] = function_module
+
+        if hasattr(function_module, "UserValves"):
+            UserValves = function_module.UserValves
+
+            try:
+                form_data = {k: v for k, v in form_data.items() if v is not None}
+                user_valves = UserValves(**form_data)
+                Functions.update_user_valves_by_id_and_user_id(
+                    id, user.id, user_valves.model_dump()
+                )
+                return user_valves.model_dump()
+            except Exception as e:
+                print(e)
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.DEFAULT(e),
+                )
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.NOT_FOUND,
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
diff --git a/backend/open_webui/apps/webui/routers/knowledge.py b/backend/open_webui/apps/webui/routers/knowledge.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b5381a745bbe17779191662c74a9d090a5f353b
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/knowledge.py
@@ -0,0 +1,381 @@
+import json
+from typing import Optional, Union
+from pydantic import BaseModel
+from fastapi import APIRouter, Depends, HTTPException, status
+import logging
+
+from open_webui.apps.webui.models.knowledge import (
+    Knowledges,
+    KnowledgeUpdateForm,
+    KnowledgeForm,
+    KnowledgeResponse,
+)
+from open_webui.apps.webui.models.files import Files, FileModel
+from open_webui.apps.retrieval.vector.connector import VECTOR_DB_CLIENT
+from open_webui.apps.retrieval.main import process_file, ProcessFileForm
+
+
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.utils.utils import get_admin_user, get_verified_user
+from open_webui.env import SRC_LOG_LEVELS
+
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+router = APIRouter()
+
+############################
+# GetKnowledgeItems
+############################
+
+
+@router.get(
+    "/", response_model=Optional[Union[list[KnowledgeResponse], KnowledgeResponse]]
+)
+async def get_knowledge_items(
+    id: Optional[str] = None, user=Depends(get_verified_user)
+):
+    if id:
+        knowledge = Knowledges.get_knowledge_by_id(id=id)
+
+        if knowledge:
+            return knowledge
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.NOT_FOUND,
+            )
+    else:
+        knowledge_bases = []
+
+        for knowledge in Knowledges.get_knowledge_items():
+
+            files = []
+            if knowledge.data:
+                files = Files.get_file_metadatas_by_ids(
+                    knowledge.data.get("file_ids", [])
+                )
+
+                # Check if all files exist
+                if len(files) != len(knowledge.data.get("file_ids", [])):
+                    missing_files = list(
+                        set(knowledge.data.get("file_ids", []))
+                        - set([file.id for file in files])
+                    )
+                    if missing_files:
+                        data = knowledge.data or {}
+                        file_ids = data.get("file_ids", [])
+
+                        for missing_file in missing_files:
+                            file_ids.remove(missing_file)
+
+                        data["file_ids"] = file_ids
+                        Knowledges.update_knowledge_by_id(
+                            id=knowledge.id, form_data=KnowledgeUpdateForm(data=data)
+                        )
+
+                        files = Files.get_file_metadatas_by_ids(file_ids)
+
+            knowledge_bases.append(
+                KnowledgeResponse(
+                    **knowledge.model_dump(),
+                    files=files,
+                )
+            )
+        return knowledge_bases
+
+
+############################
+# CreateNewKnowledge
+############################
+
+
+@router.post("/create", response_model=Optional[KnowledgeResponse])
+async def create_new_knowledge(form_data: KnowledgeForm, user=Depends(get_admin_user)):
+    knowledge = Knowledges.insert_new_knowledge(user.id, form_data)
+
+    if knowledge:
+        return knowledge
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.FILE_EXISTS,
+        )
+
+
+############################
+# GetKnowledgeById
+############################
+
+
+class KnowledgeFilesResponse(KnowledgeResponse):
+    files: list[FileModel]
+
+
+@router.get("/{id}", response_model=Optional[KnowledgeFilesResponse])
+async def get_knowledge_by_id(id: str, user=Depends(get_verified_user)):
+    knowledge = Knowledges.get_knowledge_by_id(id=id)
+
+    if knowledge:
+        file_ids = knowledge.data.get("file_ids", []) if knowledge.data else []
+        files = Files.get_files_by_ids(file_ids)
+
+        return KnowledgeFilesResponse(
+            **knowledge.model_dump(),
+            files=files,
+        )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# UpdateKnowledgeById
+############################
+
+
+@router.post("/{id}/update", response_model=Optional[KnowledgeFilesResponse])
+async def update_knowledge_by_id(
+    id: str,
+    form_data: KnowledgeUpdateForm,
+    user=Depends(get_admin_user),
+):
+    knowledge = Knowledges.update_knowledge_by_id(id=id, form_data=form_data)
+
+    if knowledge:
+        file_ids = knowledge.data.get("file_ids", []) if knowledge.data else []
+        files = Files.get_files_by_ids(file_ids)
+
+        return KnowledgeFilesResponse(
+            **knowledge.model_dump(),
+            files=files,
+        )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.ID_TAKEN,
+        )
+
+
+############################
+# AddFileToKnowledge
+############################
+
+
+class KnowledgeFileIdForm(BaseModel):
+    file_id: str
+
+
+@router.post("/{id}/file/add", response_model=Optional[KnowledgeFilesResponse])
+def add_file_to_knowledge_by_id(
+    id: str,
+    form_data: KnowledgeFileIdForm,
+    user=Depends(get_admin_user),
+):
+    knowledge = Knowledges.get_knowledge_by_id(id=id)
+    file = Files.get_file_by_id(form_data.file_id)
+    if not file:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+    if not file.data:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.FILE_NOT_PROCESSED,
+        )
+
+    # Add content to the vector database
+    try:
+        process_file(ProcessFileForm(file_id=form_data.file_id, collection_name=id))
+    except Exception as e:
+        log.debug(e)
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=str(e),
+        )
+
+    if knowledge:
+        data = knowledge.data or {}
+        file_ids = data.get("file_ids", [])
+
+        if form_data.file_id not in file_ids:
+            file_ids.append(form_data.file_id)
+            data["file_ids"] = file_ids
+
+            knowledge = Knowledges.update_knowledge_by_id(
+                id=id, form_data=KnowledgeUpdateForm(data=data)
+            )
+
+            if knowledge:
+                files = Files.get_files_by_ids(file_ids)
+
+                return KnowledgeFilesResponse(
+                    **knowledge.model_dump(),
+                    files=files,
+                )
+            else:
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.DEFAULT("knowledge"),
+                )
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("file_id"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+@router.post("/{id}/file/update", response_model=Optional[KnowledgeFilesResponse])
+def update_file_from_knowledge_by_id(
+    id: str,
+    form_data: KnowledgeFileIdForm,
+    user=Depends(get_admin_user),
+):
+    knowledge = Knowledges.get_knowledge_by_id(id=id)
+    file = Files.get_file_by_id(form_data.file_id)
+    if not file:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+    # Remove content from the vector database
+    VECTOR_DB_CLIENT.delete(
+        collection_name=knowledge.id, filter={"file_id": form_data.file_id}
+    )
+
+    # Add content to the vector database
+    try:
+        process_file(ProcessFileForm(file_id=form_data.file_id, collection_name=id))
+    except Exception as e:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=str(e),
+        )
+
+    if knowledge:
+        data = knowledge.data or {}
+        file_ids = data.get("file_ids", [])
+
+        files = Files.get_files_by_ids(file_ids)
+
+        return KnowledgeFilesResponse(
+            **knowledge.model_dump(),
+            files=files,
+        )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# RemoveFileFromKnowledge
+############################
+
+
+@router.post("/{id}/file/remove", response_model=Optional[KnowledgeFilesResponse])
+def remove_file_from_knowledge_by_id(
+    id: str,
+    form_data: KnowledgeFileIdForm,
+    user=Depends(get_admin_user),
+):
+    knowledge = Knowledges.get_knowledge_by_id(id=id)
+    file = Files.get_file_by_id(form_data.file_id)
+    if not file:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+    # Remove content from the vector database
+    VECTOR_DB_CLIENT.delete(
+        collection_name=knowledge.id, filter={"file_id": form_data.file_id}
+    )
+
+    result = VECTOR_DB_CLIENT.query(
+        collection_name=knowledge.id,
+        filter={"file_id": form_data.file_id},
+    )
+
+    Files.delete_file_by_id(form_data.file_id)
+
+    if knowledge:
+        data = knowledge.data or {}
+        file_ids = data.get("file_ids", [])
+
+        if form_data.file_id in file_ids:
+            file_ids.remove(form_data.file_id)
+            data["file_ids"] = file_ids
+
+            knowledge = Knowledges.update_knowledge_by_id(
+                id=id, form_data=KnowledgeUpdateForm(data=data)
+            )
+
+            if knowledge:
+                files = Files.get_files_by_ids(file_ids)
+
+                return KnowledgeFilesResponse(
+                    **knowledge.model_dump(),
+                    files=files,
+                )
+            else:
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.DEFAULT("knowledge"),
+                )
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("file_id"),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# ResetKnowledgeById
+############################
+
+
+@router.post("/{id}/reset", response_model=Optional[KnowledgeResponse])
+async def reset_knowledge_by_id(id: str, user=Depends(get_admin_user)):
+    try:
+        VECTOR_DB_CLIENT.delete_collection(collection_name=id)
+    except Exception as e:
+        log.debug(e)
+        pass
+
+    knowledge = Knowledges.update_knowledge_by_id(
+        id=id, form_data=KnowledgeUpdateForm(data={"file_ids": []})
+    )
+    return knowledge
+
+
+############################
+# DeleteKnowledgeById
+############################
+
+
+@router.delete("/{id}/delete", response_model=bool)
+async def delete_knowledge_by_id(id: str, user=Depends(get_admin_user)):
+    try:
+        VECTOR_DB_CLIENT.delete_collection(collection_name=id)
+    except Exception as e:
+        log.debug(e)
+        pass
+    result = Knowledges.delete_knowledge_by_id(id=id)
+    return result
diff --git a/backend/open_webui/apps/webui/routers/memories.py b/backend/open_webui/apps/webui/routers/memories.py
new file mode 100644
index 0000000000000000000000000000000000000000..ccf84a9d4cb40b101ca9aa06574a79893817013a
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/memories.py
@@ -0,0 +1,190 @@
+from fastapi import APIRouter, Depends, HTTPException, Request
+from pydantic import BaseModel
+import logging
+from typing import Optional
+
+from open_webui.apps.webui.models.memories import Memories, MemoryModel
+from open_webui.apps.retrieval.vector.connector import VECTOR_DB_CLIENT
+from open_webui.utils.utils import get_verified_user
+from open_webui.env import SRC_LOG_LEVELS
+
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+router = APIRouter()
+
+
+@router.get("/ef")
+async def get_embeddings(request: Request):
+    return {"result": request.app.state.EMBEDDING_FUNCTION("hello world")}
+
+
+############################
+# GetMemories
+############################
+
+
+@router.get("/", response_model=list[MemoryModel])
+async def get_memories(user=Depends(get_verified_user)):
+    return Memories.get_memories_by_user_id(user.id)
+
+
+############################
+# AddMemory
+############################
+
+
+class AddMemoryForm(BaseModel):
+    content: str
+
+
+class MemoryUpdateModel(BaseModel):
+    content: Optional[str] = None
+
+
+@router.post("/add", response_model=Optional[MemoryModel])
+async def add_memory(
+    request: Request,
+    form_data: AddMemoryForm,
+    user=Depends(get_verified_user),
+):
+    memory = Memories.insert_new_memory(user.id, form_data.content)
+
+    VECTOR_DB_CLIENT.upsert(
+        collection_name=f"user-memory-{user.id}",
+        items=[
+            {
+                "id": memory.id,
+                "text": memory.content,
+                "vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
+                "metadata": {"created_at": memory.created_at},
+            }
+        ],
+    )
+
+    return memory
+
+
+############################
+# QueryMemory
+############################
+
+
+class QueryMemoryForm(BaseModel):
+    content: str
+    k: Optional[int] = 1
+
+
+@router.post("/query")
+async def query_memory(
+    request: Request, form_data: QueryMemoryForm, user=Depends(get_verified_user)
+):
+    results = VECTOR_DB_CLIENT.search(
+        collection_name=f"user-memory-{user.id}",
+        vectors=[request.app.state.EMBEDDING_FUNCTION(form_data.content)],
+        limit=form_data.k,
+    )
+
+    return results
+
+
+############################
+# ResetMemoryFromVectorDB
+############################
+@router.post("/reset", response_model=bool)
+async def reset_memory_from_vector_db(
+    request: Request, user=Depends(get_verified_user)
+):
+    VECTOR_DB_CLIENT.delete_collection(f"user-memory-{user.id}")
+
+    memories = Memories.get_memories_by_user_id(user.id)
+    VECTOR_DB_CLIENT.upsert(
+        collection_name=f"user-memory-{user.id}",
+        items=[
+            {
+                "id": memory.id,
+                "text": memory.content,
+                "vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
+                "metadata": {
+                    "created_at": memory.created_at,
+                    "updated_at": memory.updated_at,
+                },
+            }
+            for memory in memories
+        ],
+    )
+
+    return True
+
+
+############################
+# DeleteMemoriesByUserId
+############################
+
+
+@router.delete("/delete/user", response_model=bool)
+async def delete_memory_by_user_id(user=Depends(get_verified_user)):
+    result = Memories.delete_memories_by_user_id(user.id)
+
+    if result:
+        try:
+            VECTOR_DB_CLIENT.delete_collection(f"user-memory-{user.id}")
+        except Exception as e:
+            log.error(e)
+        return True
+
+    return False
+
+
+############################
+# UpdateMemoryById
+############################
+
+
+@router.post("/{memory_id}/update", response_model=Optional[MemoryModel])
+async def update_memory_by_id(
+    memory_id: str,
+    request: Request,
+    form_data: MemoryUpdateModel,
+    user=Depends(get_verified_user),
+):
+    memory = Memories.update_memory_by_id(memory_id, form_data.content)
+    if memory is None:
+        raise HTTPException(status_code=404, detail="Memory not found")
+
+    if form_data.content is not None:
+        VECTOR_DB_CLIENT.upsert(
+            collection_name=f"user-memory-{user.id}",
+            items=[
+                {
+                    "id": memory.id,
+                    "text": memory.content,
+                    "vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
+                    "metadata": {
+                        "created_at": memory.created_at,
+                        "updated_at": memory.updated_at,
+                    },
+                }
+            ],
+        )
+
+    return memory
+
+
+############################
+# DeleteMemoryById
+############################
+
+
+@router.delete("/{memory_id}", response_model=bool)
+async def delete_memory_by_id(memory_id: str, user=Depends(get_verified_user)):
+    result = Memories.delete_memory_by_id_and_user_id(memory_id, user.id)
+
+    if result:
+        VECTOR_DB_CLIENT.delete(
+            collection_name=f"user-memory-{user.id}", ids=[memory_id]
+        )
+        return True
+
+    return False
diff --git a/backend/open_webui/apps/webui/routers/models.py b/backend/open_webui/apps/webui/routers/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5cb2395ecfc5f9f8f9a54ffa97f142cc27ebd3f
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/models.py
@@ -0,0 +1,104 @@
+from typing import Optional
+
+from open_webui.apps.webui.models.models import (
+    ModelForm,
+    ModelModel,
+    ModelResponse,
+    Models,
+)
+from open_webui.constants import ERROR_MESSAGES
+from fastapi import APIRouter, Depends, HTTPException, Request, status
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+router = APIRouter()
+
+###########################
+# getModels
+###########################
+
+
+@router.get("/", response_model=list[ModelResponse])
+async def get_models(id: Optional[str] = None, user=Depends(get_verified_user)):
+    if id:
+        model = Models.get_model_by_id(id)
+        if model:
+            return [model]
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.NOT_FOUND,
+            )
+    else:
+        return Models.get_all_models()
+
+
+############################
+# AddNewModel
+############################
+
+
+@router.post("/add", response_model=Optional[ModelModel])
+async def add_new_model(
+    request: Request,
+    form_data: ModelForm,
+    user=Depends(get_admin_user),
+):
+    if form_data.id in request.app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.MODEL_ID_TAKEN,
+        )
+    else:
+        model = Models.insert_new_model(form_data, user.id)
+
+        if model:
+            return model
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.DEFAULT(),
+            )
+
+
+############################
+# UpdateModelById
+############################
+
+
+@router.post("/update", response_model=Optional[ModelModel])
+async def update_model_by_id(
+    request: Request,
+    id: str,
+    form_data: ModelForm,
+    user=Depends(get_admin_user),
+):
+    model = Models.get_model_by_id(id)
+    if model:
+        model = Models.update_model_by_id(id, form_data)
+        return model
+    else:
+        if form_data.id in request.app.state.MODELS:
+            model = Models.insert_new_model(form_data, user.id)
+            if model:
+                return model
+            else:
+                raise HTTPException(
+                    status_code=status.HTTP_401_UNAUTHORIZED,
+                    detail=ERROR_MESSAGES.DEFAULT(),
+                )
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.DEFAULT(),
+            )
+
+
+############################
+# DeleteModelById
+############################
+
+
+@router.delete("/delete", response_model=bool)
+async def delete_model_by_id(id: str, user=Depends(get_admin_user)):
+    result = Models.delete_model_by_id(id)
+    return result
diff --git a/backend/open_webui/apps/webui/routers/prompts.py b/backend/open_webui/apps/webui/routers/prompts.py
new file mode 100644
index 0000000000000000000000000000000000000000..593c643b979a82cd6800ae561ab89fa9612e551f
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/prompts.py
@@ -0,0 +1,90 @@
+from typing import Optional
+
+from open_webui.apps.webui.models.prompts import PromptForm, PromptModel, Prompts
+from open_webui.constants import ERROR_MESSAGES
+from fastapi import APIRouter, Depends, HTTPException, status
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+router = APIRouter()
+
+############################
+# GetPrompts
+############################
+
+
+@router.get("/", response_model=list[PromptModel])
+async def get_prompts(user=Depends(get_verified_user)):
+    return Prompts.get_prompts()
+
+
+############################
+# CreateNewPrompt
+############################
+
+
+@router.post("/create", response_model=Optional[PromptModel])
+async def create_new_prompt(form_data: PromptForm, user=Depends(get_admin_user)):
+    prompt = Prompts.get_prompt_by_command(form_data.command)
+    if prompt is None:
+        prompt = Prompts.insert_new_prompt(user.id, form_data)
+
+        if prompt:
+            return prompt
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(),
+        )
+    raise HTTPException(
+        status_code=status.HTTP_400_BAD_REQUEST,
+        detail=ERROR_MESSAGES.COMMAND_TAKEN,
+    )
+
+
+############################
+# GetPromptByCommand
+############################
+
+
+@router.get("/command/{command}", response_model=Optional[PromptModel])
+async def get_prompt_by_command(command: str, user=Depends(get_verified_user)):
+    prompt = Prompts.get_prompt_by_command(f"/{command}")
+
+    if prompt:
+        return prompt
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# UpdatePromptByCommand
+############################
+
+
+@router.post("/command/{command}/update", response_model=Optional[PromptModel])
+async def update_prompt_by_command(
+    command: str,
+    form_data: PromptForm,
+    user=Depends(get_admin_user),
+):
+    prompt = Prompts.update_prompt_by_command(f"/{command}", form_data)
+    if prompt:
+        return prompt
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+
+
+############################
+# DeletePromptByCommand
+############################
+
+
+@router.delete("/command/{command}/delete", response_model=bool)
+async def delete_prompt_by_command(command: str, user=Depends(get_admin_user)):
+    result = Prompts.delete_prompt_by_command(f"/{command}")
+    return result
diff --git a/backend/open_webui/apps/webui/routers/tools.py b/backend/open_webui/apps/webui/routers/tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1ad89deaef92aaa6ad8050ac5e082f7db83a4f3
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/tools.py
@@ -0,0 +1,358 @@
+import os
+from pathlib import Path
+from typing import Optional
+
+from open_webui.apps.webui.models.tools import ToolForm, ToolModel, ToolResponse, Tools
+from open_webui.apps.webui.utils import load_toolkit_module_by_id, replace_imports
+from open_webui.config import CACHE_DIR, DATA_DIR
+from open_webui.constants import ERROR_MESSAGES
+from fastapi import APIRouter, Depends, HTTPException, Request, status
+from open_webui.utils.tools import get_tools_specs
+from open_webui.utils.utils import get_admin_user, get_verified_user
+
+
+router = APIRouter()
+
+############################
+# GetToolkits
+############################
+
+
+@router.get("/", response_model=list[ToolResponse])
+async def get_toolkits(user=Depends(get_verified_user)):
+    toolkits = [toolkit for toolkit in Tools.get_tools()]
+    return toolkits
+
+
+############################
+# ExportToolKits
+############################
+
+
+@router.get("/export", response_model=list[ToolModel])
+async def get_toolkits(user=Depends(get_admin_user)):
+    toolkits = [toolkit for toolkit in Tools.get_tools()]
+    return toolkits
+
+
+############################
+# CreateNewToolKit
+############################
+
+
+@router.post("/create", response_model=Optional[ToolResponse])
+async def create_new_toolkit(
+    request: Request,
+    form_data: ToolForm,
+    user=Depends(get_admin_user),
+):
+    if not form_data.id.isidentifier():
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail="Only alphanumeric characters and underscores are allowed in the id",
+        )
+
+    form_data.id = form_data.id.lower()
+
+    toolkit = Tools.get_tool_by_id(form_data.id)
+    if toolkit is None:
+        try:
+            form_data.content = replace_imports(form_data.content)
+            toolkit_module, frontmatter = load_toolkit_module_by_id(
+                form_data.id, content=form_data.content
+            )
+            form_data.meta.manifest = frontmatter
+
+            TOOLS = request.app.state.TOOLS
+            TOOLS[form_data.id] = toolkit_module
+
+            specs = get_tools_specs(TOOLS[form_data.id])
+            toolkit = Tools.insert_new_tool(user.id, form_data, specs)
+
+            tool_cache_dir = Path(CACHE_DIR) / "tools" / form_data.id
+            tool_cache_dir.mkdir(parents=True, exist_ok=True)
+
+            if toolkit:
+                return toolkit
+            else:
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.DEFAULT("Error creating toolkit"),
+                )
+        except Exception as e:
+            print(e)
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT(str(e)),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.ID_TAKEN,
+        )
+
+
+############################
+# GetToolkitById
+############################
+
+
+@router.get("/id/{id}", response_model=Optional[ToolModel])
+async def get_toolkit_by_id(id: str, user=Depends(get_admin_user)):
+    toolkit = Tools.get_tool_by_id(id)
+
+    if toolkit:
+        return toolkit
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# UpdateToolkitById
+############################
+
+
+@router.post("/id/{id}/update", response_model=Optional[ToolModel])
+async def update_toolkit_by_id(
+    request: Request,
+    id: str,
+    form_data: ToolForm,
+    user=Depends(get_admin_user),
+):
+    try:
+        form_data.content = replace_imports(form_data.content)
+        toolkit_module, frontmatter = load_toolkit_module_by_id(
+            id, content=form_data.content
+        )
+        form_data.meta.manifest = frontmatter
+
+        TOOLS = request.app.state.TOOLS
+        TOOLS[id] = toolkit_module
+
+        specs = get_tools_specs(TOOLS[id])
+
+        updated = {
+            **form_data.model_dump(exclude={"id"}),
+            "specs": specs,
+        }
+
+        print(updated)
+        toolkit = Tools.update_tool_by_id(id, updated)
+
+        if toolkit:
+            return toolkit
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating toolkit"),
+            )
+
+    except Exception as e:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(str(e)),
+        )
+
+
+############################
+# DeleteToolkitById
+############################
+
+
+@router.delete("/id/{id}/delete", response_model=bool)
+async def delete_toolkit_by_id(request: Request, id: str, user=Depends(get_admin_user)):
+    result = Tools.delete_tool_by_id(id)
+
+    if result:
+        TOOLS = request.app.state.TOOLS
+        if id in TOOLS:
+            del TOOLS[id]
+
+    return result
+
+
+############################
+# GetToolValves
+############################
+
+
+@router.get("/id/{id}/valves", response_model=Optional[dict])
+async def get_toolkit_valves_by_id(id: str, user=Depends(get_admin_user)):
+    toolkit = Tools.get_tool_by_id(id)
+    if toolkit:
+        try:
+            valves = Tools.get_tool_valves_by_id(id)
+            return valves
+        except Exception as e:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT(str(e)),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# GetToolValvesSpec
+############################
+
+
+@router.get("/id/{id}/valves/spec", response_model=Optional[dict])
+async def get_toolkit_valves_spec_by_id(
+    request: Request, id: str, user=Depends(get_admin_user)
+):
+    toolkit = Tools.get_tool_by_id(id)
+    if toolkit:
+        if id in request.app.state.TOOLS:
+            toolkit_module = request.app.state.TOOLS[id]
+        else:
+            toolkit_module, _ = load_toolkit_module_by_id(id)
+            request.app.state.TOOLS[id] = toolkit_module
+
+        if hasattr(toolkit_module, "Valves"):
+            Valves = toolkit_module.Valves
+            return Valves.schema()
+        return None
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# UpdateToolValves
+############################
+
+
+@router.post("/id/{id}/valves/update", response_model=Optional[dict])
+async def update_toolkit_valves_by_id(
+    request: Request, id: str, form_data: dict, user=Depends(get_admin_user)
+):
+    toolkit = Tools.get_tool_by_id(id)
+    if toolkit:
+        if id in request.app.state.TOOLS:
+            toolkit_module = request.app.state.TOOLS[id]
+        else:
+            toolkit_module, _ = load_toolkit_module_by_id(id)
+            request.app.state.TOOLS[id] = toolkit_module
+
+        if hasattr(toolkit_module, "Valves"):
+            Valves = toolkit_module.Valves
+
+            try:
+                form_data = {k: v for k, v in form_data.items() if v is not None}
+                valves = Valves(**form_data)
+                Tools.update_tool_valves_by_id(id, valves.model_dump())
+                return valves.model_dump()
+            except Exception as e:
+                print(e)
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.DEFAULT(str(e)),
+                )
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.NOT_FOUND,
+            )
+
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# ToolUserValves
+############################
+
+
+@router.get("/id/{id}/valves/user", response_model=Optional[dict])
+async def get_toolkit_user_valves_by_id(id: str, user=Depends(get_verified_user)):
+    toolkit = Tools.get_tool_by_id(id)
+    if toolkit:
+        try:
+            user_valves = Tools.get_user_valves_by_id_and_user_id(id, user.id)
+            return user_valves
+        except Exception as e:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT(str(e)),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+@router.get("/id/{id}/valves/user/spec", response_model=Optional[dict])
+async def get_toolkit_user_valves_spec_by_id(
+    request: Request, id: str, user=Depends(get_verified_user)
+):
+    toolkit = Tools.get_tool_by_id(id)
+    if toolkit:
+        if id in request.app.state.TOOLS:
+            toolkit_module = request.app.state.TOOLS[id]
+        else:
+            toolkit_module, _ = load_toolkit_module_by_id(id)
+            request.app.state.TOOLS[id] = toolkit_module
+
+        if hasattr(toolkit_module, "UserValves"):
+            UserValves = toolkit_module.UserValves
+            return UserValves.schema()
+        return None
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+@router.post("/id/{id}/valves/user/update", response_model=Optional[dict])
+async def update_toolkit_user_valves_by_id(
+    request: Request, id: str, form_data: dict, user=Depends(get_verified_user)
+):
+    toolkit = Tools.get_tool_by_id(id)
+
+    if toolkit:
+        if id in request.app.state.TOOLS:
+            toolkit_module = request.app.state.TOOLS[id]
+        else:
+            toolkit_module, _ = load_toolkit_module_by_id(id)
+            request.app.state.TOOLS[id] = toolkit_module
+
+        if hasattr(toolkit_module, "UserValves"):
+            UserValves = toolkit_module.UserValves
+
+            try:
+                form_data = {k: v for k, v in form_data.items() if v is not None}
+                user_valves = UserValves(**form_data)
+                Tools.update_user_valves_by_id_and_user_id(
+                    id, user.id, user_valves.model_dump()
+                )
+                return user_valves.model_dump()
+            except Exception as e:
+                print(e)
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.DEFAULT(str(e)),
+                )
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.NOT_FOUND,
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
diff --git a/backend/open_webui/apps/webui/routers/users.py b/backend/open_webui/apps/webui/routers/users.py
new file mode 100644
index 0000000000000000000000000000000000000000..abc540efa8f59596a5aabcde193e37b1996e5f25
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/users.py
@@ -0,0 +1,258 @@
+import logging
+from typing import Optional
+
+from open_webui.apps.webui.models.auths import Auths
+from open_webui.apps.webui.models.chats import Chats
+from open_webui.apps.webui.models.users import (
+    UserModel,
+    UserRoleUpdateForm,
+    Users,
+    UserSettings,
+    UserUpdateForm,
+)
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.env import SRC_LOG_LEVELS
+from fastapi import APIRouter, Depends, HTTPException, Request, status
+from pydantic import BaseModel
+from open_webui.utils.utils import get_admin_user, get_password_hash, get_verified_user
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+router = APIRouter()
+
+############################
+# GetUsers
+############################
+
+
+@router.get("/", response_model=list[UserModel])
+async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_admin_user)):
+    return Users.get_users(skip, limit)
+
+
+############################
+# User Permissions
+############################
+
+
+@router.get("/permissions/user")
+async def get_user_permissions(request: Request, user=Depends(get_admin_user)):
+    return request.app.state.config.USER_PERMISSIONS
+
+
+@router.post("/permissions/user")
+async def update_user_permissions(
+    request: Request, form_data: dict, user=Depends(get_admin_user)
+):
+    request.app.state.config.USER_PERMISSIONS = form_data
+    return request.app.state.config.USER_PERMISSIONS
+
+
+############################
+# UpdateUserRole
+############################
+
+
+@router.post("/update/role", response_model=Optional[UserModel])
+async def update_user_role(form_data: UserRoleUpdateForm, user=Depends(get_admin_user)):
+    if user.id != form_data.id and form_data.id != Users.get_first_user().id:
+        return Users.update_user_role_by_id(form_data.id, form_data.role)
+
+    raise HTTPException(
+        status_code=status.HTTP_403_FORBIDDEN,
+        detail=ERROR_MESSAGES.ACTION_PROHIBITED,
+    )
+
+
+############################
+# GetUserSettingsBySessionUser
+############################
+
+
+@router.get("/user/settings", response_model=Optional[UserSettings])
+async def get_user_settings_by_session_user(user=Depends(get_verified_user)):
+    user = Users.get_user_by_id(user.id)
+    if user:
+        return user.settings
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.USER_NOT_FOUND,
+        )
+
+
+############################
+# UpdateUserSettingsBySessionUser
+############################
+
+
+@router.post("/user/settings/update", response_model=UserSettings)
+async def update_user_settings_by_session_user(
+    form_data: UserSettings, user=Depends(get_verified_user)
+):
+    user = Users.update_user_by_id(user.id, {"settings": form_data.model_dump()})
+    if user:
+        return user.settings
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.USER_NOT_FOUND,
+        )
+
+
+############################
+# GetUserInfoBySessionUser
+############################
+
+
+@router.get("/user/info", response_model=Optional[dict])
+async def get_user_info_by_session_user(user=Depends(get_verified_user)):
+    user = Users.get_user_by_id(user.id)
+    if user:
+        return user.info
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.USER_NOT_FOUND,
+        )
+
+
+############################
+# UpdateUserInfoBySessionUser
+############################
+
+
+@router.post("/user/info/update", response_model=Optional[dict])
+async def update_user_info_by_session_user(
+    form_data: dict, user=Depends(get_verified_user)
+):
+    user = Users.get_user_by_id(user.id)
+    if user:
+        if user.info is None:
+            user.info = {}
+
+        user = Users.update_user_by_id(user.id, {"info": {**user.info, **form_data}})
+        if user:
+            return user.info
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.USER_NOT_FOUND,
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.USER_NOT_FOUND,
+        )
+
+
+############################
+# GetUserById
+############################
+
+
+class UserResponse(BaseModel):
+    name: str
+    profile_image_url: str
+
+
+@router.get("/{user_id}", response_model=UserResponse)
+async def get_user_by_id(user_id: str, user=Depends(get_verified_user)):
+    # Check if user_id is a shared chat
+    # If it is, get the user_id from the chat
+    if user_id.startswith("shared-"):
+        chat_id = user_id.replace("shared-", "")
+        chat = Chats.get_chat_by_id(chat_id)
+        if chat:
+            user_id = chat.user_id
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.USER_NOT_FOUND,
+            )
+
+    user = Users.get_user_by_id(user_id)
+
+    if user:
+        return UserResponse(name=user.name, profile_image_url=user.profile_image_url)
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.USER_NOT_FOUND,
+        )
+
+
+############################
+# UpdateUserById
+############################
+
+
+@router.post("/{user_id}/update", response_model=Optional[UserModel])
+async def update_user_by_id(
+    user_id: str,
+    form_data: UserUpdateForm,
+    session_user=Depends(get_admin_user),
+):
+    user = Users.get_user_by_id(user_id)
+
+    if user:
+        if form_data.email.lower() != user.email:
+            email_user = Users.get_user_by_email(form_data.email.lower())
+            if email_user:
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.EMAIL_TAKEN,
+                )
+
+        if form_data.password:
+            hashed = get_password_hash(form_data.password)
+            log.debug(f"hashed: {hashed}")
+            Auths.update_user_password_by_id(user_id, hashed)
+
+        Auths.update_email_by_id(user_id, form_data.email.lower())
+        updated_user = Users.update_user_by_id(
+            user_id,
+            {
+                "name": form_data.name,
+                "email": form_data.email.lower(),
+                "profile_image_url": form_data.profile_image_url,
+            },
+        )
+
+        if updated_user:
+            return updated_user
+
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(),
+        )
+
+    raise HTTPException(
+        status_code=status.HTTP_400_BAD_REQUEST,
+        detail=ERROR_MESSAGES.USER_NOT_FOUND,
+    )
+
+
+############################
+# DeleteUserById
+############################
+
+
+@router.delete("/{user_id}", response_model=bool)
+async def delete_user_by_id(user_id: str, user=Depends(get_admin_user)):
+    if user.id != user_id:
+        result = Auths.delete_auth_by_id(user_id)
+
+        if result:
+            return True
+
+        raise HTTPException(
+            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+            detail=ERROR_MESSAGES.DELETE_USER_ERROR,
+        )
+
+    raise HTTPException(
+        status_code=status.HTTP_403_FORBIDDEN,
+        detail=ERROR_MESSAGES.ACTION_PROHIBITED,
+    )
diff --git a/backend/open_webui/apps/webui/routers/utils.py b/backend/open_webui/apps/webui/routers/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ab0f6b156c66ace9820cad38622f918e2f13117
--- /dev/null
+++ b/backend/open_webui/apps/webui/routers/utils.py
@@ -0,0 +1,99 @@
+import black
+import markdown
+
+from open_webui.apps.webui.models.chats import ChatTitleMessagesForm
+from open_webui.config import DATA_DIR, ENABLE_ADMIN_EXPORT
+from open_webui.constants import ERROR_MESSAGES
+from fastapi import APIRouter, Depends, HTTPException, Response, status
+from pydantic import BaseModel
+from starlette.responses import FileResponse
+from open_webui.utils.misc import get_gravatar_url
+from open_webui.utils.pdf_generator import PDFGenerator
+from open_webui.utils.utils import get_admin_user
+
+router = APIRouter()
+
+
+@router.get("/gravatar")
+async def get_gravatar(
+    email: str,
+):
+    return get_gravatar_url(email)
+
+
+class CodeFormatRequest(BaseModel):
+    code: str
+
+
+@router.post("/code/format")
+async def format_code(request: CodeFormatRequest):
+    try:
+        formatted_code = black.format_str(request.code, mode=black.Mode())
+        return {"code": formatted_code}
+    except black.NothingChanged:
+        return {"code": request.code}
+    except Exception as e:
+        raise HTTPException(status_code=400, detail=str(e))
+
+
+class MarkdownForm(BaseModel):
+    md: str
+
+
+@router.post("/markdown")
+async def get_html_from_markdown(
+    form_data: MarkdownForm,
+):
+    return {"html": markdown.markdown(form_data.md)}
+
+
+class ChatForm(BaseModel):
+    title: str
+    messages: list[dict]
+
+
+@router.post("/pdf")
+async def download_chat_as_pdf(
+    form_data: ChatTitleMessagesForm,
+):
+    try:
+        pdf_bytes = PDFGenerator(form_data).generate_chat_pdf()
+
+        return Response(
+            content=pdf_bytes,
+            media_type="application/pdf",
+            headers={"Content-Disposition": "attachment;filename=chat.pdf"},
+        )
+    except Exception as e:
+        print(e)
+        raise HTTPException(status_code=400, detail=str(e))
+
+
+@router.get("/db/download")
+async def download_db(user=Depends(get_admin_user)):
+    if not ENABLE_ADMIN_EXPORT:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+    from open_webui.apps.webui.internal.db import engine
+
+    if engine.name != "sqlite":
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DB_NOT_SQLITE,
+        )
+    return FileResponse(
+        engine.url.database,
+        media_type="application/octet-stream",
+        filename="webui.db",
+    )
+
+
+@router.get("/litellm/config")
+async def download_litellm_config_yaml(user=Depends(get_admin_user)):
+    return FileResponse(
+        f"{DATA_DIR}/litellm/config.yaml",
+        media_type="application/octet-stream",
+        filename="config.yaml",
+    )
diff --git a/backend/open_webui/apps/webui/utils.py b/backend/open_webui/apps/webui/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..51d37965686d1780414c361a48497da435939983
--- /dev/null
+++ b/backend/open_webui/apps/webui/utils.py
@@ -0,0 +1,170 @@
+import os
+import re
+import subprocess
+import sys
+from importlib import util
+import types
+import tempfile
+
+from open_webui.apps.webui.models.functions import Functions
+from open_webui.apps.webui.models.tools import Tools
+
+
+def extract_frontmatter(content):
+    """
+    Extract frontmatter as a dictionary from the provided content string.
+    """
+    frontmatter = {}
+    frontmatter_started = False
+    frontmatter_ended = False
+    frontmatter_pattern = re.compile(r"^\s*([a-z_]+):\s*(.*)\s*$", re.IGNORECASE)
+
+    try:
+        lines = content.splitlines()
+        if len(lines) < 1 or lines[0].strip() != '"""':
+            # The content doesn't start with triple quotes
+            return {}
+
+        frontmatter_started = True
+
+        for line in lines[1:]:
+            if '"""' in line:
+                if frontmatter_started:
+                    frontmatter_ended = True
+                    break
+
+            if frontmatter_started and not frontmatter_ended:
+                match = frontmatter_pattern.match(line)
+                if match:
+                    key, value = match.groups()
+                    frontmatter[key.strip()] = value.strip()
+
+    except Exception as e:
+        print(f"An error occurred: {e}")
+        return {}
+
+    return frontmatter
+
+
+def replace_imports(content):
+    """
+    Replace the import paths in the content.
+    """
+    replacements = {
+        "from utils": "from open_webui.utils",
+        "from apps": "from open_webui.apps",
+        "from main": "from open_webui.main",
+        "from config": "from open_webui.config",
+    }
+
+    for old, new in replacements.items():
+        content = content.replace(old, new)
+
+    return content
+
+
+def load_toolkit_module_by_id(toolkit_id, content=None):
+
+    if content is None:
+        tool = Tools.get_tool_by_id(toolkit_id)
+        if not tool:
+            raise Exception(f"Toolkit not found: {toolkit_id}")
+
+        content = tool.content
+
+        content = replace_imports(content)
+        Tools.update_tool_by_id(toolkit_id, {"content": content})
+    else:
+        frontmatter = extract_frontmatter(content)
+        # Install required packages found within the frontmatter
+        install_frontmatter_requirements(frontmatter.get("requirements", ""))
+
+    module_name = f"tool_{toolkit_id}"
+    module = types.ModuleType(module_name)
+    sys.modules[module_name] = module
+
+    # Create a temporary file and use it to define `__file__` so
+    # that it works as expected from the module's perspective.
+    temp_file = tempfile.NamedTemporaryFile(delete=False)
+    temp_file.close()
+    try:
+        with open(temp_file.name, "w", encoding="utf-8") as f:
+            f.write(content)
+        module.__dict__["__file__"] = temp_file.name
+
+        # Executing the modified content in the created module's namespace
+        exec(content, module.__dict__)
+        frontmatter = extract_frontmatter(content)
+        print(f"Loaded module: {module.__name__}")
+
+        # Create and return the object if the class 'Tools' is found in the module
+        if hasattr(module, "Tools"):
+            return module.Tools(), frontmatter
+        else:
+            raise Exception("No Tools class found in the module")
+    except Exception as e:
+        print(f"Error loading module: {toolkit_id}: {e}")
+        del sys.modules[module_name]  # Clean up
+        raise e
+    finally:
+        os.unlink(temp_file.name)
+
+
+def load_function_module_by_id(function_id, content=None):
+    if content is None:
+        function = Functions.get_function_by_id(function_id)
+        if not function:
+            raise Exception(f"Function not found: {function_id}")
+        content = function.content
+
+        content = replace_imports(content)
+        Functions.update_function_by_id(function_id, {"content": content})
+    else:
+        frontmatter = extract_frontmatter(content)
+        install_frontmatter_requirements(frontmatter.get("requirements", ""))
+
+    module_name = f"function_{function_id}"
+    module = types.ModuleType(module_name)
+    sys.modules[module_name] = module
+
+    # Create a temporary file and use it to define `__file__` so
+    # that it works as expected from the module's perspective.
+    temp_file = tempfile.NamedTemporaryFile(delete=False)
+    temp_file.close()
+    try:
+        with open(temp_file.name, "w", encoding="utf-8") as f:
+            f.write(content)
+        module.__dict__["__file__"] = temp_file.name
+
+        # Execute the modified content in the created module's namespace
+        exec(content, module.__dict__)
+        frontmatter = extract_frontmatter(content)
+        print(f"Loaded module: {module.__name__}")
+
+        # Create appropriate object based on available class type in the module
+        if hasattr(module, "Pipe"):
+            return module.Pipe(), "pipe", frontmatter
+        elif hasattr(module, "Filter"):
+            return module.Filter(), "filter", frontmatter
+        elif hasattr(module, "Action"):
+            return module.Action(), "action", frontmatter
+        else:
+            raise Exception("No Function class found in the module")
+    except Exception as e:
+        print(f"Error loading module: {function_id}: {e}")
+        del sys.modules[module_name]  # Cleanup by removing the module in case of error
+
+        Functions.update_function_by_id(function_id, {"is_active": False})
+        raise e
+    finally:
+        os.unlink(temp_file.name)
+
+
+def install_frontmatter_requirements(requirements):
+    if requirements:
+        req_list = [req.strip() for req in requirements.split(",")]
+        for req in req_list:
+            print(f"Installing requirement: {req}")
+            subprocess.check_call([sys.executable, "-m", "pip", "install", req])
+    else:
+        print("No requirements found in frontmatter.")
diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d1bd72d898103d4559b570354aef858ccf44642
--- /dev/null
+++ b/backend/open_webui/config.py
@@ -0,0 +1,1550 @@
+import json
+import logging
+import os
+import shutil
+from datetime import datetime
+from pathlib import Path
+from typing import Generic, Optional, TypeVar
+from urllib.parse import urlparse
+
+import chromadb
+import requests
+import yaml
+from open_webui.apps.webui.internal.db import Base, get_db
+from open_webui.env import (
+    OPEN_WEBUI_DIR,
+    DATA_DIR,
+    ENV,
+    FRONTEND_BUILD_DIR,
+    WEBUI_AUTH,
+    WEBUI_FAVICON_URL,
+    WEBUI_NAME,
+    log,
+)
+from pydantic import BaseModel
+from sqlalchemy import JSON, Column, DateTime, Integer, func
+
+
+class EndpointFilter(logging.Filter):
+    def filter(self, record: logging.LogRecord) -> bool:
+        return record.getMessage().find("/health") == -1
+
+
+# Filter out /endpoint
+logging.getLogger("uvicorn.access").addFilter(EndpointFilter())
+
+####################################
+# Config helpers
+####################################
+
+
+# Function to run the alembic migrations
+def run_migrations():
+    print("Running migrations")
+    try:
+        from alembic import command
+        from alembic.config import Config
+
+        alembic_cfg = Config(OPEN_WEBUI_DIR / "alembic.ini")
+
+        # Set the script location dynamically
+        migrations_path = OPEN_WEBUI_DIR / "migrations"
+        alembic_cfg.set_main_option("script_location", str(migrations_path))
+
+        command.upgrade(alembic_cfg, "head")
+    except Exception as e:
+        print(f"Error: {e}")
+
+
+run_migrations()
+
+
+class Config(Base):
+    __tablename__ = "config"
+
+    id = Column(Integer, primary_key=True)
+    data = Column(JSON, nullable=False)
+    version = Column(Integer, nullable=False, default=0)
+    created_at = Column(DateTime, nullable=False, server_default=func.now())
+    updated_at = Column(DateTime, nullable=True, onupdate=func.now())
+
+
+def load_json_config():
+    with open(f"{DATA_DIR}/config.json", "r") as file:
+        return json.load(file)
+
+
+def save_to_db(data):
+    with get_db() as db:
+        existing_config = db.query(Config).first()
+        if not existing_config:
+            new_config = Config(data=data, version=0)
+            db.add(new_config)
+        else:
+            existing_config.data = data
+            existing_config.updated_at = datetime.now()
+            db.add(existing_config)
+        db.commit()
+
+
+def reset_config():
+    with get_db() as db:
+        db.query(Config).delete()
+        db.commit()
+
+
+# When initializing, check if config.json exists and migrate it to the database
+if os.path.exists(f"{DATA_DIR}/config.json"):
+    data = load_json_config()
+    save_to_db(data)
+    os.rename(f"{DATA_DIR}/config.json", f"{DATA_DIR}/old_config.json")
+
+DEFAULT_CONFIG = {
+    "version": 0,
+    "ui": {
+        "default_locale": "",
+        "prompt_suggestions": [
+            {
+                "title": [
+                    "Help me study",
+                    "vocabulary for a college entrance exam",
+                ],
+                "content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.",
+            },
+            {
+                "title": [
+                    "Give me ideas",
+                    "for what to do with my kids' art",
+                ],
+                "content": "What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.",
+            },
+            {
+                "title": ["Tell me a fun fact", "about the Roman Empire"],
+                "content": "Tell me a random fun fact about the Roman Empire",
+            },
+            {
+                "title": [
+                    "Show me a code snippet",
+                    "of a website's sticky header",
+                ],
+                "content": "Show me a code snippet of a website's sticky header in CSS and JavaScript.",
+            },
+            {
+                "title": [
+                    "Explain options trading",
+                    "if I'm familiar with buying and selling stocks",
+                ],
+                "content": "Explain options trading in simple terms if I'm familiar with buying and selling stocks.",
+            },
+            {
+                "title": ["Overcome procrastination", "give me tips"],
+                "content": "Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?",
+            },
+            {
+                "title": [
+                    "Grammar check",
+                    "rewrite it for better readability ",
+                ],
+                "content": 'Check the following sentence for grammar and clarity: "[sentence]". Rewrite it for better readability while maintaining its original meaning.',
+            },
+        ],
+    },
+}
+
+
+def get_config():
+    with get_db() as db:
+        config_entry = db.query(Config).order_by(Config.id.desc()).first()
+        return config_entry.data if config_entry else DEFAULT_CONFIG
+
+
+CONFIG_DATA = get_config()
+
+
+def get_config_value(config_path: str):
+    path_parts = config_path.split(".")
+    cur_config = CONFIG_DATA
+    for key in path_parts:
+        if key in cur_config:
+            cur_config = cur_config[key]
+        else:
+            return None
+    return cur_config
+
+
+PERSISTENT_CONFIG_REGISTRY = []
+
+
+def save_config(config):
+    global CONFIG_DATA
+    global PERSISTENT_CONFIG_REGISTRY
+    try:
+        save_to_db(config)
+        CONFIG_DATA = config
+
+        # Trigger updates on all registered PersistentConfig entries
+        for config_item in PERSISTENT_CONFIG_REGISTRY:
+            config_item.update()
+    except Exception as e:
+        log.exception(e)
+        return False
+    return True
+
+
+T = TypeVar("T")
+
+
+class PersistentConfig(Generic[T]):
+    def __init__(self, env_name: str, config_path: str, env_value: T):
+        self.env_name = env_name
+        self.config_path = config_path
+        self.env_value = env_value
+        self.config_value = get_config_value(config_path)
+        if self.config_value is not None:
+            log.info(f"'{env_name}' loaded from the latest database entry")
+            self.value = self.config_value
+        else:
+            self.value = env_value
+
+        PERSISTENT_CONFIG_REGISTRY.append(self)
+
+    def __str__(self):
+        return str(self.value)
+
+    @property
+    def __dict__(self):
+        raise TypeError(
+            "PersistentConfig object cannot be converted to dict, use config_get or .value instead."
+        )
+
+    def __getattribute__(self, item):
+        if item == "__dict__":
+            raise TypeError(
+                "PersistentConfig object cannot be converted to dict, use config_get or .value instead."
+            )
+        return super().__getattribute__(item)
+
+    def update(self):
+        new_value = get_config_value(self.config_path)
+        if new_value is not None:
+            self.value = new_value
+            log.info(f"Updated {self.env_name} to new value {self.value}")
+
+    def save(self):
+        log.info(f"Saving '{self.env_name}' to the database")
+        path_parts = self.config_path.split(".")
+        sub_config = CONFIG_DATA
+        for key in path_parts[:-1]:
+            if key not in sub_config:
+                sub_config[key] = {}
+            sub_config = sub_config[key]
+        sub_config[path_parts[-1]] = self.value
+        save_to_db(CONFIG_DATA)
+        self.config_value = self.value
+
+
+class AppConfig:
+    _state: dict[str, PersistentConfig]
+
+    def __init__(self):
+        super().__setattr__("_state", {})
+
+    def __setattr__(self, key, value):
+        if isinstance(value, PersistentConfig):
+            self._state[key] = value
+        else:
+            self._state[key].value = value
+            self._state[key].save()
+
+    def __getattr__(self, key):
+        return self._state[key].value
+
+
+####################################
+# WEBUI_AUTH (Required for security)
+####################################
+
+JWT_EXPIRES_IN = PersistentConfig(
+    "JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1")
+)
+
+####################################
+# OAuth config
+####################################
+
+ENABLE_OAUTH_SIGNUP = PersistentConfig(
+    "ENABLE_OAUTH_SIGNUP",
+    "oauth.enable_signup",
+    os.environ.get("ENABLE_OAUTH_SIGNUP", "False").lower() == "true",
+)
+
+OAUTH_MERGE_ACCOUNTS_BY_EMAIL = PersistentConfig(
+    "OAUTH_MERGE_ACCOUNTS_BY_EMAIL",
+    "oauth.merge_accounts_by_email",
+    os.environ.get("OAUTH_MERGE_ACCOUNTS_BY_EMAIL", "False").lower() == "true",
+)
+
+OAUTH_PROVIDERS = {}
+
+GOOGLE_CLIENT_ID = PersistentConfig(
+    "GOOGLE_CLIENT_ID",
+    "oauth.google.client_id",
+    os.environ.get("GOOGLE_CLIENT_ID", ""),
+)
+
+GOOGLE_CLIENT_SECRET = PersistentConfig(
+    "GOOGLE_CLIENT_SECRET",
+    "oauth.google.client_secret",
+    os.environ.get("GOOGLE_CLIENT_SECRET", ""),
+)
+
+GOOGLE_OAUTH_SCOPE = PersistentConfig(
+    "GOOGLE_OAUTH_SCOPE",
+    "oauth.google.scope",
+    os.environ.get("GOOGLE_OAUTH_SCOPE", "openid email profile"),
+)
+
+GOOGLE_REDIRECT_URI = PersistentConfig(
+    "GOOGLE_REDIRECT_URI",
+    "oauth.google.redirect_uri",
+    os.environ.get("GOOGLE_REDIRECT_URI", ""),
+)
+
+MICROSOFT_CLIENT_ID = PersistentConfig(
+    "MICROSOFT_CLIENT_ID",
+    "oauth.microsoft.client_id",
+    os.environ.get("MICROSOFT_CLIENT_ID", ""),
+)
+
+MICROSOFT_CLIENT_SECRET = PersistentConfig(
+    "MICROSOFT_CLIENT_SECRET",
+    "oauth.microsoft.client_secret",
+    os.environ.get("MICROSOFT_CLIENT_SECRET", ""),
+)
+
+MICROSOFT_CLIENT_TENANT_ID = PersistentConfig(
+    "MICROSOFT_CLIENT_TENANT_ID",
+    "oauth.microsoft.tenant_id",
+    os.environ.get("MICROSOFT_CLIENT_TENANT_ID", ""),
+)
+
+MICROSOFT_OAUTH_SCOPE = PersistentConfig(
+    "MICROSOFT_OAUTH_SCOPE",
+    "oauth.microsoft.scope",
+    os.environ.get("MICROSOFT_OAUTH_SCOPE", "openid email profile"),
+)
+
+MICROSOFT_REDIRECT_URI = PersistentConfig(
+    "MICROSOFT_REDIRECT_URI",
+    "oauth.microsoft.redirect_uri",
+    os.environ.get("MICROSOFT_REDIRECT_URI", ""),
+)
+
+OAUTH_CLIENT_ID = PersistentConfig(
+    "OAUTH_CLIENT_ID",
+    "oauth.oidc.client_id",
+    os.environ.get("OAUTH_CLIENT_ID", ""),
+)
+
+OAUTH_CLIENT_SECRET = PersistentConfig(
+    "OAUTH_CLIENT_SECRET",
+    "oauth.oidc.client_secret",
+    os.environ.get("OAUTH_CLIENT_SECRET", ""),
+)
+
+OPENID_PROVIDER_URL = PersistentConfig(
+    "OPENID_PROVIDER_URL",
+    "oauth.oidc.provider_url",
+    os.environ.get("OPENID_PROVIDER_URL", ""),
+)
+
+OPENID_REDIRECT_URI = PersistentConfig(
+    "OPENID_REDIRECT_URI",
+    "oauth.oidc.redirect_uri",
+    os.environ.get("OPENID_REDIRECT_URI", ""),
+)
+
+OAUTH_SCOPES = PersistentConfig(
+    "OAUTH_SCOPES",
+    "oauth.oidc.scopes",
+    os.environ.get("OAUTH_SCOPES", "openid email profile"),
+)
+
+OAUTH_PROVIDER_NAME = PersistentConfig(
+    "OAUTH_PROVIDER_NAME",
+    "oauth.oidc.provider_name",
+    os.environ.get("OAUTH_PROVIDER_NAME", "SSO"),
+)
+
+OAUTH_USERNAME_CLAIM = PersistentConfig(
+    "OAUTH_USERNAME_CLAIM",
+    "oauth.oidc.username_claim",
+    os.environ.get("OAUTH_USERNAME_CLAIM", "name"),
+)
+
+OAUTH_PICTURE_CLAIM = PersistentConfig(
+    "OAUTH_PICTURE_CLAIM",
+    "oauth.oidc.avatar_claim",
+    os.environ.get("OAUTH_PICTURE_CLAIM", "picture"),
+)
+
+OAUTH_EMAIL_CLAIM = PersistentConfig(
+    "OAUTH_EMAIL_CLAIM",
+    "oauth.oidc.email_claim",
+    os.environ.get("OAUTH_EMAIL_CLAIM", "email"),
+)
+
+ENABLE_OAUTH_ROLE_MANAGEMENT = PersistentConfig(
+    "ENABLE_OAUTH_ROLE_MANAGEMENT",
+    "oauth.enable_role_mapping",
+    os.environ.get("ENABLE_OAUTH_ROLE_MANAGEMENT", "False").lower() == "true",
+)
+
+OAUTH_ROLES_CLAIM = PersistentConfig(
+    "OAUTH_ROLES_CLAIM",
+    "oauth.roles_claim",
+    os.environ.get("OAUTH_ROLES_CLAIM", "roles"),
+)
+
+OAUTH_ALLOWED_ROLES = PersistentConfig(
+    "OAUTH_ALLOWED_ROLES",
+    "oauth.allowed_roles",
+    [
+        role.strip()
+        for role in os.environ.get("OAUTH_ALLOWED_ROLES", "user,admin").split(",")
+    ],
+)
+
+OAUTH_ADMIN_ROLES = PersistentConfig(
+    "OAUTH_ADMIN_ROLES",
+    "oauth.admin_roles",
+    [role.strip() for role in os.environ.get("OAUTH_ADMIN_ROLES", "admin").split(",")],
+)
+
+
+def load_oauth_providers():
+    OAUTH_PROVIDERS.clear()
+    if GOOGLE_CLIENT_ID.value and GOOGLE_CLIENT_SECRET.value:
+        OAUTH_PROVIDERS["google"] = {
+            "client_id": GOOGLE_CLIENT_ID.value,
+            "client_secret": GOOGLE_CLIENT_SECRET.value,
+            "server_metadata_url": "https://accounts.google.com/.well-known/openid-configuration",
+            "scope": GOOGLE_OAUTH_SCOPE.value,
+            "redirect_uri": GOOGLE_REDIRECT_URI.value,
+        }
+
+    if (
+        MICROSOFT_CLIENT_ID.value
+        and MICROSOFT_CLIENT_SECRET.value
+        and MICROSOFT_CLIENT_TENANT_ID.value
+    ):
+        OAUTH_PROVIDERS["microsoft"] = {
+            "client_id": MICROSOFT_CLIENT_ID.value,
+            "client_secret": MICROSOFT_CLIENT_SECRET.value,
+            "server_metadata_url": f"https://login.microsoftonline.com/{MICROSOFT_CLIENT_TENANT_ID.value}/v2.0/.well-known/openid-configuration",
+            "scope": MICROSOFT_OAUTH_SCOPE.value,
+            "redirect_uri": MICROSOFT_REDIRECT_URI.value,
+        }
+
+    if (
+        OAUTH_CLIENT_ID.value
+        and OAUTH_CLIENT_SECRET.value
+        and OPENID_PROVIDER_URL.value
+    ):
+        OAUTH_PROVIDERS["oidc"] = {
+            "client_id": OAUTH_CLIENT_ID.value,
+            "client_secret": OAUTH_CLIENT_SECRET.value,
+            "server_metadata_url": OPENID_PROVIDER_URL.value,
+            "scope": OAUTH_SCOPES.value,
+            "name": OAUTH_PROVIDER_NAME.value,
+            "redirect_uri": OPENID_REDIRECT_URI.value,
+        }
+
+
+load_oauth_providers()
+
+####################################
+# Static DIR
+####################################
+
+STATIC_DIR = Path(os.getenv("STATIC_DIR", OPEN_WEBUI_DIR / "static")).resolve()
+
+frontend_favicon = FRONTEND_BUILD_DIR / "static" / "favicon.png"
+
+if frontend_favicon.exists():
+    try:
+        shutil.copyfile(frontend_favicon, STATIC_DIR / "favicon.png")
+    except Exception as e:
+        logging.error(f"An error occurred: {e}")
+else:
+    logging.warning(f"Frontend favicon not found at {frontend_favicon}")
+
+frontend_splash = FRONTEND_BUILD_DIR / "static" / "splash.png"
+
+if frontend_splash.exists():
+    try:
+        shutil.copyfile(frontend_splash, STATIC_DIR / "splash.png")
+    except Exception as e:
+        logging.error(f"An error occurred: {e}")
+else:
+    logging.warning(f"Frontend splash not found at {frontend_splash}")
+
+
+####################################
+# CUSTOM_NAME
+####################################
+
+CUSTOM_NAME = os.environ.get("CUSTOM_NAME", "")
+
+if CUSTOM_NAME:
+    try:
+        r = requests.get(f"https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}")
+        data = r.json()
+        if r.ok:
+            if "logo" in data:
+                WEBUI_FAVICON_URL = url = (
+                    f"https://api.openwebui.com{data['logo']}"
+                    if data["logo"][0] == "/"
+                    else data["logo"]
+                )
+
+                r = requests.get(url, stream=True)
+                if r.status_code == 200:
+                    with open(f"{STATIC_DIR}/favicon.png", "wb") as f:
+                        r.raw.decode_content = True
+                        shutil.copyfileobj(r.raw, f)
+
+            if "splash" in data:
+                url = (
+                    f"https://api.openwebui.com{data['splash']}"
+                    if data["splash"][0] == "/"
+                    else data["splash"]
+                )
+
+                r = requests.get(url, stream=True)
+                if r.status_code == 200:
+                    with open(f"{STATIC_DIR}/splash.png", "wb") as f:
+                        r.raw.decode_content = True
+                        shutil.copyfileobj(r.raw, f)
+
+            WEBUI_NAME = data["name"]
+    except Exception as e:
+        log.exception(e)
+        pass
+
+
+####################################
+# STORAGE PROVIDER
+####################################
+
+STORAGE_PROVIDER = os.environ.get("STORAGE_PROVIDER", "")  # defaults to local, s3
+
+S3_ACCESS_KEY_ID = os.environ.get("S3_ACCESS_KEY_ID", None)
+S3_SECRET_ACCESS_KEY = os.environ.get("S3_SECRET_ACCESS_KEY", None)
+S3_REGION_NAME = os.environ.get("S3_REGION_NAME", None)
+S3_BUCKET_NAME = os.environ.get("S3_BUCKET_NAME", None)
+S3_ENDPOINT_URL = os.environ.get("S3_ENDPOINT_URL", None)
+
+####################################
+# File Upload DIR
+####################################
+
+UPLOAD_DIR = f"{DATA_DIR}/uploads"
+Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True)
+
+
+####################################
+# Cache DIR
+####################################
+
+CACHE_DIR = f"{DATA_DIR}/cache"
+Path(CACHE_DIR).mkdir(parents=True, exist_ok=True)
+
+####################################
+# OLLAMA_BASE_URL
+####################################
+
+ENABLE_OLLAMA_API = PersistentConfig(
+    "ENABLE_OLLAMA_API",
+    "ollama.enable",
+    os.environ.get("ENABLE_OLLAMA_API", "True").lower() == "true",
+)
+
+OLLAMA_API_BASE_URL = os.environ.get(
+    "OLLAMA_API_BASE_URL", "http://localhost:11434/api"
+)
+
+OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "")
+
+K8S_FLAG = os.environ.get("K8S_FLAG", "")
+USE_OLLAMA_DOCKER = os.environ.get("USE_OLLAMA_DOCKER", "false")
+
+if OLLAMA_BASE_URL == "" and OLLAMA_API_BASE_URL != "":
+    OLLAMA_BASE_URL = (
+        OLLAMA_API_BASE_URL[:-4]
+        if OLLAMA_API_BASE_URL.endswith("/api")
+        else OLLAMA_API_BASE_URL
+    )
+
+if ENV == "prod":
+    if OLLAMA_BASE_URL == "/ollama" and not K8S_FLAG:
+        if USE_OLLAMA_DOCKER.lower() == "true":
+            # if you use all-in-one docker container (Open WebUI + Ollama)
+            # with the docker build arg USE_OLLAMA=true (--build-arg="USE_OLLAMA=true") this only works with http://localhost:11434
+            OLLAMA_BASE_URL = "http://localhost:11434"
+        else:
+            OLLAMA_BASE_URL = "http://host.docker.internal:11434"
+    elif K8S_FLAG:
+        OLLAMA_BASE_URL = "http://ollama-service.open-webui.svc.cluster.local:11434"
+
+
+OLLAMA_BASE_URLS = os.environ.get("OLLAMA_BASE_URLS", "")
+OLLAMA_BASE_URLS = OLLAMA_BASE_URLS if OLLAMA_BASE_URLS != "" else OLLAMA_BASE_URL
+
+OLLAMA_BASE_URLS = [url.strip() for url in OLLAMA_BASE_URLS.split(";")]
+OLLAMA_BASE_URLS = PersistentConfig(
+    "OLLAMA_BASE_URLS", "ollama.base_urls", OLLAMA_BASE_URLS
+)
+
+####################################
+# OPENAI_API
+####################################
+
+
+ENABLE_OPENAI_API = PersistentConfig(
+    "ENABLE_OPENAI_API",
+    "openai.enable",
+    os.environ.get("ENABLE_OPENAI_API", "True").lower() == "true",
+)
+
+
+OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
+OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "")
+
+
+if OPENAI_API_BASE_URL == "":
+    OPENAI_API_BASE_URL = "https://api.openai.com/v1"
+
+OPENAI_API_KEYS = os.environ.get("OPENAI_API_KEYS", "")
+OPENAI_API_KEYS = OPENAI_API_KEYS if OPENAI_API_KEYS != "" else OPENAI_API_KEY
+
+OPENAI_API_KEYS = [url.strip() for url in OPENAI_API_KEYS.split(";")]
+OPENAI_API_KEYS = PersistentConfig(
+    "OPENAI_API_KEYS", "openai.api_keys", OPENAI_API_KEYS
+)
+
+OPENAI_API_BASE_URLS = os.environ.get("OPENAI_API_BASE_URLS", "")
+OPENAI_API_BASE_URLS = (
+    OPENAI_API_BASE_URLS if OPENAI_API_BASE_URLS != "" else OPENAI_API_BASE_URL
+)
+
+OPENAI_API_BASE_URLS = [
+    url.strip() if url != "" else "https://api.openai.com/v1"
+    for url in OPENAI_API_BASE_URLS.split(";")
+]
+OPENAI_API_BASE_URLS = PersistentConfig(
+    "OPENAI_API_BASE_URLS", "openai.api_base_urls", OPENAI_API_BASE_URLS
+)
+
+OPENAI_API_KEY = ""
+
+try:
+    OPENAI_API_KEY = OPENAI_API_KEYS.value[
+        OPENAI_API_BASE_URLS.value.index("https://api.openai.com/v1")
+    ]
+except Exception:
+    pass
+
+OPENAI_API_BASE_URL = "https://api.openai.com/v1"
+
+####################################
+# WEBUI
+####################################
+
+ENABLE_SIGNUP = PersistentConfig(
+    "ENABLE_SIGNUP",
+    "ui.enable_signup",
+    (
+        False
+        if not WEBUI_AUTH
+        else os.environ.get("ENABLE_SIGNUP", "True").lower() == "true"
+    ),
+)
+
+ENABLE_LOGIN_FORM = PersistentConfig(
+    "ENABLE_LOGIN_FORM",
+    "ui.ENABLE_LOGIN_FORM",
+    os.environ.get("ENABLE_LOGIN_FORM", "True").lower() == "true",
+)
+
+DEFAULT_LOCALE = PersistentConfig(
+    "DEFAULT_LOCALE",
+    "ui.default_locale",
+    os.environ.get("DEFAULT_LOCALE", ""),
+)
+
+DEFAULT_MODELS = PersistentConfig(
+    "DEFAULT_MODELS", "ui.default_models", os.environ.get("DEFAULT_MODELS", None)
+)
+
+DEFAULT_PROMPT_SUGGESTIONS = PersistentConfig(
+    "DEFAULT_PROMPT_SUGGESTIONS",
+    "ui.prompt_suggestions",
+    [
+        {
+            "title": ["Help me study", "vocabulary for a college entrance exam"],
+            "content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.",
+        },
+        {
+            "title": ["Give me ideas", "for what to do with my kids' art"],
+            "content": "What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.",
+        },
+        {
+            "title": ["Tell me a fun fact", "about the Roman Empire"],
+            "content": "Tell me a random fun fact about the Roman Empire",
+        },
+        {
+            "title": ["Show me a code snippet", "of a website's sticky header"],
+            "content": "Show me a code snippet of a website's sticky header in CSS and JavaScript.",
+        },
+        {
+            "title": [
+                "Explain options trading",
+                "if I'm familiar with buying and selling stocks",
+            ],
+            "content": "Explain options trading in simple terms if I'm familiar with buying and selling stocks.",
+        },
+        {
+            "title": ["Overcome procrastination", "give me tips"],
+            "content": "Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?",
+        },
+    ],
+)
+
+DEFAULT_USER_ROLE = PersistentConfig(
+    "DEFAULT_USER_ROLE",
+    "ui.default_user_role",
+    os.getenv("DEFAULT_USER_ROLE", "pending"),
+)
+
+USER_PERMISSIONS_CHAT_DELETION = (
+    os.environ.get("USER_PERMISSIONS_CHAT_DELETION", "True").lower() == "true"
+)
+
+USER_PERMISSIONS_CHAT_EDITING = (
+    os.environ.get("USER_PERMISSIONS_CHAT_EDITING", "True").lower() == "true"
+)
+
+USER_PERMISSIONS_CHAT_TEMPORARY = (
+    os.environ.get("USER_PERMISSIONS_CHAT_TEMPORARY", "True").lower() == "true"
+)
+
+USER_PERMISSIONS = PersistentConfig(
+    "USER_PERMISSIONS",
+    "ui.user_permissions",
+    {
+        "chat": {
+            "deletion": USER_PERMISSIONS_CHAT_DELETION,
+            "editing": USER_PERMISSIONS_CHAT_EDITING,
+            "temporary": USER_PERMISSIONS_CHAT_TEMPORARY,
+        }
+    },
+)
+
+
+ENABLE_EVALUATION_ARENA_MODELS = PersistentConfig(
+    "ENABLE_EVALUATION_ARENA_MODELS",
+    "evaluation.arena.enable",
+    os.environ.get("ENABLE_EVALUATION_ARENA_MODELS", "True").lower() == "true",
+)
+EVALUATION_ARENA_MODELS = PersistentConfig(
+    "EVALUATION_ARENA_MODELS",
+    "evaluation.arena.models",
+    [],
+)
+
+DEFAULT_ARENA_MODEL = {
+    "id": "arena-model",
+    "name": "Arena Model",
+    "meta": {
+        "profile_image_url": "/favicon.png",
+        "description": "Submit your questions to anonymous AI chatbots and vote on the best response.",
+        "model_ids": None,
+    },
+}
+
+ENABLE_MODEL_FILTER = PersistentConfig(
+    "ENABLE_MODEL_FILTER",
+    "model_filter.enable",
+    os.environ.get("ENABLE_MODEL_FILTER", "False").lower() == "true",
+)
+MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "")
+MODEL_FILTER_LIST = PersistentConfig(
+    "MODEL_FILTER_LIST",
+    "model_filter.list",
+    [model.strip() for model in MODEL_FILTER_LIST.split(";")],
+)
+
+WEBHOOK_URL = PersistentConfig(
+    "WEBHOOK_URL", "webhook_url", os.environ.get("WEBHOOK_URL", "")
+)
+
+ENABLE_ADMIN_EXPORT = os.environ.get("ENABLE_ADMIN_EXPORT", "True").lower() == "true"
+
+ENABLE_ADMIN_CHAT_ACCESS = (
+    os.environ.get("ENABLE_ADMIN_CHAT_ACCESS", "True").lower() == "true"
+)
+
+ENABLE_COMMUNITY_SHARING = PersistentConfig(
+    "ENABLE_COMMUNITY_SHARING",
+    "ui.enable_community_sharing",
+    os.environ.get("ENABLE_COMMUNITY_SHARING", "True").lower() == "true",
+)
+
+ENABLE_MESSAGE_RATING = PersistentConfig(
+    "ENABLE_MESSAGE_RATING",
+    "ui.enable_message_rating",
+    os.environ.get("ENABLE_MESSAGE_RATING", "True").lower() == "true",
+)
+
+
+def validate_cors_origins(origins):
+    for origin in origins:
+        if origin != "*":
+            validate_cors_origin(origin)
+
+
+def validate_cors_origin(origin):
+    parsed_url = urlparse(origin)
+
+    # Check if the scheme is either http or https
+    if parsed_url.scheme not in ["http", "https"]:
+        raise ValueError(
+            f"Invalid scheme in CORS_ALLOW_ORIGIN: '{origin}'. Only 'http' and 'https' are allowed."
+        )
+
+    # Ensure that the netloc (domain + port) is present, indicating it's a valid URL
+    if not parsed_url.netloc:
+        raise ValueError(f"Invalid URL structure in CORS_ALLOW_ORIGIN: '{origin}'.")
+
+
+# For production, you should only need one host as
+# fastapi serves the svelte-kit built frontend and backend from the same host and port.
+# To test CORS_ALLOW_ORIGIN locally, you can set something like
+# CORS_ALLOW_ORIGIN=http://localhost:5173;http://localhost:8080
+# in your .env file depending on your frontend port, 5173 in this case.
+CORS_ALLOW_ORIGIN = os.environ.get("CORS_ALLOW_ORIGIN", "*").split(";")
+
+if "*" in CORS_ALLOW_ORIGIN:
+    log.warning(
+        "\n\nWARNING: CORS_ALLOW_ORIGIN IS SET TO '*' - NOT RECOMMENDED FOR PRODUCTION DEPLOYMENTS.\n"
+    )
+
+validate_cors_origins(CORS_ALLOW_ORIGIN)
+
+
+class BannerModel(BaseModel):
+    id: str
+    type: str
+    title: Optional[str] = None
+    content: str
+    dismissible: bool
+    timestamp: int
+
+
+try:
+    banners = json.loads(os.environ.get("WEBUI_BANNERS", "[]"))
+    banners = [BannerModel(**banner) for banner in banners]
+except Exception as e:
+    print(f"Error loading WEBUI_BANNERS: {e}")
+    banners = []
+
+WEBUI_BANNERS = PersistentConfig("WEBUI_BANNERS", "ui.banners", banners)
+
+
+SHOW_ADMIN_DETAILS = PersistentConfig(
+    "SHOW_ADMIN_DETAILS",
+    "auth.admin.show",
+    os.environ.get("SHOW_ADMIN_DETAILS", "true").lower() == "true",
+)
+
+ADMIN_EMAIL = PersistentConfig(
+    "ADMIN_EMAIL",
+    "auth.admin.email",
+    os.environ.get("ADMIN_EMAIL", None),
+)
+
+
+####################################
+# TASKS
+####################################
+
+
+TASK_MODEL = PersistentConfig(
+    "TASK_MODEL",
+    "task.model.default",
+    os.environ.get("TASK_MODEL", ""),
+)
+
+TASK_MODEL_EXTERNAL = PersistentConfig(
+    "TASK_MODEL_EXTERNAL",
+    "task.model.external",
+    os.environ.get("TASK_MODEL_EXTERNAL", ""),
+)
+
+TITLE_GENERATION_PROMPT_TEMPLATE = PersistentConfig(
+    "TITLE_GENERATION_PROMPT_TEMPLATE",
+    "task.title.prompt_template",
+    os.environ.get("TITLE_GENERATION_PROMPT_TEMPLATE", ""),
+)
+
+TAGS_GENERATION_PROMPT_TEMPLATE = PersistentConfig(
+    "TAGS_GENERATION_PROMPT_TEMPLATE",
+    "task.tags.prompt_template",
+    os.environ.get("TAGS_GENERATION_PROMPT_TEMPLATE", ""),
+)
+
+ENABLE_SEARCH_QUERY = PersistentConfig(
+    "ENABLE_SEARCH_QUERY",
+    "task.search.enable",
+    os.environ.get("ENABLE_SEARCH_QUERY", "True").lower() == "true",
+)
+
+
+SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE = PersistentConfig(
+    "SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE",
+    "task.search.prompt_template",
+    os.environ.get("SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE", ""),
+)
+
+
+TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = PersistentConfig(
+    "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE",
+    "task.tools.prompt_template",
+    os.environ.get("TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE", ""),
+)
+
+
+####################################
+# Vector Database
+####################################
+
+VECTOR_DB = os.environ.get("VECTOR_DB", "chroma")
+
+# Chroma
+CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db"
+CHROMA_TENANT = os.environ.get("CHROMA_TENANT", chromadb.DEFAULT_TENANT)
+CHROMA_DATABASE = os.environ.get("CHROMA_DATABASE", chromadb.DEFAULT_DATABASE)
+CHROMA_HTTP_HOST = os.environ.get("CHROMA_HTTP_HOST", "")
+CHROMA_HTTP_PORT = int(os.environ.get("CHROMA_HTTP_PORT", "8000"))
+# Comma-separated list of header=value pairs
+CHROMA_HTTP_HEADERS = os.environ.get("CHROMA_HTTP_HEADERS", "")
+if CHROMA_HTTP_HEADERS:
+    CHROMA_HTTP_HEADERS = dict(
+        [pair.split("=") for pair in CHROMA_HTTP_HEADERS.split(",")]
+    )
+else:
+    CHROMA_HTTP_HEADERS = None
+CHROMA_HTTP_SSL = os.environ.get("CHROMA_HTTP_SSL", "false").lower() == "true"
+# this uses the model defined in the Dockerfile ENV variable. If you dont use docker or docker based deployments such as k8s, the default embedding model will be used (sentence-transformers/all-MiniLM-L6-v2)
+
+# Milvus
+
+MILVUS_URI = os.environ.get("MILVUS_URI", f"{DATA_DIR}/vector_db/milvus.db")
+
+# Qdrant
+QDRANT_URI = os.environ.get("QDRANT_URI", None)
+
+####################################
+# Information Retrieval (RAG)
+####################################
+
+# RAG Content Extraction
+CONTENT_EXTRACTION_ENGINE = PersistentConfig(
+    "CONTENT_EXTRACTION_ENGINE",
+    "rag.CONTENT_EXTRACTION_ENGINE",
+    os.environ.get("CONTENT_EXTRACTION_ENGINE", "").lower(),
+)
+
+TIKA_SERVER_URL = PersistentConfig(
+    "TIKA_SERVER_URL",
+    "rag.tika_server_url",
+    os.getenv("TIKA_SERVER_URL", "http://tika:9998"),  # Default for sidecar deployment
+)
+
+RAG_TOP_K = PersistentConfig(
+    "RAG_TOP_K", "rag.top_k", int(os.environ.get("RAG_TOP_K", "3"))
+)
+RAG_RELEVANCE_THRESHOLD = PersistentConfig(
+    "RAG_RELEVANCE_THRESHOLD",
+    "rag.relevance_threshold",
+    float(os.environ.get("RAG_RELEVANCE_THRESHOLD", "0.0")),
+)
+
+ENABLE_RAG_HYBRID_SEARCH = PersistentConfig(
+    "ENABLE_RAG_HYBRID_SEARCH",
+    "rag.enable_hybrid_search",
+    os.environ.get("ENABLE_RAG_HYBRID_SEARCH", "").lower() == "true",
+)
+
+RAG_FILE_MAX_COUNT = PersistentConfig(
+    "RAG_FILE_MAX_COUNT",
+    "rag.file.max_count",
+    (
+        int(os.environ.get("RAG_FILE_MAX_COUNT"))
+        if os.environ.get("RAG_FILE_MAX_COUNT")
+        else None
+    ),
+)
+
+RAG_FILE_MAX_SIZE = PersistentConfig(
+    "RAG_FILE_MAX_SIZE",
+    "rag.file.max_size",
+    (
+        int(os.environ.get("RAG_FILE_MAX_SIZE"))
+        if os.environ.get("RAG_FILE_MAX_SIZE")
+        else None
+    ),
+)
+
+ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = PersistentConfig(
+    "ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION",
+    "rag.enable_web_loader_ssl_verification",
+    os.environ.get("ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION", "True").lower() == "true",
+)
+
+RAG_EMBEDDING_ENGINE = PersistentConfig(
+    "RAG_EMBEDDING_ENGINE",
+    "rag.embedding_engine",
+    os.environ.get("RAG_EMBEDDING_ENGINE", ""),
+)
+
+PDF_EXTRACT_IMAGES = PersistentConfig(
+    "PDF_EXTRACT_IMAGES",
+    "rag.pdf_extract_images",
+    os.environ.get("PDF_EXTRACT_IMAGES", "False").lower() == "true",
+)
+
+RAG_EMBEDDING_MODEL = PersistentConfig(
+    "RAG_EMBEDDING_MODEL",
+    "rag.embedding_model",
+    os.environ.get("RAG_EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2"),
+)
+log.info(f"Embedding model set: {RAG_EMBEDDING_MODEL.value}")
+
+RAG_EMBEDDING_MODEL_AUTO_UPDATE = (
+    os.environ.get("RAG_EMBEDDING_MODEL_AUTO_UPDATE", "").lower() == "true"
+)
+
+RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE = (
+    os.environ.get("RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE", "").lower() == "true"
+)
+
+RAG_EMBEDDING_BATCH_SIZE = PersistentConfig(
+    "RAG_EMBEDDING_BATCH_SIZE",
+    "rag.embedding_batch_size",
+    int(
+        os.environ.get("RAG_EMBEDDING_BATCH_SIZE")
+        or os.environ.get("RAG_EMBEDDING_OPENAI_BATCH_SIZE", "1")
+    ),
+)
+
+RAG_RERANKING_MODEL = PersistentConfig(
+    "RAG_RERANKING_MODEL",
+    "rag.reranking_model",
+    os.environ.get("RAG_RERANKING_MODEL", ""),
+)
+if RAG_RERANKING_MODEL.value != "":
+    log.info(f"Reranking model set: {RAG_RERANKING_MODEL.value}")
+
+RAG_RERANKING_MODEL_AUTO_UPDATE = (
+    os.environ.get("RAG_RERANKING_MODEL_AUTO_UPDATE", "").lower() == "true"
+)
+
+RAG_RERANKING_MODEL_TRUST_REMOTE_CODE = (
+    os.environ.get("RAG_RERANKING_MODEL_TRUST_REMOTE_CODE", "").lower() == "true"
+)
+
+
+RAG_TEXT_SPLITTER = PersistentConfig(
+    "RAG_TEXT_SPLITTER",
+    "rag.text_splitter",
+    os.environ.get("RAG_TEXT_SPLITTER", ""),
+)
+
+
+TIKTOKEN_CACHE_DIR = os.environ.get("TIKTOKEN_CACHE_DIR", f"{CACHE_DIR}/tiktoken")
+TIKTOKEN_ENCODING_NAME = PersistentConfig(
+    "TIKTOKEN_ENCODING_NAME",
+    "rag.tiktoken_encoding_name",
+    os.environ.get("TIKTOKEN_ENCODING_NAME", "cl100k_base"),
+)
+
+
+CHUNK_SIZE = PersistentConfig(
+    "CHUNK_SIZE", "rag.chunk_size", int(os.environ.get("CHUNK_SIZE", "1000"))
+)
+CHUNK_OVERLAP = PersistentConfig(
+    "CHUNK_OVERLAP",
+    "rag.chunk_overlap",
+    int(os.environ.get("CHUNK_OVERLAP", "100")),
+)
+
+DEFAULT_RAG_TEMPLATE = """You are given a user query, some textual context and rules, all inside xml tags. You have to answer the query based on the context while respecting the rules.
+
+<context>
+{{CONTEXT}}
+</context>
+
+<rules>
+- If you don't know, just say so.
+- If you are not sure, ask for clarification.
+- Answer in the same language as the user query.
+- If the context appears unreadable or of poor quality, tell the user then answer as best as you can.
+- If the answer is not in the context but you think you know the answer, explain that to the user then answer with your own knowledge.
+- Answer directly and without using xml tags.
+</rules>
+
+<user_query>
+{{QUERY}}
+</user_query>
+"""
+
+RAG_TEMPLATE = PersistentConfig(
+    "RAG_TEMPLATE",
+    "rag.template",
+    os.environ.get("RAG_TEMPLATE", DEFAULT_RAG_TEMPLATE),
+)
+
+RAG_OPENAI_API_BASE_URL = PersistentConfig(
+    "RAG_OPENAI_API_BASE_URL",
+    "rag.openai_api_base_url",
+    os.getenv("RAG_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
+)
+RAG_OPENAI_API_KEY = PersistentConfig(
+    "RAG_OPENAI_API_KEY",
+    "rag.openai_api_key",
+    os.getenv("RAG_OPENAI_API_KEY", OPENAI_API_KEY),
+)
+
+ENABLE_RAG_LOCAL_WEB_FETCH = (
+    os.getenv("ENABLE_RAG_LOCAL_WEB_FETCH", "False").lower() == "true"
+)
+
+YOUTUBE_LOADER_LANGUAGE = PersistentConfig(
+    "YOUTUBE_LOADER_LANGUAGE",
+    "rag.youtube_loader_language",
+    os.getenv("YOUTUBE_LOADER_LANGUAGE", "en").split(","),
+)
+
+
+ENABLE_RAG_WEB_SEARCH = PersistentConfig(
+    "ENABLE_RAG_WEB_SEARCH",
+    "rag.web.search.enable",
+    os.getenv("ENABLE_RAG_WEB_SEARCH", "False").lower() == "true",
+)
+
+RAG_WEB_SEARCH_ENGINE = PersistentConfig(
+    "RAG_WEB_SEARCH_ENGINE",
+    "rag.web.search.engine",
+    os.getenv("RAG_WEB_SEARCH_ENGINE", ""),
+)
+
+# You can provide a list of your own websites to filter after performing a web search.
+# This ensures the highest level of safety and reliability of the information sources.
+RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = PersistentConfig(
+    "RAG_WEB_SEARCH_DOMAIN_FILTER_LIST",
+    "rag.rag.web.search.domain.filter_list",
+    [
+        # "wikipedia.com",
+        # "wikimedia.org",
+        # "wikidata.org",
+    ],
+)
+
+SEARXNG_QUERY_URL = PersistentConfig(
+    "SEARXNG_QUERY_URL",
+    "rag.web.search.searxng_query_url",
+    os.getenv("SEARXNG_QUERY_URL", ""),
+)
+
+GOOGLE_PSE_API_KEY = PersistentConfig(
+    "GOOGLE_PSE_API_KEY",
+    "rag.web.search.google_pse_api_key",
+    os.getenv("GOOGLE_PSE_API_KEY", ""),
+)
+
+GOOGLE_PSE_ENGINE_ID = PersistentConfig(
+    "GOOGLE_PSE_ENGINE_ID",
+    "rag.web.search.google_pse_engine_id",
+    os.getenv("GOOGLE_PSE_ENGINE_ID", ""),
+)
+
+BRAVE_SEARCH_API_KEY = PersistentConfig(
+    "BRAVE_SEARCH_API_KEY",
+    "rag.web.search.brave_search_api_key",
+    os.getenv("BRAVE_SEARCH_API_KEY", ""),
+)
+
+SERPSTACK_API_KEY = PersistentConfig(
+    "SERPSTACK_API_KEY",
+    "rag.web.search.serpstack_api_key",
+    os.getenv("SERPSTACK_API_KEY", ""),
+)
+
+SERPSTACK_HTTPS = PersistentConfig(
+    "SERPSTACK_HTTPS",
+    "rag.web.search.serpstack_https",
+    os.getenv("SERPSTACK_HTTPS", "True").lower() == "true",
+)
+
+SERPER_API_KEY = PersistentConfig(
+    "SERPER_API_KEY",
+    "rag.web.search.serper_api_key",
+    os.getenv("SERPER_API_KEY", ""),
+)
+
+SERPLY_API_KEY = PersistentConfig(
+    "SERPLY_API_KEY",
+    "rag.web.search.serply_api_key",
+    os.getenv("SERPLY_API_KEY", ""),
+)
+
+TAVILY_API_KEY = PersistentConfig(
+    "TAVILY_API_KEY",
+    "rag.web.search.tavily_api_key",
+    os.getenv("TAVILY_API_KEY", ""),
+)
+
+SEARCHAPI_API_KEY = PersistentConfig(
+    "SEARCHAPI_API_KEY",
+    "rag.web.search.searchapi_api_key",
+    os.getenv("SEARCHAPI_API_KEY", ""),
+)
+
+SEARCHAPI_ENGINE = PersistentConfig(
+    "SEARCHAPI_ENGINE",
+    "rag.web.search.searchapi_engine",
+    os.getenv("SEARCHAPI_ENGINE", ""),
+)
+
+RAG_WEB_SEARCH_RESULT_COUNT = PersistentConfig(
+    "RAG_WEB_SEARCH_RESULT_COUNT",
+    "rag.web.search.result_count",
+    int(os.getenv("RAG_WEB_SEARCH_RESULT_COUNT", "3")),
+)
+
+RAG_WEB_SEARCH_CONCURRENT_REQUESTS = PersistentConfig(
+    "RAG_WEB_SEARCH_CONCURRENT_REQUESTS",
+    "rag.web.search.concurrent_requests",
+    int(os.getenv("RAG_WEB_SEARCH_CONCURRENT_REQUESTS", "10")),
+)
+
+
+####################################
+# Images
+####################################
+
+IMAGE_GENERATION_ENGINE = PersistentConfig(
+    "IMAGE_GENERATION_ENGINE",
+    "image_generation.engine",
+    os.getenv("IMAGE_GENERATION_ENGINE", "openai"),
+)
+
+ENABLE_IMAGE_GENERATION = PersistentConfig(
+    "ENABLE_IMAGE_GENERATION",
+    "image_generation.enable",
+    os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true",
+)
+AUTOMATIC1111_BASE_URL = PersistentConfig(
+    "AUTOMATIC1111_BASE_URL",
+    "image_generation.automatic1111.base_url",
+    os.getenv("AUTOMATIC1111_BASE_URL", ""),
+)
+AUTOMATIC1111_API_AUTH = PersistentConfig(
+    "AUTOMATIC1111_API_AUTH",
+    "image_generation.automatic1111.api_auth",
+    os.getenv("AUTOMATIC1111_API_AUTH", ""),
+)
+
+AUTOMATIC1111_CFG_SCALE = PersistentConfig(
+    "AUTOMATIC1111_CFG_SCALE",
+    "image_generation.automatic1111.cfg_scale",
+    (
+        float(os.environ.get("AUTOMATIC1111_CFG_SCALE"))
+        if os.environ.get("AUTOMATIC1111_CFG_SCALE")
+        else None
+    ),
+)
+
+
+AUTOMATIC1111_SAMPLER = PersistentConfig(
+    "AUTOMATIC1111_SAMPLERE",
+    "image_generation.automatic1111.sampler",
+    (
+        os.environ.get("AUTOMATIC1111_SAMPLER")
+        if os.environ.get("AUTOMATIC1111_SAMPLER")
+        else None
+    ),
+)
+
+AUTOMATIC1111_SCHEDULER = PersistentConfig(
+    "AUTOMATIC1111_SCHEDULER",
+    "image_generation.automatic1111.scheduler",
+    (
+        os.environ.get("AUTOMATIC1111_SCHEDULER")
+        if os.environ.get("AUTOMATIC1111_SCHEDULER")
+        else None
+    ),
+)
+
+COMFYUI_BASE_URL = PersistentConfig(
+    "COMFYUI_BASE_URL",
+    "image_generation.comfyui.base_url",
+    os.getenv("COMFYUI_BASE_URL", ""),
+)
+
+COMFYUI_DEFAULT_WORKFLOW = """
+{
+  "3": {
+    "inputs": {
+      "seed": 0,
+      "steps": 20,
+      "cfg": 8,
+      "sampler_name": "euler",
+      "scheduler": "normal",
+      "denoise": 1,
+      "model": [
+        "4",
+        0
+      ],
+      "positive": [
+        "6",
+        0
+      ],
+      "negative": [
+        "7",
+        0
+      ],
+      "latent_image": [
+        "5",
+        0
+      ]
+    },
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "KSampler"
+    }
+  },
+  "4": {
+    "inputs": {
+      "ckpt_name": "model.safetensors"
+    },
+    "class_type": "CheckpointLoaderSimple",
+    "_meta": {
+      "title": "Load Checkpoint"
+    }
+  },
+  "5": {
+    "inputs": {
+      "width": 512,
+      "height": 512,
+      "batch_size": 1
+    },
+    "class_type": "EmptyLatentImage",
+    "_meta": {
+      "title": "Empty Latent Image"
+    }
+  },
+  "6": {
+    "inputs": {
+      "text": "Prompt",
+      "clip": [
+        "4",
+        1
+      ]
+    },
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "CLIP Text Encode (Prompt)"
+    }
+  },
+  "7": {
+    "inputs": {
+      "text": "",
+      "clip": [
+        "4",
+        1
+      ]
+    },
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "CLIP Text Encode (Prompt)"
+    }
+  },
+  "8": {
+    "inputs": {
+      "samples": [
+        "3",
+        0
+      ],
+      "vae": [
+        "4",
+        2
+      ]
+    },
+    "class_type": "VAEDecode",
+    "_meta": {
+      "title": "VAE Decode"
+    }
+  },
+  "9": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "8",
+        0
+      ]
+    },
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "Save Image"
+    }
+  }
+}
+"""
+
+
+COMFYUI_WORKFLOW = PersistentConfig(
+    "COMFYUI_WORKFLOW",
+    "image_generation.comfyui.workflow",
+    os.getenv("COMFYUI_WORKFLOW", COMFYUI_DEFAULT_WORKFLOW),
+)
+
+COMFYUI_WORKFLOW_NODES = PersistentConfig(
+    "COMFYUI_WORKFLOW",
+    "image_generation.comfyui.nodes",
+    [],
+)
+
+IMAGES_OPENAI_API_BASE_URL = PersistentConfig(
+    "IMAGES_OPENAI_API_BASE_URL",
+    "image_generation.openai.api_base_url",
+    os.getenv("IMAGES_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
+)
+IMAGES_OPENAI_API_KEY = PersistentConfig(
+    "IMAGES_OPENAI_API_KEY",
+    "image_generation.openai.api_key",
+    os.getenv("IMAGES_OPENAI_API_KEY", OPENAI_API_KEY),
+)
+
+IMAGE_SIZE = PersistentConfig(
+    "IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
+)
+
+IMAGE_STEPS = PersistentConfig(
+    "IMAGE_STEPS", "image_generation.steps", int(os.getenv("IMAGE_STEPS", 50))
+)
+
+IMAGE_GENERATION_MODEL = PersistentConfig(
+    "IMAGE_GENERATION_MODEL",
+    "image_generation.model",
+    os.getenv("IMAGE_GENERATION_MODEL", ""),
+)
+
+####################################
+# Audio
+####################################
+
+# Transcription
+WHISPER_MODEL = PersistentConfig(
+    "WHISPER_MODEL",
+    "audio.stt.whisper_model",
+    os.getenv("WHISPER_MODEL", "base"),
+)
+
+WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models")
+WHISPER_MODEL_AUTO_UPDATE = (
+    os.environ.get("WHISPER_MODEL_AUTO_UPDATE", "").lower() == "true"
+)
+
+
+AUDIO_STT_OPENAI_API_BASE_URL = PersistentConfig(
+    "AUDIO_STT_OPENAI_API_BASE_URL",
+    "audio.stt.openai.api_base_url",
+    os.getenv("AUDIO_STT_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
+)
+
+AUDIO_STT_OPENAI_API_KEY = PersistentConfig(
+    "AUDIO_STT_OPENAI_API_KEY",
+    "audio.stt.openai.api_key",
+    os.getenv("AUDIO_STT_OPENAI_API_KEY", OPENAI_API_KEY),
+)
+
+AUDIO_STT_ENGINE = PersistentConfig(
+    "AUDIO_STT_ENGINE",
+    "audio.stt.engine",
+    os.getenv("AUDIO_STT_ENGINE", ""),
+)
+
+AUDIO_STT_MODEL = PersistentConfig(
+    "AUDIO_STT_MODEL",
+    "audio.stt.model",
+    os.getenv("AUDIO_STT_MODEL", ""),
+)
+
+AUDIO_TTS_OPENAI_API_BASE_URL = PersistentConfig(
+    "AUDIO_TTS_OPENAI_API_BASE_URL",
+    "audio.tts.openai.api_base_url",
+    os.getenv("AUDIO_TTS_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
+)
+AUDIO_TTS_OPENAI_API_KEY = PersistentConfig(
+    "AUDIO_TTS_OPENAI_API_KEY",
+    "audio.tts.openai.api_key",
+    os.getenv("AUDIO_TTS_OPENAI_API_KEY", OPENAI_API_KEY),
+)
+
+AUDIO_TTS_API_KEY = PersistentConfig(
+    "AUDIO_TTS_API_KEY",
+    "audio.tts.api_key",
+    os.getenv("AUDIO_TTS_API_KEY", ""),
+)
+
+AUDIO_TTS_ENGINE = PersistentConfig(
+    "AUDIO_TTS_ENGINE",
+    "audio.tts.engine",
+    os.getenv("AUDIO_TTS_ENGINE", ""),
+)
+
+
+AUDIO_TTS_MODEL = PersistentConfig(
+    "AUDIO_TTS_MODEL",
+    "audio.tts.model",
+    os.getenv("AUDIO_TTS_MODEL", "tts-1"),  # OpenAI default model
+)
+
+AUDIO_TTS_VOICE = PersistentConfig(
+    "AUDIO_TTS_VOICE",
+    "audio.tts.voice",
+    os.getenv("AUDIO_TTS_VOICE", "alloy"),  # OpenAI default voice
+)
+
+AUDIO_TTS_SPLIT_ON = PersistentConfig(
+    "AUDIO_TTS_SPLIT_ON",
+    "audio.tts.split_on",
+    os.getenv("AUDIO_TTS_SPLIT_ON", "punctuation"),
+)
+
+AUDIO_TTS_AZURE_SPEECH_REGION = PersistentConfig(
+    "AUDIO_TTS_AZURE_SPEECH_REGION",
+    "audio.tts.azure.speech_region",
+    os.getenv("AUDIO_TTS_AZURE_SPEECH_REGION", "eastus"),
+)
+
+AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT = PersistentConfig(
+    "AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT",
+    "audio.tts.azure.speech_output_format",
+    os.getenv(
+        "AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT", "audio-24khz-160kbitrate-mono-mp3"
+    ),
+)
diff --git a/backend/open_webui/constants.py b/backend/open_webui/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6f33af4a3f3538367787b14f7d2f51e1a1733e1
--- /dev/null
+++ b/backend/open_webui/constants.py
@@ -0,0 +1,115 @@
+from enum import Enum
+
+
+class MESSAGES(str, Enum):
+    DEFAULT = lambda msg="": f"{msg if msg else ''}"
+    MODEL_ADDED = lambda model="": f"The model '{model}' has been added successfully."
+    MODEL_DELETED = (
+        lambda model="": f"The model '{model}' has been deleted successfully."
+    )
+
+
+class WEBHOOK_MESSAGES(str, Enum):
+    DEFAULT = lambda msg="": f"{msg if msg else ''}"
+    USER_SIGNUP = lambda username="": (
+        f"New user signed up: {username}" if username else "New user signed up"
+    )
+
+
+class ERROR_MESSAGES(str, Enum):
+    def __str__(self) -> str:
+        return super().__str__()
+
+    DEFAULT = (
+        lambda err="": f'{"Something went wrong :/" if err == "" else "[ERROR: " + str(err) + "]"}'
+    )
+    ENV_VAR_NOT_FOUND = "Required environment variable not found. Terminating now."
+    CREATE_USER_ERROR = "Oops! Something went wrong while creating your account. Please try again later. If the issue persists, contact support for assistance."
+    DELETE_USER_ERROR = "Oops! Something went wrong. We encountered an issue while trying to delete the user. Please give it another shot."
+    EMAIL_MISMATCH = "Uh-oh! This email does not match the email your provider is registered with. Please check your email and try again."
+    EMAIL_TAKEN = "Uh-oh! This email is already registered. Sign in with your existing account or choose another email to start anew."
+    USERNAME_TAKEN = (
+        "Uh-oh! This username is already registered. Please choose another username."
+    )
+    COMMAND_TAKEN = "Uh-oh! This command is already registered. Please choose another command string."
+    FILE_EXISTS = "Uh-oh! This file is already registered. Please choose another file."
+
+    ID_TAKEN = "Uh-oh! This id is already registered. Please choose another id string."
+    MODEL_ID_TAKEN = "Uh-oh! This model id is already registered. Please choose another model id string."
+    NAME_TAG_TAKEN = "Uh-oh! This name tag is already registered. Please choose another name tag string."
+
+    INVALID_TOKEN = (
+        "Your session has expired or the token is invalid. Please sign in again."
+    )
+    INVALID_CRED = "The email or password provided is incorrect. Please check for typos and try logging in again."
+    INVALID_EMAIL_FORMAT = "The email format you entered is invalid. Please double-check and make sure you're using a valid email address (e.g., yourname@example.com)."
+    INVALID_PASSWORD = (
+        "The password provided is incorrect. Please check for typos and try again."
+    )
+    INVALID_TRUSTED_HEADER = "Your provider has not provided a trusted header. Please contact your administrator for assistance."
+
+    EXISTING_USERS = "You can't turn off authentication because there are existing users. If you want to disable WEBUI_AUTH, make sure your web interface doesn't have any existing users and is a fresh installation."
+
+    UNAUTHORIZED = "401 Unauthorized"
+    ACCESS_PROHIBITED = "You do not have permission to access this resource. Please contact your administrator for assistance."
+    ACTION_PROHIBITED = (
+        "The requested action has been restricted as a security measure."
+    )
+
+    FILE_NOT_SENT = "FILE_NOT_SENT"
+    FILE_NOT_SUPPORTED = "Oops! It seems like the file format you're trying to upload is not supported. Please upload a file with a supported format (e.g., JPG, PNG, PDF, TXT) and try again."
+
+    NOT_FOUND = "We could not find what you're looking for :/"
+    USER_NOT_FOUND = "We could not find what you're looking for :/"
+    API_KEY_NOT_FOUND = "Oops! It looks like there's a hiccup. The API key is missing. Please make sure to provide a valid API key to access this feature."
+
+    MALICIOUS = "Unusual activities detected, please try again in a few minutes."
+
+    PANDOC_NOT_INSTALLED = "Pandoc is not installed on the server. Please contact your administrator for assistance."
+    INCORRECT_FORMAT = (
+        lambda err="": f"Invalid format. Please use the correct format{err}"
+    )
+    RATE_LIMIT_EXCEEDED = "API rate limit exceeded"
+
+    MODEL_NOT_FOUND = lambda name="": f"Model '{name}' was not found"
+    OPENAI_NOT_FOUND = lambda name="": "OpenAI API was not found"
+    OLLAMA_NOT_FOUND = "WebUI could not connect to Ollama"
+    CREATE_API_KEY_ERROR = "Oops! Something went wrong while creating your API key. Please try again later. If the issue persists, contact support for assistance."
+
+    EMPTY_CONTENT = "The content provided is empty. Please ensure that there is text or data present before proceeding."
+
+    DB_NOT_SQLITE = "This feature is only available when running with SQLite databases."
+
+    INVALID_URL = (
+        "Oops! The URL you provided is invalid. Please double-check and try again."
+    )
+
+    WEB_SEARCH_ERROR = (
+        lambda err="": f"{err if err else 'Oops! Something went wrong while searching the web.'}"
+    )
+
+    OLLAMA_API_DISABLED = (
+        "The Ollama API is disabled. Please enable it to use this feature."
+    )
+
+    FILE_TOO_LARGE = (
+        lambda size="": f"Oops! The file you're trying to upload is too large. Please upload a file that is less than {size}."
+    )
+
+    DUPLICATE_CONTENT = (
+        "Duplicate content detected. Please provide unique content to proceed."
+    )
+    FILE_NOT_PROCESSED = "Extracted content is not available for this file. Please ensure that the file is processed before proceeding."
+
+
+class TASKS(str, Enum):
+    def __str__(self) -> str:
+        return super().__str__()
+
+    DEFAULT = lambda task="": f"{task if task else 'generation'}"
+    TITLE_GENERATION = "title_generation"
+    TAGS_GENERATION = "tags_generation"
+    EMOJI_GENERATION = "emoji_generation"
+    QUERY_GENERATION = "query_generation"
+    FUNCTION_CALLING = "function_calling"
+    MOA_RESPONSE_GENERATION = "moa_response_generation"
diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b61e1a894dc086373e179286fc57930de06509f
--- /dev/null
+++ b/backend/open_webui/env.py
@@ -0,0 +1,384 @@
+import importlib.metadata
+import json
+import logging
+import os
+import pkgutil
+import sys
+import shutil
+from pathlib import Path
+
+import markdown
+from bs4 import BeautifulSoup
+from open_webui.constants import ERROR_MESSAGES
+
+####################################
+# Load .env file
+####################################
+
+OPEN_WEBUI_DIR = Path(__file__).parent  # the path containing this file
+print(OPEN_WEBUI_DIR)
+
+BACKEND_DIR = OPEN_WEBUI_DIR.parent  # the path containing this file
+BASE_DIR = BACKEND_DIR.parent  # the path containing the backend/
+
+print(BACKEND_DIR)
+print(BASE_DIR)
+
+try:
+    from dotenv import find_dotenv, load_dotenv
+
+    load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
+except ImportError:
+    print("dotenv not installed, skipping...")
+
+DOCKER = os.environ.get("DOCKER", "False").lower() == "true"
+
+# device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
+USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")
+
+if USE_CUDA.lower() == "true":
+    try:
+        import torch
+
+        assert torch.cuda.is_available(), "CUDA not available"
+        DEVICE_TYPE = "cuda"
+    except Exception as e:
+        cuda_error = (
+            "Error when testing CUDA but USE_CUDA_DOCKER is true. "
+            f"Resetting USE_CUDA_DOCKER to false: {e}"
+        )
+        os.environ["USE_CUDA_DOCKER"] = "false"
+        USE_CUDA = "false"
+        DEVICE_TYPE = "cpu"
+else:
+    DEVICE_TYPE = "cpu"
+
+
+####################################
+# LOGGING
+####################################
+
+log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
+
+GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
+if GLOBAL_LOG_LEVEL in log_levels:
+    logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
+else:
+    GLOBAL_LOG_LEVEL = "INFO"
+
+log = logging.getLogger(__name__)
+log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
+
+if "cuda_error" in locals():
+    log.exception(cuda_error)
+
+log_sources = [
+    "AUDIO",
+    "COMFYUI",
+    "CONFIG",
+    "DB",
+    "IMAGES",
+    "MAIN",
+    "MODELS",
+    "OLLAMA",
+    "OPENAI",
+    "RAG",
+    "WEBHOOK",
+    "SOCKET",
+]
+
+SRC_LOG_LEVELS = {}
+
+for source in log_sources:
+    log_env_var = source + "_LOG_LEVEL"
+    SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
+    if SRC_LOG_LEVELS[source] not in log_levels:
+        SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
+    log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
+
+log.setLevel(SRC_LOG_LEVELS["CONFIG"])
+
+
+WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
+if WEBUI_NAME != "Open WebUI":
+    WEBUI_NAME += " (Open WebUI)"
+
+WEBUI_URL = os.environ.get("WEBUI_URL", "http://localhost:3000")
+
+WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
+
+
+####################################
+# ENV (dev,test,prod)
+####################################
+
+ENV = os.environ.get("ENV", "dev")
+
+FROM_INIT_PY = os.environ.get("FROM_INIT_PY", "False").lower() == "true"
+
+if FROM_INIT_PY:
+    PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
+else:
+    try:
+        PACKAGE_DATA = json.loads((BASE_DIR / "package.json").read_text())
+    except Exception:
+        PACKAGE_DATA = {"version": "0.0.0"}
+
+
+VERSION = PACKAGE_DATA["version"]
+
+
+# Function to parse each section
+def parse_section(section):
+    items = []
+    for li in section.find_all("li"):
+        # Extract raw HTML string
+        raw_html = str(li)
+
+        # Extract text without HTML tags
+        text = li.get_text(separator=" ", strip=True)
+
+        # Split into title and content
+        parts = text.split(": ", 1)
+        title = parts[0].strip() if len(parts) > 1 else ""
+        content = parts[1].strip() if len(parts) > 1 else text
+
+        items.append({"title": title, "content": content, "raw": raw_html})
+    return items
+
+
+try:
+    changelog_path = BASE_DIR / "CHANGELOG.md"
+    with open(str(changelog_path.absolute()), "r", encoding="utf8") as file:
+        changelog_content = file.read()
+
+except Exception:
+    changelog_content = (pkgutil.get_data("open_webui", "CHANGELOG.md") or b"").decode()
+
+
+# Convert markdown content to HTML
+html_content = markdown.markdown(changelog_content)
+
+# Parse the HTML content
+soup = BeautifulSoup(html_content, "html.parser")
+
+# Initialize JSON structure
+changelog_json = {}
+
+# Iterate over each version
+for version in soup.find_all("h2"):
+    version_number = version.get_text().strip().split(" - ")[0][1:-1]  # Remove brackets
+    date = version.get_text().strip().split(" - ")[1]
+
+    version_data = {"date": date}
+
+    # Find the next sibling that is a h3 tag (section title)
+    current = version.find_next_sibling()
+
+    while current and current.name != "h2":
+        if current.name == "h3":
+            section_title = current.get_text().lower()  # e.g., "added", "fixed"
+            section_items = parse_section(current.find_next_sibling("ul"))
+            version_data[section_title] = section_items
+
+        # Move to the next element
+        current = current.find_next_sibling()
+
+    changelog_json[version_number] = version_data
+
+
+CHANGELOG = changelog_json
+
+####################################
+# SAFE_MODE
+####################################
+
+SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true"
+
+####################################
+# WEBUI_BUILD_HASH
+####################################
+
+WEBUI_BUILD_HASH = os.environ.get("WEBUI_BUILD_HASH", "dev-build")
+
+####################################
+# DATA/FRONTEND BUILD DIR
+####################################
+
+DATA_DIR = Path(os.getenv("DATA_DIR", BACKEND_DIR / "data")).resolve()
+
+if FROM_INIT_PY:
+    NEW_DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data")).resolve()
+    NEW_DATA_DIR.mkdir(parents=True, exist_ok=True)
+
+    # Check if the data directory exists in the package directory
+    if DATA_DIR.exists() and DATA_DIR != NEW_DATA_DIR:
+        log.info(f"Moving {DATA_DIR} to {NEW_DATA_DIR}")
+        for item in DATA_DIR.iterdir():
+            dest = NEW_DATA_DIR / item.name
+            if item.is_dir():
+                shutil.copytree(item, dest, dirs_exist_ok=True)
+            else:
+                shutil.copy2(item, dest)
+
+        # Zip the data directory
+        shutil.make_archive(DATA_DIR.parent / "open_webui_data", "zip", DATA_DIR)
+
+        # Remove the old data directory
+        shutil.rmtree(DATA_DIR)
+
+    DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data"))
+
+
+STATIC_DIR = Path(os.getenv("STATIC_DIR", OPEN_WEBUI_DIR / "static"))
+
+FONTS_DIR = Path(os.getenv("FONTS_DIR", OPEN_WEBUI_DIR / "static" / "fonts"))
+
+FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
+
+if FROM_INIT_PY:
+    FRONTEND_BUILD_DIR = Path(
+        os.getenv("FRONTEND_BUILD_DIR", OPEN_WEBUI_DIR / "frontend")
+    ).resolve()
+
+
+####################################
+# Database
+####################################
+
+# Check if the file exists
+if os.path.exists(f"{DATA_DIR}/ollama.db"):
+    # Rename the file
+    os.rename(f"{DATA_DIR}/ollama.db", f"{DATA_DIR}/webui.db")
+    log.info("Database migrated from Ollama-WebUI successfully.")
+else:
+    pass
+
+DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")
+
+# Replace the postgres:// with postgresql://
+if "postgres://" in DATABASE_URL:
+    DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")
+
+DATABASE_POOL_SIZE = os.environ.get("DATABASE_POOL_SIZE", 0)
+
+if DATABASE_POOL_SIZE == "":
+    DATABASE_POOL_SIZE = 0
+else:
+    try:
+        DATABASE_POOL_SIZE = int(DATABASE_POOL_SIZE)
+    except Exception:
+        DATABASE_POOL_SIZE = 0
+
+DATABASE_POOL_MAX_OVERFLOW = os.environ.get("DATABASE_POOL_MAX_OVERFLOW", 0)
+
+if DATABASE_POOL_MAX_OVERFLOW == "":
+    DATABASE_POOL_MAX_OVERFLOW = 0
+else:
+    try:
+        DATABASE_POOL_MAX_OVERFLOW = int(DATABASE_POOL_MAX_OVERFLOW)
+    except Exception:
+        DATABASE_POOL_MAX_OVERFLOW = 0
+
+DATABASE_POOL_TIMEOUT = os.environ.get("DATABASE_POOL_TIMEOUT", 30)
+
+if DATABASE_POOL_TIMEOUT == "":
+    DATABASE_POOL_TIMEOUT = 30
+else:
+    try:
+        DATABASE_POOL_TIMEOUT = int(DATABASE_POOL_TIMEOUT)
+    except Exception:
+        DATABASE_POOL_TIMEOUT = 30
+
+DATABASE_POOL_RECYCLE = os.environ.get("DATABASE_POOL_RECYCLE", 3600)
+
+if DATABASE_POOL_RECYCLE == "":
+    DATABASE_POOL_RECYCLE = 3600
+else:
+    try:
+        DATABASE_POOL_RECYCLE = int(DATABASE_POOL_RECYCLE)
+    except Exception:
+        DATABASE_POOL_RECYCLE = 3600
+
+RESET_CONFIG_ON_START = (
+    os.environ.get("RESET_CONFIG_ON_START", "False").lower() == "true"
+)
+
+####################################
+# REDIS
+####################################
+
+REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379/0")
+
+####################################
+# WEBUI_AUTH (Required for security)
+####################################
+
+WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
+WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
+    "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
+)
+WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
+
+
+####################################
+# WEBUI_SECRET_KEY
+####################################
+
+WEBUI_SECRET_KEY = os.environ.get(
+    "WEBUI_SECRET_KEY",
+    os.environ.get(
+        "WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
+    ),  # DEPRECATED: remove at next major version
+)
+
+WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get(
+    "WEBUI_SESSION_COOKIE_SAME_SITE",
+    os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax"),
+)
+
+WEBUI_SESSION_COOKIE_SECURE = os.environ.get(
+    "WEBUI_SESSION_COOKIE_SECURE",
+    os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true",
+)
+
+if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
+    raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
+
+ENABLE_WEBSOCKET_SUPPORT = (
+    os.environ.get("ENABLE_WEBSOCKET_SUPPORT", "True").lower() == "true"
+)
+
+WEBSOCKET_MANAGER = os.environ.get("WEBSOCKET_MANAGER", "")
+
+WEBSOCKET_REDIS_URL = os.environ.get("WEBSOCKET_REDIS_URL", REDIS_URL)
+
+AIOHTTP_CLIENT_TIMEOUT = os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "")
+
+if AIOHTTP_CLIENT_TIMEOUT == "":
+    AIOHTTP_CLIENT_TIMEOUT = None
+else:
+    try:
+        AIOHTTP_CLIENT_TIMEOUT = int(AIOHTTP_CLIENT_TIMEOUT)
+    except Exception:
+        AIOHTTP_CLIENT_TIMEOUT = 300
+
+AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = os.environ.get(
+    "AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST", "3"
+)
+
+if AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST == "":
+    AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = None
+else:
+    try:
+        AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = int(
+            AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST
+        )
+    except Exception:
+        AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST = 3
+
+####################################
+# OFFLINE_MODE
+####################################
+
+OFFLINE_MODE = os.environ.get("OFFLINE_MODE", "false").lower() == "true"
diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b37e37b1607db55ade025bd529b51592bf01569
--- /dev/null
+++ b/backend/open_webui/main.py
@@ -0,0 +1,2455 @@
+import asyncio
+import inspect
+import json
+import logging
+import mimetypes
+import os
+import shutil
+import sys
+import time
+import random
+from contextlib import asynccontextmanager
+from typing import Optional
+
+import aiohttp
+import requests
+from fastapi import (
+    Depends,
+    FastAPI,
+    File,
+    Form,
+    HTTPException,
+    Request,
+    UploadFile,
+    status,
+)
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import JSONResponse, RedirectResponse
+from fastapi.staticfiles import StaticFiles
+from pydantic import BaseModel
+from sqlalchemy import text
+from starlette.exceptions import HTTPException as StarletteHTTPException
+from starlette.middleware.base import BaseHTTPMiddleware
+from starlette.middleware.sessions import SessionMiddleware
+from starlette.responses import Response, StreamingResponse
+
+from open_webui.apps.audio.main import app as audio_app
+from open_webui.apps.images.main import app as images_app
+from open_webui.apps.ollama.main import (
+    app as ollama_app,
+    get_all_models as get_ollama_models,
+    generate_chat_completion as generate_ollama_chat_completion,
+    GenerateChatCompletionForm,
+)
+from open_webui.apps.openai.main import (
+    app as openai_app,
+    generate_chat_completion as generate_openai_chat_completion,
+    get_all_models as get_openai_models,
+)
+from open_webui.apps.retrieval.main import app as retrieval_app
+from open_webui.apps.retrieval.utils import get_rag_context, rag_template
+from open_webui.apps.socket.main import (
+    app as socket_app,
+    periodic_usage_pool_cleanup,
+    get_event_call,
+    get_event_emitter,
+)
+from open_webui.apps.webui.internal.db import Session
+from open_webui.apps.webui.main import (
+    app as webui_app,
+    generate_function_chat_completion,
+    get_all_models as get_open_webui_models,
+)
+from open_webui.apps.webui.models.functions import Functions
+from open_webui.apps.webui.models.models import Models
+from open_webui.apps.webui.models.users import UserModel, Users
+from open_webui.apps.webui.utils import load_function_module_by_id
+from open_webui.config import (
+    CACHE_DIR,
+    CORS_ALLOW_ORIGIN,
+    DEFAULT_LOCALE,
+    ENABLE_ADMIN_CHAT_ACCESS,
+    ENABLE_ADMIN_EXPORT,
+    ENABLE_MODEL_FILTER,
+    ENABLE_OLLAMA_API,
+    ENABLE_OPENAI_API,
+    ENV,
+    FRONTEND_BUILD_DIR,
+    MODEL_FILTER_LIST,
+    OAUTH_PROVIDERS,
+    ENABLE_SEARCH_QUERY,
+    SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE,
+    STATIC_DIR,
+    TASK_MODEL,
+    TASK_MODEL_EXTERNAL,
+    TITLE_GENERATION_PROMPT_TEMPLATE,
+    TAGS_GENERATION_PROMPT_TEMPLATE,
+    TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
+    WEBHOOK_URL,
+    WEBUI_AUTH,
+    WEBUI_NAME,
+    AppConfig,
+    reset_config,
+)
+from open_webui.constants import TASKS
+from open_webui.env import (
+    CHANGELOG,
+    GLOBAL_LOG_LEVEL,
+    SAFE_MODE,
+    SRC_LOG_LEVELS,
+    VERSION,
+    WEBUI_BUILD_HASH,
+    WEBUI_SECRET_KEY,
+    WEBUI_SESSION_COOKIE_SAME_SITE,
+    WEBUI_SESSION_COOKIE_SECURE,
+    WEBUI_URL,
+    RESET_CONFIG_ON_START,
+    OFFLINE_MODE,
+)
+from open_webui.utils.misc import (
+    add_or_update_system_message,
+    get_last_user_message,
+    prepend_to_first_user_message_content,
+)
+from open_webui.utils.oauth import oauth_manager
+from open_webui.utils.payload import convert_payload_openai_to_ollama
+from open_webui.utils.response import (
+    convert_response_ollama_to_openai,
+    convert_streaming_response_ollama_to_openai,
+)
+from open_webui.utils.security_headers import SecurityHeadersMiddleware
+from open_webui.utils.task import (
+    moa_response_generation_template,
+    tags_generation_template,
+    search_query_generation_template,
+    emoji_generation_template,
+    title_generation_template,
+    tools_function_calling_generation_template,
+)
+from open_webui.utils.tools import get_tools
+from open_webui.utils.utils import (
+    decode_token,
+    get_admin_user,
+    get_current_user,
+    get_http_authorization_cred,
+    get_verified_user,
+)
+
+if SAFE_MODE:
+    print("SAFE MODE ENABLED")
+    Functions.deactivate_all_functions()
+
+logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MAIN"])
+
+
+class SPAStaticFiles(StaticFiles):
+    async def get_response(self, path: str, scope):
+        try:
+            return await super().get_response(path, scope)
+        except (HTTPException, StarletteHTTPException) as ex:
+            if ex.status_code == 404:
+                return await super().get_response("index.html", scope)
+            else:
+                raise ex
+
+
+print(
+    rf"""
+  ___                    __        __   _     _   _ ___
+ / _ \ _ __   ___ _ __   \ \      / /__| |__ | | | |_ _|
+| | | | '_ \ / _ \ '_ \   \ \ /\ / / _ \ '_ \| | | || |
+| |_| | |_) |  __/ | | |   \ V  V /  __/ |_) | |_| || |
+ \___/| .__/ \___|_| |_|    \_/\_/ \___|_.__/ \___/|___|
+      |_|
+
+
+v{VERSION} - building the best open-source AI user interface.
+{f"Commit: {WEBUI_BUILD_HASH}" if WEBUI_BUILD_HASH != "dev-build" else ""}
+https://github.com/open-webui/open-webui
+"""
+)
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+    if RESET_CONFIG_ON_START:
+        reset_config()
+
+    asyncio.create_task(periodic_usage_pool_cleanup())
+    yield
+
+
+app = FastAPI(
+    docs_url="/docs" if ENV == "dev" else None, openapi_url="/openapi.json" if ENV == "dev" else None, redoc_url=None, lifespan=lifespan
+)
+
+app.state.config = AppConfig()
+
+app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API
+app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API
+
+app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
+app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
+
+app.state.config.WEBHOOK_URL = WEBHOOK_URL
+
+app.state.config.TASK_MODEL = TASK_MODEL
+app.state.config.TASK_MODEL_EXTERNAL = TASK_MODEL_EXTERNAL
+app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE = TITLE_GENERATION_PROMPT_TEMPLATE
+app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE = TAGS_GENERATION_PROMPT_TEMPLATE
+app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE = (
+    SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE
+)
+app.state.config.ENABLE_SEARCH_QUERY = ENABLE_SEARCH_QUERY
+app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = (
+    TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
+)
+
+app.state.MODELS = {}
+
+
+##################################
+#
+# ChatCompletion Middleware
+#
+##################################
+
+
+def get_task_model_id(default_model_id):
+    # Set the task model
+    task_model_id = default_model_id
+    # Check if the user has a custom task model and use that model
+    if app.state.MODELS[task_model_id]["owned_by"] == "ollama":
+        if (
+            app.state.config.TASK_MODEL
+            and app.state.config.TASK_MODEL in app.state.MODELS
+        ):
+            task_model_id = app.state.config.TASK_MODEL
+    else:
+        if (
+            app.state.config.TASK_MODEL_EXTERNAL
+            and app.state.config.TASK_MODEL_EXTERNAL in app.state.MODELS
+        ):
+            task_model_id = app.state.config.TASK_MODEL_EXTERNAL
+
+    return task_model_id
+
+
+def get_filter_function_ids(model):
+    def get_priority(function_id):
+        function = Functions.get_function_by_id(function_id)
+        if function is not None and hasattr(function, "valves"):
+            # TODO: Fix FunctionModel
+            return (function.valves if function.valves else {}).get("priority", 0)
+        return 0
+
+    filter_ids = [function.id for function in Functions.get_global_filter_functions()]
+    if "info" in model and "meta" in model["info"]:
+        filter_ids.extend(model["info"]["meta"].get("filterIds", []))
+        filter_ids = list(set(filter_ids))
+
+    enabled_filter_ids = [
+        function.id
+        for function in Functions.get_functions_by_type("filter", active_only=True)
+    ]
+
+    filter_ids = [
+        filter_id for filter_id in filter_ids if filter_id in enabled_filter_ids
+    ]
+
+    filter_ids.sort(key=get_priority)
+    return filter_ids
+
+
+async def chat_completion_filter_functions_handler(body, model, extra_params):
+    skip_files = None
+
+    filter_ids = get_filter_function_ids(model)
+    for filter_id in filter_ids:
+        filter = Functions.get_function_by_id(filter_id)
+        if not filter:
+            continue
+
+        if filter_id in webui_app.state.FUNCTIONS:
+            function_module = webui_app.state.FUNCTIONS[filter_id]
+        else:
+            function_module, _, _ = load_function_module_by_id(filter_id)
+            webui_app.state.FUNCTIONS[filter_id] = function_module
+
+        # Check if the function has a file_handler variable
+        if hasattr(function_module, "file_handler"):
+            skip_files = function_module.file_handler
+
+        if hasattr(function_module, "valves") and hasattr(function_module, "Valves"):
+            valves = Functions.get_function_valves_by_id(filter_id)
+            function_module.valves = function_module.Valves(
+                **(valves if valves else {})
+            )
+
+        if not hasattr(function_module, "inlet"):
+            continue
+
+        try:
+            inlet = function_module.inlet
+
+            # Get the signature of the function
+            sig = inspect.signature(inlet)
+            params = {"body": body} | {
+                k: v
+                for k, v in {
+                    **extra_params,
+                    "__model__": model,
+                    "__id__": filter_id,
+                }.items()
+                if k in sig.parameters
+            }
+
+            if "__user__" in params and hasattr(function_module, "UserValves"):
+                try:
+                    params["__user__"]["valves"] = function_module.UserValves(
+                        **Functions.get_user_valves_by_id_and_user_id(
+                            filter_id, params["__user__"]["id"]
+                        )
+                    )
+                except Exception as e:
+                    print(e)
+
+            if inspect.iscoroutinefunction(inlet):
+                body = await inlet(**params)
+            else:
+                body = inlet(**params)
+
+        except Exception as e:
+            print(f"Error: {e}")
+            raise e
+
+    if skip_files and "files" in body.get("metadata", {}):
+        del body["metadata"]["files"]
+
+    return body, {}
+
+
+def get_tools_function_calling_payload(messages, task_model_id, content):
+    user_message = get_last_user_message(messages)
+    history = "\n".join(
+        f"{message['role'].upper()}: \"\"\"{message['content']}\"\"\""
+        for message in messages[::-1][:4]
+    )
+
+    prompt = f"History:\n{history}\nQuery: {user_message}"
+
+    return {
+        "model": task_model_id,
+        "messages": [
+            {"role": "system", "content": content},
+            {"role": "user", "content": f"Query: {prompt}"},
+        ],
+        "stream": False,
+        "metadata": {"task": str(TASKS.FUNCTION_CALLING)},
+    }
+
+
+async def get_content_from_response(response) -> Optional[str]:
+    content = None
+    if hasattr(response, "body_iterator"):
+        async for chunk in response.body_iterator:
+            data = json.loads(chunk.decode("utf-8"))
+            content = data["choices"][0]["message"]["content"]
+
+        # Cleanup any remaining background tasks if necessary
+        if response.background is not None:
+            await response.background()
+    else:
+        content = response["choices"][0]["message"]["content"]
+    return content
+
+
+async def chat_completion_tools_handler(
+    body: dict, user: UserModel, extra_params: dict
+) -> tuple[dict, dict]:
+    # If tool_ids field is present, call the functions
+    metadata = body.get("metadata", {})
+
+    tool_ids = metadata.get("tool_ids", None)
+    log.debug(f"{tool_ids=}")
+    if not tool_ids:
+        return body, {}
+
+    skip_files = False
+    contexts = []
+    citations = []
+
+    task_model_id = get_task_model_id(body["model"])
+    tools = get_tools(
+        webui_app,
+        tool_ids,
+        user,
+        {
+            **extra_params,
+            "__model__": app.state.MODELS[task_model_id],
+            "__messages__": body["messages"],
+            "__files__": metadata.get("files", []),
+        },
+    )
+    log.info(f"{tools=}")
+
+    specs = [tool["spec"] for tool in tools.values()]
+    tools_specs = json.dumps(specs)
+
+    if app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE != "":
+        template = app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
+    else:
+        template = """Available Tools: {{TOOLS}}\nReturn an empty string if no tools match the query. If a function tool matches, construct and return a JSON object in the format {\"name\": \"functionName\", \"parameters\": {\"requiredFunctionParamKey\": \"requiredFunctionParamValue\"}} using the appropriate tool and its parameters. Only return the object and limit the response to the JSON object without additional text."""
+
+    tools_function_calling_prompt = tools_function_calling_generation_template(
+        template, tools_specs
+    )
+    log.info(f"{tools_function_calling_prompt=}")
+    payload = get_tools_function_calling_payload(
+        body["messages"], task_model_id, tools_function_calling_prompt
+    )
+
+    try:
+        payload = filter_pipeline(payload, user)
+    except Exception as e:
+        raise e
+
+    try:
+        response = await generate_chat_completions(form_data=payload, user=user)
+        log.debug(f"{response=}")
+        content = await get_content_from_response(response)
+        log.debug(f"{content=}")
+
+        if not content:
+            return body, {}
+
+        try:
+            content = content[content.find("{") : content.rfind("}") + 1]
+            if not content:
+                raise Exception("No JSON object found in the response")
+
+            result = json.loads(content)
+
+            tool_function_name = result.get("name", None)
+            if tool_function_name not in tools:
+                return body, {}
+
+            tool_function_params = result.get("parameters", {})
+
+            try:
+                required_params = (
+                    tools[tool_function_name]
+                    .get("spec", {})
+                    .get("parameters", {})
+                    .get("required", [])
+                )
+                tool_function = tools[tool_function_name]["callable"]
+                tool_function_params = {
+                    k: v
+                    for k, v in tool_function_params.items()
+                    if k in required_params
+                }
+                tool_output = await tool_function(**tool_function_params)
+
+            except Exception as e:
+                tool_output = str(e)
+
+            if tools[tool_function_name]["citation"]:
+                citations.append(
+                    {
+                        "source": {
+                            "name": f"TOOL:{tools[tool_function_name]['toolkit_id']}/{tool_function_name}"
+                        },
+                        "document": [tool_output],
+                        "metadata": [{"source": tool_function_name}],
+                    }
+                )
+            if tools[tool_function_name]["file_handler"]:
+                skip_files = True
+
+            if isinstance(tool_output, str):
+                contexts.append(tool_output)
+        except Exception as e:
+            log.exception(f"Error: {e}")
+            content = None
+    except Exception as e:
+        log.exception(f"Error: {e}")
+        content = None
+
+    log.debug(f"tool_contexts: {contexts}")
+
+    if skip_files and "files" in body.get("metadata", {}):
+        del body["metadata"]["files"]
+
+    return body, {"contexts": contexts, "citations": citations}
+
+
+async def chat_completion_files_handler(body) -> tuple[dict, dict[str, list]]:
+    contexts = []
+    citations = []
+
+    if files := body.get("metadata", {}).get("files", None):
+        contexts, citations = get_rag_context(
+            files=files,
+            messages=body["messages"],
+            embedding_function=retrieval_app.state.EMBEDDING_FUNCTION,
+            k=retrieval_app.state.config.TOP_K,
+            reranking_function=retrieval_app.state.sentence_transformer_rf,
+            r=retrieval_app.state.config.RELEVANCE_THRESHOLD,
+            hybrid_search=retrieval_app.state.config.ENABLE_RAG_HYBRID_SEARCH,
+        )
+
+        log.debug(f"rag_contexts: {contexts}, citations: {citations}")
+
+    return body, {"contexts": contexts, "citations": citations}
+
+
+def is_chat_completion_request(request):
+    return request.method == "POST" and any(
+        endpoint in request.url.path
+        for endpoint in ["/ollama/api/chat", "/chat/completions"]
+    )
+
+
+async def get_body_and_model_and_user(request):
+    # Read the original request body
+    body = await request.body()
+    body_str = body.decode("utf-8")
+    body = json.loads(body_str) if body_str else {}
+
+    model_id = body["model"]
+    if model_id not in app.state.MODELS:
+        raise Exception("Model not found")
+    model = app.state.MODELS[model_id]
+
+    user = get_current_user(
+        request,
+        get_http_authorization_cred(request.headers.get("Authorization")),
+    )
+
+    return body, model, user
+
+
+class ChatCompletionMiddleware(BaseHTTPMiddleware):
+    async def dispatch(self, request: Request, call_next):
+        if not is_chat_completion_request(request):
+            return await call_next(request)
+        log.debug(f"request.url.path: {request.url.path}")
+
+        try:
+            body, model, user = await get_body_and_model_and_user(request)
+        except Exception as e:
+            return JSONResponse(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                content={"detail": str(e)},
+            )
+
+        metadata = {
+            "chat_id": body.pop("chat_id", None),
+            "message_id": body.pop("id", None),
+            "session_id": body.pop("session_id", None),
+            "tool_ids": body.get("tool_ids", None),
+            "files": body.get("files", None),
+        }
+        body["metadata"] = metadata
+
+        extra_params = {
+            "__event_emitter__": get_event_emitter(metadata),
+            "__event_call__": get_event_call(metadata),
+            "__user__": {
+                "id": user.id,
+                "email": user.email,
+                "name": user.name,
+                "role": user.role,
+            },
+        }
+
+        # Initialize data_items to store additional data to be sent to the client
+        # Initialize contexts and citation
+        data_items = []
+        contexts = []
+        citations = []
+
+        try:
+            body, flags = await chat_completion_filter_functions_handler(
+                body, model, extra_params
+            )
+        except Exception as e:
+            return JSONResponse(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                content={"detail": str(e)},
+            )
+
+        metadata = {
+            **metadata,
+            "tool_ids": body.pop("tool_ids", None),
+            "files": body.pop("files", None),
+        }
+        body["metadata"] = metadata
+
+        try:
+            body, flags = await chat_completion_tools_handler(body, user, extra_params)
+            contexts.extend(flags.get("contexts", []))
+            citations.extend(flags.get("citations", []))
+        except Exception as e:
+            log.exception(e)
+
+        try:
+            body, flags = await chat_completion_files_handler(body)
+            contexts.extend(flags.get("contexts", []))
+            citations.extend(flags.get("citations", []))
+        except Exception as e:
+            log.exception(e)
+
+        # If context is not empty, insert it into the messages
+        if len(contexts) > 0:
+            context_string = "/n".join(contexts).strip()
+            prompt = get_last_user_message(body["messages"])
+
+            if prompt is None:
+                raise Exception("No user message found")
+            if (
+                retrieval_app.state.config.RELEVANCE_THRESHOLD == 0
+                and context_string.strip() == ""
+            ):
+                log.debug(
+                    f"With a 0 relevancy threshold for RAG, the context cannot be empty"
+                )
+
+            # Workaround for Ollama 2.0+ system prompt issue
+            # TODO: replace with add_or_update_system_message
+            if model["owned_by"] == "ollama":
+                body["messages"] = prepend_to_first_user_message_content(
+                    rag_template(
+                        retrieval_app.state.config.RAG_TEMPLATE, context_string, prompt
+                    ),
+                    body["messages"],
+                )
+            else:
+                body["messages"] = add_or_update_system_message(
+                    rag_template(
+                        retrieval_app.state.config.RAG_TEMPLATE, context_string, prompt
+                    ),
+                    body["messages"],
+                )
+
+        # If there are citations, add them to the data_items
+        if len(citations) > 0:
+            data_items.append({"citations": citations})
+
+        modified_body_bytes = json.dumps(body).encode("utf-8")
+        # Replace the request body with the modified one
+        request._body = modified_body_bytes
+        # Set custom header to ensure content-length matches new body length
+        request.headers.__dict__["_list"] = [
+            (b"content-length", str(len(modified_body_bytes)).encode("utf-8")),
+            *[(k, v) for k, v in request.headers.raw if k.lower() != b"content-length"],
+        ]
+
+        response = await call_next(request)
+        if not isinstance(response, StreamingResponse):
+            return response
+
+        content_type = response.headers["Content-Type"]
+        is_openai = "text/event-stream" in content_type
+        is_ollama = "application/x-ndjson" in content_type
+        if not is_openai and not is_ollama:
+            return response
+
+        def wrap_item(item):
+            return f"data: {item}\n\n" if is_openai else f"{item}\n"
+
+        async def stream_wrapper(original_generator, data_items):
+            for item in data_items:
+                yield wrap_item(json.dumps(item))
+
+            async for data in original_generator:
+                yield data
+
+        return StreamingResponse(
+            stream_wrapper(response.body_iterator, data_items),
+            headers=dict(response.headers),
+        )
+
+    async def _receive(self, body: bytes):
+        return {"type": "http.request", "body": body, "more_body": False}
+
+
+app.add_middleware(ChatCompletionMiddleware)
+
+
+##################################
+#
+# Pipeline Middleware
+#
+##################################
+
+
+def get_sorted_filters(model_id):
+    filters = [
+        model
+        for model in app.state.MODELS.values()
+        if "pipeline" in model
+        and "type" in model["pipeline"]
+        and model["pipeline"]["type"] == "filter"
+        and (
+            model["pipeline"]["pipelines"] == ["*"]
+            or any(
+                model_id == target_model_id
+                for target_model_id in model["pipeline"]["pipelines"]
+            )
+        )
+    ]
+    sorted_filters = sorted(filters, key=lambda x: x["pipeline"]["priority"])
+    return sorted_filters
+
+
+def filter_pipeline(payload, user):
+    user = {"id": user.id, "email": user.email, "name": user.name, "role": user.role}
+    model_id = payload["model"]
+    sorted_filters = get_sorted_filters(model_id)
+
+    model = app.state.MODELS[model_id]
+
+    if "pipeline" in model:
+        sorted_filters.append(model)
+
+    for filter in sorted_filters:
+        r = None
+        try:
+            urlIdx = filter["urlIdx"]
+
+            url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
+            key = openai_app.state.config.OPENAI_API_KEYS[urlIdx]
+
+            if key == "":
+                continue
+
+            headers = {"Authorization": f"Bearer {key}"}
+            r = requests.post(
+                f"{url}/{filter['id']}/filter/inlet",
+                headers=headers,
+                json={
+                    "user": user,
+                    "body": payload,
+                },
+            )
+
+            r.raise_for_status()
+            payload = r.json()
+        except Exception as e:
+            # Handle connection error here
+            print(f"Connection error: {e}")
+
+            if r is not None:
+                res = r.json()
+                if "detail" in res:
+                    raise Exception(r.status_code, res["detail"])
+
+    return payload
+
+
+class PipelineMiddleware(BaseHTTPMiddleware):
+    async def dispatch(self, request: Request, call_next):
+        if not is_chat_completion_request(request):
+            return await call_next(request)
+
+        log.debug(f"request.url.path: {request.url.path}")
+
+        # Read the original request body
+        body = await request.body()
+        # Decode body to string
+        body_str = body.decode("utf-8")
+        # Parse string to JSON
+        data = json.loads(body_str) if body_str else {}
+
+        try:
+            user = get_current_user(
+                request,
+                get_http_authorization_cred(request.headers["Authorization"]),
+            )
+        except KeyError as e:
+            if len(e.args) > 1:
+                return JSONResponse(
+                    status_code=e.args[0],
+                    content={"detail": e.args[1]},
+                )
+            else:
+                return JSONResponse(
+                    status_code=status.HTTP_401_UNAUTHORIZED,
+                    content={"detail": "Not authenticated"},
+                )
+
+        try:
+            data = filter_pipeline(data, user)
+        except Exception as e:
+            if len(e.args) > 1:
+                return JSONResponse(
+                    status_code=e.args[0],
+                    content={"detail": e.args[1]},
+                )
+            else:
+                return JSONResponse(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    content={"detail": str(e)},
+                )
+
+        modified_body_bytes = json.dumps(data).encode("utf-8")
+        # Replace the request body with the modified one
+        request._body = modified_body_bytes
+        # Set custom header to ensure content-length matches new body length
+        request.headers.__dict__["_list"] = [
+            (b"content-length", str(len(modified_body_bytes)).encode("utf-8")),
+            *[(k, v) for k, v in request.headers.raw if k.lower() != b"content-length"],
+        ]
+
+        response = await call_next(request)
+        return response
+
+    async def _receive(self, body: bytes):
+        return {"type": "http.request", "body": body, "more_body": False}
+
+
+app.add_middleware(PipelineMiddleware)
+
+
+from urllib.parse import urlencode, parse_qs, urlparse
+
+
+class RedirectMiddleware(BaseHTTPMiddleware):
+    async def dispatch(self, request: Request, call_next):
+        # Check if the request is a GET request
+        if request.method == "GET":
+            path = request.url.path
+            query_params = dict(parse_qs(urlparse(str(request.url)).query))
+
+            # Check for the specific watch path and the presence of 'v' parameter
+            if path.endswith("/watch") and "v" in query_params:
+                video_id = query_params["v"][0]  # Extract the first 'v' parameter
+                encoded_video_id = urlencode({"youtube": video_id})
+                redirect_url = f"/?{encoded_video_id}"
+                return RedirectResponse(url=redirect_url)
+
+        # Proceed with the normal flow of other requests
+        response = await call_next(request)
+        return response
+
+
+# Add the middleware to the app
+app.add_middleware(RedirectMiddleware)
+
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=CORS_ALLOW_ORIGIN,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+app.add_middleware(SecurityHeadersMiddleware)
+
+
+@app.middleware("http")
+async def commit_session_after_request(request: Request, call_next):
+    response = await call_next(request)
+    log.debug("Commit session after request")
+    Session.commit()
+    return response
+
+
+@app.middleware("http")
+async def check_url(request: Request, call_next):
+    if len(app.state.MODELS) == 0:
+        await get_all_models()
+    else:
+        pass
+
+    start_time = int(time.time())
+    response = await call_next(request)
+    process_time = int(time.time()) - start_time
+    response.headers["X-Process-Time"] = str(process_time)
+
+    return response
+
+
+@app.middleware("http")
+async def update_embedding_function(request: Request, call_next):
+    response = await call_next(request)
+    if "/embedding/update" in request.url.path:
+        webui_app.state.EMBEDDING_FUNCTION = retrieval_app.state.EMBEDDING_FUNCTION
+    return response
+
+
+@app.middleware("http")
+async def inspect_websocket(request: Request, call_next):
+    if (
+        "/ws/socket.io" in request.url.path
+        and request.query_params.get("transport") == "websocket"
+    ):
+        upgrade = (request.headers.get("Upgrade") or "").lower()
+        connection = (request.headers.get("Connection") or "").lower().split(",")
+        # Check that there's the correct headers for an upgrade, else reject the connection
+        # This is to work around this upstream issue: https://github.com/miguelgrinberg/python-engineio/issues/367
+        if upgrade != "websocket" or "upgrade" not in connection:
+            return JSONResponse(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                content={"detail": "Invalid WebSocket upgrade request"},
+            )
+    return await call_next(request)
+
+
+app.mount("/ws", socket_app)
+app.mount("/ollama", ollama_app)
+app.mount("/openai", openai_app)
+
+app.mount("/images/api/v1", images_app)
+app.mount("/audio/api/v1", audio_app)
+app.mount("/retrieval/api/v1", retrieval_app)
+
+app.mount("/api/v1", webui_app)
+
+
+webui_app.state.EMBEDDING_FUNCTION = retrieval_app.state.EMBEDDING_FUNCTION
+
+
+async def get_all_models():
+    # TODO: Optimize this function
+    open_webui_models = []
+    openai_models = []
+    ollama_models = []
+
+    if app.state.config.ENABLE_OPENAI_API:
+        openai_models = await get_openai_models()
+        openai_models = openai_models["data"]
+
+    if app.state.config.ENABLE_OLLAMA_API:
+        ollama_models = await get_ollama_models()
+        ollama_models = [
+            {
+                "id": model["model"],
+                "name": model["name"],
+                "object": "model",
+                "created": int(time.time()),
+                "owned_by": "ollama",
+                "ollama": model,
+            }
+            for model in ollama_models["models"]
+        ]
+
+    open_webui_models = await get_open_webui_models()
+
+    models = open_webui_models + openai_models + ollama_models
+
+    # If there are no models, return an empty list
+    if len([model for model in models if model["owned_by"] != "arena"]) == 0:
+        return []
+
+    global_action_ids = [
+        function.id for function in Functions.get_global_action_functions()
+    ]
+    enabled_action_ids = [
+        function.id
+        for function in Functions.get_functions_by_type("action", active_only=True)
+    ]
+
+    custom_models = Models.get_all_models()
+    for custom_model in custom_models:
+        if custom_model.base_model_id is None:
+            for model in models:
+                if (
+                    custom_model.id == model["id"]
+                    or custom_model.id == model["id"].split(":")[0]
+                ):
+                    model["name"] = custom_model.name
+                    model["info"] = custom_model.model_dump()
+
+                    action_ids = []
+                    if "info" in model and "meta" in model["info"]:
+                        action_ids.extend(model["info"]["meta"].get("actionIds", []))
+
+                    model["action_ids"] = action_ids
+        else:
+            owned_by = "openai"
+            pipe = None
+            action_ids = []
+
+            for model in models:
+                if (
+                    custom_model.base_model_id == model["id"]
+                    or custom_model.base_model_id == model["id"].split(":")[0]
+                ):
+                    owned_by = model["owned_by"]
+                    if "pipe" in model:
+                        pipe = model["pipe"]
+                    break
+
+            if custom_model.meta:
+                meta = custom_model.meta.model_dump()
+                if "actionIds" in meta:
+                    action_ids.extend(meta["actionIds"])
+
+            models.append(
+                {
+                    "id": custom_model.id,
+                    "name": custom_model.name,
+                    "object": "model",
+                    "created": custom_model.created_at,
+                    "owned_by": owned_by,
+                    "info": custom_model.model_dump(),
+                    "preset": True,
+                    **({"pipe": pipe} if pipe is not None else {}),
+                    "action_ids": action_ids,
+                }
+            )
+
+    for model in models:
+        action_ids = []
+        if "action_ids" in model:
+            action_ids = model["action_ids"]
+            del model["action_ids"]
+
+        action_ids = action_ids + global_action_ids
+        action_ids = list(set(action_ids))
+        action_ids = [
+            action_id for action_id in action_ids if action_id in enabled_action_ids
+        ]
+
+        model["actions"] = []
+        for action_id in action_ids:
+            action = Functions.get_function_by_id(action_id)
+            if action is None:
+                raise Exception(f"Action not found: {action_id}")
+
+            if action_id in webui_app.state.FUNCTIONS:
+                function_module = webui_app.state.FUNCTIONS[action_id]
+            else:
+                function_module, _, _ = load_function_module_by_id(action_id)
+                webui_app.state.FUNCTIONS[action_id] = function_module
+
+            __webui__ = False
+            if hasattr(function_module, "__webui__"):
+                __webui__ = function_module.__webui__
+
+            if hasattr(function_module, "actions"):
+                actions = function_module.actions
+                model["actions"].extend(
+                    [
+                        {
+                            "id": f"{action_id}.{_action['id']}",
+                            "name": _action.get(
+                                "name", f"{action.name} ({_action['id']})"
+                            ),
+                            "description": action.meta.description,
+                            "icon_url": _action.get(
+                                "icon_url", action.meta.manifest.get("icon_url", None)
+                            ),
+                            **({"__webui__": __webui__} if __webui__ else {}),
+                        }
+                        for _action in actions
+                    ]
+                )
+            else:
+                model["actions"].append(
+                    {
+                        "id": action_id,
+                        "name": action.name,
+                        "description": action.meta.description,
+                        "icon_url": action.meta.manifest.get("icon_url", None),
+                        **({"__webui__": __webui__} if __webui__ else {}),
+                    }
+                )
+
+    app.state.MODELS = {model["id"]: model for model in models}
+    webui_app.state.MODELS = app.state.MODELS
+
+    return models
+
+
+@app.get("/api/models")
+async def get_models(user=Depends(get_verified_user)):
+    models = await get_all_models()
+
+    # Filter out filter pipelines
+    models = [
+        model
+        for model in models
+        if "pipeline" not in model or model["pipeline"].get("type", None) != "filter"
+    ]
+
+    if app.state.config.ENABLE_MODEL_FILTER:
+        if user.role == "user":
+            models = list(
+                filter(
+                    lambda model: model["id"] in app.state.config.MODEL_FILTER_LIST,
+                    models,
+                )
+            )
+            return {"data": models}
+
+    return {"data": models}
+
+
+@app.post("/api/chat/completions")
+async def generate_chat_completions(
+    form_data: dict, user=Depends(get_verified_user), bypass_filter: bool = False
+):
+    model_id = form_data["model"]
+
+    if model_id not in app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="Model not found",
+        )
+
+    if not bypass_filter and app.state.config.ENABLE_MODEL_FILTER:
+        if user.role == "user" and model_id not in app.state.config.MODEL_FILTER_LIST:
+            raise HTTPException(
+                status_code=status.HTTP_403_FORBIDDEN,
+                detail="Model not found",
+            )
+
+    model = app.state.MODELS[model_id]
+
+    if model["owned_by"] == "arena":
+        model_ids = model.get("info", {}).get("meta", {}).get("model_ids")
+        filter_mode = model.get("info", {}).get("meta", {}).get("filter_mode")
+        if model_ids and filter_mode == "exclude":
+            model_ids = [
+                model["id"]
+                for model in await get_all_models()
+                if model.get("owned_by") != "arena"
+                and not model.get("info", {}).get("meta", {}).get("hidden", False)
+                and model["id"] not in model_ids
+            ]
+
+        selected_model_id = None
+        if isinstance(model_ids, list) and model_ids:
+            selected_model_id = random.choice(model_ids)
+        else:
+            model_ids = [
+                model["id"]
+                for model in await get_all_models()
+                if model.get("owned_by") != "arena"
+                and not model.get("info", {}).get("meta", {}).get("hidden", False)
+            ]
+            selected_model_id = random.choice(model_ids)
+
+        form_data["model"] = selected_model_id
+
+        if form_data.get("stream") == True:
+
+            async def stream_wrapper(stream):
+                yield f"data: {json.dumps({'selected_model_id': selected_model_id})}\n\n"
+                async for chunk in stream:
+                    yield chunk
+
+            response = await generate_chat_completions(
+                form_data, user, bypass_filter=True
+            )
+            return StreamingResponse(
+                stream_wrapper(response.body_iterator), media_type="text/event-stream"
+            )
+        else:
+            return {
+                **(
+                    await generate_chat_completions(form_data, user, bypass_filter=True)
+                ),
+                "selected_model_id": selected_model_id,
+            }
+    if model.get("pipe"):
+        return await generate_function_chat_completion(form_data, user=user)
+    if model["owned_by"] == "ollama":
+        # Using /ollama/api/chat endpoint
+        form_data = convert_payload_openai_to_ollama(form_data)
+        form_data = GenerateChatCompletionForm(**form_data)
+        response = await generate_ollama_chat_completion(
+            form_data=form_data, user=user, bypass_filter=True
+        )
+        if form_data.stream:
+            response.headers["content-type"] = "text/event-stream"
+            return StreamingResponse(
+                convert_streaming_response_ollama_to_openai(response),
+                headers=dict(response.headers),
+            )
+        else:
+            return convert_response_ollama_to_openai(response)
+    else:
+        return await generate_openai_chat_completion(form_data, user=user)
+
+
+@app.post("/api/chat/completed")
+async def chat_completed(form_data: dict, user=Depends(get_verified_user)):
+    data = form_data
+    model_id = data["model"]
+    if model_id not in app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="Model not found",
+        )
+    model = app.state.MODELS[model_id]
+
+    sorted_filters = get_sorted_filters(model_id)
+    if "pipeline" in model:
+        sorted_filters = [model] + sorted_filters
+
+    for filter in sorted_filters:
+        r = None
+        try:
+            urlIdx = filter["urlIdx"]
+
+            url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
+            key = openai_app.state.config.OPENAI_API_KEYS[urlIdx]
+
+            if key != "":
+                headers = {"Authorization": f"Bearer {key}"}
+                r = requests.post(
+                    f"{url}/{filter['id']}/filter/outlet",
+                    headers=headers,
+                    json={
+                        "user": {
+                            "id": user.id,
+                            "name": user.name,
+                            "email": user.email,
+                            "role": user.role,
+                        },
+                        "body": data,
+                    },
+                )
+
+                r.raise_for_status()
+                data = r.json()
+        except Exception as e:
+            # Handle connection error here
+            print(f"Connection error: {e}")
+
+            if r is not None:
+                try:
+                    res = r.json()
+                    if "detail" in res:
+                        return JSONResponse(
+                            status_code=r.status_code,
+                            content=res,
+                        )
+                except Exception:
+                    pass
+
+            else:
+                pass
+
+    __event_emitter__ = get_event_emitter(
+        {
+            "chat_id": data["chat_id"],
+            "message_id": data["id"],
+            "session_id": data["session_id"],
+        }
+    )
+
+    __event_call__ = get_event_call(
+        {
+            "chat_id": data["chat_id"],
+            "message_id": data["id"],
+            "session_id": data["session_id"],
+        }
+    )
+
+    def get_priority(function_id):
+        function = Functions.get_function_by_id(function_id)
+        if function is not None and hasattr(function, "valves"):
+            # TODO: Fix FunctionModel to include vavles
+            return (function.valves if function.valves else {}).get("priority", 0)
+        return 0
+
+    filter_ids = [function.id for function in Functions.get_global_filter_functions()]
+    if "info" in model and "meta" in model["info"]:
+        filter_ids.extend(model["info"]["meta"].get("filterIds", []))
+        filter_ids = list(set(filter_ids))
+
+    enabled_filter_ids = [
+        function.id
+        for function in Functions.get_functions_by_type("filter", active_only=True)
+    ]
+    filter_ids = [
+        filter_id for filter_id in filter_ids if filter_id in enabled_filter_ids
+    ]
+
+    # Sort filter_ids by priority, using the get_priority function
+    filter_ids.sort(key=get_priority)
+
+    for filter_id in filter_ids:
+        filter = Functions.get_function_by_id(filter_id)
+        if not filter:
+            continue
+
+        if filter_id in webui_app.state.FUNCTIONS:
+            function_module = webui_app.state.FUNCTIONS[filter_id]
+        else:
+            function_module, _, _ = load_function_module_by_id(filter_id)
+            webui_app.state.FUNCTIONS[filter_id] = function_module
+
+        if hasattr(function_module, "valves") and hasattr(function_module, "Valves"):
+            valves = Functions.get_function_valves_by_id(filter_id)
+            function_module.valves = function_module.Valves(
+                **(valves if valves else {})
+            )
+
+        if not hasattr(function_module, "outlet"):
+            continue
+        try:
+            outlet = function_module.outlet
+
+            # Get the signature of the function
+            sig = inspect.signature(outlet)
+            params = {"body": data}
+
+            # Extra parameters to be passed to the function
+            extra_params = {
+                "__model__": model,
+                "__id__": filter_id,
+                "__event_emitter__": __event_emitter__,
+                "__event_call__": __event_call__,
+            }
+
+            # Add extra params in contained in function signature
+            for key, value in extra_params.items():
+                if key in sig.parameters:
+                    params[key] = value
+
+            if "__user__" in sig.parameters:
+                __user__ = {
+                    "id": user.id,
+                    "email": user.email,
+                    "name": user.name,
+                    "role": user.role,
+                }
+
+                try:
+                    if hasattr(function_module, "UserValves"):
+                        __user__["valves"] = function_module.UserValves(
+                            **Functions.get_user_valves_by_id_and_user_id(
+                                filter_id, user.id
+                            )
+                        )
+                except Exception as e:
+                    print(e)
+
+                params = {**params, "__user__": __user__}
+
+            if inspect.iscoroutinefunction(outlet):
+                data = await outlet(**params)
+            else:
+                data = outlet(**params)
+
+        except Exception as e:
+            print(f"Error: {e}")
+            return JSONResponse(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                content={"detail": str(e)},
+            )
+
+    return data
+
+
+@app.post("/api/chat/actions/{action_id}")
+async def chat_action(action_id: str, form_data: dict, user=Depends(get_verified_user)):
+    if "." in action_id:
+        action_id, sub_action_id = action_id.split(".")
+    else:
+        sub_action_id = None
+
+    action = Functions.get_function_by_id(action_id)
+    if not action:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="Action not found",
+        )
+
+    data = form_data
+    model_id = data["model"]
+    if model_id not in app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="Model not found",
+        )
+    model = app.state.MODELS[model_id]
+
+    __event_emitter__ = get_event_emitter(
+        {
+            "chat_id": data["chat_id"],
+            "message_id": data["id"],
+            "session_id": data["session_id"],
+        }
+    )
+    __event_call__ = get_event_call(
+        {
+            "chat_id": data["chat_id"],
+            "message_id": data["id"],
+            "session_id": data["session_id"],
+        }
+    )
+
+    if action_id in webui_app.state.FUNCTIONS:
+        function_module = webui_app.state.FUNCTIONS[action_id]
+    else:
+        function_module, _, _ = load_function_module_by_id(action_id)
+        webui_app.state.FUNCTIONS[action_id] = function_module
+
+    if hasattr(function_module, "valves") and hasattr(function_module, "Valves"):
+        valves = Functions.get_function_valves_by_id(action_id)
+        function_module.valves = function_module.Valves(**(valves if valves else {}))
+
+    if hasattr(function_module, "action"):
+        try:
+            action = function_module.action
+
+            # Get the signature of the function
+            sig = inspect.signature(action)
+            params = {"body": data}
+
+            # Extra parameters to be passed to the function
+            extra_params = {
+                "__model__": model,
+                "__id__": sub_action_id if sub_action_id is not None else action_id,
+                "__event_emitter__": __event_emitter__,
+                "__event_call__": __event_call__,
+            }
+
+            # Add extra params in contained in function signature
+            for key, value in extra_params.items():
+                if key in sig.parameters:
+                    params[key] = value
+
+            if "__user__" in sig.parameters:
+                __user__ = {
+                    "id": user.id,
+                    "email": user.email,
+                    "name": user.name,
+                    "role": user.role,
+                }
+
+                try:
+                    if hasattr(function_module, "UserValves"):
+                        __user__["valves"] = function_module.UserValves(
+                            **Functions.get_user_valves_by_id_and_user_id(
+                                action_id, user.id
+                            )
+                        )
+                except Exception as e:
+                    print(e)
+
+                params = {**params, "__user__": __user__}
+
+            if inspect.iscoroutinefunction(action):
+                data = await action(**params)
+            else:
+                data = action(**params)
+
+        except Exception as e:
+            print(f"Error: {e}")
+            return JSONResponse(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                content={"detail": str(e)},
+            )
+
+    return data
+
+
+##################################
+#
+# Task Endpoints
+#
+##################################
+
+
+# TODO: Refactor task API endpoints below into a separate file
+
+
+@app.get("/api/task/config")
+async def get_task_config(user=Depends(get_verified_user)):
+    return {
+        "TASK_MODEL": app.state.config.TASK_MODEL,
+        "TASK_MODEL_EXTERNAL": app.state.config.TASK_MODEL_EXTERNAL,
+        "TITLE_GENERATION_PROMPT_TEMPLATE": app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE,
+        "TAGS_GENERATION_PROMPT_TEMPLATE": app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE,
+        "ENABLE_SEARCH_QUERY": app.state.config.ENABLE_SEARCH_QUERY,
+        "SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE": app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE,
+        "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE": app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
+    }
+
+
+class TaskConfigForm(BaseModel):
+    TASK_MODEL: Optional[str]
+    TASK_MODEL_EXTERNAL: Optional[str]
+    TITLE_GENERATION_PROMPT_TEMPLATE: str
+    TAGS_GENERATION_PROMPT_TEMPLATE: str
+    SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE: str
+    ENABLE_SEARCH_QUERY: bool
+    TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: str
+
+
+@app.post("/api/task/config/update")
+async def update_task_config(form_data: TaskConfigForm, user=Depends(get_admin_user)):
+    app.state.config.TASK_MODEL = form_data.TASK_MODEL
+    app.state.config.TASK_MODEL_EXTERNAL = form_data.TASK_MODEL_EXTERNAL
+    app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE = (
+        form_data.TITLE_GENERATION_PROMPT_TEMPLATE
+    )
+    app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE = (
+        form_data.TAGS_GENERATION_PROMPT_TEMPLATE
+    )
+
+    app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE = (
+        form_data.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE
+    )
+    app.state.config.ENABLE_SEARCH_QUERY = form_data.ENABLE_SEARCH_QUERY
+    app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = (
+        form_data.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
+    )
+
+    return {
+        "TASK_MODEL": app.state.config.TASK_MODEL,
+        "TASK_MODEL_EXTERNAL": app.state.config.TASK_MODEL_EXTERNAL,
+        "TITLE_GENERATION_PROMPT_TEMPLATE": app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE,
+        "TAGS_GENERATION_PROMPT_TEMPLATE": app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE,
+        "SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE": app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE,
+        "ENABLE_SEARCH_QUERY": app.state.config.ENABLE_SEARCH_QUERY,
+        "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE": app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
+    }
+
+
+@app.post("/api/task/title/completions")
+async def generate_title(form_data: dict, user=Depends(get_verified_user)):
+    print("generate_title")
+
+    model_id = form_data["model"]
+    if model_id not in app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="Model not found",
+        )
+
+    # Check if the user has a custom task model
+    # If the user has a custom task model, use that model
+    task_model_id = get_task_model_id(model_id)
+    print(task_model_id)
+
+    model = app.state.MODELS[task_model_id]
+
+    if app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE != "":
+        template = app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE
+    else:
+        template = """Create a concise, 3-5 word title with an emoji as a title for the chat history, in the given language. Suitable Emojis for the summary can be used to enhance understanding but avoid quotation marks or special formatting. RESPOND ONLY WITH THE TITLE TEXT.
+
+Examples of titles:
+📉 Stock Market Trends
+🍪 Perfect Chocolate Chip Recipe
+Evolution of Music Streaming
+Remote Work Productivity Tips
+Artificial Intelligence in Healthcare
+🎮 Video Game Development Insights
+
+<chat_history>
+{{MESSAGES:END:2}}
+</chat_history>"""
+
+    content = title_generation_template(
+        template,
+        form_data["messages"],
+        {
+            "name": user.name,
+            "location": user.info.get("location") if user.info else None,
+        },
+    )
+
+    payload = {
+        "model": task_model_id,
+        "messages": [{"role": "user", "content": content}],
+        "stream": False,
+        **(
+            {"max_tokens": 50}
+            if app.state.MODELS[task_model_id]["owned_by"] == "ollama"
+            else {
+                "max_completion_tokens": 50,
+            }
+        ),
+        "chat_id": form_data.get("chat_id", None),
+        "metadata": {"task": str(TASKS.TITLE_GENERATION), "task_body": form_data},
+    }
+    log.debug(payload)
+
+    # Handle pipeline filters
+    try:
+        payload = filter_pipeline(payload, user)
+    except Exception as e:
+        if len(e.args) > 1:
+            return JSONResponse(
+                status_code=e.args[0],
+                content={"detail": e.args[1]},
+            )
+        else:
+            return JSONResponse(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                content={"detail": str(e)},
+            )
+    if "chat_id" in payload:
+        del payload["chat_id"]
+
+    return await generate_chat_completions(form_data=payload, user=user)
+
+
+@app.post("/api/task/tags/completions")
+async def generate_chat_tags(form_data: dict, user=Depends(get_verified_user)):
+    print("generate_chat_tags")
+    model_id = form_data["model"]
+    if model_id not in app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="Model not found",
+        )
+
+    # Check if the user has a custom task model
+    # If the user has a custom task model, use that model
+    task_model_id = get_task_model_id(model_id)
+    print(task_model_id)
+
+    if app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE != "":
+        template = app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE
+    else:
+        template = """### Task:
+Generate 1-3 broad tags categorizing the main themes of the chat history, along with 1-3 more specific subtopic tags.
+
+### Guidelines:
+- Start with high-level domains (e.g. Science, Technology, Philosophy, Arts, Politics, Business, Health, Sports, Entertainment, Education)
+- Consider including relevant subfields/subdomains if they are strongly represented throughout the conversation
+- If content is too short (less than 3 messages) or too diverse, use only ["General"]
+- Use the chat's primary language; default to English if multilingual
+- Prioritize accuracy over specificity
+
+### Output:
+JSON format: { "tags": ["tag1", "tag2", "tag3"] }
+
+### Chat History:
+<chat_history>
+{{MESSAGES:END:6}}
+</chat_history>"""
+
+    content = tags_generation_template(
+        template, form_data["messages"], {"name": user.name}
+    )
+
+    print("content", content)
+    payload = {
+        "model": task_model_id,
+        "messages": [{"role": "user", "content": content}],
+        "stream": False,
+        "metadata": {"task": str(TASKS.TAGS_GENERATION), "task_body": form_data},
+    }
+    log.debug(payload)
+
+    # Handle pipeline filters
+    try:
+        payload = filter_pipeline(payload, user)
+    except Exception as e:
+        if len(e.args) > 1:
+            return JSONResponse(
+                status_code=e.args[0],
+                content={"detail": e.args[1]},
+            )
+        else:
+            return JSONResponse(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                content={"detail": str(e)},
+            )
+    if "chat_id" in payload:
+        del payload["chat_id"]
+
+    return await generate_chat_completions(form_data=payload, user=user)
+
+
+@app.post("/api/task/query/completions")
+async def generate_search_query(form_data: dict, user=Depends(get_verified_user)):
+    print("generate_search_query")
+    if not app.state.config.ENABLE_SEARCH_QUERY:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=f"Search query generation is disabled",
+        )
+
+    model_id = form_data["model"]
+    if model_id not in app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="Model not found",
+        )
+
+    # Check if the user has a custom task model
+    # If the user has a custom task model, use that model
+    task_model_id = get_task_model_id(model_id)
+    print(task_model_id)
+
+    model = app.state.MODELS[task_model_id]
+
+    if app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE != "":
+        template = app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE
+    else:
+        template = """Given the user's message and interaction history, decide if a web search is necessary. You must be concise and exclusively provide a search query if one is necessary. Refrain from verbose responses or any additional commentary. Prefer suggesting a search if uncertain to provide comprehensive or updated information. If a search isn't needed at all, respond with an empty string. Default to a search query when in doubt. Today's date is {{CURRENT_DATE}}.
+
+User Message:
+{{prompt:end:4000}}
+
+Interaction History:
+{{MESSAGES:END:6}}
+
+Search Query:"""
+
+    content = search_query_generation_template(
+        template, form_data["messages"], {"name": user.name}
+    )
+
+    print("content", content)
+
+    payload = {
+        "model": task_model_id,
+        "messages": [{"role": "user", "content": content}],
+        "stream": False,
+        **(
+            {"max_tokens": 30}
+            if app.state.MODELS[task_model_id]["owned_by"] == "ollama"
+            else {
+                "max_completion_tokens": 30,
+            }
+        ),
+        "metadata": {"task": str(TASKS.QUERY_GENERATION), "task_body": form_data},
+    }
+    log.debug(payload)
+
+    # Handle pipeline filters
+    try:
+        payload = filter_pipeline(payload, user)
+    except Exception as e:
+        if len(e.args) > 1:
+            return JSONResponse(
+                status_code=e.args[0],
+                content={"detail": e.args[1]},
+            )
+        else:
+            return JSONResponse(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                content={"detail": str(e)},
+            )
+    if "chat_id" in payload:
+        del payload["chat_id"]
+
+    return await generate_chat_completions(form_data=payload, user=user)
+
+
+@app.post("/api/task/emoji/completions")
+async def generate_emoji(form_data: dict, user=Depends(get_verified_user)):
+    print("generate_emoji")
+
+    model_id = form_data["model"]
+    if model_id not in app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="Model not found",
+        )
+
+    # Check if the user has a custom task model
+    # If the user has a custom task model, use that model
+    task_model_id = get_task_model_id(model_id)
+    print(task_model_id)
+
+    model = app.state.MODELS[task_model_id]
+
+    template = '''
+Your task is to reflect the speaker's likely facial expression through a fitting emoji. Interpret emotions from the message and reflect their facial expression using fitting, diverse emojis (e.g., 😊, 😢, 😡, 😱).
+
+Message: """{{prompt}}"""
+'''
+    content = emoji_generation_template(
+        template,
+        form_data["prompt"],
+        {
+            "name": user.name,
+            "location": user.info.get("location") if user.info else None,
+        },
+    )
+
+    payload = {
+        "model": task_model_id,
+        "messages": [{"role": "user", "content": content}],
+        "stream": False,
+        **(
+            {"max_tokens": 4}
+            if app.state.MODELS[task_model_id]["owned_by"] == "ollama"
+            else {
+                "max_completion_tokens": 4,
+            }
+        ),
+        "chat_id": form_data.get("chat_id", None),
+        "metadata": {"task": str(TASKS.EMOJI_GENERATION), "task_body": form_data},
+    }
+    log.debug(payload)
+
+    # Handle pipeline filters
+    try:
+        payload = filter_pipeline(payload, user)
+    except Exception as e:
+        if len(e.args) > 1:
+            return JSONResponse(
+                status_code=e.args[0],
+                content={"detail": e.args[1]},
+            )
+        else:
+            return JSONResponse(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                content={"detail": str(e)},
+            )
+    if "chat_id" in payload:
+        del payload["chat_id"]
+
+    return await generate_chat_completions(form_data=payload, user=user)
+
+
+@app.post("/api/task/moa/completions")
+async def generate_moa_response(form_data: dict, user=Depends(get_verified_user)):
+    print("generate_moa_response")
+
+    model_id = form_data["model"]
+    if model_id not in app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="Model not found",
+        )
+
+    # Check if the user has a custom task model
+    # If the user has a custom task model, use that model
+    task_model_id = get_task_model_id(model_id)
+    print(task_model_id)
+
+    model = app.state.MODELS[task_model_id]
+
+    template = """You have been provided with a set of responses from various models to the latest user query: "{{prompt}}"
+
+Your task is to synthesize these responses into a single, high-quality response. It is crucial to critically evaluate the information provided in these responses, recognizing that some of it may be biased or incorrect. Your response should not simply replicate the given answers but should offer a refined, accurate, and comprehensive reply to the instruction. Ensure your response is well-structured, coherent, and adheres to the highest standards of accuracy and reliability.
+
+Responses from models: {{responses}}"""
+
+    content = moa_response_generation_template(
+        template,
+        form_data["prompt"],
+        form_data["responses"],
+    )
+
+    payload = {
+        "model": task_model_id,
+        "messages": [{"role": "user", "content": content}],
+        "stream": form_data.get("stream", False),
+        "chat_id": form_data.get("chat_id", None),
+        "metadata": {
+            "task": str(TASKS.MOA_RESPONSE_GENERATION),
+            "task_body": form_data,
+        },
+    }
+    log.debug(payload)
+
+    try:
+        payload = filter_pipeline(payload, user)
+    except Exception as e:
+        if len(e.args) > 1:
+            return JSONResponse(
+                status_code=e.args[0],
+                content={"detail": e.args[1]},
+            )
+        else:
+            return JSONResponse(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                content={"detail": str(e)},
+            )
+    if "chat_id" in payload:
+        del payload["chat_id"]
+
+    return await generate_chat_completions(form_data=payload, user=user)
+
+
+##################################
+#
+# Pipelines Endpoints
+#
+##################################
+
+
+# TODO: Refactor pipelines API endpoints below into a separate file
+
+
+@app.get("/api/pipelines/list")
+async def get_pipelines_list(user=Depends(get_admin_user)):
+    responses = await get_openai_models(raw=True)
+
+    print(responses)
+    urlIdxs = [
+        idx
+        for idx, response in enumerate(responses)
+        if response is not None and "pipelines" in response
+    ]
+
+    return {
+        "data": [
+            {
+                "url": openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx],
+                "idx": urlIdx,
+            }
+            for urlIdx in urlIdxs
+        ]
+    }
+
+
+@app.post("/api/pipelines/upload")
+async def upload_pipeline(
+    urlIdx: int = Form(...), file: UploadFile = File(...), user=Depends(get_admin_user)
+):
+    print("upload_pipeline", urlIdx, file.filename)
+    # Check if the uploaded file is a python file
+    if not (file.filename and file.filename.endswith(".py")):
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail="Only Python (.py) files are allowed.",
+        )
+
+    upload_folder = f"{CACHE_DIR}/pipelines"
+    os.makedirs(upload_folder, exist_ok=True)
+    file_path = os.path.join(upload_folder, file.filename)
+
+    r = None
+    try:
+        # Save the uploaded file
+        with open(file_path, "wb") as buffer:
+            shutil.copyfileobj(file.file, buffer)
+
+        url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
+        key = openai_app.state.config.OPENAI_API_KEYS[urlIdx]
+
+        headers = {"Authorization": f"Bearer {key}"}
+
+        with open(file_path, "rb") as f:
+            files = {"file": f}
+            r = requests.post(f"{url}/pipelines/upload", headers=headers, files=files)
+
+        r.raise_for_status()
+        data = r.json()
+
+        return {**data}
+    except Exception as e:
+        # Handle connection error here
+        print(f"Connection error: {e}")
+
+        detail = "Pipeline not found"
+        status_code = status.HTTP_404_NOT_FOUND
+        if r is not None:
+            status_code = r.status_code
+            try:
+                res = r.json()
+                if "detail" in res:
+                    detail = res["detail"]
+            except Exception:
+                pass
+
+        raise HTTPException(
+            status_code=status_code,
+            detail=detail,
+        )
+    finally:
+        # Ensure the file is deleted after the upload is completed or on failure
+        if os.path.exists(file_path):
+            os.remove(file_path)
+
+
+class AddPipelineForm(BaseModel):
+    url: str
+    urlIdx: int
+
+
+@app.post("/api/pipelines/add")
+async def add_pipeline(form_data: AddPipelineForm, user=Depends(get_admin_user)):
+    r = None
+    try:
+        urlIdx = form_data.urlIdx
+
+        url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
+        key = openai_app.state.config.OPENAI_API_KEYS[urlIdx]
+
+        headers = {"Authorization": f"Bearer {key}"}
+        r = requests.post(
+            f"{url}/pipelines/add", headers=headers, json={"url": form_data.url}
+        )
+
+        r.raise_for_status()
+        data = r.json()
+
+        return {**data}
+    except Exception as e:
+        # Handle connection error here
+        print(f"Connection error: {e}")
+
+        detail = "Pipeline not found"
+        if r is not None:
+            try:
+                res = r.json()
+                if "detail" in res:
+                    detail = res["detail"]
+            except Exception:
+                pass
+
+        raise HTTPException(
+            status_code=(r.status_code if r is not None else status.HTTP_404_NOT_FOUND),
+            detail=detail,
+        )
+
+
+class DeletePipelineForm(BaseModel):
+    id: str
+    urlIdx: int
+
+
+@app.delete("/api/pipelines/delete")
+async def delete_pipeline(form_data: DeletePipelineForm, user=Depends(get_admin_user)):
+    r = None
+    try:
+        urlIdx = form_data.urlIdx
+
+        url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
+        key = openai_app.state.config.OPENAI_API_KEYS[urlIdx]
+
+        headers = {"Authorization": f"Bearer {key}"}
+        r = requests.delete(
+            f"{url}/pipelines/delete", headers=headers, json={"id": form_data.id}
+        )
+
+        r.raise_for_status()
+        data = r.json()
+
+        return {**data}
+    except Exception as e:
+        # Handle connection error here
+        print(f"Connection error: {e}")
+
+        detail = "Pipeline not found"
+        if r is not None:
+            try:
+                res = r.json()
+                if "detail" in res:
+                    detail = res["detail"]
+            except Exception:
+                pass
+
+        raise HTTPException(
+            status_code=(r.status_code if r is not None else status.HTTP_404_NOT_FOUND),
+            detail=detail,
+        )
+
+
+@app.get("/api/pipelines")
+async def get_pipelines(urlIdx: Optional[int] = None, user=Depends(get_admin_user)):
+    r = None
+    try:
+        url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
+        key = openai_app.state.config.OPENAI_API_KEYS[urlIdx]
+
+        headers = {"Authorization": f"Bearer {key}"}
+        r = requests.get(f"{url}/pipelines", headers=headers)
+
+        r.raise_for_status()
+        data = r.json()
+
+        return {**data}
+    except Exception as e:
+        # Handle connection error here
+        print(f"Connection error: {e}")
+
+        detail = "Pipeline not found"
+        if r is not None:
+            try:
+                res = r.json()
+                if "detail" in res:
+                    detail = res["detail"]
+            except Exception:
+                pass
+
+        raise HTTPException(
+            status_code=(r.status_code if r is not None else status.HTTP_404_NOT_FOUND),
+            detail=detail,
+        )
+
+
+@app.get("/api/pipelines/{pipeline_id}/valves")
+async def get_pipeline_valves(
+    urlIdx: Optional[int],
+    pipeline_id: str,
+    user=Depends(get_admin_user),
+):
+    r = None
+    try:
+        url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
+        key = openai_app.state.config.OPENAI_API_KEYS[urlIdx]
+
+        headers = {"Authorization": f"Bearer {key}"}
+        r = requests.get(f"{url}/{pipeline_id}/valves", headers=headers)
+
+        r.raise_for_status()
+        data = r.json()
+
+        return {**data}
+    except Exception as e:
+        # Handle connection error here
+        print(f"Connection error: {e}")
+
+        detail = "Pipeline not found"
+
+        if r is not None:
+            try:
+                res = r.json()
+                if "detail" in res:
+                    detail = res["detail"]
+            except Exception:
+                pass
+
+        raise HTTPException(
+            status_code=(r.status_code if r is not None else status.HTTP_404_NOT_FOUND),
+            detail=detail,
+        )
+
+
+@app.get("/api/pipelines/{pipeline_id}/valves/spec")
+async def get_pipeline_valves_spec(
+    urlIdx: Optional[int],
+    pipeline_id: str,
+    user=Depends(get_admin_user),
+):
+    r = None
+    try:
+        url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
+        key = openai_app.state.config.OPENAI_API_KEYS[urlIdx]
+
+        headers = {"Authorization": f"Bearer {key}"}
+        r = requests.get(f"{url}/{pipeline_id}/valves/spec", headers=headers)
+
+        r.raise_for_status()
+        data = r.json()
+
+        return {**data}
+    except Exception as e:
+        # Handle connection error here
+        print(f"Connection error: {e}")
+
+        detail = "Pipeline not found"
+        if r is not None:
+            try:
+                res = r.json()
+                if "detail" in res:
+                    detail = res["detail"]
+            except Exception:
+                pass
+
+        raise HTTPException(
+            status_code=(r.status_code if r is not None else status.HTTP_404_NOT_FOUND),
+            detail=detail,
+        )
+
+
+@app.post("/api/pipelines/{pipeline_id}/valves/update")
+async def update_pipeline_valves(
+    urlIdx: Optional[int],
+    pipeline_id: str,
+    form_data: dict,
+    user=Depends(get_admin_user),
+):
+    r = None
+    try:
+        url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx]
+        key = openai_app.state.config.OPENAI_API_KEYS[urlIdx]
+
+        headers = {"Authorization": f"Bearer {key}"}
+        r = requests.post(
+            f"{url}/{pipeline_id}/valves/update",
+            headers=headers,
+            json={**form_data},
+        )
+
+        r.raise_for_status()
+        data = r.json()
+
+        return {**data}
+    except Exception as e:
+        # Handle connection error here
+        print(f"Connection error: {e}")
+
+        detail = "Pipeline not found"
+
+        if r is not None:
+            try:
+                res = r.json()
+                if "detail" in res:
+                    detail = res["detail"]
+            except Exception:
+                pass
+
+        raise HTTPException(
+            status_code=(r.status_code if r is not None else status.HTTP_404_NOT_FOUND),
+            detail=detail,
+        )
+
+
+##################################
+#
+# Config Endpoints
+#
+##################################
+
+
+@app.get("/api/config")
+async def get_app_config(request: Request):
+    user = None
+    if "token" in request.cookies:
+        token = request.cookies.get("token")
+        data = decode_token(token)
+        if data is not None and "id" in data:
+            user = Users.get_user_by_id(data["id"])
+
+    return {
+        "status": True,
+        "name": WEBUI_NAME,
+        "version": VERSION,
+        "default_locale": str(DEFAULT_LOCALE),
+        "oauth": {
+            "providers": {
+                name: config.get("name", name)
+                for name, config in OAUTH_PROVIDERS.items()
+            }
+        },
+        "features": {
+            "auth": WEBUI_AUTH,
+            "auth_trusted_header": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER),
+            "enable_signup": webui_app.state.config.ENABLE_SIGNUP,
+            "enable_login_form": webui_app.state.config.ENABLE_LOGIN_FORM,
+            **(
+                {
+                    "enable_web_search": retrieval_app.state.config.ENABLE_RAG_WEB_SEARCH,
+                    "enable_image_generation": images_app.state.config.ENABLED,
+                    "enable_community_sharing": webui_app.state.config.ENABLE_COMMUNITY_SHARING,
+                    "enable_message_rating": webui_app.state.config.ENABLE_MESSAGE_RATING,
+                    "enable_admin_export": ENABLE_ADMIN_EXPORT,
+                    "enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
+                }
+                if user is not None
+                else {}
+            ),
+        },
+        **(
+            {
+                "default_models": webui_app.state.config.DEFAULT_MODELS,
+                "default_prompt_suggestions": webui_app.state.config.DEFAULT_PROMPT_SUGGESTIONS,
+                "audio": {
+                    "tts": {
+                        "engine": audio_app.state.config.TTS_ENGINE,
+                        "voice": audio_app.state.config.TTS_VOICE,
+                        "split_on": audio_app.state.config.TTS_SPLIT_ON,
+                    },
+                    "stt": {
+                        "engine": audio_app.state.config.STT_ENGINE,
+                    },
+                },
+                "file": {
+                    "max_size": retrieval_app.state.config.FILE_MAX_SIZE,
+                    "max_count": retrieval_app.state.config.FILE_MAX_COUNT,
+                },
+                "permissions": {**webui_app.state.config.USER_PERMISSIONS},
+            }
+            if user is not None
+            else {}
+        ),
+    }
+
+
+@app.get("/api/config/model/filter")
+async def get_model_filter_config(user=Depends(get_admin_user)):
+    return {
+        "enabled": app.state.config.ENABLE_MODEL_FILTER,
+        "models": app.state.config.MODEL_FILTER_LIST,
+    }
+
+
+class ModelFilterConfigForm(BaseModel):
+    enabled: bool
+    models: list[str]
+
+
+@app.post("/api/config/model/filter")
+async def update_model_filter_config(
+    form_data: ModelFilterConfigForm, user=Depends(get_admin_user)
+):
+    app.state.config.ENABLE_MODEL_FILTER = form_data.enabled
+    app.state.config.MODEL_FILTER_LIST = form_data.models
+
+    return {
+        "enabled": app.state.config.ENABLE_MODEL_FILTER,
+        "models": app.state.config.MODEL_FILTER_LIST,
+    }
+
+
+# TODO: webhook endpoint should be under config endpoints
+
+
+@app.get("/api/webhook")
+async def get_webhook_url(user=Depends(get_admin_user)):
+    return {
+        "url": app.state.config.WEBHOOK_URL,
+    }
+
+
+class UrlForm(BaseModel):
+    url: str
+
+
+@app.post("/api/webhook")
+async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)):
+    app.state.config.WEBHOOK_URL = form_data.url
+    webui_app.state.WEBHOOK_URL = app.state.config.WEBHOOK_URL
+    return {"url": app.state.config.WEBHOOK_URL}
+
+
+@app.get("/api/version")
+async def get_app_version():
+    return {
+        "version": VERSION,
+    }
+
+
+@app.get("/api/changelog")
+async def get_app_changelog():
+    return {key: CHANGELOG[key] for idx, key in enumerate(CHANGELOG) if idx < 5}
+
+
+@app.get("/api/version/updates")
+async def get_app_latest_release_version():
+    if OFFLINE_MODE:
+        log.debug(
+            f"Offline mode is enabled, returning current version as latest version"
+        )
+        return {"current": VERSION, "latest": VERSION}
+    try:
+        timeout = aiohttp.ClientTimeout(total=1)
+        async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
+            async with session.get(
+                "https://api.github.com/repos/open-webui/open-webui/releases/latest"
+            ) as response:
+                response.raise_for_status()
+                data = await response.json()
+                latest_version = data["tag_name"]
+
+                return {"current": VERSION, "latest": latest_version[1:]}
+    except Exception as e:
+        log.debug(e)
+        return {"current": VERSION, "latest": VERSION}
+
+
+############################
+# OAuth Login & Callback
+############################
+
+# SessionMiddleware is used by authlib for oauth
+if len(OAUTH_PROVIDERS) > 0:
+    app.add_middleware(
+        SessionMiddleware,
+        secret_key=WEBUI_SECRET_KEY,
+        session_cookie="oui-session",
+        same_site=WEBUI_SESSION_COOKIE_SAME_SITE,
+        https_only=WEBUI_SESSION_COOKIE_SECURE,
+    )
+
+
+@app.get("/oauth/{provider}/login")
+async def oauth_login(provider: str, request: Request):
+    return await oauth_manager.handle_login(provider, request)
+
+
+# OAuth login logic is as follows:
+# 1. Attempt to find a user with matching subject ID, tied to the provider
+# 2. If OAUTH_MERGE_ACCOUNTS_BY_EMAIL is true, find a user with the email address provided via OAuth
+#    - This is considered insecure in general, as OAuth providers do not always verify email addresses
+# 3. If there is no user, and ENABLE_OAUTH_SIGNUP is true, create a user
+#    - Email addresses are considered unique, so we fail registration if the email address is already taken
+@app.get("/oauth/{provider}/callback")
+async def oauth_callback(provider: str, request: Request, response: Response):
+    return await oauth_manager.handle_callback(provider, request, response)
+
+
+@app.get("/manifest.json")
+async def get_manifest_json():
+    return {
+        "name": WEBUI_NAME,
+        "short_name": WEBUI_NAME,
+        "description": "Open WebUI is an open, extensible, user-friendly interface for AI that adapts to your workflow.",
+        "start_url": "/",
+        "display": "standalone",
+        "background_color": "#343541",
+        "orientation": "any",
+        "icons": [
+            {
+                "src": "/static/logo.png",
+                "type": "image/png",
+                "sizes": "500x500",
+                "purpose": "any",
+            },
+            {
+                "src": "/static/logo.png",
+                "type": "image/png",
+                "sizes": "500x500",
+                "purpose": "maskable",
+            },
+        ],
+    }
+
+
+@app.get("/opensearch.xml")
+async def get_opensearch_xml():
+    xml_content = rf"""
+    <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+    <ShortName>{WEBUI_NAME}</ShortName>
+    <Description>Search {WEBUI_NAME}</Description>
+    <InputEncoding>UTF-8</InputEncoding>
+    <Image width="16" height="16" type="image/x-icon">{WEBUI_URL}/static/favicon.png</Image>
+    <Url type="text/html" method="get" template="{WEBUI_URL}/?q={"{searchTerms}"}"/>
+    <moz:SearchForm>{WEBUI_URL}</moz:SearchForm>
+    </OpenSearchDescription>
+    """
+    return Response(content=xml_content, media_type="application/xml")
+
+
+@app.get("/health")
+async def healthcheck():
+    return {"status": True}
+
+
+@app.get("/health/db")
+async def healthcheck_with_db():
+    Session.execute(text("SELECT 1;")).all()
+    return {"status": True}
+
+
+app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
+app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache")
+
+
+if os.path.exists(FRONTEND_BUILD_DIR):
+    mimetypes.add_type("text/javascript", ".js")
+    app.mount(
+        "/",
+        SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True),
+        name="spa-static-files",
+    )
+else:
+    log.warning(
+        f"Frontend build directory not found at '{FRONTEND_BUILD_DIR}'. Serving API only."
+    )
diff --git a/backend/open_webui/migrations/README b/backend/open_webui/migrations/README
new file mode 100644
index 0000000000000000000000000000000000000000..f1d93dff9dbd52e0a9fc8ce4d68e0784c07da697
--- /dev/null
+++ b/backend/open_webui/migrations/README
@@ -0,0 +1,4 @@
+Generic single-database configuration.
+
+Create new migrations with
+DATABASE_URL=<replace with actual url> alembic revision --autogenerate -m "a description"
diff --git a/backend/open_webui/migrations/env.py b/backend/open_webui/migrations/env.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e860c8a0590f2401008ac80e9343de351e4e0ed
--- /dev/null
+++ b/backend/open_webui/migrations/env.py
@@ -0,0 +1,81 @@
+from logging.config import fileConfig
+
+from alembic import context
+from open_webui.apps.webui.models.auths import Auth
+from open_webui.env import DATABASE_URL
+from sqlalchemy import engine_from_config, pool
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+if config.config_file_name is not None:
+    fileConfig(config.config_file_name, disable_existing_loggers=False)
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+target_metadata = Auth.metadata
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+DB_URL = DATABASE_URL
+
+if DB_URL:
+    config.set_main_option("sqlalchemy.url", DB_URL.replace("%", "%%"))
+
+
+def run_migrations_offline() -> None:
+    """Run migrations in 'offline' mode.
+
+    This configures the context with just a URL
+    and not an Engine, though an Engine is acceptable
+    here as well.  By skipping the Engine creation
+    we don't even need a DBAPI to be available.
+
+    Calls to context.execute() here emit the given string to the
+    script output.
+
+    """
+    url = config.get_main_option("sqlalchemy.url")
+    context.configure(
+        url=url,
+        target_metadata=target_metadata,
+        literal_binds=True,
+        dialect_opts={"paramstyle": "named"},
+    )
+
+    with context.begin_transaction():
+        context.run_migrations()
+
+
+def run_migrations_online() -> None:
+    """Run migrations in 'online' mode.
+
+    In this scenario we need to create an Engine
+    and associate a connection with the context.
+
+    """
+    connectable = engine_from_config(
+        config.get_section(config.config_ini_section, {}),
+        prefix="sqlalchemy.",
+        poolclass=pool.NullPool,
+    )
+
+    with connectable.connect() as connection:
+        context.configure(connection=connection, target_metadata=target_metadata)
+
+        with context.begin_transaction():
+            context.run_migrations()
+
+
+if context.is_offline_mode():
+    run_migrations_offline()
+else:
+    run_migrations_online()
diff --git a/backend/open_webui/migrations/script.py.mako b/backend/open_webui/migrations/script.py.mako
new file mode 100644
index 0000000000000000000000000000000000000000..01e730e77d23fdb17a24aa5294a8b68f1a98f85a
--- /dev/null
+++ b/backend/open_webui/migrations/script.py.mako
@@ -0,0 +1,27 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+import open_webui.apps.webui.internal.db
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision: str = ${repr(up_revision)}
+down_revision: Union[str, None] = ${repr(down_revision)}
+branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
+depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
+
+
+def upgrade() -> None:
+    ${upgrades if upgrades else "pass"}
+
+
+def downgrade() -> None:
+    ${downgrades if downgrades else "pass"}
diff --git a/backend/open_webui/migrations/util.py b/backend/open_webui/migrations/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..955066602a5d94a5d001b169694748d49efed1c1
--- /dev/null
+++ b/backend/open_webui/migrations/util.py
@@ -0,0 +1,15 @@
+from alembic import op
+from sqlalchemy import Inspector
+
+
+def get_existing_tables():
+    con = op.get_bind()
+    inspector = Inspector.from_engine(con)
+    tables = set(inspector.get_table_names())
+    return tables
+
+
+def get_revision_id():
+    import uuid
+
+    return str(uuid.uuid4()).replace("-", "")[:12]
diff --git a/backend/open_webui/migrations/versions/1af9b942657b_migrate_tags.py b/backend/open_webui/migrations/versions/1af9b942657b_migrate_tags.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a0ab1b491d5a136d19fd1d79788a88c60a8c941
--- /dev/null
+++ b/backend/open_webui/migrations/versions/1af9b942657b_migrate_tags.py
@@ -0,0 +1,151 @@
+"""Migrate tags
+
+Revision ID: 1af9b942657b
+Revises: 242a2047eae0
+Create Date: 2024-10-09 21:02:35.241684
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.sql import table, select, update, column
+from sqlalchemy.engine.reflection import Inspector
+
+import json
+
+revision = "1af9b942657b"
+down_revision = "242a2047eae0"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # Setup an inspection on the existing table to avoid issues
+    conn = op.get_bind()
+    inspector = Inspector.from_engine(conn)
+
+    # Clean up potential leftover temp table from previous failures
+    conn.execute(sa.text("DROP TABLE IF EXISTS _alembic_tmp_tag"))
+
+    # Check if the 'tag' table exists
+    tables = inspector.get_table_names()
+
+    # Step 1: Modify Tag table using batch mode for SQLite support
+    if "tag" in tables:
+        # Get the current columns in the 'tag' table
+        columns = [col["name"] for col in inspector.get_columns("tag")]
+
+        # Get any existing unique constraints on the 'tag' table
+        current_constraints = inspector.get_unique_constraints("tag")
+
+        with op.batch_alter_table("tag", schema=None) as batch_op:
+            # Check if the unique constraint already exists
+            if not any(
+                constraint["name"] == "uq_id_user_id"
+                for constraint in current_constraints
+            ):
+                # Create unique constraint if it doesn't exist
+                batch_op.create_unique_constraint("uq_id_user_id", ["id", "user_id"])
+
+            # Check if the 'data' column exists before trying to drop it
+            if "data" in columns:
+                batch_op.drop_column("data")
+
+            # Check if the 'meta' column needs to be created
+            if "meta" not in columns:
+                # Add the 'meta' column if it doesn't already exist
+                batch_op.add_column(sa.Column("meta", sa.JSON(), nullable=True))
+
+    tag = table(
+        "tag",
+        column("id", sa.String()),
+        column("name", sa.String()),
+        column("user_id", sa.String()),
+        column("meta", sa.JSON()),
+    )
+
+    # Step 2: Migrate tags
+    conn = op.get_bind()
+    result = conn.execute(sa.select(tag.c.id, tag.c.name, tag.c.user_id))
+
+    tag_updates = {}
+    for row in result:
+        new_id = row.name.replace(" ", "_").lower()
+        tag_updates[row.id] = new_id
+
+    for tag_id, new_tag_id in tag_updates.items():
+        print(f"Updating tag {tag_id} to {new_tag_id}")
+        if new_tag_id == "pinned":
+            # delete tag
+            delete_stmt = sa.delete(tag).where(tag.c.id == tag_id)
+            conn.execute(delete_stmt)
+        else:
+            # Check if the new_tag_id already exists in the database
+            existing_tag_query = sa.select(tag.c.id).where(tag.c.id == new_tag_id)
+            existing_tag_result = conn.execute(existing_tag_query).fetchone()
+
+            if existing_tag_result:
+                # Handle duplicate case: the new_tag_id already exists
+                print(
+                    f"Tag {new_tag_id} already exists. Removing current tag with ID {tag_id} to avoid duplicates."
+                )
+                # Option 1: Delete the current tag if an update to new_tag_id would cause duplication
+                delete_stmt = sa.delete(tag).where(tag.c.id == tag_id)
+                conn.execute(delete_stmt)
+            else:
+                update_stmt = sa.update(tag).where(tag.c.id == tag_id)
+                update_stmt = update_stmt.values(id=new_tag_id)
+                conn.execute(update_stmt)
+
+    # Add columns `pinned` and `meta` to 'chat'
+    op.add_column("chat", sa.Column("pinned", sa.Boolean(), nullable=True))
+    op.add_column(
+        "chat", sa.Column("meta", sa.JSON(), nullable=False, server_default="{}")
+    )
+
+    chatidtag = table(
+        "chatidtag", column("chat_id", sa.String()), column("tag_name", sa.String())
+    )
+    chat = table(
+        "chat",
+        column("id", sa.String()),
+        column("pinned", sa.Boolean()),
+        column("meta", sa.JSON()),
+    )
+
+    # Fetch existing tags
+    conn = op.get_bind()
+    result = conn.execute(sa.select(chatidtag.c.chat_id, chatidtag.c.tag_name))
+
+    chat_updates = {}
+    for row in result:
+        chat_id = row.chat_id
+        tag_name = row.tag_name.replace(" ", "_").lower()
+
+        if tag_name == "pinned":
+            # Specifically handle 'pinned' tag
+            if chat_id not in chat_updates:
+                chat_updates[chat_id] = {"pinned": True, "meta": {}}
+            else:
+                chat_updates[chat_id]["pinned"] = True
+        else:
+            if chat_id not in chat_updates:
+                chat_updates[chat_id] = {"pinned": False, "meta": {"tags": [tag_name]}}
+            else:
+                tags = chat_updates[chat_id]["meta"].get("tags", [])
+                tags.append(tag_name)
+
+                chat_updates[chat_id]["meta"]["tags"] = list(set(tags))
+
+    # Update chats based on accumulated changes
+    for chat_id, updates in chat_updates.items():
+        update_stmt = sa.update(chat).where(chat.c.id == chat_id)
+        update_stmt = update_stmt.values(
+            meta=updates.get("meta", {}), pinned=updates.get("pinned", False)
+        )
+        conn.execute(update_stmt)
+    pass
+
+
+def downgrade():
+    pass
diff --git a/backend/open_webui/migrations/versions/242a2047eae0_update_chat_table.py b/backend/open_webui/migrations/versions/242a2047eae0_update_chat_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..6017da31695b37185c30c085f751f5d72b392e5d
--- /dev/null
+++ b/backend/open_webui/migrations/versions/242a2047eae0_update_chat_table.py
@@ -0,0 +1,107 @@
+"""Update chat table
+
+Revision ID: 242a2047eae0
+Revises: 6a39f3d8e55c
+Create Date: 2024-10-09 21:02:35.241684
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.sql import table, select, update
+
+import json
+
+revision = "242a2047eae0"
+down_revision = "6a39f3d8e55c"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    conn = op.get_bind()
+    inspector = sa.inspect(conn)
+
+    columns = inspector.get_columns("chat")
+    column_dict = {col["name"]: col for col in columns}
+
+    chat_column = column_dict.get("chat")
+    old_chat_exists = "old_chat" in column_dict
+
+    if chat_column:
+        if isinstance(chat_column["type"], sa.Text):
+            print("Converting 'chat' column to JSON")
+
+            if old_chat_exists:
+                print("Dropping old 'old_chat' column")
+                op.drop_column("chat", "old_chat")
+
+            # Step 1: Rename current 'chat' column to 'old_chat'
+            print("Renaming 'chat' column to 'old_chat'")
+            op.alter_column(
+                "chat", "chat", new_column_name="old_chat", existing_type=sa.Text()
+            )
+
+            # Step 2: Add new 'chat' column of type JSON
+            print("Adding new 'chat' column of type JSON")
+            op.add_column("chat", sa.Column("chat", sa.JSON(), nullable=True))
+        else:
+            # If the column is already JSON, no need to do anything
+            pass
+
+    # Step 3: Migrate data from 'old_chat' to 'chat'
+    chat_table = table(
+        "chat",
+        sa.Column("id", sa.String(), primary_key=True),
+        sa.Column("old_chat", sa.Text()),
+        sa.Column("chat", sa.JSON()),
+    )
+
+    # - Selecting all data from the table
+    connection = op.get_bind()
+    results = connection.execute(select(chat_table.c.id, chat_table.c.old_chat))
+    for row in results:
+        try:
+            # Convert text JSON to actual JSON object, assuming the text is in JSON format
+            json_data = json.loads(row.old_chat)
+        except json.JSONDecodeError:
+            json_data = None  # Handle cases where the text cannot be converted to JSON
+
+        connection.execute(
+            sa.update(chat_table)
+            .where(chat_table.c.id == row.id)
+            .values(chat=json_data)
+        )
+
+    # Step 4: Drop 'old_chat' column
+    print("Dropping 'old_chat' column")
+    op.drop_column("chat", "old_chat")
+
+
+def downgrade():
+    # Step 1: Add 'old_chat' column back as Text
+    op.add_column("chat", sa.Column("old_chat", sa.Text(), nullable=True))
+
+    # Step 2: Convert 'chat' JSON data back to text and store in 'old_chat'
+    chat_table = table(
+        "chat",
+        sa.Column("id", sa.String(), primary_key=True),
+        sa.Column("chat", sa.JSON()),
+        sa.Column("old_chat", sa.Text()),
+    )
+
+    connection = op.get_bind()
+    results = connection.execute(select(chat_table.c.id, chat_table.c.chat))
+    for row in results:
+        text_data = json.dumps(row.chat) if row.chat is not None else None
+        connection.execute(
+            sa.update(chat_table)
+            .where(chat_table.c.id == row.id)
+            .values(old_chat=text_data)
+        )
+
+    # Step 3: Remove the new 'chat' JSON column
+    op.drop_column("chat", "chat")
+
+    # Step 4: Rename 'old_chat' back to 'chat'
+    op.alter_column("chat", "old_chat", new_column_name="chat", existing_type=sa.Text())
diff --git a/backend/open_webui/migrations/versions/3ab32c4b8f59_update_tags.py b/backend/open_webui/migrations/versions/3ab32c4b8f59_update_tags.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e010424b05242c15f6998f88f7efa4cde30aa73
--- /dev/null
+++ b/backend/open_webui/migrations/versions/3ab32c4b8f59_update_tags.py
@@ -0,0 +1,81 @@
+"""Update tags
+
+Revision ID: 3ab32c4b8f59
+Revises: 1af9b942657b
+Create Date: 2024-10-09 21:02:35.241684
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.sql import table, select, update, column
+from sqlalchemy.engine.reflection import Inspector
+
+import json
+
+revision = "3ab32c4b8f59"
+down_revision = "1af9b942657b"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    conn = op.get_bind()
+    inspector = Inspector.from_engine(conn)
+
+    # Inspecting the 'tag' table constraints and structure
+    existing_pk = inspector.get_pk_constraint("tag")
+    unique_constraints = inspector.get_unique_constraints("tag")
+    existing_indexes = inspector.get_indexes("tag")
+
+    print(f"Primary Key: {existing_pk}")
+    print(f"Unique Constraints: {unique_constraints}")
+    print(f"Indexes: {existing_indexes}")
+
+    with op.batch_alter_table("tag", schema=None) as batch_op:
+        # Drop existing primary key constraint if it exists
+        if existing_pk and existing_pk.get("constrained_columns"):
+            pk_name = existing_pk.get("name")
+            if pk_name:
+                print(f"Dropping primary key constraint: {pk_name}")
+                batch_op.drop_constraint(pk_name, type_="primary")
+
+        # Now create the new primary key with the combination of 'id' and 'user_id'
+        print("Creating new primary key with 'id' and 'user_id'.")
+        batch_op.create_primary_key("pk_id_user_id", ["id", "user_id"])
+
+        # Drop unique constraints that could conflict with the new primary key
+        for constraint in unique_constraints:
+            if (
+                constraint["name"] == "uq_id_user_id"
+            ):  # Adjust this name according to what is actually returned by the inspector
+                print(f"Dropping unique constraint: {constraint['name']}")
+                batch_op.drop_constraint(constraint["name"], type_="unique")
+
+        for index in existing_indexes:
+            if index["unique"]:
+                if not any(
+                    constraint["name"] == index["name"]
+                    for constraint in unique_constraints
+                ):
+                    # You are attempting to drop unique indexes
+                    print(f"Dropping unique index: {index['name']}")
+                    batch_op.drop_index(index["name"])
+
+
+def downgrade():
+    conn = op.get_bind()
+    inspector = Inspector.from_engine(conn)
+
+    current_pk = inspector.get_pk_constraint("tag")
+
+    with op.batch_alter_table("tag", schema=None) as batch_op:
+        # Drop the current primary key first, if it matches the one we know we added in upgrade
+        if current_pk and "pk_id_user_id" == current_pk.get("name"):
+            batch_op.drop_constraint("pk_id_user_id", type_="primary")
+
+        # Restore the original primary key
+        batch_op.create_primary_key("pk_id", ["id"])
+
+        # Since primary key on just 'id' is restored, we now add back any unique constraints if necessary
+        batch_op.create_unique_constraint("uq_id_user_id", ["id", "user_id"])
diff --git a/backend/open_webui/migrations/versions/4ace53fd72c8_update_folder_table_datetime.py b/backend/open_webui/migrations/versions/4ace53fd72c8_update_folder_table_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..16f7967c8ec8c4e6c65e8d17c1ff2f8ac4a0a102
--- /dev/null
+++ b/backend/open_webui/migrations/versions/4ace53fd72c8_update_folder_table_datetime.py
@@ -0,0 +1,67 @@
+"""Update folder table and change DateTime to BigInteger for timestamp fields
+
+Revision ID: 4ace53fd72c8
+Revises: af906e964978
+Create Date: 2024-10-23 03:00:00.000000
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+
+revision = "4ace53fd72c8"
+down_revision = "af906e964978"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # Perform safe alterations using batch operation
+    with op.batch_alter_table("folder", schema=None) as batch_op:
+        # Step 1: Remove server defaults for created_at and updated_at
+        batch_op.alter_column(
+            "created_at",
+            server_default=None,  # Removing server default
+        )
+        batch_op.alter_column(
+            "updated_at",
+            server_default=None,  # Removing server default
+        )
+
+        # Step 2: Change the column types to BigInteger for created_at
+        batch_op.alter_column(
+            "created_at",
+            type_=sa.BigInteger(),
+            existing_type=sa.DateTime(),
+            existing_nullable=False,
+            postgresql_using="extract(epoch from created_at)::bigint",  # Conversion for PostgreSQL
+        )
+
+        # Change the column types to BigInteger for updated_at
+        batch_op.alter_column(
+            "updated_at",
+            type_=sa.BigInteger(),
+            existing_type=sa.DateTime(),
+            existing_nullable=False,
+            postgresql_using="extract(epoch from updated_at)::bigint",  # Conversion for PostgreSQL
+        )
+
+
+def downgrade():
+    # Downgrade: Convert columns back to DateTime and restore defaults
+    with op.batch_alter_table("folder", schema=None) as batch_op:
+        batch_op.alter_column(
+            "created_at",
+            type_=sa.DateTime(),
+            existing_type=sa.BigInteger(),
+            existing_nullable=False,
+            server_default=sa.func.now(),  # Restoring server default on downgrade
+        )
+        batch_op.alter_column(
+            "updated_at",
+            type_=sa.DateTime(),
+            existing_type=sa.BigInteger(),
+            existing_nullable=False,
+            server_default=sa.func.now(),  # Restoring server default on downgrade
+            onupdate=sa.func.now(),  # Restoring onupdate behavior if it was there
+        )
diff --git a/backend/open_webui/migrations/versions/6a39f3d8e55c_add_knowledge_table.py b/backend/open_webui/migrations/versions/6a39f3d8e55c_add_knowledge_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..881e6ae64126281e03ef09397f212d2e257c9623
--- /dev/null
+++ b/backend/open_webui/migrations/versions/6a39f3d8e55c_add_knowledge_table.py
@@ -0,0 +1,80 @@
+"""Add knowledge table
+
+Revision ID: 6a39f3d8e55c
+Revises: c0fbf31ca0db
+Create Date: 2024-10-01 14:02:35.241684
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.sql import table, column, select
+import json
+
+
+revision = "6a39f3d8e55c"
+down_revision = "c0fbf31ca0db"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # Creating the 'knowledge' table
+    print("Creating knowledge table")
+    knowledge_table = op.create_table(
+        "knowledge",
+        sa.Column("id", sa.Text(), primary_key=True),
+        sa.Column("user_id", sa.Text(), nullable=False),
+        sa.Column("name", sa.Text(), nullable=False),
+        sa.Column("description", sa.Text(), nullable=True),
+        sa.Column("data", sa.JSON(), nullable=True),
+        sa.Column("meta", sa.JSON(), nullable=True),
+        sa.Column("created_at", sa.BigInteger(), nullable=False),
+        sa.Column("updated_at", sa.BigInteger(), nullable=True),
+    )
+
+    print("Migrating data from document table to knowledge table")
+    # Representation of the existing 'document' table
+    document_table = table(
+        "document",
+        column("collection_name", sa.String()),
+        column("user_id", sa.String()),
+        column("name", sa.String()),
+        column("title", sa.Text()),
+        column("content", sa.Text()),
+        column("timestamp", sa.BigInteger()),
+    )
+
+    # Select all from existing document table
+    documents = op.get_bind().execute(
+        select(
+            document_table.c.collection_name,
+            document_table.c.user_id,
+            document_table.c.name,
+            document_table.c.title,
+            document_table.c.content,
+            document_table.c.timestamp,
+        )
+    )
+
+    # Insert data into knowledge table from document table
+    for doc in documents:
+        op.get_bind().execute(
+            knowledge_table.insert().values(
+                id=doc.collection_name,
+                user_id=doc.user_id,
+                description=doc.name,
+                meta={
+                    "legacy": True,
+                    "document": True,
+                    "tags": json.loads(doc.content or "{}").get("tags", []),
+                },
+                name=doc.title,
+                created_at=doc.timestamp,
+                updated_at=doc.timestamp,  # using created_at for both created_at and updated_at in project
+            )
+        )
+
+
+def downgrade():
+    op.drop_table("knowledge")
diff --git a/backend/open_webui/migrations/versions/7e5b5dc7342b_init.py b/backend/open_webui/migrations/versions/7e5b5dc7342b_init.py
new file mode 100644
index 0000000000000000000000000000000000000000..607a7b2c91dd021e4bf8a3923ccf21e04ca7de88
--- /dev/null
+++ b/backend/open_webui/migrations/versions/7e5b5dc7342b_init.py
@@ -0,0 +1,204 @@
+"""init
+
+Revision ID: 7e5b5dc7342b
+Revises:
+Create Date: 2024-06-24 13:15:33.808998
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+
+import open_webui.apps.webui.internal.db
+from open_webui.apps.webui.internal.db import JSONField
+from open_webui.migrations.util import get_existing_tables
+
+# revision identifiers, used by Alembic.
+revision: str = "7e5b5dc7342b"
+down_revision: Union[str, None] = None
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+    existing_tables = set(get_existing_tables())
+
+    # ### commands auto generated by Alembic - please adjust! ###
+    if "auth" not in existing_tables:
+        op.create_table(
+            "auth",
+            sa.Column("id", sa.String(), nullable=False),
+            sa.Column("email", sa.String(), nullable=True),
+            sa.Column("password", sa.Text(), nullable=True),
+            sa.Column("active", sa.Boolean(), nullable=True),
+            sa.PrimaryKeyConstraint("id"),
+        )
+
+    if "chat" not in existing_tables:
+        op.create_table(
+            "chat",
+            sa.Column("id", sa.String(), nullable=False),
+            sa.Column("user_id", sa.String(), nullable=True),
+            sa.Column("title", sa.Text(), nullable=True),
+            sa.Column("chat", sa.Text(), nullable=True),
+            sa.Column("created_at", sa.BigInteger(), nullable=True),
+            sa.Column("updated_at", sa.BigInteger(), nullable=True),
+            sa.Column("share_id", sa.Text(), nullable=True),
+            sa.Column("archived", sa.Boolean(), nullable=True),
+            sa.PrimaryKeyConstraint("id"),
+            sa.UniqueConstraint("share_id"),
+        )
+
+    if "chatidtag" not in existing_tables:
+        op.create_table(
+            "chatidtag",
+            sa.Column("id", sa.String(), nullable=False),
+            sa.Column("tag_name", sa.String(), nullable=True),
+            sa.Column("chat_id", sa.String(), nullable=True),
+            sa.Column("user_id", sa.String(), nullable=True),
+            sa.Column("timestamp", sa.BigInteger(), nullable=True),
+            sa.PrimaryKeyConstraint("id"),
+        )
+
+    if "document" not in existing_tables:
+        op.create_table(
+            "document",
+            sa.Column("collection_name", sa.String(), nullable=False),
+            sa.Column("name", sa.String(), nullable=True),
+            sa.Column("title", sa.Text(), nullable=True),
+            sa.Column("filename", sa.Text(), nullable=True),
+            sa.Column("content", sa.Text(), nullable=True),
+            sa.Column("user_id", sa.String(), nullable=True),
+            sa.Column("timestamp", sa.BigInteger(), nullable=True),
+            sa.PrimaryKeyConstraint("collection_name"),
+            sa.UniqueConstraint("name"),
+        )
+
+    if "file" not in existing_tables:
+        op.create_table(
+            "file",
+            sa.Column("id", sa.String(), nullable=False),
+            sa.Column("user_id", sa.String(), nullable=True),
+            sa.Column("filename", sa.Text(), nullable=True),
+            sa.Column("meta", JSONField(), nullable=True),
+            sa.Column("created_at", sa.BigInteger(), nullable=True),
+            sa.PrimaryKeyConstraint("id"),
+        )
+
+    if "function" not in existing_tables:
+        op.create_table(
+            "function",
+            sa.Column("id", sa.String(), nullable=False),
+            sa.Column("user_id", sa.String(), nullable=True),
+            sa.Column("name", sa.Text(), nullable=True),
+            sa.Column("type", sa.Text(), nullable=True),
+            sa.Column("content", sa.Text(), nullable=True),
+            sa.Column("meta", JSONField(), nullable=True),
+            sa.Column("valves", JSONField(), nullable=True),
+            sa.Column("is_active", sa.Boolean(), nullable=True),
+            sa.Column("is_global", sa.Boolean(), nullable=True),
+            sa.Column("updated_at", sa.BigInteger(), nullable=True),
+            sa.Column("created_at", sa.BigInteger(), nullable=True),
+            sa.PrimaryKeyConstraint("id"),
+        )
+
+    if "memory" not in existing_tables:
+        op.create_table(
+            "memory",
+            sa.Column("id", sa.String(), nullable=False),
+            sa.Column("user_id", sa.String(), nullable=True),
+            sa.Column("content", sa.Text(), nullable=True),
+            sa.Column("updated_at", sa.BigInteger(), nullable=True),
+            sa.Column("created_at", sa.BigInteger(), nullable=True),
+            sa.PrimaryKeyConstraint("id"),
+        )
+
+    if "model" not in existing_tables:
+        op.create_table(
+            "model",
+            sa.Column("id", sa.Text(), nullable=False),
+            sa.Column("user_id", sa.Text(), nullable=True),
+            sa.Column("base_model_id", sa.Text(), nullable=True),
+            sa.Column("name", sa.Text(), nullable=True),
+            sa.Column("params", JSONField(), nullable=True),
+            sa.Column("meta", JSONField(), nullable=True),
+            sa.Column("updated_at", sa.BigInteger(), nullable=True),
+            sa.Column("created_at", sa.BigInteger(), nullable=True),
+            sa.PrimaryKeyConstraint("id"),
+        )
+
+    if "prompt" not in existing_tables:
+        op.create_table(
+            "prompt",
+            sa.Column("command", sa.String(), nullable=False),
+            sa.Column("user_id", sa.String(), nullable=True),
+            sa.Column("title", sa.Text(), nullable=True),
+            sa.Column("content", sa.Text(), nullable=True),
+            sa.Column("timestamp", sa.BigInteger(), nullable=True),
+            sa.PrimaryKeyConstraint("command"),
+        )
+
+    if "tag" not in existing_tables:
+        op.create_table(
+            "tag",
+            sa.Column("id", sa.String(), nullable=False),
+            sa.Column("name", sa.String(), nullable=True),
+            sa.Column("user_id", sa.String(), nullable=True),
+            sa.Column("data", sa.Text(), nullable=True),
+            sa.PrimaryKeyConstraint("id"),
+        )
+
+    if "tool" not in existing_tables:
+        op.create_table(
+            "tool",
+            sa.Column("id", sa.String(), nullable=False),
+            sa.Column("user_id", sa.String(), nullable=True),
+            sa.Column("name", sa.Text(), nullable=True),
+            sa.Column("content", sa.Text(), nullable=True),
+            sa.Column("specs", JSONField(), nullable=True),
+            sa.Column("meta", JSONField(), nullable=True),
+            sa.Column("valves", JSONField(), nullable=True),
+            sa.Column("updated_at", sa.BigInteger(), nullable=True),
+            sa.Column("created_at", sa.BigInteger(), nullable=True),
+            sa.PrimaryKeyConstraint("id"),
+        )
+
+    if "user" not in existing_tables:
+        op.create_table(
+            "user",
+            sa.Column("id", sa.String(), nullable=False),
+            sa.Column("name", sa.String(), nullable=True),
+            sa.Column("email", sa.String(), nullable=True),
+            sa.Column("role", sa.String(), nullable=True),
+            sa.Column("profile_image_url", sa.Text(), nullable=True),
+            sa.Column("last_active_at", sa.BigInteger(), nullable=True),
+            sa.Column("updated_at", sa.BigInteger(), nullable=True),
+            sa.Column("created_at", sa.BigInteger(), nullable=True),
+            sa.Column("api_key", sa.String(), nullable=True),
+            sa.Column("settings", JSONField(), nullable=True),
+            sa.Column("info", JSONField(), nullable=True),
+            sa.Column("oauth_sub", sa.Text(), nullable=True),
+            sa.PrimaryKeyConstraint("id"),
+            sa.UniqueConstraint("api_key"),
+            sa.UniqueConstraint("oauth_sub"),
+        )
+    # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_table("user")
+    op.drop_table("tool")
+    op.drop_table("tag")
+    op.drop_table("prompt")
+    op.drop_table("model")
+    op.drop_table("memory")
+    op.drop_table("function")
+    op.drop_table("file")
+    op.drop_table("document")
+    op.drop_table("chatidtag")
+    op.drop_table("chat")
+    op.drop_table("auth")
+    # ### end Alembic commands ###
diff --git a/backend/open_webui/migrations/versions/af906e964978_add_feedback_table.py b/backend/open_webui/migrations/versions/af906e964978_add_feedback_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..9116aa38843259b8e7c7f855b46cec124587bc46
--- /dev/null
+++ b/backend/open_webui/migrations/versions/af906e964978_add_feedback_table.py
@@ -0,0 +1,51 @@
+"""Add feedback table
+
+Revision ID: af906e964978
+Revises: c29facfe716b
+Create Date: 2024-10-20 17:02:35.241684
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+
+# Revision identifiers, used by Alembic.
+revision = "af906e964978"
+down_revision = "c29facfe716b"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### Create feedback table ###
+    op.create_table(
+        "feedback",
+        sa.Column(
+            "id", sa.Text(), primary_key=True
+        ),  # Unique identifier for each feedback (TEXT type)
+        sa.Column(
+            "user_id", sa.Text(), nullable=True
+        ),  # ID of the user providing the feedback (TEXT type)
+        sa.Column(
+            "version", sa.BigInteger(), default=0
+        ),  # Version of feedback (BIGINT type)
+        sa.Column("type", sa.Text(), nullable=True),  # Type of feedback (TEXT type)
+        sa.Column("data", sa.JSON(), nullable=True),  # Feedback data (JSON type)
+        sa.Column(
+            "meta", sa.JSON(), nullable=True
+        ),  # Metadata for feedback (JSON type)
+        sa.Column(
+            "snapshot", sa.JSON(), nullable=True
+        ),  # snapshot data for feedback (JSON type)
+        sa.Column(
+            "created_at", sa.BigInteger(), nullable=False
+        ),  # Feedback creation timestamp (BIGINT representing epoch)
+        sa.Column(
+            "updated_at", sa.BigInteger(), nullable=False
+        ),  # Feedback update timestamp (BIGINT representing epoch)
+    )
+
+
+def downgrade():
+    # ### Drop feedback table ###
+    op.drop_table("feedback")
diff --git a/backend/open_webui/migrations/versions/c0fbf31ca0db_update_file_table.py b/backend/open_webui/migrations/versions/c0fbf31ca0db_update_file_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f7f2abf706f8fdeb03503a05497744c74bac8e5
--- /dev/null
+++ b/backend/open_webui/migrations/versions/c0fbf31ca0db_update_file_table.py
@@ -0,0 +1,32 @@
+"""Update file table
+
+Revision ID: c0fbf31ca0db
+Revises: ca81bd47c050
+Create Date: 2024-09-20 15:26:35.241684
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision: str = "c0fbf31ca0db"
+down_revision: Union[str, None] = "ca81bd47c050"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column("file", sa.Column("hash", sa.Text(), nullable=True))
+    op.add_column("file", sa.Column("data", sa.JSON(), nullable=True))
+    op.add_column("file", sa.Column("updated_at", sa.BigInteger(), nullable=True))
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column("file", "updated_at")
+    op.drop_column("file", "data")
+    op.drop_column("file", "hash")
diff --git a/backend/open_webui/migrations/versions/c29facfe716b_update_file_table_path.py b/backend/open_webui/migrations/versions/c29facfe716b_update_file_table_path.py
new file mode 100644
index 0000000000000000000000000000000000000000..de82854b8811ed75c85da2c4c3e9229cef89f028
--- /dev/null
+++ b/backend/open_webui/migrations/versions/c29facfe716b_update_file_table_path.py
@@ -0,0 +1,79 @@
+"""Update file table path
+
+Revision ID: c29facfe716b
+Revises: c69f45358db4
+Create Date: 2024-10-20 17:02:35.241684
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+import json
+from sqlalchemy.sql import table, column
+from sqlalchemy import String, Text, JSON, and_
+
+
+revision = "c29facfe716b"
+down_revision = "c69f45358db4"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # 1. Add the `path` column to the "file" table.
+    op.add_column("file", sa.Column("path", sa.Text(), nullable=True))
+
+    # 2. Convert the `meta` column from Text/JSONField to `JSON()`
+    # Use Alembic's default batch_op for dialect compatibility.
+    with op.batch_alter_table("file", schema=None) as batch_op:
+        batch_op.alter_column(
+            "meta",
+            type_=sa.JSON(),
+            existing_type=sa.Text(),
+            existing_nullable=True,
+            nullable=True,
+            postgresql_using="meta::json",
+        )
+
+    # 3. Migrate legacy data from `meta` JSONField
+    # Fetch and process `meta` data from the table, add values to the new `path` column as necessary.
+    # We will use SQLAlchemy core bindings to ensure safety across different databases.
+
+    file_table = table(
+        "file", column("id", String), column("meta", JSON), column("path", Text)
+    )
+
+    # Create connection to the database
+    connection = op.get_bind()
+
+    # Get the rows where `meta` has a path and `path` column is null (new column)
+    # Loop through each row in the result set to update the path
+    results = connection.execute(
+        sa.select(file_table.c.id, file_table.c.meta).where(
+            and_(file_table.c.path.is_(None), file_table.c.meta.isnot(None))
+        )
+    ).fetchall()
+
+    # Iterate over each row to extract and update the `path` from `meta` column
+    for row in results:
+        if "path" in row.meta:
+            # Extract the `path` field from the `meta` JSON
+            path = row.meta.get("path")
+
+            # Update the `file` table with the new `path` value
+            connection.execute(
+                file_table.update()
+                .where(file_table.c.id == row.id)
+                .values({"path": path})
+            )
+
+
+def downgrade():
+    # 1. Remove the `path` column
+    op.drop_column("file", "path")
+
+    # 2. Revert the `meta` column back to Text/JSONField
+    with op.batch_alter_table("file", schema=None) as batch_op:
+        batch_op.alter_column(
+            "meta", type_=sa.Text(), existing_type=sa.JSON(), existing_nullable=True
+        )
diff --git a/backend/open_webui/migrations/versions/c69f45358db4_add_folder_table.py b/backend/open_webui/migrations/versions/c69f45358db4_add_folder_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..83e0dc28edcfe76e5742faa8c078523b1cfb4269
--- /dev/null
+++ b/backend/open_webui/migrations/versions/c69f45358db4_add_folder_table.py
@@ -0,0 +1,50 @@
+"""Add folder table
+
+Revision ID: c69f45358db4
+Revises: 3ab32c4b8f59
+Create Date: 2024-10-16 02:02:35.241684
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+
+revision = "c69f45358db4"
+down_revision = "3ab32c4b8f59"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    op.create_table(
+        "folder",
+        sa.Column("id", sa.Text(), nullable=False),
+        sa.Column("parent_id", sa.Text(), nullable=True),
+        sa.Column("user_id", sa.Text(), nullable=False),
+        sa.Column("name", sa.Text(), nullable=False),
+        sa.Column("items", sa.JSON(), nullable=True),
+        sa.Column("meta", sa.JSON(), nullable=True),
+        sa.Column("is_expanded", sa.Boolean(), default=False, nullable=False),
+        sa.Column(
+            "created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False
+        ),
+        sa.Column(
+            "updated_at",
+            sa.DateTime(),
+            nullable=False,
+            server_default=sa.func.now(),
+            onupdate=sa.func.now(),
+        ),
+        sa.PrimaryKeyConstraint("id", "user_id"),
+    )
+
+    op.add_column(
+        "chat",
+        sa.Column("folder_id", sa.Text(), nullable=True),
+    )
+
+
+def downgrade():
+    op.drop_column("chat", "folder_id")
+
+    op.drop_table("folder")
diff --git a/backend/open_webui/migrations/versions/ca81bd47c050_add_config_table.py b/backend/open_webui/migrations/versions/ca81bd47c050_add_config_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..1540aa6a7f263880b89d7e84bf86ead434c904f7
--- /dev/null
+++ b/backend/open_webui/migrations/versions/ca81bd47c050_add_config_table.py
@@ -0,0 +1,41 @@
+"""Add config table
+
+Revision ID: ca81bd47c050
+Revises: 7e5b5dc7342b
+Create Date: 2024-08-25 15:26:35.241684
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision: str = "ca81bd47c050"
+down_revision: Union[str, None] = "7e5b5dc7342b"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade():
+    op.create_table(
+        "config",
+        sa.Column("id", sa.Integer, primary_key=True),
+        sa.Column("data", sa.JSON(), nullable=False),
+        sa.Column("version", sa.Integer, nullable=False),
+        sa.Column(
+            "created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()
+        ),
+        sa.Column(
+            "updated_at",
+            sa.DateTime(),
+            nullable=True,
+            server_default=sa.func.now(),
+            onupdate=sa.func.now(),
+        ),
+    )
+
+
+def downgrade():
+    op.drop_table("config")
diff --git a/backend/open_webui/static/assets/pdf-style.css b/backend/open_webui/static/assets/pdf-style.css
new file mode 100644
index 0000000000000000000000000000000000000000..db9ac83ddb823bd4d10297ac59c1955ed082369f
--- /dev/null
+++ b/backend/open_webui/static/assets/pdf-style.css
@@ -0,0 +1,319 @@
+/* HTML and Body */
+@font-face {
+	font-family: 'NotoSans';
+	src: url('fonts/NotoSans-Variable.ttf');
+}
+
+@font-face {
+	font-family: 'NotoSansJP';
+	src: url('fonts/NotoSansJP-Variable.ttf');
+}
+
+@font-face {
+	font-family: 'NotoSansKR';
+	src: url('fonts/NotoSansKR-Variable.ttf');
+}
+
+@font-face {
+	font-family: 'NotoSansSC';
+	src: url('fonts/NotoSansSC-Variable.ttf');
+}
+
+@font-face {
+	font-family: 'NotoSansSC-Regular';
+	src: url('fonts/NotoSansSC-Regular.ttf');
+}
+
+html {
+	font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'NotoSans', 'NotoSansJP', 'NotoSansKR',
+		'NotoSansSC', 'STSong-Light', 'MSung-Light', 'HeiseiMin-W3', 'HYSMyeongJo-Medium', Roboto,
+		'Helvetica Neue', Arial, sans-serif;
+	font-size: 14px; /* Default font size */
+	line-height: 1.5;
+}
+
+*,
+*::before,
+*::after {
+	box-sizing: inherit;
+}
+
+body {
+	margin: 0;
+	color: #212529;
+	background-color: #fff;
+	width: auto;
+}
+
+/* Typography */
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+	font-weight: 500;
+	margin: 0;
+}
+
+h1 {
+	font-size: 2.5rem;
+}
+
+h2 {
+	font-size: 2rem;
+}
+
+h3 {
+	font-size: 1.75rem;
+}
+
+h4 {
+	font-size: 1.5rem;
+}
+
+h5 {
+	font-size: 1.25rem;
+}
+
+h6 {
+	font-size: 1rem;
+}
+
+p {
+	margin-top: 0;
+	margin-bottom: 1rem;
+}
+
+/* Grid System */
+.container {
+	width: 100%;
+	padding-right: 15px;
+	padding-left: 15px;
+	margin-right: auto;
+	margin-left: auto;
+}
+
+/* Utilities */
+.text-center {
+	text-align: center;
+}
+
+/* Additional Text Utilities */
+.text-muted {
+	color: #6c757d; /* Muted text color */
+}
+
+/* Small Text */
+small {
+	font-size: 80%; /* Smaller font size relative to the base */
+	color: #6c757d; /* Lighter text color for secondary information */
+	margin-bottom: 0;
+	margin-top: 0;
+}
+
+/* Strong Element Styles */
+strong {
+	font-weight: bolder; /* Ensures the text is bold */
+	color: inherit; /* Inherits the color from its parent element */
+}
+
+/* link */
+a {
+	color: #007bff;
+	text-decoration: none;
+	background-color: transparent;
+}
+
+a:hover {
+	color: #0056b3;
+	text-decoration: underline;
+}
+
+/* General styles for lists */
+ol,
+ul,
+li {
+	padding-left: 40px; /* Increase padding to move bullet points to the right */
+	margin-left: 20px; /* Indent lists from the left */
+}
+
+/* Ordered list styles */
+ol {
+	list-style-type: decimal; /* Use numbers for ordered lists */
+	margin-bottom: 10px; /* Space after each list */
+}
+
+ol li {
+	margin-bottom: 0.5rem; /* Space between ordered list items */
+}
+
+/* Unordered list styles */
+ul {
+	list-style-type: disc; /* Use bullets for unordered lists */
+	margin-bottom: 10px; /* Space after each list */
+}
+
+ul li {
+	margin-bottom: 0.5rem; /* Space between unordered list items */
+}
+
+/* List item styles */
+li {
+	margin-bottom: 5px; /* Space between list items */
+	line-height: 1.5; /* Line height for better readability */
+}
+
+/* Nested lists */
+ol ol,
+ol ul,
+ul ol,
+ul ul {
+	padding-left: 20px;
+	margin-left: 30px; /* Further indent nested lists */
+	margin-bottom: 0; /* Remove extra margin at the bottom of nested lists */
+}
+
+/* Code blocks */
+pre {
+	background-color: #f4f4f4;
+	padding: 10px;
+	overflow-x: auto;
+	max-width: 100%; /* Ensure it doesn't overflow the page */
+	width: 80%; /* Set a specific width for a container-like appearance */
+	margin: 0 1em; /* Center the pre block */
+	box-sizing: border-box; /* Include padding in the width */
+	border: 1px solid #ccc; /* Optional: Add a border for better definition */
+	border-radius: 4px; /* Optional: Add rounded corners */
+}
+
+code {
+	font-family: 'Courier New', Courier, monospace;
+	background-color: #f4f4f4;
+	padding: 2px 4px;
+	border-radius: 4px;
+	box-sizing: border-box; /* Include padding in the width */
+}
+
+.message {
+	margin-top: 8px;
+	margin-bottom: 8px;
+	max-width: 100%;
+	overflow-wrap: break-word;
+}
+
+/* Table Styles */
+table {
+	width: 100%;
+	margin-bottom: 1rem;
+	color: #212529;
+	border-collapse: collapse; /* Removes the space between borders */
+}
+
+th,
+td {
+	margin: 0;
+	padding: 0.75rem;
+	vertical-align: top;
+	border-top: 1px solid #dee2e6;
+}
+
+thead th {
+	vertical-align: bottom;
+	border-bottom: 2px solid #dee2e6;
+}
+
+tbody + tbody {
+	border-top: 2px solid #dee2e6;
+}
+
+/* markdown-section styles */
+.markdown-section blockquote,
+.markdown-section h1,
+.markdown-section h2,
+.markdown-section h3,
+.markdown-section h4,
+.markdown-section h5,
+.markdown-section h6,
+.markdown-section p,
+.markdown-section pre,
+.markdown-section table,
+.markdown-section ul {
+	/* Give most block elements margin top and bottom */
+	margin-top: 1rem;
+}
+
+/* Remove top margin if it's the first child */
+.markdown-section blockquote:first-child,
+.markdown-section h1:first-child,
+.markdown-section h2:first-child,
+.markdown-section h3:first-child,
+.markdown-section h4:first-child,
+.markdown-section h5:first-child,
+.markdown-section h6:first-child,
+.markdown-section p:first-child,
+.markdown-section pre:first-child,
+.markdown-section table:first-child,
+.markdown-section ul:first-child {
+	margin-top: 0;
+}
+
+/* Remove top margin of <ul> following a <p> */
+.markdown-section p + ul {
+	margin-top: 0;
+}
+
+/* Remove bottom margin of <p> if it is followed by a <ul> */
+/* Note: :has is not supported in CSS, so you would need JavaScript for this behavior */
+.markdown-section p {
+	margin-bottom: 0;
+}
+
+/* Add a rule to reset margin-bottom for <p> not followed by <ul> */
+.markdown-section p + ul {
+	margin-top: 0;
+}
+
+/* List item styles */
+.markdown-section li {
+	padding: 2px;
+}
+
+.markdown-section li p {
+	margin-bottom: 0;
+	padding: 0;
+}
+
+/* Avoid margins for nested lists */
+.markdown-section li > ul {
+	margin-top: 0;
+	margin-bottom: 0;
+}
+
+/* Table styles */
+.markdown-section table {
+	width: 100%;
+	border-collapse: collapse;
+	margin: 1rem 0;
+}
+
+.markdown-section th,
+.markdown-section td {
+	border: 1px solid #ddd;
+	padding: 0.5rem;
+	text-align: left;
+}
+
+.markdown-section th {
+	background-color: #f2f2f2;
+}
+
+.markdown-section pre {
+	padding: 10px;
+	margin: 10px;
+}
+
+.markdown-section pre code {
+	position: relative;
+	color: rgb(172, 0, 95);
+}
diff --git a/backend/open_webui/static/favicon.png b/backend/open_webui/static/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..2b2074780847581edf9cf2ed0d2e9ebd8ff08c56
Binary files /dev/null and b/backend/open_webui/static/favicon.png differ
diff --git a/backend/open_webui/static/fonts/NotoSans-Bold.ttf b/backend/open_webui/static/fonts/NotoSans-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..56310ad1ad635e6ac20478daff20f14f0647e3ed
--- /dev/null
+++ b/backend/open_webui/static/fonts/NotoSans-Bold.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cf382cad35e731fc4f13b1bf068c5085cd17bee2141014cc94919c140529488d
+size 582604
diff --git a/backend/open_webui/static/fonts/NotoSans-Italic.ttf b/backend/open_webui/static/fonts/NotoSans-Italic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..1ad4f126bc92b2188effdf3f0db8e4a260ecc5c4
--- /dev/null
+++ b/backend/open_webui/static/fonts/NotoSans-Italic.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:380a500e3dda76d955dadc77053227cc61149814737dc9f7d973d09415ad851f
+size 597000
diff --git a/backend/open_webui/static/fonts/NotoSans-Regular.ttf b/backend/open_webui/static/fonts/NotoSans-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..defecf9b7931c29b64603ca1eed7eb4f513d42ad
--- /dev/null
+++ b/backend/open_webui/static/fonts/NotoSans-Regular.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3be6b371cef19ed6add589bd106444ab74c9793bc812d3159298b73d00ee011c
+size 582748
diff --git a/backend/open_webui/static/fonts/NotoSans-Variable.ttf b/backend/open_webui/static/fonts/NotoSans-Variable.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..1e0b14b76564180e3fc9f59c50efd2742cc1e878
--- /dev/null
+++ b/backend/open_webui/static/fonts/NotoSans-Variable.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:74df1f61ab9d4bfaa961c65f8dc991deaae2885b0a6a6d6a60ed23980b3c8554
+size 2490816
diff --git a/backend/open_webui/static/fonts/NotoSansJP-Regular.ttf b/backend/open_webui/static/fonts/NotoSansJP-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..34e480073b8a55f1e9476c669acde48e049bd0b5
--- /dev/null
+++ b/backend/open_webui/static/fonts/NotoSansJP-Regular.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fb3df01b4182734d021d79ec5bac17903bb681e926a059c59ed81a373d612241
+size 5732824
diff --git a/backend/open_webui/static/fonts/NotoSansJP-Variable.ttf b/backend/open_webui/static/fonts/NotoSansJP-Variable.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..d4a816c22ac73995af0112a8ea0d9d195c8e864e
--- /dev/null
+++ b/backend/open_webui/static/fonts/NotoSansJP-Variable.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:33119a596d1bfae91165585c90c6e9752cf2c9120b45c18388fb81724b3ec64b
+size 9586480
diff --git a/backend/open_webui/static/fonts/NotoSansKR-Regular.ttf b/backend/open_webui/static/fonts/NotoSansKR-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..c847342886ba5d1e326844ba2d679b9d0bdaa805
--- /dev/null
+++ b/backend/open_webui/static/fonts/NotoSansKR-Regular.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9db318b65ee9c575a43e7efd273dbdd1afef26e467eea3e1073a50e1a6595f6d
+size 6192764
diff --git a/backend/open_webui/static/fonts/NotoSansKR-Variable.ttf b/backend/open_webui/static/fonts/NotoSansKR-Variable.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..a4af1a68197b794946d4303a264fc78c334974a2
--- /dev/null
+++ b/backend/open_webui/static/fonts/NotoSansKR-Variable.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2d2267a83d089cb1a517a4f901676d05d283346e650d1b1845d601cbd696a98e
+size 10361060
diff --git a/backend/open_webui/static/fonts/NotoSansSC-Regular.ttf b/backend/open_webui/static/fonts/NotoSansSC-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..6326270d98c00a198618b1fb3cf7dd71b4fd93f1
--- /dev/null
+++ b/backend/open_webui/static/fonts/NotoSansSC-Regular.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cf8b2a0576d5680284ab03a7a8219499d59bbe981a79bb3dc0031f251c39736
+size 10560616
diff --git a/backend/open_webui/static/fonts/NotoSansSC-Variable.ttf b/backend/open_webui/static/fonts/NotoSansSC-Variable.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..05e0ad417a9e92b52630f22fb3e1cb5e7c3522aa
--- /dev/null
+++ b/backend/open_webui/static/fonts/NotoSansSC-Variable.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2e68d43ae2c504f4e302a9cf522ecc3f06ef66d724cade58bbe13a3a4af70512
+size 17805476
diff --git a/backend/open_webui/static/logo.png b/backend/open_webui/static/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..519af1db620dbf4de3694660dae7abd7392f0b3c
Binary files /dev/null and b/backend/open_webui/static/logo.png differ
diff --git a/backend/open_webui/static/splash.png b/backend/open_webui/static/splash.png
new file mode 100644
index 0000000000000000000000000000000000000000..389196ca6a364b9e4b7daa0fc13be463b914b251
Binary files /dev/null and b/backend/open_webui/static/splash.png differ
diff --git a/backend/open_webui/static/user-import.csv b/backend/open_webui/static/user-import.csv
new file mode 100644
index 0000000000000000000000000000000000000000..918a92aad71d708ae13fedb8b91f79c29a5b3e9d
--- /dev/null
+++ b/backend/open_webui/static/user-import.csv
@@ -0,0 +1 @@
+Name,Email,Password,Role
diff --git a/backend/open_webui/storage/provider.py b/backend/open_webui/storage/provider.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3168ead8fca439389c2ba629c55069be2d79778
--- /dev/null
+++ b/backend/open_webui/storage/provider.py
@@ -0,0 +1,164 @@
+import os
+import boto3
+from botocore.exceptions import ClientError
+import shutil
+
+
+from typing import BinaryIO, Tuple, Optional, Union
+
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.config import (
+    STORAGE_PROVIDER,
+    S3_ACCESS_KEY_ID,
+    S3_SECRET_ACCESS_KEY,
+    S3_BUCKET_NAME,
+    S3_REGION_NAME,
+    S3_ENDPOINT_URL,
+    UPLOAD_DIR,
+)
+
+
+import boto3
+from botocore.exceptions import ClientError
+from typing import BinaryIO, Tuple, Optional
+
+
+class StorageProvider:
+    def __init__(self, provider: Optional[str] = None):
+        self.storage_provider: str = provider or STORAGE_PROVIDER
+
+        self.s3_client = None
+        self.s3_bucket_name: Optional[str] = None
+
+        if self.storage_provider == "s3":
+            self._initialize_s3()
+
+    def _initialize_s3(self) -> None:
+        """Initializes the S3 client and bucket name if using S3 storage."""
+        self.s3_client = boto3.client(
+            "s3",
+            region_name=S3_REGION_NAME,
+            endpoint_url=S3_ENDPOINT_URL,
+            aws_access_key_id=S3_ACCESS_KEY_ID,
+            aws_secret_access_key=S3_SECRET_ACCESS_KEY,
+        )
+        self.bucket_name = S3_BUCKET_NAME
+
+    def _upload_to_s3(self, file_path: str, filename: str) -> Tuple[bytes, str]:
+        """Handles uploading of the file to S3 storage."""
+        if not self.s3_client:
+            raise RuntimeError("S3 Client is not initialized.")
+
+        try:
+            self.s3_client.upload_file(file_path, self.bucket_name, filename)
+            return open(file_path, "rb").read(), file_path
+        except ClientError as e:
+            raise RuntimeError(f"Error uploading file to S3: {e}")
+
+    def _upload_to_local(self, contents: bytes, filename: str) -> Tuple[bytes, str]:
+        """Handles uploading of the file to local storage."""
+        file_path = f"{UPLOAD_DIR}/{filename}"
+        with open(file_path, "wb") as f:
+            f.write(contents)
+        return contents, file_path
+
+    def _get_file_from_s3(self, file_path: str) -> str:
+        """Handles downloading of the file from S3 storage."""
+        if not self.s3_client:
+            raise RuntimeError("S3 Client is not initialized.")
+
+        try:
+            bucket_name, key = file_path.split("//")[1].split("/")
+            local_file_path = f"{UPLOAD_DIR}/{key}"
+            self.s3_client.download_file(bucket_name, key, local_file_path)
+            return local_file_path
+        except ClientError as e:
+            raise RuntimeError(f"Error downloading file from S3: {e}")
+
+    def _get_file_from_local(self, file_path: str) -> str:
+        """Handles downloading of the file from local storage."""
+        return file_path
+
+    def _delete_from_s3(self, filename: str) -> None:
+        """Handles deletion of the file from S3 storage."""
+        if not self.s3_client:
+            raise RuntimeError("S3 Client is not initialized.")
+
+        try:
+            self.s3_client.delete_object(Bucket=self.bucket_name, Key=filename)
+        except ClientError as e:
+            raise RuntimeError(f"Error deleting file from S3: {e}")
+
+    def _delete_from_local(self, filename: str) -> None:
+        """Handles deletion of the file from local storage."""
+        file_path = f"{UPLOAD_DIR}/{filename}"
+        if os.path.isfile(file_path):
+            os.remove(file_path)
+        else:
+            print(f"File {file_path} not found in local storage.")
+
+    def _delete_all_from_s3(self) -> None:
+        """Handles deletion of all files from S3 storage."""
+        if not self.s3_client:
+            raise RuntimeError("S3 Client is not initialized.")
+
+        try:
+            response = self.s3_client.list_objects_v2(Bucket=self.bucket_name)
+            if "Contents" in response:
+                for content in response["Contents"]:
+                    self.s3_client.delete_object(
+                        Bucket=self.bucket_name, Key=content["Key"]
+                    )
+        except ClientError as e:
+            raise RuntimeError(f"Error deleting all files from S3: {e}")
+
+    def _delete_all_from_local(self) -> None:
+        """Handles deletion of all files from local storage."""
+        if os.path.exists(UPLOAD_DIR):
+            for filename in os.listdir(UPLOAD_DIR):
+                file_path = os.path.join(UPLOAD_DIR, filename)
+                try:
+                    if os.path.isfile(file_path) or os.path.islink(file_path):
+                        os.unlink(file_path)  # Remove the file or link
+                    elif os.path.isdir(file_path):
+                        shutil.rmtree(file_path)  # Remove the directory
+                except Exception as e:
+                    print(f"Failed to delete {file_path}. Reason: {e}")
+        else:
+            print(f"Directory {UPLOAD_DIR} not found in local storage.")
+
+    def upload_file(self, file: BinaryIO, filename: str) -> Tuple[bytes, str]:
+        """Uploads a file either to S3 or the local file system."""
+        contents = file.read()
+        if not contents:
+            raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)
+        contents, file_path = self._upload_to_local(contents, filename)
+
+        if self.storage_provider == "s3":
+            return self._upload_to_s3(file_path, filename)
+        return contents, file_path
+
+    def get_file(self, file_path: str) -> str:
+        """Downloads a file either from S3 or the local file system and returns the file path."""
+        if self.storage_provider == "s3":
+            return self._get_file_from_s3(file_path)
+        return self._get_file_from_local(file_path)
+
+    def delete_file(self, filename: str) -> None:
+        """Deletes a file either from S3 or the local file system."""
+        if self.storage_provider == "s3":
+            self._delete_from_s3(filename)
+
+        # Always delete from local storage
+        self._delete_from_local(filename)
+
+    def delete_all_files(self) -> None:
+        """Deletes all files from the storage."""
+        if self.storage_provider == "s3":
+            self._delete_all_from_s3()
+
+        # Always delete from local storage
+        self._delete_all_from_local()
+
+
+Storage = StorageProvider(provider=STORAGE_PROVIDER)
diff --git a/backend/open_webui/test/__init__.py b/backend/open_webui/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/open_webui/test/apps/webui/routers/test_auths.py b/backend/open_webui/test/apps/webui/routers/test_auths.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc14fb8ddcf4257e031ff126678e3f99bd282b9d
--- /dev/null
+++ b/backend/open_webui/test/apps/webui/routers/test_auths.py
@@ -0,0 +1,200 @@
+from test.util.abstract_integration_test import AbstractPostgresTest
+from test.util.mock_user import mock_webui_user
+
+
+class TestAuths(AbstractPostgresTest):
+    BASE_PATH = "/api/v1/auths"
+
+    def setup_class(cls):
+        super().setup_class()
+        from open_webui.apps.webui.models.auths import Auths
+        from open_webui.apps.webui.models.users import Users
+
+        cls.users = Users
+        cls.auths = Auths
+
+    def test_get_session_user(self):
+        with mock_webui_user():
+            response = self.fast_api_client.get(self.create_url(""))
+        assert response.status_code == 200
+        assert response.json() == {
+            "id": "1",
+            "name": "John Doe",
+            "email": "john.doe@openwebui.com",
+            "role": "user",
+            "profile_image_url": "/user.png",
+        }
+
+    def test_update_profile(self):
+        from open_webui.utils.utils import get_password_hash
+
+        user = self.auths.insert_new_auth(
+            email="john.doe@openwebui.com",
+            password=get_password_hash("old_password"),
+            name="John Doe",
+            profile_image_url="/user.png",
+            role="user",
+        )
+
+        with mock_webui_user(id=user.id):
+            response = self.fast_api_client.post(
+                self.create_url("/update/profile"),
+                json={"name": "John Doe 2", "profile_image_url": "/user2.png"},
+            )
+        assert response.status_code == 200
+        db_user = self.users.get_user_by_id(user.id)
+        assert db_user.name == "John Doe 2"
+        assert db_user.profile_image_url == "/user2.png"
+
+    def test_update_password(self):
+        from open_webui.utils.utils import get_password_hash
+
+        user = self.auths.insert_new_auth(
+            email="john.doe@openwebui.com",
+            password=get_password_hash("old_password"),
+            name="John Doe",
+            profile_image_url="/user.png",
+            role="user",
+        )
+
+        with mock_webui_user(id=user.id):
+            response = self.fast_api_client.post(
+                self.create_url("/update/password"),
+                json={"password": "old_password", "new_password": "new_password"},
+            )
+        assert response.status_code == 200
+
+        old_auth = self.auths.authenticate_user(
+            "john.doe@openwebui.com", "old_password"
+        )
+        assert old_auth is None
+        new_auth = self.auths.authenticate_user(
+            "john.doe@openwebui.com", "new_password"
+        )
+        assert new_auth is not None
+
+    def test_signin(self):
+        from open_webui.utils.utils import get_password_hash
+
+        user = self.auths.insert_new_auth(
+            email="john.doe@openwebui.com",
+            password=get_password_hash("password"),
+            name="John Doe",
+            profile_image_url="/user.png",
+            role="user",
+        )
+        response = self.fast_api_client.post(
+            self.create_url("/signin"),
+            json={"email": "john.doe@openwebui.com", "password": "password"},
+        )
+        assert response.status_code == 200
+        data = response.json()
+        assert data["id"] == user.id
+        assert data["name"] == "John Doe"
+        assert data["email"] == "john.doe@openwebui.com"
+        assert data["role"] == "user"
+        assert data["profile_image_url"] == "/user.png"
+        assert data["token"] is not None and len(data["token"]) > 0
+        assert data["token_type"] == "Bearer"
+
+    def test_signup(self):
+        response = self.fast_api_client.post(
+            self.create_url("/signup"),
+            json={
+                "name": "John Doe",
+                "email": "john.doe@openwebui.com",
+                "password": "password",
+            },
+        )
+        assert response.status_code == 200
+        data = response.json()
+        assert data["id"] is not None and len(data["id"]) > 0
+        assert data["name"] == "John Doe"
+        assert data["email"] == "john.doe@openwebui.com"
+        assert data["role"] in ["admin", "user", "pending"]
+        assert data["profile_image_url"] == "/user.png"
+        assert data["token"] is not None and len(data["token"]) > 0
+        assert data["token_type"] == "Bearer"
+
+    def test_add_user(self):
+        with mock_webui_user():
+            response = self.fast_api_client.post(
+                self.create_url("/add"),
+                json={
+                    "name": "John Doe 2",
+                    "email": "john.doe2@openwebui.com",
+                    "password": "password2",
+                    "role": "admin",
+                },
+            )
+        assert response.status_code == 200
+        data = response.json()
+        assert data["id"] is not None and len(data["id"]) > 0
+        assert data["name"] == "John Doe 2"
+        assert data["email"] == "john.doe2@openwebui.com"
+        assert data["role"] == "admin"
+        assert data["profile_image_url"] == "/user.png"
+        assert data["token"] is not None and len(data["token"]) > 0
+        assert data["token_type"] == "Bearer"
+
+    def test_get_admin_details(self):
+        self.auths.insert_new_auth(
+            email="john.doe@openwebui.com",
+            password="password",
+            name="John Doe",
+            profile_image_url="/user.png",
+            role="admin",
+        )
+        with mock_webui_user():
+            response = self.fast_api_client.get(self.create_url("/admin/details"))
+
+        assert response.status_code == 200
+        assert response.json() == {
+            "name": "John Doe",
+            "email": "john.doe@openwebui.com",
+        }
+
+    def test_create_api_key_(self):
+        user = self.auths.insert_new_auth(
+            email="john.doe@openwebui.com",
+            password="password",
+            name="John Doe",
+            profile_image_url="/user.png",
+            role="admin",
+        )
+        with mock_webui_user(id=user.id):
+            response = self.fast_api_client.post(self.create_url("/api_key"))
+        assert response.status_code == 200
+        data = response.json()
+        assert data["api_key"] is not None
+        assert len(data["api_key"]) > 0
+
+    def test_delete_api_key(self):
+        user = self.auths.insert_new_auth(
+            email="john.doe@openwebui.com",
+            password="password",
+            name="John Doe",
+            profile_image_url="/user.png",
+            role="admin",
+        )
+        self.users.update_user_api_key_by_id(user.id, "abc")
+        with mock_webui_user(id=user.id):
+            response = self.fast_api_client.delete(self.create_url("/api_key"))
+        assert response.status_code == 200
+        assert response.json() == True
+        db_user = self.users.get_user_by_id(user.id)
+        assert db_user.api_key is None
+
+    def test_get_api_key(self):
+        user = self.auths.insert_new_auth(
+            email="john.doe@openwebui.com",
+            password="password",
+            name="John Doe",
+            profile_image_url="/user.png",
+            role="admin",
+        )
+        self.users.update_user_api_key_by_id(user.id, "abc")
+        with mock_webui_user(id=user.id):
+            response = self.fast_api_client.get(self.create_url("/api_key"))
+        assert response.status_code == 200
+        assert response.json() == {"api_key": "abc"}
diff --git a/backend/open_webui/test/apps/webui/routers/test_chats.py b/backend/open_webui/test/apps/webui/routers/test_chats.py
new file mode 100644
index 0000000000000000000000000000000000000000..935316fd8f5a273d09a9644ef72a9ab1a11d4361
--- /dev/null
+++ b/backend/open_webui/test/apps/webui/routers/test_chats.py
@@ -0,0 +1,236 @@
+import uuid
+
+from test.util.abstract_integration_test import AbstractPostgresTest
+from test.util.mock_user import mock_webui_user
+
+
+class TestChats(AbstractPostgresTest):
+    BASE_PATH = "/api/v1/chats"
+
+    def setup_class(cls):
+        super().setup_class()
+
+    def setup_method(self):
+        super().setup_method()
+        from open_webui.apps.webui.models.chats import ChatForm, Chats
+
+        self.chats = Chats
+        self.chats.insert_new_chat(
+            "2",
+            ChatForm(
+                **{
+                    "chat": {
+                        "name": "chat1",
+                        "description": "chat1 description",
+                        "tags": ["tag1", "tag2"],
+                        "history": {"currentId": "1", "messages": []},
+                    }
+                }
+            ),
+        )
+
+    def test_get_session_user_chat_list(self):
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/"))
+        assert response.status_code == 200
+        first_chat = response.json()[0]
+        assert first_chat["id"] is not None
+        assert first_chat["title"] == "New Chat"
+        assert first_chat["created_at"] is not None
+        assert first_chat["updated_at"] is not None
+
+    def test_delete_all_user_chats(self):
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.delete(self.create_url("/"))
+        assert response.status_code == 200
+        assert len(self.chats.get_chats()) == 0
+
+    def test_get_user_chat_list_by_user_id(self):
+        with mock_webui_user(id="3"):
+            response = self.fast_api_client.get(self.create_url("/list/user/2"))
+        assert response.status_code == 200
+        first_chat = response.json()[0]
+        assert first_chat["id"] is not None
+        assert first_chat["title"] == "New Chat"
+        assert first_chat["created_at"] is not None
+        assert first_chat["updated_at"] is not None
+
+    def test_create_new_chat(self):
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.post(
+                self.create_url("/new"),
+                json={
+                    "chat": {
+                        "name": "chat2",
+                        "description": "chat2 description",
+                        "tags": ["tag1", "tag2"],
+                    }
+                },
+            )
+        assert response.status_code == 200
+        data = response.json()
+        assert data["archived"] is False
+        assert data["chat"] == {
+            "name": "chat2",
+            "description": "chat2 description",
+            "tags": ["tag1", "tag2"],
+        }
+        assert data["user_id"] == "2"
+        assert data["id"] is not None
+        assert data["share_id"] is None
+        assert data["title"] == "New Chat"
+        assert data["updated_at"] is not None
+        assert data["created_at"] is not None
+        assert len(self.chats.get_chats()) == 2
+
+    def test_get_user_chats(self):
+        self.test_get_session_user_chat_list()
+
+    def test_get_user_archived_chats(self):
+        self.chats.archive_all_chats_by_user_id("2")
+        from open_webui.apps.webui.internal.db import Session
+
+        Session.commit()
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/all/archived"))
+        assert response.status_code == 200
+        first_chat = response.json()[0]
+        assert first_chat["id"] is not None
+        assert first_chat["title"] == "New Chat"
+        assert first_chat["created_at"] is not None
+        assert first_chat["updated_at"] is not None
+
+    def test_get_all_user_chats_in_db(self):
+        with mock_webui_user(id="4"):
+            response = self.fast_api_client.get(self.create_url("/all/db"))
+        assert response.status_code == 200
+        assert len(response.json()) == 1
+
+    def test_get_archived_session_user_chat_list(self):
+        self.test_get_user_archived_chats()
+
+    def test_archive_all_chats(self):
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.post(self.create_url("/archive/all"))
+        assert response.status_code == 200
+        assert len(self.chats.get_archived_chats_by_user_id("2")) == 1
+
+    def test_get_shared_chat_by_id(self):
+        chat_id = self.chats.get_chats()[0].id
+        self.chats.update_chat_share_id_by_id(chat_id, chat_id)
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url(f"/share/{chat_id}"))
+        assert response.status_code == 200
+        data = response.json()
+        assert data["id"] == chat_id
+        assert data["chat"] == {
+            "name": "chat1",
+            "description": "chat1 description",
+            "tags": ["tag1", "tag2"],
+            "history": {"currentId": "1", "messages": []},
+        }
+        assert data["id"] == chat_id
+        assert data["share_id"] == chat_id
+        assert data["title"] == "New Chat"
+
+    def test_get_chat_by_id(self):
+        chat_id = self.chats.get_chats()[0].id
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url(f"/{chat_id}"))
+        assert response.status_code == 200
+        data = response.json()
+        assert data["id"] == chat_id
+        assert data["chat"] == {
+            "name": "chat1",
+            "description": "chat1 description",
+            "tags": ["tag1", "tag2"],
+            "history": {"currentId": "1", "messages": []},
+        }
+        assert data["share_id"] is None
+        assert data["title"] == "New Chat"
+        assert data["user_id"] == "2"
+
+    def test_update_chat_by_id(self):
+        chat_id = self.chats.get_chats()[0].id
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.post(
+                self.create_url(f"/{chat_id}"),
+                json={
+                    "chat": {
+                        "name": "chat2",
+                        "description": "chat2 description",
+                        "tags": ["tag2", "tag4"],
+                        "title": "Just another title",
+                    }
+                },
+            )
+        assert response.status_code == 200
+        data = response.json()
+        assert data["id"] == chat_id
+        assert data["chat"] == {
+            "name": "chat2",
+            "title": "Just another title",
+            "description": "chat2 description",
+            "tags": ["tag2", "tag4"],
+            "history": {"currentId": "1", "messages": []},
+        }
+        assert data["share_id"] is None
+        assert data["title"] == "Just another title"
+        assert data["user_id"] == "2"
+
+    def test_delete_chat_by_id(self):
+        chat_id = self.chats.get_chats()[0].id
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.delete(self.create_url(f"/{chat_id}"))
+        assert response.status_code == 200
+        assert response.json() is True
+
+    def test_clone_chat_by_id(self):
+        chat_id = self.chats.get_chats()[0].id
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url(f"/{chat_id}/clone"))
+
+        assert response.status_code == 200
+        data = response.json()
+        assert data["id"] != chat_id
+        assert data["chat"] == {
+            "branchPointMessageId": "1",
+            "description": "chat1 description",
+            "history": {"currentId": "1", "messages": []},
+            "name": "chat1",
+            "originalChatId": chat_id,
+            "tags": ["tag1", "tag2"],
+            "title": "Clone of New Chat",
+        }
+        assert data["share_id"] is None
+        assert data["title"] == "Clone of New Chat"
+        assert data["user_id"] == "2"
+
+    def test_archive_chat_by_id(self):
+        chat_id = self.chats.get_chats()[0].id
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url(f"/{chat_id}/archive"))
+        assert response.status_code == 200
+
+        chat = self.chats.get_chat_by_id(chat_id)
+        assert chat.archived is True
+
+    def test_share_chat_by_id(self):
+        chat_id = self.chats.get_chats()[0].id
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.post(self.create_url(f"/{chat_id}/share"))
+        assert response.status_code == 200
+
+        chat = self.chats.get_chat_by_id(chat_id)
+        assert chat.share_id is not None
+
+    def test_delete_shared_chat_by_id(self):
+        chat_id = self.chats.get_chats()[0].id
+        share_id = str(uuid.uuid4())
+        self.chats.update_chat_share_id_by_id(chat_id, share_id)
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.delete(self.create_url(f"/{chat_id}/share"))
+        assert response.status_code
+
+        chat = self.chats.get_chat_by_id(chat_id)
+        assert chat.share_id is None
diff --git a/backend/open_webui/test/apps/webui/routers/test_models.py b/backend/open_webui/test/apps/webui/routers/test_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d52658b8f0d2312232f67fb3856f319f1fa7d2d
--- /dev/null
+++ b/backend/open_webui/test/apps/webui/routers/test_models.py
@@ -0,0 +1,61 @@
+from test.util.abstract_integration_test import AbstractPostgresTest
+from test.util.mock_user import mock_webui_user
+
+
+class TestModels(AbstractPostgresTest):
+    BASE_PATH = "/api/v1/models"
+
+    def setup_class(cls):
+        super().setup_class()
+        from open_webui.apps.webui.models.models import Model
+
+        cls.models = Model
+
+    def test_models(self):
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/"))
+        assert response.status_code == 200
+        assert len(response.json()) == 0
+
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.post(
+                self.create_url("/add"),
+                json={
+                    "id": "my-model",
+                    "base_model_id": "base-model-id",
+                    "name": "Hello World",
+                    "meta": {
+                        "profile_image_url": "/static/favicon.png",
+                        "description": "description",
+                        "capabilities": None,
+                        "model_config": {},
+                    },
+                    "params": {},
+                },
+            )
+        assert response.status_code == 200
+
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/"))
+        assert response.status_code == 200
+        assert len(response.json()) == 1
+
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(
+                self.create_url(query_params={"id": "my-model"})
+            )
+        assert response.status_code == 200
+        data = response.json()[0]
+        assert data["id"] == "my-model"
+        assert data["name"] == "Hello World"
+
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.delete(
+                self.create_url("/delete?id=my-model")
+            )
+        assert response.status_code == 200
+
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/"))
+        assert response.status_code == 200
+        assert len(response.json()) == 0
diff --git a/backend/open_webui/test/apps/webui/routers/test_prompts.py b/backend/open_webui/test/apps/webui/routers/test_prompts.py
new file mode 100644
index 0000000000000000000000000000000000000000..d91bf77dc5b2602bd433f9dbe01ded8574df3db9
--- /dev/null
+++ b/backend/open_webui/test/apps/webui/routers/test_prompts.py
@@ -0,0 +1,91 @@
+from test.util.abstract_integration_test import AbstractPostgresTest
+from test.util.mock_user import mock_webui_user
+
+
+class TestPrompts(AbstractPostgresTest):
+    BASE_PATH = "/api/v1/prompts"
+
+    def test_prompts(self):
+        # Get all prompts
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/"))
+        assert response.status_code == 200
+        assert len(response.json()) == 0
+
+        # Create a two new prompts
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.post(
+                self.create_url("/create"),
+                json={
+                    "command": "/my-command",
+                    "title": "Hello World",
+                    "content": "description",
+                },
+            )
+        assert response.status_code == 200
+        with mock_webui_user(id="3"):
+            response = self.fast_api_client.post(
+                self.create_url("/create"),
+                json={
+                    "command": "/my-command2",
+                    "title": "Hello World 2",
+                    "content": "description 2",
+                },
+            )
+        assert response.status_code == 200
+
+        # Get all prompts
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/"))
+        assert response.status_code == 200
+        assert len(response.json()) == 2
+
+        # Get prompt by command
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/command/my-command"))
+        assert response.status_code == 200
+        data = response.json()
+        assert data["command"] == "/my-command"
+        assert data["title"] == "Hello World"
+        assert data["content"] == "description"
+        assert data["user_id"] == "2"
+
+        # Update prompt
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.post(
+                self.create_url("/command/my-command2/update"),
+                json={
+                    "command": "irrelevant for request",
+                    "title": "Hello World Updated",
+                    "content": "description Updated",
+                },
+            )
+        assert response.status_code == 200
+        data = response.json()
+        assert data["command"] == "/my-command2"
+        assert data["title"] == "Hello World Updated"
+        assert data["content"] == "description Updated"
+        assert data["user_id"] == "3"
+
+        # Get prompt by command
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/command/my-command2"))
+        assert response.status_code == 200
+        data = response.json()
+        assert data["command"] == "/my-command2"
+        assert data["title"] == "Hello World Updated"
+        assert data["content"] == "description Updated"
+        assert data["user_id"] == "3"
+
+        # Delete prompt
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.delete(
+                self.create_url("/command/my-command/delete")
+            )
+        assert response.status_code == 200
+
+        # Get all prompts
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/"))
+        assert response.status_code == 200
+        assert len(response.json()) == 1
diff --git a/backend/open_webui/test/apps/webui/routers/test_users.py b/backend/open_webui/test/apps/webui/routers/test_users.py
new file mode 100644
index 0000000000000000000000000000000000000000..6facf7055a2a3ef5d817ab71200a18a43c060c1b
--- /dev/null
+++ b/backend/open_webui/test/apps/webui/routers/test_users.py
@@ -0,0 +1,167 @@
+from test.util.abstract_integration_test import AbstractPostgresTest
+from test.util.mock_user import mock_webui_user
+
+
+def _get_user_by_id(data, param):
+    return next((item for item in data if item["id"] == param), None)
+
+
+def _assert_user(data, id, **kwargs):
+    user = _get_user_by_id(data, id)
+    assert user is not None
+    comparison_data = {
+        "name": f"user {id}",
+        "email": f"user{id}@openwebui.com",
+        "profile_image_url": f"/user{id}.png",
+        "role": "user",
+        **kwargs,
+    }
+    for key, value in comparison_data.items():
+        assert user[key] == value
+
+
+class TestUsers(AbstractPostgresTest):
+    BASE_PATH = "/api/v1/users"
+
+    def setup_class(cls):
+        super().setup_class()
+        from open_webui.apps.webui.models.users import Users
+
+        cls.users = Users
+
+    def setup_method(self):
+        super().setup_method()
+        self.users.insert_new_user(
+            id="1",
+            name="user 1",
+            email="user1@openwebui.com",
+            profile_image_url="/user1.png",
+            role="user",
+        )
+        self.users.insert_new_user(
+            id="2",
+            name="user 2",
+            email="user2@openwebui.com",
+            profile_image_url="/user2.png",
+            role="user",
+        )
+
+    def test_users(self):
+        # Get all users
+        with mock_webui_user(id="3"):
+            response = self.fast_api_client.get(self.create_url(""))
+        assert response.status_code == 200
+        assert len(response.json()) == 2
+        data = response.json()
+        _assert_user(data, "1")
+        _assert_user(data, "2")
+
+        # update role
+        with mock_webui_user(id="3"):
+            response = self.fast_api_client.post(
+                self.create_url("/update/role"), json={"id": "2", "role": "admin"}
+            )
+        assert response.status_code == 200
+        _assert_user([response.json()], "2", role="admin")
+
+        # Get all users
+        with mock_webui_user(id="3"):
+            response = self.fast_api_client.get(self.create_url(""))
+        assert response.status_code == 200
+        assert len(response.json()) == 2
+        data = response.json()
+        _assert_user(data, "1")
+        _assert_user(data, "2", role="admin")
+
+        # Get (empty) user settings
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/user/settings"))
+        assert response.status_code == 200
+        assert response.json() is None
+
+        # Update user settings
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.post(
+                self.create_url("/user/settings/update"),
+                json={
+                    "ui": {"attr1": "value1", "attr2": "value2"},
+                    "model_config": {"attr3": "value3", "attr4": "value4"},
+                },
+            )
+        assert response.status_code == 200
+
+        # Get user settings
+        with mock_webui_user(id="2"):
+            response = self.fast_api_client.get(self.create_url("/user/settings"))
+        assert response.status_code == 200
+        assert response.json() == {
+            "ui": {"attr1": "value1", "attr2": "value2"},
+            "model_config": {"attr3": "value3", "attr4": "value4"},
+        }
+
+        # Get (empty) user info
+        with mock_webui_user(id="1"):
+            response = self.fast_api_client.get(self.create_url("/user/info"))
+        assert response.status_code == 200
+        assert response.json() is None
+
+        # Update user info
+        with mock_webui_user(id="1"):
+            response = self.fast_api_client.post(
+                self.create_url("/user/info/update"),
+                json={"attr1": "value1", "attr2": "value2"},
+            )
+        assert response.status_code == 200
+
+        # Get user info
+        with mock_webui_user(id="1"):
+            response = self.fast_api_client.get(self.create_url("/user/info"))
+        assert response.status_code == 200
+        assert response.json() == {"attr1": "value1", "attr2": "value2"}
+
+        # Get user by id
+        with mock_webui_user(id="1"):
+            response = self.fast_api_client.get(self.create_url("/2"))
+        assert response.status_code == 200
+        assert response.json() == {"name": "user 2", "profile_image_url": "/user2.png"}
+
+        # Update user by id
+        with mock_webui_user(id="1"):
+            response = self.fast_api_client.post(
+                self.create_url("/2/update"),
+                json={
+                    "name": "user 2 updated",
+                    "email": "user2-updated@openwebui.com",
+                    "profile_image_url": "/user2-updated.png",
+                },
+            )
+        assert response.status_code == 200
+
+        # Get all users
+        with mock_webui_user(id="3"):
+            response = self.fast_api_client.get(self.create_url(""))
+        assert response.status_code == 200
+        assert len(response.json()) == 2
+        data = response.json()
+        _assert_user(data, "1")
+        _assert_user(
+            data,
+            "2",
+            role="admin",
+            name="user 2 updated",
+            email="user2-updated@openwebui.com",
+            profile_image_url="/user2-updated.png",
+        )
+
+        # Delete user by id
+        with mock_webui_user(id="1"):
+            response = self.fast_api_client.delete(self.create_url("/2"))
+        assert response.status_code == 200
+
+        # Get all users
+        with mock_webui_user(id="3"):
+            response = self.fast_api_client.get(self.create_url(""))
+        assert response.status_code == 200
+        assert len(response.json()) == 1
+        data = response.json()
+        _assert_user(data, "1")
diff --git a/backend/open_webui/test/util/abstract_integration_test.py b/backend/open_webui/test/util/abstract_integration_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..2814731e067dc1bc2ac49ca8b48e1a32441d63ff
--- /dev/null
+++ b/backend/open_webui/test/util/abstract_integration_test.py
@@ -0,0 +1,161 @@
+import logging
+import os
+import time
+
+import docker
+import pytest
+from docker import DockerClient
+from pytest_docker.plugin import get_docker_ip
+from fastapi.testclient import TestClient
+from sqlalchemy import text, create_engine
+
+
+log = logging.getLogger(__name__)
+
+
+def get_fast_api_client():
+    from main import app
+
+    with TestClient(app) as c:
+        return c
+
+
+class AbstractIntegrationTest:
+    BASE_PATH = None
+
+    def create_url(self, path="", query_params=None):
+        if self.BASE_PATH is None:
+            raise Exception("BASE_PATH is not set")
+        parts = self.BASE_PATH.split("/")
+        parts = [part.strip() for part in parts if part.strip() != ""]
+        path_parts = path.split("/")
+        path_parts = [part.strip() for part in path_parts if part.strip() != ""]
+        query_parts = ""
+        if query_params:
+            query_parts = "&".join(
+                [f"{key}={value}" for key, value in query_params.items()]
+            )
+            query_parts = f"?{query_parts}"
+        return "/".join(parts + path_parts) + query_parts
+
+    @classmethod
+    def setup_class(cls):
+        pass
+
+    def setup_method(self):
+        pass
+
+    @classmethod
+    def teardown_class(cls):
+        pass
+
+    def teardown_method(self):
+        pass
+
+
+class AbstractPostgresTest(AbstractIntegrationTest):
+    DOCKER_CONTAINER_NAME = "postgres-test-container-will-get-deleted"
+    docker_client: DockerClient
+
+    @classmethod
+    def _create_db_url(cls, env_vars_postgres: dict) -> str:
+        host = get_docker_ip()
+        user = env_vars_postgres["POSTGRES_USER"]
+        pw = env_vars_postgres["POSTGRES_PASSWORD"]
+        port = 8081
+        db = env_vars_postgres["POSTGRES_DB"]
+        return f"postgresql://{user}:{pw}@{host}:{port}/{db}"
+
+    @classmethod
+    def setup_class(cls):
+        super().setup_class()
+        try:
+            env_vars_postgres = {
+                "POSTGRES_USER": "user",
+                "POSTGRES_PASSWORD": "example",
+                "POSTGRES_DB": "openwebui",
+            }
+            cls.docker_client = docker.from_env()
+            cls.docker_client.containers.run(
+                "postgres:16.2",
+                detach=True,
+                environment=env_vars_postgres,
+                name=cls.DOCKER_CONTAINER_NAME,
+                ports={5432: ("0.0.0.0", 8081)},
+                command="postgres -c log_statement=all",
+            )
+            time.sleep(0.5)
+
+            database_url = cls._create_db_url(env_vars_postgres)
+            os.environ["DATABASE_URL"] = database_url
+            retries = 10
+            db = None
+            while retries > 0:
+                try:
+                    from open_webui.config import OPEN_WEBUI_DIR
+
+                    db = create_engine(database_url, pool_pre_ping=True)
+                    db = db.connect()
+                    log.info("postgres is ready!")
+                    break
+                except Exception as e:
+                    log.warning(e)
+                    time.sleep(3)
+                    retries -= 1
+
+            if db:
+                # import must be after setting env!
+                cls.fast_api_client = get_fast_api_client()
+                db.close()
+            else:
+                raise Exception("Could not connect to Postgres")
+        except Exception as ex:
+            log.error(ex)
+            cls.teardown_class()
+            pytest.fail(f"Could not setup test environment: {ex}")
+
+    def _check_db_connection(self):
+        from open_webui.apps.webui.internal.db import Session
+
+        retries = 10
+        while retries > 0:
+            try:
+                Session.execute(text("SELECT 1"))
+                Session.commit()
+                break
+            except Exception as e:
+                Session.rollback()
+                log.warning(e)
+                time.sleep(3)
+                retries -= 1
+
+    def setup_method(self):
+        super().setup_method()
+        self._check_db_connection()
+
+    @classmethod
+    def teardown_class(cls) -> None:
+        super().teardown_class()
+        cls.docker_client.containers.get(cls.DOCKER_CONTAINER_NAME).remove(force=True)
+
+    def teardown_method(self):
+        from open_webui.apps.webui.internal.db import Session
+
+        # rollback everything not yet committed
+        Session.commit()
+
+        # truncate all tables
+        tables = [
+            "auth",
+            "chat",
+            "chatidtag",
+            "document",
+            "memory",
+            "model",
+            "prompt",
+            "tag",
+            '"user"',
+        ]
+        for table in tables:
+            Session.execute(text(f"TRUNCATE TABLE {table}"))
+        Session.commit()
diff --git a/backend/open_webui/test/util/mock_user.py b/backend/open_webui/test/util/mock_user.py
new file mode 100644
index 0000000000000000000000000000000000000000..96456a2c814fdea1a23f2ce41687d3c25a25eda5
--- /dev/null
+++ b/backend/open_webui/test/util/mock_user.py
@@ -0,0 +1,45 @@
+from contextlib import contextmanager
+
+from fastapi import FastAPI
+
+
+@contextmanager
+def mock_webui_user(**kwargs):
+    from open_webui.apps.webui.main import app
+
+    with mock_user(app, **kwargs):
+        yield
+
+
+@contextmanager
+def mock_user(app: FastAPI, **kwargs):
+    from open_webui.utils.utils import (
+        get_current_user,
+        get_verified_user,
+        get_admin_user,
+        get_current_user_by_api_key,
+    )
+    from open_webui.apps.webui.models.users import User
+
+    def create_user():
+        user_parameters = {
+            "id": "1",
+            "name": "John Doe",
+            "email": "john.doe@openwebui.com",
+            "role": "user",
+            "profile_image_url": "/user.png",
+            "last_active_at": 1627351200,
+            "updated_at": 1627351200,
+            "created_at": 162735120,
+            **kwargs,
+        }
+        return User(**user_parameters)
+
+    app.dependency_overrides = {
+        get_current_user: create_user,
+        get_verified_user: create_user,
+        get_admin_user: create_user,
+        get_current_user_by_api_key: create_user,
+    }
+    yield
+    app.dependency_overrides = {}
diff --git a/backend/open_webui/utils/logo.png b/backend/open_webui/utils/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..519af1db620dbf4de3694660dae7abd7392f0b3c
Binary files /dev/null and b/backend/open_webui/utils/logo.png differ
diff --git a/backend/open_webui/utils/misc.py b/backend/open_webui/utils/misc.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5af492ba3198678eba4c148095ab15e215a5201
--- /dev/null
+++ b/backend/open_webui/utils/misc.py
@@ -0,0 +1,336 @@
+import hashlib
+import re
+import time
+import uuid
+from datetime import timedelta
+from pathlib import Path
+from typing import Callable, Optional
+
+
+def get_messages_content(messages: list[dict]) -> str:
+    return "\n".join(
+        [
+            f"{message['role'].upper()}: {get_content_from_message(message)}"
+            for message in messages
+        ]
+    )
+
+
+def get_last_user_message_item(messages: list[dict]) -> Optional[dict]:
+    for message in reversed(messages):
+        if message["role"] == "user":
+            return message
+    return None
+
+
+def get_content_from_message(message: dict) -> Optional[str]:
+    if isinstance(message["content"], list):
+        for item in message["content"]:
+            if item["type"] == "text":
+                return item["text"]
+    else:
+        return message["content"]
+    return None
+
+
+def get_last_user_message(messages: list[dict]) -> Optional[str]:
+    message = get_last_user_message_item(messages)
+    if message is None:
+        return None
+    return get_content_from_message(message)
+
+
+def get_last_assistant_message(messages: list[dict]) -> Optional[str]:
+    for message in reversed(messages):
+        if message["role"] == "assistant":
+            return get_content_from_message(message)
+    return None
+
+
+def get_system_message(messages: list[dict]) -> Optional[dict]:
+    for message in messages:
+        if message["role"] == "system":
+            return message
+    return None
+
+
+def remove_system_message(messages: list[dict]) -> list[dict]:
+    return [message for message in messages if message["role"] != "system"]
+
+
+def pop_system_message(messages: list[dict]) -> tuple[Optional[dict], list[dict]]:
+    return get_system_message(messages), remove_system_message(messages)
+
+
+def prepend_to_first_user_message_content(
+    content: str, messages: list[dict]
+) -> list[dict]:
+    for message in messages:
+        if message["role"] == "user":
+            if isinstance(message["content"], list):
+                for item in message["content"]:
+                    if item["type"] == "text":
+                        item["text"] = f"{content}\n{item['text']}"
+            else:
+                message["content"] = f"{content}\n{message['content']}"
+            break
+    return messages
+
+
+def add_or_update_system_message(content: str, messages: list[dict]):
+    """
+    Adds a new system message at the beginning of the messages list
+    or updates the existing system message at the beginning.
+
+    :param msg: The message to be added or appended.
+    :param messages: The list of message dictionaries.
+    :return: The updated list of message dictionaries.
+    """
+
+    if messages and messages[0].get("role") == "system":
+        messages[0]["content"] = f"{content}\n{messages[0]['content']}"
+    else:
+        # Insert at the beginning
+        messages.insert(0, {"role": "system", "content": content})
+
+    return messages
+
+
+def openai_chat_message_template(model: str):
+    return {
+        "id": f"{model}-{str(uuid.uuid4())}",
+        "created": int(time.time()),
+        "model": model,
+        "choices": [{"index": 0, "logprobs": None, "finish_reason": None}],
+    }
+
+
+def openai_chat_chunk_message_template(
+    model: str, message: Optional[str] = None
+) -> dict:
+    template = openai_chat_message_template(model)
+    template["object"] = "chat.completion.chunk"
+    if message:
+        template["choices"][0]["delta"] = {"content": message}
+    else:
+        template["choices"][0]["finish_reason"] = "stop"
+    return template
+
+
+def openai_chat_completion_message_template(
+    model: str, message: Optional[str] = None
+) -> dict:
+    template = openai_chat_message_template(model)
+    template["object"] = "chat.completion"
+    if message is not None:
+        template["choices"][0]["message"] = {"content": message, "role": "assistant"}
+    template["choices"][0]["finish_reason"] = "stop"
+    return template
+
+
+def get_gravatar_url(email):
+    # Trim leading and trailing whitespace from
+    # an email address and force all characters
+    # to lower case
+    address = str(email).strip().lower()
+
+    # Create a SHA256 hash of the final string
+    hash_object = hashlib.sha256(address.encode())
+    hash_hex = hash_object.hexdigest()
+
+    # Grab the actual image URL
+    return f"https://www.gravatar.com/avatar/{hash_hex}?d=mp"
+
+
+def calculate_sha256(file):
+    sha256 = hashlib.sha256()
+    # Read the file in chunks to efficiently handle large files
+    for chunk in iter(lambda: file.read(8192), b""):
+        sha256.update(chunk)
+    return sha256.hexdigest()
+
+
+def calculate_sha256_string(string):
+    # Create a new SHA-256 hash object
+    sha256_hash = hashlib.sha256()
+    # Update the hash object with the bytes of the input string
+    sha256_hash.update(string.encode("utf-8"))
+    # Get the hexadecimal representation of the hash
+    hashed_string = sha256_hash.hexdigest()
+    return hashed_string
+
+
+def validate_email_format(email: str) -> bool:
+    if email.endswith("@localhost"):
+        return True
+
+    return bool(re.match(r"[^@]+@[^@]+\.[^@]+", email))
+
+
+def sanitize_filename(file_name):
+    # Convert to lowercase
+    lower_case_file_name = file_name.lower()
+
+    # Remove special characters using regular expression
+    sanitized_file_name = re.sub(r"[^\w\s]", "", lower_case_file_name)
+
+    # Replace spaces with dashes
+    final_file_name = re.sub(r"\s+", "-", sanitized_file_name)
+
+    return final_file_name
+
+
+def extract_folders_after_data_docs(path):
+    # Convert the path to a Path object if it's not already
+    path = Path(path)
+
+    # Extract parts of the path
+    parts = path.parts
+
+    # Find the index of '/data/docs' in the path
+    try:
+        index_data_docs = parts.index("data") + 1
+        index_docs = parts.index("docs", index_data_docs) + 1
+    except ValueError:
+        return []
+
+    # Exclude the filename and accumulate folder names
+    tags = []
+
+    folders = parts[index_docs:-1]
+    for idx, _ in enumerate(folders):
+        tags.append("/".join(folders[: idx + 1]))
+
+    return tags
+
+
+def parse_duration(duration: str) -> Optional[timedelta]:
+    if duration == "-1" or duration == "0":
+        return None
+
+    # Regular expression to find number and unit pairs
+    pattern = r"(-?\d+(\.\d+)?)(ms|s|m|h|d|w)"
+    matches = re.findall(pattern, duration)
+
+    if not matches:
+        raise ValueError("Invalid duration string")
+
+    total_duration = timedelta()
+
+    for number, _, unit in matches:
+        number = float(number)
+        if unit == "ms":
+            total_duration += timedelta(milliseconds=number)
+        elif unit == "s":
+            total_duration += timedelta(seconds=number)
+        elif unit == "m":
+            total_duration += timedelta(minutes=number)
+        elif unit == "h":
+            total_duration += timedelta(hours=number)
+        elif unit == "d":
+            total_duration += timedelta(days=number)
+        elif unit == "w":
+            total_duration += timedelta(weeks=number)
+
+    return total_duration
+
+
+def parse_ollama_modelfile(model_text):
+    parameters_meta = {
+        "mirostat": int,
+        "mirostat_eta": float,
+        "mirostat_tau": float,
+        "num_ctx": int,
+        "repeat_last_n": int,
+        "repeat_penalty": float,
+        "temperature": float,
+        "seed": int,
+        "tfs_z": float,
+        "num_predict": int,
+        "top_k": int,
+        "top_p": float,
+        "num_keep": int,
+        "typical_p": float,
+        "presence_penalty": float,
+        "frequency_penalty": float,
+        "penalize_newline": bool,
+        "numa": bool,
+        "num_batch": int,
+        "num_gpu": int,
+        "main_gpu": int,
+        "low_vram": bool,
+        "f16_kv": bool,
+        "vocab_only": bool,
+        "use_mmap": bool,
+        "use_mlock": bool,
+        "num_thread": int,
+    }
+
+    data = {"base_model_id": None, "params": {}}
+
+    # Parse base model
+    base_model_match = re.search(
+        r"^FROM\s+(\w+)", model_text, re.MULTILINE | re.IGNORECASE
+    )
+    if base_model_match:
+        data["base_model_id"] = base_model_match.group(1)
+
+    # Parse template
+    template_match = re.search(
+        r'TEMPLATE\s+"""(.+?)"""', model_text, re.DOTALL | re.IGNORECASE
+    )
+    if template_match:
+        data["params"] = {"template": template_match.group(1).strip()}
+
+    # Parse stops
+    stops = re.findall(r'PARAMETER stop "(.*?)"', model_text, re.IGNORECASE)
+    if stops:
+        data["params"]["stop"] = stops
+
+    # Parse other parameters from the provided list
+    for param, param_type in parameters_meta.items():
+        param_match = re.search(rf"PARAMETER {param} (.+)", model_text, re.IGNORECASE)
+        if param_match:
+            value = param_match.group(1)
+
+            try:
+                if param_type is int:
+                    value = int(value)
+                elif param_type is float:
+                    value = float(value)
+                elif param_type is bool:
+                    value = value.lower() == "true"
+            except Exception as e:
+                print(e)
+                continue
+
+            data["params"][param] = value
+
+    # Parse adapter
+    adapter_match = re.search(r"ADAPTER (.+)", model_text, re.IGNORECASE)
+    if adapter_match:
+        data["params"]["adapter"] = adapter_match.group(1)
+
+    # Parse system description
+    system_desc_match = re.search(
+        r'SYSTEM\s+"""(.+?)"""', model_text, re.DOTALL | re.IGNORECASE
+    )
+    system_desc_match_single = re.search(
+        r"SYSTEM\s+([^\n]+)", model_text, re.IGNORECASE
+    )
+
+    if system_desc_match:
+        data["params"]["system"] = system_desc_match.group(1).strip()
+    elif system_desc_match_single:
+        data["params"]["system"] = system_desc_match_single.group(1).strip()
+
+    # Parse messages
+    messages = []
+    message_matches = re.findall(r"MESSAGE (\w+) (.+)", model_text, re.IGNORECASE)
+    for role, content in message_matches:
+        messages.append({"role": role, "content": content})
+
+    if messages:
+        data["params"]["messages"] = messages
+
+    return data
diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py
new file mode 100644
index 0000000000000000000000000000000000000000..722b1ea73cb2e1167d4acc5b0905e9b3cd826ae8
--- /dev/null
+++ b/backend/open_webui/utils/oauth.py
@@ -0,0 +1,261 @@
+import base64
+import logging
+import mimetypes
+import uuid
+
+import aiohttp
+from authlib.integrations.starlette_client import OAuth
+from authlib.oidc.core import UserInfo
+from fastapi import (
+    HTTPException,
+    status,
+)
+from starlette.responses import RedirectResponse
+
+from open_webui.apps.webui.models.auths import Auths
+from open_webui.apps.webui.models.users import Users
+from open_webui.config import (
+    DEFAULT_USER_ROLE,
+    ENABLE_OAUTH_SIGNUP,
+    OAUTH_MERGE_ACCOUNTS_BY_EMAIL,
+    OAUTH_PROVIDERS,
+    ENABLE_OAUTH_ROLE_MANAGEMENT,
+    OAUTH_ROLES_CLAIM,
+    OAUTH_EMAIL_CLAIM,
+    OAUTH_PICTURE_CLAIM,
+    OAUTH_USERNAME_CLAIM,
+    OAUTH_ALLOWED_ROLES,
+    OAUTH_ADMIN_ROLES,
+    WEBHOOK_URL,
+    JWT_EXPIRES_IN,
+    AppConfig,
+)
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.env import WEBUI_SESSION_COOKIE_SAME_SITE, WEBUI_SESSION_COOKIE_SECURE
+from open_webui.utils.misc import parse_duration
+from open_webui.utils.utils import get_password_hash, create_token
+from open_webui.utils.webhook import post_webhook
+
+log = logging.getLogger(__name__)
+
+auth_manager_config = AppConfig()
+auth_manager_config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
+auth_manager_config.ENABLE_OAUTH_SIGNUP = ENABLE_OAUTH_SIGNUP
+auth_manager_config.OAUTH_MERGE_ACCOUNTS_BY_EMAIL = OAUTH_MERGE_ACCOUNTS_BY_EMAIL
+auth_manager_config.ENABLE_OAUTH_ROLE_MANAGEMENT = ENABLE_OAUTH_ROLE_MANAGEMENT
+auth_manager_config.OAUTH_ROLES_CLAIM = OAUTH_ROLES_CLAIM
+auth_manager_config.OAUTH_EMAIL_CLAIM = OAUTH_EMAIL_CLAIM
+auth_manager_config.OAUTH_PICTURE_CLAIM = OAUTH_PICTURE_CLAIM
+auth_manager_config.OAUTH_USERNAME_CLAIM = OAUTH_USERNAME_CLAIM
+auth_manager_config.OAUTH_ALLOWED_ROLES = OAUTH_ALLOWED_ROLES
+auth_manager_config.OAUTH_ADMIN_ROLES = OAUTH_ADMIN_ROLES
+auth_manager_config.WEBHOOK_URL = WEBHOOK_URL
+auth_manager_config.JWT_EXPIRES_IN = JWT_EXPIRES_IN
+
+
+class OAuthManager:
+    def __init__(self):
+        self.oauth = OAuth()
+        for provider_name, provider_config in OAUTH_PROVIDERS.items():
+            self.oauth.register(
+                name=provider_name,
+                client_id=provider_config["client_id"],
+                client_secret=provider_config["client_secret"],
+                server_metadata_url=provider_config["server_metadata_url"],
+                client_kwargs={
+                    "scope": provider_config["scope"],
+                },
+                redirect_uri=provider_config["redirect_uri"],
+            )
+
+    def get_client(self, provider_name):
+        return self.oauth.create_client(provider_name)
+
+    def get_user_role(self, user, user_data):
+        if user and Users.get_num_users() == 1:
+            # If the user is the only user, assign the role "admin" - actually repairs role for single user on login
+            return "admin"
+        if not user and Users.get_num_users() == 0:
+            # If there are no users, assign the role "admin", as the first user will be an admin
+            return "admin"
+
+        if auth_manager_config.ENABLE_OAUTH_ROLE_MANAGEMENT:
+            oauth_claim = auth_manager_config.OAUTH_ROLES_CLAIM
+            oauth_allowed_roles = auth_manager_config.OAUTH_ALLOWED_ROLES
+            oauth_admin_roles = auth_manager_config.OAUTH_ADMIN_ROLES
+            oauth_roles = None
+            role = "pending"  # Default/fallback role if no matching roles are found
+
+            # Next block extracts the roles from the user data, accepting nested claims of any depth
+            if oauth_claim and oauth_allowed_roles and oauth_admin_roles:
+                claim_data = user_data
+                nested_claims = oauth_claim.split(".")
+                for nested_claim in nested_claims:
+                    claim_data = claim_data.get(nested_claim, {})
+                oauth_roles = claim_data if isinstance(claim_data, list) else None
+
+            # If any roles are found, check if they match the allowed or admin roles
+            if oauth_roles:
+                # If role management is enabled, and matching roles are provided, use the roles
+                for allowed_role in oauth_allowed_roles:
+                    # If the user has any of the allowed roles, assign the role "user"
+                    if allowed_role in oauth_roles:
+                        role = "user"
+                        break
+                for admin_role in oauth_admin_roles:
+                    # If the user has any of the admin roles, assign the role "admin"
+                    if admin_role in oauth_roles:
+                        role = "admin"
+                        break
+        else:
+            if not user:
+                # If role management is disabled, use the default role for new users
+                role = auth_manager_config.DEFAULT_USER_ROLE
+            else:
+                # If role management is disabled, use the existing role for existing users
+                role = user.role
+
+        return role
+
+    async def handle_login(self, provider, request):
+        if provider not in OAUTH_PROVIDERS:
+            raise HTTPException(404)
+        # If the provider has a custom redirect URL, use that, otherwise automatically generate one
+        redirect_uri = OAUTH_PROVIDERS[provider].get("redirect_uri") or request.url_for(
+            "oauth_callback", provider=provider
+        )
+        client = self.get_client(provider)
+        if client is None:
+            raise HTTPException(404)
+        return await client.authorize_redirect(request, redirect_uri)
+
+    async def handle_callback(self, provider, request, response):
+        if provider not in OAUTH_PROVIDERS:
+            raise HTTPException(404)
+        client = self.get_client(provider)
+        try:
+            token = await client.authorize_access_token(request)
+        except Exception as e:
+            log.warning(f"OAuth callback error: {e}")
+            raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
+        user_data: UserInfo = token["userinfo"]
+        if not user_data:
+            user_data: UserInfo = await client.userinfo(token=token)
+        if not user_data:
+            log.warning(f"OAuth callback failed, user data is missing: {token}")
+            raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
+
+        sub = user_data.get("sub")
+        if not sub:
+            log.warning(f"OAuth callback failed, sub is missing: {user_data}")
+            raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
+        provider_sub = f"{provider}@{sub}"
+        email_claim = auth_manager_config.OAUTH_EMAIL_CLAIM
+        email = user_data.get(email_claim, "").lower()
+        # We currently mandate that email addresses are provided
+        if not email:
+            log.warning(f"OAuth callback failed, email is missing: {user_data}")
+            raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
+
+        # Check if the user exists
+        user = Users.get_user_by_oauth_sub(provider_sub)
+
+        if not user:
+            # If the user does not exist, check if merging is enabled
+            if auth_manager_config.OAUTH_MERGE_ACCOUNTS_BY_EMAIL:
+                # Check if the user exists by email
+                user = Users.get_user_by_email(email)
+                if user:
+                    # Update the user with the new oauth sub
+                    Users.update_user_oauth_sub_by_id(user.id, provider_sub)
+
+        if user:
+            determined_role = self.get_user_role(user, user_data)
+            if user.role != determined_role:
+                Users.update_user_role_by_id(user.id, determined_role)
+
+        if not user:
+            # If the user does not exist, check if signups are enabled
+            if auth_manager_config.ENABLE_OAUTH_SIGNUP:
+                # Check if an existing user with the same email already exists
+                existing_user = Users.get_user_by_email(
+                    user_data.get("email", "").lower()
+                )
+                if existing_user:
+                    raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
+
+                picture_claim = auth_manager_config.OAUTH_PICTURE_CLAIM
+                picture_url = user_data.get(picture_claim, "")
+                if picture_url:
+                    # Download the profile image into a base64 string
+                    try:
+                        async with aiohttp.ClientSession() as session:
+                            async with session.get(picture_url) as resp:
+                                picture = await resp.read()
+                                base64_encoded_picture = base64.b64encode(
+                                    picture
+                                ).decode("utf-8")
+                                guessed_mime_type = mimetypes.guess_type(picture_url)[0]
+                                if guessed_mime_type is None:
+                                    # assume JPG, browsers are tolerant enough of image formats
+                                    guessed_mime_type = "image/jpeg"
+                                picture_url = f"data:{guessed_mime_type};base64,{base64_encoded_picture}"
+                    except Exception as e:
+                        log.error(
+                            f"Error downloading profile image '{picture_url}': {e}"
+                        )
+                        picture_url = ""
+                if not picture_url:
+                    picture_url = "/user.png"
+                username_claim = auth_manager_config.OAUTH_USERNAME_CLAIM
+
+                role = self.get_user_role(None, user_data)
+
+                user = Auths.insert_new_auth(
+                    email=email,
+                    password=get_password_hash(
+                        str(uuid.uuid4())
+                    ),  # Random password, not used
+                    name=user_data.get(username_claim, "User"),
+                    profile_image_url=picture_url,
+                    role=role,
+                    oauth_sub=provider_sub,
+                )
+
+                if auth_manager_config.WEBHOOK_URL:
+                    post_webhook(
+                        auth_manager_config.WEBHOOK_URL,
+                        auth_manager_config.WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
+                        {
+                            "action": "signup",
+                            "message": auth_manager_config.WEBHOOK_MESSAGES.USER_SIGNUP(
+                                user.name
+                            ),
+                            "user": user.model_dump_json(exclude_none=True),
+                        },
+                    )
+            else:
+                raise HTTPException(
+                    status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
+                )
+
+        jwt_token = create_token(
+            data={"id": user.id},
+            expires_delta=parse_duration(auth_manager_config.JWT_EXPIRES_IN),
+        )
+
+        # Set the cookie token
+        response.set_cookie(
+            key="token",
+            value=jwt_token,
+            httponly=True,  # Ensures the cookie is not accessible via JavaScript
+            samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
+            secure=WEBUI_SESSION_COOKIE_SECURE,
+        )
+
+        # Redirect back to the frontend with the JWT token
+        redirect_url = f"{request.base_url}auth#token={jwt_token}"
+        return RedirectResponse(url=redirect_url)
+
+
+oauth_manager = OAuthManager()
diff --git a/backend/open_webui/utils/payload.py b/backend/open_webui/utils/payload.py
new file mode 100644
index 0000000000000000000000000000000000000000..04e3a98c408ee52bbd68e3f9778e5851814e31d5
--- /dev/null
+++ b/backend/open_webui/utils/payload.py
@@ -0,0 +1,183 @@
+from open_webui.utils.task import prompt_template
+from open_webui.utils.misc import (
+    add_or_update_system_message,
+)
+
+from typing import Callable, Optional
+
+
+# inplace function: form_data is modified
+def apply_model_system_prompt_to_body(params: dict, form_data: dict, user) -> dict:
+    system = params.get("system", None)
+    if not system:
+        return form_data
+
+    if user:
+        template_params = {
+            "user_name": user.name,
+            "user_location": user.info.get("location") if user.info else None,
+        }
+    else:
+        template_params = {}
+    system = prompt_template(system, **template_params)
+    form_data["messages"] = add_or_update_system_message(
+        system, form_data.get("messages", [])
+    )
+    return form_data
+
+
+# inplace function: form_data is modified
+def apply_model_params_to_body(
+    params: dict, form_data: dict, mappings: dict[str, Callable]
+) -> dict:
+    if not params:
+        return form_data
+
+    for key, cast_func in mappings.items():
+        if (value := params.get(key)) is not None:
+            form_data[key] = cast_func(value)
+
+    return form_data
+
+
+# inplace function: form_data is modified
+def apply_model_params_to_body_openai(params: dict, form_data: dict) -> dict:
+    mappings = {
+        "temperature": float,
+        "top_p": float,
+        "max_tokens": int,
+        "frequency_penalty": float,
+        "seed": lambda x: x,
+        "stop": lambda x: [bytes(s, "utf-8").decode("unicode_escape") for s in x],
+    }
+    return apply_model_params_to_body(params, form_data, mappings)
+
+
+def apply_model_params_to_body_ollama(params: dict, form_data: dict) -> dict:
+    opts = [
+        "temperature",
+        "top_p",
+        "seed",
+        "mirostat",
+        "mirostat_eta",
+        "mirostat_tau",
+        "num_ctx",
+        "num_batch",
+        "num_keep",
+        "repeat_last_n",
+        "tfs_z",
+        "top_k",
+        "min_p",
+        "use_mmap",
+        "use_mlock",
+        "num_thread",
+        "num_gpu",
+    ]
+    mappings = {i: lambda x: x for i in opts}
+    form_data = apply_model_params_to_body(params, form_data, mappings)
+
+    name_differences = {
+        "max_tokens": "num_predict",
+        "frequency_penalty": "repeat_penalty",
+    }
+
+    for key, value in name_differences.items():
+        if (param := params.get(key, None)) is not None:
+            form_data[value] = param
+
+    return form_data
+
+
+def convert_messages_openai_to_ollama(messages: list[dict]) -> list[dict]:
+    ollama_messages = []
+
+    for message in messages:
+        # Initialize the new message structure with the role
+        new_message = {"role": message["role"]}
+
+        content = message.get("content", [])
+
+        # Check if the content is a string (just a simple message)
+        if isinstance(content, str):
+            # If the content is a string, it's pure text
+            new_message["content"] = content
+        else:
+            # Otherwise, assume the content is a list of dicts, e.g., text followed by an image URL
+            content_text = ""
+            images = []
+
+            # Iterate through the list of content items
+            for item in content:
+                # Check if it's a text type
+                if item.get("type") == "text":
+                    content_text += item.get("text", "")
+
+                # Check if it's an image URL type
+                elif item.get("type") == "image_url":
+                    img_url = item.get("image_url", {}).get("url", "")
+                    if img_url:
+                        # If the image url starts with data:, it's a base64 image and should be trimmed
+                        if img_url.startswith("data:"):
+                            img_url = img_url.split(",")[-1]
+                        images.append(img_url)
+
+            # Add content text (if any)
+            if content_text:
+                new_message["content"] = content_text.strip()
+
+            # Add images (if any)
+            if images:
+                new_message["images"] = images
+
+        # Append the new formatted message to the result
+        ollama_messages.append(new_message)
+
+    return ollama_messages
+
+
+def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
+    """
+    Converts a payload formatted for OpenAI's API to be compatible with Ollama's API endpoint for chat completions.
+
+    Args:
+        openai_payload (dict): The payload originally designed for OpenAI API usage.
+
+    Returns:
+        dict: A modified payload compatible with the Ollama API.
+    """
+    ollama_payload = {}
+
+    # Mapping basic model and message details
+    ollama_payload["model"] = openai_payload.get("model")
+    ollama_payload["messages"] = convert_messages_openai_to_ollama(
+        openai_payload.get("messages")
+    )
+    ollama_payload["stream"] = openai_payload.get("stream", False)
+
+    # If there are advanced parameters in the payload, format them in Ollama's options field
+    ollama_options = {}
+
+    # Handle parameters which map directly
+    for param in ["temperature", "top_p", "seed"]:
+        if param in openai_payload:
+            ollama_options[param] = openai_payload[param]
+
+    # Mapping OpenAI's `max_tokens` -> Ollama's `num_predict`
+    if "max_completion_tokens" in openai_payload:
+        ollama_options["num_predict"] = openai_payload["max_completion_tokens"]
+    elif "max_tokens" in openai_payload:
+        ollama_options["num_predict"] = openai_payload["max_tokens"]
+
+    # Handle frequency / presence_penalty, which needs renaming and checking
+    if "frequency_penalty" in openai_payload:
+        ollama_options["repeat_penalty"] = openai_payload["frequency_penalty"]
+
+    if "presence_penalty" in openai_payload and "penalty" not in ollama_options:
+        # We are assuming presence penalty uses a similar concept in Ollama, which needs custom handling if exists.
+        ollama_options["new_topic_penalty"] = openai_payload["presence_penalty"]
+
+    # Add options to payload if any have been set
+    if ollama_options:
+        ollama_payload["options"] = ollama_options
+
+    return ollama_payload
diff --git a/backend/open_webui/utils/pdf_generator.py b/backend/open_webui/utils/pdf_generator.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c3cf55cee1cafaa96c5aa7e09a5c1754b60bceb
--- /dev/null
+++ b/backend/open_webui/utils/pdf_generator.py
@@ -0,0 +1,139 @@
+from datetime import datetime
+from io import BytesIO
+from pathlib import Path
+from typing import Dict, Any, List
+
+from markdown import markdown
+
+import site
+from fpdf import FPDF
+
+from open_webui.env import STATIC_DIR, FONTS_DIR
+from open_webui.apps.webui.models.chats import ChatTitleMessagesForm
+
+
+class PDFGenerator:
+    """
+    Description:
+    The `PDFGenerator` class is designed to create PDF documents from chat messages.
+    The process involves transforming markdown content into HTML and then into a PDF format
+
+    Attributes:
+    - `form_data`: An instance of `ChatTitleMessagesForm` containing title and messages.
+
+    """
+
+    def __init__(self, form_data: ChatTitleMessagesForm):
+        self.html_body = None
+        self.messages_html = None
+        self.form_data = form_data
+
+        self.css = Path(STATIC_DIR / "assets" / "pdf-style.css").read_text()
+
+    def format_timestamp(self, timestamp: float) -> str:
+        """Convert a UNIX timestamp to a formatted date string."""
+        try:
+            date_time = datetime.fromtimestamp(timestamp)
+            return date_time.strftime("%Y-%m-%d, %H:%M:%S")
+        except (ValueError, TypeError) as e:
+            # Log the error if necessary
+            return ""
+
+    def _build_html_message(self, message: Dict[str, Any]) -> str:
+        """Build HTML for a single message."""
+        role = message.get("role", "user")
+        content = message.get("content", "")
+        timestamp = message.get("timestamp")
+
+        model = message.get("model") if role == "assistant" else ""
+
+        date_str = self.format_timestamp(timestamp) if timestamp else ""
+
+        # extends pymdownx extension to convert markdown to html.
+        # - https://facelessuser.github.io/pymdown-extensions/usage_notes/
+        html_content = markdown(content, extensions=["pymdownx.extra"])
+
+        html_message = f"""
+              <div class="message">
+                  <small> {date_str} </small>
+                  <div>
+                      <h2>
+                          <strong>{role.title()}</strong>
+                          <small class="text-muted">{model}</small>
+                      </h2>
+                  </div>
+                  <div class="markdown-section">
+                      {html_content}
+                  </div>
+              </div>
+          """
+        return html_message
+
+    def _generate_html_body(self) -> str:
+        """Generate the full HTML body for the PDF."""
+        return f"""
+        <html>
+            <head>
+                <meta charset="UTF-8">
+                <meta name="viewport" content="width=device-width, initial-scale=1.0">
+            </head>
+            <body>
+                <div class="container"> 
+                    <div class="text-center">
+                        <h1>{self.form_data.title}</h1>
+                    </div>
+                    <div>
+                        {self.messages_html}
+                    </div>
+                </div>
+            </body>
+        </html>
+        """
+
+    def generate_chat_pdf(self) -> bytes:
+        """
+        Generate a PDF from chat messages.
+        """
+        try:
+            global FONTS_DIR
+
+            pdf = FPDF()
+            pdf.add_page()
+
+            # When running using `pip install` the static directory is in the site packages.
+            if not FONTS_DIR.exists():
+                FONTS_DIR = Path(site.getsitepackages()[0]) / "static/fonts"
+            # When running using `pip install -e .` the static directory is in the site packages.
+            # This path only works if `open-webui serve` is run from the root of this project.
+            if not FONTS_DIR.exists():
+                FONTS_DIR = Path("./backend/static/fonts")
+
+            pdf.add_font("NotoSans", "", f"{FONTS_DIR}/NotoSans-Regular.ttf")
+            pdf.add_font("NotoSans", "b", f"{FONTS_DIR}/NotoSans-Bold.ttf")
+            pdf.add_font("NotoSans", "i", f"{FONTS_DIR}/NotoSans-Italic.ttf")
+            pdf.add_font("NotoSansKR", "", f"{FONTS_DIR}/NotoSansKR-Regular.ttf")
+            pdf.add_font("NotoSansJP", "", f"{FONTS_DIR}/NotoSansJP-Regular.ttf")
+            pdf.add_font("NotoSansSC", "", f"{FONTS_DIR}/NotoSansSC-Regular.ttf")
+
+            pdf.set_font("NotoSans", size=12)
+            pdf.set_fallback_fonts(["NotoSansKR", "NotoSansJP", "NotoSansSC"])
+
+            pdf.set_auto_page_break(auto=True, margin=15)
+
+            # Build HTML messages
+            messages_html_list: List[str] = [
+                self._build_html_message(msg) for msg in self.form_data.messages
+            ]
+            self.messages_html = "<div>" + "".join(messages_html_list) + "</div>"
+
+            # Generate full HTML body
+            self.html_body = self._generate_html_body()
+
+            pdf.write_html(self.html_body)
+
+            # Save the pdf with name .pdf
+            pdf_bytes = pdf.output()
+
+            return bytes(pdf_bytes)
+        except Exception as e:
+            raise e
diff --git a/backend/open_webui/utils/response.py b/backend/open_webui/utils/response.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8501e92cc1b5afe410b5b66adfc5caa35fde9bd
--- /dev/null
+++ b/backend/open_webui/utils/response.py
@@ -0,0 +1,31 @@
+import json
+from open_webui.utils.misc import (
+    openai_chat_chunk_message_template,
+    openai_chat_completion_message_template,
+)
+
+
+def convert_response_ollama_to_openai(ollama_response: dict) -> dict:
+    model = ollama_response.get("model", "ollama")
+    message_content = ollama_response.get("message", {}).get("content", "")
+
+    response = openai_chat_completion_message_template(model, message_content)
+    return response
+
+
+async def convert_streaming_response_ollama_to_openai(ollama_streaming_response):
+    async for data in ollama_streaming_response.body_iterator:
+        data = json.loads(data)
+
+        model = data.get("model", "ollama")
+        message_content = data.get("message", {}).get("content", "")
+        done = data.get("done", False)
+
+        data = openai_chat_chunk_message_template(
+            model, message_content if not done else None
+        )
+
+        line = f"data: {json.dumps(data)}\n\n"
+        yield line
+
+    yield "data: [DONE]\n\n"
diff --git a/backend/open_webui/utils/schemas.py b/backend/open_webui/utils/schemas.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d1d448cd7f4a9ba4c4840d889ce18e0bd71bd89
--- /dev/null
+++ b/backend/open_webui/utils/schemas.py
@@ -0,0 +1,112 @@
+from ast import literal_eval
+from typing import Any, Literal, Optional, Type
+
+from pydantic import BaseModel, Field, create_model
+
+
+def json_schema_to_model(tool_dict: dict[str, Any]) -> Type[BaseModel]:
+    """
+    Converts a JSON schema to a Pydantic BaseModel class.
+
+    Args:
+        json_schema: The JSON schema to convert.
+
+    Returns:
+        A Pydantic BaseModel class.
+    """
+
+    # Extract the model name from the schema title.
+    model_name = tool_dict["name"]
+    schema = tool_dict["parameters"]
+
+    # Extract the field definitions from the schema properties.
+    field_definitions = {
+        name: json_schema_to_pydantic_field(name, prop, schema.get("required", []))
+        for name, prop in schema.get("properties", {}).items()
+    }
+
+    # Create the BaseModel class using create_model().
+    return create_model(model_name, **field_definitions)
+
+
+def json_schema_to_pydantic_field(
+    name: str, json_schema: dict[str, Any], required: list[str]
+) -> Any:
+    """
+    Converts a JSON schema property to a Pydantic field definition.
+
+    Args:
+        name: The field name.
+        json_schema: The JSON schema property.
+
+    Returns:
+        A Pydantic field definition.
+    """
+
+    # Get the field type.
+    type_ = json_schema_to_pydantic_type(json_schema)
+
+    # Get the field description.
+    description = json_schema.get("description")
+
+    # Get the field examples.
+    examples = json_schema.get("examples")
+
+    # Create a Field object with the type, description, and examples.
+    # The 'required' flag will be set later when creating the model.
+    return (
+        type_,
+        Field(
+            description=description,
+            examples=examples,
+            default=... if name in required else None,
+        ),
+    )
+
+
+def json_schema_to_pydantic_type(json_schema: dict[str, Any]) -> Any:
+    """
+    Converts a JSON schema type to a Pydantic type.
+
+    Args:
+        json_schema: The JSON schema to convert.
+
+    Returns:
+        A Pydantic type.
+    """
+
+    type_ = json_schema.get("type")
+
+    if type_ == "string" or type_ == "str":
+        return str
+    elif type_ == "integer" or type_ == "int":
+        return int
+    elif type_ == "number" or type_ == "float":
+        return float
+    elif type_ == "boolean" or type_ == "bool":
+        return bool
+    elif type_ == "array" or type_ == "list":
+        items_schema = json_schema.get("items")
+        if items_schema:
+            item_type = json_schema_to_pydantic_type(items_schema)
+            return list[item_type]
+        else:
+            return list
+    elif type_ == "object":
+        # Handle nested models.
+        properties = json_schema.get("properties")
+        if properties:
+            nested_model = json_schema_to_model(json_schema)
+            return nested_model
+        else:
+            return dict
+    elif type_ == "null":
+        return Optional[Any]  # Use Optional[Any] for nullable fields
+    elif type_ == "literal":
+        return Literal[literal_eval(json_schema.get("enum"))]
+    elif type_ == "optional":
+        inner_schema = json_schema.get("items", {"type": "string"})
+        inner_type = json_schema_to_pydantic_type(inner_schema)
+        return Optional[inner_type]
+    else:
+        raise ValueError(f"Unsupported JSON schema type: {type_}")
diff --git a/backend/open_webui/utils/security_headers.py b/backend/open_webui/utils/security_headers.py
new file mode 100644
index 0000000000000000000000000000000000000000..a656b29355a8e39a17527d59c6dac9f27fc51ab1
--- /dev/null
+++ b/backend/open_webui/utils/security_headers.py
@@ -0,0 +1,115 @@
+import re
+import os
+
+from fastapi import Request
+from starlette.middleware.base import BaseHTTPMiddleware
+from typing import Dict
+
+
+class SecurityHeadersMiddleware(BaseHTTPMiddleware):
+    async def dispatch(self, request: Request, call_next):
+        response = await call_next(request)
+        response.headers.update(set_security_headers())
+        return response
+
+
+def set_security_headers() -> Dict[str, str]:
+    """
+    Sets security headers based on environment variables.
+
+    This function reads specific environment variables and uses their values
+    to set corresponding security headers. The headers that can be set are:
+    - cache-control
+    - strict-transport-security
+    - referrer-policy
+    - x-content-type-options
+    - x-download-options
+    - x-frame-options
+    - x-permitted-cross-domain-policies
+
+    Each environment variable is associated with a specific setter function
+    that constructs the header. If the environment variable is set, the
+    corresponding header is added to the options dictionary.
+
+    Returns:
+        dict: A dictionary containing the security headers and their values.
+    """
+    options = {}
+    header_setters = {
+        "CACHE_CONTROL": set_cache_control,
+        "HSTS": set_hsts,
+        "REFERRER_POLICY": set_referrer,
+        "XCONTENT_TYPE": set_xcontent_type,
+        "XDOWNLOAD_OPTIONS": set_xdownload_options,
+        "XFRAME_OPTIONS": set_xframe,
+        "XPERMITTED_CROSS_DOMAIN_POLICIES": set_xpermitted_cross_domain_policies,
+    }
+
+    for env_var, setter in header_setters.items():
+        value = os.environ.get(env_var, None)
+        if value:
+            header = setter(value)
+            if header:
+                options.update(header)
+
+    return options
+
+
+# Set HTTP Strict Transport Security(HSTS) response header
+def set_hsts(value: str):
+    pattern = r"^max-age=(\d+)(;includeSubDomains)?(;preload)?$"
+    match = re.match(pattern, value, re.IGNORECASE)
+    if not match:
+        value = "max-age=31536000;includeSubDomains"
+    return {"Strict-Transport-Security": value}
+
+
+# Set X-Frame-Options response header
+def set_xframe(value: str):
+    pattern = r"^(DENY|SAMEORIGIN)$"
+    match = re.match(pattern, value, re.IGNORECASE)
+    if not match:
+        value = "DENY"
+    return {"X-Frame-Options": value}
+
+
+# Set Referrer-Policy response header
+def set_referrer(value: str):
+    pattern = r"^(no-referrer|no-referrer-when-downgrade|origin|origin-when-cross-origin|same-origin|strict-origin|strict-origin-when-cross-origin|unsafe-url)$"
+    match = re.match(pattern, value, re.IGNORECASE)
+    if not match:
+        value = "no-referrer"
+    return {"Referrer-Policy": value}
+
+
+# Set Cache-Control response header
+def set_cache_control(value: str):
+    pattern = r"^(public|private|no-cache|no-store|must-revalidate|proxy-revalidate|max-age=\d+|s-maxage=\d+|no-transform|immutable)(,\s*(public|private|no-cache|no-store|must-revalidate|proxy-revalidate|max-age=\d+|s-maxage=\d+|no-transform|immutable))*$"
+    match = re.match(pattern, value, re.IGNORECASE)
+    if not match:
+        value = "no-store, max-age=0"
+
+    return {"Cache-Control": value}
+
+
+# Set X-Download-Options response header
+def set_xdownload_options(value: str):
+    if value != "noopen":
+        value = "noopen"
+    return {"X-Download-Options": value}
+
+
+# Set X-Content-Type-Options response header
+def set_xcontent_type(value: str):
+    if value != "nosniff":
+        value = "nosniff"
+    return {"X-Content-Type-Options": value}
+
+
+# Set X-Permitted-Cross-Domain-Policies response header
+def set_xpermitted_cross_domain_policies(value: str):
+    pattern = r"^(none|master-only|by-content-type|by-ftp-filename)$"
+    match = re.match(pattern, value, re.IGNORECASE)
+    if not match:
+        value = "none"
+    return {"X-Permitted-Cross-Domain-Policies": value}
diff --git a/backend/open_webui/utils/task.py b/backend/open_webui/utils/task.py
new file mode 100644
index 0000000000000000000000000000000000000000..799cca11a8b25c366f0cbb10d31f6b0a61a11701
--- /dev/null
+++ b/backend/open_webui/utils/task.py
@@ -0,0 +1,223 @@
+import math
+import re
+from datetime import datetime
+from typing import Optional
+
+
+from open_webui.utils.misc import get_last_user_message, get_messages_content
+
+
+def prompt_template(
+    template: str, user_name: Optional[str] = None, user_location: Optional[str] = None
+) -> str:
+    # Get the current date
+    current_date = datetime.now()
+
+    # Format the date to YYYY-MM-DD
+    formatted_date = current_date.strftime("%Y-%m-%d")
+    formatted_time = current_date.strftime("%I:%M:%S %p")
+
+    template = template.replace("{{CURRENT_DATE}}", formatted_date)
+    template = template.replace("{{CURRENT_TIME}}", formatted_time)
+    template = template.replace(
+        "{{CURRENT_DATETIME}}", f"{formatted_date} {formatted_time}"
+    )
+
+    if user_name:
+        # Replace {{USER_NAME}} in the template with the user's name
+        template = template.replace("{{USER_NAME}}", user_name)
+    else:
+        # Replace {{USER_NAME}} in the template with "Unknown"
+        template = template.replace("{{USER_NAME}}", "Unknown")
+
+    if user_location:
+        # Replace {{USER_LOCATION}} in the template with the current location
+        template = template.replace("{{USER_LOCATION}}", user_location)
+    else:
+        # Replace {{USER_LOCATION}} in the template with "Unknown"
+        template = template.replace("{{USER_LOCATION}}", "Unknown")
+
+    return template
+
+
+def replace_prompt_variable(template: str, prompt: str) -> str:
+    def replacement_function(match):
+        full_match = match.group(0)
+        start_length = match.group(1)
+        end_length = match.group(2)
+        middle_length = match.group(3)
+
+        if full_match == "{{prompt}}":
+            return prompt
+        elif start_length is not None:
+            return prompt[: int(start_length)]
+        elif end_length is not None:
+            return prompt[-int(end_length) :]
+        elif middle_length is not None:
+            middle_length = int(middle_length)
+            if len(prompt) <= middle_length:
+                return prompt
+            start = prompt[: math.ceil(middle_length / 2)]
+            end = prompt[-math.floor(middle_length / 2) :]
+            return f"{start}...{end}"
+        return ""
+
+    template = re.sub(
+        r"{{prompt}}|{{prompt:start:(\d+)}}|{{prompt:end:(\d+)}}|{{prompt:middletruncate:(\d+)}}",
+        replacement_function,
+        template,
+    )
+    return template
+
+
+def replace_messages_variable(template: str, messages: list[str]) -> str:
+    def replacement_function(match):
+        full_match = match.group(0)
+        start_length = match.group(1)
+        end_length = match.group(2)
+        middle_length = match.group(3)
+
+        # Process messages based on the number of messages required
+        if full_match == "{{MESSAGES}}":
+            return get_messages_content(messages)
+        elif start_length is not None:
+            return get_messages_content(messages[: int(start_length)])
+        elif end_length is not None:
+            return get_messages_content(messages[-int(end_length) :])
+        elif middle_length is not None:
+            mid = int(middle_length)
+
+            if len(messages) <= mid:
+                return get_messages_content(messages)
+            # Handle middle truncation: split to get start and end portions of the messages list
+            half = mid // 2
+            start_msgs = messages[:half]
+            end_msgs = messages[-half:] if mid % 2 == 0 else messages[-(half + 1) :]
+            formatted_start = get_messages_content(start_msgs)
+            formatted_end = get_messages_content(end_msgs)
+            return f"{formatted_start}\n{formatted_end}"
+        return ""
+
+    template = re.sub(
+        r"{{MESSAGES}}|{{MESSAGES:START:(\d+)}}|{{MESSAGES:END:(\d+)}}|{{MESSAGES:MIDDLETRUNCATE:(\d+)}}",
+        replacement_function,
+        template,
+    )
+
+    return template
+
+
+# {{prompt:middletruncate:8000}}
+
+
+def title_generation_template(
+    template: str, messages: list[dict], user: Optional[dict] = None
+) -> str:
+    prompt = get_last_user_message(messages)
+    template = replace_prompt_variable(template, prompt)
+    template = replace_messages_variable(template, messages)
+
+    template = prompt_template(
+        template,
+        **(
+            {"user_name": user.get("name"), "user_location": user.get("location")}
+            if user
+            else {}
+        ),
+    )
+
+    return template
+
+
+def tags_generation_template(
+    template: str, messages: list[dict], user: Optional[dict] = None
+) -> str:
+    prompt = get_last_user_message(messages)
+    template = replace_prompt_variable(template, prompt)
+    template = replace_messages_variable(template, messages)
+
+    template = prompt_template(
+        template,
+        **(
+            {"user_name": user.get("name"), "user_location": user.get("location")}
+            if user
+            else {}
+        ),
+    )
+    return template
+
+
+def emoji_generation_template(
+    template: str, prompt: str, user: Optional[dict] = None
+) -> str:
+    template = replace_prompt_variable(template, prompt)
+    template = prompt_template(
+        template,
+        **(
+            {"user_name": user.get("name"), "user_location": user.get("location")}
+            if user
+            else {}
+        ),
+    )
+
+    return template
+
+
+def search_query_generation_template(
+    template: str, messages: list[dict], user: Optional[dict] = None
+) -> str:
+    prompt = get_last_user_message(messages)
+    template = replace_prompt_variable(template, prompt)
+    template = replace_messages_variable(template, messages)
+
+    template = prompt_template(
+        template,
+        **(
+            {"user_name": user.get("name"), "user_location": user.get("location")}
+            if user
+            else {}
+        ),
+    )
+    return template
+
+
+def moa_response_generation_template(
+    template: str, prompt: str, responses: list[str]
+) -> str:
+    def replacement_function(match):
+        full_match = match.group(0)
+        start_length = match.group(1)
+        end_length = match.group(2)
+        middle_length = match.group(3)
+
+        if full_match == "{{prompt}}":
+            return prompt
+        elif start_length is not None:
+            return prompt[: int(start_length)]
+        elif end_length is not None:
+            return prompt[-int(end_length) :]
+        elif middle_length is not None:
+            middle_length = int(middle_length)
+            if len(prompt) <= middle_length:
+                return prompt
+            start = prompt[: math.ceil(middle_length / 2)]
+            end = prompt[-math.floor(middle_length / 2) :]
+            return f"{start}...{end}"
+        return ""
+
+    template = re.sub(
+        r"{{prompt}}|{{prompt:start:(\d+)}}|{{prompt:end:(\d+)}}|{{prompt:middletruncate:(\d+)}}",
+        replacement_function,
+        template,
+    )
+
+    responses = [f'"""{response}"""' for response in responses]
+    responses = "\n\n".join(responses)
+
+    template = template.replace("{{responses}}", responses)
+    return template
+
+
+def tools_function_calling_generation_template(template: str, tools_specs: str) -> str:
+    template = template.replace("{{TOOLS}}", tools_specs)
+    return template
diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b57eb35b63ee32185084fd917fcb93e63d4233b
--- /dev/null
+++ b/backend/open_webui/utils/tools.py
@@ -0,0 +1,163 @@
+import inspect
+import logging
+from typing import Awaitable, Callable, get_type_hints
+
+from open_webui.apps.webui.models.tools import Tools
+from open_webui.apps.webui.models.users import UserModel
+from open_webui.apps.webui.utils import load_toolkit_module_by_id
+from open_webui.utils.schemas import json_schema_to_model
+
+log = logging.getLogger(__name__)
+
+
+def apply_extra_params_to_tool_function(
+    function: Callable, extra_params: dict
+) -> Callable[..., Awaitable]:
+    sig = inspect.signature(function)
+    extra_params = {
+        key: value for key, value in extra_params.items() if key in sig.parameters
+    }
+    is_coroutine = inspect.iscoroutinefunction(function)
+
+    async def new_function(**kwargs):
+        extra_kwargs = kwargs | extra_params
+        if is_coroutine:
+            return await function(**extra_kwargs)
+        return function(**extra_kwargs)
+
+    return new_function
+
+
+# Mutation on extra_params
+def get_tools(
+    webui_app, tool_ids: list[str], user: UserModel, extra_params: dict
+) -> dict[str, dict]:
+    tools = {}
+    for tool_id in tool_ids:
+        toolkit = Tools.get_tool_by_id(tool_id)
+        if toolkit is None:
+            continue
+
+        module = webui_app.state.TOOLS.get(tool_id, None)
+        if module is None:
+            module, _ = load_toolkit_module_by_id(tool_id)
+            webui_app.state.TOOLS[tool_id] = module
+
+        extra_params["__id__"] = tool_id
+        if hasattr(module, "valves") and hasattr(module, "Valves"):
+            valves = Tools.get_tool_valves_by_id(tool_id) or {}
+            module.valves = module.Valves(**valves)
+
+        if hasattr(module, "UserValves"):
+            extra_params["__user__"]["valves"] = module.UserValves(  # type: ignore
+                **Tools.get_user_valves_by_id_and_user_id(tool_id, user.id)
+            )
+
+        for spec in toolkit.specs:
+            # TODO: Fix hack for OpenAI API
+            for val in spec.get("parameters", {}).get("properties", {}).values():
+                if val["type"] == "str":
+                    val["type"] = "string"
+            function_name = spec["name"]
+
+            # convert to function that takes only model params and inserts custom params
+            original_func = getattr(module, function_name)
+            callable = apply_extra_params_to_tool_function(original_func, extra_params)
+            if hasattr(original_func, "__doc__"):
+                callable.__doc__ = original_func.__doc__
+
+            # TODO: This needs to be a pydantic model
+            tool_dict = {
+                "toolkit_id": tool_id,
+                "callable": callable,
+                "spec": spec,
+                "pydantic_model": json_schema_to_model(spec),
+                "file_handler": hasattr(module, "file_handler") and module.file_handler,
+                "citation": hasattr(module, "citation") and module.citation,
+            }
+
+            # TODO: if collision, prepend toolkit name
+            if function_name in tools:
+                log.warning(f"Tool {function_name} already exists in another toolkit!")
+                log.warning(f"Collision between {toolkit} and {tool_id}.")
+                log.warning(f"Discarding {toolkit}.{function_name}")
+            else:
+                tools[function_name] = tool_dict
+    return tools
+
+
+def doc_to_dict(docstring):
+    lines = docstring.split("\n")
+    description = lines[1].strip()
+    param_dict = {}
+
+    for line in lines:
+        if ":param" in line:
+            line = line.replace(":param", "").strip()
+            param, desc = line.split(":", 1)
+            param_dict[param.strip()] = desc.strip()
+    ret_dict = {"description": description, "params": param_dict}
+    return ret_dict
+
+
+def get_tools_specs(tools) -> list[dict]:
+    function_list = [
+        {"name": func, "function": getattr(tools, func)}
+        for func in dir(tools)
+        if callable(getattr(tools, func))
+        and not func.startswith("__")
+        and not inspect.isclass(getattr(tools, func))
+    ]
+
+    specs = []
+    for function_item in function_list:
+        function_name = function_item["name"]
+        function = function_item["function"]
+
+        function_doc = doc_to_dict(function.__doc__ or function_name)
+        specs.append(
+            {
+                "name": function_name,
+                # TODO: multi-line desc?
+                "description": function_doc.get("description", function_name),
+                "parameters": {
+                    "type": "object",
+                    "properties": {
+                        param_name: {
+                            "type": param_annotation.__name__.lower(),
+                            **(
+                                {
+                                    "enum": (
+                                        str(param_annotation.__args__)
+                                        if hasattr(param_annotation, "__args__")
+                                        else None
+                                    )
+                                }
+                                if hasattr(param_annotation, "__args__")
+                                else {}
+                            ),
+                            "description": function_doc.get("params", {}).get(
+                                param_name, param_name
+                            ),
+                        }
+                        for param_name, param_annotation in get_type_hints(
+                            function
+                        ).items()
+                        if param_name != "return"
+                        and not (
+                            param_name.startswith("__") and param_name.endswith("__")
+                        )
+                    },
+                    "required": [
+                        name
+                        for name, param in inspect.signature(
+                            function
+                        ).parameters.items()
+                        if param.default is param.empty
+                        and not (name.startswith("__") and name.endswith("__"))
+                    ],
+                },
+            }
+        )
+
+    return specs
diff --git a/backend/open_webui/utils/utils.py b/backend/open_webui/utils/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..79faa1831f4926ff760aaee1369a609a5f4aa315
--- /dev/null
+++ b/backend/open_webui/utils/utils.py
@@ -0,0 +1,141 @@
+import logging
+import uuid
+from datetime import UTC, datetime, timedelta
+from typing import Optional, Union
+
+import jwt
+from open_webui.apps.webui.models.users import Users
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.env import WEBUI_SECRET_KEY
+from fastapi import Depends, HTTPException, Request, Response, status
+from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
+from passlib.context import CryptContext
+
+logging.getLogger("passlib").setLevel(logging.ERROR)
+
+
+SESSION_SECRET = WEBUI_SECRET_KEY
+ALGORITHM = "HS256"
+
+##############
+# Auth Utils
+##############
+
+bearer_security = HTTPBearer(auto_error=False)
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+
+def verify_password(plain_password, hashed_password):
+    return (
+        pwd_context.verify(plain_password, hashed_password) if hashed_password else None
+    )
+
+
+def get_password_hash(password):
+    return pwd_context.hash(password)
+
+
+def create_token(data: dict, expires_delta: Union[timedelta, None] = None) -> str:
+    payload = data.copy()
+
+    if expires_delta:
+        expire = datetime.now(UTC) + expires_delta
+        payload.update({"exp": expire})
+
+    encoded_jwt = jwt.encode(payload, SESSION_SECRET, algorithm=ALGORITHM)
+    return encoded_jwt
+
+
+def decode_token(token: str) -> Optional[dict]:
+    try:
+        decoded = jwt.decode(token, SESSION_SECRET, algorithms=[ALGORITHM])
+        return decoded
+    except Exception:
+        return None
+
+
+def extract_token_from_auth_header(auth_header: str):
+    return auth_header[len("Bearer ") :]
+
+
+def create_api_key():
+    key = str(uuid.uuid4()).replace("-", "")
+    return f"sk-{key}"
+
+
+def get_http_authorization_cred(auth_header: str):
+    try:
+        scheme, credentials = auth_header.split(" ")
+        return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
+    except Exception:
+        raise ValueError(ERROR_MESSAGES.INVALID_TOKEN)
+
+
+def get_current_user(
+    request: Request,
+    auth_token: HTTPAuthorizationCredentials = Depends(bearer_security),
+):
+    token = None
+
+    if auth_token is not None:
+        token = auth_token.credentials
+
+    if token is None and "token" in request.cookies:
+        token = request.cookies.get("token")
+
+    if token is None:
+        raise HTTPException(status_code=403, detail="Not authenticated")
+
+    # auth by api key
+    if token.startswith("sk-"):
+        return get_current_user_by_api_key(token)
+
+    # auth by jwt token
+    data = decode_token(token)
+    if data is not None and "id" in data:
+        user = Users.get_user_by_id(data["id"])
+        if user is None:
+            raise HTTPException(
+                status_code=status.HTTP_401_UNAUTHORIZED,
+                detail=ERROR_MESSAGES.INVALID_TOKEN,
+            )
+        else:
+            Users.update_user_last_active_by_id(user.id)
+        return user
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.UNAUTHORIZED,
+        )
+
+
+def get_current_user_by_api_key(api_key: str):
+    user = Users.get_user_by_api_key(api_key)
+
+    if user is None:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.INVALID_TOKEN,
+        )
+    else:
+        Users.update_user_last_active_by_id(user.id)
+
+    return user
+
+
+def get_verified_user(user=Depends(get_current_user)):
+    if user.role not in {"user", "admin"}:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+    return user
+
+
+def get_admin_user(user=Depends(get_current_user)):
+    if user.role != "admin":
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+    return user
diff --git a/backend/open_webui/utils/webhook.py b/backend/open_webui/utils/webhook.py
new file mode 100644
index 0000000000000000000000000000000000000000..234209884fd9e9e1da544ae19cd0cc97db36ef61
--- /dev/null
+++ b/backend/open_webui/utils/webhook.py
@@ -0,0 +1,55 @@
+import json
+import logging
+
+import requests
+from open_webui.config import WEBUI_FAVICON_URL, WEBUI_NAME
+from open_webui.env import SRC_LOG_LEVELS, VERSION
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["WEBHOOK"])
+
+
+def post_webhook(url: str, message: str, event_data: dict) -> bool:
+    try:
+        payload = {}
+
+        # Slack and Google Chat Webhooks
+        if "https://hooks.slack.com" in url or "https://chat.googleapis.com" in url:
+            payload["text"] = message
+        # Discord Webhooks
+        elif "https://discord.com/api/webhooks" in url:
+            payload["content"] = message
+        # Microsoft Teams Webhooks
+        elif "webhook.office.com" in url:
+            action = event_data.get("action", "undefined")
+            facts = [
+                {"name": name, "value": value}
+                for name, value in json.loads(event_data.get("user", {})).items()
+            ]
+            payload = {
+                "@type": "MessageCard",
+                "@context": "http://schema.org/extensions",
+                "themeColor": "0076D7",
+                "summary": message,
+                "sections": [
+                    {
+                        "activityTitle": message,
+                        "activitySubtitle": f"{WEBUI_NAME} ({VERSION}) - {action}",
+                        "activityImage": WEBUI_FAVICON_URL,
+                        "facts": facts,
+                        "markdown": True,
+                    }
+                ],
+            }
+        # Default Payload
+        else:
+            payload = {**event_data}
+
+        log.debug(f"payload: {payload}")
+        r = requests.post(url, json=payload)
+        r.raise_for_status()
+        log.debug(f"r.text: {r.text}")
+        return True
+    except Exception as e:
+        log.exception(e)
+        return False
diff --git a/backend/requirements.txt b/backend/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..561f291cb556deeb5315d786c0a8fb0dba085aad
--- /dev/null
+++ b/backend/requirements.txt
@@ -0,0 +1,94 @@
+fastapi==0.111.0
+uvicorn[standard]==0.30.6
+pydantic==2.9.2
+python-multipart==0.0.9
+
+Flask==3.0.3
+Flask-Cors==5.0.0
+
+python-socketio==5.11.3
+python-jose==3.3.0
+passlib[bcrypt]==1.7.4
+
+requests==2.32.3
+aiohttp==3.10.8
+async-timeout
+
+sqlalchemy==2.0.32
+alembic==1.13.2
+peewee==3.17.6
+peewee-migrate==1.12.2
+psycopg2-binary==2.9.9
+PyMySQL==1.1.1
+bcrypt==4.2.0
+
+pymongo
+redis
+boto3==1.35.0
+
+argon2-cffi==23.1.0
+APScheduler==3.10.4
+
+# AI libraries
+openai
+anthropic
+google-generativeai==0.7.2
+tiktoken
+
+langchain==0.2.15
+langchain-community==0.2.12
+langchain-chroma==0.1.4
+
+fake-useragent==1.5.1
+chromadb==0.5.9
+pymilvus==2.4.7
+qdrant-client~=1.12.0
+
+sentence-transformers==3.2.0
+colbert-ai==0.2.21
+einops==0.8.0
+
+
+ftfy==6.2.3
+pypdf==4.3.1
+xhtml2pdf==0.2.16
+pymdown-extensions==10.11.2
+docx2txt==0.8
+python-pptx==1.0.0
+unstructured==0.15.9
+nltk==3.9.1
+Markdown==3.7
+pypandoc==1.13
+pandas==2.2.3
+openpyxl==3.1.5
+pyxlsb==1.0.10
+xlrd==2.0.1
+validators==0.33.0
+psutil
+
+opencv-python-headless==4.10.0.84
+rapidocr-onnxruntime==1.3.24
+
+fpdf2==2.7.9
+rank-bm25==0.2.2
+
+faster-whisper==1.0.3
+
+PyJWT[crypto]==2.9.0
+authlib==1.3.2
+
+black==24.8.0
+langfuse==2.44.0
+youtube-transcript-api==0.6.2
+pytube==15.0.0
+
+extract_msg
+pydub
+duckduckgo-search~=6.2.13
+
+## Tests
+docker~=7.1.0
+pytest~=8.3.2
+pytest-docker~=3.1.1
+
+googleapis-common-protos==1.63.2
diff --git a/backend/start.sh b/backend/start.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a945acb62e931cf1e0643d7bdcb5a414b4d71704
--- /dev/null
+++ b/backend/start.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+cd "$SCRIPT_DIR" || exit
+
+KEY_FILE=.webui_secret_key
+
+PORT="${PORT:-8080}"
+HOST="${HOST:-0.0.0.0}"
+if test "$WEBUI_SECRET_KEY $WEBUI_JWT_SECRET_KEY" = " "; then
+  echo "Loading WEBUI_SECRET_KEY from file, not provided as an environment variable."
+
+  if ! [ -e "$KEY_FILE" ]; then
+    echo "Generating WEBUI_SECRET_KEY"
+    # Generate a random value to use as a WEBUI_SECRET_KEY in case the user didn't provide one.
+    echo $(head -c 12 /dev/random | base64) > "$KEY_FILE"
+  fi
+
+  echo "Loading WEBUI_SECRET_KEY from $KEY_FILE"
+  WEBUI_SECRET_KEY=$(cat "$KEY_FILE")
+fi
+
+if [[ "${USE_OLLAMA_DOCKER,,}" == "true" ]]; then
+    echo "USE_OLLAMA is set to true, starting ollama serve."
+    ollama serve &
+fi
+
+if [[ "${USE_CUDA_DOCKER,,}" == "true" ]]; then
+  echo "CUDA is enabled, appending LD_LIBRARY_PATH to include torch/cudnn & cublas libraries."
+  export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib/python3.11/site-packages/torch/lib:/usr/local/lib/python3.11/site-packages/nvidia/cudnn/lib"
+fi
+
+# Check if SPACE_ID is set, if so, configure for space
+if [ -n "$SPACE_ID" ]; then
+  echo "Configuring for HuggingFace Space deployment"
+  if [ -n "$ADMIN_USER_EMAIL" ] && [ -n "$ADMIN_USER_PASSWORD" ]; then
+    echo "Admin user configured, creating"
+    WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" uvicorn open_webui.main:app --host "$HOST" --port "$PORT" --forwarded-allow-ips '*' &
+    webui_pid=$!
+    echo "Waiting for webui to start..."
+    while ! curl -s http://localhost:8080/health > /dev/null; do
+      sleep 1
+    done
+    echo "Creating admin user..."
+    curl \
+      -X POST "http://localhost:8080/api/v1/auths/signup" \
+      -H "accept: application/json" \
+      -H "Content-Type: application/json" \
+      -d "{ \"email\": \"${ADMIN_USER_EMAIL}\", \"password\": \"${ADMIN_USER_PASSWORD}\", \"name\": \"Admin\" }"
+    echo "Shutting down webui..."
+    kill $webui_pid
+  fi
+
+  export WEBUI_URL=${SPACE_HOST}
+fi
+
+WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" exec uvicorn open_webui.main:app --host "$HOST" --port "$PORT" --forwarded-allow-ips '*'
diff --git a/backend/start_windows.bat b/backend/start_windows.bat
new file mode 100644
index 0000000000000000000000000000000000000000..3e8c6b97c42d1c0d7d611a61f50b140173cea477
--- /dev/null
+++ b/backend/start_windows.bat
@@ -0,0 +1,33 @@
+:: This method is not recommended, and we recommend you use the `start.sh` file with WSL instead.
+@echo off
+SETLOCAL ENABLEDELAYEDEXPANSION
+
+:: Get the directory of the current script
+SET "SCRIPT_DIR=%~dp0"
+cd /d "%SCRIPT_DIR%" || exit /b
+
+SET "KEY_FILE=.webui_secret_key"
+IF "%PORT%"=="" SET PORT=8080
+IF "%HOST%"=="" SET HOST=0.0.0.0
+SET "WEBUI_SECRET_KEY=%WEBUI_SECRET_KEY%"
+SET "WEBUI_JWT_SECRET_KEY=%WEBUI_JWT_SECRET_KEY%"
+
+:: Check if WEBUI_SECRET_KEY and WEBUI_JWT_SECRET_KEY are not set
+IF "%WEBUI_SECRET_KEY%%WEBUI_JWT_SECRET_KEY%" == " " (
+    echo Loading WEBUI_SECRET_KEY from file, not provided as an environment variable.
+
+    IF NOT EXIST "%KEY_FILE%" (
+        echo Generating WEBUI_SECRET_KEY
+        :: Generate a random value to use as a WEBUI_SECRET_KEY in case the user didn't provide one
+        SET /p WEBUI_SECRET_KEY=<nul
+        FOR /L %%i IN (1,1,12) DO SET /p WEBUI_SECRET_KEY=<!random!>>%KEY_FILE%
+        echo WEBUI_SECRET_KEY generated
+    )
+
+    echo Loading WEBUI_SECRET_KEY from %KEY_FILE%
+    SET /p WEBUI_SECRET_KEY=<%KEY_FILE%
+)
+
+:: Execute uvicorn
+SET "WEBUI_SECRET_KEY=%WEBUI_SECRET_KEY%"
+uvicorn open_webui.main:app --host "%HOST%" --port "%PORT%" --forwarded-allow-ips '*'
diff --git a/confirm_remove.sh b/confirm_remove.sh
new file mode 100755
index 0000000000000000000000000000000000000000..051908e6de668bc47b3edd5b79b70d65094823a2
--- /dev/null
+++ b/confirm_remove.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+echo "Warning: This will remove all containers and volumes, including persistent data. Do you want to continue? [Y/N]"
+read ans
+if [ "$ans" == "Y" ] || [ "$ans" == "y" ]; then
+  command docker-compose 2>/dev/null
+  if [ "$?" == "0" ]; then
+    docker-compose down -v
+  else
+    docker compose down -v
+  fi
+else
+  echo "Operation cancelled."
+fi
diff --git a/cypress.config.ts b/cypress.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dbb538233809b1c26de4c0d33ae45e0c30405c2a
--- /dev/null
+++ b/cypress.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from 'cypress';
+
+export default defineConfig({
+	e2e: {
+		baseUrl: 'http://localhost:8080'
+	},
+	video: true
+});
diff --git a/cypress/data/example-doc.txt b/cypress/data/example-doc.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d4f6f455ed18711baf9ddb8ae814538ea7076e8d
--- /dev/null
+++ b/cypress/data/example-doc.txt
@@ -0,0 +1,9 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pellentesque elit eget gravida cum sociis natoque. Morbi tristique senectus et netus et malesuada. Sapien nec sagittis aliquam malesuada bibendum. Amet consectetur adipiscing elit duis tristique sollicitudin. Non pulvinar neque laoreet suspendisse interdum consectetur libero. Arcu cursus vitae congue mauris rhoncus aenean vel elit scelerisque. Nec feugiat nisl pretium fusce id velit. Imperdiet proin fermentum leo vel. Arcu dui vivamus arcu felis bibendum ut tristique et egestas. Pellentesque sit amet porttitor eget dolor morbi non arcu risus. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam. Et ultrices neque ornare aenean euismod.
+
+Enim nulla aliquet porttitor lacus luctus accumsan tortor posuere ac. Viverra nibh cras pulvinar mattis nunc. Lacinia at quis risus sed vulputate. Ac tortor vitae purus faucibus ornare suspendisse sed nisi lacus. Bibendum arcu vitae elementum curabitur vitae nunc. Consectetur adipiscing elit duis tristique sollicitudin nibh sit amet commodo. Velit egestas dui id ornare arcu odio ut. Et malesuada fames ac turpis egestas integer eget aliquet. Lacus suspendisse faucibus interdum posuere lorem ipsum dolor sit. Morbi tristique senectus et netus. Pretium viverra suspendisse potenti nullam ac tortor vitae. Parturient montes nascetur ridiculus mus mauris vitae. Quis viverra nibh cras pulvinar mattis nunc sed blandit libero. Euismod nisi porta lorem mollis aliquam ut porttitor leo. Mauris in aliquam sem fringilla ut morbi. Faucibus pulvinar elementum integer enim neque. Neque sodales ut etiam sit. Consectetur a erat nam at.
+
+Sed nisi lacus sed viverra tellus in hac habitasse. Proin sagittis nisl rhoncus mattis rhoncus. Risus commodo viverra maecenas accumsan lacus. Morbi quis commodo odio aenean sed adipiscing. Mollis nunc sed id semper risus in. Ultricies mi eget mauris pharetra et ultrices neque. Amet luctus venenatis lectus magna fringilla urna porttitor rhoncus. Eget magna fermentum iaculis eu non diam phasellus. Id diam maecenas ultricies mi eget mauris pharetra et ultrices. Id donec ultrices tincidunt arcu non sodales. Sed cras ornare arcu dui vivamus arcu felis bibendum ut. Urna duis convallis convallis tellus id interdum velit. Rhoncus mattis rhoncus urna neque viverra justo nec. Purus semper eget duis at tellus at urna condimentum. Et odio pellentesque diam volutpat commodo sed egestas. Blandit volutpat maecenas volutpat blandit. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Est ullamcorper eget nulla facilisi etiam dignissim.
+
+Justo nec ultrices dui sapien eget mi proin sed. Purus gravida quis blandit turpis cursus in hac. Placerat orci nulla pellentesque dignissim enim sit. Morbi tristique senectus et netus et malesuada fames ac. Consequat mauris nunc congue nisi. Eu lobortis elementum nibh tellus molestie nunc non blandit. Viverra justo nec ultrices dui. Morbi non arcu risus quis. Elementum sagittis vitae et leo duis. Lectus mauris ultrices eros in cursus. Neque laoreet suspendisse interdum consectetur.
+
+Facilisis gravida neque convallis a cras. Nisl rhoncus mattis rhoncus urna neque viverra justo. Faucibus purus in massa tempor. Lacus laoreet non curabitur gravida arcu ac tortor. Tincidunt eget nullam non nisi est sit amet. Ornare lectus sit amet est placerat in egestas. Sollicitudin tempor id eu nisl nunc mi. Scelerisque viverra mauris in aliquam sem fringilla ut. Ullamcorper sit amet risus nullam. Mauris rhoncus aenean vel elit scelerisque mauris pellentesque pulvinar. Velit euismod in pellentesque massa placerat duis ultricies lacus. Pharetra magna ac placerat vestibulum lectus mauris ultrices eros in. Lorem ipsum dolor sit amet. Sit amet mauris commodo quis imperdiet. Quam pellentesque nec nam aliquam sem et tortor. Amet nisl purus in mollis nunc. Sed risus pretium quam vulputate dignissim suspendisse in est. Nisl condimentum id venenatis a condimentum. Velit euismod in pellentesque massa. Quam id leo in vitae turpis massa sed.
diff --git a/cypress/e2e/chat.cy.ts b/cypress/e2e/chat.cy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..17c4d8e735239d8289629bb5056946d9797dd701
--- /dev/null
+++ b/cypress/e2e/chat.cy.ts
@@ -0,0 +1,106 @@
+// eslint-disable-next-line @typescript-eslint/triple-slash-reference
+/// <reference path="../support/index.d.ts" />
+
+// These tests run through the chat flow.
+describe('Settings', () => {
+	// Wait for 2 seconds after all tests to fix an issue with Cypress's video recording missing the last few frames
+	after(() => {
+		// eslint-disable-next-line cypress/no-unnecessary-waiting
+		cy.wait(2000);
+	});
+
+	beforeEach(() => {
+		// Login as the admin user
+		cy.loginAdmin();
+		// Visit the home page
+		cy.visit('/');
+	});
+
+	context('Ollama', () => {
+		it('user can select a model', () => {
+			// Click on the model selector
+			cy.get('button[aria-label="Select a model"]').click();
+			// Select the first model
+			cy.get('button[aria-label="model-item"]').first().click();
+		});
+
+		it('user can perform text chat', () => {
+			// Click on the model selector
+			cy.get('button[aria-label="Select a model"]').click();
+			// Select the first model
+			cy.get('button[aria-label="model-item"]').first().click();
+			// Type a message
+			cy.get('#chat-input').type('Hi, what can you do? A single sentence only please.', {
+				force: true
+			});
+			// Send the message
+			cy.get('button[type="submit"]').click();
+			// User's message should be visible
+			cy.get('.chat-user').should('exist');
+			// Wait for the response
+			// .chat-assistant is created after the first token is received
+			cy.get('.chat-assistant', { timeout: 10_000 }).should('exist');
+			// Generation Info is created after the stop token is received
+			cy.get('div[aria-label="Generation Info"]', { timeout: 120_000 }).should('exist');
+		});
+
+		it('user can share chat', () => {
+			// Click on the model selector
+			cy.get('button[aria-label="Select a model"]').click();
+			// Select the first model
+			cy.get('button[aria-label="model-item"]').first().click();
+			// Type a message
+			cy.get('#chat-input').type('Hi, what can you do? A single sentence only please.', {
+				force: true
+			});
+			// Send the message
+			cy.get('button[type="submit"]').click();
+			// User's message should be visible
+			cy.get('.chat-user').should('exist');
+			// Wait for the response
+			// .chat-assistant is created after the first token is received
+			cy.get('.chat-assistant', { timeout: 10_000 }).should('exist');
+			// Generation Info is created after the stop token is received
+			cy.get('div[aria-label="Generation Info"]', { timeout: 120_000 }).should('exist');
+			// spy on requests
+			const spy = cy.spy();
+			cy.intercept('POST', '/api/v1/chats/**/share', spy);
+			// Open context menu
+			cy.get('#chat-context-menu-button').click();
+			// Click share button
+			cy.get('#chat-share-button').click();
+			// Check if the share dialog is visible
+			cy.get('#copy-and-share-chat-button').should('exist');
+			// Click the copy button
+			cy.get('#copy-and-share-chat-button').click();
+			cy.wrap({}, { timeout: 5_000 }).should(() => {
+				// Check if the share request was made
+				expect(spy).to.be.callCount(1);
+			});
+		});
+
+		it('user can generate image', () => {
+			// Click on the model selector
+			cy.get('button[aria-label="Select a model"]').click();
+			// Select the first model
+			cy.get('button[aria-label="model-item"]').first().click();
+			// Type a message
+			cy.get('#chat-input').type('Hi, what can you do? A single sentence only please.', {
+				force: true
+			});
+			// Send the message
+			cy.get('button[type="submit"]').click();
+			// User's message should be visible
+			cy.get('.chat-user').should('exist');
+			// Wait for the response
+			// .chat-assistant is created after the first token is received
+			cy.get('.chat-assistant', { timeout: 10_000 }).should('exist');
+			// Generation Info is created after the stop token is received
+			cy.get('div[aria-label="Generation Info"]', { timeout: 120_000 }).should('exist');
+			// Click on the generate image button
+			cy.get('[aria-label="Generate Image"]').click();
+			// Wait for image to be visible
+			cy.get('img[data-cy="image"]', { timeout: 60_000 }).should('be.visible');
+		});
+	});
+});
diff --git a/cypress/e2e/documents.cy.ts b/cypress/e2e/documents.cy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b14b1de20c916d4b1303a8d304c5abc334e949ea
--- /dev/null
+++ b/cypress/e2e/documents.cy.ts
@@ -0,0 +1,2 @@
+// eslint-disable-next-line @typescript-eslint/triple-slash-reference
+/// <reference path="../support/index.d.ts" />
diff --git a/cypress/e2e/registration.cy.ts b/cypress/e2e/registration.cy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..232d75e882a93870f318f96f98ffd56efbc4bd8f
--- /dev/null
+++ b/cypress/e2e/registration.cy.ts
@@ -0,0 +1,52 @@
+// eslint-disable-next-line @typescript-eslint/triple-slash-reference
+/// <reference path="../support/index.d.ts" />
+import { adminUser } from '../support/e2e';
+
+// These tests assume the following defaults:
+// 1. No users exist in the database or that the test admin user is an admin
+// 2. Language is set to English
+// 3. The default role for new users is 'pending'
+describe('Registration and Login', () => {
+	// Wait for 2 seconds after all tests to fix an issue with Cypress's video recording missing the last few frames
+	after(() => {
+		// eslint-disable-next-line cypress/no-unnecessary-waiting
+		cy.wait(2000);
+	});
+
+	beforeEach(() => {
+		cy.visit('/');
+	});
+
+	it('should register a new user as pending', () => {
+		const userName = `Test User - ${Date.now()}`;
+		const userEmail = `cypress-${Date.now()}@example.com`;
+		// Toggle from sign in to sign up
+		cy.contains('Sign up').click();
+		// Fill out the form
+		cy.get('input[autocomplete="name"]').type(userName);
+		cy.get('input[autocomplete="email"]').type(userEmail);
+		cy.get('input[type="password"]').type('password');
+		// Submit the form
+		cy.get('button[type="submit"]').click();
+		// Wait until the user is redirected to the home page
+		cy.contains(userName);
+		// Expect the user to be pending
+		cy.contains('Check Again');
+	});
+
+	it('can login with the admin user', () => {
+		// Fill out the form
+		cy.get('input[autocomplete="email"]').type(adminUser.email);
+		cy.get('input[type="password"]').type(adminUser.password);
+		// Submit the form
+		cy.get('button[type="submit"]').click();
+		// Wait until the user is redirected to the home page
+		cy.contains(adminUser.name);
+		// Dismiss the changelog dialog if it is visible
+		cy.getAllLocalStorage().then((ls) => {
+			if (!ls['version']) {
+				cy.get('button').contains("Okay, Let's Go!").click();
+			}
+		});
+	});
+});
diff --git a/cypress/e2e/settings.cy.ts b/cypress/e2e/settings.cy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4ea91698072fd25cb75a6961ac3d9f0652c303da
--- /dev/null
+++ b/cypress/e2e/settings.cy.ts
@@ -0,0 +1,63 @@
+// eslint-disable-next-line @typescript-eslint/triple-slash-reference
+/// <reference path="../support/index.d.ts" />
+import { adminUser } from '../support/e2e';
+
+// These tests run through the various settings pages, ensuring that the user can interact with them as expected
+describe('Settings', () => {
+	// Wait for 2 seconds after all tests to fix an issue with Cypress's video recording missing the last few frames
+	after(() => {
+		// eslint-disable-next-line cypress/no-unnecessary-waiting
+		cy.wait(2000);
+	});
+
+	beforeEach(() => {
+		// Login as the admin user
+		cy.loginAdmin();
+		// Visit the home page
+		cy.visit('/');
+		// Click on the user menu
+		cy.get('button[aria-label="User Menu"]').click();
+		// Click on the settings link
+		cy.get('button').contains('Settings').click();
+	});
+
+	context('General', () => {
+		it('user can open the General modal and hit save', () => {
+			cy.get('button').contains('General').click();
+			cy.get('button').contains('Save').click();
+		});
+	});
+
+	context('Interface', () => {
+		it('user can open the Interface modal and hit save', () => {
+			cy.get('button').contains('Interface').click();
+			cy.get('button').contains('Save').click();
+		});
+	});
+
+	context('Audio', () => {
+		it('user can open the Audio modal and hit save', () => {
+			cy.get('button').contains('Audio').click();
+			cy.get('button').contains('Save').click();
+		});
+	});
+
+	context('Chats', () => {
+		it('user can open the Chats modal', () => {
+			cy.get('button').contains('Chats').click();
+		});
+	});
+
+	context('Account', () => {
+		it('user can open the Account modal and hit save', () => {
+			cy.get('button').contains('Account').click();
+			cy.get('button').contains('Save').click();
+		});
+	});
+
+	context('About', () => {
+		it('user can open the About modal', () => {
+			cy.get('button').contains('About').click();
+		});
+	});
+});
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0b94c47872c87b7ac9aa4ed9752cf46a3ec51c8f
--- /dev/null
+++ b/cypress/support/e2e.ts
@@ -0,0 +1,78 @@
+/// <reference types="cypress" />
+// eslint-disable-next-line @typescript-eslint/triple-slash-reference
+/// <reference path="../support/index.d.ts" />
+
+export const adminUser = {
+	name: 'Admin User',
+	email: 'admin@example.com',
+	password: 'password'
+};
+
+const login = (email: string, password: string) => {
+	return cy.session(
+		email,
+		() => {
+			// Make sure to test against us english to have stable tests,
+			// regardless on local language preferences
+			localStorage.setItem('locale', 'en-US');
+			// Visit auth page
+			cy.visit('/auth');
+			// Fill out the form
+			cy.get('input[autocomplete="email"]').type(email);
+			cy.get('input[type="password"]').type(password);
+			// Submit the form
+			cy.get('button[type="submit"]').click();
+			// Wait until the user is redirected to the home page
+			cy.get('#chat-search').should('exist');
+			// Get the current version to skip the changelog dialog
+			if (localStorage.getItem('version') === null) {
+				cy.get('button').contains("Okay, Let's Go!").click();
+			}
+		},
+		{
+			validate: () => {
+				cy.request({
+					method: 'GET',
+					url: '/api/v1/auths/',
+					headers: {
+						Authorization: 'Bearer ' + localStorage.getItem('token')
+					}
+				});
+			}
+		}
+	);
+};
+
+const register = (name: string, email: string, password: string) => {
+	return cy
+		.request({
+			method: 'POST',
+			url: '/api/v1/auths/signup',
+			body: {
+				name: name,
+				email: email,
+				password: password
+			},
+			failOnStatusCode: false
+		})
+		.then((response) => {
+			expect(response.status).to.be.oneOf([200, 400]);
+		});
+};
+
+const registerAdmin = () => {
+	return register(adminUser.name, adminUser.email, adminUser.password);
+};
+
+const loginAdmin = () => {
+	return login(adminUser.email, adminUser.password);
+};
+
+Cypress.Commands.add('login', (email, password) => login(email, password));
+Cypress.Commands.add('register', (name, email, password) => register(name, email, password));
+Cypress.Commands.add('registerAdmin', () => registerAdmin());
+Cypress.Commands.add('loginAdmin', () => loginAdmin());
+
+before(() => {
+	cy.registerAdmin();
+});
diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..647db92115a3c6ee9e5b35b90b585a2a0a837306
--- /dev/null
+++ b/cypress/support/index.d.ts
@@ -0,0 +1,13 @@
+// load the global Cypress types
+/// <reference types="cypress" />
+
+declare namespace Cypress {
+	interface Chainable {
+		login(email: string, password: string): Chainable<Element>;
+		register(name: string, email: string, password: string): Chainable<Element>;
+		registerAdmin(): Chainable<Element>;
+		loginAdmin(): Chainable<Element>;
+		uploadTestDocument(suffix: any): Chainable<Element>;
+		deleteTestDocument(suffix: any): Chainable<Element>;
+	}
+}
diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..ff28d946496fa69c603c0ec9287317a7dcc83279
--- /dev/null
+++ b/cypress/tsconfig.json
@@ -0,0 +1,7 @@
+{
+	"extends": "../tsconfig.json",
+	"compilerOptions": {
+		"inlineSourceMap": true,
+		"sourceMap": false
+	}
+}
diff --git a/docker-compose.a1111-test.yaml b/docker-compose.a1111-test.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e6ab12c07f0995369107943d0b23a68ebac607b3
--- /dev/null
+++ b/docker-compose.a1111-test.yaml
@@ -0,0 +1,31 @@
+# This is an overlay that spins up stable-diffusion-webui for integration testing
+# This is not designed to be used in production
+services:
+  stable-diffusion-webui:
+    # Not built for ARM64
+    platform: linux/amd64
+    image: ghcr.io/neggles/sd-webui-docker:latest
+    restart: unless-stopped
+    environment:
+      CLI_ARGS: "--api --use-cpu all --precision full --no-half --skip-torch-cuda-test --ckpt /empty.pt --do-not-download-clip --disable-nan-check --disable-opt-split-attention"
+      PYTHONUNBUFFERED: "1"
+      TERM: "vt100"
+      SD_WEBUI_VARIANT: "default"
+    # Hack to get container working on Apple Silicon
+    # Rosetta creates a conflict ${HOME}/.cache folder
+    entrypoint: /bin/bash
+    command:
+      - -c
+      - |
+        export HOME=/root-home
+        rm -rf $${HOME}/.cache
+        /docker/entrypoint.sh python -u webui.py --listen --port $${WEBUI_PORT} --skip-version-check $${CLI_ARGS}
+    volumes:
+      - ./test/test_files/image_gen/sd-empty.pt:/empty.pt
+
+  open-webui:
+    environment:
+      ENABLE_IMAGE_GENERATION: "true"
+      AUTOMATIC1111_BASE_URL: http://stable-diffusion-webui:7860
+      IMAGE_SIZE: "64x64"
+      IMAGE_STEPS: "3"
diff --git a/docker-compose.amdgpu.yaml b/docker-compose.amdgpu.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..7a1295d94515c6f32a9c11d913ce603a989f504a
--- /dev/null
+++ b/docker-compose.amdgpu.yaml
@@ -0,0 +1,8 @@
+services:
+  ollama:
+    devices:
+      - /dev/kfd:/dev/kfd
+      - /dev/dri:/dev/dri
+    image: ollama/ollama:${OLLAMA_DOCKER_TAG-rocm}
+    environment:
+      - 'HSA_OVERRIDE_GFX_VERSION=${HSA_OVERRIDE_GFX_VERSION-11.0.0}'
\ No newline at end of file
diff --git a/docker-compose.api.yaml b/docker-compose.api.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8f8fbe59ad03274ef4f6a414cc9333290588e0aa
--- /dev/null
+++ b/docker-compose.api.yaml
@@ -0,0 +1,5 @@
+services:
+  ollama:
+    # Expose Ollama API outside the container stack
+    ports:
+      - ${OLLAMA_WEBAPI_PORT-11434}:11434
diff --git a/docker-compose.data.yaml b/docker-compose.data.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4b70601f894a1ad3530cc25ea616a3a5ddb691a6
--- /dev/null
+++ b/docker-compose.data.yaml
@@ -0,0 +1,4 @@
+services:
+  ollama:
+    volumes:
+      - ${OLLAMA_DATA_DIR-./ollama-data}:/root/.ollama
diff --git a/docker-compose.gpu.yaml b/docker-compose.gpu.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..de821235da42f5e116315f7407d2c6de0451c99c
--- /dev/null
+++ b/docker-compose.gpu.yaml
@@ -0,0 +1,11 @@
+services:
+  ollama:
+    # GPU support
+    deploy:
+      resources:
+        reservations:
+          devices:
+            - driver: ${OLLAMA_GPU_DRIVER-nvidia}
+              count: ${OLLAMA_GPU_COUNT-1}
+              capabilities:
+                - gpu
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..74249febd9e37e2166fa6c07a73770812e745b7d
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,34 @@
+services:
+  ollama:
+    volumes:
+      - ollama:/root/.ollama
+    container_name: ollama
+    pull_policy: always
+    tty: true
+    restart: unless-stopped
+    image: ollama/ollama:${OLLAMA_DOCKER_TAG-latest}
+
+  open-webui:
+    build:
+      context: .
+      args:
+        OLLAMA_BASE_URL: '/ollama'
+      dockerfile: Dockerfile
+    image: ghcr.io/open-webui/open-webui:${WEBUI_DOCKER_TAG-main}
+    container_name: open-webui
+    volumes:
+      - open-webui:/app/backend/data
+    depends_on:
+      - ollama
+    ports:
+      - ${OPEN_WEBUI_PORT-3000}:8080
+    environment:
+      - 'OLLAMA_BASE_URL=http://ollama:11434'
+      - 'WEBUI_SECRET_KEY='
+    extra_hosts:
+      - host.docker.internal:host-gateway
+    restart: unless-stopped
+
+volumes:
+  ollama: {}
+  open-webui: {}
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..ec8a79bbcea4ed9b62faa312e1ca6041b4426f3c
--- /dev/null
+++ b/docs/CONTRIBUTING.md
@@ -0,0 +1,73 @@
+# Contributing to Open WebUI
+
+🚀 **Welcome, Contributors!** 🚀
+
+Your interest in contributing to Open WebUI is greatly appreciated. This document is here to guide you through the process, ensuring your contributions enhance the project effectively. Let's make Open WebUI even better, together!
+
+## 📌 Key Points
+
+### 🦙 Ollama vs. Open WebUI
+
+It's crucial to distinguish between Ollama and Open WebUI:
+
+- **Open WebUI** focuses on providing an intuitive and responsive web interface for chat interactions.
+- **Ollama** is the underlying technology that powers these interactions.
+
+If your issue or contribution pertains directly to the core Ollama technology, please direct it to the appropriate [Ollama project repository](https://ollama.com/). Open WebUI's repository is dedicated to the web interface aspect only.
+
+### 🚨 Reporting Issues
+
+Noticed something off? Have an idea? Check our [Issues tab](https://github.com/open-webui/open-webui/issues) to see if it's already been reported or suggested. If not, feel free to open a new issue. When reporting an issue, please follow our issue templates. These templates are designed to ensure that all necessary details are provided from the start, enabling us to address your concerns more efficiently.
+
+> [!IMPORTANT]
+>
+> - **Template Compliance:** Please be aware that failure to follow the provided issue template, or not providing the requested information at all, will likely result in your issue being closed without further consideration. This approach is critical for maintaining the manageability and integrity of issue tracking.
+> - **Detail is Key:** To ensure your issue is understood and can be effectively addressed, it's imperative to include comprehensive details. Descriptions should be clear, including steps to reproduce, expected outcomes, and actual results. Lack of sufficient detail may hinder our ability to resolve your issue.
+
+### 🧭 Scope of Support
+
+We've noticed an uptick in issues not directly related to Open WebUI but rather to the environment it's run in, especially Docker setups. While we strive to support Docker deployment, understanding Docker fundamentals is crucial for a smooth experience.
+
+- **Docker Deployment Support**: Open WebUI supports Docker deployment. Familiarity with Docker is assumed. For Docker basics, please refer to the [official Docker documentation](https://docs.docker.com/get-started/overview/).
+
+- **Advanced Configurations**: Setting up reverse proxies for HTTPS and managing Docker deployments requires foundational knowledge. There are numerous online resources available to learn these skills. Ensuring you have this knowledge will greatly enhance your experience with Open WebUI and similar projects.
+
+## 💡 Contributing
+
+Looking to contribute? Great! Here's how you can help:
+
+### 🛠 Pull Requests
+
+We welcome pull requests. Before submitting one, please:
+
+1. Open a discussion regarding your ideas [here](https://github.com/open-webui/open-webui/discussions/new/choose).
+2. Follow the project's coding standards and include tests for new features.
+3. Update documentation as necessary.
+4. Write clear, descriptive commit messages.
+5. It's essential to complete your pull request in a timely manner. We move fast, and having PRs hang around too long is not feasible. If you can't get it done within a reasonable time frame, we may have to close it to keep the project moving forward.
+
+### 📚 Documentation & Tutorials
+
+Help us make Open WebUI more accessible by improving documentation, writing tutorials, or creating guides on setting up and optimizing the web UI.
+
+### 🌐 Translations and Internationalization
+
+Help us make Open WebUI available to a wider audience. In this section, we'll guide you through the process of adding new translations to the project.
+
+We use JSON files to store translations. You can find the existing translation files in the `src/lib/i18n/locales` directory. Each directory corresponds to a specific language, for example, `en-US` for English (US), `fr-FR` for French (France) and so on. You can refer to [ISO 639 Language Codes](http://www.lingoes.net/en/translator/langcode.htm) to find the appropriate code for a specific language.
+
+To add a new language:
+
+- Create a new directory in the `src/lib/i18n/locales` path with the appropriate language code as its name. For instance, if you're adding translations for Spanish (Spain), create a new directory named `es-ES`.
+- Copy the American English translation file(s) (from `en-US` directory in `src/lib/i18n/locale`) to this new directory and update the string values in JSON format according to your language. Make sure to preserve the structure of the JSON object.
+- Add the language code and its respective title to languages file at `src/lib/i18n/locales/languages.json`.
+
+### 🤔 Questions & Feedback
+
+Got questions or feedback? Join our [Discord community](https://discord.gg/5rJgQTnV4s) or open an issue. We're here to help!
+
+## 🙏 Thank You!
+
+Your contributions, big or small, make a significant impact on Open WebUI. We're excited to see what you bring to the project!
+
+Together, let's create an even more powerful tool for the community. 🌟
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4113c35a78f9d9c7d3096cf851f76f495421fc51
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,3 @@
+# Project workflow
+
+[![](https://mermaid.ink/img/pako:eNq1k01rAjEQhv_KkFNLFe1N9iAUevFSRVl6Cci4Gd1ANtlmsmtF_O_N7iqtHxR76ClhMu87zwyZvcicIpEIpo-KbEavGjceC2lL9EFnukQbIGXygNye5y9TY7DAZTpZLsjXXVYXg3dapRM4hh9mu5A7-3hTfSXtAtJK21Tsj8dPl3USmJZkGVbebWNKD2rNOjAYl6HJHYdkNBwNpb3U9aNZvzFNYE6h8tFiSyZzBUGJG4K1dwVwTSYQrCptlLRvLt5dA5i2la5Ruk51Ux0VKQjuxPVbAwuyiuFlNgHfzJ5DoxtgqQf1813gnZRLZ5lAYcD7WT1lpGtiQKug9C4jZrrp-Fd-1-Y1bdzo4dvnZDLz7lPHyj8sOgfg4x84E7RTuEaZt8yRZqtDfgT_rwG2u3Dv_ERPFOQL1Cqu2F5aAClCTgVJkcSrojVWJkgh7SGmYhXcYmczkQRfUU9UZfQ4baRI1miYDl_QqlPg?type=png)](https://mermaid.live/edit#pako:eNq1k01rAjEQhv_KkFNLFe1N9iAUevFSRVl6Cci4Gd1ANtlmsmtF_O_N7iqtHxR76ClhMu87zwyZvcicIpEIpo-KbEavGjceC2lL9EFnukQbIGXygNye5y9TY7DAZTpZLsjXXVYXg3dapRM4hh9mu5A7-3hTfSXtAtJK21Tsj8dPl3USmJZkGVbebWNKD2rNOjAYl6HJHYdkNBwNpb3U9aNZvzFNYE6h8tFiSyZzBUGJG4K1dwVwTSYQrCptlLRvLt5dA5i2la5Ruk51Ux0VKQjuxPVbAwuyiuFlNgHfzJ5DoxtgqQf1813gnZRLZ5lAYcD7WT1lpGtiQKug9C4jZrrp-Fd-1-Y1bdzo4dvnZDLz7lPHyj8sOgfg4x84E7RTuEaZt8yRZqtDfgT_rwG2u3Dv_ERPFOQL1Cqu2F5aAClCTgVJkcSrojVWJkgh7SGmYhXcYmczkQRfUU9UZfQ4baRI1miYDl_QqlPg)
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
new file mode 100644
index 0000000000000000000000000000000000000000..507e3c606955fac816c413d1ab1f8ec5ebd1f2e1
--- /dev/null
+++ b/docs/SECURITY.md
@@ -0,0 +1,44 @@
+# Security Policy
+
+Our primary goal is to ensure the protection and confidentiality of sensitive data stored by users on open-webui.
+
+## Supported Versions
+
+| Version | Supported          |
+| ------- | ------------------ |
+| main    | :white_check_mark: |
+| others  | :x:                |
+
+## Zero Tolerance for External Platforms
+
+Based on a precedent of an unacceptable degree of spamming and unsolicited communications from third-party platforms, we forcefully reaffirm our stance. **We refuse to engage with, join, or monitor any platforms outside of GitHub for vulnerability reporting.** Our reasons are not just procedural but are deep-seated in the ethos of our project, which champions transparency and direct community interaction inherent in the open-source culture. Any attempts to divert our processes to external platforms will be met with outright rejection. This policy is non-negotiable and understands no exceptions.
+
+Any reports or solicitations arriving from sources other than our designated GitHub repository will be dismissed without consideration. We’ve seen how external engagements can dilute and compromise the integrity of community-driven projects, and we’re not here to gamble with the security and privacy of our user community.
+
+## Reporting a Vulnerability
+
+We appreciate the community's interest in identifying potential vulnerabilities. However, effective immediately, we will **not** accept low-effort vulnerability reports. To ensure that submissions are constructive and actionable, please adhere to the following guidelines:
+
+Reports not submitted through our designated GitHub repository will be disregarded, and we will categorically reject invitations to collaborate on external platforms. Our aggressive stance on this matter underscores our commitment to a secure, transparent, and open community where all operations are visible and contributors are accountable.
+
+1. **No Vague Reports**: Submissions such as "I found a vulnerability" without any details will be treated as spam and will not be accepted.
+
+2. **In-Depth Understanding Required**: Reports must reflect a clear understanding of the codebase and provide specific details about the vulnerability, including the affected components and potential impacts.
+
+3. **Proof of Concept (PoC) is Mandatory**: Each submission must include a well-documented proof of concept (PoC) that demonstrates the vulnerability. If confidentiality is a concern, reporters are encouraged to create a private fork of the repository and share access with the maintainers. Reports lacking valid evidence will be disregarded.
+
+4. **Required Patch Submission**: Along with the PoC, reporters must provide a patch or actionable steps to remediate the identified vulnerability. This helps us evaluate and implement fixes rapidly.
+
+5. **Streamlined Merging Process**: When vulnerability reports meet the above criteria, we can consider them for immediate merging, similar to regular pull requests. Well-structured and thorough submissions will expedite the process of enhancing our security.
+
+**Non-compliant submissions will be closed, and repeat violators may be banned.** Our goal is to foster a constructive reporting environment where quality submissions promote better security for all users.
+
+## Product Security
+
+We regularly audit our internal processes and system architecture for vulnerabilities using a combination of automated and manual testing techniques. We are also planning to implement SAST and SCA scans in our project soon.
+
+For immediate concerns or detailed reports that meet our guidelines, please create an issue in our [issue tracker](/open-webui/open-webui/issues) or contact us on [Discord](https://discord.gg/5rJgQTnV4s).
+
+---
+
+_Last updated on **2024-08-19**._
diff --git a/docs/apache.md b/docs/apache.md
new file mode 100644
index 0000000000000000000000000000000000000000..ebbcc17f45777368366d1c99a54b48dd69525558
--- /dev/null
+++ b/docs/apache.md
@@ -0,0 +1,199 @@
+# Hosting UI and Models separately
+
+Sometimes, its beneficial to host Ollama, separate from the UI, but retain the RAG and RBAC support features shared across users:
+
+# Open WebUI Configuration
+
+## UI Configuration
+
+For the UI configuration, you can set up the Apache VirtualHost as follows:
+
+```
+# Assuming you have a website hosting this UI at "server.com"
+<VirtualHost 192.168.1.100:80>
+    ServerName server.com
+    DocumentRoot /home/server/public_html
+
+    ProxyPass / http://server.com:3000/ nocanon
+    ProxyPassReverse / http://server.com:3000/
+
+</VirtualHost>
+```
+
+Enable the site first before you can request SSL:
+
+`a2ensite server.com.conf` # this will enable the site. a2ensite is short for "Apache 2 Enable Site"
+
+```
+# For SSL
+<VirtualHost 192.168.1.100:443>
+    ServerName server.com
+    DocumentRoot /home/server/public_html
+
+    ProxyPass / http://server.com:3000/ nocanon
+    ProxyPassReverse / http://server.com:3000/
+
+    SSLEngine on
+    SSLCertificateFile /etc/ssl/virtualmin/170514456861234/ssl.cert
+    SSLCertificateKeyFile /etc/ssl/virtualmin/170514456861234/ssl.key
+    SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
+
+    SSLProxyEngine on
+    SSLCACertificateFile /etc/ssl/virtualmin/170514456865864/ssl.ca
+</VirtualHost>
+
+```
+
+I'm using virtualmin here for my SSL clusters, but you can also use certbot directly or your preferred SSL method. To use SSL:
+
+### Prerequisites.
+
+Run the following commands:
+
+`snap install certbot --classic`
+`snap apt install python3-certbot-apache` (this will install the apache plugin).
+
+Navigate to the apache sites-available directory:
+
+`cd /etc/apache2/sites-available/`
+
+Create server.com.conf if it is not yet already created, containing the above `<virtualhost>` configuration (it should match your case. Modify as necessary). Use the one without the SSL:
+
+Once it's created, run `certbot --apache -d server.com`, this will request and add/create an SSL keys for you as well as create the server.com.le-ssl.conf
+
+# Configuring Ollama Server
+
+On your latest installation of Ollama, make sure that you have setup your api server from the official Ollama reference:
+
+[Ollama FAQ](https://github.com/jmorganca/ollama/blob/main/docs/faq.md)
+
+### TL;DR
+
+The guide doesn't seem to match the current updated service file on linux. So, we will address it here:
+
+Unless when you're compiling Ollama from source, installing with the standard install `curl https://ollama.com/install.sh | sh` creates a file called `ollama.service` in /etc/systemd/system. You can use nano to edit the file:
+
+```
+sudo nano /etc/systemd/system/ollama.service
+```
+
+Add the following lines:
+
+```
+Environment="OLLAMA_HOST=0.0.0.0:11434" # this line is mandatory. You can also specify
+```
+
+For instance:
+
+```
+[Unit]
+Description=Ollama Service
+After=network-online.target
+
+[Service]
+ExecStart=/usr/local/bin/ollama serve
+Environment="OLLAMA_HOST=0.0.0.0:11434" # this line is mandatory. You can also specify 192.168.254.109:DIFFERENT_PORT, format
+Environment="OLLAMA_ORIGINS=http://192.168.254.106:11434,https://models.server.city" # this line is optional
+User=ollama
+Group=ollama
+Restart=always
+RestartSec=3
+Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/s>
+
+[Install]
+WantedBy=default.target
+```
+
+Save the file by pressing CTRL+S, then press CTRL+X
+
+When your computer restarts, the Ollama server will now be listening on the IP:PORT you specified, in this case 0.0.0.0:11434, or 192.168.254.106:11434 (whatever your local IP address is). Make sure that your router is correctly configured to serve pages from that local IP by forwarding 11434 to your local IP server.
+
+# Ollama Model Configuration
+
+## For the Ollama model configuration, use the following Apache VirtualHost setup:
+
+Navigate to the apache sites-available directory:
+
+`cd /etc/apache2/sites-available/`
+
+`nano models.server.city.conf` # match this with your ollama server domain
+
+Add the following virtualhost containing this example (modify as needed):
+
+```
+
+# Assuming you have a website hosting this UI at "models.server.city"
+<IfModule mod_ssl.c>
+    <VirtualHost 192.168.254.109:443>
+        DocumentRoot "/var/www/html/"
+        ServerName models.server.city
+        <Directory "/var/www/html/">
+            Options None
+            Require all granted
+        </Directory>
+
+        ProxyRequests Off
+        ProxyPreserveHost On
+        ProxyAddHeaders On
+        SSLProxyEngine on
+
+        ProxyPass / http://server.city:1000/ nocanon # or port 11434
+        ProxyPassReverse / http://server.city:1000/ # or port 11434
+
+        SSLCertificateFile /etc/letsencrypt/live/models.server.city/fullchain.pem
+        SSLCertificateKeyFile /etc/letsencrypt/live/models.server.city/privkey.pem
+        Include /etc/letsencrypt/options-ssl-apache.conf
+    </VirtualHost>
+</IfModule>
+```
+
+You may need to enable the site first (if you haven't done so yet) before you can request SSL:
+
+`a2ensite models.server.city.conf`
+
+#### For the SSL part of Ollama server
+
+Run the following commands:
+
+Navigate to the apache sites-available directory:
+
+`cd /etc/apache2/sites-available/`
+`certbot --apache -d server.com`
+
+```
+<VirtualHost 192.168.254.109:80>
+    DocumentRoot "/var/www/html/"
+    ServerName models.server.city
+    <Directory "/var/www/html/">
+        Options None
+        Require all granted
+    </Directory>
+
+    ProxyRequests Off
+    ProxyPreserveHost On
+    ProxyAddHeaders On
+    SSLProxyEngine on
+
+    ProxyPass / http://server.city:1000/ nocanon # or port 11434
+    ProxyPassReverse / http://server.city:1000/ # or port 11434
+
+    RewriteEngine on
+    RewriteCond %{SERVER_NAME} =models.server.city
+    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
+</VirtualHost>
+
+```
+
+Don't forget to restart/reload Apache with `systemctl reload apache2`
+
+Open your site at https://server.com!
+
+**Congratulations**, your _**Open-AI-like Chat-GPT style UI**_ is now serving AI with RAG, RBAC and multimodal features! Download Ollama models if you haven't yet done so!
+
+If you encounter any misconfiguration or errors, please file an issue or engage with our discussion. There are a lot of friendly developers here to assist you.
+
+Let's make this UI much more user friendly for everyone!
+
+Thanks for making open-webui your UI Choice for AI!
+
+This doc is made by **Bob Reyes**, your **Open-WebUI** fan from the Philippines.
diff --git a/hatch_build.py b/hatch_build.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ddaf0749bd6783b785fe63d6ce4b7f3e8a5b5d9
--- /dev/null
+++ b/hatch_build.py
@@ -0,0 +1,23 @@
+# noqa: INP001
+import os
+import shutil
+import subprocess
+from sys import stderr
+
+from hatchling.builders.hooks.plugin.interface import BuildHookInterface
+
+
+class CustomBuildHook(BuildHookInterface):
+    def initialize(self, version, build_data):
+        super().initialize(version, build_data)
+        stderr.write(">>> Building Open Webui frontend\n")
+        npm = shutil.which("npm")
+        if npm is None:
+            raise RuntimeError(
+                "NodeJS `npm` is required for building Open Webui but it was not found"
+            )
+        stderr.write("### npm install\n")
+        subprocess.run([npm, "install"], check=True)  # noqa: S603
+        stderr.write("\n### npm run build\n")
+        os.environ["APP_BUILD_HASH"] = version
+        subprocess.run([npm, "run", "build"], check=True)  # noqa: S603
diff --git a/i18next-parser.config.ts b/i18next-parser.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..37ce57ee1474e49787145f82cd6a64ad65773c4d
--- /dev/null
+++ b/i18next-parser.config.ts
@@ -0,0 +1,38 @@
+// i18next-parser.config.ts
+import { getLanguages } from './src/lib/i18n/index.ts';
+
+const getLangCodes = async () => {
+	const languages = await getLanguages();
+	return languages.map((l) => l.code);
+};
+
+export default {
+	contextSeparator: '_',
+	createOldCatalogs: false,
+	defaultNamespace: 'translation',
+	defaultValue: '',
+	indentation: 2,
+	keepRemoved: false,
+	keySeparator: false,
+	lexers: {
+		svelte: ['JavascriptLexer'],
+		js: ['JavascriptLexer'],
+		ts: ['JavascriptLexer'],
+
+		default: ['JavascriptLexer']
+	},
+	lineEnding: 'auto',
+	locales: await getLangCodes(),
+	namespaceSeparator: false,
+	output: 'src/lib/i18n/locales/$LOCALE/$NAMESPACE.json',
+	pluralSeparator: '_',
+	input: 'src/**/*.{js,svelte}',
+	sort: true,
+	verbose: true,
+	failOnWarnings: false,
+	failOnUpdate: false,
+	customValueTemplate: null,
+	resetDefaultValueLocale: null,
+	i18nextOptions: null,
+	yamlOptions: null
+};
diff --git a/kubernetes/helm/README.md b/kubernetes/helm/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5737007d964de10301f85357e83a91a3bf635248
--- /dev/null
+++ b/kubernetes/helm/README.md
@@ -0,0 +1,4 @@
+# Helm Charts
+Open WebUI Helm Charts are now hosted in a separate repo, which can be found here: https://github.com/open-webui/helm-charts 
+
+The charts are released at https://helm.openwebui.com. 
\ No newline at end of file
diff --git a/kubernetes/manifest/base/kustomization.yaml b/kubernetes/manifest/base/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..61500f87c513a3e53c595422e1ed33169da4a129
--- /dev/null
+++ b/kubernetes/manifest/base/kustomization.yaml
@@ -0,0 +1,8 @@
+resources:
+  - open-webui.yaml
+  - ollama-service.yaml
+  - ollama-statefulset.yaml
+  - webui-deployment.yaml
+  - webui-service.yaml
+  - webui-ingress.yaml
+  - webui-pvc.yaml
diff --git a/kubernetes/manifest/base/ollama-service.yaml b/kubernetes/manifest/base/ollama-service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8bab65b59effa86ab8e80b6b3f8629ccdbf7ca39
--- /dev/null
+++ b/kubernetes/manifest/base/ollama-service.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: ollama-service
+  namespace: open-webui
+spec:
+  selector:
+    app: ollama
+  ports:
+  - protocol: TCP
+    port: 11434
+    targetPort: 11434
\ No newline at end of file
diff --git a/kubernetes/manifest/base/ollama-statefulset.yaml b/kubernetes/manifest/base/ollama-statefulset.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..cd1144caf9d506c7cf5922395c9a075621ad0072
--- /dev/null
+++ b/kubernetes/manifest/base/ollama-statefulset.yaml
@@ -0,0 +1,41 @@
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: ollama
+  namespace: open-webui
+spec:
+  serviceName: "ollama"
+  replicas: 1
+  selector:
+    matchLabels:
+      app: ollama
+  template:
+    metadata:
+      labels:
+        app: ollama
+    spec:
+      containers:
+      - name: ollama
+        image: ollama/ollama:latest
+        ports:
+        - containerPort: 11434
+        resources:
+          requests:
+            cpu: "2000m"
+            memory: "2Gi"
+          limits:
+            cpu: "4000m"
+            memory: "4Gi"
+            nvidia.com/gpu: "0"
+        volumeMounts:
+        - name: ollama-volume
+          mountPath: /root/.ollama
+        tty: true
+  volumeClaimTemplates:
+  - metadata:
+      name: ollama-volume
+    spec:
+      accessModes: [ "ReadWriteOnce" ]
+      resources:
+        requests:
+          storage: 30Gi
\ No newline at end of file
diff --git a/kubernetes/manifest/base/open-webui.yaml b/kubernetes/manifest/base/open-webui.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..9c1a599f32690ddd8150be77b9bd8a7cd1b14245
--- /dev/null
+++ b/kubernetes/manifest/base/open-webui.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: open-webui
\ No newline at end of file
diff --git a/kubernetes/manifest/base/webui-deployment.yaml b/kubernetes/manifest/base/webui-deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..79a0a9a23c9652bf755273e7801e3002499a5aa6
--- /dev/null
+++ b/kubernetes/manifest/base/webui-deployment.yaml
@@ -0,0 +1,38 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: open-webui-deployment
+  namespace: open-webui
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: open-webui
+  template:
+    metadata:
+      labels:
+        app: open-webui
+    spec:
+      containers:
+      - name: open-webui
+        image: ghcr.io/open-webui/open-webui:main
+        ports:
+        - containerPort: 8080
+        resources:
+          requests:
+            cpu: "500m"
+            memory: "500Mi"
+          limits:
+            cpu: "1000m"
+            memory: "1Gi"
+        env:
+        - name: OLLAMA_BASE_URL
+          value: "http://ollama-service.open-webui.svc.cluster.local:11434"
+        tty: true
+        volumeMounts:
+        - name: webui-volume
+          mountPath: /app/backend/data
+      volumes:
+      - name: webui-volume
+        persistentVolumeClaim:
+          claimName: open-webui-pvc          
\ No newline at end of file
diff --git a/kubernetes/manifest/base/webui-ingress.yaml b/kubernetes/manifest/base/webui-ingress.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..dc0b53ccd456e70910683d28bb117a0272992e1c
--- /dev/null
+++ b/kubernetes/manifest/base/webui-ingress.yaml
@@ -0,0 +1,20 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: open-webui-ingress
+  namespace: open-webui
+  #annotations:
+    # Use appropriate annotations for your Ingress controller, e.g., for NGINX:
+    # nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+  rules:
+  - host: open-webui.minikube.local
+    http:
+      paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: open-webui-service
+            port:
+              number: 8080
diff --git a/kubernetes/manifest/base/webui-pvc.yaml b/kubernetes/manifest/base/webui-pvc.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..97fb761d422d510a2b725a08c3c52dca53c43ccd
--- /dev/null
+++ b/kubernetes/manifest/base/webui-pvc.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  labels:
+    app: open-webui
+  name: open-webui-pvc
+  namespace: open-webui
+spec:
+  accessModes: ["ReadWriteOnce"]
+  resources:
+    requests:
+      storage: 2Gi
\ No newline at end of file
diff --git a/kubernetes/manifest/base/webui-service.yaml b/kubernetes/manifest/base/webui-service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d73845f00a8ec45bc39f629b819542e7d2ced84e
--- /dev/null
+++ b/kubernetes/manifest/base/webui-service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: open-webui-service
+  namespace: open-webui
+spec:
+  type: NodePort  # Use LoadBalancer if you're on a cloud that supports it
+  selector:
+    app: open-webui
+  ports:
+    - protocol: TCP
+      port: 8080
+      targetPort: 8080
+      # If using NodePort, you can optionally specify the nodePort:
+      # nodePort: 30000
\ No newline at end of file
diff --git a/kubernetes/manifest/gpu/kustomization.yaml b/kubernetes/manifest/gpu/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c0d39fbfaab6be80a5542e238bec20ce98da096d
--- /dev/null
+++ b/kubernetes/manifest/gpu/kustomization.yaml
@@ -0,0 +1,8 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+resources:
+  - ../base
+
+patches:
+- path: ollama-statefulset-gpu.yaml
diff --git a/kubernetes/manifest/gpu/ollama-statefulset-gpu.yaml b/kubernetes/manifest/gpu/ollama-statefulset-gpu.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3e42443656d06c6df25075d094e5c8efb180fda1
--- /dev/null
+++ b/kubernetes/manifest/gpu/ollama-statefulset-gpu.yaml
@@ -0,0 +1,17 @@
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: ollama
+  namespace: open-webui
+spec:
+  selector:
+    matchLabels:
+      app: ollama
+  serviceName: "ollama"
+  template:
+    spec:
+      containers:
+      - name: ollama
+        resources:
+          limits:
+            nvidia.com/gpu: "1"
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..6cf21ae1a3a2870914a821e7840b48b7fbe75159
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,11629 @@
+{
+	"name": "open-webui",
+	"version": "0.3.35",
+	"lockfileVersion": 3,
+	"requires": true,
+	"packages": {
+		"": {
+			"name": "open-webui",
+			"version": "0.3.35",
+			"dependencies": {
+				"@codemirror/lang-javascript": "^6.2.2",
+				"@codemirror/lang-python": "^6.1.6",
+				"@codemirror/language-data": "^6.5.1",
+				"@codemirror/theme-one-dark": "^6.1.2",
+				"@huggingface/transformers": "^3.0.0",
+				"@pyscript/core": "^0.4.32",
+				"@sveltejs/adapter-node": "^2.0.0",
+				"@xyflow/svelte": "^0.1.19",
+				"async": "^3.2.5",
+				"bits-ui": "^0.19.7",
+				"codemirror": "^6.0.1",
+				"crc-32": "^1.2.2",
+				"dayjs": "^1.11.10",
+				"dompurify": "^3.1.6",
+				"eventsource-parser": "^1.1.2",
+				"file-saver": "^2.0.5",
+				"fuse.js": "^7.0.0",
+				"highlight.js": "^11.9.0",
+				"i18next": "^23.10.0",
+				"i18next-browser-languagedetector": "^7.2.0",
+				"i18next-resources-to-backend": "^1.2.0",
+				"idb": "^7.1.1",
+				"js-sha256": "^0.10.1",
+				"katex": "^0.16.9",
+				"marked": "^9.1.0",
+				"mermaid": "^10.9.3",
+				"paneforge": "^0.0.6",
+				"panzoom": "^9.4.3",
+				"prosemirror-commands": "^1.6.0",
+				"prosemirror-example-setup": "^1.2.3",
+				"prosemirror-history": "^1.4.1",
+				"prosemirror-keymap": "^1.2.2",
+				"prosemirror-markdown": "^1.13.1",
+				"prosemirror-model": "^1.23.0",
+				"prosemirror-schema-basic": "^1.2.3",
+				"prosemirror-schema-list": "^1.4.1",
+				"prosemirror-state": "^1.4.3",
+				"prosemirror-view": "^1.34.3",
+				"pyodide": "^0.26.1",
+				"socket.io-client": "^4.2.0",
+				"sortablejs": "^1.15.2",
+				"svelte-sonner": "^0.3.19",
+				"tippy.js": "^6.3.7",
+				"turndown": "^7.2.0",
+				"uuid": "^9.0.1"
+			},
+			"devDependencies": {
+				"@sveltejs/adapter-auto": "3.2.2",
+				"@sveltejs/adapter-static": "^3.0.2",
+				"@sveltejs/kit": "^2.5.20",
+				"@sveltejs/vite-plugin-svelte": "^3.1.1",
+				"@tailwindcss/typography": "^0.5.13",
+				"@typescript-eslint/eslint-plugin": "^6.17.0",
+				"@typescript-eslint/parser": "^6.17.0",
+				"autoprefixer": "^10.4.16",
+				"cypress": "^13.15.0",
+				"eslint": "^8.56.0",
+				"eslint-config-prettier": "^9.1.0",
+				"eslint-plugin-cypress": "^3.4.0",
+				"eslint-plugin-svelte": "^2.43.0",
+				"i18next-parser": "^9.0.1",
+				"postcss": "^8.4.31",
+				"prettier": "^3.3.3",
+				"prettier-plugin-svelte": "^3.2.6",
+				"svelte": "^4.2.18",
+				"svelte-check": "^3.8.5",
+				"svelte-confetti": "^1.3.2",
+				"tailwindcss": "^3.3.3",
+				"tslib": "^2.4.1",
+				"typescript": "^5.5.4",
+				"vite": "^5.3.5",
+				"vitest": "^1.6.0"
+			},
+			"engines": {
+				"node": ">=18.13.0 <=22.x.x",
+				"npm": ">=6.0.0"
+			}
+		},
+		"node_modules/@aashutoshrathi/word-wrap": {
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+			"integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/@alloc/quick-lru": {
+			"version": "5.2.0",
+			"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+			"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+			"dev": true,
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/@ampproject/remapping": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+			"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+			"dependencies": {
+				"@jridgewell/gen-mapping": "^0.3.5",
+				"@jridgewell/trace-mapping": "^0.3.24"
+			},
+			"engines": {
+				"node": ">=6.0.0"
+			}
+		},
+		"node_modules/@babel/runtime": {
+			"version": "7.24.1",
+			"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz",
+			"integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==",
+			"dependencies": {
+				"regenerator-runtime": "^0.14.0"
+			},
+			"engines": {
+				"node": ">=6.9.0"
+			}
+		},
+		"node_modules/@braintree/sanitize-url": {
+			"version": "6.0.4",
+			"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz",
+			"integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="
+		},
+		"node_modules/@codemirror/autocomplete": {
+			"version": "6.16.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.2.tgz",
+			"integrity": "sha512-MjfDrHy0gHKlPWsvSsikhO1+BOh+eBHNgfH1OXs1+DAf30IonQldgMM3kxLDTG9ktE7kDLaA1j/l7KMPA4KNfw==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.17.0",
+				"@lezer/common": "^1.0.0"
+			},
+			"peerDependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"@lezer/common": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/commands": {
+			"version": "6.6.0",
+			"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.0.tgz",
+			"integrity": "sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.4.0",
+				"@codemirror/view": "^6.27.0",
+				"@lezer/common": "^1.1.0"
+			}
+		},
+		"node_modules/@codemirror/lang-angular": {
+			"version": "0.1.3",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.3.tgz",
+			"integrity": "sha512-xgeWGJQQl1LyStvndWtruUvb4SnBZDAu/gvFH/ZU+c0W25tQR8e5hq7WTwiIY2dNxnf+49mRiGI/9yxIwB6f5w==",
+			"dependencies": {
+				"@codemirror/lang-html": "^6.0.0",
+				"@codemirror/lang-javascript": "^6.1.2",
+				"@codemirror/language": "^6.0.0",
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.3.3"
+			}
+		},
+		"node_modules/@codemirror/lang-cpp": {
+			"version": "6.0.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz",
+			"integrity": "sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@lezer/cpp": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-css": {
+			"version": "6.3.0",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.0.tgz",
+			"integrity": "sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@lezer/common": "^1.0.2",
+				"@lezer/css": "^1.1.7"
+			}
+		},
+		"node_modules/@codemirror/lang-go": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz",
+			"integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/language": "^6.6.0",
+				"@codemirror/state": "^6.0.0",
+				"@lezer/common": "^1.0.0",
+				"@lezer/go": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-html": {
+			"version": "6.4.9",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
+			"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/lang-css": "^6.0.0",
+				"@codemirror/lang-javascript": "^6.0.0",
+				"@codemirror/language": "^6.4.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.17.0",
+				"@lezer/common": "^1.0.0",
+				"@lezer/css": "^1.1.0",
+				"@lezer/html": "^1.3.0"
+			}
+		},
+		"node_modules/@codemirror/lang-java": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.1.tgz",
+			"integrity": "sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@lezer/java": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-javascript": {
+			"version": "6.2.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
+			"integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/language": "^6.6.0",
+				"@codemirror/lint": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.17.0",
+				"@lezer/common": "^1.0.0",
+				"@lezer/javascript": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-json": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
+			"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@lezer/json": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-less": {
+			"version": "6.0.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz",
+			"integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==",
+			"dependencies": {
+				"@codemirror/lang-css": "^6.2.0",
+				"@codemirror/language": "^6.0.0",
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-liquid": {
+			"version": "6.2.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.2.1.tgz",
+			"integrity": "sha512-J1Mratcm6JLNEiX+U2OlCDTysGuwbHD76XwuL5o5bo9soJtSbz2g6RU3vGHFyS5DC8rgVmFSzi7i6oBftm7tnA==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/lang-html": "^6.0.0",
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"@lezer/common": "^1.0.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.3.1"
+			}
+		},
+		"node_modules/@codemirror/lang-markdown": {
+			"version": "6.3.0",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.3.0.tgz",
+			"integrity": "sha512-lYrI8SdL/vhd0w0aHIEvIRLRecLF7MiiRfzXFZY94dFwHqC9HtgxgagJ8fyYNBldijGatf9wkms60d8SrAj6Nw==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.7.1",
+				"@codemirror/lang-html": "^6.0.0",
+				"@codemirror/language": "^6.3.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"@lezer/common": "^1.2.1",
+				"@lezer/markdown": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-php": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.1.tgz",
+			"integrity": "sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==",
+			"dependencies": {
+				"@codemirror/lang-html": "^6.0.0",
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@lezer/common": "^1.0.0",
+				"@lezer/php": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-python": {
+			"version": "6.1.6",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.1.6.tgz",
+			"integrity": "sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.3.2",
+				"@codemirror/language": "^6.8.0",
+				"@codemirror/state": "^6.0.0",
+				"@lezer/common": "^1.2.1",
+				"@lezer/python": "^1.1.4"
+			}
+		},
+		"node_modules/@codemirror/lang-rust": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.1.tgz",
+			"integrity": "sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@lezer/rust": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-sass": {
+			"version": "6.0.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz",
+			"integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==",
+			"dependencies": {
+				"@codemirror/lang-css": "^6.2.0",
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@lezer/common": "^1.0.2",
+				"@lezer/sass": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-sql": {
+			"version": "6.8.0",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.8.0.tgz",
+			"integrity": "sha512-aGLmY4OwGqN3TdSx3h6QeA1NrvaYtF7kkoWR/+W7/JzB0gQtJ+VJxewlnE3+VImhA4WVlhmkJr109PefOOhjLg==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-vue": {
+			"version": "0.1.3",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz",
+			"integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==",
+			"dependencies": {
+				"@codemirror/lang-html": "^6.0.0",
+				"@codemirror/lang-javascript": "^6.1.2",
+				"@codemirror/language": "^6.0.0",
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.3.1"
+			}
+		},
+		"node_modules/@codemirror/lang-wast": {
+			"version": "6.0.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz",
+			"integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-xml": {
+			"version": "6.1.0",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
+			"integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/language": "^6.4.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"@lezer/common": "^1.0.0",
+				"@lezer/xml": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-yaml": {
+			"version": "6.1.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.1.tgz",
+			"integrity": "sha512-HV2NzbK9bbVnjWxwObuZh5FuPCowx51mEfoFT9y3y+M37fA3+pbxx4I7uePuygFzDsAmCTwQSc/kXh/flab4uw==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.2.0",
+				"@lezer/yaml": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/language": {
+			"version": "6.10.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz",
+			"integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==",
+			"dependencies": {
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.23.0",
+				"@lezer/common": "^1.1.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0",
+				"style-mod": "^4.0.0"
+			}
+		},
+		"node_modules/@codemirror/language-data": {
+			"version": "6.5.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.1.tgz",
+			"integrity": "sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==",
+			"dependencies": {
+				"@codemirror/lang-angular": "^0.1.0",
+				"@codemirror/lang-cpp": "^6.0.0",
+				"@codemirror/lang-css": "^6.0.0",
+				"@codemirror/lang-go": "^6.0.0",
+				"@codemirror/lang-html": "^6.0.0",
+				"@codemirror/lang-java": "^6.0.0",
+				"@codemirror/lang-javascript": "^6.0.0",
+				"@codemirror/lang-json": "^6.0.0",
+				"@codemirror/lang-less": "^6.0.0",
+				"@codemirror/lang-liquid": "^6.0.0",
+				"@codemirror/lang-markdown": "^6.0.0",
+				"@codemirror/lang-php": "^6.0.0",
+				"@codemirror/lang-python": "^6.0.0",
+				"@codemirror/lang-rust": "^6.0.0",
+				"@codemirror/lang-sass": "^6.0.0",
+				"@codemirror/lang-sql": "^6.0.0",
+				"@codemirror/lang-vue": "^0.1.1",
+				"@codemirror/lang-wast": "^6.0.0",
+				"@codemirror/lang-xml": "^6.0.0",
+				"@codemirror/lang-yaml": "^6.0.0",
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/legacy-modes": "^6.4.0"
+			}
+		},
+		"node_modules/@codemirror/legacy-modes": {
+			"version": "6.4.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.4.1.tgz",
+			"integrity": "sha512-vdg3XY7OAs5uLDx2Iw+cGfnwtd7kM+Et/eMsqAGTfT/JKiVBQZXosTzjEbWAi/FrY6DcQIz8mQjBozFHZEUWQA==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0"
+			}
+		},
+		"node_modules/@codemirror/lint": {
+			"version": "6.8.0",
+			"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.0.tgz",
+			"integrity": "sha512-lsFofvaw0lnPRJlQylNsC4IRt/1lI4OD/yYslrSGVndOJfStc58v+8p9dgGiD90ktOfL7OhBWns1ZETYgz0EJA==",
+			"dependencies": {
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"crelt": "^1.0.5"
+			}
+		},
+		"node_modules/@codemirror/search": {
+			"version": "6.5.6",
+			"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
+			"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
+			"dependencies": {
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"crelt": "^1.0.5"
+			}
+		},
+		"node_modules/@codemirror/state": {
+			"version": "6.4.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
+			"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
+		},
+		"node_modules/@codemirror/theme-one-dark": {
+			"version": "6.1.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
+			"integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"@lezer/highlight": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/view": {
+			"version": "6.28.0",
+			"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.0.tgz",
+			"integrity": "sha512-fo7CelaUDKWIyemw4b+J57cWuRkOu4SWCCPfNDkPvfWkGjM9D5racHQXr4EQeYCD6zEBIBxGCeaKkQo+ysl0gA==",
+			"dependencies": {
+				"@codemirror/state": "^6.4.0",
+				"style-mod": "^4.1.0",
+				"w3c-keyname": "^2.2.4"
+			}
+		},
+		"node_modules/@colors/colors": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
+			"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
+			"dev": true,
+			"optional": true,
+			"engines": {
+				"node": ">=0.1.90"
+			}
+		},
+		"node_modules/@cypress/request": {
+			"version": "3.0.5",
+			"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz",
+			"integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==",
+			"dev": true,
+			"dependencies": {
+				"aws-sign2": "~0.7.0",
+				"aws4": "^1.8.0",
+				"caseless": "~0.12.0",
+				"combined-stream": "~1.0.6",
+				"extend": "~3.0.2",
+				"forever-agent": "~0.6.1",
+				"form-data": "~4.0.0",
+				"http-signature": "~1.4.0",
+				"is-typedarray": "~1.0.0",
+				"isstream": "~0.1.2",
+				"json-stringify-safe": "~5.0.1",
+				"mime-types": "~2.1.19",
+				"performance-now": "^2.1.0",
+				"qs": "6.13.0",
+				"safe-buffer": "^5.1.2",
+				"tough-cookie": "^4.1.3",
+				"tunnel-agent": "^0.6.0",
+				"uuid": "^8.3.2"
+			},
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/@cypress/request/node_modules/uuid": {
+			"version": "8.3.2",
+			"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+			"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+			"dev": true,
+			"bin": {
+				"uuid": "dist/bin/uuid"
+			}
+		},
+		"node_modules/@cypress/xvfb": {
+			"version": "1.2.4",
+			"resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz",
+			"integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==",
+			"dev": true,
+			"dependencies": {
+				"debug": "^3.1.0",
+				"lodash.once": "^4.1.1"
+			}
+		},
+		"node_modules/@cypress/xvfb/node_modules/debug": {
+			"version": "3.2.7",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+			"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+			"dev": true,
+			"dependencies": {
+				"ms": "^2.1.1"
+			}
+		},
+		"node_modules/@emnapi/runtime": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
+			"integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
+			"optional": true,
+			"dependencies": {
+				"tslib": "^2.4.0"
+			}
+		},
+		"node_modules/@esbuild/aix-ppc64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
+			"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
+			"cpu": [
+				"ppc64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"aix"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/android-arm": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
+			"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
+			"cpu": [
+				"arm"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"android"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/android-arm64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
+			"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
+			"cpu": [
+				"arm64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"android"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/android-x64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
+			"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
+			"cpu": [
+				"x64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"android"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/darwin-arm64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
+			"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
+			"cpu": [
+				"arm64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"darwin"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/darwin-x64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
+			"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
+			"cpu": [
+				"x64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"darwin"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/freebsd-arm64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
+			"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
+			"cpu": [
+				"arm64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"freebsd"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/freebsd-x64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
+			"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
+			"cpu": [
+				"x64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"freebsd"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/linux-arm": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
+			"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
+			"cpu": [
+				"arm"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/linux-arm64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
+			"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
+			"cpu": [
+				"arm64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/linux-ia32": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
+			"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
+			"cpu": [
+				"ia32"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/linux-loong64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
+			"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
+			"cpu": [
+				"loong64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/linux-mips64el": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
+			"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
+			"cpu": [
+				"mips64el"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/linux-ppc64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
+			"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
+			"cpu": [
+				"ppc64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/linux-riscv64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
+			"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
+			"cpu": [
+				"riscv64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/linux-s390x": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
+			"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
+			"cpu": [
+				"s390x"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/linux-x64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
+			"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
+			"cpu": [
+				"x64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/netbsd-x64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
+			"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
+			"cpu": [
+				"x64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"netbsd"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/openbsd-x64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
+			"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
+			"cpu": [
+				"x64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"openbsd"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/sunos-x64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
+			"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
+			"cpu": [
+				"x64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"sunos"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/win32-arm64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
+			"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
+			"cpu": [
+				"arm64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"win32"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/win32-ia32": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
+			"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
+			"cpu": [
+				"ia32"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"win32"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@esbuild/win32-x64": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
+			"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
+			"cpu": [
+				"x64"
+			],
+			"dev": true,
+			"optional": true,
+			"os": [
+				"win32"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@eslint-community/eslint-utils": {
+			"version": "4.4.0",
+			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+			"integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+			"dev": true,
+			"dependencies": {
+				"eslint-visitor-keys": "^3.3.0"
+			},
+			"engines": {
+				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+			},
+			"peerDependencies": {
+				"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+			}
+		},
+		"node_modules/@eslint-community/regexpp": {
+			"version": "4.10.0",
+			"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
+			"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
+			"dev": true,
+			"engines": {
+				"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+			}
+		},
+		"node_modules/@eslint/eslintrc": {
+			"version": "2.1.4",
+			"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+			"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+			"dev": true,
+			"dependencies": {
+				"ajv": "^6.12.4",
+				"debug": "^4.3.2",
+				"espree": "^9.6.0",
+				"globals": "^13.19.0",
+				"ignore": "^5.2.0",
+				"import-fresh": "^3.2.1",
+				"js-yaml": "^4.1.0",
+				"minimatch": "^3.1.2",
+				"strip-json-comments": "^3.1.1"
+			},
+			"engines": {
+				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
+		"node_modules/@eslint/eslintrc/node_modules/minimatch": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^1.1.7"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/@eslint/js": {
+			"version": "8.57.0",
+			"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+			"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+			"dev": true,
+			"engines": {
+				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+			}
+		},
+		"node_modules/@floating-ui/core": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
+			"integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==",
+			"dependencies": {
+				"@floating-ui/utils": "^0.2.1"
+			}
+		},
+		"node_modules/@floating-ui/dom": {
+			"version": "1.6.3",
+			"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz",
+			"integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==",
+			"dependencies": {
+				"@floating-ui/core": "^1.0.0",
+				"@floating-ui/utils": "^0.2.0"
+			}
+		},
+		"node_modules/@floating-ui/utils": {
+			"version": "0.2.1",
+			"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
+			"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
+		},
+		"node_modules/@gulpjs/to-absolute-glob": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz",
+			"integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==",
+			"dev": true,
+			"dependencies": {
+				"is-negated-glob": "^1.0.0"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
+		"node_modules/@huggingface/jinja": {
+			"version": "0.3.1",
+			"resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.3.1.tgz",
+			"integrity": "sha512-SbcBWUKDQ76lzlVYOloscUk0SJjuL1LcbZsfQv/Bxxc7dwJMYuS+DAQ+HhVw6ZkTFXArejaX5HQRuCuleYwYdA==",
+			"engines": {
+				"node": ">=18"
+			}
+		},
+		"node_modules/@huggingface/transformers": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-3.0.0.tgz",
+			"integrity": "sha512-OWIPnTijAw4DQ+IFHBOrej2SDdYyykYlTtpTLCEt5MZq/e9Cb65RS2YVhdGcgbaW/6JAL3i8ZA5UhDeWGm4iRQ==",
+			"dependencies": {
+				"@huggingface/jinja": "^0.3.0",
+				"onnxruntime-node": "1.19.2",
+				"onnxruntime-web": "1.20.0-dev.20241016-2b8fc5529b",
+				"sharp": "^0.33.5"
+			}
+		},
+		"node_modules/@humanwhocodes/config-array": {
+			"version": "0.11.14",
+			"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
+			"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+			"dev": true,
+			"dependencies": {
+				"@humanwhocodes/object-schema": "^2.0.2",
+				"debug": "^4.3.1",
+				"minimatch": "^3.0.5"
+			},
+			"engines": {
+				"node": ">=10.10.0"
+			}
+		},
+		"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
+		"node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^1.1.7"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/@humanwhocodes/module-importer": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+			"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+			"dev": true,
+			"engines": {
+				"node": ">=12.22"
+			},
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/nzakas"
+			}
+		},
+		"node_modules/@humanwhocodes/object-schema": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
+			"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
+			"dev": true
+		},
+		"node_modules/@img/sharp-darwin-arm64": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
+			"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"darwin"
+			],
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			},
+			"optionalDependencies": {
+				"@img/sharp-libvips-darwin-arm64": "1.0.4"
+			}
+		},
+		"node_modules/@img/sharp-darwin-x64": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+			"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"darwin"
+			],
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			},
+			"optionalDependencies": {
+				"@img/sharp-libvips-darwin-x64": "1.0.4"
+			}
+		},
+		"node_modules/@img/sharp-libvips-darwin-arm64": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
+			"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"darwin"
+			],
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@img/sharp-libvips-darwin-x64": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+			"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"darwin"
+			],
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@img/sharp-libvips-linux-arm": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+			"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+			"cpu": [
+				"arm"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@img/sharp-libvips-linux-arm64": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+			"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@img/sharp-libvips-linux-s390x": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+			"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+			"cpu": [
+				"s390x"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@img/sharp-libvips-linux-x64": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+			"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+			"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@img/sharp-libvips-linuxmusl-x64": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+			"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@img/sharp-linux-arm": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+			"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+			"cpu": [
+				"arm"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			},
+			"optionalDependencies": {
+				"@img/sharp-libvips-linux-arm": "1.0.5"
+			}
+		},
+		"node_modules/@img/sharp-linux-arm64": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+			"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			},
+			"optionalDependencies": {
+				"@img/sharp-libvips-linux-arm64": "1.0.4"
+			}
+		},
+		"node_modules/@img/sharp-linux-s390x": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+			"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+			"cpu": [
+				"s390x"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			},
+			"optionalDependencies": {
+				"@img/sharp-libvips-linux-s390x": "1.0.4"
+			}
+		},
+		"node_modules/@img/sharp-linux-x64": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+			"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			},
+			"optionalDependencies": {
+				"@img/sharp-libvips-linux-x64": "1.0.4"
+			}
+		},
+		"node_modules/@img/sharp-linuxmusl-arm64": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+			"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			},
+			"optionalDependencies": {
+				"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+			}
+		},
+		"node_modules/@img/sharp-linuxmusl-x64": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+			"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			},
+			"optionalDependencies": {
+				"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+			}
+		},
+		"node_modules/@img/sharp-wasm32": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+			"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
+			"cpu": [
+				"wasm32"
+			],
+			"optional": true,
+			"dependencies": {
+				"@emnapi/runtime": "^1.2.0"
+			},
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@img/sharp-win32-ia32": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+			"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+			"cpu": [
+				"ia32"
+			],
+			"optional": true,
+			"os": [
+				"win32"
+			],
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@img/sharp-win32-x64": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+			"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"win32"
+			],
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			}
+		},
+		"node_modules/@internationalized/date": {
+			"version": "3.5.2",
+			"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.2.tgz",
+			"integrity": "sha512-vo1yOMUt2hzp63IutEaTUxROdvQg1qlMRsbCvbay2AK2Gai7wIgCyK5weEX3nHkiLgo4qCXHijFNC/ILhlRpOQ==",
+			"dependencies": {
+				"@swc/helpers": "^0.5.0"
+			}
+		},
+		"node_modules/@isaacs/cliui": {
+			"version": "8.0.2",
+			"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+			"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+			"dependencies": {
+				"string-width": "^5.1.2",
+				"string-width-cjs": "npm:string-width@^4.2.0",
+				"strip-ansi": "^7.0.1",
+				"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+				"wrap-ansi": "^8.1.0",
+				"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+			"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/ansi-regex?sponsor=1"
+			}
+		},
+		"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+			"version": "7.1.0",
+			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+			"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+			"dependencies": {
+				"ansi-regex": "^6.0.1"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/strip-ansi?sponsor=1"
+			}
+		},
+		"node_modules/@isaacs/fs-minipass": {
+			"version": "4.0.1",
+			"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+			"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+			"dependencies": {
+				"minipass": "^7.0.4"
+			},
+			"engines": {
+				"node": ">=18.0.0"
+			}
+		},
+		"node_modules/@jest/schemas": {
+			"version": "29.6.3",
+			"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+			"integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+			"dev": true,
+			"dependencies": {
+				"@sinclair/typebox": "^0.27.8"
+			},
+			"engines": {
+				"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+			}
+		},
+		"node_modules/@jridgewell/gen-mapping": {
+			"version": "0.3.5",
+			"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+			"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+			"dependencies": {
+				"@jridgewell/set-array": "^1.2.1",
+				"@jridgewell/sourcemap-codec": "^1.4.10",
+				"@jridgewell/trace-mapping": "^0.3.24"
+			},
+			"engines": {
+				"node": ">=6.0.0"
+			}
+		},
+		"node_modules/@jridgewell/resolve-uri": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+			"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+			"engines": {
+				"node": ">=6.0.0"
+			}
+		},
+		"node_modules/@jridgewell/set-array": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+			"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+			"engines": {
+				"node": ">=6.0.0"
+			}
+		},
+		"node_modules/@jridgewell/sourcemap-codec": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+			"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+		},
+		"node_modules/@jridgewell/trace-mapping": {
+			"version": "0.3.25",
+			"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+			"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+			"dependencies": {
+				"@jridgewell/resolve-uri": "^3.1.0",
+				"@jridgewell/sourcemap-codec": "^1.4.14"
+			}
+		},
+		"node_modules/@lezer/common": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
+			"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ=="
+		},
+		"node_modules/@lezer/cpp": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.2.tgz",
+			"integrity": "sha512-macwKtyeUO0EW86r3xWQCzOV9/CF8imJLpJlPv3sDY57cPGeUZ8gXWOWNlJr52TVByMV3PayFQCA5SHEERDmVQ==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/css": {
+			"version": "1.1.9",
+			"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.9.tgz",
+			"integrity": "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/go": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.0.tgz",
+			"integrity": "sha512-co9JfT3QqX1YkrMmourYw2Z8meGC50Ko4d54QEcQbEYpvdUvN4yb0NBZdn/9ertgvjsySxHsKzH3lbm3vqJ4Jw==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/highlight": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
+			"integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
+			"dependencies": {
+				"@lezer/common": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/html": {
+			"version": "1.3.10",
+			"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
+			"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/java": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.2.tgz",
+			"integrity": "sha512-3j8X70JvYf0BZt8iSRLXLkt0Ry1hVUgH6wT32yBxH/Xi55nW2VMhc1Az4SKwu4YGSmxCm1fsqDDcHTuFjC8pmg==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/javascript": {
+			"version": "1.4.16",
+			"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.16.tgz",
+			"integrity": "sha512-84UXR3N7s11MPQHWgMnjb9571fr19MmXnr5zTv2XX0gHXXUvW3uPJ8GCjKrfTXmSdfktjRK0ayKklw+A13rk4g==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.1.3",
+				"@lezer/lr": "^1.3.0"
+			}
+		},
+		"node_modules/@lezer/json": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz",
+			"integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/lr": {
+			"version": "1.4.1",
+			"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.1.tgz",
+			"integrity": "sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==",
+			"dependencies": {
+				"@lezer/common": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/markdown": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.3.1.tgz",
+			"integrity": "sha512-DGlzU/i8DC8k0uz1F+jeePrkATl0jWakauTzftMQOcbaMkHbNSRki/4E2tOzJWsVpoKYhe7iTJ03aepdwVUXUA==",
+			"dependencies": {
+				"@lezer/common": "^1.0.0",
+				"@lezer/highlight": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/php": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.2.tgz",
+			"integrity": "sha512-GN7BnqtGRpFyeoKSEqxvGvhJQiI4zkgmYnDk/JIyc7H7Ifc1tkPnUn/R2R8meH3h/aBf5rzjvU8ZQoyiNDtDrA==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.1.0"
+			}
+		},
+		"node_modules/@lezer/python": {
+			"version": "1.1.14",
+			"resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.14.tgz",
+			"integrity": "sha512-ykDOb2Ti24n76PJsSa4ZoDF0zH12BSw1LGfQXCYJhJyOGiFTfGaX0Du66Ze72R+u/P35U+O6I9m8TFXov1JzsA==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/rust": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz",
+			"integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/sass": {
+			"version": "1.0.7",
+			"resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.0.7.tgz",
+			"integrity": "sha512-8HLlOkuX/SMHOggI2DAsXUw38TuURe+3eQ5hiuk9QmYOUyC55B1dYEIMkav5A4IELVaW4e1T4P9WRiI5ka4mdw==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/xml": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.5.tgz",
+			"integrity": "sha512-VFouqOzmUWfIg+tfmpcdV33ewtK+NSwd4ngSe1aG7HFb4BN0ExyY1b8msp+ndFrnlG4V4iC8yXacjFtrwERnaw==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/yaml": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz",
+			"integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.4.0"
+			}
+		},
+		"node_modules/@melt-ui/svelte": {
+			"version": "0.76.0",
+			"resolved": "https://registry.npmjs.org/@melt-ui/svelte/-/svelte-0.76.0.tgz",
+			"integrity": "sha512-X1ktxKujjLjOBt8LBvfckHGDMrkHWceRt1jdsUTf0EH76ikNPP1ofSoiV0IhlduDoCBV+2YchJ8kXCDfDXfC9Q==",
+			"dependencies": {
+				"@floating-ui/core": "^1.3.1",
+				"@floating-ui/dom": "^1.4.5",
+				"@internationalized/date": "^3.5.0",
+				"dequal": "^2.0.3",
+				"focus-trap": "^7.5.2",
+				"nanoid": "^5.0.4"
+			},
+			"peerDependencies": {
+				"svelte": ">=3 <5"
+			}
+		},
+		"node_modules/@mixmark-io/domino": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
+			"integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="
+		},
+		"node_modules/@nodelib/fs.scandir": {
+			"version": "2.1.5",
+			"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+			"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+			"dev": true,
+			"dependencies": {
+				"@nodelib/fs.stat": "2.0.5",
+				"run-parallel": "^1.1.9"
+			},
+			"engines": {
+				"node": ">= 8"
+			}
+		},
+		"node_modules/@nodelib/fs.stat": {
+			"version": "2.0.5",
+			"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+			"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+			"dev": true,
+			"engines": {
+				"node": ">= 8"
+			}
+		},
+		"node_modules/@nodelib/fs.walk": {
+			"version": "1.2.8",
+			"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+			"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+			"dev": true,
+			"dependencies": {
+				"@nodelib/fs.scandir": "2.1.5",
+				"fastq": "^1.6.0"
+			},
+			"engines": {
+				"node": ">= 8"
+			}
+		},
+		"node_modules/@pkgjs/parseargs": {
+			"version": "0.11.0",
+			"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+			"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+			"optional": true,
+			"engines": {
+				"node": ">=14"
+			}
+		},
+		"node_modules/@polka/url": {
+			"version": "1.0.0-next.25",
+			"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
+			"integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ=="
+		},
+		"node_modules/@popperjs/core": {
+			"version": "2.11.8",
+			"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+			"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/popperjs"
+			}
+		},
+		"node_modules/@protobufjs/aspromise": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+			"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
+		},
+		"node_modules/@protobufjs/base64": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+			"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+		},
+		"node_modules/@protobufjs/codegen": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+			"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+		},
+		"node_modules/@protobufjs/eventemitter": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+			"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
+		},
+		"node_modules/@protobufjs/fetch": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+			"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+			"dependencies": {
+				"@protobufjs/aspromise": "^1.1.1",
+				"@protobufjs/inquire": "^1.1.0"
+			}
+		},
+		"node_modules/@protobufjs/float": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+			"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
+		},
+		"node_modules/@protobufjs/inquire": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+			"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
+		},
+		"node_modules/@protobufjs/path": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+			"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
+		},
+		"node_modules/@protobufjs/pool": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+			"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
+		},
+		"node_modules/@protobufjs/utf8": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+			"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
+		},
+		"node_modules/@pyscript/core": {
+			"version": "0.4.32",
+			"resolved": "https://registry.npmjs.org/@pyscript/core/-/core-0.4.32.tgz",
+			"integrity": "sha512-WQATzPp1ggf871+PukCmTypzScXkEB1EWD/vg5GNxpM96N6rDPqQ13msuA5XvwU01ZVhL8HHSFDLk4IfaXNGWg==",
+			"dependencies": {
+				"@ungap/with-resolvers": "^0.1.0",
+				"basic-devtools": "^0.1.6",
+				"polyscript": "^0.12.8",
+				"sticky-module": "^0.1.1",
+				"to-json-callback": "^0.1.1",
+				"type-checked-collections": "^0.1.7"
+			}
+		},
+		"node_modules/@rollup/plugin-commonjs": {
+			"version": "25.0.7",
+			"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz",
+			"integrity": "sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==",
+			"dependencies": {
+				"@rollup/pluginutils": "^5.0.1",
+				"commondir": "^1.0.1",
+				"estree-walker": "^2.0.2",
+				"glob": "^8.0.3",
+				"is-reference": "1.2.1",
+				"magic-string": "^0.30.3"
+			},
+			"engines": {
+				"node": ">=14.0.0"
+			},
+			"peerDependencies": {
+				"rollup": "^2.68.0||^3.0.0||^4.0.0"
+			},
+			"peerDependenciesMeta": {
+				"rollup": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@rollup/plugin-json": {
+			"version": "6.1.0",
+			"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
+			"integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
+			"dependencies": {
+				"@rollup/pluginutils": "^5.1.0"
+			},
+			"engines": {
+				"node": ">=14.0.0"
+			},
+			"peerDependencies": {
+				"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+			},
+			"peerDependenciesMeta": {
+				"rollup": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@rollup/plugin-node-resolve": {
+			"version": "15.2.3",
+			"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
+			"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
+			"dependencies": {
+				"@rollup/pluginutils": "^5.0.1",
+				"@types/resolve": "1.20.2",
+				"deepmerge": "^4.2.2",
+				"is-builtin-module": "^3.2.1",
+				"is-module": "^1.0.0",
+				"resolve": "^1.22.1"
+			},
+			"engines": {
+				"node": ">=14.0.0"
+			},
+			"peerDependencies": {
+				"rollup": "^2.78.0||^3.0.0||^4.0.0"
+			},
+			"peerDependenciesMeta": {
+				"rollup": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@rollup/pluginutils": {
+			"version": "5.1.0",
+			"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
+			"integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
+			"dependencies": {
+				"@types/estree": "^1.0.0",
+				"estree-walker": "^2.0.2",
+				"picomatch": "^2.3.1"
+			},
+			"engines": {
+				"node": ">=14.0.0"
+			},
+			"peerDependencies": {
+				"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+			},
+			"peerDependenciesMeta": {
+				"rollup": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@rollup/rollup-android-arm-eabi": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
+			"integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
+			"cpu": [
+				"arm"
+			],
+			"optional": true,
+			"os": [
+				"android"
+			]
+		},
+		"node_modules/@rollup/rollup-android-arm64": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
+			"integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"android"
+			]
+		},
+		"node_modules/@rollup/rollup-darwin-arm64": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
+			"integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"darwin"
+			]
+		},
+		"node_modules/@rollup/rollup-darwin-x64": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
+			"integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"darwin"
+			]
+		},
+		"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
+			"integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
+			"cpu": [
+				"arm"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			]
+		},
+		"node_modules/@rollup/rollup-linux-arm-musleabihf": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
+			"integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
+			"cpu": [
+				"arm"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			]
+		},
+		"node_modules/@rollup/rollup-linux-arm64-gnu": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
+			"integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			]
+		},
+		"node_modules/@rollup/rollup-linux-arm64-musl": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
+			"integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			]
+		},
+		"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
+			"integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
+			"cpu": [
+				"ppc64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			]
+		},
+		"node_modules/@rollup/rollup-linux-riscv64-gnu": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
+			"integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
+			"cpu": [
+				"riscv64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			]
+		},
+		"node_modules/@rollup/rollup-linux-s390x-gnu": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
+			"integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
+			"cpu": [
+				"s390x"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			]
+		},
+		"node_modules/@rollup/rollup-linux-x64-gnu": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
+			"integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			]
+		},
+		"node_modules/@rollup/rollup-linux-x64-musl": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
+			"integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			]
+		},
+		"node_modules/@rollup/rollup-win32-arm64-msvc": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
+			"integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"win32"
+			]
+		},
+		"node_modules/@rollup/rollup-win32-ia32-msvc": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
+			"integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
+			"cpu": [
+				"ia32"
+			],
+			"optional": true,
+			"os": [
+				"win32"
+			]
+		},
+		"node_modules/@rollup/rollup-win32-x64-msvc": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
+			"integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"win32"
+			]
+		},
+		"node_modules/@sinclair/typebox": {
+			"version": "0.27.8",
+			"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+			"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+			"dev": true
+		},
+		"node_modules/@socket.io/component-emitter": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+			"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
+		},
+		"node_modules/@svelte-put/shortcut": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/@svelte-put/shortcut/-/shortcut-3.1.1.tgz",
+			"integrity": "sha512-2L5EYTZXiaKvbEelVkg5znxqvfZGZai3m97+cAiUBhLZwXnGtviTDpHxOoZBsqz41szlfRMcamW/8o0+fbW3ZQ==",
+			"peerDependencies": {
+				"svelte": "^3.55.0 || ^4.0.0 || ^5.0.0"
+			}
+		},
+		"node_modules/@sveltejs/adapter-auto": {
+			"version": "3.2.2",
+			"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.2.2.tgz",
+			"integrity": "sha512-Mso5xPCA8zgcKrv+QioVlqMZkyUQ5MjDJiEPuG/Z7cV/5tmwV7LmcVWk5tZ+H0NCOV1x12AsoSpt/CwFwuVXMA==",
+			"dev": true,
+			"dependencies": {
+				"import-meta-resolve": "^4.1.0"
+			},
+			"peerDependencies": {
+				"@sveltejs/kit": "^2.0.0"
+			}
+		},
+		"node_modules/@sveltejs/adapter-node": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-2.1.2.tgz",
+			"integrity": "sha512-ZfVY5buBclWHoBT+RbkMUViJGEIZ3IfT/0Hvhlgp+qC3LRZwp+wS1Zsw5dgkB2sFDZXctbLNXJtwlkjSp1mw0g==",
+			"dependencies": {
+				"@rollup/plugin-commonjs": "^25.0.7",
+				"@rollup/plugin-json": "^6.1.0",
+				"@rollup/plugin-node-resolve": "^15.2.3",
+				"rollup": "^4.8.0"
+			},
+			"peerDependencies": {
+				"@sveltejs/kit": "^2.0.0"
+			}
+		},
+		"node_modules/@sveltejs/adapter-static": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.2.tgz",
+			"integrity": "sha512-/EBFydZDwfwFfFEuF1vzUseBoRziwKP7AoHAwv+Ot3M084sE/HTVBHf9mCmXfdM9ijprY5YEugZjleflncX5fQ==",
+			"dev": true,
+			"peerDependencies": {
+				"@sveltejs/kit": "^2.0.0"
+			}
+		},
+		"node_modules/@sveltejs/kit": {
+			"version": "2.6.2",
+			"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.6.2.tgz",
+			"integrity": "sha512-ruogrSPXjckn5poUiZU8VYNCSPHq66SFR1AATvOikQxtP6LNI4niAZVX/AWZRe/EPDG3oY2DNJ9c5z7u0t2NAQ==",
+			"hasInstallScript": true,
+			"dependencies": {
+				"@types/cookie": "^0.6.0",
+				"cookie": "^0.7.0",
+				"devalue": "^5.1.0",
+				"esm-env": "^1.0.0",
+				"import-meta-resolve": "^4.1.0",
+				"kleur": "^4.1.5",
+				"magic-string": "^0.30.5",
+				"mrmime": "^2.0.0",
+				"sade": "^1.8.1",
+				"set-cookie-parser": "^2.6.0",
+				"sirv": "^2.0.4",
+				"tiny-glob": "^0.2.9"
+			},
+			"bin": {
+				"svelte-kit": "svelte-kit.js"
+			},
+			"engines": {
+				"node": ">=18.13"
+			},
+			"peerDependencies": {
+				"@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1",
+				"svelte": "^4.0.0 || ^5.0.0-next.0",
+				"vite": "^5.0.3"
+			}
+		},
+		"node_modules/@sveltejs/vite-plugin-svelte": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.1.tgz",
+			"integrity": "sha512-rimpFEAboBBHIlzISibg94iP09k/KYdHgVhJlcsTfn7KMBhc70jFX/GRWkRdFCc2fdnk+4+Bdfej23cMDnJS6A==",
+			"dependencies": {
+				"@sveltejs/vite-plugin-svelte-inspector": "^2.1.0",
+				"debug": "^4.3.4",
+				"deepmerge": "^4.3.1",
+				"kleur": "^4.1.5",
+				"magic-string": "^0.30.10",
+				"svelte-hmr": "^0.16.0",
+				"vitefu": "^0.2.5"
+			},
+			"engines": {
+				"node": "^18.0.0 || >=20"
+			},
+			"peerDependencies": {
+				"svelte": "^4.0.0 || ^5.0.0-next.0",
+				"vite": "^5.0.0"
+			}
+		},
+		"node_modules/@sveltejs/vite-plugin-svelte-inspector": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz",
+			"integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==",
+			"dependencies": {
+				"debug": "^4.3.4"
+			},
+			"engines": {
+				"node": "^18.0.0 || >=20"
+			},
+			"peerDependencies": {
+				"@sveltejs/vite-plugin-svelte": "^3.0.0",
+				"svelte": "^4.0.0 || ^5.0.0-next.0",
+				"vite": "^5.0.0"
+			}
+		},
+		"node_modules/@swc/helpers": {
+			"version": "0.5.7",
+			"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.7.tgz",
+			"integrity": "sha512-BVvNZhx362+l2tSwSuyEUV4h7+jk9raNdoTSdLfwTshXJSaGmYKluGRJznziCI3KX02Z19DdsQrdfrpXAU3Hfg==",
+			"dependencies": {
+				"tslib": "^2.4.0"
+			}
+		},
+		"node_modules/@tailwindcss/typography": {
+			"version": "0.5.13",
+			"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz",
+			"integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==",
+			"dev": true,
+			"dependencies": {
+				"lodash.castarray": "^4.4.0",
+				"lodash.isplainobject": "^4.0.6",
+				"lodash.merge": "^4.6.2",
+				"postcss-selector-parser": "6.0.10"
+			},
+			"peerDependencies": {
+				"tailwindcss": ">=3.0.0 || insiders"
+			}
+		},
+		"node_modules/@types/cookie": {
+			"version": "0.6.0",
+			"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+			"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
+		},
+		"node_modules/@types/d3-color": {
+			"version": "3.1.3",
+			"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+			"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
+		},
+		"node_modules/@types/d3-drag": {
+			"version": "3.0.7",
+			"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+			"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+			"dependencies": {
+				"@types/d3-selection": "*"
+			}
+		},
+		"node_modules/@types/d3-interpolate": {
+			"version": "3.0.4",
+			"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+			"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+			"dependencies": {
+				"@types/d3-color": "*"
+			}
+		},
+		"node_modules/@types/d3-scale": {
+			"version": "4.0.8",
+			"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
+			"integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
+			"dependencies": {
+				"@types/d3-time": "*"
+			}
+		},
+		"node_modules/@types/d3-scale-chromatic": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz",
+			"integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw=="
+		},
+		"node_modules/@types/d3-selection": {
+			"version": "3.0.10",
+			"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz",
+			"integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg=="
+		},
+		"node_modules/@types/d3-time": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz",
+			"integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw=="
+		},
+		"node_modules/@types/d3-transition": {
+			"version": "3.0.8",
+			"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz",
+			"integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==",
+			"dependencies": {
+				"@types/d3-selection": "*"
+			}
+		},
+		"node_modules/@types/d3-zoom": {
+			"version": "3.0.8",
+			"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+			"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+			"dependencies": {
+				"@types/d3-interpolate": "*",
+				"@types/d3-selection": "*"
+			}
+		},
+		"node_modules/@types/debug": {
+			"version": "4.1.12",
+			"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+			"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+			"dependencies": {
+				"@types/ms": "*"
+			}
+		},
+		"node_modules/@types/estree": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+			"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
+		},
+		"node_modules/@types/json-schema": {
+			"version": "7.0.15",
+			"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+			"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+			"dev": true
+		},
+		"node_modules/@types/linkify-it": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
+			"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="
+		},
+		"node_modules/@types/markdown-it": {
+			"version": "14.1.2",
+			"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
+			"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
+			"dependencies": {
+				"@types/linkify-it": "^5",
+				"@types/mdurl": "^2"
+			}
+		},
+		"node_modules/@types/mdast": {
+			"version": "3.0.15",
+			"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz",
+			"integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==",
+			"dependencies": {
+				"@types/unist": "^2"
+			}
+		},
+		"node_modules/@types/mdurl": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
+			"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="
+		},
+		"node_modules/@types/minimatch": {
+			"version": "3.0.5",
+			"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
+			"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
+			"dev": true
+		},
+		"node_modules/@types/ms": {
+			"version": "0.7.34",
+			"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
+			"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
+		},
+		"node_modules/@types/node": {
+			"version": "20.11.30",
+			"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
+			"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
+			"dependencies": {
+				"undici-types": "~5.26.4"
+			}
+		},
+		"node_modules/@types/pug": {
+			"version": "2.0.10",
+			"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
+			"integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
+			"dev": true
+		},
+		"node_modules/@types/resolve": {
+			"version": "1.20.2",
+			"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
+			"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
+		},
+		"node_modules/@types/semver": {
+			"version": "7.5.8",
+			"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
+			"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
+			"dev": true
+		},
+		"node_modules/@types/sinonjs__fake-timers": {
+			"version": "8.1.1",
+			"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
+			"integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==",
+			"dev": true
+		},
+		"node_modules/@types/sizzle": {
+			"version": "2.3.8",
+			"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz",
+			"integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==",
+			"dev": true
+		},
+		"node_modules/@types/symlink-or-copy": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/@types/symlink-or-copy/-/symlink-or-copy-1.2.2.tgz",
+			"integrity": "sha512-MQ1AnmTLOncwEf9IVU+B2e4Hchrku5N67NkgcAHW0p3sdzPe0FNMANxEm6OJUzPniEQGkeT3OROLlCwZJLWFZA==",
+			"dev": true
+		},
+		"node_modules/@types/unist": {
+			"version": "2.0.10",
+			"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
+			"integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="
+		},
+		"node_modules/@types/yauzl": {
+			"version": "2.10.3",
+			"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+			"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+			"dev": true,
+			"optional": true,
+			"dependencies": {
+				"@types/node": "*"
+			}
+		},
+		"node_modules/@typescript-eslint/eslint-plugin": {
+			"version": "6.21.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
+			"integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
+			"dev": true,
+			"dependencies": {
+				"@eslint-community/regexpp": "^4.5.1",
+				"@typescript-eslint/scope-manager": "6.21.0",
+				"@typescript-eslint/type-utils": "6.21.0",
+				"@typescript-eslint/utils": "6.21.0",
+				"@typescript-eslint/visitor-keys": "6.21.0",
+				"debug": "^4.3.4",
+				"graphemer": "^1.4.0",
+				"ignore": "^5.2.4",
+				"natural-compare": "^1.4.0",
+				"semver": "^7.5.4",
+				"ts-api-utils": "^1.0.1"
+			},
+			"engines": {
+				"node": "^16.0.0 || >=18.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
+				"eslint": "^7.0.0 || ^8.0.0"
+			},
+			"peerDependenciesMeta": {
+				"typescript": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@typescript-eslint/parser": {
+			"version": "6.21.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
+			"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/scope-manager": "6.21.0",
+				"@typescript-eslint/types": "6.21.0",
+				"@typescript-eslint/typescript-estree": "6.21.0",
+				"@typescript-eslint/visitor-keys": "6.21.0",
+				"debug": "^4.3.4"
+			},
+			"engines": {
+				"node": "^16.0.0 || >=18.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"eslint": "^7.0.0 || ^8.0.0"
+			},
+			"peerDependenciesMeta": {
+				"typescript": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@typescript-eslint/scope-manager": {
+			"version": "6.21.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
+			"integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/types": "6.21.0",
+				"@typescript-eslint/visitor-keys": "6.21.0"
+			},
+			"engines": {
+				"node": "^16.0.0 || >=18.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			}
+		},
+		"node_modules/@typescript-eslint/type-utils": {
+			"version": "6.21.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
+			"integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/typescript-estree": "6.21.0",
+				"@typescript-eslint/utils": "6.21.0",
+				"debug": "^4.3.4",
+				"ts-api-utils": "^1.0.1"
+			},
+			"engines": {
+				"node": "^16.0.0 || >=18.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"eslint": "^7.0.0 || ^8.0.0"
+			},
+			"peerDependenciesMeta": {
+				"typescript": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@typescript-eslint/types": {
+			"version": "6.21.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
+			"integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
+			"dev": true,
+			"engines": {
+				"node": "^16.0.0 || >=18.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			}
+		},
+		"node_modules/@typescript-eslint/typescript-estree": {
+			"version": "6.21.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
+			"integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/types": "6.21.0",
+				"@typescript-eslint/visitor-keys": "6.21.0",
+				"debug": "^4.3.4",
+				"globby": "^11.1.0",
+				"is-glob": "^4.0.3",
+				"minimatch": "9.0.3",
+				"semver": "^7.5.4",
+				"ts-api-utils": "^1.0.1"
+			},
+			"engines": {
+				"node": "^16.0.0 || >=18.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependenciesMeta": {
+				"typescript": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@typescript-eslint/utils": {
+			"version": "6.21.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
+			"integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
+			"dev": true,
+			"dependencies": {
+				"@eslint-community/eslint-utils": "^4.4.0",
+				"@types/json-schema": "^7.0.12",
+				"@types/semver": "^7.5.0",
+				"@typescript-eslint/scope-manager": "6.21.0",
+				"@typescript-eslint/types": "6.21.0",
+				"@typescript-eslint/typescript-estree": "6.21.0",
+				"semver": "^7.5.4"
+			},
+			"engines": {
+				"node": "^16.0.0 || >=18.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"eslint": "^7.0.0 || ^8.0.0"
+			}
+		},
+		"node_modules/@typescript-eslint/visitor-keys": {
+			"version": "6.21.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
+			"integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/types": "6.21.0",
+				"eslint-visitor-keys": "^3.4.1"
+			},
+			"engines": {
+				"node": "^16.0.0 || >=18.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			}
+		},
+		"node_modules/@ungap/structured-clone": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+			"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
+		},
+		"node_modules/@ungap/with-resolvers": {
+			"version": "0.1.0",
+			"resolved": "https://registry.npmjs.org/@ungap/with-resolvers/-/with-resolvers-0.1.0.tgz",
+			"integrity": "sha512-g7f0IkJdPW2xhY7H4iE72DAsIyfuwEFc6JWc2tYFwKDMWWAF699vGjrM348cwQuOXgHpe1gWFe+Eiyjx/ewvvw=="
+		},
+		"node_modules/@vitest/expect": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz",
+			"integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==",
+			"dev": true,
+			"dependencies": {
+				"@vitest/spy": "1.6.0",
+				"@vitest/utils": "1.6.0",
+				"chai": "^4.3.10"
+			},
+			"funding": {
+				"url": "https://opencollective.com/vitest"
+			}
+		},
+		"node_modules/@vitest/runner": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz",
+			"integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==",
+			"dev": true,
+			"dependencies": {
+				"@vitest/utils": "1.6.0",
+				"p-limit": "^5.0.0",
+				"pathe": "^1.1.1"
+			},
+			"funding": {
+				"url": "https://opencollective.com/vitest"
+			}
+		},
+		"node_modules/@vitest/runner/node_modules/p-limit": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
+			"integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
+			"dev": true,
+			"dependencies": {
+				"yocto-queue": "^1.0.0"
+			},
+			"engines": {
+				"node": ">=18"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/@vitest/runner/node_modules/yocto-queue": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
+			"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+			"dev": true,
+			"engines": {
+				"node": ">=12.20"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/@vitest/snapshot": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz",
+			"integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==",
+			"dev": true,
+			"dependencies": {
+				"magic-string": "^0.30.5",
+				"pathe": "^1.1.1",
+				"pretty-format": "^29.7.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/vitest"
+			}
+		},
+		"node_modules/@vitest/spy": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz",
+			"integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==",
+			"dev": true,
+			"dependencies": {
+				"tinyspy": "^2.2.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/vitest"
+			}
+		},
+		"node_modules/@vitest/utils": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz",
+			"integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==",
+			"dev": true,
+			"dependencies": {
+				"diff-sequences": "^29.6.3",
+				"estree-walker": "^3.0.3",
+				"loupe": "^2.3.7",
+				"pretty-format": "^29.7.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/vitest"
+			}
+		},
+		"node_modules/@vitest/utils/node_modules/estree-walker": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+			"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+			"dev": true,
+			"dependencies": {
+				"@types/estree": "^1.0.0"
+			}
+		},
+		"node_modules/@webreflection/fetch": {
+			"version": "0.1.5",
+			"resolved": "https://registry.npmjs.org/@webreflection/fetch/-/fetch-0.1.5.tgz",
+			"integrity": "sha512-zCcqCJoNLvdeF41asAK71XPlwSPieeRDsE09albBunJEksuYPYNillKNQjf8p5BqSoTKTuKrW3lUm3MNodUC4g=="
+		},
+		"node_modules/@xyflow/svelte": {
+			"version": "0.1.19",
+			"resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-0.1.19.tgz",
+			"integrity": "sha512-yW5w5aI+Yqkob4kLQpVDo/ZmX+E9Pw7459kqwLfv4YG4N1NYXrsDRh9cyph/rapbuDnPi6zqK5E8LKrgaCQC0w==",
+			"dependencies": {
+				"@svelte-put/shortcut": "^3.1.0",
+				"@xyflow/system": "0.0.42",
+				"classcat": "^5.0.4"
+			},
+			"peerDependencies": {
+				"svelte": "^3.0.0 || ^4.0.0"
+			}
+		},
+		"node_modules/@xyflow/system": {
+			"version": "0.0.42",
+			"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.42.tgz",
+			"integrity": "sha512-kWYj+Y0GOct0jKYTdyRMNOLPxGNbb2TYvPg2gTmJnZ31DOOMkL5uRBLX825DR2gOACDu+i5FHLxPJUPf/eGOJw==",
+			"dependencies": {
+				"@types/d3-drag": "^3.0.7",
+				"@types/d3-selection": "^3.0.10",
+				"@types/d3-transition": "^3.0.8",
+				"@types/d3-zoom": "^3.0.8",
+				"d3-drag": "^3.0.0",
+				"d3-selection": "^3.0.0",
+				"d3-zoom": "^3.0.0"
+			}
+		},
+		"node_modules/acorn": {
+			"version": "8.11.3",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+			"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+			"bin": {
+				"acorn": "bin/acorn"
+			},
+			"engines": {
+				"node": ">=0.4.0"
+			}
+		},
+		"node_modules/acorn-jsx": {
+			"version": "5.3.2",
+			"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+			"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+			"dev": true,
+			"peerDependencies": {
+				"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+			}
+		},
+		"node_modules/acorn-walk": {
+			"version": "8.3.2",
+			"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
+			"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.4.0"
+			}
+		},
+		"node_modules/aggregate-error": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+			"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+			"dev": true,
+			"dependencies": {
+				"clean-stack": "^2.0.0",
+				"indent-string": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/ajv": {
+			"version": "6.12.6",
+			"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+			"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+			"dev": true,
+			"dependencies": {
+				"fast-deep-equal": "^3.1.1",
+				"fast-json-stable-stringify": "^2.0.0",
+				"json-schema-traverse": "^0.4.1",
+				"uri-js": "^4.2.2"
+			},
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/epoberezkin"
+			}
+		},
+		"node_modules/amator": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz",
+			"integrity": "sha512-V5+aH8pe+Z3u/UG3L3pG3BaFQGXAyXHVQDroRwjPHdh08bcUEchAVsU1MCuJSCaU5o60wTK6KaE6te5memzgYw==",
+			"dependencies": {
+				"bezier-easing": "^2.0.3"
+			}
+		},
+		"node_modules/ansi-colors": {
+			"version": "4.1.3",
+			"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+			"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/ansi-escapes": {
+			"version": "4.3.2",
+			"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+			"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+			"dev": true,
+			"dependencies": {
+				"type-fest": "^0.21.3"
+			},
+			"engines": {
+				"node": ">=8"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/ansi-escapes/node_modules/type-fest": {
+			"version": "0.21.3",
+			"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+			"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+			"dev": true,
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/ansi-regex": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+			"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/ansi-styles": {
+			"version": "4.3.0",
+			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+			"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+			"dependencies": {
+				"color-convert": "^2.0.1"
+			},
+			"engines": {
+				"node": ">=8"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/ansi-styles?sponsor=1"
+			}
+		},
+		"node_modules/any-promise": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+			"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+			"dev": true
+		},
+		"node_modules/anymatch": {
+			"version": "3.1.3",
+			"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+			"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+			"dev": true,
+			"dependencies": {
+				"normalize-path": "^3.0.0",
+				"picomatch": "^2.0.4"
+			},
+			"engines": {
+				"node": ">= 8"
+			}
+		},
+		"node_modules/arch": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
+			"integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			]
+		},
+		"node_modules/arg": {
+			"version": "5.0.2",
+			"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+			"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+			"dev": true
+		},
+		"node_modules/argparse": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+			"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+		},
+		"node_modules/aria-query": {
+			"version": "5.3.0",
+			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+			"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+			"dependencies": {
+				"dequal": "^2.0.3"
+			}
+		},
+		"node_modules/array-union": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+			"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/asn1": {
+			"version": "0.2.6",
+			"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+			"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+			"dev": true,
+			"dependencies": {
+				"safer-buffer": "~2.1.0"
+			}
+		},
+		"node_modules/assert-plus": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+			"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.8"
+			}
+		},
+		"node_modules/assertion-error": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+			"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+			"dev": true,
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/astral-regex": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+			"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/async": {
+			"version": "3.2.5",
+			"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
+			"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
+		},
+		"node_modules/asynckit": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+			"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+			"dev": true
+		},
+		"node_modules/at-least-node": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+			"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 4.0.0"
+			}
+		},
+		"node_modules/autoprefixer": {
+			"version": "10.4.19",
+			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
+			"integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/postcss/"
+				},
+				{
+					"type": "tidelift",
+					"url": "https://tidelift.com/funding/github/npm/autoprefixer"
+				},
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			],
+			"dependencies": {
+				"browserslist": "^4.23.0",
+				"caniuse-lite": "^1.0.30001599",
+				"fraction.js": "^4.3.7",
+				"normalize-range": "^0.1.2",
+				"picocolors": "^1.0.0",
+				"postcss-value-parser": "^4.2.0"
+			},
+			"bin": {
+				"autoprefixer": "bin/autoprefixer"
+			},
+			"engines": {
+				"node": "^10 || ^12 || >=14"
+			},
+			"peerDependencies": {
+				"postcss": "^8.1.0"
+			}
+		},
+		"node_modules/aws-sign2": {
+			"version": "0.7.0",
+			"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+			"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
+			"dev": true,
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/aws4": {
+			"version": "1.13.2",
+			"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
+			"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
+			"dev": true
+		},
+		"node_modules/axobject-query": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz",
+			"integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==",
+			"dependencies": {
+				"dequal": "^2.0.3"
+			}
+		},
+		"node_modules/balanced-match": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+			"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+		},
+		"node_modules/bare-events": {
+			"version": "2.2.2",
+			"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz",
+			"integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==",
+			"dev": true,
+			"optional": true
+		},
+		"node_modules/base64-js": {
+			"version": "1.5.1",
+			"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+			"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			]
+		},
+		"node_modules/basic-devtools": {
+			"version": "0.1.6",
+			"resolved": "https://registry.npmjs.org/basic-devtools/-/basic-devtools-0.1.6.tgz",
+			"integrity": "sha512-g9zJ63GmdUesS3/Fwv0B5SYX6nR56TQXmGr+wE5PRTNCnGQMYWhUx/nZB/mMWnQJVLPPAp89oxDNlasdtNkW5Q=="
+		},
+		"node_modules/bcrypt-pbkdf": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+			"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
+			"dev": true,
+			"dependencies": {
+				"tweetnacl": "^0.14.3"
+			}
+		},
+		"node_modules/bezier-easing": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
+			"integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
+		},
+		"node_modules/binary-extensions": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+			"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/bits-ui": {
+			"version": "0.19.7",
+			"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.19.7.tgz",
+			"integrity": "sha512-GHUpKvN7QyazhnZNkUy0lxg6W1M6KJHWSZ4a/UGCjPE6nQgk6vKbGysY67PkDtQMknZTZAzVoMj1Eic4IKeCRQ==",
+			"dependencies": {
+				"@internationalized/date": "^3.5.1",
+				"@melt-ui/svelte": "0.76.0",
+				"nanoid": "^5.0.5"
+			},
+			"peerDependencies": {
+				"svelte": "^4.0.0"
+			}
+		},
+		"node_modules/bl": {
+			"version": "5.1.0",
+			"resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
+			"integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
+			"dev": true,
+			"dependencies": {
+				"buffer": "^6.0.3",
+				"inherits": "^2.0.4",
+				"readable-stream": "^3.4.0"
+			}
+		},
+		"node_modules/bl/node_modules/readable-stream": {
+			"version": "3.6.2",
+			"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+			"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+			"dev": true,
+			"dependencies": {
+				"inherits": "^2.0.3",
+				"string_decoder": "^1.1.1",
+				"util-deprecate": "^1.0.1"
+			},
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/blob-util": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz",
+			"integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==",
+			"dev": true
+		},
+		"node_modules/bluebird": {
+			"version": "3.7.2",
+			"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+			"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+			"dev": true
+		},
+		"node_modules/boolbase": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+			"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+			"dev": true
+		},
+		"node_modules/brace-expansion": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+			"dependencies": {
+				"balanced-match": "^1.0.0"
+			}
+		},
+		"node_modules/braces": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+			"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+			"dev": true,
+			"dependencies": {
+				"fill-range": "^7.1.1"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/broccoli-node-api": {
+			"version": "1.7.0",
+			"resolved": "https://registry.npmjs.org/broccoli-node-api/-/broccoli-node-api-1.7.0.tgz",
+			"integrity": "sha512-QIqLSVJWJUVOhclmkmypJJH9u9s/aWH4+FH6Q6Ju5l+Io4dtwqdPUNmDfw40o6sxhbZHhqGujDJuHTML1wG8Yw==",
+			"dev": true
+		},
+		"node_modules/broccoli-node-info": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/broccoli-node-info/-/broccoli-node-info-2.2.0.tgz",
+			"integrity": "sha512-VabSGRpKIzpmC+r+tJueCE5h8k6vON7EIMMWu6d/FyPdtijwLQ7QvzShEw+m3mHoDzUaj/kiZsDYrS8X2adsBg==",
+			"dev": true,
+			"engines": {
+				"node": "8.* || >= 10.*"
+			}
+		},
+		"node_modules/broccoli-output-wrapper": {
+			"version": "3.2.5",
+			"resolved": "https://registry.npmjs.org/broccoli-output-wrapper/-/broccoli-output-wrapper-3.2.5.tgz",
+			"integrity": "sha512-bQAtwjSrF4Nu0CK0JOy5OZqw9t5U0zzv2555EA/cF8/a8SLDTIetk9UgrtMVw7qKLKdSpOZ2liZNeZZDaKgayw==",
+			"dev": true,
+			"dependencies": {
+				"fs-extra": "^8.1.0",
+				"heimdalljs-logger": "^0.1.10",
+				"symlink-or-copy": "^1.2.0"
+			},
+			"engines": {
+				"node": "10.* || >= 12.*"
+			}
+		},
+		"node_modules/broccoli-output-wrapper/node_modules/fs-extra": {
+			"version": "8.1.0",
+			"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+			"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+			"dev": true,
+			"dependencies": {
+				"graceful-fs": "^4.2.0",
+				"jsonfile": "^4.0.0",
+				"universalify": "^0.1.0"
+			},
+			"engines": {
+				"node": ">=6 <7 || >=8"
+			}
+		},
+		"node_modules/broccoli-output-wrapper/node_modules/jsonfile": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+			"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+			"dev": true,
+			"optionalDependencies": {
+				"graceful-fs": "^4.1.6"
+			}
+		},
+		"node_modules/broccoli-output-wrapper/node_modules/universalify": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+			"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 4.0.0"
+			}
+		},
+		"node_modules/broccoli-plugin": {
+			"version": "4.0.7",
+			"resolved": "https://registry.npmjs.org/broccoli-plugin/-/broccoli-plugin-4.0.7.tgz",
+			"integrity": "sha512-a4zUsWtA1uns1K7p9rExYVYG99rdKeGRymW0qOCNkvDPHQxVi3yVyJHhQbM3EZwdt2E0mnhr5e0c/bPpJ7p3Wg==",
+			"dev": true,
+			"dependencies": {
+				"broccoli-node-api": "^1.7.0",
+				"broccoli-output-wrapper": "^3.2.5",
+				"fs-merger": "^3.2.1",
+				"promise-map-series": "^0.3.0",
+				"quick-temp": "^0.1.8",
+				"rimraf": "^3.0.2",
+				"symlink-or-copy": "^1.3.1"
+			},
+			"engines": {
+				"node": "10.* || >= 12.*"
+			}
+		},
+		"node_modules/browserslist": {
+			"version": "4.23.0",
+			"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
+			"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/browserslist"
+				},
+				{
+					"type": "tidelift",
+					"url": "https://tidelift.com/funding/github/npm/browserslist"
+				},
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			],
+			"dependencies": {
+				"caniuse-lite": "^1.0.30001587",
+				"electron-to-chromium": "^1.4.668",
+				"node-releases": "^2.0.14",
+				"update-browserslist-db": "^1.0.13"
+			},
+			"bin": {
+				"browserslist": "cli.js"
+			},
+			"engines": {
+				"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+			}
+		},
+		"node_modules/buffer": {
+			"version": "6.0.3",
+			"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+			"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			],
+			"dependencies": {
+				"base64-js": "^1.3.1",
+				"ieee754": "^1.2.1"
+			}
+		},
+		"node_modules/buffer-crc32": {
+			"version": "0.2.13",
+			"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+			"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+			"dev": true,
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/builtin-modules": {
+			"version": "3.3.0",
+			"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
+			"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
+			"engines": {
+				"node": ">=6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/cac": {
+			"version": "6.7.14",
+			"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+			"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/cachedir": {
+			"version": "2.4.0",
+			"resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz",
+			"integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/call-bind": {
+			"version": "1.0.7",
+			"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+			"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+			"dev": true,
+			"dependencies": {
+				"es-define-property": "^1.0.0",
+				"es-errors": "^1.3.0",
+				"function-bind": "^1.1.2",
+				"get-intrinsic": "^1.2.4",
+				"set-function-length": "^1.2.1"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/callsites": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+			"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/camelcase-css": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+			"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+			"dev": true,
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/caniuse-lite": {
+			"version": "1.0.30001600",
+			"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz",
+			"integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/browserslist"
+				},
+				{
+					"type": "tidelift",
+					"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+				},
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			]
+		},
+		"node_modules/caseless": {
+			"version": "0.12.0",
+			"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+			"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
+			"dev": true
+		},
+		"node_modules/chai": {
+			"version": "4.4.1",
+			"resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz",
+			"integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==",
+			"dev": true,
+			"dependencies": {
+				"assertion-error": "^1.1.0",
+				"check-error": "^1.0.3",
+				"deep-eql": "^4.1.3",
+				"get-func-name": "^2.0.2",
+				"loupe": "^2.3.6",
+				"pathval": "^1.1.1",
+				"type-detect": "^4.0.8"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/chalk": {
+			"version": "4.1.2",
+			"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+			"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+			"dev": true,
+			"dependencies": {
+				"ansi-styles": "^4.1.0",
+				"supports-color": "^7.1.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/chalk?sponsor=1"
+			}
+		},
+		"node_modules/character-entities": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+			"integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/wooorm"
+			}
+		},
+		"node_modules/check-error": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+			"integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+			"dev": true,
+			"dependencies": {
+				"get-func-name": "^2.0.2"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/check-more-types": {
+			"version": "2.24.0",
+			"resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
+			"integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==",
+			"dev": true,
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/cheerio": {
+			"version": "1.0.0-rc.12",
+			"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
+			"integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
+			"dev": true,
+			"dependencies": {
+				"cheerio-select": "^2.1.0",
+				"dom-serializer": "^2.0.0",
+				"domhandler": "^5.0.3",
+				"domutils": "^3.0.1",
+				"htmlparser2": "^8.0.1",
+				"parse5": "^7.0.0",
+				"parse5-htmlparser2-tree-adapter": "^7.0.0"
+			},
+			"engines": {
+				"node": ">= 6"
+			},
+			"funding": {
+				"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
+			}
+		},
+		"node_modules/cheerio-select": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
+			"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
+			"dev": true,
+			"dependencies": {
+				"boolbase": "^1.0.0",
+				"css-select": "^5.1.0",
+				"css-what": "^6.1.0",
+				"domelementtype": "^2.3.0",
+				"domhandler": "^5.0.3",
+				"domutils": "^3.0.1"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/fb55"
+			}
+		},
+		"node_modules/chokidar": {
+			"version": "3.6.0",
+			"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+			"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+			"dev": true,
+			"dependencies": {
+				"anymatch": "~3.1.2",
+				"braces": "~3.0.2",
+				"glob-parent": "~5.1.2",
+				"is-binary-path": "~2.1.0",
+				"is-glob": "~4.0.1",
+				"normalize-path": "~3.0.0",
+				"readdirp": "~3.6.0"
+			},
+			"engines": {
+				"node": ">= 8.10.0"
+			},
+			"funding": {
+				"url": "https://paulmillr.com/funding/"
+			},
+			"optionalDependencies": {
+				"fsevents": "~2.3.2"
+			}
+		},
+		"node_modules/chokidar/node_modules/glob-parent": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+			"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+			"dev": true,
+			"dependencies": {
+				"is-glob": "^4.0.1"
+			},
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/chownr": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+			"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+			"engines": {
+				"node": ">=18"
+			}
+		},
+		"node_modules/ci-info": {
+			"version": "3.9.0",
+			"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+			"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/sibiraj-s"
+				}
+			],
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/classcat": {
+			"version": "5.0.5",
+			"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
+			"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="
+		},
+		"node_modules/clean-stack": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+			"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/cli-cursor": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+			"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+			"dev": true,
+			"dependencies": {
+				"restore-cursor": "^3.1.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/cli-table3": {
+			"version": "0.6.4",
+			"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.4.tgz",
+			"integrity": "sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw==",
+			"dev": true,
+			"dependencies": {
+				"string-width": "^4.2.0"
+			},
+			"engines": {
+				"node": "10.* || >= 12.*"
+			},
+			"optionalDependencies": {
+				"@colors/colors": "1.5.0"
+			}
+		},
+		"node_modules/cli-table3/node_modules/emoji-regex": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+			"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+			"dev": true
+		},
+		"node_modules/cli-table3/node_modules/string-width": {
+			"version": "4.2.3",
+			"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+			"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+			"dev": true,
+			"dependencies": {
+				"emoji-regex": "^8.0.0",
+				"is-fullwidth-code-point": "^3.0.0",
+				"strip-ansi": "^6.0.1"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/cli-truncate": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
+			"integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
+			"dev": true,
+			"dependencies": {
+				"slice-ansi": "^3.0.0",
+				"string-width": "^4.2.0"
+			},
+			"engines": {
+				"node": ">=8"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/cli-truncate/node_modules/emoji-regex": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+			"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+			"dev": true
+		},
+		"node_modules/cli-truncate/node_modules/string-width": {
+			"version": "4.2.3",
+			"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+			"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+			"dev": true,
+			"dependencies": {
+				"emoji-regex": "^8.0.0",
+				"is-fullwidth-code-point": "^3.0.0",
+				"strip-ansi": "^6.0.1"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/clone": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+			"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.8"
+			}
+		},
+		"node_modules/clone-stats": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz",
+			"integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==",
+			"dev": true
+		},
+		"node_modules/code-red": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
+			"integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
+			"dependencies": {
+				"@jridgewell/sourcemap-codec": "^1.4.15",
+				"@types/estree": "^1.0.1",
+				"acorn": "^8.10.0",
+				"estree-walker": "^3.0.3",
+				"periscopic": "^3.1.0"
+			}
+		},
+		"node_modules/code-red/node_modules/estree-walker": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+			"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+			"dependencies": {
+				"@types/estree": "^1.0.0"
+			}
+		},
+		"node_modules/codedent": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/codedent/-/codedent-0.1.2.tgz",
+			"integrity": "sha512-qEqzcy5viM3UoCN0jYHZeXZoyd4NZQzYFg0kOBj8O1CgoGG9WYYTF+VeQRsN0OSKFjF3G1u4WDUOtOsWEx6N2w==",
+			"dependencies": {
+				"plain-tag": "^0.1.3"
+			}
+		},
+		"node_modules/codemirror": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
+			"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/commands": "^6.0.0",
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/lint": "^6.0.0",
+				"@codemirror/search": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0"
+			}
+		},
+		"node_modules/coincident": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmjs.org/coincident/-/coincident-1.2.3.tgz",
+			"integrity": "sha512-Uxz3BMTWIslzeWjuQnizGWVg0j6khbvHUQ8+5BdM7WuJEm4ALXwq3wluYoB+uF68uPBz/oUOeJnYURKyfjexlA==",
+			"dependencies": {
+				"@ungap/structured-clone": "^1.2.0",
+				"@ungap/with-resolvers": "^0.1.0",
+				"gc-hook": "^0.3.1",
+				"proxy-target": "^3.0.2"
+			},
+			"optionalDependencies": {
+				"ws": "^8.16.0"
+			}
+		},
+		"node_modules/color": {
+			"version": "4.2.3",
+			"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+			"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+			"dependencies": {
+				"color-convert": "^2.0.1",
+				"color-string": "^1.9.0"
+			},
+			"engines": {
+				"node": ">=12.5.0"
+			}
+		},
+		"node_modules/color-convert": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+			"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+			"dependencies": {
+				"color-name": "~1.1.4"
+			},
+			"engines": {
+				"node": ">=7.0.0"
+			}
+		},
+		"node_modules/color-name": {
+			"version": "1.1.4",
+			"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+			"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+		},
+		"node_modules/color-string": {
+			"version": "1.9.1",
+			"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+			"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+			"dependencies": {
+				"color-name": "^1.0.0",
+				"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==",
+			"dev": true
+		},
+		"node_modules/colors": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+			"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.1.90"
+			}
+		},
+		"node_modules/combined-stream": {
+			"version": "1.0.8",
+			"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+			"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+			"dev": true,
+			"dependencies": {
+				"delayed-stream": "~1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/commander": {
+			"version": "12.1.0",
+			"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+			"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+			"dev": true,
+			"engines": {
+				"node": ">=18"
+			}
+		},
+		"node_modules/common-tags": {
+			"version": "1.8.2",
+			"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
+			"integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
+			"dev": true,
+			"engines": {
+				"node": ">=4.0.0"
+			}
+		},
+		"node_modules/commondir": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+			"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
+		},
+		"node_modules/concat-map": {
+			"version": "0.0.1",
+			"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+			"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+			"dev": true
+		},
+		"node_modules/confbox": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz",
+			"integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==",
+			"dev": true
+		},
+		"node_modules/convert-source-map": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+			"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+			"dev": true
+		},
+		"node_modules/cookie": {
+			"version": "0.7.1",
+			"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+			"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/core-util-is": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+			"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+			"dev": true
+		},
+		"node_modules/cose-base": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
+			"integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
+			"dependencies": {
+				"layout-base": "^1.0.0"
+			}
+		},
+		"node_modules/crc-32": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+			"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+			"bin": {
+				"crc32": "bin/crc32.njs"
+			},
+			"engines": {
+				"node": ">=0.8"
+			}
+		},
+		"node_modules/crelt": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+			"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
+		},
+		"node_modules/cross-spawn": {
+			"version": "7.0.3",
+			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+			"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+			"dependencies": {
+				"path-key": "^3.1.0",
+				"shebang-command": "^2.0.0",
+				"which": "^2.0.1"
+			},
+			"engines": {
+				"node": ">= 8"
+			}
+		},
+		"node_modules/css-select": {
+			"version": "5.1.0",
+			"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
+			"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+			"dev": true,
+			"dependencies": {
+				"boolbase": "^1.0.0",
+				"css-what": "^6.1.0",
+				"domhandler": "^5.0.2",
+				"domutils": "^3.0.1",
+				"nth-check": "^2.0.1"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/fb55"
+			}
+		},
+		"node_modules/css-tree": {
+			"version": "2.3.1",
+			"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+			"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+			"dependencies": {
+				"mdn-data": "2.0.30",
+				"source-map-js": "^1.0.1"
+			},
+			"engines": {
+				"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+			}
+		},
+		"node_modules/css-what": {
+			"version": "6.1.0",
+			"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
+			"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+			"dev": true,
+			"engines": {
+				"node": ">= 6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/fb55"
+			}
+		},
+		"node_modules/cssesc": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+			"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+			"dev": true,
+			"bin": {
+				"cssesc": "bin/cssesc"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/cypress": {
+			"version": "13.15.0",
+			"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz",
+			"integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==",
+			"dev": true,
+			"hasInstallScript": true,
+			"dependencies": {
+				"@cypress/request": "^3.0.4",
+				"@cypress/xvfb": "^1.2.4",
+				"@types/sinonjs__fake-timers": "8.1.1",
+				"@types/sizzle": "^2.3.2",
+				"arch": "^2.2.0",
+				"blob-util": "^2.0.2",
+				"bluebird": "^3.7.2",
+				"buffer": "^5.7.1",
+				"cachedir": "^2.3.0",
+				"chalk": "^4.1.0",
+				"check-more-types": "^2.24.0",
+				"cli-cursor": "^3.1.0",
+				"cli-table3": "~0.6.1",
+				"commander": "^6.2.1",
+				"common-tags": "^1.8.0",
+				"dayjs": "^1.10.4",
+				"debug": "^4.3.4",
+				"enquirer": "^2.3.6",
+				"eventemitter2": "6.4.7",
+				"execa": "4.1.0",
+				"executable": "^4.1.1",
+				"extract-zip": "2.0.1",
+				"figures": "^3.2.0",
+				"fs-extra": "^9.1.0",
+				"getos": "^3.2.1",
+				"is-ci": "^3.0.1",
+				"is-installed-globally": "~0.4.0",
+				"lazy-ass": "^1.6.0",
+				"listr2": "^3.8.3",
+				"lodash": "^4.17.21",
+				"log-symbols": "^4.0.0",
+				"minimist": "^1.2.8",
+				"ospath": "^1.2.2",
+				"pretty-bytes": "^5.6.0",
+				"process": "^0.11.10",
+				"proxy-from-env": "1.0.0",
+				"request-progress": "^3.0.0",
+				"semver": "^7.5.3",
+				"supports-color": "^8.1.1",
+				"tmp": "~0.2.3",
+				"untildify": "^4.0.0",
+				"yauzl": "^2.10.0"
+			},
+			"bin": {
+				"cypress": "bin/cypress"
+			},
+			"engines": {
+				"node": "^16.0.0 || ^18.0.0 || >=20.0.0"
+			}
+		},
+		"node_modules/cypress/node_modules/buffer": {
+			"version": "5.7.1",
+			"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+			"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			],
+			"dependencies": {
+				"base64-js": "^1.3.1",
+				"ieee754": "^1.1.13"
+			}
+		},
+		"node_modules/cypress/node_modules/commander": {
+			"version": "6.2.1",
+			"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+			"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
+			"dev": true,
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/cypress/node_modules/fs-extra": {
+			"version": "9.1.0",
+			"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+			"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+			"dev": true,
+			"dependencies": {
+				"at-least-node": "^1.0.0",
+				"graceful-fs": "^4.2.0",
+				"jsonfile": "^6.0.1",
+				"universalify": "^2.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			}
+		},
+		"node_modules/cypress/node_modules/supports-color": {
+			"version": "8.1.1",
+			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+			"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+			"dev": true,
+			"dependencies": {
+				"has-flag": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/supports-color?sponsor=1"
+			}
+		},
+		"node_modules/cytoscape": {
+			"version": "3.29.2",
+			"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.29.2.tgz",
+			"integrity": "sha512-2G1ycU28Nh7OHT9rkXRLpCDP30MKH1dXJORZuBhtEhEW7pKwgPi77ImqlCWinouyE1PNepIOGZBOrE84DG7LyQ==",
+			"engines": {
+				"node": ">=0.10"
+			}
+		},
+		"node_modules/cytoscape-cose-bilkent": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
+			"integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
+			"dependencies": {
+				"cose-base": "^1.0.0"
+			},
+			"peerDependencies": {
+				"cytoscape": "^3.2.0"
+			}
+		},
+		"node_modules/d3": {
+			"version": "7.9.0",
+			"resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
+			"integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+			"dependencies": {
+				"d3-array": "3",
+				"d3-axis": "3",
+				"d3-brush": "3",
+				"d3-chord": "3",
+				"d3-color": "3",
+				"d3-contour": "4",
+				"d3-delaunay": "6",
+				"d3-dispatch": "3",
+				"d3-drag": "3",
+				"d3-dsv": "3",
+				"d3-ease": "3",
+				"d3-fetch": "3",
+				"d3-force": "3",
+				"d3-format": "3",
+				"d3-geo": "3",
+				"d3-hierarchy": "3",
+				"d3-interpolate": "3",
+				"d3-path": "3",
+				"d3-polygon": "3",
+				"d3-quadtree": "3",
+				"d3-random": "3",
+				"d3-scale": "4",
+				"d3-scale-chromatic": "3",
+				"d3-selection": "3",
+				"d3-shape": "3",
+				"d3-time": "3",
+				"d3-time-format": "4",
+				"d3-timer": "3",
+				"d3-transition": "3",
+				"d3-zoom": "3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-array": {
+			"version": "3.2.4",
+			"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+			"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+			"dependencies": {
+				"internmap": "1 - 2"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-axis": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
+			"integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-brush": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
+			"integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
+			"dependencies": {
+				"d3-dispatch": "1 - 3",
+				"d3-drag": "2 - 3",
+				"d3-interpolate": "1 - 3",
+				"d3-selection": "3",
+				"d3-transition": "3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-chord": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
+			"integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
+			"dependencies": {
+				"d3-path": "1 - 3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-color": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+			"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-contour": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
+			"integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
+			"dependencies": {
+				"d3-array": "^3.2.0"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-delaunay": {
+			"version": "6.0.4",
+			"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+			"integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+			"dependencies": {
+				"delaunator": "5"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-dispatch": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+			"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-drag": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+			"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+			"dependencies": {
+				"d3-dispatch": "1 - 3",
+				"d3-selection": "3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-dsv": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
+			"integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+			"dependencies": {
+				"commander": "7",
+				"iconv-lite": "0.6",
+				"rw": "1"
+			},
+			"bin": {
+				"csv2json": "bin/dsv2json.js",
+				"csv2tsv": "bin/dsv2dsv.js",
+				"dsv2dsv": "bin/dsv2dsv.js",
+				"dsv2json": "bin/dsv2json.js",
+				"json2csv": "bin/json2dsv.js",
+				"json2dsv": "bin/json2dsv.js",
+				"json2tsv": "bin/json2dsv.js",
+				"tsv2csv": "bin/dsv2dsv.js",
+				"tsv2json": "bin/dsv2json.js"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-dsv/node_modules/commander": {
+			"version": "7.2.0",
+			"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+			"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+			"engines": {
+				"node": ">= 10"
+			}
+		},
+		"node_modules/d3-ease": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+			"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-fetch": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
+			"integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
+			"dependencies": {
+				"d3-dsv": "1 - 3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-force": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
+			"integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+			"dependencies": {
+				"d3-dispatch": "1 - 3",
+				"d3-quadtree": "1 - 3",
+				"d3-timer": "1 - 3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-format": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+			"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-geo": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
+			"integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
+			"dependencies": {
+				"d3-array": "2.5.0 - 3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-hierarchy": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+			"integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-interpolate": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+			"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+			"dependencies": {
+				"d3-color": "1 - 3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-path": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+			"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-polygon": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
+			"integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-quadtree": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
+			"integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-random": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
+			"integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-sankey": {
+			"version": "0.12.3",
+			"resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
+			"integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
+			"dependencies": {
+				"d3-array": "1 - 2",
+				"d3-shape": "^1.2.0"
+			}
+		},
+		"node_modules/d3-sankey/node_modules/d3-array": {
+			"version": "2.12.1",
+			"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+			"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+			"dependencies": {
+				"internmap": "^1.0.0"
+			}
+		},
+		"node_modules/d3-sankey/node_modules/d3-path": {
+			"version": "1.0.9",
+			"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
+			"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
+		},
+		"node_modules/d3-sankey/node_modules/d3-shape": {
+			"version": "1.3.7",
+			"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
+			"integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+			"dependencies": {
+				"d3-path": "1"
+			}
+		},
+		"node_modules/d3-sankey/node_modules/internmap": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+			"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
+		},
+		"node_modules/d3-scale": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+			"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+			"dependencies": {
+				"d3-array": "2.10.0 - 3",
+				"d3-format": "1 - 3",
+				"d3-interpolate": "1.2.0 - 3",
+				"d3-time": "2.1.1 - 3",
+				"d3-time-format": "2 - 4"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-scale-chromatic": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+			"integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
+			"dependencies": {
+				"d3-color": "1 - 3",
+				"d3-interpolate": "1 - 3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-selection": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
+			"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-shape": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+			"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+			"dependencies": {
+				"d3-path": "^3.1.0"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-time": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+			"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+			"dependencies": {
+				"d3-array": "2 - 3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-time-format": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+			"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+			"dependencies": {
+				"d3-time": "1 - 3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-timer": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+			"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/d3-transition": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
+			"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+			"dependencies": {
+				"d3-color": "1 - 3",
+				"d3-dispatch": "1 - 3",
+				"d3-ease": "1 - 3",
+				"d3-interpolate": "1 - 3",
+				"d3-timer": "1 - 3"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"peerDependencies": {
+				"d3-selection": "2 - 3"
+			}
+		},
+		"node_modules/d3-zoom": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+			"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+			"dependencies": {
+				"d3-dispatch": "1 - 3",
+				"d3-drag": "2 - 3",
+				"d3-interpolate": "1 - 3",
+				"d3-selection": "2 - 3",
+				"d3-transition": "2 - 3"
+			},
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/dagre-d3-es": {
+			"version": "7.0.10",
+			"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz",
+			"integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==",
+			"dependencies": {
+				"d3": "^7.8.2",
+				"lodash-es": "^4.17.21"
+			}
+		},
+		"node_modules/dashdash": {
+			"version": "1.14.1",
+			"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+			"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
+			"dev": true,
+			"dependencies": {
+				"assert-plus": "^1.0.0"
+			},
+			"engines": {
+				"node": ">=0.10"
+			}
+		},
+		"node_modules/dayjs": {
+			"version": "1.11.10",
+			"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
+			"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
+		},
+		"node_modules/debug": {
+			"version": "4.3.4",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+			"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+			"dependencies": {
+				"ms": "2.1.2"
+			},
+			"engines": {
+				"node": ">=6.0"
+			},
+			"peerDependenciesMeta": {
+				"supports-color": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/decode-named-character-reference": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
+			"integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
+			"dependencies": {
+				"character-entities": "^2.0.0"
+			},
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/wooorm"
+			}
+		},
+		"node_modules/deep-eql": {
+			"version": "4.1.3",
+			"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+			"integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+			"dev": true,
+			"dependencies": {
+				"type-detect": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/deep-is": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+			"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+			"dev": true
+		},
+		"node_modules/deepmerge": {
+			"version": "4.3.1",
+			"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+			"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/define-data-property": {
+			"version": "1.1.4",
+			"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+			"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+			"dev": true,
+			"dependencies": {
+				"es-define-property": "^1.0.0",
+				"es-errors": "^1.3.0",
+				"gopd": "^1.0.1"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/delaunator": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
+			"integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
+			"dependencies": {
+				"robust-predicates": "^3.0.2"
+			}
+		},
+		"node_modules/delayed-stream": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+			"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.4.0"
+			}
+		},
+		"node_modules/dequal": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+			"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/detect-indent": {
+			"version": "6.1.0",
+			"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
+			"integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/detect-libc": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+			"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/devalue": {
+			"version": "5.1.1",
+			"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
+			"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="
+		},
+		"node_modules/didyoumean": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+			"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+			"dev": true
+		},
+		"node_modules/diff": {
+			"version": "5.2.0",
+			"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+			"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+			"engines": {
+				"node": ">=0.3.1"
+			}
+		},
+		"node_modules/diff-sequences": {
+			"version": "29.6.3",
+			"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+			"integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+			"dev": true,
+			"engines": {
+				"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+			}
+		},
+		"node_modules/dir-glob": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+			"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+			"dev": true,
+			"dependencies": {
+				"path-type": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/dlv": {
+			"version": "1.1.3",
+			"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+			"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+			"dev": true
+		},
+		"node_modules/doctrine": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+			"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+			"dev": true,
+			"dependencies": {
+				"esutils": "^2.0.2"
+			},
+			"engines": {
+				"node": ">=6.0.0"
+			}
+		},
+		"node_modules/dom-serializer": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+			"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+			"dev": true,
+			"dependencies": {
+				"domelementtype": "^2.3.0",
+				"domhandler": "^5.0.2",
+				"entities": "^4.2.0"
+			},
+			"funding": {
+				"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+			}
+		},
+		"node_modules/domelementtype": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+			"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/fb55"
+				}
+			]
+		},
+		"node_modules/domhandler": {
+			"version": "5.0.3",
+			"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+			"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+			"dev": true,
+			"dependencies": {
+				"domelementtype": "^2.3.0"
+			},
+			"engines": {
+				"node": ">= 4"
+			},
+			"funding": {
+				"url": "https://github.com/fb55/domhandler?sponsor=1"
+			}
+		},
+		"node_modules/dompurify": {
+			"version": "3.1.6",
+			"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
+			"integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="
+		},
+		"node_modules/domutils": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+			"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+			"dev": true,
+			"dependencies": {
+				"dom-serializer": "^2.0.0",
+				"domelementtype": "^2.3.0",
+				"domhandler": "^5.0.3"
+			},
+			"funding": {
+				"url": "https://github.com/fb55/domutils?sponsor=1"
+			}
+		},
+		"node_modules/eastasianwidth": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+			"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
+		},
+		"node_modules/ecc-jsbn": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+			"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
+			"dev": true,
+			"dependencies": {
+				"jsbn": "~0.1.0",
+				"safer-buffer": "^2.1.0"
+			}
+		},
+		"node_modules/electron-to-chromium": {
+			"version": "1.4.715",
+			"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz",
+			"integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==",
+			"dev": true
+		},
+		"node_modules/elkjs": {
+			"version": "0.9.3",
+			"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz",
+			"integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ=="
+		},
+		"node_modules/emoji-regex": {
+			"version": "9.2.2",
+			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+			"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+		},
+		"node_modules/end-of-stream": {
+			"version": "1.4.4",
+			"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+			"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+			"dev": true,
+			"dependencies": {
+				"once": "^1.4.0"
+			}
+		},
+		"node_modules/engine.io-client": {
+			"version": "6.5.4",
+			"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
+			"integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
+			"dependencies": {
+				"@socket.io/component-emitter": "~3.1.0",
+				"debug": "~4.3.1",
+				"engine.io-parser": "~5.2.1",
+				"ws": "~8.17.1",
+				"xmlhttprequest-ssl": "~2.0.0"
+			}
+		},
+		"node_modules/engine.io-parser": {
+			"version": "5.2.2",
+			"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
+			"integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
+			"engines": {
+				"node": ">=10.0.0"
+			}
+		},
+		"node_modules/enquirer": {
+			"version": "2.4.1",
+			"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
+			"integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
+			"dev": true,
+			"dependencies": {
+				"ansi-colors": "^4.1.1",
+				"strip-ansi": "^6.0.1"
+			},
+			"engines": {
+				"node": ">=8.6"
+			}
+		},
+		"node_modules/ensure-posix-path": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz",
+			"integrity": "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==",
+			"dev": true
+		},
+		"node_modules/entities": {
+			"version": "4.5.0",
+			"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+			"engines": {
+				"node": ">=0.12"
+			},
+			"funding": {
+				"url": "https://github.com/fb55/entities?sponsor=1"
+			}
+		},
+		"node_modules/eol": {
+			"version": "0.9.1",
+			"resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz",
+			"integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==",
+			"dev": true
+		},
+		"node_modules/es-define-property": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+			"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+			"dev": true,
+			"dependencies": {
+				"get-intrinsic": "^1.2.4"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/es-errors": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+			"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+			"dev": true,
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/es6-promise": {
+			"version": "3.3.1",
+			"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+			"integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
+			"dev": true
+		},
+		"node_modules/esbuild": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
+			"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
+			"dev": true,
+			"hasInstallScript": true,
+			"bin": {
+				"esbuild": "bin/esbuild"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"optionalDependencies": {
+				"@esbuild/aix-ppc64": "0.20.2",
+				"@esbuild/android-arm": "0.20.2",
+				"@esbuild/android-arm64": "0.20.2",
+				"@esbuild/android-x64": "0.20.2",
+				"@esbuild/darwin-arm64": "0.20.2",
+				"@esbuild/darwin-x64": "0.20.2",
+				"@esbuild/freebsd-arm64": "0.20.2",
+				"@esbuild/freebsd-x64": "0.20.2",
+				"@esbuild/linux-arm": "0.20.2",
+				"@esbuild/linux-arm64": "0.20.2",
+				"@esbuild/linux-ia32": "0.20.2",
+				"@esbuild/linux-loong64": "0.20.2",
+				"@esbuild/linux-mips64el": "0.20.2",
+				"@esbuild/linux-ppc64": "0.20.2",
+				"@esbuild/linux-riscv64": "0.20.2",
+				"@esbuild/linux-s390x": "0.20.2",
+				"@esbuild/linux-x64": "0.20.2",
+				"@esbuild/netbsd-x64": "0.20.2",
+				"@esbuild/openbsd-x64": "0.20.2",
+				"@esbuild/sunos-x64": "0.20.2",
+				"@esbuild/win32-arm64": "0.20.2",
+				"@esbuild/win32-ia32": "0.20.2",
+				"@esbuild/win32-x64": "0.20.2"
+			}
+		},
+		"node_modules/escalade": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+			"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/escape-string-regexp": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+			"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+			"dev": true,
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/eslint": {
+			"version": "8.57.0",
+			"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+			"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
+			"dev": true,
+			"dependencies": {
+				"@eslint-community/eslint-utils": "^4.2.0",
+				"@eslint-community/regexpp": "^4.6.1",
+				"@eslint/eslintrc": "^2.1.4",
+				"@eslint/js": "8.57.0",
+				"@humanwhocodes/config-array": "^0.11.14",
+				"@humanwhocodes/module-importer": "^1.0.1",
+				"@nodelib/fs.walk": "^1.2.8",
+				"@ungap/structured-clone": "^1.2.0",
+				"ajv": "^6.12.4",
+				"chalk": "^4.0.0",
+				"cross-spawn": "^7.0.2",
+				"debug": "^4.3.2",
+				"doctrine": "^3.0.0",
+				"escape-string-regexp": "^4.0.0",
+				"eslint-scope": "^7.2.2",
+				"eslint-visitor-keys": "^3.4.3",
+				"espree": "^9.6.1",
+				"esquery": "^1.4.2",
+				"esutils": "^2.0.2",
+				"fast-deep-equal": "^3.1.3",
+				"file-entry-cache": "^6.0.1",
+				"find-up": "^5.0.0",
+				"glob-parent": "^6.0.2",
+				"globals": "^13.19.0",
+				"graphemer": "^1.4.0",
+				"ignore": "^5.2.0",
+				"imurmurhash": "^0.1.4",
+				"is-glob": "^4.0.0",
+				"is-path-inside": "^3.0.3",
+				"js-yaml": "^4.1.0",
+				"json-stable-stringify-without-jsonify": "^1.0.1",
+				"levn": "^0.4.1",
+				"lodash.merge": "^4.6.2",
+				"minimatch": "^3.1.2",
+				"natural-compare": "^1.4.0",
+				"optionator": "^0.9.3",
+				"strip-ansi": "^6.0.1",
+				"text-table": "^0.2.0"
+			},
+			"bin": {
+				"eslint": "bin/eslint.js"
+			},
+			"engines": {
+				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint-compat-utils": {
+			"version": "0.5.1",
+			"resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz",
+			"integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==",
+			"dev": true,
+			"dependencies": {
+				"semver": "^7.5.4"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"peerDependencies": {
+				"eslint": ">=6.0.0"
+			}
+		},
+		"node_modules/eslint-config-prettier": {
+			"version": "9.1.0",
+			"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
+			"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
+			"dev": true,
+			"bin": {
+				"eslint-config-prettier": "bin/cli.js"
+			},
+			"peerDependencies": {
+				"eslint": ">=7.0.0"
+			}
+		},
+		"node_modules/eslint-plugin-cypress": {
+			"version": "3.4.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-3.4.0.tgz",
+			"integrity": "sha512-Rrrr3Ri6wHqzrRr+TyUV7bDS4UnMMrFY1R1PP2F7XdGfe9txDC6lQEshyoNOWqGoPkbbeDm1x1XPc/adxemsnA==",
+			"dev": true,
+			"dependencies": {
+				"globals": "^13.20.0"
+			},
+			"peerDependencies": {
+				"eslint": ">=7"
+			}
+		},
+		"node_modules/eslint-plugin-svelte": {
+			"version": "2.43.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.43.0.tgz",
+			"integrity": "sha512-REkxQWvg2pp7QVLxQNa+dJ97xUqRe7Y2JJbSWkHSuszu0VcblZtXkPBPckkivk99y5CdLw4slqfPylL2d/X4jQ==",
+			"dev": true,
+			"dependencies": {
+				"@eslint-community/eslint-utils": "^4.4.0",
+				"@jridgewell/sourcemap-codec": "^1.4.15",
+				"eslint-compat-utils": "^0.5.1",
+				"esutils": "^2.0.3",
+				"known-css-properties": "^0.34.0",
+				"postcss": "^8.4.38",
+				"postcss-load-config": "^3.1.4",
+				"postcss-safe-parser": "^6.0.0",
+				"postcss-selector-parser": "^6.1.0",
+				"semver": "^7.6.2",
+				"svelte-eslint-parser": "^0.41.0"
+			},
+			"engines": {
+				"node": "^14.17.0 || >=16.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ota-meshi"
+			},
+			"peerDependencies": {
+				"eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0",
+				"svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191"
+			},
+			"peerDependenciesMeta": {
+				"svelte": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/eslint-plugin-svelte/node_modules/postcss-selector-parser": {
+			"version": "6.1.1",
+			"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz",
+			"integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==",
+			"dev": true,
+			"dependencies": {
+				"cssesc": "^3.0.0",
+				"util-deprecate": "^1.0.2"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/eslint-scope": {
+			"version": "7.2.2",
+			"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+			"integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+			"dev": true,
+			"dependencies": {
+				"esrecurse": "^4.3.0",
+				"estraverse": "^5.2.0"
+			},
+			"engines": {
+				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint-visitor-keys": {
+			"version": "3.4.3",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+			"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+			"dev": true,
+			"engines": {
+				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint/node_modules/brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
+		"node_modules/eslint/node_modules/minimatch": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^1.1.7"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/esm-env": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz",
+			"integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA=="
+		},
+		"node_modules/espree": {
+			"version": "9.6.1",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+			"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+			"dev": true,
+			"dependencies": {
+				"acorn": "^8.9.0",
+				"acorn-jsx": "^5.3.2",
+				"eslint-visitor-keys": "^3.4.1"
+			},
+			"engines": {
+				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/esquery": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+			"integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+			"dev": true,
+			"dependencies": {
+				"estraverse": "^5.1.0"
+			},
+			"engines": {
+				"node": ">=0.10"
+			}
+		},
+		"node_modules/esrecurse": {
+			"version": "4.3.0",
+			"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+			"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+			"dev": true,
+			"dependencies": {
+				"estraverse": "^5.2.0"
+			},
+			"engines": {
+				"node": ">=4.0"
+			}
+		},
+		"node_modules/estraverse": {
+			"version": "5.3.0",
+			"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+			"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+			"dev": true,
+			"engines": {
+				"node": ">=4.0"
+			}
+		},
+		"node_modules/estree-walker": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+			"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+		},
+		"node_modules/esutils": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+			"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/eventemitter2": {
+			"version": "6.4.7",
+			"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz",
+			"integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==",
+			"dev": true
+		},
+		"node_modules/eventsource-parser": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
+			"integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
+			"engines": {
+				"node": ">=14.18"
+			}
+		},
+		"node_modules/execa": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
+			"integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==",
+			"dev": true,
+			"dependencies": {
+				"cross-spawn": "^7.0.0",
+				"get-stream": "^5.0.0",
+				"human-signals": "^1.1.1",
+				"is-stream": "^2.0.0",
+				"merge-stream": "^2.0.0",
+				"npm-run-path": "^4.0.0",
+				"onetime": "^5.1.0",
+				"signal-exit": "^3.0.2",
+				"strip-final-newline": "^2.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sindresorhus/execa?sponsor=1"
+			}
+		},
+		"node_modules/execa/node_modules/signal-exit": {
+			"version": "3.0.7",
+			"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+			"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+			"dev": true
+		},
+		"node_modules/executable": {
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz",
+			"integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==",
+			"dev": true,
+			"dependencies": {
+				"pify": "^2.2.0"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/extend": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+			"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+			"dev": true
+		},
+		"node_modules/extract-zip": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+			"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+			"dev": true,
+			"dependencies": {
+				"debug": "^4.1.1",
+				"get-stream": "^5.1.0",
+				"yauzl": "^2.10.0"
+			},
+			"bin": {
+				"extract-zip": "cli.js"
+			},
+			"engines": {
+				"node": ">= 10.17.0"
+			},
+			"optionalDependencies": {
+				"@types/yauzl": "^2.9.1"
+			}
+		},
+		"node_modules/extsprintf": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+			"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
+			"dev": true,
+			"engines": [
+				"node >=0.6.0"
+			]
+		},
+		"node_modules/fast-deep-equal": {
+			"version": "3.1.3",
+			"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+			"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+			"dev": true
+		},
+		"node_modules/fast-fifo": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+			"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
+			"dev": true
+		},
+		"node_modules/fast-glob": {
+			"version": "3.3.2",
+			"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+			"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+			"dev": true,
+			"dependencies": {
+				"@nodelib/fs.stat": "^2.0.2",
+				"@nodelib/fs.walk": "^1.2.3",
+				"glob-parent": "^5.1.2",
+				"merge2": "^1.3.0",
+				"micromatch": "^4.0.4"
+			},
+			"engines": {
+				"node": ">=8.6.0"
+			}
+		},
+		"node_modules/fast-glob/node_modules/glob-parent": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+			"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+			"dev": true,
+			"dependencies": {
+				"is-glob": "^4.0.1"
+			},
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/fast-json-stable-stringify": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+			"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+			"dev": true
+		},
+		"node_modules/fast-levenshtein": {
+			"version": "2.0.6",
+			"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+			"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+			"dev": true
+		},
+		"node_modules/fastq": {
+			"version": "1.17.1",
+			"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+			"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+			"dev": true,
+			"dependencies": {
+				"reusify": "^1.0.4"
+			}
+		},
+		"node_modules/fd-slicer": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+			"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+			"dev": true,
+			"dependencies": {
+				"pend": "~1.2.0"
+			}
+		},
+		"node_modules/figures": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+			"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+			"dev": true,
+			"dependencies": {
+				"escape-string-regexp": "^1.0.5"
+			},
+			"engines": {
+				"node": ">=8"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/figures/node_modules/escape-string-regexp": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+			"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.8.0"
+			}
+		},
+		"node_modules/file-entry-cache": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+			"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+			"dev": true,
+			"dependencies": {
+				"flat-cache": "^3.0.4"
+			},
+			"engines": {
+				"node": "^10.12.0 || >=12.0.0"
+			}
+		},
+		"node_modules/file-saver": {
+			"version": "2.0.5",
+			"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+			"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
+		},
+		"node_modules/fill-range": {
+			"version": "7.1.1",
+			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+			"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+			"dev": true,
+			"dependencies": {
+				"to-regex-range": "^5.0.1"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/find-up": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+			"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+			"dev": true,
+			"dependencies": {
+				"locate-path": "^6.0.0",
+				"path-exists": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/flat-cache": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+			"integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+			"dev": true,
+			"dependencies": {
+				"flatted": "^3.2.9",
+				"keyv": "^4.5.3",
+				"rimraf": "^3.0.2"
+			},
+			"engines": {
+				"node": "^10.12.0 || >=12.0.0"
+			}
+		},
+		"node_modules/flatbuffers": {
+			"version": "1.12.0",
+			"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz",
+			"integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ=="
+		},
+		"node_modules/flatted": {
+			"version": "3.3.1",
+			"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+			"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+			"dev": true
+		},
+		"node_modules/focus-trap": {
+			"version": "7.5.4",
+			"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz",
+			"integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==",
+			"dependencies": {
+				"tabbable": "^6.2.0"
+			}
+		},
+		"node_modules/foreground-child": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+			"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+			"dependencies": {
+				"cross-spawn": "^7.0.0",
+				"signal-exit": "^4.0.1"
+			},
+			"engines": {
+				"node": ">=14"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/forever-agent": {
+			"version": "0.6.1",
+			"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+			"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
+			"dev": true,
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/form-data": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+			"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+			"dev": true,
+			"dependencies": {
+				"asynckit": "^0.4.0",
+				"combined-stream": "^1.0.8",
+				"mime-types": "^2.1.12"
+			},
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/fraction.js": {
+			"version": "4.3.7",
+			"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+			"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+			"dev": true,
+			"engines": {
+				"node": "*"
+			},
+			"funding": {
+				"type": "patreon",
+				"url": "https://github.com/sponsors/rawify"
+			}
+		},
+		"node_modules/fs-extra": {
+			"version": "11.2.0",
+			"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
+			"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+			"dev": true,
+			"dependencies": {
+				"graceful-fs": "^4.2.0",
+				"jsonfile": "^6.0.1",
+				"universalify": "^2.0.0"
+			},
+			"engines": {
+				"node": ">=14.14"
+			}
+		},
+		"node_modules/fs-merger": {
+			"version": "3.2.1",
+			"resolved": "https://registry.npmjs.org/fs-merger/-/fs-merger-3.2.1.tgz",
+			"integrity": "sha512-AN6sX12liy0JE7C2evclwoo0aCG3PFulLjrTLsJpWh/2mM+DinhpSGqYLbHBBbIW1PLRNcFhJG8Axtz8mQW3ug==",
+			"dev": true,
+			"dependencies": {
+				"broccoli-node-api": "^1.7.0",
+				"broccoli-node-info": "^2.1.0",
+				"fs-extra": "^8.0.1",
+				"fs-tree-diff": "^2.0.1",
+				"walk-sync": "^2.2.0"
+			}
+		},
+		"node_modules/fs-merger/node_modules/fs-extra": {
+			"version": "8.1.0",
+			"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+			"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+			"dev": true,
+			"dependencies": {
+				"graceful-fs": "^4.2.0",
+				"jsonfile": "^4.0.0",
+				"universalify": "^0.1.0"
+			},
+			"engines": {
+				"node": ">=6 <7 || >=8"
+			}
+		},
+		"node_modules/fs-merger/node_modules/jsonfile": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+			"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+			"dev": true,
+			"optionalDependencies": {
+				"graceful-fs": "^4.1.6"
+			}
+		},
+		"node_modules/fs-merger/node_modules/universalify": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+			"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 4.0.0"
+			}
+		},
+		"node_modules/fs-mkdirp-stream": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz",
+			"integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==",
+			"dev": true,
+			"dependencies": {
+				"graceful-fs": "^4.2.8",
+				"streamx": "^2.12.0"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
+		"node_modules/fs-tree-diff": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/fs-tree-diff/-/fs-tree-diff-2.0.1.tgz",
+			"integrity": "sha512-x+CfAZ/lJHQqwlD64pYM5QxWjzWhSjroaVsr8PW831zOApL55qPibed0c+xebaLWVr2BnHFoHdrwOv8pzt8R5A==",
+			"dev": true,
+			"dependencies": {
+				"@types/symlink-or-copy": "^1.2.0",
+				"heimdalljs-logger": "^0.1.7",
+				"object-assign": "^4.1.0",
+				"path-posix": "^1.0.0",
+				"symlink-or-copy": "^1.1.8"
+			},
+			"engines": {
+				"node": "6.* || 8.* || >= 10.*"
+			}
+		},
+		"node_modules/fs.realpath": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+			"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+		},
+		"node_modules/fsevents": {
+			"version": "2.3.3",
+			"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+			"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+			"hasInstallScript": true,
+			"optional": true,
+			"os": [
+				"darwin"
+			],
+			"engines": {
+				"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+			}
+		},
+		"node_modules/function-bind": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+			"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/fuse.js": {
+			"version": "7.0.0",
+			"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
+			"integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==",
+			"engines": {
+				"node": ">=10"
+			}
+		},
+		"node_modules/gc-hook": {
+			"version": "0.3.1",
+			"resolved": "https://registry.npmjs.org/gc-hook/-/gc-hook-0.3.1.tgz",
+			"integrity": "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A=="
+		},
+		"node_modules/get-func-name": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+			"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+			"dev": true,
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/get-intrinsic": {
+			"version": "1.2.4",
+			"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+			"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+			"dev": true,
+			"dependencies": {
+				"es-errors": "^1.3.0",
+				"function-bind": "^1.1.2",
+				"has-proto": "^1.0.1",
+				"has-symbols": "^1.0.3",
+				"hasown": "^2.0.0"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/get-stream": {
+			"version": "5.2.0",
+			"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+			"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+			"dev": true,
+			"dependencies": {
+				"pump": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/getos": {
+			"version": "3.2.1",
+			"resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz",
+			"integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==",
+			"dev": true,
+			"dependencies": {
+				"async": "^3.2.0"
+			}
+		},
+		"node_modules/getpass": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+			"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
+			"dev": true,
+			"dependencies": {
+				"assert-plus": "^1.0.0"
+			}
+		},
+		"node_modules/glob": {
+			"version": "8.1.0",
+			"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+			"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+			"dependencies": {
+				"fs.realpath": "^1.0.0",
+				"inflight": "^1.0.4",
+				"inherits": "2",
+				"minimatch": "^5.0.1",
+				"once": "^1.3.0"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/glob-parent": {
+			"version": "6.0.2",
+			"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+			"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+			"dev": true,
+			"dependencies": {
+				"is-glob": "^4.0.3"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
+		"node_modules/glob-stream": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.0.tgz",
+			"integrity": "sha512-CdIUuwOkYNv9ZadR3jJvap8CMooKziQZ/QCSPhEb7zqfsEI5YnPmvca7IvbaVE3z58ZdUYD2JsU6AUWjL8WZJA==",
+			"dev": true,
+			"dependencies": {
+				"@gulpjs/to-absolute-glob": "^4.0.0",
+				"anymatch": "^3.1.3",
+				"fastq": "^1.13.0",
+				"glob-parent": "^6.0.2",
+				"is-glob": "^4.0.3",
+				"is-negated-glob": "^1.0.0",
+				"normalize-path": "^3.0.0",
+				"streamx": "^2.12.5"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
+		"node_modules/glob/node_modules/minimatch": {
+			"version": "5.1.6",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+			"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+			"dependencies": {
+				"brace-expansion": "^2.0.1"
+			},
+			"engines": {
+				"node": ">=10"
+			}
+		},
+		"node_modules/global-dirs": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz",
+			"integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==",
+			"dev": true,
+			"dependencies": {
+				"ini": "2.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/globals": {
+			"version": "13.24.0",
+			"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+			"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+			"dev": true,
+			"dependencies": {
+				"type-fest": "^0.20.2"
+			},
+			"engines": {
+				"node": ">=8"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/globalyzer": {
+			"version": "0.1.0",
+			"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
+			"integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="
+		},
+		"node_modules/globby": {
+			"version": "11.1.0",
+			"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+			"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+			"dev": true,
+			"dependencies": {
+				"array-union": "^2.1.0",
+				"dir-glob": "^3.0.1",
+				"fast-glob": "^3.2.9",
+				"ignore": "^5.2.0",
+				"merge2": "^1.4.1",
+				"slash": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/globrex": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
+			"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
+		},
+		"node_modules/gopd": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+			"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+			"dev": true,
+			"dependencies": {
+				"get-intrinsic": "^1.1.3"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/graceful-fs": {
+			"version": "4.2.11",
+			"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+			"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+			"dev": true
+		},
+		"node_modules/graphemer": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+			"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+			"dev": true
+		},
+		"node_modules/guid-typescript": {
+			"version": "1.0.9",
+			"resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
+			"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ=="
+		},
+		"node_modules/gulp-sort": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/gulp-sort/-/gulp-sort-2.0.0.tgz",
+			"integrity": "sha512-MyTel3FXOdh1qhw1yKhpimQrAmur9q1X0ZigLmCOxouQD+BD3za9/89O+HfbgBQvvh4igEbp0/PUWO+VqGYG1g==",
+			"dev": true,
+			"dependencies": {
+				"through2": "^2.0.1"
+			}
+		},
+		"node_modules/has-flag": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+			"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/has-property-descriptors": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+			"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+			"dev": true,
+			"dependencies": {
+				"es-define-property": "^1.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/has-proto": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+			"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+			"dev": true,
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/has-symbols": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+			"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+			"dev": true,
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/hasown": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+			"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+			"dependencies": {
+				"function-bind": "^1.1.2"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/heimdalljs": {
+			"version": "0.2.6",
+			"resolved": "https://registry.npmjs.org/heimdalljs/-/heimdalljs-0.2.6.tgz",
+			"integrity": "sha512-o9bd30+5vLBvBtzCPwwGqpry2+n0Hi6H1+qwt6y+0kwRHGGF8TFIhJPmnuM0xO97zaKrDZMwO/V56fAnn8m/tA==",
+			"dev": true,
+			"dependencies": {
+				"rsvp": "~3.2.1"
+			}
+		},
+		"node_modules/heimdalljs-logger": {
+			"version": "0.1.10",
+			"resolved": "https://registry.npmjs.org/heimdalljs-logger/-/heimdalljs-logger-0.1.10.tgz",
+			"integrity": "sha512-pO++cJbhIufVI/fmB/u2Yty3KJD0TqNPecehFae0/eps0hkZ3b4Zc/PezUMOpYuHFQbA7FxHZxa305EhmjLj4g==",
+			"dev": true,
+			"dependencies": {
+				"debug": "^2.2.0",
+				"heimdalljs": "^0.2.6"
+			}
+		},
+		"node_modules/heimdalljs-logger/node_modules/debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dev": true,
+			"dependencies": {
+				"ms": "2.0.0"
+			}
+		},
+		"node_modules/heimdalljs-logger/node_modules/ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+			"dev": true
+		},
+		"node_modules/heimdalljs/node_modules/rsvp": {
+			"version": "3.2.1",
+			"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.2.1.tgz",
+			"integrity": "sha512-Rf4YVNYpKjZ6ASAmibcwTNciQ5Co5Ztq6iZPEykHpkoflnD/K5ryE/rHehFsTm4NJj8nKDhbi3eKBWGogmNnkg==",
+			"dev": true
+		},
+		"node_modules/highlight.js": {
+			"version": "11.9.0",
+			"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
+			"integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==",
+			"engines": {
+				"node": ">=12.0.0"
+			}
+		},
+		"node_modules/html-escaper": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
+			"integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="
+		},
+		"node_modules/htmlparser2": {
+			"version": "8.0.2",
+			"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
+			"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+			"dev": true,
+			"funding": [
+				"https://github.com/fb55/htmlparser2?sponsor=1",
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/fb55"
+				}
+			],
+			"dependencies": {
+				"domelementtype": "^2.3.0",
+				"domhandler": "^5.0.3",
+				"domutils": "^3.0.1",
+				"entities": "^4.4.0"
+			}
+		},
+		"node_modules/http-signature": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz",
+			"integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==",
+			"dev": true,
+			"dependencies": {
+				"assert-plus": "^1.0.0",
+				"jsprim": "^2.0.2",
+				"sshpk": "^1.18.0"
+			},
+			"engines": {
+				"node": ">=0.10"
+			}
+		},
+		"node_modules/human-signals": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
+			"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==",
+			"dev": true,
+			"engines": {
+				"node": ">=8.12.0"
+			}
+		},
+		"node_modules/i18next": {
+			"version": "23.10.1",
+			"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.10.1.tgz",
+			"integrity": "sha512-NDiIzFbcs3O9PXpfhkjyf7WdqFn5Vq6mhzhtkXzj51aOcNuPNcTwuYNuXCpHsanZGHlHKL35G7huoFeVic1hng==",
+			"funding": [
+				{
+					"type": "individual",
+					"url": "https://locize.com"
+				},
+				{
+					"type": "individual",
+					"url": "https://locize.com/i18next.html"
+				},
+				{
+					"type": "individual",
+					"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+				}
+			],
+			"dependencies": {
+				"@babel/runtime": "^7.23.2"
+			}
+		},
+		"node_modules/i18next-browser-languagedetector": {
+			"version": "7.2.0",
+			"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz",
+			"integrity": "sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==",
+			"dependencies": {
+				"@babel/runtime": "^7.23.2"
+			}
+		},
+		"node_modules/i18next-parser": {
+			"version": "9.0.1",
+			"resolved": "https://registry.npmjs.org/i18next-parser/-/i18next-parser-9.0.1.tgz",
+			"integrity": "sha512-/Pr93/yEBdwsMKRsk4Zn63K368ALhzh8BRVrM6JNGOHy86ZKpiNJI6m8l1S/4T4Ofy1J4dlwkD7N98M70GP4aA==",
+			"dev": true,
+			"dependencies": {
+				"@babel/runtime": "^7.23.2",
+				"broccoli-plugin": "^4.0.7",
+				"cheerio": "^1.0.0-rc.2",
+				"colors": "1.4.0",
+				"commander": "~12.1.0",
+				"eol": "^0.9.1",
+				"esbuild": "^0.20.1",
+				"fs-extra": "^11.1.0",
+				"gulp-sort": "^2.0.0",
+				"i18next": "^23.5.1",
+				"js-yaml": "4.1.0",
+				"lilconfig": "^3.0.0",
+				"rsvp": "^4.8.2",
+				"sort-keys": "^5.0.0",
+				"typescript": "^5.0.4",
+				"vinyl": "~3.0.0",
+				"vinyl-fs": "^4.0.0"
+			},
+			"bin": {
+				"i18next": "bin/cli.js"
+			},
+			"engines": {
+				"node": ">=18.0.0 || >=20.0.0 || >=22.0.0",
+				"npm": ">=6",
+				"yarn": ">=1"
+			}
+		},
+		"node_modules/i18next-resources-to-backend": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.0.tgz",
+			"integrity": "sha512-8f1l03s+QxDmCfpSXCh9V+AFcxAwIp0UaroWuyOx+hmmv8484GcELHs+lnu54FrNij8cDBEXvEwhzZoXsKcVpg==",
+			"dependencies": {
+				"@babel/runtime": "^7.23.2"
+			}
+		},
+		"node_modules/iconv-lite": {
+			"version": "0.6.3",
+			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+			"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+			"dependencies": {
+				"safer-buffer": ">= 2.1.2 < 3.0.0"
+			},
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/idb": {
+			"version": "7.1.1",
+			"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
+			"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="
+		},
+		"node_modules/ieee754": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+			"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			]
+		},
+		"node_modules/ignore": {
+			"version": "5.3.1",
+			"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+			"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
+			"dev": true,
+			"engines": {
+				"node": ">= 4"
+			}
+		},
+		"node_modules/import-fresh": {
+			"version": "3.3.0",
+			"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+			"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+			"dev": true,
+			"dependencies": {
+				"parent-module": "^1.0.0",
+				"resolve-from": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/import-meta-resolve": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
+			"integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==",
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/wooorm"
+			}
+		},
+		"node_modules/imurmurhash": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+			"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.8.19"
+			}
+		},
+		"node_modules/indent-string": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+			"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/inflight": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+			"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+			"dependencies": {
+				"once": "^1.3.0",
+				"wrappy": "1"
+			}
+		},
+		"node_modules/inherits": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+			"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+		},
+		"node_modules/ini": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
+			"integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
+			"dev": true,
+			"engines": {
+				"node": ">=10"
+			}
+		},
+		"node_modules/internmap": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+			"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/is-arrayish": {
+			"version": "0.3.2",
+			"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+			"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+		},
+		"node_modules/is-binary-path": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+			"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+			"dev": true,
+			"dependencies": {
+				"binary-extensions": "^2.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/is-builtin-module": {
+			"version": "3.2.1",
+			"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
+			"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
+			"dependencies": {
+				"builtin-modules": "^3.3.0"
+			},
+			"engines": {
+				"node": ">=6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/is-ci": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
+			"integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
+			"dev": true,
+			"dependencies": {
+				"ci-info": "^3.2.0"
+			},
+			"bin": {
+				"is-ci": "bin.js"
+			}
+		},
+		"node_modules/is-core-module": {
+			"version": "2.13.1",
+			"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+			"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
+			"dependencies": {
+				"hasown": "^2.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/is-extglob": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+			"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/is-fullwidth-code-point": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+			"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/is-glob": {
+			"version": "4.0.3",
+			"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+			"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+			"dev": true,
+			"dependencies": {
+				"is-extglob": "^2.1.1"
+			},
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/is-installed-globally": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
+			"integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==",
+			"dev": true,
+			"dependencies": {
+				"global-dirs": "^3.0.0",
+				"is-path-inside": "^3.0.2"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/is-module": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+			"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="
+		},
+		"node_modules/is-negated-glob": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
+			"integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/is-number": {
+			"version": "7.0.0",
+			"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+			"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.12.0"
+			}
+		},
+		"node_modules/is-path-inside": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+			"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/is-plain-obj": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+			"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+			"dev": true,
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/is-reference": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+			"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+			"dependencies": {
+				"@types/estree": "*"
+			}
+		},
+		"node_modules/is-stream": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+			"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/is-typedarray": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+			"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
+			"dev": true
+		},
+		"node_modules/is-unicode-supported": {
+			"version": "0.1.0",
+			"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+			"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+			"dev": true,
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/is-valid-glob": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz",
+			"integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/isarray": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+			"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+			"dev": true
+		},
+		"node_modules/isexe": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+			"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+		},
+		"node_modules/isstream": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+			"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
+			"dev": true
+		},
+		"node_modules/jackspeak": {
+			"version": "2.3.6",
+			"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
+			"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
+			"dev": true,
+			"dependencies": {
+				"@isaacs/cliui": "^8.0.2"
+			},
+			"engines": {
+				"node": ">=14"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			},
+			"optionalDependencies": {
+				"@pkgjs/parseargs": "^0.11.0"
+			}
+		},
+		"node_modules/jiti": {
+			"version": "1.21.0",
+			"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
+			"integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
+			"dev": true,
+			"bin": {
+				"jiti": "bin/jiti.js"
+			}
+		},
+		"node_modules/js-sha256": {
+			"version": "0.10.1",
+			"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.10.1.tgz",
+			"integrity": "sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw=="
+		},
+		"node_modules/js-tokens": {
+			"version": "9.0.0",
+			"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz",
+			"integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==",
+			"dev": true
+		},
+		"node_modules/js-yaml": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+			"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+			"dev": true,
+			"dependencies": {
+				"argparse": "^2.0.1"
+			},
+			"bin": {
+				"js-yaml": "bin/js-yaml.js"
+			}
+		},
+		"node_modules/jsbn": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+			"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
+			"dev": true
+		},
+		"node_modules/json-buffer": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+			"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+			"dev": true
+		},
+		"node_modules/json-schema": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+			"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
+			"dev": true
+		},
+		"node_modules/json-schema-traverse": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+			"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+			"dev": true
+		},
+		"node_modules/json-stable-stringify-without-jsonify": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+			"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+			"dev": true
+		},
+		"node_modules/json-stringify-safe": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+			"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+			"dev": true
+		},
+		"node_modules/jsonfile": {
+			"version": "6.1.0",
+			"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+			"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+			"dev": true,
+			"dependencies": {
+				"universalify": "^2.0.0"
+			},
+			"optionalDependencies": {
+				"graceful-fs": "^4.1.6"
+			}
+		},
+		"node_modules/jsprim": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
+			"integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==",
+			"dev": true,
+			"engines": [
+				"node >=0.6.0"
+			],
+			"dependencies": {
+				"assert-plus": "1.0.0",
+				"extsprintf": "1.3.0",
+				"json-schema": "0.4.0",
+				"verror": "1.10.0"
+			}
+		},
+		"node_modules/katex": {
+			"version": "0.16.10",
+			"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz",
+			"integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==",
+			"funding": [
+				"https://opencollective.com/katex",
+				"https://github.com/sponsors/katex"
+			],
+			"dependencies": {
+				"commander": "^8.3.0"
+			},
+			"bin": {
+				"katex": "cli.js"
+			}
+		},
+		"node_modules/katex/node_modules/commander": {
+			"version": "8.3.0",
+			"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+			"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+			"engines": {
+				"node": ">= 12"
+			}
+		},
+		"node_modules/keyv": {
+			"version": "4.5.4",
+			"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+			"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+			"dev": true,
+			"dependencies": {
+				"json-buffer": "3.0.1"
+			}
+		},
+		"node_modules/khroma": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
+			"integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
+		},
+		"node_modules/kleur": {
+			"version": "4.1.5",
+			"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+			"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/known-css-properties": {
+			"version": "0.34.0",
+			"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz",
+			"integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==",
+			"dev": true
+		},
+		"node_modules/layout-base": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
+			"integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="
+		},
+		"node_modules/lazy-ass": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
+			"integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==",
+			"dev": true,
+			"engines": {
+				"node": "> 0.8"
+			}
+		},
+		"node_modules/lead": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz",
+			"integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==",
+			"dev": true,
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
+		"node_modules/levn": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+			"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+			"dev": true,
+			"dependencies": {
+				"prelude-ls": "^1.2.1",
+				"type-check": "~0.4.0"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/lilconfig": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
+			"integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=14"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/antonk52"
+			}
+		},
+		"node_modules/lines-and-columns": {
+			"version": "1.2.4",
+			"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+			"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+			"dev": true
+		},
+		"node_modules/linkify-it": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+			"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+			"dependencies": {
+				"uc.micro": "^2.0.0"
+			}
+		},
+		"node_modules/listr2": {
+			"version": "3.14.0",
+			"resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz",
+			"integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==",
+			"dev": true,
+			"dependencies": {
+				"cli-truncate": "^2.1.0",
+				"colorette": "^2.0.16",
+				"log-update": "^4.0.0",
+				"p-map": "^4.0.0",
+				"rfdc": "^1.3.0",
+				"rxjs": "^7.5.1",
+				"through": "^2.3.8",
+				"wrap-ansi": "^7.0.0"
+			},
+			"engines": {
+				"node": ">=10.0.0"
+			},
+			"peerDependencies": {
+				"enquirer": ">= 2.3.0 < 3"
+			},
+			"peerDependenciesMeta": {
+				"enquirer": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/listr2/node_modules/emoji-regex": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+			"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+			"dev": true
+		},
+		"node_modules/listr2/node_modules/string-width": {
+			"version": "4.2.3",
+			"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+			"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+			"dev": true,
+			"dependencies": {
+				"emoji-regex": "^8.0.0",
+				"is-fullwidth-code-point": "^3.0.0",
+				"strip-ansi": "^6.0.1"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/listr2/node_modules/wrap-ansi": {
+			"version": "7.0.0",
+			"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+			"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+			"dev": true,
+			"dependencies": {
+				"ansi-styles": "^4.0.0",
+				"string-width": "^4.1.0",
+				"strip-ansi": "^6.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+			}
+		},
+		"node_modules/local-pkg": {
+			"version": "0.5.0",
+			"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
+			"integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==",
+			"dev": true,
+			"dependencies": {
+				"mlly": "^1.4.2",
+				"pkg-types": "^1.0.3"
+			},
+			"engines": {
+				"node": ">=14"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/antfu"
+			}
+		},
+		"node_modules/locate-character": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
+			"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
+		},
+		"node_modules/locate-path": {
+			"version": "6.0.0",
+			"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+			"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+			"dev": true,
+			"dependencies": {
+				"p-locate": "^5.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/lodash": {
+			"version": "4.17.21",
+			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+			"dev": true
+		},
+		"node_modules/lodash-es": {
+			"version": "4.17.21",
+			"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+			"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+		},
+		"node_modules/lodash.castarray": {
+			"version": "4.4.0",
+			"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
+			"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
+			"dev": true
+		},
+		"node_modules/lodash.isplainobject": {
+			"version": "4.0.6",
+			"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+			"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+			"dev": true
+		},
+		"node_modules/lodash.merge": {
+			"version": "4.6.2",
+			"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+			"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+			"dev": true
+		},
+		"node_modules/lodash.once": {
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+			"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+			"dev": true
+		},
+		"node_modules/log-symbols": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+			"integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+			"dev": true,
+			"dependencies": {
+				"chalk": "^4.1.0",
+				"is-unicode-supported": "^0.1.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/log-update": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz",
+			"integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==",
+			"dev": true,
+			"dependencies": {
+				"ansi-escapes": "^4.3.0",
+				"cli-cursor": "^3.1.0",
+				"slice-ansi": "^4.0.0",
+				"wrap-ansi": "^6.2.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/log-update/node_modules/emoji-regex": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+			"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+			"dev": true
+		},
+		"node_modules/log-update/node_modules/slice-ansi": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+			"integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+			"dev": true,
+			"dependencies": {
+				"ansi-styles": "^4.0.0",
+				"astral-regex": "^2.0.0",
+				"is-fullwidth-code-point": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/slice-ansi?sponsor=1"
+			}
+		},
+		"node_modules/log-update/node_modules/string-width": {
+			"version": "4.2.3",
+			"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+			"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+			"dev": true,
+			"dependencies": {
+				"emoji-regex": "^8.0.0",
+				"is-fullwidth-code-point": "^3.0.0",
+				"strip-ansi": "^6.0.1"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/log-update/node_modules/wrap-ansi": {
+			"version": "6.2.0",
+			"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+			"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+			"dev": true,
+			"dependencies": {
+				"ansi-styles": "^4.0.0",
+				"string-width": "^4.1.0",
+				"strip-ansi": "^6.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/long": {
+			"version": "5.2.3",
+			"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
+			"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
+		},
+		"node_modules/loupe": {
+			"version": "2.3.7",
+			"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+			"integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+			"dev": true,
+			"dependencies": {
+				"get-func-name": "^2.0.1"
+			}
+		},
+		"node_modules/magic-string": {
+			"version": "0.30.11",
+			"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
+			"integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
+			"dependencies": {
+				"@jridgewell/sourcemap-codec": "^1.5.0"
+			}
+		},
+		"node_modules/markdown-it": {
+			"version": "14.1.0",
+			"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
+			"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+			"dependencies": {
+				"argparse": "^2.0.1",
+				"entities": "^4.4.0",
+				"linkify-it": "^5.0.0",
+				"mdurl": "^2.0.0",
+				"punycode.js": "^2.3.1",
+				"uc.micro": "^2.1.0"
+			},
+			"bin": {
+				"markdown-it": "bin/markdown-it.mjs"
+			}
+		},
+		"node_modules/marked": {
+			"version": "9.1.6",
+			"resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz",
+			"integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==",
+			"bin": {
+				"marked": "bin/marked.js"
+			},
+			"engines": {
+				"node": ">= 16"
+			}
+		},
+		"node_modules/matcher-collection": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz",
+			"integrity": "sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==",
+			"dev": true,
+			"dependencies": {
+				"@types/minimatch": "^3.0.3",
+				"minimatch": "^3.0.2"
+			},
+			"engines": {
+				"node": "6.* || 8.* || >= 10.*"
+			}
+		},
+		"node_modules/matcher-collection/node_modules/brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
+		"node_modules/matcher-collection/node_modules/minimatch": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^1.1.7"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/mdast-util-from-markdown": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz",
+			"integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==",
+			"dependencies": {
+				"@types/mdast": "^3.0.0",
+				"@types/unist": "^2.0.0",
+				"decode-named-character-reference": "^1.0.0",
+				"mdast-util-to-string": "^3.1.0",
+				"micromark": "^3.0.0",
+				"micromark-util-decode-numeric-character-reference": "^1.0.0",
+				"micromark-util-decode-string": "^1.0.0",
+				"micromark-util-normalize-identifier": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0",
+				"micromark-util-types": "^1.0.0",
+				"unist-util-stringify-position": "^3.0.0",
+				"uvu": "^0.5.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/unified"
+			}
+		},
+		"node_modules/mdast-util-to-string": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz",
+			"integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==",
+			"dependencies": {
+				"@types/mdast": "^3.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/unified"
+			}
+		},
+		"node_modules/mdn-data": {
+			"version": "2.0.30",
+			"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+			"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
+		},
+		"node_modules/mdurl": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+			"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
+		},
+		"node_modules/merge-stream": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+			"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+			"dev": true
+		},
+		"node_modules/merge2": {
+			"version": "1.4.1",
+			"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+			"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 8"
+			}
+		},
+		"node_modules/mermaid": {
+			"version": "10.9.3",
+			"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.9.3.tgz",
+			"integrity": "sha512-V80X1isSEvAewIL3xhmz/rVmc27CVljcsbWxkxlWJWY/1kQa4XOABqpDl2qQLGKzpKm6WbTfUEKImBlUfFYArw==",
+			"dependencies": {
+				"@braintree/sanitize-url": "^6.0.1",
+				"@types/d3-scale": "^4.0.3",
+				"@types/d3-scale-chromatic": "^3.0.0",
+				"cytoscape": "^3.28.1",
+				"cytoscape-cose-bilkent": "^4.1.0",
+				"d3": "^7.4.0",
+				"d3-sankey": "^0.12.3",
+				"dagre-d3-es": "7.0.10",
+				"dayjs": "^1.11.7",
+				"dompurify": "^3.0.5 <3.1.7",
+				"elkjs": "^0.9.0",
+				"katex": "^0.16.9",
+				"khroma": "^2.0.0",
+				"lodash-es": "^4.17.21",
+				"mdast-util-from-markdown": "^1.3.0",
+				"non-layered-tidy-tree-layout": "^2.0.2",
+				"stylis": "^4.1.3",
+				"ts-dedent": "^2.2.0",
+				"uuid": "^9.0.0",
+				"web-worker": "^1.2.0"
+			}
+		},
+		"node_modules/micromark": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz",
+			"integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"@types/debug": "^4.0.0",
+				"debug": "^4.0.0",
+				"decode-named-character-reference": "^1.0.0",
+				"micromark-core-commonmark": "^1.0.1",
+				"micromark-factory-space": "^1.0.0",
+				"micromark-util-character": "^1.0.0",
+				"micromark-util-chunked": "^1.0.0",
+				"micromark-util-combine-extensions": "^1.0.0",
+				"micromark-util-decode-numeric-character-reference": "^1.0.0",
+				"micromark-util-encode": "^1.0.0",
+				"micromark-util-normalize-identifier": "^1.0.0",
+				"micromark-util-resolve-all": "^1.0.0",
+				"micromark-util-sanitize-uri": "^1.0.0",
+				"micromark-util-subtokenize": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0",
+				"micromark-util-types": "^1.0.1",
+				"uvu": "^0.5.0"
+			}
+		},
+		"node_modules/micromark-core-commonmark": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz",
+			"integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"decode-named-character-reference": "^1.0.0",
+				"micromark-factory-destination": "^1.0.0",
+				"micromark-factory-label": "^1.0.0",
+				"micromark-factory-space": "^1.0.0",
+				"micromark-factory-title": "^1.0.0",
+				"micromark-factory-whitespace": "^1.0.0",
+				"micromark-util-character": "^1.0.0",
+				"micromark-util-chunked": "^1.0.0",
+				"micromark-util-classify-character": "^1.0.0",
+				"micromark-util-html-tag-name": "^1.0.0",
+				"micromark-util-normalize-identifier": "^1.0.0",
+				"micromark-util-resolve-all": "^1.0.0",
+				"micromark-util-subtokenize": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0",
+				"micromark-util-types": "^1.0.1",
+				"uvu": "^0.5.0"
+			}
+		},
+		"node_modules/micromark-factory-destination": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz",
+			"integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-character": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0",
+				"micromark-util-types": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-factory-label": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz",
+			"integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-character": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0",
+				"micromark-util-types": "^1.0.0",
+				"uvu": "^0.5.0"
+			}
+		},
+		"node_modules/micromark-factory-space": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz",
+			"integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-character": "^1.0.0",
+				"micromark-util-types": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-factory-title": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz",
+			"integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-factory-space": "^1.0.0",
+				"micromark-util-character": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0",
+				"micromark-util-types": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-factory-whitespace": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz",
+			"integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-factory-space": "^1.0.0",
+				"micromark-util-character": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0",
+				"micromark-util-types": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-util-character": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz",
+			"integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-symbol": "^1.0.0",
+				"micromark-util-types": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-util-chunked": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz",
+			"integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-symbol": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-util-classify-character": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz",
+			"integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-character": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0",
+				"micromark-util-types": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-util-combine-extensions": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz",
+			"integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-chunked": "^1.0.0",
+				"micromark-util-types": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-util-decode-numeric-character-reference": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz",
+			"integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-symbol": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-util-decode-string": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz",
+			"integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"decode-named-character-reference": "^1.0.0",
+				"micromark-util-character": "^1.0.0",
+				"micromark-util-decode-numeric-character-reference": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-util-encode": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz",
+			"integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			]
+		},
+		"node_modules/micromark-util-html-tag-name": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz",
+			"integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			]
+		},
+		"node_modules/micromark-util-normalize-identifier": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz",
+			"integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-symbol": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-util-resolve-all": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz",
+			"integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-types": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-util-sanitize-uri": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz",
+			"integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-character": "^1.0.0",
+				"micromark-util-encode": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0"
+			}
+		},
+		"node_modules/micromark-util-subtokenize": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz",
+			"integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			],
+			"dependencies": {
+				"micromark-util-chunked": "^1.0.0",
+				"micromark-util-symbol": "^1.0.0",
+				"micromark-util-types": "^1.0.0",
+				"uvu": "^0.5.0"
+			}
+		},
+		"node_modules/micromark-util-symbol": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz",
+			"integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			]
+		},
+		"node_modules/micromark-util-types": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz",
+			"integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==",
+			"funding": [
+				{
+					"type": "GitHub Sponsors",
+					"url": "https://github.com/sponsors/unifiedjs"
+				},
+				{
+					"type": "OpenCollective",
+					"url": "https://opencollective.com/unified"
+				}
+			]
+		},
+		"node_modules/micromatch": {
+			"version": "4.0.8",
+			"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+			"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+			"dev": true,
+			"dependencies": {
+				"braces": "^3.0.3",
+				"picomatch": "^2.3.1"
+			},
+			"engines": {
+				"node": ">=8.6"
+			}
+		},
+		"node_modules/mime-db": {
+			"version": "1.52.0",
+			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+			"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/mime-types": {
+			"version": "2.1.35",
+			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+			"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+			"dev": true,
+			"dependencies": {
+				"mime-db": "1.52.0"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/mimic-fn": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+			"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/min-indent": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+			"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+			"dev": true,
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/minimatch": {
+			"version": "9.0.3",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+			"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^2.0.1"
+			},
+			"engines": {
+				"node": ">=16 || 14 >=14.17"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/minimist": {
+			"version": "1.2.8",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+			"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+			"dev": true,
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/minipass": {
+			"version": "7.1.2",
+			"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+			"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+			"engines": {
+				"node": ">=16 || 14 >=14.17"
+			}
+		},
+		"node_modules/minizlib": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz",
+			"integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==",
+			"dependencies": {
+				"minipass": "^7.0.4",
+				"rimraf": "^5.0.5"
+			},
+			"engines": {
+				"node": ">= 18"
+			}
+		},
+		"node_modules/minizlib/node_modules/glob": {
+			"version": "10.4.5",
+			"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+			"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+			"dependencies": {
+				"foreground-child": "^3.1.0",
+				"jackspeak": "^3.1.2",
+				"minimatch": "^9.0.4",
+				"minipass": "^7.1.2",
+				"package-json-from-dist": "^1.0.0",
+				"path-scurry": "^1.11.1"
+			},
+			"bin": {
+				"glob": "dist/esm/bin.mjs"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/minizlib/node_modules/jackspeak": {
+			"version": "3.4.3",
+			"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+			"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+			"dependencies": {
+				"@isaacs/cliui": "^8.0.2"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			},
+			"optionalDependencies": {
+				"@pkgjs/parseargs": "^0.11.0"
+			}
+		},
+		"node_modules/minizlib/node_modules/minimatch": {
+			"version": "9.0.5",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+			"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+			"dependencies": {
+				"brace-expansion": "^2.0.1"
+			},
+			"engines": {
+				"node": ">=16 || 14 >=14.17"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/minizlib/node_modules/rimraf": {
+			"version": "5.0.10",
+			"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
+			"integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
+			"dependencies": {
+				"glob": "^10.3.7"
+			},
+			"bin": {
+				"rimraf": "dist/esm/bin.mjs"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/mkdirp": {
+			"version": "0.5.6",
+			"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+			"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+			"dev": true,
+			"dependencies": {
+				"minimist": "^1.2.6"
+			},
+			"bin": {
+				"mkdirp": "bin/cmd.js"
+			}
+		},
+		"node_modules/mktemp": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/mktemp/-/mktemp-0.4.0.tgz",
+			"integrity": "sha512-IXnMcJ6ZyTuhRmJSjzvHSRhlVPiN9Jwc6e59V0bEJ0ba6OBeX2L0E+mRN1QseeOF4mM+F1Rit6Nh7o+rl2Yn/A==",
+			"dev": true,
+			"engines": {
+				"node": ">0.9"
+			}
+		},
+		"node_modules/mlly": {
+			"version": "1.7.0",
+			"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz",
+			"integrity": "sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==",
+			"dev": true,
+			"dependencies": {
+				"acorn": "^8.11.3",
+				"pathe": "^1.1.2",
+				"pkg-types": "^1.1.0",
+				"ufo": "^1.5.3"
+			}
+		},
+		"node_modules/mri": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+			"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/mrmime": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
+			"integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
+			"engines": {
+				"node": ">=10"
+			}
+		},
+		"node_modules/ms": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+			"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+		},
+		"node_modules/mz": {
+			"version": "2.7.0",
+			"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+			"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+			"dev": true,
+			"dependencies": {
+				"any-promise": "^1.0.0",
+				"object-assign": "^4.0.1",
+				"thenify-all": "^1.0.0"
+			}
+		},
+		"node_modules/nanoid": {
+			"version": "5.0.6",
+			"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.6.tgz",
+			"integrity": "sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==",
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			],
+			"bin": {
+				"nanoid": "bin/nanoid.js"
+			},
+			"engines": {
+				"node": "^18 || >=20"
+			}
+		},
+		"node_modules/natural-compare": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+			"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+			"dev": true
+		},
+		"node_modules/ngraph.events": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.2.tgz",
+			"integrity": "sha512-JsUbEOzANskax+WSYiAPETemLWYXmixuPAlmZmhIbIj6FH/WDgEGCGnRwUQBK0GjOnVm8Ui+e5IJ+5VZ4e32eQ=="
+		},
+		"node_modules/node-releases": {
+			"version": "2.0.14",
+			"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+			"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
+			"dev": true
+		},
+		"node_modules/non-layered-tidy-tree-layout": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
+			"integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="
+		},
+		"node_modules/normalize-path": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+			"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/normalize-range": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+			"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/now-and-later": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz",
+			"integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==",
+			"dev": true,
+			"dependencies": {
+				"once": "^1.4.0"
+			},
+			"engines": {
+				"node": ">= 10.13.0"
+			}
+		},
+		"node_modules/npm-run-path": {
+			"version": "4.0.1",
+			"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+			"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+			"dev": true,
+			"dependencies": {
+				"path-key": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/nth-check": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+			"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+			"dev": true,
+			"dependencies": {
+				"boolbase": "^1.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/fb55/nth-check?sponsor=1"
+			}
+		},
+		"node_modules/object-assign": {
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+			"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/object-hash": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+			"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+			"dev": true,
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/object-inspect": {
+			"version": "1.13.2",
+			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
+			"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+			"dev": true,
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/once": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+			"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+			"dependencies": {
+				"wrappy": "1"
+			}
+		},
+		"node_modules/onetime": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+			"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+			"dev": true,
+			"dependencies": {
+				"mimic-fn": "^2.1.0"
+			},
+			"engines": {
+				"node": ">=6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/onnxruntime-common": {
+			"version": "1.19.2",
+			"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.19.2.tgz",
+			"integrity": "sha512-a4R7wYEVFbZBlp0BfhpbFWqe4opCor3KM+5Wm22Az3NGDcQMiU2hfG/0MfnBs+1ZrlSGmlgWeMcXQkDk1UFb8Q=="
+		},
+		"node_modules/onnxruntime-node": {
+			"version": "1.19.2",
+			"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.19.2.tgz",
+			"integrity": "sha512-9eHMP/HKbbeUcqte1JYzaaRC8JPn7ojWeCeoyShO86TOR97OCyIyAIOGX3V95ErjslVhJRXY8Em/caIUc0hm1Q==",
+			"hasInstallScript": true,
+			"os": [
+				"win32",
+				"darwin",
+				"linux"
+			],
+			"dependencies": {
+				"onnxruntime-common": "1.19.2",
+				"tar": "^7.0.1"
+			}
+		},
+		"node_modules/onnxruntime-web": {
+			"version": "1.20.0-dev.20241016-2b8fc5529b",
+			"resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.20.0-dev.20241016-2b8fc5529b.tgz",
+			"integrity": "sha512-1XovqtgqeEFtupuyzdDQo7Tqj4GRyNHzOoXjapCEo4rfH3JrXok5VtqucWfRXHPsOI5qoNxMQ9VE+drDIp6woQ==",
+			"dependencies": {
+				"flatbuffers": "^1.12.0",
+				"guid-typescript": "^1.0.9",
+				"long": "^5.2.3",
+				"onnxruntime-common": "1.20.0-dev.20241016-2b8fc5529b",
+				"platform": "^1.3.6",
+				"protobufjs": "^7.2.4"
+			}
+		},
+		"node_modules/onnxruntime-web/node_modules/onnxruntime-common": {
+			"version": "1.20.0-dev.20241016-2b8fc5529b",
+			"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.20.0-dev.20241016-2b8fc5529b.tgz",
+			"integrity": "sha512-KZK8b6zCYGZFjd4ANze0pqBnqnFTS3GIVeclQpa2qseDpXrCQJfkWBixRcrZShNhm3LpFOZ8qJYFC5/qsJK9WQ=="
+		},
+		"node_modules/optionator": {
+			"version": "0.9.3",
+			"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+			"integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+			"dev": true,
+			"dependencies": {
+				"@aashutoshrathi/word-wrap": "^1.2.3",
+				"deep-is": "^0.1.3",
+				"fast-levenshtein": "^2.0.6",
+				"levn": "^0.4.1",
+				"prelude-ls": "^1.2.1",
+				"type-check": "^0.4.0"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/orderedmap": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
+			"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="
+		},
+		"node_modules/ospath": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz",
+			"integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==",
+			"dev": true
+		},
+		"node_modules/p-limit": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+			"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+			"dev": true,
+			"dependencies": {
+				"yocto-queue": "^0.1.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/p-locate": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+			"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+			"dev": true,
+			"dependencies": {
+				"p-limit": "^3.0.2"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/p-map": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+			"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+			"dev": true,
+			"dependencies": {
+				"aggregate-error": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/package-json-from-dist": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+			"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
+		},
+		"node_modules/paneforge": {
+			"version": "0.0.6",
+			"resolved": "https://registry.npmjs.org/paneforge/-/paneforge-0.0.6.tgz",
+			"integrity": "sha512-jYeN/wdREihja5c6nK3S5jritDQ+EbCqC5NrDo97qCZzZ9GkmEcN5C0ZCjF4nmhBwkDKr6tLIgz4QUKWxLXjAw==",
+			"dependencies": {
+				"nanoid": "^5.0.4"
+			},
+			"peerDependencies": {
+				"svelte": "^4.0.0 || ^5.0.0-next.1"
+			}
+		},
+		"node_modules/panzoom": {
+			"version": "9.4.3",
+			"resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.3.tgz",
+			"integrity": "sha512-xaxCpElcRbQsUtIdwlrZA90P90+BHip4Vda2BC8MEb4tkI05PmR6cKECdqUCZ85ZvBHjpI9htJrZBxV5Gp/q/w==",
+			"dependencies": {
+				"amator": "^1.1.0",
+				"ngraph.events": "^1.2.2",
+				"wheel": "^1.0.0"
+			}
+		},
+		"node_modules/parent-module": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+			"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+			"dev": true,
+			"dependencies": {
+				"callsites": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/parse5": {
+			"version": "7.1.2",
+			"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
+			"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+			"dev": true,
+			"dependencies": {
+				"entities": "^4.4.0"
+			},
+			"funding": {
+				"url": "https://github.com/inikulin/parse5?sponsor=1"
+			}
+		},
+		"node_modules/parse5-htmlparser2-tree-adapter": {
+			"version": "7.0.0",
+			"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
+			"integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
+			"dev": true,
+			"dependencies": {
+				"domhandler": "^5.0.2",
+				"parse5": "^7.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/inikulin/parse5?sponsor=1"
+			}
+		},
+		"node_modules/path-exists": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+			"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/path-is-absolute": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+			"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/path-key": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+			"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/path-parse": {
+			"version": "1.0.7",
+			"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+			"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
+		},
+		"node_modules/path-posix": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz",
+			"integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==",
+			"dev": true
+		},
+		"node_modules/path-scurry": {
+			"version": "1.11.1",
+			"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+			"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+			"dependencies": {
+				"lru-cache": "^10.2.0",
+				"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+			},
+			"engines": {
+				"node": ">=16 || 14 >=14.18"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/path-scurry/node_modules/lru-cache": {
+			"version": "10.2.0",
+			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
+			"integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
+			"engines": {
+				"node": "14 || >=16.14"
+			}
+		},
+		"node_modules/path-type": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+			"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/pathe": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+			"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+			"dev": true
+		},
+		"node_modules/pathval": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+			"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+			"dev": true,
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/pend": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+			"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+			"dev": true
+		},
+		"node_modules/performance-now": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+			"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+			"dev": true
+		},
+		"node_modules/periscopic": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
+			"integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
+			"dependencies": {
+				"@types/estree": "^1.0.0",
+				"estree-walker": "^3.0.0",
+				"is-reference": "^3.0.0"
+			}
+		},
+		"node_modules/periscopic/node_modules/estree-walker": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+			"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+			"dependencies": {
+				"@types/estree": "^1.0.0"
+			}
+		},
+		"node_modules/periscopic/node_modules/is-reference": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
+			"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
+			"dependencies": {
+				"@types/estree": "*"
+			}
+		},
+		"node_modules/picocolors": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
+			"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
+		},
+		"node_modules/picomatch": {
+			"version": "2.3.1",
+			"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+			"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+			"engines": {
+				"node": ">=8.6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/jonschlinkert"
+			}
+		},
+		"node_modules/pify": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+			"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/pirates": {
+			"version": "4.0.6",
+			"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+			"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/pkg-types": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz",
+			"integrity": "sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==",
+			"dev": true,
+			"dependencies": {
+				"confbox": "^0.1.7",
+				"mlly": "^1.7.0",
+				"pathe": "^1.1.2"
+			}
+		},
+		"node_modules/plain-tag": {
+			"version": "0.1.3",
+			"resolved": "https://registry.npmjs.org/plain-tag/-/plain-tag-0.1.3.tgz",
+			"integrity": "sha512-yyVAOFKTAElc7KdLt2+UKGExNYwYb/Y/WE9i+1ezCQsJE8gbKSjewfpRqK2nQgZ4d4hhAAGgDCOcIZVilqE5UA=="
+		},
+		"node_modules/platform": {
+			"version": "1.3.6",
+			"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
+			"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="
+		},
+		"node_modules/polyscript": {
+			"version": "0.12.8",
+			"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.12.8.tgz",
+			"integrity": "sha512-kcG3W9jU/s1sYjWOTAa2jAh5D2jm3zJRi+glSTsC+lA3D1b/Sd67pEIGpyL9bWNKYSimqAx4se6jAhQjJZ7+jQ==",
+			"dependencies": {
+				"@ungap/structured-clone": "^1.2.0",
+				"@ungap/with-resolvers": "^0.1.0",
+				"@webreflection/fetch": "^0.1.5",
+				"basic-devtools": "^0.1.6",
+				"codedent": "^0.1.2",
+				"coincident": "^1.2.3",
+				"gc-hook": "^0.3.1",
+				"html-escaper": "^3.0.3",
+				"proxy-target": "^3.0.2",
+				"sticky-module": "^0.1.1",
+				"to-json-callback": "^0.1.1"
+			}
+		},
+		"node_modules/postcss": {
+			"version": "8.4.47",
+			"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
+			"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+			"funding": [
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/postcss/"
+				},
+				{
+					"type": "tidelift",
+					"url": "https://tidelift.com/funding/github/npm/postcss"
+				},
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			],
+			"dependencies": {
+				"nanoid": "^3.3.7",
+				"picocolors": "^1.1.0",
+				"source-map-js": "^1.2.1"
+			},
+			"engines": {
+				"node": "^10 || ^12 || >=14"
+			}
+		},
+		"node_modules/postcss-import": {
+			"version": "15.1.0",
+			"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+			"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+			"dev": true,
+			"dependencies": {
+				"postcss-value-parser": "^4.0.0",
+				"read-cache": "^1.0.0",
+				"resolve": "^1.1.7"
+			},
+			"engines": {
+				"node": ">=14.0.0"
+			},
+			"peerDependencies": {
+				"postcss": "^8.0.0"
+			}
+		},
+		"node_modules/postcss-js": {
+			"version": "4.0.1",
+			"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+			"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+			"dev": true,
+			"dependencies": {
+				"camelcase-css": "^2.0.1"
+			},
+			"engines": {
+				"node": "^12 || ^14 || >= 16"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/postcss/"
+			},
+			"peerDependencies": {
+				"postcss": "^8.4.21"
+			}
+		},
+		"node_modules/postcss-load-config": {
+			"version": "3.1.4",
+			"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
+			"integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
+			"dev": true,
+			"dependencies": {
+				"lilconfig": "^2.0.5",
+				"yaml": "^1.10.2"
+			},
+			"engines": {
+				"node": ">= 10"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/postcss/"
+			},
+			"peerDependencies": {
+				"postcss": ">=8.0.9",
+				"ts-node": ">=9.0.0"
+			},
+			"peerDependenciesMeta": {
+				"postcss": {
+					"optional": true
+				},
+				"ts-node": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/postcss-load-config/node_modules/lilconfig": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+			"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=10"
+			}
+		},
+		"node_modules/postcss-nested": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
+			"integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
+			"dev": true,
+			"dependencies": {
+				"postcss-selector-parser": "^6.0.11"
+			},
+			"engines": {
+				"node": ">=12.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/postcss/"
+			},
+			"peerDependencies": {
+				"postcss": "^8.2.14"
+			}
+		},
+		"node_modules/postcss-nested/node_modules/postcss-selector-parser": {
+			"version": "6.0.16",
+			"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
+			"integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
+			"dev": true,
+			"dependencies": {
+				"cssesc": "^3.0.0",
+				"util-deprecate": "^1.0.2"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/postcss-safe-parser": {
+			"version": "6.0.0",
+			"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz",
+			"integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=12.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/postcss/"
+			},
+			"peerDependencies": {
+				"postcss": "^8.3.3"
+			}
+		},
+		"node_modules/postcss-scss": {
+			"version": "4.0.9",
+			"resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz",
+			"integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/postcss/"
+				},
+				{
+					"type": "tidelift",
+					"url": "https://tidelift.com/funding/github/npm/postcss-scss"
+				},
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			],
+			"engines": {
+				"node": ">=12.0"
+			},
+			"peerDependencies": {
+				"postcss": "^8.4.29"
+			}
+		},
+		"node_modules/postcss-selector-parser": {
+			"version": "6.0.10",
+			"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+			"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+			"dev": true,
+			"dependencies": {
+				"cssesc": "^3.0.0",
+				"util-deprecate": "^1.0.2"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/postcss-value-parser": {
+			"version": "4.2.0",
+			"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+			"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+			"dev": true
+		},
+		"node_modules/postcss/node_modules/nanoid": {
+			"version": "3.3.7",
+			"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+			"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			],
+			"bin": {
+				"nanoid": "bin/nanoid.cjs"
+			},
+			"engines": {
+				"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+			}
+		},
+		"node_modules/prelude-ls": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+			"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+			"dev": true,
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/prettier": {
+			"version": "3.3.3",
+			"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
+			"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
+			"dev": true,
+			"bin": {
+				"prettier": "bin/prettier.cjs"
+			},
+			"engines": {
+				"node": ">=14"
+			},
+			"funding": {
+				"url": "https://github.com/prettier/prettier?sponsor=1"
+			}
+		},
+		"node_modules/prettier-plugin-svelte": {
+			"version": "3.2.6",
+			"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.6.tgz",
+			"integrity": "sha512-Y1XWLw7vXUQQZmgv1JAEiLcErqUniAF2wO7QJsw8BVMvpLET2dI5WpEIEJx1r11iHVdSMzQxivyfrH9On9t2IQ==",
+			"dev": true,
+			"peerDependencies": {
+				"prettier": "^3.0.0",
+				"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
+			}
+		},
+		"node_modules/pretty-bytes": {
+			"version": "5.6.0",
+			"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
+			"integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/pretty-format": {
+			"version": "29.7.0",
+			"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+			"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+			"dev": true,
+			"dependencies": {
+				"@jest/schemas": "^29.6.3",
+				"ansi-styles": "^5.0.0",
+				"react-is": "^18.0.0"
+			},
+			"engines": {
+				"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+			}
+		},
+		"node_modules/pretty-format/node_modules/ansi-styles": {
+			"version": "5.2.0",
+			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+			"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+			"dev": true,
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/ansi-styles?sponsor=1"
+			}
+		},
+		"node_modules/process": {
+			"version": "0.11.10",
+			"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+			"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+			"dev": true,
+			"engines": {
+				"node": ">= 0.6.0"
+			}
+		},
+		"node_modules/process-nextick-args": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+			"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+			"dev": true
+		},
+		"node_modules/promise-map-series": {
+			"version": "0.3.0",
+			"resolved": "https://registry.npmjs.org/promise-map-series/-/promise-map-series-0.3.0.tgz",
+			"integrity": "sha512-3npG2NGhTc8BWBolLLf8l/92OxMGaRLbqvIh9wjCHhDXNvk4zsxaTaCpiCunW09qWPrN2zeNSNwRLVBrQQtutA==",
+			"dev": true,
+			"engines": {
+				"node": "10.* || >= 12.*"
+			}
+		},
+		"node_modules/prosemirror-commands": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.0.tgz",
+			"integrity": "sha512-xn1U/g36OqXn2tn5nGmvnnimAj/g1pUx2ypJJIe8WkVX83WyJVC5LTARaxZa2AtQRwntu9Jc5zXs9gL9svp/mg==",
+			"dependencies": {
+				"prosemirror-model": "^1.0.0",
+				"prosemirror-state": "^1.0.0",
+				"prosemirror-transform": "^1.0.0"
+			}
+		},
+		"node_modules/prosemirror-dropcursor": {
+			"version": "1.8.1",
+			"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz",
+			"integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==",
+			"dependencies": {
+				"prosemirror-state": "^1.0.0",
+				"prosemirror-transform": "^1.1.0",
+				"prosemirror-view": "^1.1.0"
+			}
+		},
+		"node_modules/prosemirror-example-setup": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmjs.org/prosemirror-example-setup/-/prosemirror-example-setup-1.2.3.tgz",
+			"integrity": "sha512-+hXZi8+xbFvYM465zZH3rdZ9w7EguVKmUYwYLZjIJIjPK+I0nPTwn8j0ByW2avchVczRwZmOJGNvehblyIerSQ==",
+			"dependencies": {
+				"prosemirror-commands": "^1.0.0",
+				"prosemirror-dropcursor": "^1.0.0",
+				"prosemirror-gapcursor": "^1.0.0",
+				"prosemirror-history": "^1.0.0",
+				"prosemirror-inputrules": "^1.0.0",
+				"prosemirror-keymap": "^1.0.0",
+				"prosemirror-menu": "^1.0.0",
+				"prosemirror-schema-list": "^1.0.0",
+				"prosemirror-state": "^1.0.0"
+			}
+		},
+		"node_modules/prosemirror-gapcursor": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz",
+			"integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==",
+			"dependencies": {
+				"prosemirror-keymap": "^1.0.0",
+				"prosemirror-model": "^1.0.0",
+				"prosemirror-state": "^1.0.0",
+				"prosemirror-view": "^1.0.0"
+			}
+		},
+		"node_modules/prosemirror-history": {
+			"version": "1.4.1",
+			"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz",
+			"integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==",
+			"dependencies": {
+				"prosemirror-state": "^1.2.2",
+				"prosemirror-transform": "^1.0.0",
+				"prosemirror-view": "^1.31.0",
+				"rope-sequence": "^1.3.0"
+			}
+		},
+		"node_modules/prosemirror-inputrules": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz",
+			"integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==",
+			"dependencies": {
+				"prosemirror-state": "^1.0.0",
+				"prosemirror-transform": "^1.0.0"
+			}
+		},
+		"node_modules/prosemirror-keymap": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz",
+			"integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==",
+			"dependencies": {
+				"prosemirror-state": "^1.0.0",
+				"w3c-keyname": "^2.2.0"
+			}
+		},
+		"node_modules/prosemirror-markdown": {
+			"version": "1.13.1",
+			"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz",
+			"integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==",
+			"dependencies": {
+				"@types/markdown-it": "^14.0.0",
+				"markdown-it": "^14.0.0",
+				"prosemirror-model": "^1.20.0"
+			}
+		},
+		"node_modules/prosemirror-menu": {
+			"version": "1.2.4",
+			"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz",
+			"integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==",
+			"dependencies": {
+				"crelt": "^1.0.0",
+				"prosemirror-commands": "^1.0.0",
+				"prosemirror-history": "^1.0.0",
+				"prosemirror-state": "^1.0.0"
+			}
+		},
+		"node_modules/prosemirror-model": {
+			"version": "1.23.0",
+			"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz",
+			"integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==",
+			"dependencies": {
+				"orderedmap": "^2.0.0"
+			}
+		},
+		"node_modules/prosemirror-schema-basic": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz",
+			"integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==",
+			"dependencies": {
+				"prosemirror-model": "^1.19.0"
+			}
+		},
+		"node_modules/prosemirror-schema-list": {
+			"version": "1.4.1",
+			"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz",
+			"integrity": "sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==",
+			"dependencies": {
+				"prosemirror-model": "^1.0.0",
+				"prosemirror-state": "^1.0.0",
+				"prosemirror-transform": "^1.7.3"
+			}
+		},
+		"node_modules/prosemirror-state": {
+			"version": "1.4.3",
+			"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz",
+			"integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==",
+			"dependencies": {
+				"prosemirror-model": "^1.0.0",
+				"prosemirror-transform": "^1.0.0",
+				"prosemirror-view": "^1.27.0"
+			}
+		},
+		"node_modules/prosemirror-transform": {
+			"version": "1.10.0",
+			"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.0.tgz",
+			"integrity": "sha512-9UOgFSgN6Gj2ekQH5CTDJ8Rp/fnKR2IkYfGdzzp5zQMFsS4zDllLVx/+jGcX86YlACpG7UR5fwAXiWzxqWtBTg==",
+			"dependencies": {
+				"prosemirror-model": "^1.21.0"
+			}
+		},
+		"node_modules/prosemirror-view": {
+			"version": "1.34.3",
+			"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.34.3.tgz",
+			"integrity": "sha512-mKZ54PrX19sSaQye+sef+YjBbNu2voNwLS1ivb6aD2IRmxRGW64HU9B644+7OfJStGLyxvOreKqEgfvXa91WIA==",
+			"dependencies": {
+				"prosemirror-model": "^1.20.0",
+				"prosemirror-state": "^1.0.0",
+				"prosemirror-transform": "^1.1.0"
+			}
+		},
+		"node_modules/protobufjs": {
+			"version": "7.4.0",
+			"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
+			"integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
+			"hasInstallScript": true,
+			"dependencies": {
+				"@protobufjs/aspromise": "^1.1.2",
+				"@protobufjs/base64": "^1.1.2",
+				"@protobufjs/codegen": "^2.0.4",
+				"@protobufjs/eventemitter": "^1.1.0",
+				"@protobufjs/fetch": "^1.1.0",
+				"@protobufjs/float": "^1.0.2",
+				"@protobufjs/inquire": "^1.1.0",
+				"@protobufjs/path": "^1.1.2",
+				"@protobufjs/pool": "^1.1.0",
+				"@protobufjs/utf8": "^1.1.0",
+				"@types/node": ">=13.7.0",
+				"long": "^5.0.0"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			}
+		},
+		"node_modules/proxy-from-env": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
+			"integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==",
+			"dev": true
+		},
+		"node_modules/proxy-target": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/proxy-target/-/proxy-target-3.0.2.tgz",
+			"integrity": "sha512-FFE1XNwXX/FNC3/P8HiKaJSy/Qk68RitG/QEcLy/bVnTAPlgTAWPZKh0pARLAnpfXQPKyalBhk009NRTgsk8vQ=="
+		},
+		"node_modules/psl": {
+			"version": "1.9.0",
+			"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+			"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
+			"dev": true
+		},
+		"node_modules/pump": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+			"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+			"dev": true,
+			"dependencies": {
+				"end-of-stream": "^1.1.0",
+				"once": "^1.3.1"
+			}
+		},
+		"node_modules/punycode": {
+			"version": "2.3.1",
+			"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+			"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/punycode.js": {
+			"version": "2.3.1",
+			"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+			"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/pyodide": {
+			"version": "0.26.1",
+			"resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.26.1.tgz",
+			"integrity": "sha512-P+Gm88nwZqY7uBgjbQH8CqqU6Ei/rDn7pS1t02sNZsbyLJMyE2OVXjgNuqVT3KqYWnyGREUN0DbBUCJqk8R0ew==",
+			"dependencies": {
+				"ws": "^8.5.0"
+			},
+			"engines": {
+				"node": ">=18.0.0"
+			}
+		},
+		"node_modules/qs": {
+			"version": "6.13.0",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+			"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+			"dev": true,
+			"dependencies": {
+				"side-channel": "^1.0.6"
+			},
+			"engines": {
+				"node": ">=0.6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/querystringify": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+			"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+			"dev": true
+		},
+		"node_modules/queue-microtask": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+			"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			]
+		},
+		"node_modules/queue-tick": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
+			"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
+			"dev": true
+		},
+		"node_modules/quick-temp": {
+			"version": "0.1.8",
+			"resolved": "https://registry.npmjs.org/quick-temp/-/quick-temp-0.1.8.tgz",
+			"integrity": "sha512-YsmIFfD9j2zaFwJkzI6eMG7y0lQP7YeWzgtFgNl38pGWZBSXJooZbOWwkcRot7Vt0Fg9L23pX0tqWU3VvLDsiA==",
+			"dev": true,
+			"dependencies": {
+				"mktemp": "~0.4.0",
+				"rimraf": "^2.5.4",
+				"underscore.string": "~3.3.4"
+			}
+		},
+		"node_modules/quick-temp/node_modules/brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
+		"node_modules/quick-temp/node_modules/glob": {
+			"version": "7.2.3",
+			"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+			"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+			"dev": true,
+			"dependencies": {
+				"fs.realpath": "^1.0.0",
+				"inflight": "^1.0.4",
+				"inherits": "2",
+				"minimatch": "^3.1.1",
+				"once": "^1.3.0",
+				"path-is-absolute": "^1.0.0"
+			},
+			"engines": {
+				"node": "*"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/quick-temp/node_modules/minimatch": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^1.1.7"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/quick-temp/node_modules/rimraf": {
+			"version": "2.7.1",
+			"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+			"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+			"dev": true,
+			"dependencies": {
+				"glob": "^7.1.3"
+			},
+			"bin": {
+				"rimraf": "bin.js"
+			}
+		},
+		"node_modules/react-is": {
+			"version": "18.3.1",
+			"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+			"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+			"dev": true
+		},
+		"node_modules/read-cache": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+			"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+			"dev": true,
+			"dependencies": {
+				"pify": "^2.3.0"
+			}
+		},
+		"node_modules/readable-stream": {
+			"version": "2.3.8",
+			"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+			"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+			"dev": true,
+			"dependencies": {
+				"core-util-is": "~1.0.0",
+				"inherits": "~2.0.3",
+				"isarray": "~1.0.0",
+				"process-nextick-args": "~2.0.0",
+				"safe-buffer": "~5.1.1",
+				"string_decoder": "~1.1.1",
+				"util-deprecate": "~1.0.1"
+			}
+		},
+		"node_modules/readdirp": {
+			"version": "3.6.0",
+			"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+			"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+			"dev": true,
+			"dependencies": {
+				"picomatch": "^2.2.1"
+			},
+			"engines": {
+				"node": ">=8.10.0"
+			}
+		},
+		"node_modules/regenerator-runtime": {
+			"version": "0.14.1",
+			"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+			"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+		},
+		"node_modules/remove-trailing-separator": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+			"integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==",
+			"dev": true
+		},
+		"node_modules/replace-ext": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz",
+			"integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==",
+			"dev": true,
+			"engines": {
+				"node": ">= 10"
+			}
+		},
+		"node_modules/request-progress": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
+			"integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==",
+			"dev": true,
+			"dependencies": {
+				"throttleit": "^1.0.0"
+			}
+		},
+		"node_modules/requires-port": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+			"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+			"dev": true
+		},
+		"node_modules/resolve": {
+			"version": "1.22.8",
+			"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+			"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+			"dependencies": {
+				"is-core-module": "^2.13.0",
+				"path-parse": "^1.0.7",
+				"supports-preserve-symlinks-flag": "^1.0.0"
+			},
+			"bin": {
+				"resolve": "bin/resolve"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/resolve-from": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+			"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+			"dev": true,
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/resolve-options": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz",
+			"integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==",
+			"dev": true,
+			"dependencies": {
+				"value-or-function": "^4.0.0"
+			},
+			"engines": {
+				"node": ">= 10.13.0"
+			}
+		},
+		"node_modules/restore-cursor": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+			"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+			"dev": true,
+			"dependencies": {
+				"onetime": "^5.1.0",
+				"signal-exit": "^3.0.2"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/restore-cursor/node_modules/signal-exit": {
+			"version": "3.0.7",
+			"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+			"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+			"dev": true
+		},
+		"node_modules/reusify": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+			"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+			"dev": true,
+			"engines": {
+				"iojs": ">=1.0.0",
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/rfdc": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz",
+			"integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==",
+			"dev": true
+		},
+		"node_modules/rimraf": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+			"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+			"dev": true,
+			"dependencies": {
+				"glob": "^7.1.3"
+			},
+			"bin": {
+				"rimraf": "bin.js"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/rimraf/node_modules/brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
+		"node_modules/rimraf/node_modules/glob": {
+			"version": "7.2.3",
+			"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+			"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+			"dev": true,
+			"dependencies": {
+				"fs.realpath": "^1.0.0",
+				"inflight": "^1.0.4",
+				"inherits": "2",
+				"minimatch": "^3.1.1",
+				"once": "^1.3.0",
+				"path-is-absolute": "^1.0.0"
+			},
+			"engines": {
+				"node": "*"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/rimraf/node_modules/minimatch": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^1.1.7"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/robust-predicates": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
+			"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
+		},
+		"node_modules/rollup": {
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz",
+			"integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==",
+			"dependencies": {
+				"@types/estree": "1.0.5"
+			},
+			"bin": {
+				"rollup": "dist/bin/rollup"
+			},
+			"engines": {
+				"node": ">=18.0.0",
+				"npm": ">=8.0.0"
+			},
+			"optionalDependencies": {
+				"@rollup/rollup-android-arm-eabi": "4.22.4",
+				"@rollup/rollup-android-arm64": "4.22.4",
+				"@rollup/rollup-darwin-arm64": "4.22.4",
+				"@rollup/rollup-darwin-x64": "4.22.4",
+				"@rollup/rollup-linux-arm-gnueabihf": "4.22.4",
+				"@rollup/rollup-linux-arm-musleabihf": "4.22.4",
+				"@rollup/rollup-linux-arm64-gnu": "4.22.4",
+				"@rollup/rollup-linux-arm64-musl": "4.22.4",
+				"@rollup/rollup-linux-powerpc64le-gnu": "4.22.4",
+				"@rollup/rollup-linux-riscv64-gnu": "4.22.4",
+				"@rollup/rollup-linux-s390x-gnu": "4.22.4",
+				"@rollup/rollup-linux-x64-gnu": "4.22.4",
+				"@rollup/rollup-linux-x64-musl": "4.22.4",
+				"@rollup/rollup-win32-arm64-msvc": "4.22.4",
+				"@rollup/rollup-win32-ia32-msvc": "4.22.4",
+				"@rollup/rollup-win32-x64-msvc": "4.22.4",
+				"fsevents": "~2.3.2"
+			}
+		},
+		"node_modules/rope-sequence": {
+			"version": "1.3.4",
+			"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
+			"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="
+		},
+		"node_modules/rsvp": {
+			"version": "4.8.5",
+			"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
+			"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==",
+			"dev": true,
+			"engines": {
+				"node": "6.* || >= 7.*"
+			}
+		},
+		"node_modules/run-parallel": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+			"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			],
+			"dependencies": {
+				"queue-microtask": "^1.2.2"
+			}
+		},
+		"node_modules/rw": {
+			"version": "1.3.3",
+			"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+			"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="
+		},
+		"node_modules/rxjs": {
+			"version": "7.8.1",
+			"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+			"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+			"dev": true,
+			"dependencies": {
+				"tslib": "^2.1.0"
+			}
+		},
+		"node_modules/sade": {
+			"version": "1.8.1",
+			"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+			"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+			"dependencies": {
+				"mri": "^1.1.0"
+			},
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/safe-buffer": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+			"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+			"dev": true
+		},
+		"node_modules/safer-buffer": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+		},
+		"node_modules/sander": {
+			"version": "0.5.1",
+			"resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
+			"integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==",
+			"dev": true,
+			"dependencies": {
+				"es6-promise": "^3.1.2",
+				"graceful-fs": "^4.1.3",
+				"mkdirp": "^0.5.1",
+				"rimraf": "^2.5.2"
+			}
+		},
+		"node_modules/sander/node_modules/brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
+		"node_modules/sander/node_modules/glob": {
+			"version": "7.2.3",
+			"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+			"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+			"dev": true,
+			"dependencies": {
+				"fs.realpath": "^1.0.0",
+				"inflight": "^1.0.4",
+				"inherits": "2",
+				"minimatch": "^3.1.1",
+				"once": "^1.3.0",
+				"path-is-absolute": "^1.0.0"
+			},
+			"engines": {
+				"node": "*"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/sander/node_modules/minimatch": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^1.1.7"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/sander/node_modules/rimraf": {
+			"version": "2.7.1",
+			"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+			"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+			"dev": true,
+			"dependencies": {
+				"glob": "^7.1.3"
+			},
+			"bin": {
+				"rimraf": "bin.js"
+			}
+		},
+		"node_modules/semver": {
+			"version": "7.6.3",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+			"bin": {
+				"semver": "bin/semver.js"
+			},
+			"engines": {
+				"node": ">=10"
+			}
+		},
+		"node_modules/set-cookie-parser": {
+			"version": "2.6.0",
+			"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
+			"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
+		},
+		"node_modules/set-function-length": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+			"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+			"dev": true,
+			"dependencies": {
+				"define-data-property": "^1.1.4",
+				"es-errors": "^1.3.0",
+				"function-bind": "^1.1.2",
+				"get-intrinsic": "^1.2.4",
+				"gopd": "^1.0.1",
+				"has-property-descriptors": "^1.0.2"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/sharp": {
+			"version": "0.33.5",
+			"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
+			"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
+			"hasInstallScript": true,
+			"dependencies": {
+				"color": "^4.2.3",
+				"detect-libc": "^2.0.3",
+				"semver": "^7.6.3"
+			},
+			"engines": {
+				"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/libvips"
+			},
+			"optionalDependencies": {
+				"@img/sharp-darwin-arm64": "0.33.5",
+				"@img/sharp-darwin-x64": "0.33.5",
+				"@img/sharp-libvips-darwin-arm64": "1.0.4",
+				"@img/sharp-libvips-darwin-x64": "1.0.4",
+				"@img/sharp-libvips-linux-arm": "1.0.5",
+				"@img/sharp-libvips-linux-arm64": "1.0.4",
+				"@img/sharp-libvips-linux-s390x": "1.0.4",
+				"@img/sharp-libvips-linux-x64": "1.0.4",
+				"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+				"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+				"@img/sharp-linux-arm": "0.33.5",
+				"@img/sharp-linux-arm64": "0.33.5",
+				"@img/sharp-linux-s390x": "0.33.5",
+				"@img/sharp-linux-x64": "0.33.5",
+				"@img/sharp-linuxmusl-arm64": "0.33.5",
+				"@img/sharp-linuxmusl-x64": "0.33.5",
+				"@img/sharp-wasm32": "0.33.5",
+				"@img/sharp-win32-ia32": "0.33.5",
+				"@img/sharp-win32-x64": "0.33.5"
+			}
+		},
+		"node_modules/shebang-command": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+			"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+			"dependencies": {
+				"shebang-regex": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/shebang-regex": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+			"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/side-channel": {
+			"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==",
+			"dev": true,
+			"dependencies": {
+				"call-bind": "^1.0.7",
+				"es-errors": "^1.3.0",
+				"get-intrinsic": "^1.2.4",
+				"object-inspect": "^1.13.1"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/siginfo": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+			"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+			"dev": true
+		},
+		"node_modules/signal-exit": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+			"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+			"engines": {
+				"node": ">=14"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/simple-swizzle": {
+			"version": "0.2.2",
+			"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+			"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+			"dependencies": {
+				"is-arrayish": "^0.3.1"
+			}
+		},
+		"node_modules/sirv": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
+			"integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
+			"dependencies": {
+				"@polka/url": "^1.0.0-next.24",
+				"mrmime": "^2.0.0",
+				"totalist": "^3.0.0"
+			},
+			"engines": {
+				"node": ">= 10"
+			}
+		},
+		"node_modules/slash": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+			"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/slice-ansi": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
+			"integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
+			"dev": true,
+			"dependencies": {
+				"ansi-styles": "^4.0.0",
+				"astral-regex": "^2.0.0",
+				"is-fullwidth-code-point": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/socket.io-client": {
+			"version": "4.7.5",
+			"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
+			"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
+			"dependencies": {
+				"@socket.io/component-emitter": "~3.1.0",
+				"debug": "~4.3.2",
+				"engine.io-client": "~6.5.2",
+				"socket.io-parser": "~4.2.4"
+			},
+			"engines": {
+				"node": ">=10.0.0"
+			}
+		},
+		"node_modules/socket.io-parser": {
+			"version": "4.2.4",
+			"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+			"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+			"dependencies": {
+				"@socket.io/component-emitter": "~3.1.0",
+				"debug": "~4.3.1"
+			},
+			"engines": {
+				"node": ">=10.0.0"
+			}
+		},
+		"node_modules/sorcery": {
+			"version": "0.11.0",
+			"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
+			"integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==",
+			"dev": true,
+			"dependencies": {
+				"@jridgewell/sourcemap-codec": "^1.4.14",
+				"buffer-crc32": "^0.2.5",
+				"minimist": "^1.2.0",
+				"sander": "^0.5.0"
+			},
+			"bin": {
+				"sorcery": "bin/sorcery"
+			}
+		},
+		"node_modules/sort-keys": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.0.0.tgz",
+			"integrity": "sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==",
+			"dev": true,
+			"dependencies": {
+				"is-plain-obj": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/sortablejs": {
+			"version": "1.15.2",
+			"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz",
+			"integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA=="
+		},
+		"node_modules/source-map-js": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+			"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/sprintf-js": {
+			"version": "1.1.3",
+			"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+			"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+			"dev": true
+		},
+		"node_modules/sshpk": {
+			"version": "1.18.0",
+			"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
+			"integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
+			"dev": true,
+			"dependencies": {
+				"asn1": "~0.2.3",
+				"assert-plus": "^1.0.0",
+				"bcrypt-pbkdf": "^1.0.0",
+				"dashdash": "^1.12.0",
+				"ecc-jsbn": "~0.1.1",
+				"getpass": "^0.1.1",
+				"jsbn": "~0.1.0",
+				"safer-buffer": "^2.0.2",
+				"tweetnacl": "~0.14.0"
+			},
+			"bin": {
+				"sshpk-conv": "bin/sshpk-conv",
+				"sshpk-sign": "bin/sshpk-sign",
+				"sshpk-verify": "bin/sshpk-verify"
+			},
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/stackback": {
+			"version": "0.0.2",
+			"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+			"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+			"dev": true
+		},
+		"node_modules/std-env": {
+			"version": "3.7.0",
+			"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
+			"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
+			"dev": true
+		},
+		"node_modules/sticky-module": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/sticky-module/-/sticky-module-0.1.1.tgz",
+			"integrity": "sha512-IuYgnyIMUx/m6rtu14l/LR2MaqOLtpXcWkxPmtPsiScRHEo+S4Tojk+DWFHOncSdFX/OsoLOM4+T92yOmI1AMw=="
+		},
+		"node_modules/stream-composer": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz",
+			"integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==",
+			"dev": true,
+			"dependencies": {
+				"streamx": "^2.13.2"
+			}
+		},
+		"node_modules/streamx": {
+			"version": "2.16.1",
+			"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz",
+			"integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==",
+			"dev": true,
+			"dependencies": {
+				"fast-fifo": "^1.1.0",
+				"queue-tick": "^1.0.1"
+			},
+			"optionalDependencies": {
+				"bare-events": "^2.2.0"
+			}
+		},
+		"node_modules/string_decoder": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+			"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+			"dev": true,
+			"dependencies": {
+				"safe-buffer": "~5.1.0"
+			}
+		},
+		"node_modules/string-width": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+			"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+			"dependencies": {
+				"eastasianwidth": "^0.2.0",
+				"emoji-regex": "^9.2.2",
+				"strip-ansi": "^7.0.1"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/string-width-cjs": {
+			"name": "string-width",
+			"version": "4.2.3",
+			"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+			"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+			"dependencies": {
+				"emoji-regex": "^8.0.0",
+				"is-fullwidth-code-point": "^3.0.0",
+				"strip-ansi": "^6.0.1"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/string-width-cjs/node_modules/emoji-regex": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+			"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+		},
+		"node_modules/string-width/node_modules/ansi-regex": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+			"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/ansi-regex?sponsor=1"
+			}
+		},
+		"node_modules/string-width/node_modules/strip-ansi": {
+			"version": "7.1.0",
+			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+			"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+			"dependencies": {
+				"ansi-regex": "^6.0.1"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/strip-ansi?sponsor=1"
+			}
+		},
+		"node_modules/strip-ansi": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+			"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+			"dependencies": {
+				"ansi-regex": "^5.0.1"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/strip-ansi-cjs": {
+			"name": "strip-ansi",
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+			"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+			"dependencies": {
+				"ansi-regex": "^5.0.1"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/strip-final-newline": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+			"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/strip-indent": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+			"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+			"dev": true,
+			"dependencies": {
+				"min-indent": "^1.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/strip-json-comments": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+			"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/strip-literal": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz",
+			"integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==",
+			"dev": true,
+			"dependencies": {
+				"js-tokens": "^9.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/antfu"
+			}
+		},
+		"node_modules/style-mod": {
+			"version": "4.1.2",
+			"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
+			"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
+		},
+		"node_modules/stylis": {
+			"version": "4.3.2",
+			"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+			"integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg=="
+		},
+		"node_modules/sucrase": {
+			"version": "3.35.0",
+			"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+			"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+			"dev": true,
+			"dependencies": {
+				"@jridgewell/gen-mapping": "^0.3.2",
+				"commander": "^4.0.0",
+				"glob": "^10.3.10",
+				"lines-and-columns": "^1.1.6",
+				"mz": "^2.7.0",
+				"pirates": "^4.0.1",
+				"ts-interface-checker": "^0.1.9"
+			},
+			"bin": {
+				"sucrase": "bin/sucrase",
+				"sucrase-node": "bin/sucrase-node"
+			},
+			"engines": {
+				"node": ">=16 || 14 >=14.17"
+			}
+		},
+		"node_modules/sucrase/node_modules/commander": {
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+			"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+			"dev": true,
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/sucrase/node_modules/glob": {
+			"version": "10.3.10",
+			"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+			"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+			"dev": true,
+			"dependencies": {
+				"foreground-child": "^3.1.0",
+				"jackspeak": "^2.3.5",
+				"minimatch": "^9.0.1",
+				"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+				"path-scurry": "^1.10.1"
+			},
+			"bin": {
+				"glob": "dist/esm/bin.mjs"
+			},
+			"engines": {
+				"node": ">=16 || 14 >=14.17"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/supports-color": {
+			"version": "7.2.0",
+			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+			"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+			"dev": true,
+			"dependencies": {
+				"has-flag": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/supports-preserve-symlinks-flag": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+			"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/svelte": {
+			"version": "4.2.19",
+			"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz",
+			"integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==",
+			"dependencies": {
+				"@ampproject/remapping": "^2.2.1",
+				"@jridgewell/sourcemap-codec": "^1.4.15",
+				"@jridgewell/trace-mapping": "^0.3.18",
+				"@types/estree": "^1.0.1",
+				"acorn": "^8.9.0",
+				"aria-query": "^5.3.0",
+				"axobject-query": "^4.0.0",
+				"code-red": "^1.0.3",
+				"css-tree": "^2.3.1",
+				"estree-walker": "^3.0.3",
+				"is-reference": "^3.0.1",
+				"locate-character": "^3.0.0",
+				"magic-string": "^0.30.4",
+				"periscopic": "^3.1.0"
+			},
+			"engines": {
+				"node": ">=16"
+			}
+		},
+		"node_modules/svelte-check": {
+			"version": "3.8.5",
+			"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.5.tgz",
+			"integrity": "sha512-3OGGgr9+bJ/+1nbPgsvulkLC48xBsqsgtc8Wam281H4G9F5v3mYGa2bHRsPuwHC5brKl4AxJH95QF73kmfihGQ==",
+			"dev": true,
+			"dependencies": {
+				"@jridgewell/trace-mapping": "^0.3.17",
+				"chokidar": "^3.4.1",
+				"picocolors": "^1.0.0",
+				"sade": "^1.7.4",
+				"svelte-preprocess": "^5.1.3",
+				"typescript": "^5.0.3"
+			},
+			"bin": {
+				"svelte-check": "bin/svelte-check"
+			},
+			"peerDependencies": {
+				"svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
+			}
+		},
+		"node_modules/svelte-confetti": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/svelte-confetti/-/svelte-confetti-1.3.2.tgz",
+			"integrity": "sha512-R+JwFTC7hIgWVA/OuXrkj384B7CMoceb0t9VacyW6dORTQg0pWojVBB8Bo3tM30cLEQE48Fekzqgx+XSzHESMA==",
+			"dev": true,
+			"peerDependencies": {
+				"svelte": "^4.0.0"
+			}
+		},
+		"node_modules/svelte-eslint-parser": {
+			"version": "0.41.0",
+			"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.41.0.tgz",
+			"integrity": "sha512-L6f4hOL+AbgfBIB52Z310pg1d2QjRqm7wy3kI1W6hhdhX5bvu7+f0R6w4ykp5HoDdzq+vGhIJmsisaiJDGmVfA==",
+			"dev": true,
+			"dependencies": {
+				"eslint-scope": "^7.2.2",
+				"eslint-visitor-keys": "^3.4.3",
+				"espree": "^9.6.1",
+				"postcss": "^8.4.39",
+				"postcss-scss": "^4.0.9"
+			},
+			"engines": {
+				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ota-meshi"
+			},
+			"peerDependencies": {
+				"svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191"
+			},
+			"peerDependenciesMeta": {
+				"svelte": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/svelte-hmr": {
+			"version": "0.16.0",
+			"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
+			"integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==",
+			"engines": {
+				"node": "^12.20 || ^14.13.1 || >= 16"
+			},
+			"peerDependencies": {
+				"svelte": "^3.19.0 || ^4.0.0"
+			}
+		},
+		"node_modules/svelte-preprocess": {
+			"version": "5.1.3",
+			"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz",
+			"integrity": "sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==",
+			"dev": true,
+			"hasInstallScript": true,
+			"dependencies": {
+				"@types/pug": "^2.0.6",
+				"detect-indent": "^6.1.0",
+				"magic-string": "^0.30.5",
+				"sorcery": "^0.11.0",
+				"strip-indent": "^3.0.0"
+			},
+			"engines": {
+				"node": ">= 16.0.0",
+				"pnpm": "^8.0.0"
+			},
+			"peerDependencies": {
+				"@babel/core": "^7.10.2",
+				"coffeescript": "^2.5.1",
+				"less": "^3.11.3 || ^4.0.0",
+				"postcss": "^7 || ^8",
+				"postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
+				"pug": "^3.0.0",
+				"sass": "^1.26.8",
+				"stylus": "^0.55.0",
+				"sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
+				"svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0",
+				"typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0"
+			},
+			"peerDependenciesMeta": {
+				"@babel/core": {
+					"optional": true
+				},
+				"coffeescript": {
+					"optional": true
+				},
+				"less": {
+					"optional": true
+				},
+				"postcss": {
+					"optional": true
+				},
+				"postcss-load-config": {
+					"optional": true
+				},
+				"pug": {
+					"optional": true
+				},
+				"sass": {
+					"optional": true
+				},
+				"stylus": {
+					"optional": true
+				},
+				"sugarss": {
+					"optional": true
+				},
+				"typescript": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/svelte-sonner": {
+			"version": "0.3.28",
+			"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.28.tgz",
+			"integrity": "sha512-K3AmlySeFifF/cKgsYNv5uXqMVNln0NBAacOYgmkQStLa/UoU0LhfAACU6Gr+YYC8bOCHdVmFNoKuDbMEsppJg==",
+			"peerDependencies": {
+				"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1"
+			}
+		},
+		"node_modules/svelte/node_modules/estree-walker": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+			"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+			"dependencies": {
+				"@types/estree": "^1.0.0"
+			}
+		},
+		"node_modules/svelte/node_modules/is-reference": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
+			"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
+			"dependencies": {
+				"@types/estree": "*"
+			}
+		},
+		"node_modules/symlink-or-copy": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz",
+			"integrity": "sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA==",
+			"dev": true
+		},
+		"node_modules/tabbable": {
+			"version": "6.2.0",
+			"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
+			"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
+		},
+		"node_modules/tailwindcss": {
+			"version": "3.4.1",
+			"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
+			"integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==",
+			"dev": true,
+			"dependencies": {
+				"@alloc/quick-lru": "^5.2.0",
+				"arg": "^5.0.2",
+				"chokidar": "^3.5.3",
+				"didyoumean": "^1.2.2",
+				"dlv": "^1.1.3",
+				"fast-glob": "^3.3.0",
+				"glob-parent": "^6.0.2",
+				"is-glob": "^4.0.3",
+				"jiti": "^1.19.1",
+				"lilconfig": "^2.1.0",
+				"micromatch": "^4.0.5",
+				"normalize-path": "^3.0.0",
+				"object-hash": "^3.0.0",
+				"picocolors": "^1.0.0",
+				"postcss": "^8.4.23",
+				"postcss-import": "^15.1.0",
+				"postcss-js": "^4.0.1",
+				"postcss-load-config": "^4.0.1",
+				"postcss-nested": "^6.0.1",
+				"postcss-selector-parser": "^6.0.11",
+				"resolve": "^1.22.2",
+				"sucrase": "^3.32.0"
+			},
+			"bin": {
+				"tailwind": "lib/cli.js",
+				"tailwindcss": "lib/cli.js"
+			},
+			"engines": {
+				"node": ">=14.0.0"
+			}
+		},
+		"node_modules/tailwindcss/node_modules/lilconfig": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+			"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=10"
+			}
+		},
+		"node_modules/tailwindcss/node_modules/postcss-load-config": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+			"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/postcss/"
+				},
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			],
+			"dependencies": {
+				"lilconfig": "^3.0.0",
+				"yaml": "^2.3.4"
+			},
+			"engines": {
+				"node": ">= 14"
+			},
+			"peerDependencies": {
+				"postcss": ">=8.0.9",
+				"ts-node": ">=9.0.0"
+			},
+			"peerDependenciesMeta": {
+				"postcss": {
+					"optional": true
+				},
+				"ts-node": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
+			"integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=14"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/antonk52"
+			}
+		},
+		"node_modules/tailwindcss/node_modules/postcss-selector-parser": {
+			"version": "6.0.16",
+			"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
+			"integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
+			"dev": true,
+			"dependencies": {
+				"cssesc": "^3.0.0",
+				"util-deprecate": "^1.0.2"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/tailwindcss/node_modules/yaml": {
+			"version": "2.4.1",
+			"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
+			"integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==",
+			"dev": true,
+			"bin": {
+				"yaml": "bin.mjs"
+			},
+			"engines": {
+				"node": ">= 14"
+			}
+		},
+		"node_modules/tar": {
+			"version": "7.4.3",
+			"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
+			"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
+			"dependencies": {
+				"@isaacs/fs-minipass": "^4.0.0",
+				"chownr": "^3.0.0",
+				"minipass": "^7.1.2",
+				"minizlib": "^3.0.1",
+				"mkdirp": "^3.0.1",
+				"yallist": "^5.0.0"
+			},
+			"engines": {
+				"node": ">=18"
+			}
+		},
+		"node_modules/tar/node_modules/mkdirp": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
+			"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
+			"bin": {
+				"mkdirp": "dist/cjs/src/bin.js"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
+		"node_modules/teex": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
+			"integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
+			"dev": true,
+			"dependencies": {
+				"streamx": "^2.12.5"
+			}
+		},
+		"node_modules/text-table": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+			"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+			"dev": true
+		},
+		"node_modules/thenify": {
+			"version": "3.3.1",
+			"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+			"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+			"dev": true,
+			"dependencies": {
+				"any-promise": "^1.0.0"
+			}
+		},
+		"node_modules/thenify-all": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+			"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+			"dev": true,
+			"dependencies": {
+				"thenify": ">= 3.1.0 < 4"
+			},
+			"engines": {
+				"node": ">=0.8"
+			}
+		},
+		"node_modules/throttleit": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz",
+			"integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==",
+			"dev": true,
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/through": {
+			"version": "2.3.8",
+			"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+			"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+			"dev": true
+		},
+		"node_modules/through2": {
+			"version": "2.0.5",
+			"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+			"integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+			"dev": true,
+			"dependencies": {
+				"readable-stream": "~2.3.6",
+				"xtend": "~4.0.1"
+			}
+		},
+		"node_modules/tiny-glob": {
+			"version": "0.2.9",
+			"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
+			"integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
+			"dependencies": {
+				"globalyzer": "0.1.0",
+				"globrex": "^0.1.2"
+			}
+		},
+		"node_modules/tinybench": {
+			"version": "2.8.0",
+			"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz",
+			"integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==",
+			"dev": true
+		},
+		"node_modules/tinypool": {
+			"version": "0.8.4",
+			"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
+			"integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=14.0.0"
+			}
+		},
+		"node_modules/tinyspy": {
+			"version": "2.2.1",
+			"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
+			"integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+			"dev": true,
+			"engines": {
+				"node": ">=14.0.0"
+			}
+		},
+		"node_modules/tippy.js": {
+			"version": "6.3.7",
+			"resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
+			"integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
+			"dependencies": {
+				"@popperjs/core": "^2.9.0"
+			}
+		},
+		"node_modules/tmp": {
+			"version": "0.2.3",
+			"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
+			"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
+			"dev": true,
+			"engines": {
+				"node": ">=14.14"
+			}
+		},
+		"node_modules/to-json-callback": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/to-json-callback/-/to-json-callback-0.1.1.tgz",
+			"integrity": "sha512-BzOeinTT3NjE+FJ2iCvWB8HvyuyBzoH3WlSnJ+AYVC4tlePyZWSYdkQIFOARWiq0t35/XhmI0uQsFiUsRksRqg=="
+		},
+		"node_modules/to-regex-range": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+			"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+			"dev": true,
+			"dependencies": {
+				"is-number": "^7.0.0"
+			},
+			"engines": {
+				"node": ">=8.0"
+			}
+		},
+		"node_modules/to-through": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz",
+			"integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==",
+			"dev": true,
+			"dependencies": {
+				"streamx": "^2.12.5"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
+		"node_modules/totalist": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+			"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/tough-cookie": {
+			"version": "4.1.4",
+			"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+			"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+			"dev": true,
+			"dependencies": {
+				"psl": "^1.1.33",
+				"punycode": "^2.1.1",
+				"universalify": "^0.2.0",
+				"url-parse": "^1.5.3"
+			},
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/tough-cookie/node_modules/universalify": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+			"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 4.0.0"
+			}
+		},
+		"node_modules/ts-api-utils": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
+			"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=16"
+			},
+			"peerDependencies": {
+				"typescript": ">=4.2.0"
+			}
+		},
+		"node_modules/ts-dedent": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
+			"integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+			"engines": {
+				"node": ">=6.10"
+			}
+		},
+		"node_modules/ts-interface-checker": {
+			"version": "0.1.13",
+			"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+			"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+			"dev": true
+		},
+		"node_modules/tslib": {
+			"version": "2.6.2",
+			"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+			"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+		},
+		"node_modules/tunnel-agent": {
+			"version": "0.6.0",
+			"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+			"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+			"dev": true,
+			"dependencies": {
+				"safe-buffer": "^5.0.1"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/turndown": {
+			"version": "7.2.0",
+			"resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz",
+			"integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==",
+			"dependencies": {
+				"@mixmark-io/domino": "^2.2.0"
+			}
+		},
+		"node_modules/tweetnacl": {
+			"version": "0.14.5",
+			"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+			"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
+			"dev": true
+		},
+		"node_modules/type-check": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+			"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+			"dev": true,
+			"dependencies": {
+				"prelude-ls": "^1.2.1"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/type-checked-collections": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/type-checked-collections/-/type-checked-collections-0.1.7.tgz",
+			"integrity": "sha512-fLIydlJy7IG9XL4wjRwEcKhxx/ekLXiWiMvcGo01cOMF+TN+5ZqajM1mRNRz2bNNi1bzou2yofhjZEQi7kgl9A=="
+		},
+		"node_modules/type-detect": {
+			"version": "4.0.8",
+			"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+			"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+			"dev": true,
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/type-fest": {
+			"version": "0.20.2",
+			"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+			"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/typescript": {
+			"version": "5.5.4",
+			"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+			"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+			"dev": true,
+			"bin": {
+				"tsc": "bin/tsc",
+				"tsserver": "bin/tsserver"
+			},
+			"engines": {
+				"node": ">=14.17"
+			}
+		},
+		"node_modules/uc.micro": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+			"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="
+		},
+		"node_modules/ufo": {
+			"version": "1.5.3",
+			"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz",
+			"integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==",
+			"dev": true
+		},
+		"node_modules/underscore.string": {
+			"version": "3.3.6",
+			"resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz",
+			"integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==",
+			"dev": true,
+			"dependencies": {
+				"sprintf-js": "^1.1.1",
+				"util-deprecate": "^1.0.2"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/undici-types": {
+			"version": "5.26.5",
+			"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+			"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+		},
+		"node_modules/unist-util-stringify-position": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz",
+			"integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==",
+			"dependencies": {
+				"@types/unist": "^2.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/unified"
+			}
+		},
+		"node_modules/universalify": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+			"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+			"dev": true,
+			"engines": {
+				"node": ">= 10.0.0"
+			}
+		},
+		"node_modules/untildify": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+			"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/update-browserslist-db": {
+			"version": "1.0.13",
+			"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+			"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/browserslist"
+				},
+				{
+					"type": "tidelift",
+					"url": "https://tidelift.com/funding/github/npm/browserslist"
+				},
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			],
+			"dependencies": {
+				"escalade": "^3.1.1",
+				"picocolors": "^1.0.0"
+			},
+			"bin": {
+				"update-browserslist-db": "cli.js"
+			},
+			"peerDependencies": {
+				"browserslist": ">= 4.21.0"
+			}
+		},
+		"node_modules/uri-js": {
+			"version": "4.4.1",
+			"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+			"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+			"dev": true,
+			"dependencies": {
+				"punycode": "^2.1.0"
+			}
+		},
+		"node_modules/url-parse": {
+			"version": "1.5.10",
+			"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+			"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+			"dev": true,
+			"dependencies": {
+				"querystringify": "^2.1.1",
+				"requires-port": "^1.0.0"
+			}
+		},
+		"node_modules/util-deprecate": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+			"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+			"dev": true
+		},
+		"node_modules/uuid": {
+			"version": "9.0.1",
+			"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+			"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+			"funding": [
+				"https://github.com/sponsors/broofa",
+				"https://github.com/sponsors/ctavan"
+			],
+			"bin": {
+				"uuid": "dist/bin/uuid"
+			}
+		},
+		"node_modules/uvu": {
+			"version": "0.5.6",
+			"resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
+			"integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==",
+			"dependencies": {
+				"dequal": "^2.0.0",
+				"diff": "^5.0.0",
+				"kleur": "^4.0.3",
+				"sade": "^1.7.3"
+			},
+			"bin": {
+				"uvu": "bin.js"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/value-or-function": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz",
+			"integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 10.13.0"
+			}
+		},
+		"node_modules/verror": {
+			"version": "1.10.0",
+			"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+			"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
+			"dev": true,
+			"engines": [
+				"node >=0.6.0"
+			],
+			"dependencies": {
+				"assert-plus": "^1.0.0",
+				"core-util-is": "1.0.2",
+				"extsprintf": "^1.2.0"
+			}
+		},
+		"node_modules/verror/node_modules/core-util-is": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+			"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
+			"dev": true
+		},
+		"node_modules/vinyl": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz",
+			"integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==",
+			"dev": true,
+			"dependencies": {
+				"clone": "^2.1.2",
+				"clone-stats": "^1.0.0",
+				"remove-trailing-separator": "^1.1.0",
+				"replace-ext": "^2.0.0",
+				"teex": "^1.0.1"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
+		"node_modules/vinyl-contents": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz",
+			"integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==",
+			"dev": true,
+			"dependencies": {
+				"bl": "^5.0.0",
+				"vinyl": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
+		"node_modules/vinyl-fs": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz",
+			"integrity": "sha512-7GbgBnYfaquMk3Qu9g22x000vbYkOex32930rBnc3qByw6HfMEAoELjCjoJv4HuEQxHAurT+nvMHm6MnJllFLw==",
+			"dev": true,
+			"dependencies": {
+				"fs-mkdirp-stream": "^2.0.1",
+				"glob-stream": "^8.0.0",
+				"graceful-fs": "^4.2.11",
+				"iconv-lite": "^0.6.3",
+				"is-valid-glob": "^1.0.0",
+				"lead": "^4.0.0",
+				"normalize-path": "3.0.0",
+				"resolve-options": "^2.0.0",
+				"stream-composer": "^1.0.2",
+				"streamx": "^2.14.0",
+				"to-through": "^3.0.0",
+				"value-or-function": "^4.0.0",
+				"vinyl": "^3.0.0",
+				"vinyl-sourcemap": "^2.0.0"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
+		"node_modules/vinyl-sourcemap": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz",
+			"integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==",
+			"dev": true,
+			"dependencies": {
+				"convert-source-map": "^2.0.0",
+				"graceful-fs": "^4.2.10",
+				"now-and-later": "^3.0.0",
+				"streamx": "^2.12.5",
+				"vinyl": "^3.0.0",
+				"vinyl-contents": "^2.0.0"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
+		"node_modules/vite": {
+			"version": "5.4.6",
+			"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz",
+			"integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==",
+			"dependencies": {
+				"esbuild": "^0.21.3",
+				"postcss": "^8.4.43",
+				"rollup": "^4.20.0"
+			},
+			"bin": {
+				"vite": "bin/vite.js"
+			},
+			"engines": {
+				"node": "^18.0.0 || >=20.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/vitejs/vite?sponsor=1"
+			},
+			"optionalDependencies": {
+				"fsevents": "~2.3.3"
+			},
+			"peerDependencies": {
+				"@types/node": "^18.0.0 || >=20.0.0",
+				"less": "*",
+				"lightningcss": "^1.21.0",
+				"sass": "*",
+				"sass-embedded": "*",
+				"stylus": "*",
+				"sugarss": "*",
+				"terser": "^5.4.0"
+			},
+			"peerDependenciesMeta": {
+				"@types/node": {
+					"optional": true
+				},
+				"less": {
+					"optional": true
+				},
+				"lightningcss": {
+					"optional": true
+				},
+				"sass": {
+					"optional": true
+				},
+				"sass-embedded": {
+					"optional": true
+				},
+				"stylus": {
+					"optional": true
+				},
+				"sugarss": {
+					"optional": true
+				},
+				"terser": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/vite-node": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz",
+			"integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==",
+			"dev": true,
+			"dependencies": {
+				"cac": "^6.7.14",
+				"debug": "^4.3.4",
+				"pathe": "^1.1.1",
+				"picocolors": "^1.0.0",
+				"vite": "^5.0.0"
+			},
+			"bin": {
+				"vite-node": "vite-node.mjs"
+			},
+			"engines": {
+				"node": "^18.0.0 || >=20.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/vitest"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+			"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+			"cpu": [
+				"ppc64"
+			],
+			"optional": true,
+			"os": [
+				"aix"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/android-arm": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+			"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+			"cpu": [
+				"arm"
+			],
+			"optional": true,
+			"os": [
+				"android"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/android-arm64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+			"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"android"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/android-x64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+			"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"android"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+			"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"darwin"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/darwin-x64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+			"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"darwin"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+			"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"freebsd"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+			"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"freebsd"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/linux-arm": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+			"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+			"cpu": [
+				"arm"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/linux-arm64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+			"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/linux-ia32": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+			"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+			"cpu": [
+				"ia32"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/linux-loong64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+			"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+			"cpu": [
+				"loong64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+			"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+			"cpu": [
+				"mips64el"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+			"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+			"cpu": [
+				"ppc64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+			"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+			"cpu": [
+				"riscv64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/linux-s390x": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+			"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+			"cpu": [
+				"s390x"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/linux-x64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+			"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"linux"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+			"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"netbsd"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+			"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"openbsd"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/sunos-x64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+			"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"sunos"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/win32-arm64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+			"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+			"cpu": [
+				"arm64"
+			],
+			"optional": true,
+			"os": [
+				"win32"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/win32-ia32": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+			"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+			"cpu": [
+				"ia32"
+			],
+			"optional": true,
+			"os": [
+				"win32"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/@esbuild/win32-x64": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+			"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+			"cpu": [
+				"x64"
+			],
+			"optional": true,
+			"os": [
+				"win32"
+			],
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/vite/node_modules/esbuild": {
+			"version": "0.21.5",
+			"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+			"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+			"hasInstallScript": true,
+			"bin": {
+				"esbuild": "bin/esbuild"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"optionalDependencies": {
+				"@esbuild/aix-ppc64": "0.21.5",
+				"@esbuild/android-arm": "0.21.5",
+				"@esbuild/android-arm64": "0.21.5",
+				"@esbuild/android-x64": "0.21.5",
+				"@esbuild/darwin-arm64": "0.21.5",
+				"@esbuild/darwin-x64": "0.21.5",
+				"@esbuild/freebsd-arm64": "0.21.5",
+				"@esbuild/freebsd-x64": "0.21.5",
+				"@esbuild/linux-arm": "0.21.5",
+				"@esbuild/linux-arm64": "0.21.5",
+				"@esbuild/linux-ia32": "0.21.5",
+				"@esbuild/linux-loong64": "0.21.5",
+				"@esbuild/linux-mips64el": "0.21.5",
+				"@esbuild/linux-ppc64": "0.21.5",
+				"@esbuild/linux-riscv64": "0.21.5",
+				"@esbuild/linux-s390x": "0.21.5",
+				"@esbuild/linux-x64": "0.21.5",
+				"@esbuild/netbsd-x64": "0.21.5",
+				"@esbuild/openbsd-x64": "0.21.5",
+				"@esbuild/sunos-x64": "0.21.5",
+				"@esbuild/win32-arm64": "0.21.5",
+				"@esbuild/win32-ia32": "0.21.5",
+				"@esbuild/win32-x64": "0.21.5"
+			}
+		},
+		"node_modules/vitefu": {
+			"version": "0.2.5",
+			"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
+			"integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
+			"peerDependencies": {
+				"vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
+			},
+			"peerDependenciesMeta": {
+				"vite": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/vitest": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
+			"integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==",
+			"dev": true,
+			"dependencies": {
+				"@vitest/expect": "1.6.0",
+				"@vitest/runner": "1.6.0",
+				"@vitest/snapshot": "1.6.0",
+				"@vitest/spy": "1.6.0",
+				"@vitest/utils": "1.6.0",
+				"acorn-walk": "^8.3.2",
+				"chai": "^4.3.10",
+				"debug": "^4.3.4",
+				"execa": "^8.0.1",
+				"local-pkg": "^0.5.0",
+				"magic-string": "^0.30.5",
+				"pathe": "^1.1.1",
+				"picocolors": "^1.0.0",
+				"std-env": "^3.5.0",
+				"strip-literal": "^2.0.0",
+				"tinybench": "^2.5.1",
+				"tinypool": "^0.8.3",
+				"vite": "^5.0.0",
+				"vite-node": "1.6.0",
+				"why-is-node-running": "^2.2.2"
+			},
+			"bin": {
+				"vitest": "vitest.mjs"
+			},
+			"engines": {
+				"node": "^18.0.0 || >=20.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/vitest"
+			},
+			"peerDependencies": {
+				"@edge-runtime/vm": "*",
+				"@types/node": "^18.0.0 || >=20.0.0",
+				"@vitest/browser": "1.6.0",
+				"@vitest/ui": "1.6.0",
+				"happy-dom": "*",
+				"jsdom": "*"
+			},
+			"peerDependenciesMeta": {
+				"@edge-runtime/vm": {
+					"optional": true
+				},
+				"@types/node": {
+					"optional": true
+				},
+				"@vitest/browser": {
+					"optional": true
+				},
+				"@vitest/ui": {
+					"optional": true
+				},
+				"happy-dom": {
+					"optional": true
+				},
+				"jsdom": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/vitest/node_modules/execa": {
+			"version": "8.0.1",
+			"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+			"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+			"dev": true,
+			"dependencies": {
+				"cross-spawn": "^7.0.3",
+				"get-stream": "^8.0.1",
+				"human-signals": "^5.0.0",
+				"is-stream": "^3.0.0",
+				"merge-stream": "^2.0.0",
+				"npm-run-path": "^5.1.0",
+				"onetime": "^6.0.0",
+				"signal-exit": "^4.1.0",
+				"strip-final-newline": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=16.17"
+			},
+			"funding": {
+				"url": "https://github.com/sindresorhus/execa?sponsor=1"
+			}
+		},
+		"node_modules/vitest/node_modules/get-stream": {
+			"version": "8.0.1",
+			"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+			"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+			"dev": true,
+			"engines": {
+				"node": ">=16"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/vitest/node_modules/human-signals": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+			"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=16.17.0"
+			}
+		},
+		"node_modules/vitest/node_modules/is-stream": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+			"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+			"dev": true,
+			"engines": {
+				"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/vitest/node_modules/mimic-fn": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+			"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+			"dev": true,
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/vitest/node_modules/npm-run-path": {
+			"version": "5.3.0",
+			"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+			"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+			"dev": true,
+			"dependencies": {
+				"path-key": "^4.0.0"
+			},
+			"engines": {
+				"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/vitest/node_modules/onetime": {
+			"version": "6.0.0",
+			"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+			"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+			"dev": true,
+			"dependencies": {
+				"mimic-fn": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/vitest/node_modules/path-key": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+			"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/vitest/node_modules/strip-final-newline": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+			"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+			"dev": true,
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/w3c-keyname": {
+			"version": "2.2.8",
+			"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+			"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
+		},
+		"node_modules/walk-sync": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz",
+			"integrity": "sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==",
+			"dev": true,
+			"dependencies": {
+				"@types/minimatch": "^3.0.3",
+				"ensure-posix-path": "^1.1.0",
+				"matcher-collection": "^2.0.0",
+				"minimatch": "^3.0.4"
+			},
+			"engines": {
+				"node": "8.* || >= 10.*"
+			}
+		},
+		"node_modules/walk-sync/node_modules/brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
+		"node_modules/walk-sync/node_modules/minimatch": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^1.1.7"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/web-worker": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz",
+			"integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA=="
+		},
+		"node_modules/wheel": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz",
+			"integrity": "sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA=="
+		},
+		"node_modules/which": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+			"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+			"dependencies": {
+				"isexe": "^2.0.0"
+			},
+			"bin": {
+				"node-which": "bin/node-which"
+			},
+			"engines": {
+				"node": ">= 8"
+			}
+		},
+		"node_modules/why-is-node-running": {
+			"version": "2.2.2",
+			"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
+			"integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==",
+			"dev": true,
+			"dependencies": {
+				"siginfo": "^2.0.0",
+				"stackback": "0.0.2"
+			},
+			"bin": {
+				"why-is-node-running": "cli.js"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/wrap-ansi": {
+			"version": "8.1.0",
+			"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+			"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+			"dependencies": {
+				"ansi-styles": "^6.1.0",
+				"string-width": "^5.0.1",
+				"strip-ansi": "^7.0.1"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+			}
+		},
+		"node_modules/wrap-ansi-cjs": {
+			"name": "wrap-ansi",
+			"version": "7.0.0",
+			"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+			"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+			"dependencies": {
+				"ansi-styles": "^4.0.0",
+				"string-width": "^4.1.0",
+				"strip-ansi": "^6.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+			}
+		},
+		"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+			"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+		},
+		"node_modules/wrap-ansi-cjs/node_modules/string-width": {
+			"version": "4.2.3",
+			"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+			"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+			"dependencies": {
+				"emoji-regex": "^8.0.0",
+				"is-fullwidth-code-point": "^3.0.0",
+				"strip-ansi": "^6.0.1"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/wrap-ansi/node_modules/ansi-regex": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+			"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/ansi-regex?sponsor=1"
+			}
+		},
+		"node_modules/wrap-ansi/node_modules/ansi-styles": {
+			"version": "6.2.1",
+			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+			"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/ansi-styles?sponsor=1"
+			}
+		},
+		"node_modules/wrap-ansi/node_modules/strip-ansi": {
+			"version": "7.1.0",
+			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+			"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+			"dependencies": {
+				"ansi-regex": "^6.0.1"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/chalk/strip-ansi?sponsor=1"
+			}
+		},
+		"node_modules/wrappy": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+			"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+		},
+		"node_modules/ws": {
+			"version": "8.17.1",
+			"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+			"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+			"engines": {
+				"node": ">=10.0.0"
+			},
+			"peerDependencies": {
+				"bufferutil": "^4.0.1",
+				"utf-8-validate": ">=5.0.2"
+			},
+			"peerDependenciesMeta": {
+				"bufferutil": {
+					"optional": true
+				},
+				"utf-8-validate": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/xmlhttprequest-ssl": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
+			"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
+			"engines": {
+				"node": ">=0.4.0"
+			}
+		},
+		"node_modules/xtend": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+			"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.4"
+			}
+		},
+		"node_modules/yallist": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+			"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+			"engines": {
+				"node": ">=18"
+			}
+		},
+		"node_modules/yaml": {
+			"version": "1.10.2",
+			"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+			"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"node_modules/yauzl": {
+			"version": "2.10.0",
+			"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+			"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+			"dev": true,
+			"dependencies": {
+				"buffer-crc32": "~0.2.3",
+				"fd-slicer": "~1.1.0"
+			}
+		},
+		"node_modules/yocto-queue": {
+			"version": "0.1.0",
+			"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+			"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+			"dev": true,
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		}
+	}
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..319402307e25502da137d4c7d81d7784cb000563
--- /dev/null
+++ b/package.json
@@ -0,0 +1,101 @@
+{
+	"name": "open-webui",
+	"version": "0.3.35",
+	"private": true,
+	"scripts": {
+		"dev": "npm run pyodide:fetch && vite dev --host",
+		"build": "npm run pyodide:fetch && vite build",
+		"preview": "vite preview",
+		"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
+		"lint": "npm run lint:frontend ; npm run lint:types ; npm run lint:backend",
+		"lint:frontend": "eslint . --fix",
+		"lint:types": "npm run check",
+		"lint:backend": "pylint backend/",
+		"format": "prettier --plugin-search-dir --write \"**/*.{js,ts,svelte,css,md,html,json}\"",
+		"format:backend": "black . --exclude \".venv/|/venv/\"",
+		"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write \"src/lib/i18n/**/*.{js,json}\"",
+		"cy:open": "cypress open",
+		"test:frontend": "vitest --passWithNoTests",
+		"pyodide:fetch": "node scripts/prepare-pyodide.js"
+	},
+	"devDependencies": {
+		"@sveltejs/adapter-auto": "3.2.2",
+		"@sveltejs/adapter-static": "^3.0.2",
+		"@sveltejs/kit": "^2.5.20",
+		"@sveltejs/vite-plugin-svelte": "^3.1.1",
+		"@tailwindcss/typography": "^0.5.13",
+		"@typescript-eslint/eslint-plugin": "^6.17.0",
+		"@typescript-eslint/parser": "^6.17.0",
+		"autoprefixer": "^10.4.16",
+		"cypress": "^13.15.0",
+		"eslint": "^8.56.0",
+		"eslint-config-prettier": "^9.1.0",
+		"eslint-plugin-cypress": "^3.4.0",
+		"eslint-plugin-svelte": "^2.43.0",
+		"i18next-parser": "^9.0.1",
+		"postcss": "^8.4.31",
+		"prettier": "^3.3.3",
+		"prettier-plugin-svelte": "^3.2.6",
+		"svelte": "^4.2.18",
+		"svelte-check": "^3.8.5",
+		"svelte-confetti": "^1.3.2",
+		"tailwindcss": "^3.3.3",
+		"tslib": "^2.4.1",
+		"typescript": "^5.5.4",
+		"vite": "^5.3.5",
+		"vitest": "^1.6.0"
+	},
+	"type": "module",
+	"dependencies": {
+		"@codemirror/lang-javascript": "^6.2.2",
+		"@codemirror/lang-python": "^6.1.6",
+		"@codemirror/language-data": "^6.5.1",
+		"@codemirror/theme-one-dark": "^6.1.2",
+		"@huggingface/transformers": "^3.0.0",
+		"@pyscript/core": "^0.4.32",
+		"@sveltejs/adapter-node": "^2.0.0",
+		"@xyflow/svelte": "^0.1.19",
+		"async": "^3.2.5",
+		"bits-ui": "^0.19.7",
+		"codemirror": "^6.0.1",
+		"crc-32": "^1.2.2",
+		"dayjs": "^1.11.10",
+		"dompurify": "^3.1.6",
+		"eventsource-parser": "^1.1.2",
+		"file-saver": "^2.0.5",
+		"fuse.js": "^7.0.0",
+		"highlight.js": "^11.9.0",
+		"i18next": "^23.10.0",
+		"i18next-browser-languagedetector": "^7.2.0",
+		"i18next-resources-to-backend": "^1.2.0",
+		"idb": "^7.1.1",
+		"js-sha256": "^0.10.1",
+		"katex": "^0.16.9",
+		"marked": "^9.1.0",
+		"mermaid": "^10.9.3",
+		"paneforge": "^0.0.6",
+		"panzoom": "^9.4.3",
+		"prosemirror-commands": "^1.6.0",
+		"prosemirror-example-setup": "^1.2.3",
+		"prosemirror-history": "^1.4.1",
+		"prosemirror-keymap": "^1.2.2",
+		"prosemirror-markdown": "^1.13.1",
+		"prosemirror-model": "^1.23.0",
+		"prosemirror-schema-basic": "^1.2.3",
+		"prosemirror-schema-list": "^1.4.1",
+		"prosemirror-state": "^1.4.3",
+		"prosemirror-view": "^1.34.3",
+		"pyodide": "^0.26.1",
+		"socket.io-client": "^4.2.0",
+		"sortablejs": "^1.15.2",
+		"svelte-sonner": "^0.3.19",
+		"tippy.js": "^6.3.7",
+		"turndown": "^7.2.0",
+		"uuid": "^9.0.1"
+	},
+	"engines": {
+		"node": ">=18.13.0 <=22.x.x",
+		"npm": ">=6.0.0"
+	}
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..0f7721681d725ddea512a5ed734891cf6545ca3c
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+	plugins: {
+		tailwindcss: {},
+		autoprefixer: {}
+	}
+};
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..b6248063da71f7d1d61999e90f16ddf0731b7ea9
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,145 @@
+[project]
+name = "open-webui"
+description = "Open WebUI"
+authors = [
+    { name = "Timothy Jaeryang Baek", email = "tim@openwebui.com" }
+]
+license = { file = "LICENSE" }
+dependencies = [
+    "fastapi==0.111.0",
+    "uvicorn[standard]==0.30.6",
+    "pydantic==2.9.2",
+    "python-multipart==0.0.9",
+
+    "Flask==3.0.3",
+    "Flask-Cors==5.0.0",
+
+    "python-socketio==5.11.3",
+    "python-jose==3.3.0",
+    "passlib[bcrypt]==1.7.4",
+
+    "requests==2.32.3",
+    "aiohttp==3.10.8",
+    "async-timeout",
+
+    "sqlalchemy==2.0.32",
+    "alembic==1.13.2",
+    "peewee==3.17.6",
+    "peewee-migrate==1.12.2",
+    "psycopg2-binary==2.9.9",
+    "PyMySQL==1.1.1",
+    "bcrypt==4.2.0",
+
+    "pymongo",
+    "redis",
+    "boto3==1.35.0",
+
+    "argon2-cffi==23.1.0",
+    "APScheduler==3.10.4",
+
+    "openai",
+    "anthropic",
+    "google-generativeai==0.7.2",
+    "tiktoken",
+
+    "langchain==0.2.15",
+    "langchain-community==0.2.12",
+    "langchain-chroma==0.1.4",
+
+    "fake-useragent==1.5.1",
+    "chromadb==0.5.9",
+    "pymilvus==2.4.7",
+
+    "sentence-transformers==3.2.0",
+    "colbert-ai==0.2.21",
+    "einops==0.8.0",
+
+    "ftfy==6.2.3",
+    "pypdf==4.3.1",
+    "xhtml2pdf==0.2.16",
+    "pymdown-extensions==10.11.2",
+    "docx2txt==0.8",
+    "python-pptx==1.0.0",
+    "unstructured==0.15.9",
+    "nltk==3.9.1",
+    "Markdown==3.7",
+    "pypandoc==1.13",
+    "pandas==2.2.3",
+    "openpyxl==3.1.5",
+    "pyxlsb==1.0.10",
+    "xlrd==2.0.1",
+    "validators==0.33.0",
+    "psutil",
+
+    "opencv-python-headless==4.10.0.84",
+    "rapidocr-onnxruntime==1.3.24",
+
+    "fpdf2==2.7.9",
+    "rank-bm25==0.2.2",
+
+    "faster-whisper==1.0.3",
+
+    "PyJWT[crypto]==2.9.0",
+    "authlib==1.3.2",
+
+    "black==24.8.0",
+    "langfuse==2.44.0",
+    "youtube-transcript-api==0.6.2",
+    "pytube==15.0.0",
+
+    "extract_msg",
+    "pydub",
+    "duckduckgo-search~=6.2.13",
+
+    "docker~=7.1.0",
+    "pytest~=8.3.2",
+    "pytest-docker~=3.1.1",
+
+    "googleapis-common-protos==1.63.2"
+]
+readme = "README.md"
+requires-python = ">= 3.11, < 3.12.0a1"
+dynamic = ["version"]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "License :: OSI Approved :: MIT License",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.11",
+    "Topic :: Communications :: Chat",
+    "Topic :: Multimedia",
+]
+
+[project.scripts]
+open-webui = "open_webui:app"
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.rye]
+managed = true
+dev-dependencies = []
+
+[tool.hatch.metadata]
+allow-direct-references = true
+
+[tool.hatch.version]
+path = "package.json"
+pattern = '"version":\s*"(?P<version>[^"]+)"'
+
+[tool.hatch.build.hooks.custom]  # keep this for reading hooks from `hatch_build.py`
+
+[tool.hatch.build.targets.wheel]
+sources = ["backend"]
+exclude = [
+    ".dockerignore",
+    ".gitignore",
+    ".webui_secret_key",
+    "dev.sh",
+    "requirements.txt",
+    "start.sh",
+    "start_windows.bat",
+    "webui.db",
+    "chroma.sqlite3",
+]
+force-include = { "CHANGELOG.md" = "open_webui/CHANGELOG.md", build = "open_webui/frontend" }
diff --git a/run-compose.sh b/run-compose.sh
new file mode 100755
index 0000000000000000000000000000000000000000..21574e95997fd512bc243adf0fa77456e570010d
--- /dev/null
+++ b/run-compose.sh
@@ -0,0 +1,241 @@
+#!/bin/bash
+
+# Define color and formatting codes
+BOLD='\033[1m'
+GREEN='\033[1;32m'
+WHITE='\033[1;37m'
+RED='\033[0;31m'
+NC='\033[0m' # No Color
+# Unicode character for tick mark
+TICK='\u2713'
+
+# Detect GPU driver
+get_gpu_driver() {
+    # Detect NVIDIA GPUs using lspci or nvidia-smi
+    if lspci | grep -i nvidia >/dev/null || nvidia-smi >/dev/null 2>&1; then
+        echo "nvidia"
+        return
+    fi
+
+    # Detect AMD GPUs (including GCN architecture check for amdgpu vs radeon)
+    if lspci | grep -i amd >/dev/null; then
+        # List of known GCN and later architecture cards
+        # This is a simplified list, and in a real-world scenario, you'd want a more comprehensive one
+        local gcn_and_later=("Radeon HD 7000" "Radeon HD 8000" "Radeon R5" "Radeon R7" "Radeon R9" "Radeon RX")
+
+        # Get GPU information
+        local gpu_info=$(lspci | grep -i 'vga.*amd')
+
+        for model in "${gcn_and_later[@]}"; do
+            if echo "$gpu_info" | grep -iq "$model"; then
+                echo "amdgpu"
+                return
+            fi
+        done
+
+        # Default to radeon if no GCN or later architecture is detected
+        echo "radeon"
+        return
+    fi
+
+    # Detect Intel GPUs
+    if lspci | grep -i intel >/dev/null; then
+        echo "i915"
+        return
+    fi
+
+    # If no known GPU is detected
+    echo "Unknown or unsupported GPU driver"
+    exit 1
+}
+
+# Function for rolling animation
+show_loading() {
+    local spin='-\|/'
+    local i=0
+
+    printf " "
+
+    while kill -0 $1 2>/dev/null; do
+        i=$(( (i+1) %4 ))
+        printf "\b${spin:$i:1}"
+        sleep .1
+    done
+
+    # Replace the spinner with a tick
+    printf "\b${GREEN}${TICK}${NC}"
+}
+
+# Usage information
+usage() {
+    echo "Usage: $0 [OPTIONS]"
+    echo "Options:"
+    echo "  --enable-gpu[count=COUNT]  Enable GPU support with the specified count."
+    echo "  --enable-api[port=PORT]    Enable API and expose it on the specified port."
+    echo "  --webui[port=PORT]         Set the port for the web user interface."
+    echo "  --data[folder=PATH]        Bind mount for ollama data folder (by default will create the 'ollama' volume)."
+    echo "  --build                    Build the docker image before running the compose project."
+    echo "  --drop                     Drop the compose project."
+    echo "  -q, --quiet                Run script in headless mode."
+    echo "  -h, --help                 Show this help message."
+    echo ""
+    echo "Examples:"
+    echo "  $0 --drop"
+    echo "  $0 --enable-gpu[count=1]"
+    echo "  $0 --enable-gpu[count=all]"
+    echo "  $0 --enable-api[port=11435]"
+    echo "  $0 --enable-gpu[count=1] --enable-api[port=12345] --webui[port=3000]"
+    echo "  $0 --enable-gpu[count=1] --enable-api[port=12345] --webui[port=3000] --data[folder=./ollama-data]"
+    echo "  $0 --enable-gpu[count=1] --enable-api[port=12345] --webui[port=3000] --data[folder=./ollama-data] --build"
+    echo ""
+    echo "This script configures and runs a docker-compose setup with optional GPU support, API exposure, and web UI configuration."
+    echo "About the gpu to use, the script automatically detects it using the "lspci" command."
+    echo "In this case the gpu detected is: $(get_gpu_driver)"
+}
+
+# Default values
+gpu_count=1
+api_port=11435
+webui_port=3000
+headless=false
+build_image=false
+kill_compose=false
+
+# Function to extract value from the parameter
+extract_value() {
+    echo "$1" | sed -E 's/.*\[.*=(.*)\].*/\1/; t; s/.*//'
+}
+
+# Parse arguments
+while [[ $# -gt 0 ]]; do
+    key="$1"
+
+    case $key in
+        --enable-gpu*)
+            enable_gpu=true
+            value=$(extract_value "$key")
+            gpu_count=${value:-1}
+            ;;
+        --enable-api*)
+            enable_api=true
+            value=$(extract_value "$key")
+            api_port=${value:-11435}
+            ;;
+        --webui*)
+            value=$(extract_value "$key")
+            webui_port=${value:-3000}
+            ;;
+        --data*)
+            value=$(extract_value "$key")
+            data_dir=${value:-"./ollama-data"}
+            ;;
+        --drop)
+            kill_compose=true
+            ;;
+        --build)
+            build_image=true
+            ;;
+        -q|--quiet)
+            headless=true
+            ;;
+        -h|--help)
+            usage
+            exit
+            ;;
+        *)
+            # Unknown option
+            echo "Unknown option: $key"
+            usage
+            exit 1
+            ;;
+    esac
+    shift # past argument or value
+done
+
+if [[ $kill_compose == true ]]; then
+    docker compose down --remove-orphans
+    echo -e "${GREEN}${BOLD}Compose project dropped successfully.${NC}"
+    exit
+else
+    DEFAULT_COMPOSE_COMMAND="docker compose -f docker-compose.yaml"
+    if [[ $enable_gpu == true ]]; then
+        # Validate and process command-line arguments
+        if [[ -n $gpu_count ]]; then
+            if ! [[ $gpu_count =~ ^([0-9]+|all)$ ]]; then
+                echo "Invalid GPU count: $gpu_count"
+                exit 1
+            fi
+            echo "Enabling GPU with $gpu_count GPUs"
+            # Add your GPU allocation logic here
+            export OLLAMA_GPU_DRIVER=$(get_gpu_driver)
+            export OLLAMA_GPU_COUNT=$gpu_count # Set OLLAMA_GPU_COUNT environment variable
+        fi
+        DEFAULT_COMPOSE_COMMAND+=" -f docker-compose.gpu.yaml"
+    fi
+    if [[ $enable_api == true ]]; then
+        DEFAULT_COMPOSE_COMMAND+=" -f docker-compose.api.yaml"
+        if [[ -n $api_port ]]; then
+            export OLLAMA_WEBAPI_PORT=$api_port # Set OLLAMA_WEBAPI_PORT environment variable
+        fi
+    fi
+    if [[ -n $data_dir ]]; then
+        DEFAULT_COMPOSE_COMMAND+=" -f docker-compose.data.yaml"
+        export OLLAMA_DATA_DIR=$data_dir # Set OLLAMA_DATA_DIR environment variable
+    fi
+    if [[ -n $webui_port ]]; then
+        export OPEN_WEBUI_PORT=$webui_port # Set OPEN_WEBUI_PORT environment variable
+    fi
+    DEFAULT_COMPOSE_COMMAND+=" up -d"
+    DEFAULT_COMPOSE_COMMAND+=" --remove-orphans"
+    DEFAULT_COMPOSE_COMMAND+=" --force-recreate"
+    if [[ $build_image == true ]]; then
+        DEFAULT_COMPOSE_COMMAND+=" --build"
+    fi
+fi
+
+# Recap of environment variables
+echo
+echo -e "${WHITE}${BOLD}Current Setup:${NC}"
+echo -e "   ${GREEN}${BOLD}GPU Driver:${NC} ${OLLAMA_GPU_DRIVER:-Not Enabled}"
+echo -e "   ${GREEN}${BOLD}GPU Count:${NC} ${OLLAMA_GPU_COUNT:-Not Enabled}"
+echo -e "   ${GREEN}${BOLD}WebAPI Port:${NC} ${OLLAMA_WEBAPI_PORT:-Not Enabled}"
+echo -e "   ${GREEN}${BOLD}Data Folder:${NC} ${data_dir:-Using ollama volume}"
+echo -e "   ${GREEN}${BOLD}WebUI Port:${NC} $webui_port"
+echo
+
+if [[ $headless == true ]]; then
+    echo -ne "${WHITE}${BOLD}Running in headless mode... ${NC}"
+    choice="y"
+else
+    # Ask for user acceptance
+    echo -ne "${WHITE}${BOLD}Do you want to proceed with current setup? (Y/n): ${NC}"
+    read -n1 -s choice
+fi
+
+echo
+
+if [[ $choice == "" || $choice == "y" ]]; then
+    # Execute the command with the current user
+    eval "$DEFAULT_COMPOSE_COMMAND" &
+
+    # Capture the background process PID
+    PID=$!
+
+    # Display the loading animation
+    #show_loading $PID
+
+    # Wait for the command to finish
+    wait $PID
+
+    echo
+    # Check exit status
+    if [ $? -eq 0 ]; then
+        echo -e "${GREEN}${BOLD}Compose project started successfully.${NC}"
+    else
+        echo -e "${RED}${BOLD}There was an error starting the compose project.${NC}"
+    fi
+else
+    echo "Aborted."
+fi
+
+echo
diff --git a/run-ollama-docker.sh b/run-ollama-docker.sh
new file mode 100644
index 0000000000000000000000000000000000000000..c2a025bea3fa88beab7c0e6640de682dc8169c02
--- /dev/null
+++ b/run-ollama-docker.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+host_port=11434
+container_port=11434
+
+read -r -p "Do you want ollama in Docker with GPU support? (y/n): " use_gpu
+
+docker rm -f ollama || true
+docker pull ollama/ollama:latest
+
+docker_args="-d -v ollama:/root/.ollama -p $host_port:$container_port --name ollama ollama/ollama"
+
+if [ "$use_gpu" = "y" ]; then
+    docker_args="--gpus=all $docker_args"
+fi
+
+docker run $docker_args
+
+docker image prune -f
\ No newline at end of file
diff --git a/run.sh b/run.sh
new file mode 100644
index 0000000000000000000000000000000000000000..6793fe16271c0b56b9ad1cc409d533bfe1e6ca83
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+image_name="open-webui"
+container_name="open-webui"
+host_port=3000
+container_port=8080
+
+docker build -t "$image_name" .
+docker stop "$container_name" &>/dev/null || true
+docker rm "$container_name" &>/dev/null || true
+
+docker run -d -p "$host_port":"$container_port" \
+    --add-host=host.docker.internal:host-gateway \
+    -v "${image_name}:/app/backend/data" \
+    --name "$container_name" \
+    --restart always \
+    "$image_name"
+
+docker image prune -f
diff --git a/scripts/prepare-pyodide.js b/scripts/prepare-pyodide.js
new file mode 100644
index 0000000000000000000000000000000000000000..5aaac5927e7dbf5674bd3dc4839b9ed1548bf7b4
--- /dev/null
+++ b/scripts/prepare-pyodide.js
@@ -0,0 +1,85 @@
+const packages = [
+	'micropip',
+	'packaging',
+	'requests',
+	'beautifulsoup4',
+	'numpy',
+	'pandas',
+	'matplotlib',
+	'scikit-learn',
+	'scipy',
+	'regex',
+	'seaborn'
+];
+
+import { loadPyodide } from 'pyodide';
+import { writeFile, readFile, copyFile, readdir, rmdir } from 'fs/promises';
+
+async function downloadPackages() {
+	console.log('Setting up pyodide + micropip');
+
+	let pyodide;
+	try {
+		pyodide = await loadPyodide({
+			packageCacheDir: 'static/pyodide'
+		});
+	} catch (err) {
+		console.error('Failed to load Pyodide:', err);
+		return;
+	}
+
+	const packageJson = JSON.parse(await readFile('package.json'));
+	const pyodideVersion = packageJson.dependencies.pyodide.replace('^', '');
+
+	try {
+		const pyodidePackageJson = JSON.parse(await readFile('static/pyodide/package.json'));
+		const pyodidePackageVersion = pyodidePackageJson.version.replace('^', '');
+
+		if (pyodideVersion !== pyodidePackageVersion) {
+			console.log('Pyodide version mismatch, removing static/pyodide directory');
+			await rmdir('static/pyodide', { recursive: true });
+		}
+	} catch (e) {
+		console.log('Pyodide package not found, proceeding with download.');
+	}
+
+	try {
+		console.log('Loading micropip package');
+		await pyodide.loadPackage('micropip');
+
+		const micropip = pyodide.pyimport('micropip');
+		console.log('Downloading Pyodide packages:', packages);
+
+		try {
+			for (const pkg of packages) {
+				console.log(`Installing package: ${pkg}`);
+				await micropip.install(pkg);
+			}
+		} catch (err) {
+			console.error('Package installation failed:', err);
+			return;
+		}
+
+		console.log('Pyodide packages downloaded, freezing into lock file');
+
+		try {
+			const lockFile = await micropip.freeze();
+			await writeFile('static/pyodide/pyodide-lock.json', lockFile);
+		} catch (err) {
+			console.error('Failed to write lock file:', err);
+		}
+	} catch (err) {
+		console.error('Failed to load or install micropip:', err);
+	}
+}
+
+async function copyPyodide() {
+	console.log('Copying Pyodide files into static directory');
+	// Copy all files from node_modules/pyodide to static/pyodide
+	for await (const entry of await readdir('node_modules/pyodide')) {
+		await copyFile(`node_modules/pyodide/${entry}`, `static/pyodide/${entry}`);
+	}
+}
+
+await downloadPackages();
+await copyPyodide();
diff --git a/src/app.css b/src/app.css
new file mode 100644
index 0000000000000000000000000000000000000000..ca5249bbd1b95b3aa9bfa38b7b34cd53dd3290f5
--- /dev/null
+++ b/src/app.css
@@ -0,0 +1,207 @@
+@font-face {
+	font-family: 'Inter';
+	src: url('/assets/fonts/Inter-Variable.ttf');
+	font-display: swap;
+}
+
+@font-face {
+	font-family: 'Archivo';
+	src: url('/assets/fonts/Archivo-Variable.ttf');
+	font-display: swap;
+}
+
+@font-face {
+	font-family: 'Mona Sans';
+	src: url('/assets/fonts/Mona-Sans.woff2');
+	font-display: swap;
+}
+
+html {
+	word-break: break-word;
+}
+
+code {
+	/* white-space-collapse: preserve !important; */
+	overflow-x: auto;
+	width: auto;
+}
+
+math {
+	margin-top: 1rem;
+}
+
+.hljs {
+	@apply rounded-lg;
+}
+
+.input-prose {
+	@apply prose dark:prose-invert prose-p:my-0 prose-img:my-1 prose-headings:my-1 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-ul:-my-0 prose-ol:-my-0 prose-li:-my-0 whitespace-pre-line;
+}
+
+.input-prose-sm {
+	@apply prose dark:prose-invert prose-p:my-0 prose-img:my-1 prose-headings:my-1 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-ul:-my-0 prose-ol:-my-0 prose-li:-my-0 whitespace-pre-line text-sm;
+}
+
+.markdown-prose {
+	@apply prose dark:prose-invert prose-p:my-0 prose-img:my-1 prose-headings:my-1 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-ul:-my-0 prose-ol:-my-0 prose-li:-my-0 whitespace-pre-line;
+}
+
+.markdown a {
+	@apply underline;
+}
+
+.font-primary {
+	font-family: 'Archivo', sans-serif;
+}
+
+iframe {
+	@apply rounded-lg;
+}
+
+li p {
+	display: inline;
+}
+
+::-webkit-scrollbar-thumb {
+	--tw-border-opacity: 1;
+	background-color: rgba(236, 236, 236, 0.8);
+	border-color: rgba(255, 255, 255, var(--tw-border-opacity));
+	border-radius: 9999px;
+	border-width: 1px;
+}
+
+/* Dark theme scrollbar styles */
+.dark ::-webkit-scrollbar-thumb {
+	background-color: rgba(33, 33, 33, 0.8); /* Darker color for dark theme */
+	border-color: rgba(0, 0, 0, var(--tw-border-opacity));
+}
+
+::-webkit-scrollbar {
+	height: 0.4rem;
+	width: 0.4rem;
+}
+
+::-webkit-scrollbar-track {
+	background-color: transparent;
+	border-radius: 9999px;
+}
+
+select {
+	background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
+	background-position: right 0.5rem center;
+	background-repeat: no-repeat;
+	background-size: 1.5em 1.5em;
+	padding-right: 2.5rem;
+	-webkit-print-color-adjust: exact;
+	print-color-adjust: exact;
+	/* for Firefox */
+	-moz-appearance: none;
+	/* for Chrome */
+	-webkit-appearance: none;
+}
+
+.katex-mathml {
+	display: none;
+}
+
+.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
+	visibility: visible;
+}
+.scrollbar-hidden::-webkit-scrollbar-thumb {
+	visibility: hidden;
+}
+
+.scrollbar-hidden::-webkit-scrollbar-corner {
+	display: none;
+}
+
+.scrollbar-none::-webkit-scrollbar {
+	display: none; /* for Chrome, Safari and Opera */
+}
+
+.scrollbar-none::-webkit-scrollbar-corner {
+	display: none;
+}
+
+.scrollbar-none {
+	-ms-overflow-style: none; /* IE and Edge */
+	scrollbar-width: none; /* Firefox */
+}
+
+input::-webkit-outer-spin-button,
+input::-webkit-inner-spin-button {
+	/* display: none; <- Crashes Chrome on hover */
+	-webkit-appearance: none;
+	margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
+}
+
+input[type='number'] {
+	-moz-appearance: textfield; /* Firefox */
+}
+
+.cm-editor {
+	height: 100%;
+	width: 100%;
+}
+
+.cm-scroller {
+	@apply scrollbar-hidden;
+}
+
+.cm-editor.cm-focused {
+	outline: none;
+}
+
+.tippy-box[data-theme~='dark'] {
+	@apply rounded-lg bg-gray-950 text-xs border border-gray-900 shadow-xl;
+}
+
+.password {
+	-webkit-text-security: disc;
+}
+
+.codespan {
+	color: #eb5757;
+	border-width: 0px;
+	padding: 3px 8px;
+	font-size: 0.8em;
+	font-weight: 600;
+	@apply rounded-md dark:bg-gray-800 bg-gray-100 mx-0.5;
+}
+
+.svelte-flow {
+	background-color: transparent !important;
+}
+
+.svelte-flow__edge > path {
+	stroke-width: 0.5;
+}
+
+.svelte-flow__edge.animated > path {
+	stroke-width: 2;
+	@apply stroke-gray-600 dark:stroke-gray-500;
+}
+
+.bg-gray-950-90 {
+	background-color: rgba(var(--color-gray-950, #0d0d0d), 0.9);
+}
+
+.ProseMirror {
+	@apply h-full  min-h-fit max-h-full whitespace-pre-wrap;
+}
+
+.ProseMirror:focus {
+	outline: none;
+}
+
+.placeholder::after {
+	content: attr(data-placeholder);
+	cursor: text;
+	pointer-events: none;
+
+	float: left;
+
+	@apply absolute inset-0 z-0 text-gray-500;
+}
diff --git a/src/app.d.ts b/src/app.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f59b884c51ed3c31fc0738fd38d0d75b580df5e4
--- /dev/null
+++ b/src/app.d.ts
@@ -0,0 +1,12 @@
+// See https://kit.svelte.dev/docs/types#app
+// for information about these interfaces
+declare global {
+	namespace App {
+		// interface Error {}
+		// interface Locals {}
+		// interface PageData {}
+		// interface Platform {}
+	}
+}
+
+export {};
diff --git a/src/app.html b/src/app.html
new file mode 100644
index 0000000000000000000000000000000000000000..f6e46c9cfbe85f6fea6a16c5dc489d6873121392
--- /dev/null
+++ b/src/app.html
@@ -0,0 +1,231 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<link rel="icon" href="%sveltekit.assets%/favicon.png" />
+		<link rel="apple-touch-icon" href="%sveltekit.assets%/favicon.png" />
+		<link rel="manifest" href="%sveltekit.assets%/manifest.json" crossorigin="use-credentials" />
+		<meta
+			name="viewport"
+			content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover"
+		/>
+		<meta name="theme-color" content="#171717" />
+		<meta name="robots" content="noindex,nofollow" />
+		<meta name="description" content="Open WebUI" />
+		<link
+			rel="search"
+			type="application/opensearchdescription+xml"
+			title="Open WebUI"
+			href="/opensearch.xml"
+		/>
+
+		<script>
+			function resizeIframe(obj) {
+				obj.style.height = obj.contentWindow.document.documentElement.scrollHeight + 'px';
+			}
+		</script>
+
+		<script>
+			// On page load or when changing themes, best to add inline in `head` to avoid FOUC
+			(() => {
+				const metaThemeColorTag = document.querySelector('meta[name="theme-color"]');
+				const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
+
+				if (!localStorage?.theme) {
+					localStorage.theme = 'system';
+				}
+
+				if (localStorage.theme === 'system') {
+					document.documentElement.classList.add(prefersDarkTheme ? 'dark' : 'light');
+					metaThemeColorTag.setAttribute('content', prefersDarkTheme ? '#171717' : '#ffffff');
+				} else if (localStorage.theme === 'oled-dark') {
+					document.documentElement.style.setProperty('--color-gray-800', '#101010');
+					document.documentElement.style.setProperty('--color-gray-850', '#050505');
+					document.documentElement.style.setProperty('--color-gray-900', '#000000');
+					document.documentElement.style.setProperty('--color-gray-950', '#000000');
+					document.documentElement.classList.add('dark');
+					metaThemeColorTag.setAttribute('content', '#000000');
+				} else if (localStorage.theme === 'light') {
+					document.documentElement.classList.add('light');
+					metaThemeColorTag.setAttribute('content', '#ffffff');
+				} else if (localStorage.theme === 'her') {
+					document.documentElement.classList.add('dark');
+					document.documentElement.classList.add('her');
+					metaThemeColorTag.setAttribute('content', '#983724');
+				} else {
+					document.documentElement.classList.add('dark');
+					metaThemeColorTag.setAttribute('content', '#171717');
+				}
+
+				window.matchMedia('(prefers-color-scheme: dark)').addListener((e) => {
+					if (localStorage.theme === 'system') {
+						if (e.matches) {
+							document.documentElement.classList.add('dark');
+							document.documentElement.classList.remove('light');
+							metaThemeColorTag.setAttribute('content', '#171717');
+						} else {
+							document.documentElement.classList.add('light');
+							document.documentElement.classList.remove('dark');
+							metaThemeColorTag.setAttribute('content', '#ffffff');
+						}
+					}
+				});
+			})();
+		</script>
+
+		<title>Open WebUI</title>
+
+		%sveltekit.head%
+	</head>
+
+	<body data-sveltekit-preload-data="hover">
+		<div style="display: contents">%sveltekit.body%</div>
+
+		<div
+			id="splash-screen"
+			style="position: fixed; z-index: 100; top: 0; left: 0; width: 100%; height: 100%"
+		>
+			<style type="text/css" nonce="">
+				html {
+					overflow-y: scroll !important;
+				}
+			</style>
+
+			<img
+				id="logo"
+				style="
+					position: absolute;
+					width: auto;
+					height: 6rem;
+					top: 44%;
+					left: 50%;
+					transform: translateX(-50%);
+				"
+				src="/static/splash.png"
+			/>
+
+			<div
+				style="
+					position: absolute;
+					top: 33%;
+					left: 50%;
+
+					width: 24rem;
+					transform: translateX(-50%);
+
+					display: flex;
+					flex-direction: column;
+					align-items: center;
+				"
+			>
+				<img
+					id="logo-her"
+					style="width: auto; height: 13rem"
+					src="/static/splash.png"
+					class="animate-pulse-fast"
+				/>
+
+				<div style="position: relative; width: 24rem; margin-top: 0.5rem">
+					<div
+						id="progress-background"
+						style="
+							position: absolute;
+							width: 100%;
+							height: 0.75rem;
+
+							border-radius: 9999px;
+							background-color: #fafafa9a;
+						"
+					></div>
+
+					<div
+						id="progress-bar"
+						style="
+							position: absolute;
+							width: 0%;
+							height: 0.75rem;
+							border-radius: 9999px;
+							background-color: #fff;
+						"
+						class="bg-white"
+					></div>
+				</div>
+			</div>
+
+			<!-- <span style="position: absolute; bottom: 32px; left: 50%; margin: -36px 0 0 -36px">
+				Footer content
+			</span> -->
+		</div>
+	</body>
+</html>
+
+<style type="text/css" nonce="">
+	html {
+		overflow-y: hidden !important;
+	}
+
+	#splash-screen {
+		background: #fff;
+	}
+
+	html.dark #splash-screen {
+		background: #000;
+	}
+
+	html.dark #splash-screen img {
+		filter: invert(1);
+	}
+
+	html.her #splash-screen {
+		background: #983724;
+	}
+
+	#logo-her {
+		display: none;
+	}
+
+	#progress-background {
+		display: none;
+	}
+
+	#progress-bar {
+		display: none;
+	}
+
+	html.her #logo {
+		display: none;
+	}
+
+	html.her #logo-her {
+		display: block;
+		filter: invert(1);
+	}
+
+	html.her #progress-background {
+		display: block;
+	}
+
+	html.her #progress-bar {
+		display: block;
+	}
+
+	@media (max-width: 24rem) {
+		html.her #progress-background {
+			display: none;
+		}
+
+		html.her #progress-bar {
+			display: none;
+		}
+	}
+
+	@keyframes pulse {
+		50% {
+			opacity: 0.65;
+		}
+	}
+
+	.animate-pulse-fast {
+		animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+	}
+</style>
diff --git a/src/lib/apis/audio/index.ts b/src/lib/apis/audio/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5cd6ab949ca469f54dbec0cc43a7d7e784340306
--- /dev/null
+++ b/src/lib/apis/audio/index.ts
@@ -0,0 +1,193 @@
+import { AUDIO_API_BASE_URL } from '$lib/constants';
+
+export const getAudioConfig = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${AUDIO_API_BASE_URL}/config`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+type OpenAIConfigForm = {
+	url: string;
+	key: string;
+	model: string;
+	speaker: string;
+};
+
+export const updateAudioConfig = async (token: string, payload: OpenAIConfigForm) => {
+	let error = null;
+
+	const res = await fetch(`${AUDIO_API_BASE_URL}/config/update`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...payload
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const transcribeAudio = async (token: string, file: File) => {
+	const data = new FormData();
+	data.append('file', file);
+
+	let error = null;
+	const res = await fetch(`${AUDIO_API_BASE_URL}/transcriptions`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: data
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const synthesizeOpenAISpeech = async (
+	token: string = '',
+	speaker: string = 'alloy',
+	text: string = '',
+	model?: string
+) => {
+	let error = null;
+
+	const res = await fetch(`${AUDIO_API_BASE_URL}/speech`, {
+		method: 'POST',
+		headers: {
+			Authorization: `Bearer ${token}`,
+			'Content-Type': 'application/json'
+		},
+		body: JSON.stringify({
+			input: text,
+			voice: speaker,
+			...(model && { model })
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+interface AvailableModelsResponse {
+	models: { name: string; id: string }[] | { id: string }[];
+}
+
+export const getModels = async (token: string = ''): Promise<AvailableModelsResponse> => {
+	let error = null;
+
+	const res = await fetch(`${AUDIO_API_BASE_URL}/models`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getVoices = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${AUDIO_API_BASE_URL}/voices`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/auths/index.ts b/src/lib/apis/auths/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..30f21c3029fc1e0abe855891cec4771d33f5db4a
--- /dev/null
+++ b/src/lib/apis/auths/index.ts
@@ -0,0 +1,550 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const getAdminDetails = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/admin/details`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getAdminConfig = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/admin/config`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateAdminConfig = async (token: string, body: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/admin/config`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify(body)
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getSessionUser = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		credentials: 'include'
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const userSignIn = async (email: string, password: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signin`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json'
+		},
+		credentials: 'include',
+		body: JSON.stringify({
+			email: email,
+			password: password
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const userSignUp = async (
+	name: string,
+	email: string,
+	password: string,
+	profile_image_url: string
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json'
+		},
+		credentials: 'include',
+		body: JSON.stringify({
+			name: name,
+			email: email,
+			password: password,
+			profile_image_url: profile_image_url
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const userSignOut = async () => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signout`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json'
+		},
+		credentials: 'include'
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res;
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+};
+
+export const addUser = async (
+	token: string,
+	name: string,
+	email: string,
+	password: string,
+	role: string = 'pending'
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/add`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			name: name,
+			email: email,
+			password: password,
+			role: role
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateUserProfile = async (token: string, name: string, profileImageUrl: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/update/profile`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			name: name,
+			profile_image_url: profileImageUrl
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateUserPassword = async (token: string, password: string, newPassword: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/update/password`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			password: password,
+			new_password: newPassword
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getSignUpEnabledStatus = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup/enabled`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getDefaultUserRole = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup/user/role`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateDefaultUserRole = async (token: string, role: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup/user/role`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			role: role
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const toggleSignUpEnabledStatus = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup/enabled/toggle`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getJWTExpiresDuration = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/token/expires`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateJWTExpiresDuration = async (token: string, duration: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/token/expires/update`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			duration: duration
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const createAPIKey = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/api_key`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+	if (error) {
+		throw error;
+	}
+	return res.api_key;
+};
+
+export const getAPIKey = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/api_key`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+	if (error) {
+		throw error;
+	}
+	return res.api_key;
+};
+
+export const deleteAPIKey = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/api_key`, {
+		method: 'DELETE',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+	if (error) {
+		throw error;
+	}
+	return res;
+};
diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d93d21c73abf4d2726dbc38f67ea5d8398c1217b
--- /dev/null
+++ b/src/lib/apis/chats/index.ts
@@ -0,0 +1,1013 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+import { getTimeRange } from '$lib/utils';
+
+export const createNewChat = async (token: string, chat: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/new`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			chat: chat
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const importChat = async (
+	token: string,
+	chat: object,
+	meta: object | null,
+	pinned?: boolean,
+	folderId?: string | null
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/import`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			chat: chat,
+			meta: meta ?? {},
+			pinned: pinned,
+			folder_id: folderId
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getChatList = async (token: string = '', page: number | null = null) => {
+	let error = null;
+	const searchParams = new URLSearchParams();
+
+	if (page !== null) {
+		searchParams.append('page', `${page}`);
+	}
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/?${searchParams.toString()}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.map((chat) => ({
+		...chat,
+		time_range: getTimeRange(chat.updated_at)
+	}));
+};
+
+export const getChatListByUserId = async (token: string = '', userId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/list/user/${userId}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.map((chat) => ({
+		...chat,
+		time_range: getTimeRange(chat.updated_at)
+	}));
+};
+
+export const getArchivedChatList = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/archived`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getAllChats = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/all`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getChatListBySearchText = async (token: string, text: string, page: number = 1) => {
+	let error = null;
+
+	const searchParams = new URLSearchParams();
+	searchParams.append('text', text);
+	searchParams.append('page', `${page}`);
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/search?${searchParams.toString()}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.map((chat) => ({
+		...chat,
+		time_range: getTimeRange(chat.updated_at)
+	}));
+};
+
+export const getChatsByFolderId = async (token: string, folderId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/folder/${folderId}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getAllArchivedChats = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/all/archived`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getAllUserChats = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/all/db`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getAllTags = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/all/tags`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getPinnedChatList = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/pinned`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.map((chat) => ({
+		...chat,
+		time_range: getTimeRange(chat.updated_at)
+	}));
+};
+
+export const getChatListByTagName = async (token: string = '', tagName: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/tags`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			name: tagName
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.map((chat) => ({
+		...chat,
+		time_range: getTimeRange(chat.updated_at)
+	}));
+};
+
+export const getChatById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getChatByShareId = async (token: string, share_id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/share/${share_id}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getChatPinnedStatusById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/pinned`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = err;
+			}
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const toggleChatPinnedStatusById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/pin`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = err;
+			}
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const cloneChatById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/clone`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = err;
+			}
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const shareChatById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/share`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateChatFolderIdById = async (token: string, id: string, folderId?: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/folder`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			folder_id: folderId
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const archiveChatById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/archive`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteSharedChatById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/share`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateChatById = async (token: string, id: string, chat: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			chat: chat
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteChatById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getTagsById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/tags`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const addTagById = async (token: string, id: string, tagName: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/tags`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			name: tagName
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteTagById = async (token: string, id: string, tagName: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/tags`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			name: tagName
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+export const deleteTagsById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/tags/all`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteAllChats = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const archiveAllChats = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/chats/archive/all`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/configs/index.ts b/src/lib/apis/configs/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0c4de6ad65617b52cbb60e304112e186b1a2394f
--- /dev/null
+++ b/src/lib/apis/configs/index.ts
@@ -0,0 +1,176 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+import type { Banner } from '$lib/types';
+
+export const importConfig = async (token: string, config) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/import`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			config: config
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const exportConfig = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/export`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const setDefaultModels = async (token: string, models: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/default/models`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			models: models
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const setDefaultPromptSuggestions = async (token: string, promptSuggestions: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/default/suggestions`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			suggestions: promptSuggestions
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getBanners = async (token: string): Promise<Banner[]> => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/banners`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const setBanners = async (token: string, banners: Banner[]) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/configs/banners`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			banners: banners
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/evaluations/index.ts b/src/lib/apis/evaluations/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f6f35f7c18de93af0d188ff61c417698094e5dee
--- /dev/null
+++ b/src/lib/apis/evaluations/index.ts
@@ -0,0 +1,246 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const getConfig = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/config`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateConfig = async (token: string, config: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/config`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...config
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getAllFeedbacks = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/feedbacks/all`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const exportAllFeedbacks = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/feedbacks/all/export`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const createNewFeedback = async (token: string, feedback: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/feedback`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...feedback
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getFeedbackById = async (token: string, feedbackId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/feedback/${feedbackId}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateFeedbackById = async (token: string, feedbackId: string, feedback: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/feedback/${feedbackId}`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...feedback
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteFeedbackById = async (token: string, feedbackId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/feedback/${feedbackId}`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/files/index.ts b/src/lib/apis/files/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6a42ec6147e87315d27d815528e98632401896ac
--- /dev/null
+++ b/src/lib/apis/files/index.ts
@@ -0,0 +1,243 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const uploadFile = async (token: string, file: File) => {
+	const data = new FormData();
+	data.append('file', file);
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/files/`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: data
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const uploadDir = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/files/upload/dir`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getFiles = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/files/`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getFileById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/files/${id}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateFileDataContentById = async (token: string, id: string, content: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/files/${id}/data/content/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			content: content
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getFileContentById = async (id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/files/${id}/content`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json'
+		},
+		credentials: 'include'
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return await res.blob();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteFileById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/files/${id}`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteAllFiles = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/files/all`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/folders/index.ts b/src/lib/apis/folders/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f1a1f5b4832ba0a193fca92a84ef86ee3e3399f6
--- /dev/null
+++ b/src/lib/apis/folders/index.ts
@@ -0,0 +1,269 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const createNewFolder = async (token: string, name: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/folders/`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			name: name
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getFolders = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/folders/`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getFolderById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${id}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateFolderNameById = async (token: string, id: string, name: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${id}/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			name: name
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateFolderIsExpandedById = async (
+	token: string,
+	id: string,
+	isExpanded: boolean
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${id}/update/expanded`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			is_expanded: isExpanded
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateFolderParentIdById = async (token: string, id: string, parentId?: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${id}/update/parent`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			parent_id: parentId
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+type FolderItems = {
+	chat_ids: string[];
+	file_ids: string[];
+};
+
+export const updateFolderItemsById = async (token: string, id: string, items: FolderItems) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${id}/update/items`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			items: items
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteFolderById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${id}`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/functions/index.ts b/src/lib/apis/functions/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ed3306b3214967e98dee60ab0cf14042bc0bf141
--- /dev/null
+++ b/src/lib/apis/functions/index.ts
@@ -0,0 +1,455 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const createNewFunction = async (token: string, func: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/create`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...func
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getFunctions = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const exportFunctions = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/export`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getFunctionById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateFunctionById = async (token: string, id: string, func: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...func
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteFunctionById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/delete`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const toggleFunctionById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/toggle`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const toggleGlobalById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/toggle/global`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getFunctionValvesById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/valves`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getFunctionValvesSpecById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/valves/spec`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateFunctionValvesById = async (token: string, id: string, valves: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/valves/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...valves
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getUserValvesById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/valves/user`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getUserValvesSpecById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/valves/user/spec`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateUserValvesById = async (token: string, id: string, valves: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/functions/id/${id}/valves/user/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...valves
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/images/index.ts b/src/lib/apis/images/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e6510437b5176c8b9aad7a571b07628d0bee7a5
--- /dev/null
+++ b/src/lib/apis/images/index.ts
@@ -0,0 +1,232 @@
+import { IMAGES_API_BASE_URL } from '$lib/constants';
+
+export const getConfig = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${IMAGES_API_BASE_URL}/config`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateConfig = async (token: string = '', config: object) => {
+	let error = null;
+
+	const res = await fetch(`${IMAGES_API_BASE_URL}/config/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			...config
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const verifyConfigUrl = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${IMAGES_API_BASE_URL}/config/url/verify`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getImageGenerationConfig = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${IMAGES_API_BASE_URL}/image/config`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateImageGenerationConfig = async (token: string = '', config: object) => {
+	let error = null;
+
+	const res = await fetch(`${IMAGES_API_BASE_URL}/image/config/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({ ...config })
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getImageGenerationModels = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${IMAGES_API_BASE_URL}/models`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const imageGenerations = async (token: string = '', prompt: string) => {
+	let error = null;
+
+	const res = await fetch(`${IMAGES_API_BASE_URL}/generations`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			prompt: prompt
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..40d0e03924c67b9c83b05214a6de66cff9257295
--- /dev/null
+++ b/src/lib/apis/index.ts
@@ -0,0 +1,1060 @@
+import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
+
+export const getModels = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/models`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	let models = res?.data ?? [];
+
+	models = models
+		.filter((models) => models)
+		// Sort the models
+		.sort((a, b) => {
+			// Check if models have position property
+			const aHasPosition = a.info?.meta?.position !== undefined;
+			const bHasPosition = b.info?.meta?.position !== undefined;
+
+			// If both a and b have the position property
+			if (aHasPosition && bHasPosition) {
+				return a.info.meta.position - b.info.meta.position;
+			}
+
+			// If only a has the position property, it should come first
+			if (aHasPosition) return -1;
+
+			// If only b has the position property, it should come first
+			if (bHasPosition) return 1;
+
+			// Compare case-insensitively by name for models without position property
+			const lowerA = a.name.toLowerCase();
+			const lowerB = b.name.toLowerCase();
+
+			if (lowerA < lowerB) return -1;
+			if (lowerA > lowerB) return 1;
+
+			// If same case-insensitively, sort by original strings,
+			// lowercase will come before uppercase due to ASCII values
+			if (a.name < b.name) return -1;
+			if (a.name > b.name) return 1;
+
+			return 0; // They are equal
+		});
+
+	console.log(models);
+	return models;
+};
+
+type ChatCompletedForm = {
+	model: string;
+	messages: string[];
+	chat_id: string;
+	session_id: string;
+};
+
+export const chatCompleted = async (token: string, body: ChatCompletedForm) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/chat/completed`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify(body)
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = err;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+type ChatActionForm = {
+	model: string;
+	messages: string[];
+	chat_id: string;
+};
+
+export const chatAction = async (token: string, action_id: string, body: ChatActionForm) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/chat/actions/${action_id}`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify(body)
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = err;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getTaskConfig = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/task/config`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateTaskConfig = async (token: string, config: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/task/config/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify(config)
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = err;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const generateTitle = async (
+	token: string = '',
+	model: string,
+	messages: string[],
+	chat_id?: string
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/task/title/completions`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			model: model,
+			messages: messages,
+			...(chat_id && { chat_id: chat_id })
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res?.choices[0]?.message?.content.replace(/["']/g, '') ?? 'New Chat';
+};
+
+export const generateTags = async (
+	token: string = '',
+	model: string,
+	messages: string,
+	chat_id?: string
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/task/tags/completions`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			model: model,
+			messages: messages,
+			...(chat_id && { chat_id: chat_id })
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	try {
+		// Step 1: Safely extract the response string
+		const response = res?.choices[0]?.message?.content ?? '';
+
+		// Step 2: Attempt to fix common JSON format issues like single quotes
+		const sanitizedResponse = response.replace(/['‘’`]/g, '"'); // Convert single quotes to double quotes for valid JSON
+
+		// Step 3: Find the relevant JSON block within the response
+		const jsonStartIndex = sanitizedResponse.indexOf('{');
+		const jsonEndIndex = sanitizedResponse.lastIndexOf('}');
+
+		// Step 4: Check if we found a valid JSON block (with both `{` and `}`)
+		if (jsonStartIndex !== -1 && jsonEndIndex !== -1) {
+			const jsonResponse = sanitizedResponse.substring(jsonStartIndex, jsonEndIndex + 1);
+
+			// Step 5: Parse the JSON block
+			const parsed = JSON.parse(jsonResponse);
+
+			// Step 6: If there's a "tags" key, return the tags array; otherwise, return an empty array
+			if (parsed && parsed.tags) {
+				return Array.isArray(parsed.tags) ? parsed.tags : [];
+			} else {
+				return [];
+			}
+		}
+
+		// If no valid JSON block found, return an empty array
+		return [];
+	} catch (e) {
+		// Catch and safely return empty array on any parsing errors
+		console.error('Failed to parse response: ', e);
+		return [];
+	}
+};
+
+export const generateEmoji = async (
+	token: string = '',
+	model: string,
+	prompt: string,
+	chat_id?: string
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/task/emoji/completions`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			model: model,
+			prompt: prompt,
+			...(chat_id && { chat_id: chat_id })
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	const response = res?.choices[0]?.message?.content.replace(/["']/g, '') ?? null;
+
+	if (response) {
+		if (/\p{Extended_Pictographic}/u.test(response)) {
+			return response.match(/\p{Extended_Pictographic}/gu)[0];
+		}
+	}
+
+	return null;
+};
+
+export const generateSearchQuery = async (
+	token: string = '',
+	model: string,
+	messages: object[],
+	prompt: string
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/task/query/completions`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			model: model,
+			messages: messages,
+			prompt: prompt
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res?.choices[0]?.message?.content.replace(/["']/g, '') ?? prompt;
+};
+
+export const generateMoACompletion = async (
+	token: string = '',
+	model: string,
+	prompt: string,
+	responses: string[]
+) => {
+	const controller = new AbortController();
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/task/moa/completions`, {
+		signal: controller.signal,
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			model: model,
+			prompt: prompt,
+			responses: responses,
+			stream: true
+		})
+	}).catch((err) => {
+		console.log(err);
+		error = err;
+		return null;
+	});
+
+	if (error) {
+		throw error;
+	}
+
+	return [res, controller];
+};
+
+export const getPipelinesList = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/pipelines/list`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	let pipelines = res?.data ?? [];
+	return pipelines;
+};
+
+export const uploadPipeline = async (token: string, file: File, urlIdx: string) => {
+	let error = null;
+
+	// Create a new FormData object to handle the file upload
+	const formData = new FormData();
+	formData.append('file', file);
+	formData.append('urlIdx', urlIdx);
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/pipelines/upload`, {
+		method: 'POST',
+		headers: {
+			...(token && { authorization: `Bearer ${token}` })
+			// 'Content-Type': 'multipart/form-data' is not needed as Fetch API will set it automatically
+		},
+		body: formData
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = err;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const downloadPipeline = async (token: string, url: string, urlIdx: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/pipelines/add`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			url: url,
+			urlIdx: urlIdx
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = err;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deletePipeline = async (token: string, id: string, urlIdx: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/pipelines/delete`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			id: id,
+			urlIdx: urlIdx
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = err;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getPipelines = async (token: string, urlIdx?: string) => {
+	let error = null;
+
+	const searchParams = new URLSearchParams();
+	if (urlIdx !== undefined) {
+		searchParams.append('urlIdx', urlIdx);
+	}
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/pipelines?${searchParams.toString()}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	let pipelines = res?.data ?? [];
+	return pipelines;
+};
+
+export const getPipelineValves = async (token: string, pipeline_id: string, urlIdx: string) => {
+	let error = null;
+
+	const searchParams = new URLSearchParams();
+	if (urlIdx !== undefined) {
+		searchParams.append('urlIdx', urlIdx);
+	}
+
+	const res = await fetch(
+		`${WEBUI_BASE_URL}/api/pipelines/${pipeline_id}/valves?${searchParams.toString()}`,
+		{
+			method: 'GET',
+			headers: {
+				Accept: 'application/json',
+				'Content-Type': 'application/json',
+				...(token && { authorization: `Bearer ${token}` })
+			}
+		}
+	)
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getPipelineValvesSpec = async (token: string, pipeline_id: string, urlIdx: string) => {
+	let error = null;
+
+	const searchParams = new URLSearchParams();
+	if (urlIdx !== undefined) {
+		searchParams.append('urlIdx', urlIdx);
+	}
+
+	const res = await fetch(
+		`${WEBUI_BASE_URL}/api/pipelines/${pipeline_id}/valves/spec?${searchParams.toString()}`,
+		{
+			method: 'GET',
+			headers: {
+				Accept: 'application/json',
+				'Content-Type': 'application/json',
+				...(token && { authorization: `Bearer ${token}` })
+			}
+		}
+	)
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updatePipelineValves = async (
+	token: string = '',
+	pipeline_id: string,
+	valves: object,
+	urlIdx: string
+) => {
+	let error = null;
+
+	const searchParams = new URLSearchParams();
+	if (urlIdx !== undefined) {
+		searchParams.append('urlIdx', urlIdx);
+	}
+
+	const res = await fetch(
+		`${WEBUI_BASE_URL}/api/pipelines/${pipeline_id}/valves/update?${searchParams.toString()}`,
+		{
+			method: 'POST',
+			headers: {
+				Accept: 'application/json',
+				'Content-Type': 'application/json',
+				...(token && { authorization: `Bearer ${token}` })
+			},
+			body: JSON.stringify(valves)
+		}
+	)
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = err;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getBackendConfig = async () => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/config`, {
+		method: 'GET',
+		credentials: 'include',
+		headers: {
+			'Content-Type': 'application/json'
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getChangelog = async () => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/changelog`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json'
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getVersionUpdates = async () => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/version/updates`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json'
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getModelFilterConfig = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/config/model/filter`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateModelFilterConfig = async (
+	token: string,
+	enabled: boolean,
+	models: string[]
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/config/model/filter`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			enabled: enabled,
+			models: models
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getWebhookUrl = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/webhook`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.url;
+};
+
+export const updateWebhookUrl = async (token: string, url: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/webhook`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			url: url
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.url;
+};
+
+export const getCommunitySharingEnabledStatus = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/community_sharing`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const toggleCommunitySharingEnabledStatus = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/community_sharing/toggle`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getModelConfig = async (token: string): Promise<GlobalModelConfig> => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/config/models`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.models;
+};
+
+export interface ModelConfig {
+	id: string;
+	name: string;
+	meta: ModelMeta;
+	base_model_id?: string;
+	params: ModelParams;
+}
+
+export interface ModelMeta {
+	description?: string;
+	capabilities?: object;
+	profile_image_url?: string;
+}
+
+export interface ModelParams {}
+
+export type GlobalModelConfig = ModelConfig[];
+
+export const updateModelConfig = async (token: string, config: GlobalModelConfig) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/config/models`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			models: config
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/knowledge/index.ts b/src/lib/apis/knowledge/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8428668996329f343f79ef28c58b20bbf3cc6f4a
--- /dev/null
+++ b/src/lib/apis/knowledge/index.ts
@@ -0,0 +1,308 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const createNewKnowledge = async (token: string, name: string, description: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/create`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			name: name,
+			description: description
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getKnowledgeItems = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getKnowledgeById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/${id}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+type KnowledgeUpdateForm = {
+	name?: string;
+	description?: string;
+	data?: object;
+};
+
+export const updateKnowledgeById = async (token: string, id: string, form: KnowledgeUpdateForm) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/${id}/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			name: form?.name ? form.name : undefined,
+			description: form?.description ? form.description : undefined,
+			data: form?.data ? form.data : undefined
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const addFileToKnowledgeById = async (token: string, id: string, fileId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/${id}/file/add`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			file_id: fileId
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateFileFromKnowledgeById = async (token: string, id: string, fileId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/${id}/file/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			file_id: fileId
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const removeFileFromKnowledgeById = async (token: string, id: string, fileId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/${id}/file/remove`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			file_id: fileId
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const resetKnowledgeById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/${id}/reset`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteKnowledgeById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/${id}/delete`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/memories/index.ts b/src/lib/apis/memories/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3fd83ca9e06fdb2cf8cf8e94fd01666134cb024d
--- /dev/null
+++ b/src/lib/apis/memories/index.ts
@@ -0,0 +1,186 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const getMemories = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/memories/`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const addNewMemory = async (token: string, content: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/memories/add`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			content: content
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateMemoryById = async (token: string, id: string, content: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/memories/${id}/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			content: content
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const queryMemory = async (token: string, content: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/memories/query`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			content: content
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteMemoryById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/memories/${id}`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteMemoriesByUserId = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/memories/delete/user`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/models/index.ts b/src/lib/apis/models/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9faa358d33100d3193b1a9200f361fd835d30098
--- /dev/null
+++ b/src/lib/apis/models/index.ts
@@ -0,0 +1,167 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const addNewModel = async (token: string, model: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/models/add`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify(model)
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getModelInfos = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/models`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getModelById = async (token: string, id: string) => {
+	let error = null;
+
+	const searchParams = new URLSearchParams();
+	searchParams.append('id', id);
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/models?${searchParams.toString()}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateModelById = async (token: string, id: string, model: object) => {
+	let error = null;
+
+	const searchParams = new URLSearchParams();
+	searchParams.append('id', id);
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/models/update?${searchParams.toString()}`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify(model)
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteModelById = async (token: string, id: string) => {
+	let error = null;
+
+	const searchParams = new URLSearchParams();
+	searchParams.append('id', id);
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/models/delete?${searchParams.toString()}`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/ollama/index.ts b/src/lib/apis/ollama/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d4e994312e22e87d6399062a84bdab6c89ac72b0
--- /dev/null
+++ b/src/lib/apis/ollama/index.ts
@@ -0,0 +1,508 @@
+import { OLLAMA_API_BASE_URL } from '$lib/constants';
+
+export const getOllamaConfig = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/config`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateOllamaConfig = async (token: string = '', enable_ollama_api: boolean) => {
+	let error = null;
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/config/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			enable_ollama_api: enable_ollama_api
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getOllamaUrls = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/urls`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.OLLAMA_BASE_URLS;
+};
+
+export const updateOllamaUrls = async (token: string = '', urls: string[]) => {
+	let error = null;
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/urls/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			urls: urls
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.OLLAMA_BASE_URLS;
+};
+
+export const getOllamaVersion = async (token: string, urlIdx?: number) => {
+	let error = null;
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/api/version${urlIdx ? `/${urlIdx}` : ''}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res?.version ?? false;
+};
+
+export const getOllamaModels = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/api/tags`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return (res?.models ?? [])
+		.map((model) => ({ id: model.model, name: model.name ?? model.model, ...model }))
+		.sort((a, b) => {
+			return a.name.localeCompare(b.name);
+		});
+};
+
+export const generatePrompt = async (token: string = '', model: string, conversation: string) => {
+	let error = null;
+
+	if (conversation === '') {
+		conversation = '[no existing conversation]';
+	}
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/api/generate`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			model: model,
+			prompt: `Conversation:
+			${conversation}
+
+			As USER in the conversation above, your task is to continue the conversation. Remember, Your responses should be crafted as if you're a human conversing in a natural, realistic manner, keeping in mind the context and flow of the dialogue. Please generate a fitting response to the last message in the conversation, or if there is no existing conversation, initiate one as a normal person would.
+			
+			Response:
+			`
+		})
+	}).catch((err) => {
+		console.log(err);
+		if ('detail' in err) {
+			error = err.detail;
+		}
+		return null;
+	});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const generateEmbeddings = async (token: string = '', model: string, text: string) => {
+	let error = null;
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/api/embeddings`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			model: model,
+			prompt: text
+		})
+	}).catch((err) => {
+		error = err;
+		return null;
+	});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const generateTextCompletion = async (token: string = '', model: string, text: string) => {
+	let error = null;
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/api/generate`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			model: model,
+			prompt: text,
+			stream: true
+		})
+	}).catch((err) => {
+		error = err;
+		return null;
+	});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const generateChatCompletion = async (token: string = '', body: object) => {
+	let controller = new AbortController();
+	let error = null;
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/api/chat`, {
+		signal: controller.signal,
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify(body)
+	}).catch((err) => {
+		error = err;
+		return null;
+	});
+
+	if (error) {
+		throw error;
+	}
+
+	return [res, controller];
+};
+
+export const createModel = async (
+	token: string,
+	tagName: string,
+	content: string,
+	urlIdx: string | null = null
+) => {
+	let error = null;
+
+	const res = await fetch(
+		`${OLLAMA_API_BASE_URL}/api/create${urlIdx !== null ? `/${urlIdx}` : ''}`,
+		{
+			method: 'POST',
+			headers: {
+				Accept: 'application/json',
+				'Content-Type': 'application/json',
+				Authorization: `Bearer ${token}`
+			},
+			body: JSON.stringify({
+				name: tagName,
+				modelfile: content
+			})
+		}
+	).catch((err) => {
+		error = err;
+		return null;
+	});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteModel = async (token: string, tagName: string, urlIdx: string | null = null) => {
+	let error = null;
+
+	const res = await fetch(
+		`${OLLAMA_API_BASE_URL}/api/delete${urlIdx !== null ? `/${urlIdx}` : ''}`,
+		{
+			method: 'DELETE',
+			headers: {
+				Accept: 'application/json',
+				'Content-Type': 'application/json',
+				Authorization: `Bearer ${token}`
+			},
+			body: JSON.stringify({
+				name: tagName
+			})
+		}
+	)
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			console.log(json);
+			return true;
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+
+			if ('detail' in err) {
+				error = err.detail;
+			}
+
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const pullModel = async (token: string, tagName: string, urlIdx: number | null = null) => {
+	let error = null;
+	const controller = new AbortController();
+
+	const res = await fetch(`${OLLAMA_API_BASE_URL}/api/pull${urlIdx !== null ? `/${urlIdx}` : ''}`, {
+		signal: controller.signal,
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			name: tagName
+		})
+	}).catch((err) => {
+		console.log(err);
+		error = err;
+
+		if ('detail' in err) {
+			error = err.detail;
+		}
+
+		return null;
+	});
+	if (error) {
+		throw error;
+	}
+	return [res, controller];
+};
+
+export const downloadModel = async (
+	token: string,
+	download_url: string,
+	urlIdx: string | null = null
+) => {
+	let error = null;
+
+	const res = await fetch(
+		`${OLLAMA_API_BASE_URL}/models/download${urlIdx !== null ? `/${urlIdx}` : ''}`,
+		{
+			method: 'POST',
+			headers: {
+				Accept: 'application/json',
+				'Content-Type': 'application/json',
+				Authorization: `Bearer ${token}`
+			},
+			body: JSON.stringify({
+				url: download_url
+			})
+		}
+	).catch((err) => {
+		console.log(err);
+		error = err;
+
+		if ('detail' in err) {
+			error = err.detail;
+		}
+
+		return null;
+	});
+	if (error) {
+		throw error;
+	}
+	return res;
+};
+
+export const uploadModel = async (token: string, file: File, urlIdx: string | null = null) => {
+	let error = null;
+
+	const formData = new FormData();
+	formData.append('file', file);
+
+	const res = await fetch(
+		`${OLLAMA_API_BASE_URL}/models/upload${urlIdx !== null ? `/${urlIdx}` : ''}`,
+		{
+			method: 'POST',
+			headers: {
+				Authorization: `Bearer ${token}`
+			},
+			body: formData
+		}
+	).catch((err) => {
+		console.log(err);
+		error = err;
+
+		if ('detail' in err) {
+			error = err.detail;
+		}
+
+		return null;
+	});
+	if (error) {
+		throw error;
+	}
+	return res;
+};
+
+// export const pullModel = async (token: string, tagName: string) => {
+// 	return await fetch(`${OLLAMA_API_BASE_URL}/pull`, {
+// 		method: 'POST',
+// 		headers: {
+// 			'Content-Type': 'text/event-stream',
+// 			Authorization: `Bearer ${token}`
+// 		},
+// 		body: JSON.stringify({
+// 			name: tagName
+// 		})
+// 	});
+// };
diff --git a/src/lib/apis/openai/index.ts b/src/lib/apis/openai/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2bb11d12a74e48933536509d9fb16800d2efc53b
--- /dev/null
+++ b/src/lib/apis/openai/index.ts
@@ -0,0 +1,330 @@
+import { OPENAI_API_BASE_URL } from '$lib/constants';
+
+export const getOpenAIConfig = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${OPENAI_API_BASE_URL}/config`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateOpenAIConfig = async (token: string = '', enable_openai_api: boolean) => {
+	let error = null;
+
+	const res = await fetch(`${OPENAI_API_BASE_URL}/config/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			enable_openai_api: enable_openai_api
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getOpenAIUrls = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${OPENAI_API_BASE_URL}/urls`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.OPENAI_API_BASE_URLS;
+};
+
+export const updateOpenAIUrls = async (token: string = '', urls: string[]) => {
+	let error = null;
+
+	const res = await fetch(`${OPENAI_API_BASE_URL}/urls/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			urls: urls
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.OPENAI_API_BASE_URLS;
+};
+
+export const getOpenAIKeys = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${OPENAI_API_BASE_URL}/keys`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.OPENAI_API_KEYS;
+};
+
+export const updateOpenAIKeys = async (token: string = '', keys: string[]) => {
+	let error = null;
+
+	const res = await fetch(`${OPENAI_API_BASE_URL}/keys/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			...(token && { authorization: `Bearer ${token}` })
+		},
+		body: JSON.stringify({
+			keys: keys
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			if ('detail' in err) {
+				error = err.detail;
+			} else {
+				error = 'Server connection failed';
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res.OPENAI_API_KEYS;
+};
+
+export const getOpenAIModels = async (token: string, urlIdx?: number) => {
+	let error = null;
+
+	const res = await fetch(
+		`${OPENAI_API_BASE_URL}/models${typeof urlIdx === 'number' ? `/${urlIdx}` : ''}`,
+		{
+			method: 'GET',
+			headers: {
+				Accept: 'application/json',
+				'Content-Type': 'application/json',
+				...(token && { authorization: `Bearer ${token}` })
+			}
+		}
+	)
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = `OpenAI: ${err?.error?.message ?? 'Network Problem'}`;
+			return [];
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getOpenAIModelsDirect = async (
+	base_url: string = 'https://api.openai.com/v1',
+	api_key: string = ''
+) => {
+	let error = null;
+
+	const res = await fetch(`${base_url}/models`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${api_key}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = `OpenAI: ${err?.error?.message ?? 'Network Problem'}`;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	const models = Array.isArray(res) ? res : (res?.data ?? null);
+
+	return models
+		.map((model) => ({ id: model.id, name: model.name ?? model.id, external: true }))
+		.filter((model) => (base_url.includes('openai') ? model.name.includes('gpt') : true))
+		.sort((a, b) => {
+			return a.name.localeCompare(b.name);
+		});
+};
+
+export const generateOpenAIChatCompletion = async (
+	token: string = '',
+	body: object,
+	url: string = OPENAI_API_BASE_URL
+): Promise<[Response | null, AbortController]> => {
+	const controller = new AbortController();
+	let error = null;
+
+	const res = await fetch(`${url}/chat/completions`, {
+		signal: controller.signal,
+		method: 'POST',
+		headers: {
+			Authorization: `Bearer ${token}`,
+			'Content-Type': 'application/json'
+		},
+		body: JSON.stringify(body)
+	}).catch((err) => {
+		console.log(err);
+		error = err;
+		return null;
+	});
+
+	if (error) {
+		throw error;
+	}
+
+	return [res, controller];
+};
+
+export const synthesizeOpenAISpeech = async (
+	token: string = '',
+	speaker: string = 'alloy',
+	text: string = '',
+	model: string = 'tts-1'
+) => {
+	let error = null;
+
+	const res = await fetch(`${OPENAI_API_BASE_URL}/audio/speech`, {
+		method: 'POST',
+		headers: {
+			Authorization: `Bearer ${token}`,
+			'Content-Type': 'application/json'
+		},
+		body: JSON.stringify({
+			model: model,
+			input: text,
+			voice: speaker
+		})
+	}).catch((err) => {
+		console.log(err);
+		error = err;
+		return null;
+	});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/prompts/index.ts b/src/lib/apis/prompts/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ca9c7d543d7815340a667b74dc1b258f3ff496f8
--- /dev/null
+++ b/src/lib/apis/prompts/index.ts
@@ -0,0 +1,178 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const createNewPrompt = async (
+	token: string,
+	command: string,
+	title: string,
+	content: string
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/create`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			command: `/${command}`,
+			title: title,
+			content: content
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getPrompts = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getPromptByCommand = async (token: string, command: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/command/${command}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updatePromptByCommand = async (
+	token: string,
+	command: string,
+	title: string,
+	content: string
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/command/${command}/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			command: `/${command}`,
+			title: title,
+			content: content
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deletePromptByCommand = async (token: string, command: string) => {
+	let error = null;
+
+	command = command.charAt(0) === '/' ? command.slice(1) : command;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/command/${command}/delete`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/retrieval/index.ts b/src/lib/apis/retrieval/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6c6b18b9fd6d97a569008bd0d5b8e8b27d9a9504
--- /dev/null
+++ b/src/lib/apis/retrieval/index.ts
@@ -0,0 +1,567 @@
+import { RETRIEVAL_API_BASE_URL } from '$lib/constants';
+
+export const getRAGConfig = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/config`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+type ChunkConfigForm = {
+	chunk_size: number;
+	chunk_overlap: number;
+};
+
+type ContentExtractConfigForm = {
+	engine: string;
+	tika_server_url: string | null;
+};
+
+type YoutubeConfigForm = {
+	language: string[];
+	translation?: string | null;
+};
+
+type RAGConfigForm = {
+	pdf_extract_images?: boolean;
+	chunk?: ChunkConfigForm;
+	content_extraction?: ContentExtractConfigForm;
+	web_loader_ssl_verification?: boolean;
+	youtube?: YoutubeConfigForm;
+};
+
+export const updateRAGConfig = async (token: string, payload: RAGConfigForm) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/config/update`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...payload
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getRAGTemplate = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/template`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res?.template ?? '';
+};
+
+export const getQuerySettings = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/query/settings`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+type QuerySettings = {
+	k: number | null;
+	r: number | null;
+	template: string | null;
+};
+
+export const updateQuerySettings = async (token: string, settings: QuerySettings) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/query/settings/update`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...settings
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getEmbeddingConfig = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/embedding`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+type OpenAIConfigForm = {
+	key: string;
+	url: string;
+};
+
+type EmbeddingModelUpdateForm = {
+	openai_config?: OpenAIConfigForm;
+	embedding_engine: string;
+	embedding_model: string;
+	embedding_batch_size?: number;
+};
+
+export const updateEmbeddingConfig = async (token: string, payload: EmbeddingModelUpdateForm) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/embedding/update`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...payload
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getRerankingConfig = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/reranking`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+type RerankingModelUpdateForm = {
+	reranking_model: string;
+};
+
+export const updateRerankingConfig = async (token: string, payload: RerankingModelUpdateForm) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/reranking/update`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...payload
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export interface SearchDocument {
+	status: boolean;
+	collection_name: string;
+	filenames: string[];
+}
+
+export const processFile = async (
+	token: string,
+	file_id: string,
+	collection_name: string | null = null
+) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/process/file`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			file_id: file_id,
+			collection_name: collection_name ? collection_name : undefined
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const processYoutubeVideo = async (token: string, url: string) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/process/youtube`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			url: url
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const processWeb = async (token: string, collection_name: string, url: string) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/process/web`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			url: url,
+			collection_name: collection_name
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const processWebSearch = async (
+	token: string,
+	query: string,
+	collection_name?: string
+): Promise<SearchDocument | null> => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/process/web/search`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			query,
+			collection_name: collection_name ?? ''
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const queryDoc = async (
+	token: string,
+	collection_name: string,
+	query: string,
+	k: number | null = null
+) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/query/doc`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			collection_name: collection_name,
+			query: query,
+			k: k
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const queryCollection = async (
+	token: string,
+	collection_names: string,
+	query: string,
+	k: number | null = null
+) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/query/collection`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			collection_names: collection_names,
+			query: query,
+			k: k
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const resetUploadDir = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/reset/uploads`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const resetVectorDB = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${RETRIEVAL_API_BASE_URL}/reset/db`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/streaming/index.ts b/src/lib/apis/streaming/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a8249abe090943c51217a769953ec906aa784ba7
--- /dev/null
+++ b/src/lib/apis/streaming/index.ts
@@ -0,0 +1,124 @@
+import { EventSourceParserStream } from 'eventsource-parser/stream';
+import type { ParsedEvent } from 'eventsource-parser';
+
+type TextStreamUpdate = {
+	done: boolean;
+	value: string;
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	citations?: any;
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	selectedModelId?: any;
+	error?: any;
+	usage?: ResponseUsage;
+};
+
+type ResponseUsage = {
+	/** Including images and tools if any */
+	prompt_tokens: number;
+	/** The tokens generated */
+	completion_tokens: number;
+	/** Sum of the above two fields */
+	total_tokens: number;
+	/** Any other fields that aren't part of the base OpenAI spec */
+	[other: string]: unknown;
+};
+
+// createOpenAITextStream takes a responseBody with a SSE response,
+// and returns an async generator that emits delta updates with large deltas chunked into random sized chunks
+export async function createOpenAITextStream(
+	responseBody: ReadableStream<Uint8Array>,
+	splitLargeDeltas: boolean
+): Promise<AsyncGenerator<TextStreamUpdate>> {
+	const eventStream = responseBody
+		.pipeThrough(new TextDecoderStream())
+		.pipeThrough(new EventSourceParserStream())
+		.getReader();
+	let iterator = openAIStreamToIterator(eventStream);
+	if (splitLargeDeltas) {
+		iterator = streamLargeDeltasAsRandomChunks(iterator);
+	}
+	return iterator;
+}
+
+async function* openAIStreamToIterator(
+	reader: ReadableStreamDefaultReader<ParsedEvent>
+): AsyncGenerator<TextStreamUpdate> {
+	while (true) {
+		const { value, done } = await reader.read();
+		if (done) {
+			yield { done: true, value: '' };
+			break;
+		}
+		if (!value) {
+			continue;
+		}
+		const data = value.data;
+		if (data.startsWith('[DONE]')) {
+			yield { done: true, value: '' };
+			break;
+		}
+
+		try {
+			const parsedData = JSON.parse(data);
+			console.log(parsedData);
+
+			if (parsedData.error) {
+				yield { done: true, value: '', error: parsedData.error };
+				break;
+			}
+
+			if (parsedData.citations) {
+				yield { done: false, value: '', citations: parsedData.citations };
+				continue;
+			}
+
+			if (parsedData.selected_model_id) {
+				yield { done: false, value: '', selectedModelId: parsedData.selected_model_id };
+				continue;
+			}
+
+			yield {
+				done: false,
+				value: parsedData.choices?.[0]?.delta?.content ?? '',
+				usage: parsedData.usage
+			};
+		} catch (e) {
+			console.error('Error extracting delta from SSE event:', e);
+		}
+	}
+}
+
+// streamLargeDeltasAsRandomChunks will chunk large deltas (length > 5) into random sized chunks between 1-3 characters
+// This is to simulate a more fluid streaming, even though some providers may send large chunks of text at once
+async function* streamLargeDeltasAsRandomChunks(
+	iterator: AsyncGenerator<TextStreamUpdate>
+): AsyncGenerator<TextStreamUpdate> {
+	for await (const textStreamUpdate of iterator) {
+		if (textStreamUpdate.done) {
+			yield textStreamUpdate;
+			return;
+		}
+		if (textStreamUpdate.citations) {
+			yield textStreamUpdate;
+			continue;
+		}
+		let content = textStreamUpdate.value;
+		if (content.length < 5) {
+			yield { done: false, value: content };
+			continue;
+		}
+		while (content != '') {
+			const chunkSize = Math.min(Math.floor(Math.random() * 3) + 1, content.length);
+			const chunk = content.slice(0, chunkSize);
+			yield { done: false, value: chunk };
+			// Do not sleep if the tab is hidden
+			// Timers are throttled to 1s in hidden tabs
+			if (document?.visibilityState !== 'hidden') {
+				await sleep(5);
+			}
+			content = content.slice(chunkSize);
+		}
+	}
+}
+
+const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
diff --git a/src/lib/apis/tools/index.ts b/src/lib/apis/tools/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..28e8dde86cff79fd0b1b496a4992f22d366c39f5
--- /dev/null
+++ b/src/lib/apis/tools/index.ts
@@ -0,0 +1,391 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const createNewTool = async (token: string, tool: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/create`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...tool
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getTools = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const exportTools = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/export`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getToolById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateToolById = async (token: string, id: string, tool: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...tool
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteToolById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/delete`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getToolValvesById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getToolValvesSpecById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves/spec`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateToolValvesById = async (token: string, id: string, valves: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...valves
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getUserValvesById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves/user`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getUserValvesSpecById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves/user/spec`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateUserValvesById = async (token: string, id: string, valves: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves/user/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...valves
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/users/index.ts b/src/lib/apis/users/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0b22b7171569eafff1b67a664efb6cfffe2c0968
--- /dev/null
+++ b/src/lib/apis/users/index.ts
@@ -0,0 +1,336 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+import { getUserPosition } from '$lib/utils';
+
+export const getUserPermissions = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/permissions/user`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateUserPermissions = async (token: string, permissions: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/permissions/user`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...permissions
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateUserRole = async (token: string, id: string, role: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/update/role`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			id: id,
+			role: role
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getUsers = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res ? res : [];
+};
+
+export const getUserSettings = async (token: string) => {
+	let error = null;
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/user/settings`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateUserSettings = async (token: string, settings: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/user/settings/update`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...settings
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getUserById = async (token: string, userId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getUserInfo = async (token: string) => {
+	let error = null;
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/user/info`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateUserInfo = async (token: string, info: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/user/info/update`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...info
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getAndUpdateUserLocation = async (token: string) => {
+	const location = await getUserPosition().catch((err) => {
+		throw err;
+	});
+
+	if (location) {
+		await updateUserInfo(token, { location: location });
+		return location;
+	} else {
+		throw new Error('Failed to get user location');
+	}
+};
+
+export const deleteUserById = async (token: string, userId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}`, {
+		method: 'DELETE',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+type UserUpdateForm = {
+	profile_image_url: string;
+	email: string;
+	name: string;
+	password: string;
+};
+
+export const updateUserById = async (token: string, userId: string, user: UserUpdateForm) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}/update`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			profile_image_url: user.profile_image_url,
+			email: user.email,
+			name: user.name,
+			password: user.password !== '' ? user.password : undefined
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
diff --git a/src/lib/apis/utils/index.ts b/src/lib/apis/utils/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..40fdbfcfa22519f699ab525d3649274b1e6dc0ed
--- /dev/null
+++ b/src/lib/apis/utils/index.ts
@@ -0,0 +1,179 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const getGravatarUrl = async (email: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/utils/gravatar?email=${email}`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json'
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	return res;
+};
+
+export const formatPythonCode = async (code: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/utils/code/format`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json'
+		},
+		body: JSON.stringify({
+			code: code
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+
+			error = err;
+			if (err.detail) {
+				error = err.detail;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const downloadChatAsPDF = async (title: string, messages: object[]) => {
+	let error = null;
+
+	const blob = await fetch(`${WEBUI_API_BASE_URL}/utils/pdf`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json'
+		},
+		body: JSON.stringify({
+			title: title,
+			messages: messages
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.blob();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	return blob;
+};
+
+export const getHTMLFromMarkdown = async (md: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/utils/markdown`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json'
+		},
+		body: JSON.stringify({
+			md: md
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	return res.html;
+};
+
+export const downloadDatabase = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/utils/db/download`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (response) => {
+			if (!response.ok) {
+				throw await response.json();
+			}
+			return response.blob();
+		})
+		.then((blob) => {
+			const url = window.URL.createObjectURL(blob);
+			const a = document.createElement('a');
+			a.href = url;
+			a.download = 'webui.db';
+			document.body.appendChild(a);
+			a.click();
+			window.URL.revokeObjectURL(url);
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+};
+
+export const downloadLiteLLMConfig = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/utils/litellm/config`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (response) => {
+			if (!response.ok) {
+				throw await response.json();
+			}
+			return response.blob();
+		})
+		.then((blob) => {
+			const url = window.URL.createObjectURL(blob);
+			const a = document.createElement('a');
+			a.href = url;
+			a.download = 'config.yaml';
+			document.body.appendChild(a);
+			a.click();
+			window.URL.revokeObjectURL(url);
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+};
diff --git a/src/lib/components/AddFilesPlaceholder.svelte b/src/lib/components/AddFilesPlaceholder.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d3d700795552bf2b75983f7973d72ec6a7798c7e
--- /dev/null
+++ b/src/lib/components/AddFilesPlaceholder.svelte
@@ -0,0 +1,28 @@
+<script>
+	import { getContext } from 'svelte';
+
+	export let title = '';
+	export let content = '';
+	const i18n = getContext('i18n');
+</script>
+
+<div class="px-3">
+	<div class="text-center text-6xl mb-3">📄</div>
+	<div class="text-center dark:text-white text-xl font-semibold z-50">
+		{#if title}
+			{title}
+		{:else}
+			{$i18n.t('Add Files')}
+		{/if}
+	</div>
+
+	<slot
+		><div class="px-2 mt-2 text-center text-sm dark:text-gray-200 w-full">
+			{#if content}
+				{content}
+			{:else}
+				{$i18n.t('Drop any files here to add to the conversation')}
+			{/if}
+		</div>
+	</slot>
+</div>
diff --git a/src/lib/components/ChangelogModal.svelte b/src/lib/components/ChangelogModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9002b9100a55cd8447a71c7a950d7c5f3ae5cf9a
--- /dev/null
+++ b/src/lib/components/ChangelogModal.svelte
@@ -0,0 +1,120 @@
+<script lang="ts">
+	import { onMount, getContext } from 'svelte';
+	import { Confetti } from 'svelte-confetti';
+
+	import { WEBUI_NAME, config, settings } from '$lib/stores';
+
+	import { WEBUI_VERSION } from '$lib/constants';
+	import { getChangelog } from '$lib/apis';
+
+	import Modal from './common/Modal.svelte';
+	import { updateUserSettings } from '$lib/apis/users';
+
+	const i18n = getContext('i18n');
+
+	export let show = false;
+
+	let changelog = null;
+
+	onMount(async () => {
+		const res = await getChangelog();
+		changelog = res;
+	});
+</script>
+
+<Modal bind:show>
+	<div class="px-5 pt-4 dark:text-gray-300 text-gray-700">
+		<div class="flex justify-between items-start">
+			<div class="text-xl font-semibold">
+				{$i18n.t('What’s New in')}
+				{$WEBUI_NAME}
+				<Confetti x={[-1, -0.25]} y={[0, 0.5]} />
+			</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					localStorage.version = $config.version;
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+		<div class="flex items-center mt-1">
+			<div class="text-sm dark:text-gray-200">{$i18n.t('Release Notes')}</div>
+			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
+			<div class="text-sm dark:text-gray-200">
+				v{WEBUI_VERSION}
+			</div>
+		</div>
+	</div>
+
+	<div class=" w-full p-4 px-5 text-gray-700 dark:text-gray-100">
+		<div class=" overflow-y-scroll max-h-80 scrollbar-hidden">
+			<div class="mb-3">
+				{#if changelog}
+					{#each Object.keys(changelog) as version}
+						<div class=" mb-3 pr-2">
+							<div class="font-semibold text-xl mb-1 dark:text-white">
+								v{version} - {changelog[version].date}
+							</div>
+
+							<hr class=" dark:border-gray-800 my-2" />
+
+							{#each Object.keys(changelog[version]).filter((section) => section !== 'date') as section}
+								<div class="">
+									<div
+										class="font-semibold uppercase text-xs {section === 'added'
+											? 'text-white bg-blue-600'
+											: section === 'fixed'
+												? 'text-white bg-green-600'
+												: section === 'changed'
+													? 'text-white bg-yellow-600'
+													: section === 'removed'
+														? 'text-white bg-red-600'
+														: ''}  w-fit px-3 rounded-full my-2.5"
+									>
+										{section}
+									</div>
+
+									<div class="my-2.5 px-1.5">
+										{#each Object.keys(changelog[version][section]) as item}
+											<div class="text-sm mb-2">
+												<div class="font-semibold uppercase">
+													{changelog[version][section][item].title}
+												</div>
+												<div class="mb-2 mt-1">{changelog[version][section][item].content}</div>
+											</div>
+										{/each}
+									</div>
+								</div>
+							{/each}
+						</div>
+					{/each}
+				{/if}
+			</div>
+		</div>
+		<div class="flex justify-end pt-3 text-sm font-medium">
+			<button
+				on:click={async () => {
+					localStorage.version = $config.version;
+					await settings.set({ ...$settings, ...{ version: $config.version } });
+					await updateUserSettings(localStorage.token, { ui: $settings });
+					show = false;
+				}}
+				class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
+			>
+				<span class="relative">{$i18n.t("Okay, Let's Go!")}</span>
+			</button>
+		</div>
+	</div>
+</Modal>
diff --git a/src/lib/components/admin/AddUserModal.svelte b/src/lib/components/admin/AddUserModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..eea77fce53f0781193e14e602bed6515e2d22487
--- /dev/null
+++ b/src/lib/components/admin/AddUserModal.svelte
@@ -0,0 +1,338 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { createEventDispatcher } from 'svelte';
+	import { onMount, getContext } from 'svelte';
+	import { addUser } from '$lib/apis/auths';
+
+	import Modal from '../common/Modal.svelte';
+	import { WEBUI_BASE_URL } from '$lib/constants';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	export let show = false;
+
+	let loading = false;
+	let tab = '';
+	let inputFiles;
+
+	let _user = {
+		name: '',
+		email: '',
+		password: '',
+		role: 'user'
+	};
+
+	$: if (show) {
+		_user = {
+			name: '',
+			email: '',
+			password: '',
+			role: 'user'
+		};
+	}
+
+	const submitHandler = async () => {
+		const stopLoading = () => {
+			dispatch('save');
+			loading = false;
+		};
+
+		if (tab === '') {
+			loading = true;
+
+			const res = await addUser(
+				localStorage.token,
+				_user.name,
+				_user.email,
+				_user.password,
+				_user.role
+			).catch((error) => {
+				toast.error(error);
+			});
+
+			if (res) {
+				stopLoading();
+				show = false;
+			}
+		} else {
+			if (inputFiles) {
+				loading = true;
+
+				const file = inputFiles[0];
+				const reader = new FileReader();
+
+				reader.onload = async (e) => {
+					const csv = e.target.result;
+					const rows = csv.split('\n');
+
+					let userCount = 0;
+
+					for (const [idx, row] of rows.entries()) {
+						const columns = row.split(',').map((col) => col.trim());
+						console.log(idx, columns);
+
+						if (idx > 0) {
+							if (
+								columns.length === 4 &&
+								['admin', 'user', 'pending'].includes(columns[3].toLowerCase())
+							) {
+								const res = await addUser(
+									localStorage.token,
+									columns[0],
+									columns[1],
+									columns[2],
+									columns[3].toLowerCase()
+								).catch((error) => {
+									toast.error(`Row ${idx + 1}: ${error}`);
+									return null;
+								});
+
+								if (res) {
+									userCount = userCount + 1;
+								}
+							} else {
+								toast.error(`Row ${idx + 1}: invalid format.`);
+							}
+						}
+					}
+
+					toast.success(`Successfully imported ${userCount} users.`);
+					inputFiles = null;
+					const uploadInputElement = document.getElementById('upload-user-csv-input');
+
+					if (uploadInputElement) {
+						uploadInputElement.value = null;
+					}
+
+					stopLoading();
+				};
+
+				reader.readAsText(file);
+			} else {
+				toast.error($i18n.t('File not found.'));
+			}
+		}
+	};
+</script>
+
+<Modal size="sm" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
+			<div class=" text-lg font-medium self-center">{$i18n.t('Add User')}</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full px-4 pb-3 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				<form
+					class="flex flex-col w-full"
+					on:submit|preventDefault={() => {
+						submitHandler();
+					}}
+				>
+					<div class="flex text-center text-sm font-medium rounded-full bg-transparent/10 p-1 mb-2">
+						<button
+							class="w-full rounded-full p-1.5 {tab === '' ? 'bg-gray-50 dark:bg-gray-850' : ''}"
+							type="button"
+							on:click={() => {
+								tab = '';
+							}}>{$i18n.t('Form')}</button
+						>
+
+						<button
+							class="w-full rounded-full p-1 {tab === 'import'
+								? 'bg-gray-50 dark:bg-gray-850'
+								: ''}"
+							type="button"
+							on:click={() => {
+								tab = 'import';
+							}}>{$i18n.t('CSV Import')}</button
+						>
+					</div>
+					<div class="px-1">
+						{#if tab === ''}
+							<div class="flex flex-col w-full">
+								<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Role')}</div>
+
+								<div class="flex-1">
+									<select
+										class="w-full capitalize rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
+										bind:value={_user.role}
+										placeholder={$i18n.t('Enter Your Role')}
+										required
+									>
+										<option value="pending"> {$i18n.t('pending')} </option>
+										<option value="user"> {$i18n.t('user')} </option>
+										<option value="admin"> {$i18n.t('admin')} </option>
+									</select>
+								</div>
+							</div>
+
+							<div class="flex flex-col w-full mt-1">
+								<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
+
+								<div class="flex-1">
+									<input
+										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
+										type="text"
+										bind:value={_user.name}
+										placeholder={$i18n.t('Enter Your Full Name')}
+										autocomplete="off"
+										required
+									/>
+								</div>
+							</div>
+
+							<hr class=" border-gray-50 dark:border-gray-850 my-2.5 w-full" />
+
+							<div class="flex flex-col w-full">
+								<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div>
+
+								<div class="flex-1">
+									<input
+										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
+										type="email"
+										bind:value={_user.email}
+										placeholder={$i18n.t('Enter Your Email')}
+										required
+									/>
+								</div>
+							</div>
+
+							<div class="flex flex-col w-full mt-1">
+								<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Password')}</div>
+
+								<div class="flex-1">
+									<input
+										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
+										type="password"
+										bind:value={_user.password}
+										placeholder={$i18n.t('Enter Your Password')}
+										autocomplete="off"
+									/>
+								</div>
+							</div>
+						{:else if tab === 'import'}
+							<div>
+								<div class="mb-3 w-full">
+									<input
+										id="upload-user-csv-input"
+										hidden
+										bind:files={inputFiles}
+										type="file"
+										accept=".csv"
+									/>
+
+									<button
+										class="w-full text-sm font-medium py-3 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
+										type="button"
+										on:click={() => {
+											document.getElementById('upload-user-csv-input')?.click();
+										}}
+									>
+										{#if inputFiles}
+											{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
+										{:else}
+											{$i18n.t('Click here to select a csv file.')}
+										{/if}
+									</button>
+								</div>
+
+								<div class=" text-xs text-gray-500">
+									ⓘ {$i18n.t(
+										'Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.'
+									)}
+									<a
+										class="underline dark:text-gray-200"
+										href="{WEBUI_BASE_URL}/static/user-import.csv"
+									>
+										{$i18n.t('Click here to download user import template file.')}
+									</a>
+								</div>
+							</div>
+						{/if}
+					</div>
+
+					<div class="flex justify-end pt-3 text-sm font-medium">
+						<button
+							class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {loading
+								? ' cursor-not-allowed'
+								: ''}"
+							type="submit"
+							disabled={loading}
+						>
+							{$i18n.t('Save')}
+
+							{#if loading}
+								<div class="ml-2 self-center">
+									<svg
+										class=" w-4 h-4"
+										viewBox="0 0 24 24"
+										fill="currentColor"
+										xmlns="http://www.w3.org/2000/svg"
+										><style>
+											.spinner_ajPY {
+												transform-origin: center;
+												animation: spinner_AtaB 0.75s infinite linear;
+											}
+											@keyframes spinner_AtaB {
+												100% {
+													transform: rotate(360deg);
+												}
+											}
+										</style><path
+											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+											opacity=".25"
+										/><path
+											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+											class="spinner_ajPY"
+										/></svg
+									>
+								</div>
+							{/if}
+						</button>
+					</div>
+				</form>
+			</div>
+		</div>
+	</div>
+</Modal>
+
+<style>
+	input::-webkit-outer-spin-button,
+	input::-webkit-inner-spin-button {
+		/* display: none; <- Crashes Chrome on hover */
+		-webkit-appearance: none;
+		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
+	}
+
+	.tabs::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.tabs {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+
+	input[type='number'] {
+		-moz-appearance: textfield; /* Firefox */
+	}
+</style>
diff --git a/src/lib/components/admin/EditUserModal.svelte b/src/lib/components/admin/EditUserModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..dcaff95b662a8c307515bb9ec274d4515a8d24e7
--- /dev/null
+++ b/src/lib/components/admin/EditUserModal.svelte
@@ -0,0 +1,174 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import dayjs from 'dayjs';
+	import { createEventDispatcher } from 'svelte';
+	import { onMount, getContext } from 'svelte';
+
+	import { updateUserById } from '$lib/apis/users';
+	import Modal from '../common/Modal.svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	export let show = false;
+	export let selectedUser;
+	export let sessionUser;
+
+	let _user = {
+		profile_image_url: '',
+		name: '',
+		email: '',
+		password: ''
+	};
+
+	const submitHandler = async () => {
+		const res = await updateUserById(localStorage.token, selectedUser.id, _user).catch((error) => {
+			toast.error(error);
+		});
+
+		if (res) {
+			dispatch('save');
+			show = false;
+		}
+	};
+
+	onMount(() => {
+		if (selectedUser) {
+			_user = selectedUser;
+			_user.password = '';
+		}
+	});
+</script>
+
+<Modal size="sm" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
+			<div class=" text-lg font-medium self-center">{$i18n.t('Edit User')}</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+		<hr class=" dark:border-gray-800" />
+
+		<div class="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				<form
+					class="flex flex-col w-full"
+					on:submit|preventDefault={() => {
+						submitHandler();
+					}}
+				>
+					<div class=" flex items-center rounded-md py-2 px-4 w-full">
+						<div class=" self-center mr-5">
+							<img
+								src={selectedUser.profile_image_url}
+								class=" max-w-[55px] object-cover rounded-full"
+								alt="User profile"
+							/>
+						</div>
+
+						<div>
+							<div class=" self-center capitalize font-semibold">{selectedUser.name}</div>
+
+							<div class="text-xs text-gray-500">
+								{$i18n.t('Created at')}
+								{dayjs(selectedUser.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
+							</div>
+						</div>
+					</div>
+
+					<hr class=" dark:border-gray-800 my-3 w-full" />
+
+					<div class=" flex flex-col space-y-1.5">
+						<div class="flex flex-col w-full">
+							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div>
+
+							<div class="flex-1">
+								<input
+									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
+									type="email"
+									bind:value={_user.email}
+									autocomplete="off"
+									required
+									disabled={_user.id == sessionUser.id}
+								/>
+							</div>
+						</div>
+
+						<div class="flex flex-col w-full">
+							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
+
+							<div class="flex-1">
+								<input
+									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+									type="text"
+									bind:value={_user.name}
+									autocomplete="off"
+									required
+								/>
+							</div>
+						</div>
+
+						<div class="flex flex-col w-full">
+							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('New Password')}</div>
+
+							<div class="flex-1">
+								<input
+									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+									type="password"
+									bind:value={_user.password}
+									autocomplete="new-password"
+								/>
+							</div>
+						</div>
+					</div>
+
+					<div class="flex justify-end pt-3 text-sm font-medium">
+						<button
+							class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
+							type="submit"
+						>
+							{$i18n.t('Save')}
+						</button>
+					</div>
+				</form>
+			</div>
+		</div>
+	</div>
+</Modal>
+
+<style>
+	input::-webkit-outer-spin-button,
+	input::-webkit-inner-spin-button {
+		/* display: none; <- Crashes Chrome on hover */
+		-webkit-appearance: none;
+		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
+	}
+
+	.tabs::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.tabs {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+
+	input[type='number'] {
+		-moz-appearance: textfield; /* Firefox */
+	}
+</style>
diff --git a/src/lib/components/admin/Evaluations.svelte b/src/lib/components/admin/Evaluations.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..4632238f0cb68486e1018dc30289cf02300e66ea
--- /dev/null
+++ b/src/lib/components/admin/Evaluations.svelte
@@ -0,0 +1,677 @@
+<script lang="ts">
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext } from 'svelte';
+	import dayjs from 'dayjs';
+	import relativeTime from 'dayjs/plugin/relativeTime';
+	dayjs.extend(relativeTime);
+
+	import * as ort from 'onnxruntime-web';
+	import { AutoModel, AutoTokenizer } from '@huggingface/transformers';
+
+	const EMBEDDING_MODEL = 'TaylorAI/bge-micro-v2';
+	let tokenizer = null;
+	let model = null;
+
+	import { models } from '$lib/stores';
+	import { deleteFeedbackById, exportAllFeedbacks, getAllFeedbacks } from '$lib/apis/evaluations';
+
+	import FeedbackMenu from './Evaluations/FeedbackMenu.svelte';
+	import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import Badge from '../common/Badge.svelte';
+	import Pagination from '../common/Pagination.svelte';
+	import MagnifyingGlass from '../icons/MagnifyingGlass.svelte';
+	import Share from '../icons/Share.svelte';
+	import CloudArrowUp from '../icons/CloudArrowUp.svelte';
+	import { toast } from 'svelte-sonner';
+	import Spinner from '../common/Spinner.svelte';
+	import DocumentArrowUpSolid from '../icons/DocumentArrowUpSolid.svelte';
+	import DocumentArrowDown from '../icons/DocumentArrowDown.svelte';
+	import ArrowDownTray from '../icons/ArrowDownTray.svelte';
+
+	const i18n = getContext('i18n');
+
+	let rankedModels = [];
+	let feedbacks = [];
+
+	let query = '';
+	let page = 1;
+
+	let tagEmbeddings = new Map();
+
+	let loaded = false;
+	let loadingLeaderboard = true;
+	let debounceTimer;
+
+	$: paginatedFeedbacks = feedbacks.slice((page - 1) * 10, page * 10);
+
+	type Feedback = {
+		id: string;
+		data: {
+			rating: number;
+			model_id: string;
+			sibling_model_ids: string[] | null;
+			reason: string;
+			comment: string;
+			tags: string[];
+		};
+		user: {
+			name: string;
+			profile_image_url: string;
+		};
+		updated_at: number;
+	};
+
+	type ModelStats = {
+		rating: number;
+		won: number;
+		lost: number;
+	};
+
+	//////////////////////
+	//
+	// Rank models by Elo rating
+	//
+	//////////////////////
+
+	const rankHandler = async (similarities: Map<string, number> = new Map()) => {
+		const modelStats = calculateModelStats(feedbacks, similarities);
+
+		rankedModels = $models
+			.filter((m) => m?.owned_by !== 'arena' && (m?.info?.meta?.hidden ?? false) !== true)
+			.map((model) => {
+				const stats = modelStats.get(model.id);
+				return {
+					...model,
+					rating: stats ? Math.round(stats.rating) : '-',
+					stats: {
+						count: stats ? stats.won + stats.lost : 0,
+						won: stats ? stats.won.toString() : '-',
+						lost: stats ? stats.lost.toString() : '-'
+					}
+				};
+			})
+			.sort((a, b) => {
+				if (a.rating === '-' && b.rating !== '-') return 1;
+				if (b.rating === '-' && a.rating !== '-') return -1;
+				if (a.rating !== '-' && b.rating !== '-') return b.rating - a.rating;
+				return a.name.localeCompare(b.name);
+			});
+
+		loadingLeaderboard = false;
+	};
+
+	function calculateModelStats(
+		feedbacks: Feedback[],
+		similarities: Map<string, number>
+	): Map<string, ModelStats> {
+		const stats = new Map<string, ModelStats>();
+		const K = 32;
+
+		function getOrDefaultStats(modelId: string): ModelStats {
+			return stats.get(modelId) || { rating: 1000, won: 0, lost: 0 };
+		}
+
+		function updateStats(modelId: string, ratingChange: number, outcome: number) {
+			const currentStats = getOrDefaultStats(modelId);
+			currentStats.rating += ratingChange;
+			if (outcome === 1) currentStats.won++;
+			else if (outcome === 0) currentStats.lost++;
+			stats.set(modelId, currentStats);
+		}
+
+		function calculateEloChange(
+			ratingA: number,
+			ratingB: number,
+			outcome: number,
+			similarity: number
+		): number {
+			const expectedScore = 1 / (1 + Math.pow(10, (ratingB - ratingA) / 400));
+			return K * (outcome - expectedScore) * similarity;
+		}
+
+		feedbacks.forEach((feedback) => {
+			const modelA = feedback.data.model_id;
+			const statsA = getOrDefaultStats(modelA);
+			let outcome: number;
+
+			switch (feedback.data.rating.toString()) {
+				case '1':
+					outcome = 1;
+					break;
+				case '-1':
+					outcome = 0;
+					break;
+				default:
+					return; // Skip invalid ratings
+			}
+
+			// If the query is empty, set similarity to 1, else get the similarity from the map
+			const similarity = query !== '' ? similarities.get(feedback.id) || 0 : 1;
+			const opponents = feedback.data.sibling_model_ids || [];
+
+			opponents.forEach((modelB) => {
+				const statsB = getOrDefaultStats(modelB);
+				const changeA = calculateEloChange(statsA.rating, statsB.rating, outcome, similarity);
+				const changeB = calculateEloChange(statsB.rating, statsA.rating, 1 - outcome, similarity);
+
+				updateStats(modelA, changeA, outcome);
+				updateStats(modelB, changeB, 1 - outcome);
+			});
+		});
+
+		return stats;
+	}
+
+	//////////////////////
+	//
+	// Calculate cosine similarity
+	//
+	//////////////////////
+
+	const cosineSimilarity = (vecA, vecB) => {
+		// Ensure the lengths of the vectors are the same
+		if (vecA.length !== vecB.length) {
+			throw new Error('Vectors must be the same length');
+		}
+
+		// Calculate the dot product
+		let dotProduct = 0;
+		let normA = 0;
+		let normB = 0;
+
+		for (let i = 0; i < vecA.length; i++) {
+			dotProduct += vecA[i] * vecB[i];
+			normA += vecA[i] ** 2;
+			normB += vecB[i] ** 2;
+		}
+
+		// Calculate the magnitudes
+		normA = Math.sqrt(normA);
+		normB = Math.sqrt(normB);
+
+		// Avoid division by zero
+		if (normA === 0 || normB === 0) {
+			return 0;
+		}
+
+		// Return the cosine similarity
+		return dotProduct / (normA * normB);
+	};
+
+	const calculateMaxSimilarity = (queryEmbedding, tagEmbeddings: Map<string, number[]>) => {
+		let maxSimilarity = 0;
+		for (const tagEmbedding of tagEmbeddings.values()) {
+			const similarity = cosineSimilarity(queryEmbedding, tagEmbedding);
+			maxSimilarity = Math.max(maxSimilarity, similarity);
+		}
+		return maxSimilarity;
+	};
+
+	//////////////////////
+	//
+	// Embedding functions
+	//
+	//////////////////////
+
+	const getEmbeddings = async (text: string) => {
+		const tokens = await tokenizer(text);
+		const output = await model(tokens);
+
+		// Perform mean pooling on the last hidden states
+		const embeddings = output.last_hidden_state.mean(1);
+		return embeddings.ort_tensor.data;
+	};
+
+	const getTagEmbeddings = async (tags: string[]) => {
+		const embeddings = new Map();
+		for (const tag of tags) {
+			if (!tagEmbeddings.has(tag)) {
+				tagEmbeddings.set(tag, await getEmbeddings(tag));
+			}
+			embeddings.set(tag, tagEmbeddings.get(tag));
+		}
+		return embeddings;
+	};
+
+	const debouncedQueryHandler = async () => {
+		loadingLeaderboard = true;
+
+		if (query.trim() === '') {
+			rankHandler();
+			return;
+		}
+
+		clearTimeout(debounceTimer);
+
+		debounceTimer = setTimeout(async () => {
+			const queryEmbedding = await getEmbeddings(query);
+			const similarities = new Map<string, number>();
+
+			for (const feedback of feedbacks) {
+				const feedbackTags = feedback.data.tags || [];
+				const tagEmbeddings = await getTagEmbeddings(feedbackTags);
+				const maxSimilarity = calculateMaxSimilarity(queryEmbedding, tagEmbeddings);
+				similarities.set(feedback.id, maxSimilarity);
+			}
+
+			rankHandler(similarities);
+		}, 1500); // Debounce for 1.5 seconds
+	};
+
+	$: query, debouncedQueryHandler();
+
+	//////////////////////
+	//
+	// CRUD operations
+	//
+	//////////////////////
+
+	const deleteFeedbackHandler = async (feedbackId: string) => {
+		const response = await deleteFeedbackById(localStorage.token, feedbackId).catch((err) => {
+			toast.error(err);
+			return null;
+		});
+		if (response) {
+			feedbacks = feedbacks.filter((f) => f.id !== feedbackId);
+		}
+	};
+
+	const shareHandler = async () => {
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
+
+		// remove snapshot from feedbacks
+		const feedbacksToShare = feedbacks.map((f) => {
+			const { snapshot, user, ...rest } = f;
+			return rest;
+		});
+		console.log(feedbacksToShare);
+
+		const url = 'https://openwebui.com';
+		const tab = await window.open(`${url}/leaderboard`, '_blank');
+
+		// Define the event handler function
+		const messageHandler = (event) => {
+			if (event.origin !== url) return;
+			if (event.data === 'loaded') {
+				tab.postMessage(JSON.stringify(feedbacksToShare), '*');
+
+				// Remove the event listener after handling the message
+				window.removeEventListener('message', messageHandler);
+			}
+		};
+
+		window.addEventListener('message', messageHandler, false);
+	};
+
+	const exportHandler = async () => {
+		const _feedbacks = await exportAllFeedbacks(localStorage.token).catch((err) => {
+			toast.error(err);
+			return null;
+		});
+
+		if (_feedbacks) {
+			let blob = new Blob([JSON.stringify(_feedbacks)], {
+				type: 'application/json'
+			});
+			saveAs(blob, `feedback-history-export-${Date.now()}.json`);
+		}
+	};
+
+	const loadEmbeddingModel = async () => {
+		// Check if the tokenizer and model are already loaded and stored in the window object
+		if (!window.tokenizer) {
+			window.tokenizer = await AutoTokenizer.from_pretrained(EMBEDDING_MODEL);
+		}
+
+		if (!window.model) {
+			window.model = await AutoModel.from_pretrained(EMBEDDING_MODEL);
+		}
+
+		// Use the tokenizer and model from the window object
+		tokenizer = window.tokenizer;
+		model = window.model;
+
+		// Pre-compute embeddings for all unique tags
+		const allTags = new Set(feedbacks.flatMap((feedback) => feedback.data.tags || []));
+		await getTagEmbeddings(Array.from(allTags));
+	};
+
+	onMount(async () => {
+		feedbacks = await getAllFeedbacks(localStorage.token);
+		loaded = true;
+
+		rankHandler();
+	});
+</script>
+
+{#if loaded}
+	<div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between">
+		<div class="flex md:self-center text-lg font-medium px-0.5 shrink-0 items-center">
+			<div class=" gap-1">
+				{$i18n.t('Leaderboard')}
+			</div>
+
+			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
+
+			<span class="text-lg font-medium text-gray-500 dark:text-gray-300 mr-1.5"
+				>{rankedModels.length}</span
+			>
+		</div>
+
+		<div class=" flex space-x-2">
+			<Tooltip content={$i18n.t('Re-rank models by topic similarity')}>
+				<div class="flex flex-1">
+					<div class=" self-center ml-1 mr-3">
+						<MagnifyingGlass className="size-3" />
+					</div>
+					<input
+						class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+						bind:value={query}
+						placeholder={$i18n.t('Search')}
+						on:focus={() => {
+							loadEmbeddingModel();
+						}}
+					/>
+				</div>
+			</Tooltip>
+		</div>
+	</div>
+
+	<div
+		class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded pt-0.5"
+	>
+		{#if loadingLeaderboard}
+			<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
+				<div class="m-auto">
+					<Spinner />
+				</div>
+			</div>
+		{/if}
+		{#if (rankedModels ?? []).length === 0}
+			<div class="text-center text-xs text-gray-500 dark:text-gray-400 py-1">
+				{$i18n.t('No models found')}
+			</div>
+		{:else}
+			<table
+				class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full rounded {loadingLeaderboard
+					? 'opacity-20'
+					: ''}"
+			>
+				<thead
+					class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"
+				>
+					<tr class="">
+						<th scope="col" class="px-3 py-1.5 cursor-pointer select-none w-3">
+							{$i18n.t('RK')}
+						</th>
+						<th scope="col" class="px-3 py-1.5 cursor-pointer select-none">
+							{$i18n.t('Model')}
+						</th>
+						<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-fit">
+							{$i18n.t('Rating')}
+						</th>
+						<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-5">
+							{$i18n.t('Won')}
+						</th>
+						<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-5">
+							{$i18n.t('Lost')}
+						</th>
+					</tr>
+				</thead>
+				<tbody class="">
+					{#each rankedModels as model, modelIdx (model.id)}
+						<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs group">
+							<td class="px-3 py-1.5 text-left font-medium text-gray-900 dark:text-white w-fit">
+								<div class=" line-clamp-1">
+									{model?.rating !== '-' ? modelIdx + 1 : '-'}
+								</div>
+							</td>
+							<td class="px-3 py-1.5 flex flex-col justify-center">
+								<div class="flex items-center gap-2">
+									<div class="flex-shrink-0">
+										<img
+											src={model?.info?.meta?.profile_image_url ?? '/favicon.png'}
+											alt={model.name}
+											class="size-5 rounded-full object-cover shrink-0"
+										/>
+									</div>
+
+									<div class="font-medium text-gray-800 dark:text-gray-200 pr-4">
+										{model.name}
+									</div>
+								</div>
+							</td>
+							<td class="px-3 py-1.5 text-right font-medium text-gray-900 dark:text-white w-max">
+								{model.rating}
+							</td>
+
+							<td class=" px-3 py-1.5 text-right font-semibold text-green-500">
+								<div class=" w-10">
+									{#if model.stats.won === '-'}
+										-
+									{:else}
+										<span class="hidden group-hover:inline"
+											>{((model.stats.won / model.stats.count) * 100).toFixed(1)}%</span
+										>
+										<span class=" group-hover:hidden">{model.stats.won}</span>
+									{/if}
+								</div>
+							</td>
+
+							<td class="px-3 py-1.5 text-right font-semibold text-red-500">
+								<div class=" w-10">
+									{#if model.stats.lost === '-'}
+										-
+									{:else}
+										<span class="hidden group-hover:inline"
+											>{((model.stats.lost / model.stats.count) * 100).toFixed(1)}%</span
+										>
+										<span class=" group-hover:hidden">{model.stats.lost}</span>
+									{/if}
+								</div>
+							</td>
+						</tr>
+					{/each}
+				</tbody>
+			</table>
+		{/if}
+	</div>
+
+	<div class=" text-gray-500 text-xs mt-1.5 w-full flex justify-end">
+		<div class=" text-right">
+			<div class="line-clamp-1">
+				ⓘ {$i18n.t(
+					'The evaluation leaderboard is based on the Elo rating system and is updated in real-time.'
+				)}
+			</div>
+			{$i18n.t(
+				'The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.'
+			)}
+		</div>
+	</div>
+
+	<div class="pb-4"></div>
+
+	<div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between">
+		<div class="flex md:self-center text-lg font-medium px-0.5">
+			{$i18n.t('Feedback History')}
+
+			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
+
+			<span class="text-lg font-medium text-gray-500 dark:text-gray-300">{feedbacks.length}</span>
+		</div>
+
+		<div>
+			<div>
+				<Tooltip content={$i18n.t('Export')}>
+					<button
+						class=" p-2 rounded-xl hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition font-medium text-sm flex items-center space-x-1"
+						on:click={() => {
+							exportHandler();
+						}}
+					>
+						<ArrowDownTray className="size-3" />
+					</button>
+				</Tooltip>
+			</div>
+		</div>
+	</div>
+
+	<div
+		class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded pt-0.5"
+	>
+		{#if (feedbacks ?? []).length === 0}
+			<div class="text-center text-xs text-gray-500 dark:text-gray-400 py-1">
+				{$i18n.t('No feedbacks found')}
+			</div>
+		{:else}
+			<table
+				class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full rounded"
+			>
+				<thead
+					class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"
+				>
+					<tr class="">
+						<th scope="col" class="px-3 text-right cursor-pointer select-none w-0">
+							{$i18n.t('User')}
+						</th>
+
+						<th scope="col" class="px-3 pr-1.5 cursor-pointer select-none">
+							{$i18n.t('Models')}
+						</th>
+
+						<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-fit">
+							{$i18n.t('Result')}
+						</th>
+
+						<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-0">
+							{$i18n.t('Updated At')}
+						</th>
+
+						<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-0"> </th>
+					</tr>
+				</thead>
+				<tbody class="">
+					{#each paginatedFeedbacks as feedback (feedback.id)}
+						<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
+							<td class=" py-0.5 text-right font-semibold">
+								<div class="flex justify-center">
+									<Tooltip content={feedback?.user?.name}>
+										<div class="flex-shrink-0">
+											<img
+												src={feedback?.user?.profile_image_url ?? '/user.png'}
+												alt={feedback?.user?.name}
+												class="size-5 rounded-full object-cover shrink-0"
+											/>
+										</div>
+									</Tooltip>
+								</div>
+							</td>
+
+							<td class=" py-1 pl-3 flex flex-col">
+								<div class="flex flex-col items-start gap-0.5 h-full">
+									<div class="flex flex-col h-full">
+										{#if feedback.data?.sibling_model_ids}
+											<div class="font-semibold text-gray-600 dark:text-gray-400 flex-1">
+												{feedback.data?.model_id}
+											</div>
+
+											<Tooltip content={feedback.data.sibling_model_ids.join(', ')}>
+												<div class=" text-[0.65rem] text-gray-600 dark:text-gray-400 line-clamp-1">
+													{#if feedback.data.sibling_model_ids.length > 2}
+														<!-- {$i18n.t('and {{COUNT}} more')} -->
+														{feedback.data.sibling_model_ids.slice(0, 2).join(', ')}, {$i18n.t(
+															'and {{COUNT}} more',
+															{ COUNT: feedback.data.sibling_model_ids.length - 2 }
+														)}
+													{:else}
+														{feedback.data.sibling_model_ids.join(', ')}
+													{/if}
+												</div>
+											</Tooltip>
+										{:else}
+											<div
+												class=" text-sm font-medium text-gray-600 dark:text-gray-400 flex-1 py-1.5"
+											>
+												{feedback.data?.model_id}
+											</div>
+										{/if}
+									</div>
+								</div>
+							</td>
+							<td class="px-3 py-1 text-right font-medium text-gray-900 dark:text-white w-max">
+								<div class=" flex justify-end">
+									{#if feedback.data.rating.toString() === '1'}
+										<Badge type="info" content={$i18n.t('Won')} />
+									{:else if feedback.data.rating.toString() === '0'}
+										<Badge type="muted" content={$i18n.t('Draw')} />
+									{:else if feedback.data.rating.toString() === '-1'}
+										<Badge type="error" content={$i18n.t('Lost')} />
+									{/if}
+								</div>
+							</td>
+
+							<td class=" px-3 py-1 text-right font-medium">
+								{dayjs(feedback.updated_at * 1000).fromNow()}
+							</td>
+
+							<td class=" px-3 py-1 text-right font-semibold">
+								<FeedbackMenu
+									on:delete={(e) => {
+										deleteFeedbackHandler(feedback.id);
+									}}
+								>
+									<button
+										class="self-center w-fit text-sm p-1.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+									>
+										<EllipsisHorizontal />
+									</button>
+								</FeedbackMenu>
+							</td>
+						</tr>
+					{/each}
+				</tbody>
+			</table>
+		{/if}
+	</div>
+
+	{#if feedbacks.length > 0}
+		<div class=" flex flex-col justify-end w-full text-right gap-1">
+			<div class="line-clamp-1 text-gray-500 text-xs">
+				{$i18n.t('Help us create the best community leaderboard by sharing your feedback history!')}
+			</div>
+
+			<div class="flex space-x-1 ml-auto">
+				<Tooltip
+					content={$i18n.t(
+						'To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.'
+					)}
+				>
+					<button
+						class="flex text-xs items-center px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
+						on:click={async () => {
+							shareHandler();
+						}}
+					>
+						<div class=" self-center mr-2 font-medium line-clamp-1">
+							{$i18n.t('Share to OpenWebUI Community')}
+						</div>
+
+						<div class=" self-center">
+							<CloudArrowUp className="size-3" strokeWidth="3" />
+						</div>
+					</button>
+				</Tooltip>
+			</div>
+		</div>
+	{/if}
+
+	{#if feedbacks.length > 10}
+		<Pagination bind:page count={feedbacks.length} perPage={10} />
+	{/if}
+
+	<div class="pb-12"></div>
+{/if}
diff --git a/src/lib/components/admin/Evaluations/FeedbackMenu.svelte b/src/lib/components/admin/Evaluations/FeedbackMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..83defd8043fe247daabc4683c3fe4f23e72f1ae2
--- /dev/null
+++ b/src/lib/components/admin/Evaluations/FeedbackMenu.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
+	import Pencil from '$lib/components/icons/Pencil.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Download from '$lib/components/icons/Download.svelte';
+
+	let show = false;
+</script>
+
+<Dropdown bind:show on:change={(e) => {}}>
+	<Tooltip content={$i18n.t('More')}>
+		<slot />
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[150px] rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
+			sideOffset={-2}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					dispatch('delete');
+					show = false;
+				}}
+			>
+				<GarbageBin strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Delete')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/admin/Settings.svelte b/src/lib/components/admin/Settings.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7e1e489b1583e2074b2ff7e43af2c03ff8562085
--- /dev/null
+++ b/src/lib/components/admin/Settings.svelte
@@ -0,0 +1,430 @@
+<script>
+	import { getContext, tick, onMount } from 'svelte';
+	import { toast } from 'svelte-sonner';
+
+	import Database from './Settings/Database.svelte';
+
+	import General from './Settings/General.svelte';
+	import Users from './Settings/Users.svelte';
+
+	import Pipelines from './Settings/Pipelines.svelte';
+	import Audio from './Settings/Audio.svelte';
+	import Images from './Settings/Images.svelte';
+	import Interface from './Settings/Interface.svelte';
+	import Models from './Settings/Models.svelte';
+	import Connections from './Settings/Connections.svelte';
+	import Documents from './Settings/Documents.svelte';
+	import WebSearch from './Settings/WebSearch.svelte';
+	import { config } from '$lib/stores';
+	import { getBackendConfig } from '$lib/apis';
+	import ChartBar from '../icons/ChartBar.svelte';
+	import DocumentChartBar from '../icons/DocumentChartBar.svelte';
+	import Evaluations from './Settings/Evaluations.svelte';
+
+	const i18n = getContext('i18n');
+
+	let selectedTab = 'general';
+
+	onMount(() => {
+		const containerElement = document.getElementById('admin-settings-tabs-container');
+
+		if (containerElement) {
+			containerElement.addEventListener('wheel', function (event) {
+				if (event.deltaY !== 0) {
+					// Adjust horizontal scroll position based on vertical scroll
+					containerElement.scrollLeft += event.deltaY;
+				}
+			});
+		}
+	});
+</script>
+
+<div class="flex flex-col lg:flex-row w-full h-full py-2 lg:space-x-4">
+	<div
+		id="admin-settings-tabs-container"
+		class="tabs flex flex-row overflow-x-auto space-x-1 max-w-full lg:space-x-0 lg:space-y-1 lg:flex-col lg:flex-none lg:w-44 dark:text-gray-200 text-xs text-left scrollbar-none"
+	>
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 lg:flex-none flex text-right transition {selectedTab ===
+			'general'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'general';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('General')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'users'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'users';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						d="M8 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM3.156 11.763c.16-.629.44-1.21.813-1.72a2.5 2.5 0 0 0-2.725 1.377c-.136.287.102.58.418.58h1.449c.01-.077.025-.156.045-.237ZM12.847 11.763c.02.08.036.16.046.237h1.446c.316 0 .554-.293.417-.579a2.5 2.5 0 0 0-2.722-1.378c.374.51.653 1.09.813 1.72ZM14 7.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM3.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM5 13c-.552 0-1.013-.455-.876-.99a4.002 4.002 0 0 1 7.753 0c.136.535-.324.99-.877.99H5Z"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('Users')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'connections'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'connections';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						d="M1 9.5A3.5 3.5 0 0 0 4.5 13H12a3 3 0 0 0 .917-5.857 2.503 2.503 0 0 0-3.198-3.019 3.5 3.5 0 0 0-6.628 2.171A3.5 3.5 0 0 0 1 9.5Z"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('Connections')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'models'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'models';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M10 1c3.866 0 7 1.79 7 4s-3.134 4-7 4-7-1.79-7-4 3.134-4 7-4zm5.694 8.13c.464-.264.91-.583 1.306-.952V10c0 2.21-3.134 4-7 4s-7-1.79-7-4V8.178c.396.37.842.688 1.306.953C5.838 10.006 7.854 10.5 10 10.5s4.162-.494 5.694-1.37zM3 13.179V15c0 2.21 3.134 4 7 4s7-1.79 7-4v-1.822c-.396.37-.842.688-1.306.953-1.532.875-3.548 1.369-5.694 1.369s-4.162-.494-5.694-1.37A7.009 7.009 0 013 13.179z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('Models')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'evaluations'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'evaluations';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<DocumentChartBar />
+			</div>
+			<div class=" self-center">{$i18n.t('Evaluations')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'documents'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'documents';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 24 24"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path d="M11.625 16.5a1.875 1.875 0 1 0 0-3.75 1.875 1.875 0 0 0 0 3.75Z" />
+					<path
+						fill-rule="evenodd"
+						d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875Zm6 16.5c.66 0 1.277-.19 1.797-.518l1.048 1.048a.75.75 0 0 0 1.06-1.06l-1.047-1.048A3.375 3.375 0 1 0 11.625 18Z"
+						clip-rule="evenodd"
+					/>
+					<path
+						d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('Documents')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'web'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'web';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 24 24"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						d="M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('Web Search')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'interface'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'interface';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M2 4.25A2.25 2.25 0 0 1 4.25 2h7.5A2.25 2.25 0 0 1 14 4.25v5.5A2.25 2.25 0 0 1 11.75 12h-1.312c.1.128.21.248.328.36a.75.75 0 0 1 .234.545v.345a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1-.75-.75v-.345a.75.75 0 0 1 .234-.545c.118-.111.228-.232.328-.36H4.25A2.25 2.25 0 0 1 2 9.75v-5.5Zm2.25-.75a.75.75 0 0 0-.75.75v4.5c0 .414.336.75.75.75h7.5a.75.75 0 0 0 .75-.75v-4.5a.75.75 0 0 0-.75-.75h-7.5Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('Interface')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'audio'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'audio';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						d="M7.557 2.066A.75.75 0 0 1 8 2.75v10.5a.75.75 0 0 1-1.248.56L3.59 11H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.59l3.162-2.81a.75.75 0 0 1 .805-.124ZM12.95 3.05a.75.75 0 1 0-1.06 1.06 5.5 5.5 0 0 1 0 7.78.75.75 0 1 0 1.06 1.06 7 7 0 0 0 0-9.9Z"
+					/>
+					<path
+						d="M10.828 5.172a.75.75 0 1 0-1.06 1.06 2.5 2.5 0 0 1 0 3.536.75.75 0 1 0 1.06 1.06 4 4 0 0 0 0-5.656Z"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('Audio')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'images'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'images';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4Zm10.5 5.707a.5.5 0 0 0-.146-.353l-1-1a.5.5 0 0 0-.708 0L9.354 9.646a.5.5 0 0 1-.708 0L6.354 7.354a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0-.146.353V12a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V9.707ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('Images')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'pipelines'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'pipelines';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 24 24"
+					fill="currentColor"
+					class="size-4"
+				>
+					<path
+						d="M11.644 1.59a.75.75 0 0 1 .712 0l9.75 5.25a.75.75 0 0 1 0 1.32l-9.75 5.25a.75.75 0 0 1-.712 0l-9.75-5.25a.75.75 0 0 1 0-1.32l9.75-5.25Z"
+					/>
+					<path
+						d="m3.265 10.602 7.668 4.129a2.25 2.25 0 0 0 2.134 0l7.668-4.13 1.37.739a.75.75 0 0 1 0 1.32l-9.75 5.25a.75.75 0 0 1-.71 0l-9.75-5.25a.75.75 0 0 1 0-1.32l1.37-.738Z"
+					/>
+					<path
+						d="m10.933 19.231-7.668-4.13-1.37.739a.75.75 0 0 0 0 1.32l9.75 5.25c.221.12.489.12.71 0l9.75-5.25a.75.75 0 0 0 0-1.32l-1.37-.738-7.668 4.13a2.25 2.25 0 0 1-2.134-.001Z"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('Pipelines')}</div>
+		</button>
+
+		<button
+			class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+			'db'
+				? 'bg-gray-100 dark:bg-gray-800'
+				: ' hover:bg-gray-50 dark:hover:bg-gray-850'}"
+			on:click={() => {
+				selectedTab = 'db';
+			}}
+		>
+			<div class=" self-center mr-2">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path d="M8 7c3.314 0 6-1.343 6-3s-2.686-3-6-3-6 1.343-6 3 2.686 3 6 3Z" />
+					<path
+						d="M8 8.5c1.84 0 3.579-.37 4.914-1.037A6.33 6.33 0 0 0 14 6.78V8c0 1.657-2.686 3-6 3S2 9.657 2 8V6.78c.346.273.72.5 1.087.683C4.42 8.131 6.16 8.5 8 8.5Z"
+					/>
+					<path
+						d="M8 12.5c1.84 0 3.579-.37 4.914-1.037.366-.183.74-.41 1.086-.684V12c0 1.657-2.686 3-6 3s-6-1.343-6-3v-1.22c.346.273.72.5 1.087.683C4.42 12.131 6.16 12.5 8 12.5Z"
+					/>
+				</svg>
+			</div>
+			<div class=" self-center">{$i18n.t('Database')}</div>
+		</button>
+	</div>
+
+	<div class="flex-1 mt-3 lg:mt-0 overflow-y-scroll">
+		{#if selectedTab === 'general'}
+			<General
+				saveHandler={async () => {
+					toast.success($i18n.t('Settings saved successfully!'));
+
+					await tick();
+					await config.set(await getBackendConfig());
+				}}
+			/>
+		{:else if selectedTab === 'users'}
+			<Users
+				saveHandler={() => {
+					toast.success($i18n.t('Settings saved successfully!'));
+				}}
+			/>
+		{:else if selectedTab === 'connections'}
+			<Connections
+				on:save={() => {
+					toast.success($i18n.t('Settings saved successfully!'));
+				}}
+			/>
+		{:else if selectedTab === 'models'}
+			<Models />
+		{:else if selectedTab === 'evaluations'}
+			<Evaluations />
+		{:else if selectedTab === 'documents'}
+			<Documents
+				on:save={async () => {
+					toast.success($i18n.t('Settings saved successfully!'));
+
+					await tick();
+					await config.set(await getBackendConfig());
+				}}
+			/>
+		{:else if selectedTab === 'web'}
+			<WebSearch
+				saveHandler={async () => {
+					toast.success($i18n.t('Settings saved successfully!'));
+
+					await tick();
+					await config.set(await getBackendConfig());
+				}}
+			/>
+		{:else if selectedTab === 'interface'}
+			<Interface
+				on:save={() => {
+					toast.success($i18n.t('Settings saved successfully!'));
+				}}
+			/>
+		{:else if selectedTab === 'audio'}
+			<Audio
+				saveHandler={() => {
+					toast.success($i18n.t('Settings saved successfully!'));
+				}}
+			/>
+		{:else if selectedTab === 'images'}
+			<Images
+				on:save={() => {
+					toast.success($i18n.t('Settings saved successfully!'));
+				}}
+			/>
+		{:else if selectedTab === 'db'}
+			<Database
+				saveHandler={() => {
+					toast.success($i18n.t('Settings saved successfully!'));
+				}}
+			/>
+		{:else if selectedTab === 'pipelines'}
+			<Pipelines
+				saveHandler={() => {
+					toast.success($i18n.t('Settings saved successfully!'));
+				}}
+			/>
+		{/if}
+	</div>
+</div>
diff --git a/src/lib/components/admin/Settings/Audio.svelte b/src/lib/components/admin/Settings/Audio.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ae827e6abbd123b731c880a93ed91d9b58605616
--- /dev/null
+++ b/src/lib/components/admin/Settings/Audio.svelte
@@ -0,0 +1,560 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { createEventDispatcher, onMount, getContext } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	import { getBackendConfig } from '$lib/apis';
+	import {
+		getAudioConfig,
+		updateAudioConfig,
+		getModels as _getModels,
+		getVoices as _getVoices
+	} from '$lib/apis/audio';
+	import { config } from '$lib/stores';
+
+	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
+
+	import { TTS_RESPONSE_SPLIT } from '$lib/types';
+
+	import type { Writable } from 'svelte/store';
+	import type { i18n as i18nType } from 'i18next';
+
+	const i18n = getContext<Writable<i18nType>>('i18n');
+
+	export let saveHandler: () => void;
+
+	// Audio
+	let TTS_OPENAI_API_BASE_URL = '';
+	let TTS_OPENAI_API_KEY = '';
+	let TTS_API_KEY = '';
+	let TTS_ENGINE = '';
+	let TTS_MODEL = '';
+	let TTS_VOICE = '';
+	let TTS_SPLIT_ON: TTS_RESPONSE_SPLIT = TTS_RESPONSE_SPLIT.PUNCTUATION;
+	let TTS_AZURE_SPEECH_REGION = '';
+	let TTS_AZURE_SPEECH_OUTPUT_FORMAT = '';
+
+	let STT_OPENAI_API_BASE_URL = '';
+	let STT_OPENAI_API_KEY = '';
+	let STT_ENGINE = '';
+	let STT_MODEL = '';
+	let STT_WHISPER_MODEL = '';
+
+	let STT_WHISPER_MODEL_LOADING = false;
+
+	// eslint-disable-next-line no-undef
+	let voices: SpeechSynthesisVoice[] = [];
+	let models: Awaited<ReturnType<typeof _getModels>>['models'] = [];
+
+	const getModels = async () => {
+		if (TTS_ENGINE === '') {
+			models = [];
+		} else {
+			const res = await _getModels(localStorage.token).catch((e) => {
+				toast.error(e);
+			});
+
+			if (res) {
+				console.log(res);
+				models = res.models;
+			}
+		}
+	};
+
+	const getVoices = async () => {
+		if (TTS_ENGINE === '') {
+			const getVoicesLoop = setInterval(() => {
+				voices = speechSynthesis.getVoices();
+
+				// do your loop
+				if (voices.length > 0) {
+					clearInterval(getVoicesLoop);
+					voices.sort((a, b) => a.name.localeCompare(b.name, $i18n.resolvedLanguage));
+				}
+			}, 100);
+		} else {
+			const res = await _getVoices(localStorage.token).catch((e) => {
+				toast.error(e);
+			});
+
+			if (res) {
+				console.log(res);
+				voices = res.voices;
+				voices.sort((a, b) => a.name.localeCompare(b.name, $i18n.resolvedLanguage));
+			}
+		}
+	};
+
+	const updateConfigHandler = async () => {
+		const res = await updateAudioConfig(localStorage.token, {
+			tts: {
+				OPENAI_API_BASE_URL: TTS_OPENAI_API_BASE_URL,
+				OPENAI_API_KEY: TTS_OPENAI_API_KEY,
+				API_KEY: TTS_API_KEY,
+				ENGINE: TTS_ENGINE,
+				MODEL: TTS_MODEL,
+				VOICE: TTS_VOICE,
+				SPLIT_ON: TTS_SPLIT_ON,
+				AZURE_SPEECH_REGION: TTS_AZURE_SPEECH_REGION,
+				AZURE_SPEECH_OUTPUT_FORMAT: TTS_AZURE_SPEECH_OUTPUT_FORMAT
+			},
+			stt: {
+				OPENAI_API_BASE_URL: STT_OPENAI_API_BASE_URL,
+				OPENAI_API_KEY: STT_OPENAI_API_KEY,
+				ENGINE: STT_ENGINE,
+				MODEL: STT_MODEL,
+				WHISPER_MODEL: STT_WHISPER_MODEL
+			}
+		});
+
+		if (res) {
+			saveHandler();
+			config.set(await getBackendConfig());
+		}
+	};
+
+	const sttModelUpdateHandler = async () => {
+		STT_WHISPER_MODEL_LOADING = true;
+		await updateConfigHandler();
+		STT_WHISPER_MODEL_LOADING = false;
+	};
+
+	onMount(async () => {
+		const res = await getAudioConfig(localStorage.token);
+
+		if (res) {
+			console.log(res);
+			TTS_OPENAI_API_BASE_URL = res.tts.OPENAI_API_BASE_URL;
+			TTS_OPENAI_API_KEY = res.tts.OPENAI_API_KEY;
+			TTS_API_KEY = res.tts.API_KEY;
+
+			TTS_ENGINE = res.tts.ENGINE;
+			TTS_MODEL = res.tts.MODEL;
+			TTS_VOICE = res.tts.VOICE;
+
+			TTS_SPLIT_ON = res.tts.SPLIT_ON || TTS_RESPONSE_SPLIT.PUNCTUATION;
+
+			TTS_AZURE_SPEECH_OUTPUT_FORMAT = res.tts.AZURE_SPEECH_OUTPUT_FORMAT;
+			TTS_AZURE_SPEECH_REGION = res.tts.AZURE_SPEECH_REGION;
+
+			STT_OPENAI_API_BASE_URL = res.stt.OPENAI_API_BASE_URL;
+			STT_OPENAI_API_KEY = res.stt.OPENAI_API_KEY;
+
+			STT_ENGINE = res.stt.ENGINE;
+			STT_MODEL = res.stt.MODEL;
+			STT_WHISPER_MODEL = res.stt.WHISPER_MODEL;
+		}
+
+		await getVoices();
+		await getModels();
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={async () => {
+		await updateConfigHandler();
+		dispatch('save');
+	}}
+>
+	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
+		<div class="flex flex-col gap-3">
+			<div>
+				<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
+
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
+					<div class="flex items-center relative">
+						<select
+							class="dark:bg-gray-900 cursor-pointer w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							bind:value={STT_ENGINE}
+							placeholder="Select an engine"
+						>
+							<option value="">{$i18n.t('Whisper (Local)')}</option>
+							<option value="openai">OpenAI</option>
+							<option value="web">{$i18n.t('Web API')}</option>
+						</select>
+					</div>
+				</div>
+
+				{#if STT_ENGINE === 'openai'}
+					<div>
+						<div class="mt-1 flex gap-2 mb-1">
+							<input
+								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								placeholder={$i18n.t('API Base URL')}
+								bind:value={STT_OPENAI_API_BASE_URL}
+								required
+							/>
+
+							<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={STT_OPENAI_API_KEY} />
+						</div>
+					</div>
+
+					<hr class=" dark:border-gray-850 my-2" />
+
+					<div>
+						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
+						<div class="flex w-full">
+							<div class="flex-1">
+								<input
+									list="model-list"
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									bind:value={STT_MODEL}
+									placeholder="Select a model"
+								/>
+
+								<datalist id="model-list">
+									<option value="whisper-1" />
+								</datalist>
+							</div>
+						</div>
+					</div>
+				{:else if STT_ENGINE === ''}
+					<div>
+						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
+
+						<div class="flex w-full">
+							<div class="flex-1 mr-2">
+								<input
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									placeholder={$i18n.t('Set whisper model')}
+									bind:value={STT_WHISPER_MODEL}
+								/>
+							</div>
+
+							<button
+								class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+								on:click={() => {
+									sttModelUpdateHandler();
+								}}
+								disabled={STT_WHISPER_MODEL_LOADING}
+							>
+								{#if STT_WHISPER_MODEL_LOADING}
+									<div class="self-center">
+										<svg
+											class=" w-4 h-4"
+											viewBox="0 0 24 24"
+											fill="currentColor"
+											xmlns="http://www.w3.org/2000/svg"
+										>
+											<style>
+												.spinner_ajPY {
+													transform-origin: center;
+													animation: spinner_AtaB 0.75s infinite linear;
+												}
+
+												@keyframes spinner_AtaB {
+													100% {
+														transform: rotate(360deg);
+													}
+												}
+											</style>
+											<path
+												d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+												opacity=".25"
+											/>
+											<path
+												d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+												class="spinner_ajPY"
+											/>
+										</svg>
+									</div>
+								{:else}
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 16 16"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
+										/>
+										<path
+											d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+										/>
+									</svg>
+								{/if}
+							</button>
+						</div>
+
+						<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
+							{$i18n.t(`Open WebUI uses faster-whisper internally.`)}
+
+							<a
+								class=" hover:underline dark:text-gray-200 text-gray-800"
+								href="https://github.com/SYSTRAN/faster-whisper"
+								target="_blank"
+							>
+								{$i18n.t(
+									`Click here to learn more about faster-whisper and see the available models.`
+								)}
+							</a>
+						</div>
+					</div>
+				{/if}
+			</div>
+
+			<hr class=" dark:border-gray-800" />
+
+			<div>
+				<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
+
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Text-to-Speech Engine')}</div>
+					<div class="flex items-center relative">
+						<select
+							class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							bind:value={TTS_ENGINE}
+							placeholder="Select a mode"
+							on:change={async (e) => {
+								await updateConfigHandler();
+								await getVoices();
+								await getModels();
+
+								if (e.target?.value === 'openai') {
+									TTS_VOICE = 'alloy';
+									TTS_MODEL = 'tts-1';
+								} else {
+									TTS_VOICE = '';
+									TTS_MODEL = '';
+								}
+							}}
+						>
+							<option value="">{$i18n.t('Web API')}</option>
+							<option value="openai">{$i18n.t('OpenAI')}</option>
+							<option value="elevenlabs">{$i18n.t('ElevenLabs')}</option>
+							<option value="azure">{$i18n.t('Azure AI Speech')}</option>
+						</select>
+					</div>
+				</div>
+
+				{#if TTS_ENGINE === 'openai'}
+					<div>
+						<div class="mt-1 flex gap-2 mb-1">
+							<input
+								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								placeholder={$i18n.t('API Base URL')}
+								bind:value={TTS_OPENAI_API_BASE_URL}
+								required
+							/>
+
+							<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_OPENAI_API_KEY} />
+						</div>
+					</div>
+				{:else if TTS_ENGINE === 'elevenlabs'}
+					<div>
+						<div class="mt-1 flex gap-2 mb-1">
+							<input
+								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								placeholder={$i18n.t('API Key')}
+								bind:value={TTS_API_KEY}
+								required
+							/>
+						</div>
+					</div>
+				{:else if TTS_ENGINE === 'azure'}
+					<div>
+						<div class="mt-1 flex gap-2 mb-1">
+							<input
+								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								placeholder={$i18n.t('API Key')}
+								bind:value={TTS_API_KEY}
+								required
+							/>
+							<input
+								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								placeholder={$i18n.t('Azure Region')}
+								bind:value={TTS_AZURE_SPEECH_REGION}
+								required
+							/>
+						</div>
+					</div>
+				{/if}
+
+				<hr class=" dark:border-gray-850 my-2" />
+
+				{#if TTS_ENGINE === ''}
+					<div>
+						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
+						<div class="flex w-full">
+							<div class="flex-1">
+								<select
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									bind:value={TTS_VOICE}
+								>
+									<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
+									{#each voices as voice}
+										<option
+											value={voice.voiceURI}
+											class="bg-gray-100 dark:bg-gray-700"
+											selected={TTS_VOICE === voice.voiceURI}
+											>{voice.name.replace('+', ', ')}</option
+										>
+									{/each}
+								</select>
+							</div>
+						</div>
+					</div>
+				{:else if TTS_ENGINE === 'openai'}
+					<div class=" flex gap-2">
+						<div class="w-full">
+							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
+							<div class="flex w-full">
+								<div class="flex-1">
+									<input
+										list="voice-list"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										bind:value={TTS_VOICE}
+										placeholder="Select a voice"
+									/>
+
+									<datalist id="voice-list">
+										{#each voices as voice}
+											<option value={voice.id}>{voice.name}</option>
+										{/each}
+									</datalist>
+								</div>
+							</div>
+						</div>
+						<div class="w-full">
+							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Model')}</div>
+							<div class="flex w-full">
+								<div class="flex-1">
+									<input
+										list="tts-model-list"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										bind:value={TTS_MODEL}
+										placeholder="Select a model"
+									/>
+
+									<datalist id="tts-model-list">
+										{#each models as model}
+											<option value={model.id} class="bg-gray-50 dark:bg-gray-700" />
+										{/each}
+									</datalist>
+								</div>
+							</div>
+						</div>
+					</div>
+				{:else if TTS_ENGINE === 'elevenlabs'}
+					<div class=" flex gap-2">
+						<div class="w-full">
+							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
+							<div class="flex w-full">
+								<div class="flex-1">
+									<input
+										list="voice-list"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										bind:value={TTS_VOICE}
+										placeholder="Select a voice"
+									/>
+
+									<datalist id="voice-list">
+										{#each voices as voice}
+											<option value={voice.id}>{voice.name}</option>
+										{/each}
+									</datalist>
+								</div>
+							</div>
+						</div>
+						<div class="w-full">
+							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Model')}</div>
+							<div class="flex w-full">
+								<div class="flex-1">
+									<input
+										list="tts-model-list"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										bind:value={TTS_MODEL}
+										placeholder="Select a model"
+									/>
+
+									<datalist id="tts-model-list">
+										{#each models as model}
+											<option value={model.id} class="bg-gray-50 dark:bg-gray-700" />
+										{/each}
+									</datalist>
+								</div>
+							</div>
+						</div>
+					</div>
+				{:else if TTS_ENGINE === 'azure'}
+					<div class=" flex gap-2">
+						<div class="w-full">
+							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
+							<div class="flex w-full">
+								<div class="flex-1">
+									<input
+										list="voice-list"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										bind:value={TTS_VOICE}
+										placeholder="Select a voice"
+									/>
+
+									<datalist id="voice-list">
+										{#each voices as voice}
+											<option value={voice.id}>{voice.name}</option>
+										{/each}
+									</datalist>
+								</div>
+							</div>
+						</div>
+						<div class="w-full">
+							<div class=" mb-1.5 text-sm font-medium">
+								{$i18n.t('Output format')}
+								<a
+									href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=streaming#audio-outputs"
+									target="_blank"
+								>
+									<small>{$i18n.t('Available list')}</small>
+								</a>
+							</div>
+							<div class="flex w-full">
+								<div class="flex-1">
+									<input
+										list="tts-model-list"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										bind:value={TTS_AZURE_SPEECH_OUTPUT_FORMAT}
+										placeholder="Select a output format"
+									/>
+								</div>
+							</div>
+						</div>
+					</div>
+				{/if}
+
+				<hr class="dark:border-gray-850 my-2" />
+
+				<div class="pt-0.5 flex w-full justify-between">
+					<div class="self-center text-xs font-medium">{$i18n.t('Response splitting')}</div>
+					<div class="flex items-center relative">
+						<select
+							class="dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							aria-label="Select how to split message text for TTS requests"
+							bind:value={TTS_SPLIT_ON}
+						>
+							{#each Object.values(TTS_RESPONSE_SPLIT) as split}
+								<option value={split}
+									>{$i18n.t(split.charAt(0).toUpperCase() + split.slice(1))}</option
+								>
+							{/each}
+						</select>
+					</div>
+				</div>
+				<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
+					{$i18n.t(
+						"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string."
+					)}
+				</div>
+			</div>
+		</div>
+	</div>
+	<div class="flex justify-end text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/admin/Settings/Connections.svelte b/src/lib/components/admin/Settings/Connections.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..292d22993e82a473385480d0209ef5fede2de5ab
--- /dev/null
+++ b/src/lib/components/admin/Settings/Connections.svelte
@@ -0,0 +1,448 @@
+<script lang="ts">
+	import { models, user } from '$lib/stores';
+	import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	import {
+		getOllamaConfig,
+		getOllamaUrls,
+		getOllamaVersion,
+		updateOllamaConfig,
+		updateOllamaUrls
+	} from '$lib/apis/ollama';
+	import {
+		getOpenAIConfig,
+		getOpenAIKeys,
+		getOpenAIModels,
+		getOpenAIUrls,
+		updateOpenAIConfig,
+		updateOpenAIKeys,
+		updateOpenAIUrls
+	} from '$lib/apis/openai';
+	import { toast } from 'svelte-sonner';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import { getModels as _getModels } from '$lib/apis';
+	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
+
+	const i18n = getContext('i18n');
+
+	const getModels = async () => {
+		const models = await _getModels(localStorage.token);
+		return models;
+	};
+
+	// External
+	let OLLAMA_BASE_URLS = [''];
+
+	let OPENAI_API_KEYS = [''];
+	let OPENAI_API_BASE_URLS = [''];
+
+	let pipelineUrls = {};
+
+	let ENABLE_OPENAI_API = null;
+	let ENABLE_OLLAMA_API = null;
+
+	const verifyOpenAIHandler = async (idx) => {
+		OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.map((url) => url.replace(/\/$/, ''));
+
+		OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
+		OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS);
+
+		const res = await getOpenAIModels(localStorage.token, idx).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Server connection verified'));
+			if (res.pipelines) {
+				pipelineUrls[OPENAI_API_BASE_URLS[idx]] = true;
+			}
+		}
+
+		await models.set(await getModels());
+	};
+
+	const verifyOllamaHandler = async (idx) => {
+		OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url) => url !== '').map((url) =>
+			url.replace(/\/$/, '')
+		);
+
+		OLLAMA_BASE_URLS = await updateOllamaUrls(localStorage.token, OLLAMA_BASE_URLS);
+
+		const res = await getOllamaVersion(localStorage.token, idx).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Server connection verified'));
+		}
+
+		await models.set(await getModels());
+	};
+
+	const updateOpenAIHandler = async () => {
+		OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.map((url) => url.replace(/\/$/, ''));
+
+		// Check if API KEYS length is same than API URLS length
+		if (OPENAI_API_KEYS.length !== OPENAI_API_BASE_URLS.length) {
+			// if there are more keys than urls, remove the extra keys
+			if (OPENAI_API_KEYS.length > OPENAI_API_BASE_URLS.length) {
+				OPENAI_API_KEYS = OPENAI_API_KEYS.slice(0, OPENAI_API_BASE_URLS.length);
+			}
+
+			// if there are more urls than keys, add empty keys
+			if (OPENAI_API_KEYS.length < OPENAI_API_BASE_URLS.length) {
+				const diff = OPENAI_API_BASE_URLS.length - OPENAI_API_KEYS.length;
+				for (let i = 0; i < diff; i++) {
+					OPENAI_API_KEYS.push('');
+				}
+			}
+		}
+
+		OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
+		OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS);
+		await models.set(await getModels());
+	};
+
+	const updateOllamaUrlsHandler = async () => {
+		OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url) => url !== '').map((url) =>
+			url.replace(/\/$/, '')
+		);
+
+		console.log(OLLAMA_BASE_URLS);
+
+		if (OLLAMA_BASE_URLS.length === 0) {
+			ENABLE_OLLAMA_API = false;
+			await updateOllamaConfig(localStorage.token, ENABLE_OLLAMA_API);
+
+			toast.info($i18n.t('Ollama API disabled'));
+		} else {
+			OLLAMA_BASE_URLS = await updateOllamaUrls(localStorage.token, OLLAMA_BASE_URLS);
+
+			const ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+
+			if (ollamaVersion) {
+				toast.success($i18n.t('Server connection verified'));
+				await models.set(await getModels());
+			}
+		}
+	};
+
+	onMount(async () => {
+		if ($user.role === 'admin') {
+			await Promise.all([
+				(async () => {
+					OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token);
+				})(),
+				(async () => {
+					OPENAI_API_BASE_URLS = await getOpenAIUrls(localStorage.token);
+				})(),
+				(async () => {
+					OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token);
+				})()
+			]);
+
+			const ollamaConfig = await getOllamaConfig(localStorage.token);
+			const openaiConfig = await getOpenAIConfig(localStorage.token);
+
+			ENABLE_OPENAI_API = openaiConfig.ENABLE_OPENAI_API;
+			ENABLE_OLLAMA_API = ollamaConfig.ENABLE_OLLAMA_API;
+
+			if (ENABLE_OPENAI_API) {
+				OPENAI_API_BASE_URLS.forEach(async (url, idx) => {
+					const res = await getOpenAIModels(localStorage.token, idx);
+					if (res.pipelines) {
+						pipelineUrls[url] = true;
+					}
+				});
+			}
+		}
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between text-sm"
+	on:submit|preventDefault={() => {
+		updateOpenAIHandler();
+		updateOllamaUrlsHandler();
+
+		dispatch('save');
+	}}
+>
+	<div class="space-y-3 overflow-y-scroll scrollbar-hidden h-full">
+		{#if ENABLE_OPENAI_API !== null && ENABLE_OLLAMA_API !== null}
+			<div class=" space-y-3">
+				<div class="mt-2 space-y-2 pr-1.5">
+					<div class="flex justify-between items-center text-sm">
+						<div class="  font-medium">{$i18n.t('OpenAI API')}</div>
+
+						<div class="mt-1">
+							<Switch
+								bind:state={ENABLE_OPENAI_API}
+								on:change={async () => {
+									updateOpenAIConfig(localStorage.token, ENABLE_OPENAI_API);
+								}}
+							/>
+						</div>
+					</div>
+
+					{#if ENABLE_OPENAI_API}
+						<div class="flex flex-col gap-1">
+							{#each OPENAI_API_BASE_URLS as url, idx}
+								<div class="flex w-full gap-2">
+									<div class="flex-1 relative">
+										<input
+											class="w-full rounded-lg py-2 px-4 {pipelineUrls[url]
+												? 'pr-8'
+												: ''} text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											placeholder={$i18n.t('API Base URL')}
+											bind:value={url}
+											autocomplete="off"
+										/>
+
+										{#if pipelineUrls[url]}
+											<div class=" absolute top-2.5 right-2.5">
+												<Tooltip content="Pipelines">
+													<svg
+														xmlns="http://www.w3.org/2000/svg"
+														viewBox="0 0 24 24"
+														fill="currentColor"
+														class="size-4"
+													>
+														<path
+															d="M11.644 1.59a.75.75 0 0 1 .712 0l9.75 5.25a.75.75 0 0 1 0 1.32l-9.75 5.25a.75.75 0 0 1-.712 0l-9.75-5.25a.75.75 0 0 1 0-1.32l9.75-5.25Z"
+														/>
+														<path
+															d="m3.265 10.602 7.668 4.129a2.25 2.25 0 0 0 2.134 0l7.668-4.13 1.37.739a.75.75 0 0 1 0 1.32l-9.75 5.25a.75.75 0 0 1-.71 0l-9.75-5.25a.75.75 0 0 1 0-1.32l1.37-.738Z"
+														/>
+														<path
+															d="m10.933 19.231-7.668-4.13-1.37.739a.75.75 0 0 0 0 1.32l9.75 5.25c.221.12.489.12.71 0l9.75-5.25a.75.75 0 0 0 0-1.32l-1.37-.738-7.668 4.13a2.25 2.25 0 0 1-2.134-.001Z"
+														/>
+													</svg>
+												</Tooltip>
+											</div>
+										{/if}
+									</div>
+
+									<SensitiveInput
+										placeholder={$i18n.t('API Key')}
+										bind:value={OPENAI_API_KEYS[idx]}
+									/>
+									<div class="self-center flex items-center">
+										{#if idx === 0}
+											<button
+												class="px-1"
+												on:click={() => {
+													OPENAI_API_BASE_URLS = [...OPENAI_API_BASE_URLS, ''];
+													OPENAI_API_KEYS = [...OPENAI_API_KEYS, ''];
+												}}
+												type="button"
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+													/>
+												</svg>
+											</button>
+										{:else}
+											<button
+												class="px-1"
+												on:click={() => {
+													OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
+														(url, urlIdx) => idx !== urlIdx
+													);
+													OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
+												}}
+												type="button"
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
+												</svg>
+											</button>
+										{/if}
+									</div>
+
+									<div class="flex">
+										<Tooltip content="Verify connection" className="self-start mt-0.5">
+											<button
+												class="self-center p-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
+												on:click={() => {
+													verifyOpenAIHandler(idx);
+												}}
+												type="button"
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 20 20"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														fill-rule="evenodd"
+														d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
+														clip-rule="evenodd"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+									</div>
+								</div>
+								<div class=" mb-1 text-xs text-gray-400 dark:text-gray-500">
+									{$i18n.t('WebUI will make requests to')}
+									<span class=" text-gray-200">'{url}/models'</span>
+								</div>
+							{/each}
+						</div>
+					{/if}
+				</div>
+			</div>
+
+			<hr class=" dark:border-gray-850" />
+
+			<div class="pr-1.5 space-y-2">
+				<div class="flex justify-between items-center text-sm">
+					<div class="  font-medium">{$i18n.t('Ollama API')}</div>
+
+					<div class="mt-1">
+						<Switch
+							bind:state={ENABLE_OLLAMA_API}
+							on:change={async () => {
+								updateOllamaConfig(localStorage.token, ENABLE_OLLAMA_API);
+
+								if (OLLAMA_BASE_URLS.length === 0) {
+									OLLAMA_BASE_URLS = [''];
+								}
+							}}
+						/>
+					</div>
+				</div>
+				{#if ENABLE_OLLAMA_API}
+					<div class="flex w-full gap-1.5">
+						<div class="flex-1 flex flex-col gap-2">
+							{#each OLLAMA_BASE_URLS as url, idx}
+								<div class="flex gap-1.5">
+									<input
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')}
+										bind:value={url}
+									/>
+
+									<div class="self-center flex items-center">
+										{#if idx === 0}
+											<button
+												class="px-1"
+												on:click={() => {
+													OLLAMA_BASE_URLS = [...OLLAMA_BASE_URLS, ''];
+												}}
+												type="button"
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+													/>
+												</svg>
+											</button>
+										{:else}
+											<button
+												class="px-1"
+												on:click={() => {
+													OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter(
+														(url, urlIdx) => idx !== urlIdx
+													);
+												}}
+												type="button"
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
+												</svg>
+											</button>
+										{/if}
+									</div>
+
+									<div class="flex">
+										<Tooltip content="Verify connection" className="self-start mt-0.5">
+											<button
+												class="self-center p-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
+												on:click={() => {
+													verifyOllamaHandler(idx);
+												}}
+												type="button"
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 20 20"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														fill-rule="evenodd"
+														d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
+														clip-rule="evenodd"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+									</div>
+								</div>
+							{/each}
+						</div>
+					</div>
+
+					<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+						{$i18n.t('Trouble accessing Ollama?')}
+						<a
+							class=" text-gray-300 font-medium underline"
+							href="https://github.com/open-webui/open-webui#troubleshooting"
+							target="_blank"
+						>
+							{$i18n.t('Click here for help.')}
+						</a>
+					</div>
+				{/if}
+			</div>
+		{:else}
+			<div class="flex h-full justify-center">
+				<div class="my-auto">
+					<Spinner className="size-6" />
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/admin/Settings/Database.svelte b/src/lib/components/admin/Settings/Database.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..3c376cb80e149e917756326a4b50b0f81a994ab7
--- /dev/null
+++ b/src/lib/components/admin/Settings/Database.svelte
@@ -0,0 +1,227 @@
+<script lang="ts">
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { downloadDatabase, downloadLiteLLMConfig } from '$lib/apis/utils';
+	import { onMount, getContext } from 'svelte';
+	import { config, user } from '$lib/stores';
+	import { toast } from 'svelte-sonner';
+	import { getAllUserChats } from '$lib/apis/chats';
+	import { exportConfig, importConfig } from '$lib/apis/configs';
+
+	const i18n = getContext('i18n');
+
+	export let saveHandler: Function;
+
+	const exportAllUserChats = async () => {
+		let blob = new Blob([JSON.stringify(await getAllUserChats(localStorage.token))], {
+			type: 'application/json'
+		});
+		saveAs(blob, `all-chats-export-${Date.now()}.json`);
+	};
+
+	onMount(async () => {
+		// permissions = await getUserPermissions(localStorage.token);
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={async () => {
+		saveHandler();
+	}}
+>
+	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
+		<div>
+			<div class=" mb-2 text-sm font-medium">{$i18n.t('Database')}</div>
+
+			<input
+				id="config-json-input"
+				hidden
+				type="file"
+				accept=".json"
+				on:change={(e) => {
+					const file = e.target.files[0];
+					const reader = new FileReader();
+
+					reader.onload = async (e) => {
+						const res = await importConfig(localStorage.token, JSON.parse(e.target.result)).catch(
+							(error) => {
+								toast.error(error);
+							}
+						);
+
+						if (res) {
+							toast.success('Config imported successfully');
+						}
+						e.target.value = null;
+					};
+
+					reader.readAsText(file);
+				}}
+			/>
+
+			<button
+				type="button"
+				class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+				on:click={async () => {
+					document.getElementById('config-json-input').click();
+				}}
+			>
+				<div class=" self-center mr-3">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 16 16"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
+						<path
+							fill-rule="evenodd"
+							d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+				<div class=" self-center text-sm font-medium">
+					{$i18n.t('Import Config from JSON File')}
+				</div>
+			</button>
+
+			<button
+				type="button"
+				class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+				on:click={async () => {
+					const config = await exportConfig(localStorage.token);
+					const blob = new Blob([JSON.stringify(config)], {
+						type: 'application/json'
+					});
+					saveAs(blob, `config-${Date.now()}.json`);
+				}}
+			>
+				<div class=" self-center mr-3">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 16 16"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
+						<path
+							fill-rule="evenodd"
+							d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+				<div class=" self-center text-sm font-medium">
+					{$i18n.t('Export Config to JSON File')}
+				</div>
+			</button>
+
+			<hr class=" dark:border-gray-850 my-1" />
+
+			{#if $config?.features.enable_admin_export ?? true}
+				<div class="  flex w-full justify-between">
+					<!-- <div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div> -->
+
+					<button
+						class=" flex rounded-md py-1.5 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+						type="button"
+						on:click={() => {
+							// exportAllUserChats();
+
+							downloadDatabase(localStorage.token).catch((error) => {
+								toast.error(error);
+							});
+						}}
+					>
+						<div class=" self-center mr-3">
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 16 16"
+								fill="currentColor"
+								class="w-4 h-4"
+							>
+								<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
+								<path
+									fill-rule="evenodd"
+									d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
+									clip-rule="evenodd"
+								/>
+							</svg>
+						</div>
+						<div class=" self-center text-sm font-medium">{$i18n.t('Download Database')}</div>
+					</button>
+				</div>
+
+				<button
+					class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+					on:click={() => {
+						exportAllUserChats();
+					}}
+				>
+					<div class=" self-center mr-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
+							<path
+								fill-rule="evenodd"
+								d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center text-sm font-medium">
+						{$i18n.t('Export All Chats (All Users)')}
+					</div>
+				</button>
+			{/if}
+
+			<hr class=" dark:border-gray-850 my-1" />
+
+			<button
+				type="button"
+				class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+				on:click={() => {
+					downloadLiteLLMConfig(localStorage.token).catch((error) => {
+						toast.error(error);
+					});
+				}}
+			>
+				<div class=" self-center mr-3">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 16 16"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
+						<path
+							fill-rule="evenodd"
+							d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+				<div class=" self-center text-sm font-medium">
+					{$i18n.t('Export LiteLLM config.yaml')}
+				</div>
+			</button>
+		</div>
+	</div>
+
+	<!-- <div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+
+	</div> -->
+</form>
diff --git a/src/lib/components/admin/Settings/Documents.svelte b/src/lib/components/admin/Settings/Documents.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..39e83badce162078158402cb3704353605fadafb
--- /dev/null
+++ b/src/lib/components/admin/Settings/Documents.svelte
@@ -0,0 +1,823 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+
+	import { onMount, getContext, createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	import {
+		getQuerySettings,
+		updateQuerySettings,
+		resetVectorDB,
+		getEmbeddingConfig,
+		updateEmbeddingConfig,
+		getRerankingConfig,
+		updateRerankingConfig,
+		resetUploadDir,
+		getRAGConfig,
+		updateRAGConfig
+	} from '$lib/apis/retrieval';
+
+	import { knowledge, models } from '$lib/stores';
+	import { getKnowledgeItems } from '$lib/apis/knowledge';
+	import { uploadDir, deleteAllFiles, deleteFileById } from '$lib/apis/files';
+
+	import ResetUploadDirConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import ResetVectorDBConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import { text } from '@sveltejs/kit';
+	import Textarea from '$lib/components/common/Textarea.svelte';
+
+	const i18n = getContext('i18n');
+
+	let scanDirLoading = false;
+	let updateEmbeddingModelLoading = false;
+	let updateRerankingModelLoading = false;
+
+	let showResetConfirm = false;
+	let showResetUploadDirConfirm = false;
+
+	let embeddingEngine = '';
+	let embeddingModel = '';
+	let embeddingBatchSize = 1;
+	let rerankingModel = '';
+
+	let fileMaxSize = null;
+	let fileMaxCount = null;
+
+	let contentExtractionEngine = 'default';
+	let tikaServerUrl = '';
+	let showTikaServerUrl = false;
+
+	let textSplitter = '';
+	let chunkSize = 0;
+	let chunkOverlap = 0;
+	let pdfExtractImages = true;
+
+	let OpenAIKey = '';
+	let OpenAIUrl = '';
+
+	let querySettings = {
+		template: '',
+		r: 0.0,
+		k: 4,
+		hybrid: false
+	};
+
+	const embeddingModelUpdateHandler = async () => {
+		if (embeddingEngine === '' && embeddingModel.split('/').length - 1 > 1) {
+			toast.error(
+				$i18n.t(
+					'Model filesystem path detected. Model shortname is required for update, cannot continue.'
+				)
+			);
+			return;
+		}
+		if (embeddingEngine === 'ollama' && embeddingModel === '') {
+			toast.error(
+				$i18n.t(
+					'Model filesystem path detected. Model shortname is required for update, cannot continue.'
+				)
+			);
+			return;
+		}
+
+		if (embeddingEngine === 'openai' && embeddingModel === '') {
+			toast.error(
+				$i18n.t(
+					'Model filesystem path detected. Model shortname is required for update, cannot continue.'
+				)
+			);
+			return;
+		}
+
+		if ((embeddingEngine === 'openai' && OpenAIKey === '') || OpenAIUrl === '') {
+			toast.error($i18n.t('OpenAI URL/Key required.'));
+			return;
+		}
+
+		console.log('Update embedding model attempt:', embeddingModel);
+
+		updateEmbeddingModelLoading = true;
+		const res = await updateEmbeddingConfig(localStorage.token, {
+			embedding_engine: embeddingEngine,
+			embedding_model: embeddingModel,
+			...(embeddingEngine === 'openai' || embeddingEngine === 'ollama'
+				? {
+						embedding_batch_size: embeddingBatchSize
+					}
+				: {}),
+			...(embeddingEngine === 'openai'
+				? {
+						openai_config: {
+							key: OpenAIKey,
+							url: OpenAIUrl
+						}
+					}
+				: {})
+		}).catch(async (error) => {
+			toast.error(error);
+			await setEmbeddingConfig();
+			return null;
+		});
+		updateEmbeddingModelLoading = false;
+
+		if (res) {
+			console.log('embeddingModelUpdateHandler:', res);
+			if (res.status === true) {
+				toast.success($i18n.t('Embedding model set to "{{embedding_model}}"', res), {
+					duration: 1000 * 10
+				});
+			}
+		}
+	};
+
+	const rerankingModelUpdateHandler = async () => {
+		console.log('Update reranking model attempt:', rerankingModel);
+
+		updateRerankingModelLoading = true;
+		const res = await updateRerankingConfig(localStorage.token, {
+			reranking_model: rerankingModel
+		}).catch(async (error) => {
+			toast.error(error);
+			await setRerankingConfig();
+			return null;
+		});
+		updateRerankingModelLoading = false;
+
+		if (res) {
+			console.log('rerankingModelUpdateHandler:', res);
+			if (res.status === true) {
+				if (rerankingModel === '') {
+					toast.success($i18n.t('Reranking model disabled', res), {
+						duration: 1000 * 10
+					});
+				} else {
+					toast.success($i18n.t('Reranking model set to "{{reranking_model}}"', res), {
+						duration: 1000 * 10
+					});
+				}
+			}
+		}
+	};
+
+	const submitHandler = async () => {
+		await embeddingModelUpdateHandler();
+
+		if (querySettings.hybrid) {
+			await rerankingModelUpdateHandler();
+		}
+
+		if (contentExtractionEngine === 'tika' && tikaServerUrl === '') {
+			toast.error($i18n.t('Tika Server URL required.'));
+			return;
+		}
+		const res = await updateRAGConfig(localStorage.token, {
+			pdf_extract_images: pdfExtractImages,
+			file: {
+				max_size: fileMaxSize === '' ? null : fileMaxSize,
+				max_count: fileMaxCount === '' ? null : fileMaxCount
+			},
+			chunk: {
+				text_splitter: textSplitter,
+				chunk_overlap: chunkOverlap,
+				chunk_size: chunkSize
+			},
+			content_extraction: {
+				engine: contentExtractionEngine,
+				tika_server_url: tikaServerUrl
+			}
+		});
+
+		await updateQuerySettings(localStorage.token, querySettings);
+
+		dispatch('save');
+	};
+
+	const setEmbeddingConfig = async () => {
+		const embeddingConfig = await getEmbeddingConfig(localStorage.token);
+
+		if (embeddingConfig) {
+			embeddingEngine = embeddingConfig.embedding_engine;
+			embeddingModel = embeddingConfig.embedding_model;
+			embeddingBatchSize = embeddingConfig.embedding_batch_size ?? 1;
+
+			OpenAIKey = embeddingConfig.openai_config.key;
+			OpenAIUrl = embeddingConfig.openai_config.url;
+		}
+	};
+
+	const setRerankingConfig = async () => {
+		const rerankingConfig = await getRerankingConfig(localStorage.token);
+
+		if (rerankingConfig) {
+			rerankingModel = rerankingConfig.reranking_model;
+		}
+	};
+
+	const toggleHybridSearch = async () => {
+		querySettings.hybrid = !querySettings.hybrid;
+		querySettings = await updateQuerySettings(localStorage.token, querySettings);
+	};
+
+	onMount(async () => {
+		await setEmbeddingConfig();
+		await setRerankingConfig();
+
+		querySettings = await getQuerySettings(localStorage.token);
+
+		const res = await getRAGConfig(localStorage.token);
+
+		if (res) {
+			pdfExtractImages = res.pdf_extract_images;
+
+			textSplitter = res.chunk.text_splitter;
+			chunkSize = res.chunk.chunk_size;
+			chunkOverlap = res.chunk.chunk_overlap;
+
+			contentExtractionEngine = res.content_extraction.engine;
+			tikaServerUrl = res.content_extraction.tika_server_url;
+			showTikaServerUrl = contentExtractionEngine === 'tika';
+
+			fileMaxSize = res?.file.max_size ?? '';
+			fileMaxCount = res?.file.max_count ?? '';
+		}
+	});
+</script>
+
+<ResetUploadDirConfirmDialog
+	bind:show={showResetUploadDirConfirm}
+	on:confirm={async () => {
+		const res = await deleteAllFiles(localStorage.token).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Success'));
+		}
+	}}
+/>
+
+<ResetVectorDBConfirmDialog
+	bind:show={showResetConfirm}
+	on:confirm={() => {
+		const res = resetVectorDB(localStorage.token).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Success'));
+		}
+	}}
+/>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={() => {
+		submitHandler();
+	}}
+>
+	<div class=" space-y-2.5 overflow-y-scroll scrollbar-hidden h-full pr-1.5">
+		<div class="flex flex-col gap-0.5">
+			<div class=" mb-0.5 text-sm font-medium">{$i18n.t('General Settings')}</div>
+
+			<div class=" flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Model Engine')}</div>
+				<div class="flex items-center relative">
+					<select
+						class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+						bind:value={embeddingEngine}
+						placeholder="Select an embedding model engine"
+						on:change={(e) => {
+							if (e.target.value === 'ollama') {
+								embeddingModel = '';
+							} else if (e.target.value === 'openai') {
+								embeddingModel = 'text-embedding-3-small';
+							} else if (e.target.value === '') {
+								embeddingModel = 'sentence-transformers/all-MiniLM-L6-v2';
+							}
+						}}
+					>
+						<option value="">{$i18n.t('Default (SentenceTransformers)')}</option>
+						<option value="ollama">{$i18n.t('Ollama')}</option>
+						<option value="openai">{$i18n.t('OpenAI')}</option>
+					</select>
+				</div>
+			</div>
+
+			{#if embeddingEngine === 'openai'}
+				<div class="my-0.5 flex gap-2">
+					<input
+						class="flex-1 w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+						placeholder={$i18n.t('API Base URL')}
+						bind:value={OpenAIUrl}
+						required
+					/>
+
+					<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={OpenAIKey} />
+				</div>
+			{/if}
+			{#if embeddingEngine === 'ollama' || embeddingEngine === 'openai'}
+				<div class="flex mt-0.5 space-x-2">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Batch Size')}</div>
+					<div class=" flex-1">
+						<input
+							id="steps-range"
+							type="range"
+							min="1"
+							max="2048"
+							step="1"
+							bind:value={embeddingBatchSize}
+							class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+						/>
+					</div>
+					<div class="">
+						<input
+							bind:value={embeddingBatchSize}
+							type="number"
+							class=" bg-transparent text-center w-14"
+							min="-2"
+							max="16000"
+							step="1"
+						/>
+					</div>
+				</div>
+			{/if}
+
+			<div class=" flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('Hybrid Search')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					on:click={() => {
+						toggleHybridSearch();
+					}}
+					type="button"
+				>
+					{#if querySettings.hybrid === true}
+						<span class="ml-2 self-center">{$i18n.t('On')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+					{/if}
+				</button>
+			</div>
+		</div>
+
+		<hr class="dark:border-gray-850" />
+
+		<div class="space-y-2" />
+		<div>
+			<div class=" mb-2 text-sm font-medium">{$i18n.t('Embedding Model')}</div>
+
+			{#if embeddingEngine === 'ollama'}
+				<div class="flex w-full">
+					<div class="flex-1 mr-2">
+						<select
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							bind:value={embeddingModel}
+							placeholder={$i18n.t('Select a model')}
+							required
+						>
+							{#if !embeddingModel}
+								<option value="" disabled selected>{$i18n.t('Select a model')}</option>
+							{/if}
+							{#each $models.filter((m) => m.id && m.ollama && !(m?.preset ?? false)) as model}
+								<option value={model.id} class="bg-gray-50 dark:bg-gray-700">{model.name}</option>
+							{/each}
+						</select>
+					</div>
+				</div>
+			{:else}
+				<div class="flex w-full">
+					<div class="flex-1 mr-2">
+						<input
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							placeholder={$i18n.t('Set embedding model (e.g. {{model}})', {
+								model: embeddingModel.slice(-40)
+							})}
+							bind:value={embeddingModel}
+						/>
+					</div>
+
+					{#if embeddingEngine === ''}
+						<button
+							class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+							on:click={() => {
+								embeddingModelUpdateHandler();
+							}}
+							disabled={updateEmbeddingModelLoading}
+						>
+							{#if updateEmbeddingModelLoading}
+								<div class="self-center">
+									<svg
+										class=" w-4 h-4"
+										viewBox="0 0 24 24"
+										fill="currentColor"
+										xmlns="http://www.w3.org/2000/svg"
+									>
+										<style>
+											.spinner_ajPY {
+												transform-origin: center;
+												animation: spinner_AtaB 0.75s infinite linear;
+											}
+
+											@keyframes spinner_AtaB {
+												100% {
+													transform: rotate(360deg);
+												}
+											}
+										</style>
+										<path
+											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+											opacity=".25"
+										/>
+										<path
+											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+											class="spinner_ajPY"
+										/>
+									</svg>
+								</div>
+							{:else}
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
+									/>
+									<path
+										d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+									/>
+								</svg>
+							{/if}
+						</button>
+					{/if}
+				</div>
+			{/if}
+
+			<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
+				{$i18n.t(
+					'Warning: If you update or change your embedding model, you will need to re-import all documents.'
+				)}
+			</div>
+
+			{#if querySettings.hybrid === true}
+				<div class=" ">
+					<div class=" mb-2 text-sm font-medium">{$i18n.t('Reranking Model')}</div>
+
+					<div class="flex w-full">
+						<div class="flex-1 mr-2">
+							<input
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								placeholder={$i18n.t('Set reranking model (e.g. {{model}})', {
+									model: 'BAAI/bge-reranker-v2-m3'
+								})}
+								bind:value={rerankingModel}
+							/>
+						</div>
+						<button
+							class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+							on:click={() => {
+								rerankingModelUpdateHandler();
+							}}
+							disabled={updateRerankingModelLoading}
+						>
+							{#if updateRerankingModelLoading}
+								<div class="self-center">
+									<svg
+										class=" w-4 h-4"
+										viewBox="0 0 24 24"
+										fill="currentColor"
+										xmlns="http://www.w3.org/2000/svg"
+									>
+										<style>
+											.spinner_ajPY {
+												transform-origin: center;
+												animation: spinner_AtaB 0.75s infinite linear;
+											}
+
+											@keyframes spinner_AtaB {
+												100% {
+													transform: rotate(360deg);
+												}
+											}
+										</style>
+										<path
+											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+											opacity=".25"
+										/>
+										<path
+											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+											class="spinner_ajPY"
+										/>
+									</svg>
+								</div>
+							{:else}
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
+									/>
+									<path
+										d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+									/>
+								</svg>
+							{/if}
+						</button>
+					</div>
+				</div>
+			{/if}
+		</div>
+
+		<hr class=" dark:border-gray-850" />
+
+		<div class="">
+			<div class="text-sm font-medium mb-1">{$i18n.t('Content Extraction')}</div>
+
+			<div class="flex w-full justify-between">
+				<div class="self-center text-xs font-medium">{$i18n.t('Engine')}</div>
+				<div class="flex items-center relative">
+					<select
+						class="dark:bg-gray-900 w-fit pr-8 rounded px-2 text-xs bg-transparent outline-none text-right"
+						bind:value={contentExtractionEngine}
+						on:change={(e) => {
+							showTikaServerUrl = e.target.value === 'tika';
+						}}
+					>
+						<option value="">{$i18n.t('Default')} </option>
+						<option value="tika">{$i18n.t('Tika')}</option>
+					</select>
+				</div>
+			</div>
+
+			{#if showTikaServerUrl}
+				<div class="flex w-full mt-1">
+					<div class="flex-1 mr-2">
+						<input
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							placeholder={$i18n.t('Enter Tika Server URL')}
+							bind:value={tikaServerUrl}
+						/>
+					</div>
+				</div>
+			{/if}
+		</div>
+
+		<hr class=" dark:border-gray-850" />
+
+		<div class=" ">
+			<div class=" text-sm font-medium mb-1">{$i18n.t('Query Params')}</div>
+
+			<div class=" flex gap-1.5">
+				<div class="flex flex-col w-full gap-1">
+					<div class=" text-xs font-medium w-full">{$i18n.t('Top K')}</div>
+
+					<div class="w-full">
+						<input
+							class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							type="number"
+							placeholder={$i18n.t('Enter Top K')}
+							bind:value={querySettings.k}
+							autocomplete="off"
+							min="0"
+						/>
+					</div>
+				</div>
+
+				{#if querySettings.hybrid === true}
+					<div class=" flex flex-col w-full gap-1">
+						<div class="text-xs font-medium w-full">
+							{$i18n.t('Minimum Score')}
+						</div>
+
+						<div class="w-full">
+							<input
+								class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								type="number"
+								step="0.01"
+								placeholder={$i18n.t('Enter Score')}
+								bind:value={querySettings.r}
+								autocomplete="off"
+								min="0.0"
+								title={$i18n.t('The score should be a value between 0.0 (0%) and 1.0 (100%).')}
+							/>
+						</div>
+					</div>
+				{/if}
+			</div>
+
+			{#if querySettings.hybrid === true}
+				<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+					{$i18n.t(
+						'Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.'
+					)}
+				</div>
+			{/if}
+
+			<div class="mt-2">
+				<div class=" mb-1 text-xs font-medium">{$i18n.t('RAG Template')}</div>
+				<Tooltip
+					content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
+					placement="top-start"
+				>
+					<Textarea
+						bind:value={querySettings.template}
+						placeholder={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
+					/>
+				</Tooltip>
+			</div>
+		</div>
+
+		<hr class=" dark:border-gray-850" />
+
+		<div class=" ">
+			<div class="mb-1 text-sm font-medium">{$i18n.t('Chunk Params')}</div>
+
+			<div class="flex w-full justify-between mb-1.5">
+				<div class="self-center text-xs font-medium">{$i18n.t('Text Splitter')}</div>
+				<div class="flex items-center relative">
+					<select
+						class="dark:bg-gray-900 w-fit pr-8 rounded px-2 text-xs bg-transparent outline-none text-right"
+						bind:value={textSplitter}
+					>
+						<option value="">{$i18n.t('Default')} ({$i18n.t('Character')})</option>
+						<option value="token">{$i18n.t('Token')} ({$i18n.t('Tiktoken')})</option>
+					</select>
+				</div>
+			</div>
+
+			<div class=" flex gap-1.5">
+				<div class="  w-full justify-between">
+					<div class="self-center text-xs font-medium min-w-fit mb-1">{$i18n.t('Chunk Size')}</div>
+					<div class="self-center">
+						<input
+							class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							type="number"
+							placeholder={$i18n.t('Enter Chunk Size')}
+							bind:value={chunkSize}
+							autocomplete="off"
+							min="0"
+						/>
+					</div>
+				</div>
+
+				<div class="w-full">
+					<div class=" self-center text-xs font-medium min-w-fit mb-1">
+						{$i18n.t('Chunk Overlap')}
+					</div>
+
+					<div class="self-center">
+						<input
+							class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							type="number"
+							placeholder={$i18n.t('Enter Chunk Overlap')}
+							bind:value={chunkOverlap}
+							autocomplete="off"
+							min="0"
+						/>
+					</div>
+				</div>
+			</div>
+
+			<div class="my-2">
+				<div class="flex justify-between items-center text-xs">
+					<div class=" text-xs font-medium">{$i18n.t('PDF Extract Images (OCR)')}</div>
+
+					<div>
+						<Switch bind:state={pdfExtractImages} />
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<hr class=" dark:border-gray-850" />
+
+		<div class="">
+			<div class="text-sm font-medium mb-1">{$i18n.t('Files')}</div>
+
+			<div class=" flex gap-1.5">
+				<div class="w-full">
+					<div class=" self-center text-xs font-medium min-w-fit mb-1">
+						{$i18n.t('Max Upload Size')}
+					</div>
+
+					<div class="self-center">
+						<Tooltip
+							content={$i18n.t(
+								'The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.'
+							)}
+							placement="top-start"
+						>
+							<input
+								class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								type="number"
+								placeholder={$i18n.t('Leave empty for unlimited')}
+								bind:value={fileMaxSize}
+								autocomplete="off"
+								min="0"
+							/>
+						</Tooltip>
+					</div>
+				</div>
+
+				<div class="  w-full">
+					<div class="self-center text-xs font-medium min-w-fit mb-1">
+						{$i18n.t('Max Upload Count')}
+					</div>
+					<div class="self-center">
+						<Tooltip
+							content={$i18n.t(
+								'The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.'
+							)}
+							placement="top-start"
+						>
+							<input
+								class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								type="number"
+								placeholder={$i18n.t('Leave empty for unlimited')}
+								bind:value={fileMaxCount}
+								autocomplete="off"
+								min="0"
+							/>
+						</Tooltip>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<hr class=" dark:border-gray-850" />
+
+		<div>
+			<button
+				class=" flex rounded-xl py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+				on:click={() => {
+					showResetUploadDirConfirm = true;
+				}}
+				type="button"
+			>
+				<div class=" self-center mr-3">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 24 24"
+						fill="currentColor"
+						class="size-4"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875ZM9.75 14.25a.75.75 0 0 0 0 1.5H15a.75.75 0 0 0 0-1.5H9.75Z"
+							clip-rule="evenodd"
+						/>
+						<path
+							d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
+						/>
+					</svg>
+				</div>
+				<div class=" self-center text-sm font-medium">{$i18n.t('Reset Upload Directory')}</div>
+			</button>
+
+			<button
+				class=" flex rounded-xl py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+				on:click={() => {
+					showResetConfirm = true;
+				}}
+				type="button"
+			>
+				<div class=" self-center mr-3">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 16 16"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M3.5 2A1.5 1.5 0 0 0 2 3.5v9A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 4H9.621a1.5 1.5 0 0 1-1.06-.44L7.439 2.44A1.5 1.5 0 0 0 6.38 2H3.5Zm6.75 7.75a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0 0 1.5h4.5Z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+				<div class=" self-center text-sm font-medium">
+					{$i18n.t('Reset Vector Storage/Knowledge')}
+				</div>
+			</button>
+		</div>
+	</div>
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/admin/Settings/Evaluations.svelte b/src/lib/components/admin/Settings/Evaluations.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..62130b203f900045ff768ac3495e281b51546d14
--- /dev/null
+++ b/src/lib/components/admin/Settings/Evaluations.svelte
@@ -0,0 +1,158 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { models, user } from '$lib/stores';
+	import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+	import { getModels } from '$lib/apis';
+
+	import Switch from '$lib/components/common/Switch.svelte';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Plus from '$lib/components/icons/Plus.svelte';
+	import Model from './Evaluations/Model.svelte';
+	import ModelModal from './Evaluations/ModelModal.svelte';
+	import { getConfig, updateConfig } from '$lib/apis/evaluations';
+
+	const i18n = getContext('i18n');
+
+	let config = null;
+	let showAddModel = false;
+
+	const submitHandler = async () => {
+		config = await updateConfig(localStorage.token, config).catch((err) => {
+			toast.error(err);
+			return null;
+		});
+
+		if (config) {
+			toast.success('Settings saved successfully');
+		}
+	};
+
+	const addModelHandler = async (model) => {
+		config.EVALUATION_ARENA_MODELS.push(model);
+		config.EVALUATION_ARENA_MODELS = [...config.EVALUATION_ARENA_MODELS];
+
+		await submitHandler();
+		models.set(await getModels(localStorage.token));
+	};
+
+	const editModelHandler = async (model, modelIdx) => {
+		config.EVALUATION_ARENA_MODELS[modelIdx] = model;
+		config.EVALUATION_ARENA_MODELS = [...config.EVALUATION_ARENA_MODELS];
+
+		await submitHandler();
+		models.set(await getModels(localStorage.token));
+	};
+
+	const deleteModelHandler = async (modelIdx) => {
+		config.EVALUATION_ARENA_MODELS = config.EVALUATION_ARENA_MODELS.filter(
+			(m, mIdx) => mIdx !== modelIdx
+		);
+
+		await submitHandler();
+		models.set(await getModels(localStorage.token));
+	};
+
+	onMount(async () => {
+		if ($user.role === 'admin') {
+			config = await getConfig(localStorage.token).catch((err) => {
+				toast.error(err);
+				return null;
+			});
+		}
+	});
+</script>
+
+<ModelModal
+	bind:show={showAddModel}
+	on:submit={async (e) => {
+		addModelHandler(e.detail);
+	}}
+/>
+
+<form
+	class="flex flex-col h-full justify-between text-sm"
+	on:submit|preventDefault={() => {
+		submitHandler();
+		dispatch('save');
+	}}
+>
+	<div class="overflow-y-scroll scrollbar-hidden h-full">
+		{#if config !== null}
+			<div class="">
+				<div class="text-sm font-medium mb-2">{$i18n.t('General Settings')}</div>
+
+				<div class=" mb-2">
+					<div class="flex justify-between items-center text-xs">
+						<div class=" text-xs font-medium">{$i18n.t('Arena Models')}</div>
+
+						<Tooltip content={$i18n.t(`Message rating should be enabled to use this feature`)}>
+							<Switch bind:state={config.ENABLE_EVALUATION_ARENA_MODELS} />
+						</Tooltip>
+					</div>
+				</div>
+
+				{#if config.ENABLE_EVALUATION_ARENA_MODELS}
+					<hr class=" border-gray-50 dark:border-gray-700/10 my-2" />
+
+					<div class="flex justify-between items-center mb-2">
+						<div class="text-sm font-medium">{$i18n.t('Manage Arena Models')}</div>
+
+						<div>
+							<Tooltip content={$i18n.t('Add Arena Model')}>
+								<button
+									class="p-1"
+									type="button"
+									on:click={() => {
+										showAddModel = true;
+									}}
+								>
+									<Plus />
+								</button>
+							</Tooltip>
+						</div>
+					</div>
+
+					<div class="flex flex-col gap-2">
+						{#if (config?.EVALUATION_ARENA_MODELS ?? []).length > 0}
+							{#each config.EVALUATION_ARENA_MODELS as model, index}
+								<Model
+									{model}
+									on:edit={(e) => {
+										editModelHandler(e.detail, index);
+									}}
+									on:delete={(e) => {
+										deleteModelHandler(index);
+									}}
+								/>
+							{/each}
+						{:else}
+							<div class=" text-center text-xs text-gray-500">
+								{$i18n.t(
+									`Using the default arena model with all models. Click the plus button to add custom models.`
+								)}
+							</div>
+						{/if}
+					</div>
+				{/if}
+			</div>
+		{:else}
+			<div class="flex h-full justify-center">
+				<div class="my-auto">
+					<Spinner className="size-6" />
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/admin/Settings/Evaluations/Model.svelte b/src/lib/components/admin/Settings/Evaluations/Model.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e6e92e15ff4427ad7c5c23e25d595940eded64f3
--- /dev/null
+++ b/src/lib/components/admin/Settings/Evaluations/Model.svelte
@@ -0,0 +1,63 @@
+<script lang="ts">
+	import { getContext, createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	import Cog6 from '$lib/components/icons/Cog6.svelte';
+	import ModelModal from './ModelModal.svelte';
+	export let model;
+
+	let showModel = false;
+</script>
+
+<ModelModal
+	bind:show={showModel}
+	edit={true}
+	{model}
+	on:submit={async (e) => {
+		dispatch('edit', e.detail);
+	}}
+	on:delete={async () => {
+		dispatch('delete');
+	}}
+/>
+
+<div class="py-0.5">
+	<div class="flex justify-between items-center mb-1">
+		<div class="flex flex-col flex-1">
+			<div class="flex gap-2.5 items-center">
+				<img
+					src={model.meta.profile_image_url}
+					alt={model.name}
+					class="size-8 rounded-full object-cover shrink-0"
+				/>
+
+				<div class="w-full flex flex-col">
+					<div class="flex items-center gap-1">
+						<div class="flex-shrink-0 line-clamp-1">
+							{model.name}
+						</div>
+					</div>
+
+					<div class="flex items-center gap-1">
+						<div class=" text-xs w-full text-gray-500 bg-transparent line-clamp-1">
+							{model?.meta?.description ?? model.id}
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<div class="flex items-center">
+			<button
+				class="self-center w-fit text-sm p-1.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+				type="button"
+				on:click={() => {
+					showModel = true;
+				}}
+			>
+				<Cog6 />
+			</button>
+		</div>
+	</div>
+</div>
diff --git a/src/lib/components/admin/Settings/Evaluations/ModelModal.svelte b/src/lib/components/admin/Settings/Evaluations/ModelModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9816d242aae9e0c99e592c3325b1c825e9ef7aba
--- /dev/null
+++ b/src/lib/components/admin/Settings/Evaluations/ModelModal.svelte
@@ -0,0 +1,418 @@
+<script>
+	import { createEventDispatcher, getContext, onMount } from 'svelte';
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import Modal from '$lib/components/common/Modal.svelte';
+	import { models } from '$lib/stores';
+	import Plus from '$lib/components/icons/Plus.svelte';
+	import Minus from '$lib/components/icons/Minus.svelte';
+	import PencilSolid from '$lib/components/icons/PencilSolid.svelte';
+	import { toast } from 'svelte-sonner';
+
+	export let show = false;
+	export let edit = false;
+
+	export let model = null;
+
+	let name = '';
+	let id = '';
+
+	$: if (name) {
+		generateId();
+	}
+
+	const generateId = () => {
+		if (!edit) {
+			id = name
+				.toLowerCase()
+				.replace(/[^a-z0-9]/g, '-')
+				.replace(/-+/g, '-')
+				.replace(/^-|-$/g, '');
+		}
+	};
+
+	let profileImageUrl = '/favicon.png';
+	let description = '';
+
+	let selectedModelId = '';
+	let modelIds = [];
+	let filterMode = 'include';
+
+	let imageInputElement;
+	let loading = false;
+
+	const addModelHandler = () => {
+		if (selectedModelId) {
+			modelIds = [...modelIds, selectedModelId];
+			selectedModelId = '';
+		}
+	};
+
+	const submitHandler = () => {
+		loading = true;
+
+		if (!name || !id) {
+			loading = false;
+			toast.error('Name and ID are required, please fill them out');
+			return;
+		}
+
+		if (!edit) {
+			if ($models.find((model) => model.name === name)) {
+				loading = false;
+				name = '';
+				toast.error('Model name already exists, please choose a different one');
+				return;
+			}
+		}
+
+		const model = {
+			id: id,
+			name: name,
+			meta: {
+				profile_image_url: profileImageUrl,
+				description: description || null,
+				model_ids: modelIds.length > 0 ? modelIds : null,
+				filter_mode: modelIds.length > 0 ? (filterMode ? filterMode : null) : null
+			}
+		};
+
+		dispatch('submit', model);
+		loading = false;
+		show = false;
+
+		name = '';
+		id = '';
+		profileImageUrl = '/favicon.png';
+		description = '';
+		modelIds = [];
+		selectedModelId = '';
+	};
+
+	const initModel = () => {
+		if (model) {
+			name = model.name;
+			id = model.id;
+			profileImageUrl = model.meta.profile_image_url;
+			description = model.meta.description;
+			modelIds = model.meta.model_ids || [];
+			filterMode = model.meta?.filter_mode ?? 'include';
+		}
+	};
+
+	$: if (show) {
+		initModel();
+	}
+
+	onMount(() => {
+		initModel();
+	});
+</script>
+
+<Modal size="sm" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-100 px-5 pt-4 pb-2">
+			<div class=" text-lg font-medium self-center font-primary">
+				{#if edit}
+					{$i18n.t('Edit Arena Model')}
+				{:else}
+					{$i18n.t('Add Arena Model')}
+				{/if}
+			</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full px-4 pb-4 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				<form
+					class="flex flex-col w-full"
+					on:submit|preventDefault={() => {
+						submitHandler();
+					}}
+				>
+					<div class="px-1">
+						<div class="flex justify-center pb-3">
+							<input
+								bind:this={imageInputElement}
+								type="file"
+								hidden
+								accept="image/*"
+								on:change={(e) => {
+									const files = e.target.files ?? [];
+									let reader = new FileReader();
+									reader.onload = (event) => {
+										let originalImageUrl = `${event.target.result}`;
+
+										const img = new Image();
+										img.src = originalImageUrl;
+
+										img.onload = function () {
+											const canvas = document.createElement('canvas');
+											const ctx = canvas.getContext('2d');
+
+											// Calculate the aspect ratio of the image
+											const aspectRatio = img.width / img.height;
+
+											// Calculate the new width and height to fit within 250x250
+											let newWidth, newHeight;
+											if (aspectRatio > 1) {
+												newWidth = 250 * aspectRatio;
+												newHeight = 250;
+											} else {
+												newWidth = 250;
+												newHeight = 250 / aspectRatio;
+											}
+
+											// Set the canvas size
+											canvas.width = 250;
+											canvas.height = 250;
+
+											// Calculate the position to center the image
+											const offsetX = (250 - newWidth) / 2;
+											const offsetY = (250 - newHeight) / 2;
+
+											// Draw the image on the canvas
+											ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
+
+											// Get the base64 representation of the compressed image
+											const compressedSrc = canvas.toDataURL('image/jpeg');
+
+											// Display the compressed image
+											profileImageUrl = compressedSrc;
+
+											e.target.files = null;
+										};
+									};
+
+									if (
+										files.length > 0 &&
+										['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(
+											files[0]['type']
+										)
+									) {
+										reader.readAsDataURL(files[0]);
+									}
+								}}
+							/>
+
+							<button
+								class="relative rounded-full w-fit h-fit shrink-0"
+								type="button"
+								on:click={() => {
+									imageInputElement.click();
+								}}
+							>
+								<img
+									src={profileImageUrl}
+									class="size-16 rounded-full object-cover shrink-0"
+									alt="Profile"
+								/>
+
+								<div
+									class="absolute flex justify-center rounded-full bottom-0 left-0 right-0 top-0 h-full w-full overflow-hidden bg-gray-700 bg-fixed opacity-0 transition duration-300 ease-in-out hover:opacity-50"
+								>
+									<div class="my-auto text-white">
+										<PencilSolid className="size-4" />
+									</div>
+								</div>
+							</button>
+						</div>
+						<div class="flex gap-2">
+							<div class="flex flex-col w-full">
+								<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('Name')}</div>
+
+								<div class="flex-1">
+									<input
+										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+										type="text"
+										bind:value={name}
+										placeholder={$i18n.t('Model Name')}
+										autocomplete="off"
+										required
+									/>
+								</div>
+							</div>
+
+							<div class="flex flex-col w-full">
+								<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('ID')}</div>
+
+								<div class="flex-1">
+									<input
+										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+										type="text"
+										bind:value={id}
+										placeholder={$i18n.t('Model ID')}
+										autocomplete="off"
+										required
+										disabled={edit}
+									/>
+								</div>
+							</div>
+						</div>
+
+						<div class="flex flex-col w-full mt-2">
+							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Description')}</div>
+
+							<div class="flex-1">
+								<input
+									class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+									type="text"
+									bind:value={description}
+									placeholder={$i18n.t('Enter description')}
+									autocomplete="off"
+								/>
+							</div>
+						</div>
+
+						<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
+
+						<div class="flex flex-col w-full">
+							<div class="mb-1 flex justify-between">
+								<div class="text-xs text-gray-500">{$i18n.t('Models')}</div>
+
+								<div>
+									<button
+										class=" text-xs text-gray-500"
+										type="button"
+										on:click={() => {
+											filterMode = filterMode === 'include' ? 'exclude' : 'include';
+										}}
+									>
+										{#if filterMode === 'include'}
+											{$i18n.t('Include')}
+										{:else}
+											{$i18n.t('Exclude')}
+										{/if}
+									</button>
+								</div>
+							</div>
+
+							{#if modelIds.length > 0}
+								<div class="flex flex-col">
+									{#each modelIds as modelId, modelIdx}
+										<div class=" flex gap-2 w-full justify-between items-center">
+											<div class=" text-sm flex-1 py-1 rounded-lg">
+												{$models.find((model) => model.id === modelId)?.name}
+											</div>
+											<div class="flex-shrink-0">
+												<button
+													type="button"
+													on:click={() => {
+														modelIds = modelIds.filter((_, idx) => idx !== modelIdx);
+													}}
+												>
+													<Minus strokeWidth="2" className="size-3.5" />
+												</button>
+											</div>
+										</div>
+									{/each}
+								</div>
+							{:else}
+								<div class="text-gray-500 text-xs text-center py-2">
+									{$i18n.t('Leave empty to include all models or select specific models')}
+								</div>
+							{/if}
+						</div>
+
+						<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
+
+						<div class="flex items-center">
+							<select
+								class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
+									? ''
+									: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+								bind:value={selectedModelId}
+							>
+								<option value="">{$i18n.t('Select a model')}</option>
+								{#each $models.filter((m) => m?.owned_by !== 'arena') as model}
+									<option value={model.id} class="bg-gray-50 dark:bg-gray-700">{model.name}</option>
+								{/each}
+							</select>
+
+							<div>
+								<button
+									type="button"
+									on:click={() => {
+										addModelHandler();
+									}}
+								>
+									<Plus className="size-3.5" strokeWidth="2" />
+								</button>
+							</div>
+						</div>
+					</div>
+
+					<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
+						{#if edit}
+							<button
+								class="px-3.5 py-1.5 text-sm font-medium dark:bg-black dark:hover:bg-gray-900 dark:text-white bg-white text-black hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center"
+								type="button"
+								on:click={() => {
+									dispatch('delete', model);
+									show = false;
+								}}
+							>
+								{$i18n.t('Delete')}
+							</button>
+						{/if}
+
+						<button
+							class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {loading
+								? ' cursor-not-allowed'
+								: ''}"
+							type="submit"
+							disabled={loading}
+						>
+							{$i18n.t('Save')}
+
+							{#if loading}
+								<div class="ml-2 self-center">
+									<svg
+										class=" w-4 h-4"
+										viewBox="0 0 24 24"
+										fill="currentColor"
+										xmlns="http://www.w3.org/2000/svg"
+										><style>
+											.spinner_ajPY {
+												transform-origin: center;
+												animation: spinner_AtaB 0.75s infinite linear;
+											}
+											@keyframes spinner_AtaB {
+												100% {
+													transform: rotate(360deg);
+												}
+											}
+										</style><path
+											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+											opacity=".25"
+										/><path
+											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+											class="spinner_ajPY"
+										/></svg
+									>
+								</div>
+							{/if}
+						</button>
+					</div>
+				</form>
+			</div>
+		</div>
+	</div>
+</Modal>
diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..b5d4e01e8dfef6e60d2c1668670742f81aea1654
--- /dev/null
+++ b/src/lib/components/admin/Settings/General.svelte
@@ -0,0 +1,146 @@
+<script lang="ts">
+	import { getBackendConfig, getWebhookUrl, updateWebhookUrl } from '$lib/apis';
+	import { getAdminConfig, updateAdminConfig } from '$lib/apis/auths';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import { config } from '$lib/stores';
+	import { onMount, getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+
+	const i18n = getContext('i18n');
+
+	export let saveHandler: Function;
+
+	let adminConfig = null;
+	let webhookUrl = '';
+
+	const updateHandler = async () => {
+		webhookUrl = await updateWebhookUrl(localStorage.token, webhookUrl);
+		const res = await updateAdminConfig(localStorage.token, adminConfig);
+
+		if (res) {
+			saveHandler();
+		} else {
+			toast.error(i18n.t('Failed to update settings'));
+		}
+	};
+
+	onMount(async () => {
+		await Promise.all([
+			(async () => {
+				adminConfig = await getAdminConfig(localStorage.token);
+			})(),
+
+			(async () => {
+				webhookUrl = await getWebhookUrl(localStorage.token);
+			})()
+		]);
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={async () => {
+		updateHandler();
+	}}
+>
+	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
+		{#if adminConfig !== null}
+			<div>
+				<div class=" mb-3 text-sm font-medium">{$i18n.t('General Settings')}</div>
+
+				<div class="  flex w-full justify-between pr-2">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Enable New Sign Ups')}</div>
+
+					<Switch bind:state={adminConfig.ENABLE_SIGNUP} />
+				</div>
+
+				<div class="  my-3 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Default User Role')}</div>
+					<div class="flex items-center relative">
+						<select
+							class="dark:bg-gray-900 w-fit pr-8 rounded px-2 text-xs bg-transparent outline-none text-right"
+							bind:value={adminConfig.DEFAULT_USER_ROLE}
+							placeholder="Select a role"
+						>
+							<option value="pending">{$i18n.t('pending')}</option>
+							<option value="user">{$i18n.t('user')}</option>
+							<option value="admin">{$i18n.t('admin')}</option>
+						</select>
+					</div>
+				</div>
+
+				<hr class=" dark:border-gray-850 my-2" />
+
+				<div class="my-3 flex w-full items-center justify-between pr-2">
+					<div class=" self-center text-xs font-medium">
+						{$i18n.t('Show Admin Details in Account Pending Overlay')}
+					</div>
+
+					<Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} />
+				</div>
+
+				<div class="my-3 flex w-full items-center justify-between pr-2">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Enable Community Sharing')}</div>
+
+					<Switch bind:state={adminConfig.ENABLE_COMMUNITY_SHARING} />
+				</div>
+
+				<div class="my-3 flex w-full items-center justify-between pr-2">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Enable Message Rating')}</div>
+
+					<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
+				</div>
+
+				<hr class=" dark:border-gray-850 my-2" />
+
+				<div class=" w-full justify-between">
+					<div class="flex w-full justify-between">
+						<div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div>
+					</div>
+
+					<div class="flex mt-2 space-x-2">
+						<input
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							type="text"
+							placeholder={`e.g.) "30m","1h", "10d". `}
+							bind:value={adminConfig.JWT_EXPIRES_IN}
+						/>
+					</div>
+
+					<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+						{$i18n.t('Valid time units:')}
+						<span class=" text-gray-300 font-medium"
+							>{$i18n.t("'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.")}</span
+						>
+					</div>
+				</div>
+
+				<hr class=" dark:border-gray-850 my-2" />
+
+				<div class=" w-full justify-between">
+					<div class="flex w-full justify-between">
+						<div class=" self-center text-xs font-medium">{$i18n.t('Webhook URL')}</div>
+					</div>
+
+					<div class="flex mt-2 space-x-2">
+						<input
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							type="text"
+							placeholder={`https://example.com/webhook`}
+							bind:value={webhookUrl}
+						/>
+					</div>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/admin/Settings/Images.svelte b/src/lib/components/admin/Settings/Images.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..4863577b234874f1b17b5f3bd149f35ec348f84a
--- /dev/null
+++ b/src/lib/components/admin/Settings/Images.svelte
@@ -0,0 +1,688 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+
+	import { createEventDispatcher, onMount, getContext } from 'svelte';
+	import { config as backendConfig, user } from '$lib/stores';
+
+	import { getBackendConfig } from '$lib/apis';
+	import {
+		getImageGenerationModels,
+		getImageGenerationConfig,
+		updateImageGenerationConfig,
+		getConfig,
+		updateConfig,
+		verifyConfigUrl
+	} from '$lib/apis/images';
+	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	const dispatch = createEventDispatcher();
+
+	const i18n = getContext('i18n');
+
+	let loading = false;
+
+	let config = null;
+	let imageGenerationConfig = null;
+
+	let models = null;
+
+	let samplers = [
+		'DPM++ 2M',
+		'DPM++ SDE',
+		'DPM++ 2M SDE',
+		'DPM++ 2M SDE Heun',
+		'DPM++ 2S a',
+		'DPM++ 3M SDE',
+		'Euler a',
+		'Euler',
+		'LMS',
+		'Heun',
+		'DPM2',
+		'DPM2 a',
+		'DPM fast',
+		'DPM adaptive',
+		'Restart',
+		'DDIM',
+		'DDIM CFG++',
+		'PLMS',
+		'UniPC'
+	];
+
+	let schedulers = [
+		'Automatic',
+		'Uniform',
+		'Karras',
+		'Exponential',
+		'Polyexponential',
+		'SGM Uniform',
+		'KL Optimal',
+		'Align Your Steps',
+		'Simple',
+		'Normal',
+		'DDIM',
+		'Beta'
+	];
+
+	let requiredWorkflowNodes = [
+		{
+			type: 'prompt',
+			key: 'text',
+			node_ids: ''
+		},
+		{
+			type: 'model',
+			key: 'ckpt_name',
+			node_ids: ''
+		},
+		{
+			type: 'width',
+			key: 'width',
+			node_ids: ''
+		},
+		{
+			type: 'height',
+			key: 'height',
+			node_ids: ''
+		},
+		{
+			type: 'steps',
+			key: 'steps',
+			node_ids: ''
+		},
+		{
+			type: 'seed',
+			key: 'seed',
+			node_ids: ''
+		}
+	];
+
+	const getModels = async () => {
+		models = await getImageGenerationModels(localStorage.token).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+	};
+
+	const updateConfigHandler = async () => {
+		const res = await updateConfig(localStorage.token, config).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			config = res;
+		}
+
+		if (config.enabled) {
+			backendConfig.set(await getBackendConfig());
+			getModels();
+		}
+	};
+
+	const validateJSON = (json) => {
+		try {
+			const obj = JSON.parse(json);
+
+			if (obj && typeof obj === 'object') {
+				return true;
+			}
+		} catch (e) {}
+		return false;
+	};
+
+	const saveHandler = async () => {
+		loading = true;
+
+		if (config?.comfyui?.COMFYUI_WORKFLOW) {
+			if (!validateJSON(config.comfyui.COMFYUI_WORKFLOW)) {
+				toast.error('Invalid JSON format for ComfyUI Workflow.');
+				loading = false;
+				return;
+			}
+		}
+
+		if (config?.comfyui?.COMFYUI_WORKFLOW) {
+			config.comfyui.COMFYUI_WORKFLOW_NODES = requiredWorkflowNodes.map((node) => {
+				return {
+					type: node.type,
+					key: node.key,
+					node_ids:
+						node.node_ids.trim() === '' ? [] : node.node_ids.split(',').map((id) => id.trim())
+				};
+			});
+		}
+
+		await updateConfig(localStorage.token, config).catch((error) => {
+			toast.error(error);
+			loading = false;
+			return null;
+		});
+
+		await updateImageGenerationConfig(localStorage.token, imageGenerationConfig).catch((error) => {
+			toast.error(error);
+			loading = false;
+			return null;
+		});
+
+		getModels();
+		dispatch('save');
+		loading = false;
+	};
+
+	onMount(async () => {
+		if ($user.role === 'admin') {
+			const res = await getConfig(localStorage.token).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+
+			if (res) {
+				config = res;
+			}
+
+			if (config.enabled) {
+				getModels();
+			}
+
+			if (config.comfyui.COMFYUI_WORKFLOW) {
+				config.comfyui.COMFYUI_WORKFLOW = JSON.stringify(
+					JSON.parse(config.comfyui.COMFYUI_WORKFLOW),
+					null,
+					2
+				);
+			}
+
+			requiredWorkflowNodes = requiredWorkflowNodes.map((node) => {
+				const n = config.comfyui.COMFYUI_WORKFLOW_NODES.find((n) => n.type === node.type) ?? node;
+
+				console.log(n);
+
+				return {
+					type: n.type,
+					key: n.key,
+					node_ids: typeof n.node_ids === 'string' ? n.node_ids : n.node_ids.join(',')
+				};
+			});
+
+			const imageConfigRes = await getImageGenerationConfig(localStorage.token).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+
+			if (imageConfigRes) {
+				imageGenerationConfig = imageConfigRes;
+			}
+		}
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={async () => {
+		saveHandler();
+	}}
+>
+	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden pr-2">
+		{#if config && imageGenerationConfig}
+			<div>
+				<div class=" mb-1 text-sm font-medium">{$i18n.t('Image Settings')}</div>
+
+				<div>
+					<div class=" py-0.5 flex w-full justify-between">
+						<div class=" self-center text-xs font-medium">
+							{$i18n.t('Image Generation (Experimental)')}
+						</div>
+
+						<div class="px-1">
+							<Switch
+								bind:state={config.enabled}
+								on:change={(e) => {
+									const enabled = e.detail;
+
+									if (enabled) {
+										if (
+											config.engine === 'automatic1111' &&
+											config.automatic1111.AUTOMATIC1111_BASE_URL === ''
+										) {
+											toast.error($i18n.t('AUTOMATIC1111 Base URL is required.'));
+											config.enabled = false;
+										} else if (
+											config.engine === 'comfyui' &&
+											config.comfyui.COMFYUI_BASE_URL === ''
+										) {
+											toast.error($i18n.t('ComfyUI Base URL is required.'));
+											config.enabled = false;
+										} else if (config.engine === 'openai' && config.openai.OPENAI_API_KEY === '') {
+											toast.error($i18n.t('OpenAI API Key is required.'));
+											config.enabled = false;
+										}
+									}
+
+									updateConfigHandler();
+								}}
+							/>
+						</div>
+					</div>
+				</div>
+
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Image Generation Engine')}</div>
+					<div class="flex items-center relative">
+						<select
+							class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							bind:value={config.engine}
+							placeholder={$i18n.t('Select Engine')}
+							on:change={async () => {
+								updateConfigHandler();
+							}}
+						>
+							<option value="openai">{$i18n.t('Default (Open AI)')}</option>
+							<option value="comfyui">{$i18n.t('ComfyUI')}</option>
+							<option value="automatic1111">{$i18n.t('Automatic1111')}</option>
+						</select>
+					</div>
+				</div>
+			</div>
+			<hr class=" dark:border-gray-850" />
+
+			<div class="flex flex-col gap-2">
+				{#if (config?.engine ?? 'automatic1111') === 'automatic1111'}
+					<div>
+						<div class=" mb-2 text-sm font-medium">{$i18n.t('AUTOMATIC1111 Base URL')}</div>
+						<div class="flex w-full">
+							<div class="flex-1 mr-2">
+								<input
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
+									bind:value={config.automatic1111.AUTOMATIC1111_BASE_URL}
+								/>
+							</div>
+							<button
+								class="px-2.5 bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+								type="button"
+								on:click={async () => {
+									await updateConfigHandler();
+									const res = await verifyConfigUrl(localStorage.token).catch((error) => {
+										toast.error(error);
+										return null;
+									});
+
+									if (res) {
+										toast.success($i18n.t('Server connection verified'));
+									}
+								}}
+							>
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 20 20"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										fill-rule="evenodd"
+										d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
+										clip-rule="evenodd"
+									/>
+								</svg>
+							</button>
+						</div>
+
+						<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+							{$i18n.t('Include `--api` flag when running stable-diffusion-webui')}
+							<a
+								class=" text-gray-300 font-medium"
+								href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734"
+								target="_blank"
+							>
+								{$i18n.t('(e.g. `sh webui.sh --api`)')}
+							</a>
+						</div>
+					</div>
+
+					<div>
+						<div class=" mb-2 text-sm font-medium">
+							{$i18n.t('AUTOMATIC1111 Api Auth String')}
+						</div>
+						<SensitiveInput
+							placeholder={$i18n.t('Enter api auth string (e.g. username:password)')}
+							bind:value={config.automatic1111.AUTOMATIC1111_API_AUTH}
+							required={false}
+						/>
+
+						<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+							{$i18n.t('Include `--api-auth` flag when running stable-diffusion-webui')}
+							<a
+								class=" text-gray-300 font-medium"
+								href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/13993"
+								target="_blank"
+							>
+								{$i18n
+									.t('(e.g. `sh webui.sh --api --api-auth username_password`)')
+									.replace('_', ':')}
+							</a>
+						</div>
+					</div>
+
+					<!---Sampler-->
+					<div>
+						<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Sampler')}</div>
+						<div class="flex w-full">
+							<div class="flex-1 mr-2">
+								<Tooltip content={$i18n.t('Enter Sampler (e.g. Euler a)')} placement="top-start">
+									<input
+										list="sampler-list"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										placeholder={$i18n.t('Enter Sampler (e.g. Euler a)')}
+										bind:value={config.automatic1111.AUTOMATIC1111_SAMPLER}
+									/>
+
+									<datalist id="sampler-list">
+										{#each samplers ?? [] as sampler}
+											<option value={sampler}>{sampler}</option>
+										{/each}
+									</datalist>
+								</Tooltip>
+							</div>
+						</div>
+					</div>
+					<!---Scheduler-->
+					<div>
+						<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Scheduler')}</div>
+						<div class="flex w-full">
+							<div class="flex-1 mr-2">
+								<Tooltip content={$i18n.t('Enter Scheduler (e.g. Karras)')} placement="top-start">
+									<input
+										list="scheduler-list"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										placeholder={$i18n.t('Enter Scheduler (e.g. Karras)')}
+										bind:value={config.automatic1111.AUTOMATIC1111_SCHEDULER}
+									/>
+
+									<datalist id="scheduler-list">
+										{#each schedulers ?? [] as scheduler}
+											<option value={scheduler}>{scheduler}</option>
+										{/each}
+									</datalist>
+								</Tooltip>
+							</div>
+						</div>
+					</div>
+					<!---CFG scale-->
+					<div>
+						<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set CFG Scale')}</div>
+						<div class="flex w-full">
+							<div class="flex-1 mr-2">
+								<Tooltip content={$i18n.t('Enter CFG Scale (e.g. 7.0)')} placement="top-start">
+									<input
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										placeholder={$i18n.t('Enter CFG Scale (e.g. 7.0)')}
+										bind:value={config.automatic1111.AUTOMATIC1111_CFG_SCALE}
+									/>
+								</Tooltip>
+							</div>
+						</div>
+					</div>
+				{:else if config?.engine === 'comfyui'}
+					<div class="">
+						<div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Base URL')}</div>
+						<div class="flex w-full">
+							<div class="flex-1 mr-2">
+								<input
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
+									bind:value={config.comfyui.COMFYUI_BASE_URL}
+								/>
+							</div>
+							<button
+								class="px-2.5 bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+								type="button"
+								on:click={async () => {
+									await updateConfigHandler();
+									const res = await verifyConfigUrl(localStorage.token).catch((error) => {
+										toast.error(error);
+										return null;
+									});
+
+									if (res) {
+										toast.success($i18n.t('Server connection verified'));
+									}
+								}}
+							>
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 20 20"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										fill-rule="evenodd"
+										d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
+										clip-rule="evenodd"
+									/>
+								</svg>
+							</button>
+						</div>
+					</div>
+
+					<div class="">
+						<div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Workflow')}</div>
+
+						{#if config.comfyui.COMFYUI_WORKFLOW}
+							<textarea
+								class="w-full rounded-lg mb-1 py-2 px-4 text-xs bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none disabled:text-gray-600 resize-none"
+								rows="10"
+								bind:value={config.comfyui.COMFYUI_WORKFLOW}
+								required
+							/>
+						{/if}
+
+						<div class="flex w-full">
+							<div class="flex-1">
+								<input
+									id="upload-comfyui-workflow-input"
+									hidden
+									type="file"
+									accept=".json"
+									on:change={(e) => {
+										const file = e.target.files[0];
+										const reader = new FileReader();
+
+										reader.onload = (e) => {
+											config.comfyui.COMFYUI_WORKFLOW = e.target.result;
+											e.target.value = null;
+										};
+
+										reader.readAsText(file);
+									}}
+								/>
+
+								<button
+									class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
+									type="button"
+									on:click={() => {
+										document.getElementById('upload-comfyui-workflow-input')?.click();
+									}}
+								>
+									{$i18n.t('Click here to upload a workflow.json file.')}
+								</button>
+							</div>
+						</div>
+
+						<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+							{$i18n.t('Make sure to export a workflow.json file as API format from ComfyUI.')}
+						</div>
+					</div>
+
+					{#if config.comfyui.COMFYUI_WORKFLOW}
+						<div class="">
+							<div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Workflow Nodes')}</div>
+
+							<div class="text-xs flex flex-col gap-1.5">
+								{#each requiredWorkflowNodes as node}
+									<div class="flex w-full items-center border dark:border-gray-850 rounded-lg">
+										<div class="flex-shrink-0">
+											<div
+												class=" capitalize line-clamp-1 font-medium px-3 py-1 w-20 text-center rounded-l-lg bg-green-500/10 text-green-700 dark:text-green-200"
+											>
+												{node.type}{node.type === 'prompt' ? '*' : ''}
+											</div>
+										</div>
+										<div class="">
+											<Tooltip content="Input Key (e.g. text, unet_name, steps)">
+												<input
+													class="py-1 px-3 w-24 text-xs text-center bg-transparent outline-none border-r dark:border-gray-850"
+													placeholder="Key"
+													bind:value={node.key}
+													required
+												/>
+											</Tooltip>
+										</div>
+
+										<div class="w-full">
+											<Tooltip
+												content="Comma separated Node Ids (e.g. 1 or 1,2)"
+												placement="top-start"
+											>
+												<input
+													class="w-full py-1 px-4 rounded-r-lg text-xs bg-transparent outline-none"
+													placeholder="Node Ids"
+													bind:value={node.node_ids}
+												/>
+											</Tooltip>
+										</div>
+									</div>
+								{/each}
+							</div>
+
+							<div class="mt-2 text-xs text-right text-gray-400 dark:text-gray-500">
+								{$i18n.t('*Prompt node ID(s) are required for image generation')}
+							</div>
+						</div>
+					{/if}
+				{:else if config?.engine === 'openai'}
+					<div>
+						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('OpenAI API Config')}</div>
+
+						<div class="flex gap-2 mb-1">
+							<input
+								class="flex-1 w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								placeholder={$i18n.t('API Base URL')}
+								bind:value={config.openai.OPENAI_API_BASE_URL}
+								required
+							/>
+
+							<SensitiveInput
+								placeholder={$i18n.t('API Key')}
+								bind:value={config.openai.OPENAI_API_KEY}
+							/>
+						</div>
+					</div>
+				{/if}
+			</div>
+
+			{#if config?.enabled}
+				<hr class=" dark:border-gray-850" />
+
+				<div>
+					<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
+					<div class="flex w-full">
+						<div class="flex-1 mr-2">
+							<div class="flex w-full">
+								<div class="flex-1">
+									<Tooltip content={$i18n.t('Enter Model ID')} placement="top-start">
+										<input
+											list="model-list"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											bind:value={imageGenerationConfig.MODEL}
+											placeholder="Select a model"
+											required
+										/>
+
+										<datalist id="model-list">
+											{#each models ?? [] as model}
+												<option value={model.id}>{model.name}</option>
+											{/each}
+										</datalist>
+									</Tooltip>
+								</div>
+							</div>
+						</div>
+					</div>
+				</div>
+
+				<div>
+					<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Image Size')}</div>
+					<div class="flex w-full">
+						<div class="flex-1 mr-2">
+							<Tooltip content={$i18n.t('Enter Image Size (e.g. 512x512)')} placement="top-start">
+								<input
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
+									bind:value={imageGenerationConfig.IMAGE_SIZE}
+									required
+								/>
+							</Tooltip>
+						</div>
+					</div>
+				</div>
+
+				<div>
+					<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Steps')}</div>
+					<div class="flex w-full">
+						<div class="flex-1 mr-2">
+							<Tooltip content={$i18n.t('Enter Number of Steps (e.g. 50)')} placement="top-start">
+								<input
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
+									bind:value={imageGenerationConfig.IMAGE_STEPS}
+									required
+								/>
+							</Tooltip>
+						</div>
+					</div>
+				</div>
+			{/if}
+		{/if}
+	</div>
+
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {loading
+				? ' cursor-not-allowed'
+				: ''}"
+			type="submit"
+			disabled={loading}
+		>
+			{$i18n.t('Save')}
+
+			{#if loading}
+				<div class="ml-2 self-center">
+					<svg
+						class=" w-4 h-4"
+						viewBox="0 0 24 24"
+						fill="currentColor"
+						xmlns="http://www.w3.org/2000/svg"
+						><style>
+							.spinner_ajPY {
+								transform-origin: center;
+								animation: spinner_AtaB 0.75s infinite linear;
+							}
+							@keyframes spinner_AtaB {
+								100% {
+									transform: rotate(360deg);
+								}
+							}
+						</style><path
+							d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+							opacity=".25"
+						/><path
+							d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+							class="spinner_ajPY"
+						/></svg
+					>
+				</div>
+			{/if}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/admin/Settings/Interface.svelte b/src/lib/components/admin/Settings/Interface.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..54819ed9e3f5bf8288619e53f3ed0ffeae3650d5
--- /dev/null
+++ b/src/lib/components/admin/Settings/Interface.svelte
@@ -0,0 +1,370 @@
+<script lang="ts">
+	import { v4 as uuidv4 } from 'uuid';
+	import { toast } from 'svelte-sonner';
+
+	import { getBackendConfig, getTaskConfig, updateTaskConfig } from '$lib/apis';
+	import { setDefaultPromptSuggestions } from '$lib/apis/configs';
+	import { config, models, settings, user } from '$lib/stores';
+	import { createEventDispatcher, onMount, getContext } from 'svelte';
+
+	import { banners as _banners } from '$lib/stores';
+	import type { Banner } from '$lib/types';
+
+	import { getBanners, setBanners } from '$lib/apis/configs';
+
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import Textarea from '$lib/components/common/Textarea.svelte';
+
+	const dispatch = createEventDispatcher();
+
+	const i18n = getContext('i18n');
+
+	let taskConfig = {
+		TASK_MODEL: '',
+		TASK_MODEL_EXTERNAL: '',
+		TITLE_GENERATION_PROMPT_TEMPLATE: '',
+		TAGS_GENERATION_PROMPT_TEMPLATE: '',
+		ENABLE_SEARCH_QUERY: true,
+		SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE: ''
+	};
+
+	let promptSuggestions = [];
+	let banners: Banner[] = [];
+
+	const updateInterfaceHandler = async () => {
+		taskConfig = await updateTaskConfig(localStorage.token, taskConfig);
+
+		promptSuggestions = await setDefaultPromptSuggestions(localStorage.token, promptSuggestions);
+		await updateBanners();
+
+		await config.set(await getBackendConfig());
+	};
+
+	onMount(async () => {
+		taskConfig = await getTaskConfig(localStorage.token);
+
+		promptSuggestions = $config?.default_prompt_suggestions;
+		banners = await getBanners(localStorage.token);
+	});
+
+	const updateBanners = async () => {
+		_banners.set(await setBanners(localStorage.token, banners));
+	};
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={() => {
+		updateInterfaceHandler();
+		dispatch('save');
+	}}
+>
+	<div class="  overflow-y-scroll scrollbar-hidden h-full pr-1.5">
+		<div>
+			<div class=" mb-2.5 text-sm font-medium flex items-center">
+				<div class=" mr-1">{$i18n.t('Set Task Model')}</div>
+				<Tooltip
+					content={$i18n.t(
+						'A task model is used when performing tasks such as generating titles for chats and web search queries'
+					)}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="size-3.5"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
+						/>
+					</svg>
+				</Tooltip>
+			</div>
+			<div class="flex w-full gap-2">
+				<div class="flex-1">
+					<div class=" text-xs mb-1">{$i18n.t('Local Models')}</div>
+					<select
+						class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+						bind:value={taskConfig.TASK_MODEL}
+						placeholder={$i18n.t('Select a model')}
+					>
+						<option value="" selected>{$i18n.t('Current Model')}</option>
+						{#each $models.filter((m) => m.owned_by === 'ollama') as model}
+							<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
+								{model.name}
+							</option>
+						{/each}
+					</select>
+				</div>
+
+				<div class="flex-1">
+					<div class=" text-xs mb-1">{$i18n.t('External Models')}</div>
+					<select
+						class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+						bind:value={taskConfig.TASK_MODEL_EXTERNAL}
+						placeholder={$i18n.t('Select a model')}
+					>
+						<option value="" selected>{$i18n.t('Current Model')}</option>
+						{#each $models as model}
+							<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
+								{model.name}
+							</option>
+						{/each}
+					</select>
+				</div>
+			</div>
+
+			<div class="mt-3">
+				<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Title Generation Prompt')}</div>
+
+				<Tooltip
+					content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
+					placement="top-start"
+				>
+					<Textarea
+						bind:value={taskConfig.TITLE_GENERATION_PROMPT_TEMPLATE}
+						placeholder={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
+					/>
+				</Tooltip>
+			</div>
+
+			<div class="mt-3">
+				<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Tags Generation Prompt')}</div>
+
+				<Tooltip
+					content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
+					placement="top-start"
+				>
+					<Textarea
+						bind:value={taskConfig.TAGS_GENERATION_PROMPT_TEMPLATE}
+						placeholder={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
+					/>
+				</Tooltip>
+			</div>
+
+			<hr class=" dark:border-gray-850 my-3" />
+
+			<div class="my-3 flex w-full items-center justify-between">
+				<div class=" self-center text-xs font-medium">
+					{$i18n.t('Enable Web Search Query Generation')}
+				</div>
+
+				<Switch bind:state={taskConfig.ENABLE_SEARCH_QUERY} />
+			</div>
+
+			{#if taskConfig.ENABLE_SEARCH_QUERY}
+				<div class="">
+					<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Search Query Generation Prompt')}</div>
+
+					<Tooltip
+						content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
+						placement="top-start"
+					>
+						<Textarea
+							bind:value={taskConfig.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE}
+							placeholder={$i18n.t(
+								'Leave empty to use the default prompt, or enter a custom prompt'
+							)}
+						/>
+					</Tooltip>
+				</div>
+			{/if}
+		</div>
+
+		<hr class=" dark:border-gray-850 my-3" />
+
+		<div class=" space-y-3 {banners.length > 0 ? ' mb-3' : ''}">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-sm font-semibold">
+					{$i18n.t('Banners')}
+				</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					type="button"
+					on:click={() => {
+						if (banners.length === 0 || banners.at(-1).content !== '') {
+							banners = [
+								...banners,
+								{
+									id: uuidv4(),
+									type: '',
+									title: '',
+									content: '',
+									dismissible: true,
+									timestamp: Math.floor(Date.now() / 1000)
+								}
+							];
+						}
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+						/>
+					</svg>
+				</button>
+			</div>
+			<div class="flex flex-col space-y-1">
+				{#each banners as banner, bannerIdx}
+					<div class=" flex justify-between">
+						<div class="flex flex-row flex-1 border rounded-xl dark:border-gray-800">
+							<select
+								class="w-fit capitalize rounded-xl py-2 px-4 text-xs bg-transparent outline-none"
+								bind:value={banner.type}
+								required
+							>
+								{#if banner.type == ''}
+									<option value="" selected disabled class="text-gray-900">{$i18n.t('Type')}</option
+									>
+								{/if}
+								<option value="info" class="text-gray-900">{$i18n.t('Info')}</option>
+								<option value="warning" class="text-gray-900">{$i18n.t('Warning')}</option>
+								<option value="error" class="text-gray-900">{$i18n.t('Error')}</option>
+								<option value="success" class="text-gray-900">{$i18n.t('Success')}</option>
+							</select>
+
+							<input
+								class="pr-5 py-1.5 text-xs w-full bg-transparent outline-none"
+								placeholder={$i18n.t('Content')}
+								bind:value={banner.content}
+							/>
+
+							<div class="relative top-1.5 -left-2">
+								<Tooltip content={$i18n.t('Dismissible')} className="flex h-fit items-center">
+									<Switch bind:state={banner.dismissible} />
+								</Tooltip>
+							</div>
+						</div>
+
+						<button
+							class="px-2"
+							type="button"
+							on:click={() => {
+								banners.splice(bannerIdx, 1);
+								banners = banners;
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-4 h-4"
+							>
+								<path
+									d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+								/>
+							</svg>
+						</button>
+					</div>
+				{/each}
+			</div>
+		</div>
+
+		{#if $user.role === 'admin'}
+			<div class=" space-y-3">
+				<div class="flex w-full justify-between mb-2">
+					<div class=" self-center text-sm font-semibold">
+						{$i18n.t('Default Prompt Suggestions')}
+					</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
+								promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
+							}
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 20 20"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+							/>
+						</svg>
+					</button>
+				</div>
+				<div class="grid lg:grid-cols-2 flex-col gap-1.5">
+					{#each promptSuggestions as prompt, promptIdx}
+						<div
+							class=" flex border border-gray-100 dark:border-none dark:bg-gray-850 rounded-xl py-1.5"
+						>
+							<div class="flex flex-col flex-1 pl-1">
+								<div class="flex border-b border-gray-100 dark:border-gray-800 w-full">
+									<input
+										class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r border-gray-100 dark:border-gray-800"
+										placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
+										bind:value={prompt.title[0]}
+									/>
+
+									<input
+										class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r border-gray-100 dark:border-gray-800"
+										placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
+										bind:value={prompt.title[1]}
+									/>
+								</div>
+
+								<textarea
+									class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r border-gray-100 dark:border-gray-800 resize-none"
+									placeholder={$i18n.t('Prompt (e.g. Tell me a fun fact about the Roman Empire)')}
+									rows="3"
+									bind:value={prompt.content}
+								/>
+							</div>
+
+							<button
+								class="px-3"
+								type="button"
+								on:click={() => {
+									promptSuggestions.splice(promptIdx, 1);
+									promptSuggestions = promptSuggestions;
+								}}
+							>
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 20 20"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+									/>
+								</svg>
+							</button>
+						</div>
+					{/each}
+				</div>
+
+				{#if promptSuggestions.length > 0}
+					<div class="text-xs text-left w-full mt-2">
+						{$i18n.t('Adjusting these settings will apply changes universally to all users.')}
+					</div>
+				{/if}
+			</div>
+		{/if}
+	</div>
+
+	<div class="flex justify-end text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/admin/Settings/Models.svelte b/src/lib/components/admin/Settings/Models.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..877a1b78e71f37bf082f000ce31c7a425521c7bd
--- /dev/null
+++ b/src/lib/components/admin/Settings/Models.svelte
@@ -0,0 +1,1090 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { onMount, getContext } from 'svelte';
+
+	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
+	import { WEBUI_NAME, models, MODEL_DOWNLOAD_POOL, user, config } from '$lib/stores';
+	import { splitStream } from '$lib/utils';
+
+	import {
+		createModel,
+		deleteModel,
+		downloadModel,
+		getOllamaUrls,
+		getOllamaVersion,
+		pullModel,
+		uploadModel,
+		getOllamaConfig
+	} from '$lib/apis/ollama';
+	import { getModels as _getModels } from '$lib/apis';
+
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import ModelDeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+
+	const i18n = getContext('i18n');
+
+	const getModels = async () => {
+		return await _getModels(localStorage.token);
+	};
+
+	let modelUploadInputElement: HTMLInputElement;
+
+	let showModelDeleteConfirm = false;
+
+	// Models
+
+	let ollamaEnabled = null;
+
+	let OLLAMA_URLS = [];
+	let selectedOllamaUrlIdx: number | null = null;
+
+	let updateModelId = null;
+	let updateProgress = null;
+
+	let showExperimentalOllama = false;
+
+	let ollamaVersion = null;
+	const MAX_PARALLEL_DOWNLOADS = 3;
+
+	let modelTransferring = false;
+	let modelTag = '';
+
+	let createModelLoading = false;
+	let createModelTag = '';
+	let createModelContent = '';
+	let createModelDigest = '';
+	let createModelPullProgress = null;
+
+	let digest = '';
+	let pullProgress = null;
+
+	let modelUploadMode = 'file';
+	let modelInputFile: File[] | null = null;
+	let modelFileUrl = '';
+	let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`;
+	let modelFileDigest = '';
+
+	let uploadProgress = null;
+	let uploadMessage = '';
+
+	let deleteModelTag = '';
+
+	const updateModelsHandler = async () => {
+		for (const model of $models.filter(
+			(m) =>
+				!(m?.preset ?? false) &&
+				m.owned_by === 'ollama' &&
+				(selectedOllamaUrlIdx === null
+					? true
+					: (m?.ollama?.urls ?? []).includes(selectedOllamaUrlIdx))
+		)) {
+			console.log(model);
+
+			updateModelId = model.id;
+			const [res, controller] = await pullModel(
+				localStorage.token,
+				model.id,
+				selectedOllamaUrlIdx
+			).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+
+			if (res) {
+				const reader = res.body
+					.pipeThrough(new TextDecoderStream())
+					.pipeThrough(splitStream('\n'))
+					.getReader();
+
+				while (true) {
+					try {
+						const { value, done } = await reader.read();
+						if (done) break;
+
+						let lines = value.split('\n');
+
+						for (const line of lines) {
+							if (line !== '') {
+								let data = JSON.parse(line);
+
+								console.log(data);
+								if (data.error) {
+									throw data.error;
+								}
+								if (data.detail) {
+									throw data.detail;
+								}
+								if (data.status) {
+									if (data.digest) {
+										updateProgress = 0;
+										if (data.completed) {
+											updateProgress = Math.round((data.completed / data.total) * 1000) / 10;
+										} else {
+											updateProgress = 100;
+										}
+									} else {
+										toast.success(data.status);
+									}
+								}
+							}
+						}
+					} catch (error) {
+						console.log(error);
+					}
+				}
+			}
+		}
+
+		updateModelId = null;
+		updateProgress = null;
+	};
+
+	const pullModelHandler = async () => {
+		const sanitizedModelTag = modelTag.trim().replace(/^ollama\s+(run|pull)\s+/, '');
+		console.log($MODEL_DOWNLOAD_POOL);
+		if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) {
+			toast.error(
+				$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, {
+					modelTag: sanitizedModelTag
+				})
+			);
+			return;
+		}
+		if (Object.keys($MODEL_DOWNLOAD_POOL).length === MAX_PARALLEL_DOWNLOADS) {
+			toast.error(
+				$i18n.t('Maximum of 3 models can be downloaded simultaneously. Please try again later.')
+			);
+			return;
+		}
+
+		const [res, controller] = await pullModel(
+			localStorage.token,
+			sanitizedModelTag,
+			selectedOllamaUrlIdx
+		).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			const reader = res.body
+				.pipeThrough(new TextDecoderStream())
+				.pipeThrough(splitStream('\n'))
+				.getReader();
+
+			MODEL_DOWNLOAD_POOL.set({
+				...$MODEL_DOWNLOAD_POOL,
+				[sanitizedModelTag]: {
+					...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
+					abortController: controller,
+					reader,
+					done: false
+				}
+			});
+
+			while (true) {
+				try {
+					const { value, done } = await reader.read();
+					if (done) break;
+
+					let lines = value.split('\n');
+
+					for (const line of lines) {
+						if (line !== '') {
+							let data = JSON.parse(line);
+							console.log(data);
+							if (data.error) {
+								throw data.error;
+							}
+							if (data.detail) {
+								throw data.detail;
+							}
+
+							if (data.status) {
+								if (data.digest) {
+									let downloadProgress = 0;
+									if (data.completed) {
+										downloadProgress = Math.round((data.completed / data.total) * 1000) / 10;
+									} else {
+										downloadProgress = 100;
+									}
+
+									MODEL_DOWNLOAD_POOL.set({
+										...$MODEL_DOWNLOAD_POOL,
+										[sanitizedModelTag]: {
+											...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
+											pullProgress: downloadProgress,
+											digest: data.digest
+										}
+									});
+								} else {
+									toast.success(data.status);
+
+									MODEL_DOWNLOAD_POOL.set({
+										...$MODEL_DOWNLOAD_POOL,
+										[sanitizedModelTag]: {
+											...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
+											done: data.status === 'success'
+										}
+									});
+								}
+							}
+						}
+					}
+				} catch (error) {
+					console.log(error);
+					if (typeof error !== 'string') {
+						error = error.message;
+					}
+
+					toast.error(error);
+					// opts.callback({ success: false, error, modelName: opts.modelName });
+				}
+			}
+
+			console.log($MODEL_DOWNLOAD_POOL[sanitizedModelTag]);
+
+			if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag].done) {
+				toast.success(
+					$i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, {
+						modelName: sanitizedModelTag
+					})
+				);
+
+				models.set(await getModels());
+			} else {
+				toast.error($i18n.t('Download canceled'));
+			}
+
+			delete $MODEL_DOWNLOAD_POOL[sanitizedModelTag];
+
+			MODEL_DOWNLOAD_POOL.set({
+				...$MODEL_DOWNLOAD_POOL
+			});
+		}
+
+		modelTag = '';
+		modelTransferring = false;
+	};
+
+	const uploadModelHandler = async () => {
+		modelTransferring = true;
+
+		let uploaded = false;
+		let fileResponse = null;
+		let name = '';
+
+		if (modelUploadMode === 'file') {
+			const file = modelInputFile ? modelInputFile[0] : null;
+
+			if (file) {
+				uploadMessage = 'Uploading...';
+
+				fileResponse = await uploadModel(localStorage.token, file, selectedOllamaUrlIdx).catch(
+					(error) => {
+						toast.error(error);
+						return null;
+					}
+				);
+			}
+		} else {
+			uploadProgress = 0;
+			fileResponse = await downloadModel(
+				localStorage.token,
+				modelFileUrl,
+				selectedOllamaUrlIdx
+			).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+		}
+
+		if (fileResponse && fileResponse.ok) {
+			const reader = fileResponse.body
+				.pipeThrough(new TextDecoderStream())
+				.pipeThrough(splitStream('\n'))
+				.getReader();
+
+			while (true) {
+				const { value, done } = await reader.read();
+				if (done) break;
+
+				try {
+					let lines = value.split('\n');
+
+					for (const line of lines) {
+						if (line !== '') {
+							let data = JSON.parse(line.replace(/^data: /, ''));
+
+							if (data.progress) {
+								if (uploadMessage) {
+									uploadMessage = '';
+								}
+								uploadProgress = data.progress;
+							}
+
+							if (data.error) {
+								throw data.error;
+							}
+
+							if (data.done) {
+								modelFileDigest = data.blob;
+								name = data.name;
+								uploaded = true;
+							}
+						}
+					}
+				} catch (error) {
+					console.log(error);
+				}
+			}
+		} else {
+			const error = await fileResponse?.json();
+			toast.error(error?.detail ?? error);
+		}
+
+		if (uploaded) {
+			const res = await createModel(
+				localStorage.token,
+				`${name}:latest`,
+				`FROM @${modelFileDigest}\n${modelFileContent}`
+			);
+
+			if (res && res.ok) {
+				const reader = res.body
+					.pipeThrough(new TextDecoderStream())
+					.pipeThrough(splitStream('\n'))
+					.getReader();
+
+				while (true) {
+					const { value, done } = await reader.read();
+					if (done) break;
+
+					try {
+						let lines = value.split('\n');
+
+						for (const line of lines) {
+							if (line !== '') {
+								console.log(line);
+								let data = JSON.parse(line);
+								console.log(data);
+
+								if (data.error) {
+									throw data.error;
+								}
+								if (data.detail) {
+									throw data.detail;
+								}
+
+								if (data.status) {
+									if (
+										!data.digest &&
+										!data.status.includes('writing') &&
+										!data.status.includes('sha256')
+									) {
+										toast.success(data.status);
+									} else {
+										if (data.digest) {
+											digest = data.digest;
+
+											if (data.completed) {
+												pullProgress = Math.round((data.completed / data.total) * 1000) / 10;
+											} else {
+												pullProgress = 100;
+											}
+										}
+									}
+								}
+							}
+						}
+					} catch (error) {
+						console.log(error);
+						toast.error(error);
+					}
+				}
+			}
+		}
+
+		modelFileUrl = '';
+
+		if (modelUploadInputElement) {
+			modelUploadInputElement.value = '';
+		}
+		modelInputFile = null;
+		modelTransferring = false;
+		uploadProgress = null;
+
+		models.set(await getModels());
+	};
+
+	const deleteModelHandler = async () => {
+		const res = await deleteModel(localStorage.token, deleteModelTag, selectedOllamaUrlIdx).catch(
+			(error) => {
+				toast.error(error);
+			}
+		);
+
+		if (res) {
+			toast.success($i18n.t(`Deleted {{deleteModelTag}}`, { deleteModelTag }));
+		}
+
+		deleteModelTag = '';
+		models.set(await getModels());
+	};
+
+	const cancelModelPullHandler = async (model: string) => {
+		const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model];
+		if (abortController) {
+			abortController.abort();
+		}
+		if (reader) {
+			await reader.cancel();
+			delete $MODEL_DOWNLOAD_POOL[model];
+			MODEL_DOWNLOAD_POOL.set({
+				...$MODEL_DOWNLOAD_POOL
+			});
+			await deleteModel(localStorage.token, model);
+			toast.success(`${model} download has been canceled`);
+		}
+	};
+
+	const createModelHandler = async () => {
+		createModelLoading = true;
+		const res = await createModel(
+			localStorage.token,
+			createModelTag,
+			createModelContent,
+			selectedOllamaUrlIdx
+		).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res && res.ok) {
+			const reader = res.body
+				.pipeThrough(new TextDecoderStream())
+				.pipeThrough(splitStream('\n'))
+				.getReader();
+
+			while (true) {
+				const { value, done } = await reader.read();
+				if (done) break;
+
+				try {
+					let lines = value.split('\n');
+
+					for (const line of lines) {
+						if (line !== '') {
+							console.log(line);
+							let data = JSON.parse(line);
+							console.log(data);
+
+							if (data.error) {
+								throw data.error;
+							}
+							if (data.detail) {
+								throw data.detail;
+							}
+
+							if (data.status) {
+								if (
+									!data.digest &&
+									!data.status.includes('writing') &&
+									!data.status.includes('sha256')
+								) {
+									toast.success(data.status);
+								} else {
+									if (data.digest) {
+										createModelDigest = data.digest;
+
+										if (data.completed) {
+											createModelPullProgress =
+												Math.round((data.completed / data.total) * 1000) / 10;
+										} else {
+											createModelPullProgress = 100;
+										}
+									}
+								}
+							}
+						}
+					}
+				} catch (error) {
+					console.log(error);
+					toast.error(error);
+				}
+			}
+		}
+
+		models.set(await getModels());
+
+		createModelLoading = false;
+
+		createModelTag = '';
+		createModelContent = '';
+		createModelDigest = '';
+		createModelPullProgress = null;
+	};
+
+	onMount(async () => {
+		const ollamaConfig = await getOllamaConfig(localStorage.token);
+
+		if (ollamaConfig.ENABLE_OLLAMA_API) {
+			ollamaEnabled = true;
+
+			await Promise.all([
+				(async () => {
+					OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
+						toast.error(error);
+						return [];
+					});
+
+					if (OLLAMA_URLS.length > 0) {
+						selectedOllamaUrlIdx = 0;
+					}
+				})(),
+				(async () => {
+					ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
+				})()
+			]);
+		} else {
+			ollamaEnabled = false;
+			toast.error($i18n.t('Ollama API is disabled'));
+		}
+	});
+</script>
+
+<ModelDeleteConfirmDialog
+	bind:show={showModelDeleteConfirm}
+	on:confirm={() => {
+		deleteModelHandler();
+	}}
+/>
+
+<div class="flex flex-col h-full justify-between text-sm">
+	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
+		{#if ollamaEnabled}
+			{#if ollamaVersion !== null}
+				<div class="space-y-2 pr-1.5">
+					<div class="text-sm font-medium">{$i18n.t('Manage Ollama Models')}</div>
+
+					{#if OLLAMA_URLS.length > 0}
+						<div class="flex gap-2">
+							<div class="flex-1 pb-1">
+								<select
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									bind:value={selectedOllamaUrlIdx}
+									placeholder={$i18n.t('Select an Ollama instance')}
+								>
+									{#each OLLAMA_URLS as url, idx}
+										<option value={idx} class="bg-gray-50 dark:bg-gray-700">{url}</option>
+									{/each}
+								</select>
+							</div>
+
+							<div>
+								<div class="flex w-full justify-end">
+									<Tooltip content="Update All Models" placement="top">
+										<button
+											class="p-2.5 flex gap-2 items-center bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+											on:click={() => {
+												updateModelsHandler();
+											}}
+										>
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												viewBox="0 0 16 16"
+												fill="currentColor"
+												class="w-4 h-4"
+											>
+												<path
+													d="M7 1a.75.75 0 0 1 .75.75V6h-1.5V1.75A.75.75 0 0 1 7 1ZM6.25 6v2.94L5.03 7.72a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06L7.75 8.94V6H10a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.25Z"
+												/>
+												<path
+													d="M4.268 14A2 2 0 0 0 6 15h6a2 2 0 0 0 2-2v-3a2 2 0 0 0-1-1.732V11a3 3 0 0 1-3 3H4.268Z"
+												/>
+											</svg>
+										</button>
+									</Tooltip>
+								</div>
+							</div>
+						</div>
+
+						{#if updateModelId}
+							Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''}
+						{/if}
+					{/if}
+
+					<div class="space-y-2">
+						<div>
+							<div class=" mb-2 text-sm font-medium">{$i18n.t('Pull a model from Ollama.com')}</div>
+							<div class="flex w-full">
+								<div class="flex-1 mr-2">
+									<input
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
+											modelTag: 'mistral:7b'
+										})}
+										bind:value={modelTag}
+									/>
+								</div>
+								<button
+									class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+									on:click={() => {
+										pullModelHandler();
+									}}
+									disabled={modelTransferring}
+								>
+									{#if modelTransferring}
+										<div class="self-center">
+											<svg
+												class=" w-4 h-4"
+												viewBox="0 0 24 24"
+												fill="currentColor"
+												xmlns="http://www.w3.org/2000/svg"
+											>
+												<style>
+													.spinner_ajPY {
+														transform-origin: center;
+														animation: spinner_AtaB 0.75s infinite linear;
+													}
+
+													@keyframes spinner_AtaB {
+														100% {
+															transform: rotate(360deg);
+														}
+													}
+												</style>
+												<path
+													d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+													opacity=".25"
+												/>
+												<path
+													d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+													class="spinner_ajPY"
+												/>
+											</svg>
+										</div>
+									{:else}
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											viewBox="0 0 16 16"
+											fill="currentColor"
+											class="w-4 h-4"
+										>
+											<path
+												d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
+											/>
+											<path
+												d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+											/>
+										</svg>
+									{/if}
+								</button>
+							</div>
+
+							<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
+								{$i18n.t('To access the available model names for downloading,')}
+								<a
+									class=" text-gray-500 dark:text-gray-300 font-medium underline"
+									href="https://ollama.com/library"
+									target="_blank">{$i18n.t('click here.')}</a
+								>
+							</div>
+
+							{#if Object.keys($MODEL_DOWNLOAD_POOL).length > 0}
+								{#each Object.keys($MODEL_DOWNLOAD_POOL) as model}
+									{#if 'pullProgress' in $MODEL_DOWNLOAD_POOL[model]}
+										<div class="flex flex-col">
+											<div class="font-medium mb-1">{model}</div>
+											<div class="">
+												<div class="flex flex-row justify-between space-x-4 pr-2">
+													<div class=" flex-1">
+														<div
+															class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
+															style="width: {Math.max(
+																15,
+																$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0
+															)}%"
+														>
+															{$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0}%
+														</div>
+													</div>
+
+													<Tooltip content={$i18n.t('Cancel')}>
+														<button
+															class="text-gray-800 dark:text-gray-100"
+															on:click={() => {
+																cancelModelPullHandler(model);
+															}}
+														>
+															<svg
+																class="w-4 h-4 text-gray-800 dark:text-white"
+																aria-hidden="true"
+																xmlns="http://www.w3.org/2000/svg"
+																width="24"
+																height="24"
+																fill="currentColor"
+																viewBox="0 0 24 24"
+															>
+																<path
+																	stroke="currentColor"
+																	stroke-linecap="round"
+																	stroke-linejoin="round"
+																	stroke-width="2"
+																	d="M6 18 17.94 6M18 18 6.06 6"
+																/>
+															</svg>
+														</button>
+													</Tooltip>
+												</div>
+												{#if 'digest' in $MODEL_DOWNLOAD_POOL[model]}
+													<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+														{$MODEL_DOWNLOAD_POOL[model].digest}
+													</div>
+												{/if}
+											</div>
+										</div>
+									{/if}
+								{/each}
+							{/if}
+						</div>
+
+						<div>
+							<div class=" mb-2 text-sm font-medium">{$i18n.t('Delete a model')}</div>
+							<div class="flex w-full">
+								<div class="flex-1 mr-2">
+									<select
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										bind:value={deleteModelTag}
+										placeholder={$i18n.t('Select a model')}
+									>
+										{#if !deleteModelTag}
+											<option value="" disabled selected>{$i18n.t('Select a model')}</option>
+										{/if}
+										{#each $models.filter((m) => !(m?.preset ?? false) && m.owned_by === 'ollama' && (selectedOllamaUrlIdx === null ? true : (m?.ollama?.urls ?? []).includes(selectedOllamaUrlIdx))) as model}
+											<option value={model.id} class="bg-gray-50 dark:bg-gray-700"
+												>{model.name +
+													' (' +
+													(model.ollama.size / 1024 ** 3).toFixed(1) +
+													' GB)'}</option
+											>
+										{/each}
+									</select>
+								</div>
+								<button
+									class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+									on:click={() => {
+										showModelDeleteConfirm = true;
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 16 16"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											fill-rule="evenodd"
+											d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
+											clip-rule="evenodd"
+										/>
+									</svg>
+								</button>
+							</div>
+						</div>
+
+						<div>
+							<div class=" mb-2 text-sm font-medium">{$i18n.t('Create a model')}</div>
+							<div class="flex w-full">
+								<div class="flex-1 mr-2 flex flex-col gap-2">
+									<input
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
+											modelTag: 'my-modelfile'
+										})}
+										bind:value={createModelTag}
+										disabled={createModelLoading}
+									/>
+
+									<textarea
+										bind:value={createModelContent}
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none scrollbar-hidden"
+										rows="6"
+										placeholder={`TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`}
+										disabled={createModelLoading}
+									/>
+								</div>
+
+								<div class="flex self-start">
+									<button
+										class="px-2.5 py-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition disabled:cursor-not-allowed"
+										on:click={() => {
+											createModelHandler();
+										}}
+										disabled={createModelLoading}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											viewBox="0 0 16 16"
+											fill="currentColor"
+											class="size-4"
+										>
+											<path
+												d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
+											/>
+											<path
+												d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+											/>
+										</svg>
+									</button>
+								</div>
+							</div>
+
+							{#if createModelDigest !== ''}
+								<div class="flex flex-col mt-1">
+									<div class="font-medium mb-1">{createModelTag}</div>
+									<div class="">
+										<div class="flex flex-row justify-between space-x-4 pr-2">
+											<div class=" flex-1">
+												<div
+													class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
+													style="width: {Math.max(15, createModelPullProgress ?? 0)}%"
+												>
+													{createModelPullProgress ?? 0}%
+												</div>
+											</div>
+										</div>
+										{#if createModelDigest}
+											<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+												{createModelDigest}
+											</div>
+										{/if}
+									</div>
+								</div>
+							{/if}
+						</div>
+
+						<div class="pt-1">
+							<div class="flex justify-between items-center text-xs">
+								<div class=" text-sm font-medium">{$i18n.t('Experimental')}</div>
+								<button
+									class=" text-xs font-medium text-gray-500"
+									type="button"
+									on:click={() => {
+										showExperimentalOllama = !showExperimentalOllama;
+									}}>{showExperimentalOllama ? $i18n.t('Hide') : $i18n.t('Show')}</button
+								>
+							</div>
+						</div>
+
+						{#if showExperimentalOllama}
+							<form
+								on:submit|preventDefault={() => {
+									uploadModelHandler();
+								}}
+							>
+								<div class=" mb-2 flex w-full justify-between">
+									<div class="  text-sm font-medium">{$i18n.t('Upload a GGUF model')}</div>
+
+									<button
+										class="p-1 px-3 text-xs flex rounded transition"
+										on:click={() => {
+											if (modelUploadMode === 'file') {
+												modelUploadMode = 'url';
+											} else {
+												modelUploadMode = 'file';
+											}
+										}}
+										type="button"
+									>
+										{#if modelUploadMode === 'file'}
+											<span class="ml-2 self-center">{$i18n.t('File Mode')}</span>
+										{:else}
+											<span class="ml-2 self-center">{$i18n.t('URL Mode')}</span>
+										{/if}
+									</button>
+								</div>
+
+								<div class="flex w-full mb-1.5">
+									<div class="flex flex-col w-full">
+										{#if modelUploadMode === 'file'}
+											<div
+												class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}"
+											>
+												<input
+													id="model-upload-input"
+													bind:this={modelUploadInputElement}
+													type="file"
+													bind:files={modelInputFile}
+													on:change={() => {
+														console.log(modelInputFile);
+													}}
+													accept=".gguf,.safetensors"
+													required
+													hidden
+												/>
+
+												<button
+													type="button"
+													class="w-full rounded-lg text-left py-2 px-4 bg-gray-50 dark:text-gray-300 dark:bg-gray-850"
+													on:click={() => {
+														modelUploadInputElement.click();
+													}}
+												>
+													{#if modelInputFile && modelInputFile.length > 0}
+														{modelInputFile[0].name}
+													{:else}
+														{$i18n.t('Click here to select')}
+													{/if}
+												</button>
+											</div>
+										{:else}
+											<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
+												<input
+													class="w-full rounded-lg text-left py-2 px-4 bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
+													''
+														? 'mr-2'
+														: ''}"
+													type="url"
+													required
+													bind:value={modelFileUrl}
+													placeholder={$i18n.t('Type Hugging Face Resolve (Download) URL')}
+												/>
+											</div>
+										{/if}
+									</div>
+
+									{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
+										<button
+											class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg disabled:cursor-not-allowed transition"
+											type="submit"
+											disabled={modelTransferring}
+										>
+											{#if modelTransferring}
+												<div class="self-center">
+													<svg
+														class=" w-4 h-4"
+														viewBox="0 0 24 24"
+														fill="currentColor"
+														xmlns="http://www.w3.org/2000/svg"
+													>
+														<style>
+															.spinner_ajPY {
+																transform-origin: center;
+																animation: spinner_AtaB 0.75s infinite linear;
+															}
+
+															@keyframes spinner_AtaB {
+																100% {
+																	transform: rotate(360deg);
+																}
+															}
+														</style>
+														<path
+															d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+															opacity=".25"
+														/>
+														<path
+															d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+															class="spinner_ajPY"
+														/>
+													</svg>
+												</div>
+											{:else}
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
+													/>
+													<path
+														d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+													/>
+												</svg>
+											{/if}
+										</button>
+									{/if}
+								</div>
+
+								{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
+									<div>
+										<div>
+											<div class=" my-2.5 text-sm font-medium">{$i18n.t('Modelfile Content')}</div>
+											<textarea
+												bind:value={modelFileContent}
+												class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none"
+												rows="6"
+											/>
+										</div>
+									</div>
+								{/if}
+								<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
+									{$i18n.t('To access the GGUF models available for downloading,')}
+									<a
+										class=" text-gray-500 dark:text-gray-300 font-medium underline"
+										href="https://huggingface.co/models?search=gguf"
+										target="_blank">{$i18n.t('click here.')}</a
+									>
+								</div>
+
+								{#if uploadMessage}
+									<div class="mt-2">
+										<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
+
+										<div class="w-full rounded-full dark:bg-gray-800">
+											<div
+												class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
+												style="width: 100%"
+											>
+												{uploadMessage}
+											</div>
+										</div>
+										<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+											{modelFileDigest}
+										</div>
+									</div>
+								{:else if uploadProgress !== null}
+									<div class="mt-2">
+										<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
+
+										<div class="w-full rounded-full dark:bg-gray-800">
+											<div
+												class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
+												style="width: {Math.max(15, uploadProgress ?? 0)}%"
+											>
+												{uploadProgress ?? 0}%
+											</div>
+										</div>
+										<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+											{modelFileDigest}
+										</div>
+									</div>
+								{/if}
+							</form>
+						{/if}
+					</div>
+				</div>
+			{:else if ollamaVersion === false}
+				<div>Ollama Not Detected</div>
+			{:else}
+				<div class="flex h-full justify-center">
+					<div class="my-auto">
+						<Spinner className="size-6" />
+					</div>
+				</div>
+			{/if}
+		{:else if ollamaEnabled === false}
+			<div>{$i18n.t('Ollama API is disabled')}</div>
+		{:else}
+			<div class="flex h-full justify-center">
+				<div class="my-auto">
+					<Spinner className="size-6" />
+				</div>
+			</div>
+		{/if}
+	</div>
+</div>
diff --git a/src/lib/components/admin/Settings/Pipelines.svelte b/src/lib/components/admin/Settings/Pipelines.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..fb229d9ff0b2efe7832f4d6fe8de44a2fec1032c
--- /dev/null
+++ b/src/lib/components/admin/Settings/Pipelines.svelte
@@ -0,0 +1,559 @@
+<script lang="ts">
+	import { v4 as uuidv4 } from 'uuid';
+
+	import { toast } from 'svelte-sonner';
+	import { models } from '$lib/stores';
+	import { getContext, onMount, tick } from 'svelte';
+	import type { Writable } from 'svelte/store';
+	import type { i18n as i18nType } from 'i18next';
+	import {
+		getPipelineValves,
+		getPipelineValvesSpec,
+		updatePipelineValves,
+		getPipelines,
+		getModels,
+		getPipelinesList,
+		downloadPipeline,
+		deletePipeline,
+		uploadPipeline
+	} from '$lib/apis';
+
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import Switch from '$lib/components/common/Switch.svelte';
+
+	const i18n: Writable<i18nType> = getContext('i18n');
+
+	export let saveHandler: Function;
+
+	let downloading = false;
+	let uploading = false;
+
+	let pipelineFiles;
+
+	let PIPELINES_LIST = null;
+	let selectedPipelinesUrlIdx = '';
+
+	let pipelines = null;
+
+	let valves = null;
+	let valves_spec = null;
+	let selectedPipelineIdx = null;
+
+	let pipelineDownloadUrl = '';
+
+	const updateHandler = async () => {
+		const pipeline = pipelines[selectedPipelineIdx];
+
+		if (pipeline && (pipeline?.valves ?? false)) {
+			for (const property in valves_spec.properties) {
+				if (valves_spec.properties[property]?.type === 'array') {
+					valves[property] = valves[property].split(',').map((v) => v.trim());
+				}
+			}
+
+			const res = await updatePipelineValves(
+				localStorage.token,
+				pipeline.id,
+				valves,
+				selectedPipelinesUrlIdx
+			).catch((error) => {
+				toast.error(error);
+			});
+
+			if (res) {
+				toast.success($i18n.t('Valves updated successfully'));
+				setPipelines();
+				models.set(await getModels(localStorage.token));
+				saveHandler();
+			}
+		} else {
+			toast.error($i18n.t('No valves to update'));
+		}
+	};
+
+	const getValves = async (idx) => {
+		valves = null;
+		valves_spec = null;
+
+		valves_spec = await getPipelineValvesSpec(
+			localStorage.token,
+			pipelines[idx].id,
+			selectedPipelinesUrlIdx
+		);
+		valves = await getPipelineValves(
+			localStorage.token,
+			pipelines[idx].id,
+			selectedPipelinesUrlIdx
+		);
+
+		for (const property in valves_spec.properties) {
+			if (valves_spec.properties[property]?.type === 'array') {
+				valves[property] = valves[property].join(',');
+			}
+		}
+	};
+
+	const setPipelines = async () => {
+		pipelines = null;
+		valves = null;
+		valves_spec = null;
+
+		if (PIPELINES_LIST.length > 0) {
+			console.log(selectedPipelinesUrlIdx);
+			pipelines = await getPipelines(localStorage.token, selectedPipelinesUrlIdx);
+
+			if (pipelines.length > 0) {
+				selectedPipelineIdx = 0;
+				await getValves(selectedPipelineIdx);
+			}
+		} else {
+			pipelines = [];
+		}
+	};
+
+	const addPipelineHandler = async () => {
+		downloading = true;
+		const res = await downloadPipeline(
+			localStorage.token,
+			pipelineDownloadUrl,
+			selectedPipelinesUrlIdx
+		).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Pipeline downloaded successfully'));
+			setPipelines();
+			models.set(await getModels(localStorage.token));
+		}
+
+		downloading = false;
+	};
+
+	const uploadPipelineHandler = async () => {
+		uploading = true;
+
+		if (pipelineFiles && pipelineFiles.length !== 0) {
+			const file = pipelineFiles[0];
+
+			console.log(file);
+
+			const res = await uploadPipeline(localStorage.token, file, selectedPipelinesUrlIdx).catch(
+				(error) => {
+					console.log(error);
+					toast.error('Something went wrong :/');
+					return null;
+				}
+			);
+
+			if (res) {
+				toast.success($i18n.t('Pipeline downloaded successfully'));
+				setPipelines();
+				models.set(await getModels(localStorage.token));
+			}
+		} else {
+			toast.error($i18n.t('No file selected'));
+		}
+
+		pipelineFiles = null;
+		const pipelineUploadInputElement = document.getElementById('pipeline-upload-input');
+
+		if (pipelineUploadInputElement) {
+			pipelineUploadInputElement.value = null;
+		}
+
+		uploading = false;
+	};
+
+	const deletePipelineHandler = async () => {
+		const res = await deletePipeline(
+			localStorage.token,
+			pipelines[selectedPipelineIdx].id,
+			selectedPipelinesUrlIdx
+		).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Pipeline deleted successfully'));
+			setPipelines();
+			models.set(await getModels(localStorage.token));
+		}
+	};
+
+	onMount(async () => {
+		PIPELINES_LIST = await getPipelinesList(localStorage.token);
+		console.log(PIPELINES_LIST);
+
+		if (PIPELINES_LIST.length > 0) {
+			selectedPipelinesUrlIdx = PIPELINES_LIST[0]['idx'].toString();
+		}
+
+		await setPipelines();
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={async () => {
+		updateHandler();
+	}}
+>
+	<div class="overflow-y-scroll scrollbar-hidden h-full">
+		{#if PIPELINES_LIST !== null}
+			<div class="flex w-full justify-between mb-2">
+				<div class=" self-center text-sm font-semibold">
+					{$i18n.t('Manage Pipelines')}
+				</div>
+			</div>
+
+			{#if PIPELINES_LIST.length > 0}
+				<div class="space-y-1">
+					<div class="flex gap-2">
+						<div class="flex-1">
+							<select
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								bind:value={selectedPipelinesUrlIdx}
+								placeholder={$i18n.t('Select a pipeline url')}
+								on:change={async () => {
+									await tick();
+									await setPipelines();
+								}}
+							>
+								<option value="" selected disabled class="bg-gray-100 dark:bg-gray-700"
+									>{$i18n.t('Select a pipeline url')}</option
+								>
+
+								{#each PIPELINES_LIST as pipelines, idx}
+									<option value={pipelines.idx.toString()} class="bg-gray-100 dark:bg-gray-700"
+										>{pipelines.url}</option
+									>
+								{/each}
+							</select>
+						</div>
+					</div>
+				</div>
+
+				<div class=" my-2">
+					<div class=" mb-2 text-sm font-medium">
+						{$i18n.t('Upload Pipeline')}
+					</div>
+					<div class="flex w-full">
+						<div class="flex-1 mr-2">
+							<input
+								id="pipelines-upload-input"
+								bind:files={pipelineFiles}
+								type="file"
+								accept=".py"
+								hidden
+							/>
+
+							<button
+								class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
+								type="button"
+								on:click={() => {
+									document.getElementById('pipelines-upload-input')?.click();
+								}}
+							>
+								{#if pipelineFiles}
+									{pipelineFiles.length > 0 ? `${pipelineFiles.length}` : ''} pipeline(s) selected.
+								{:else}
+									{$i18n.t('Click here to select a py file.')}
+								{/if}
+							</button>
+						</div>
+						<button
+							class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+							on:click={() => {
+								uploadPipelineHandler();
+							}}
+							disabled={uploading}
+							type="button"
+						>
+							{#if uploading}
+								<div class="self-center">
+									<svg
+										class=" w-4 h-4"
+										viewBox="0 0 24 24"
+										fill="currentColor"
+										xmlns="http://www.w3.org/2000/svg"
+									>
+										<style>
+											.spinner_ajPY {
+												transform-origin: center;
+												animation: spinner_AtaB 0.75s infinite linear;
+											}
+
+											@keyframes spinner_AtaB {
+												100% {
+													transform: rotate(360deg);
+												}
+											}
+										</style>
+										<path
+											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+											opacity=".25"
+										/>
+										<path
+											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+											class="spinner_ajPY"
+										/>
+									</svg>
+								</div>
+							{:else}
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="size-4"
+								>
+									<path
+										d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
+									/>
+									<path
+										d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+									/>
+								</svg>
+							{/if}
+						</button>
+					</div>
+				</div>
+
+				<div class=" my-2">
+					<div class=" mb-2 text-sm font-medium">
+						{$i18n.t('Install from Github URL')}
+					</div>
+					<div class="flex w-full">
+						<div class="flex-1 mr-2">
+							<input
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								placeholder={$i18n.t('Enter Github Raw URL')}
+								bind:value={pipelineDownloadUrl}
+							/>
+						</div>
+						<button
+							class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+							on:click={() => {
+								addPipelineHandler();
+							}}
+							disabled={downloading}
+							type="button"
+						>
+							{#if downloading}
+								<div class="self-center">
+									<svg
+										class=" w-4 h-4"
+										viewBox="0 0 24 24"
+										fill="currentColor"
+										xmlns="http://www.w3.org/2000/svg"
+									>
+										<style>
+											.spinner_ajPY {
+												transform-origin: center;
+												animation: spinner_AtaB 0.75s infinite linear;
+											}
+
+											@keyframes spinner_AtaB {
+												100% {
+													transform: rotate(360deg);
+												}
+											}
+										</style>
+										<path
+											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+											opacity=".25"
+										/>
+										<path
+											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+											class="spinner_ajPY"
+										/>
+									</svg>
+								</div>
+							{:else}
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
+									/>
+									<path
+										d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+									/>
+								</svg>
+							{/if}
+						</button>
+					</div>
+
+					<div class="mt-2 text-xs text-gray-500">
+						<span class=" font-semibold dark:text-gray-200">Warning:</span> Pipelines are a plugin
+						system with arbitrary code execution —
+						<span class=" font-medium dark:text-gray-400"
+							>don't fetch random pipelines from sources you don't trust.</span
+						>
+					</div>
+				</div>
+
+				<hr class=" dark:border-gray-800 my-3 w-full" />
+
+				{#if pipelines !== null}
+					{#if pipelines.length > 0}
+						<div class="flex w-full justify-between mb-2">
+							<div class=" self-center text-sm font-semibold">
+								{$i18n.t('Pipelines Valves')}
+							</div>
+						</div>
+						<div class="space-y-1">
+							{#if pipelines.length > 0}
+								<div class="flex gap-2">
+									<div class="flex-1">
+										<select
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											bind:value={selectedPipelineIdx}
+											placeholder={$i18n.t('Select a pipeline')}
+											on:change={async () => {
+												await tick();
+												await getValves(selectedPipelineIdx);
+											}}
+										>
+											{#each pipelines as pipeline, idx}
+												<option value={idx} class="bg-gray-100 dark:bg-gray-700"
+													>{pipeline.name} ({pipeline.type ?? 'pipe'})</option
+												>
+											{/each}
+										</select>
+									</div>
+
+									<button
+										class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+										on:click={() => {
+											deletePipelineHandler();
+										}}
+										type="button"
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											viewBox="0 0 16 16"
+											fill="currentColor"
+											class="w-4 h-4"
+										>
+											<path
+												fill-rule="evenodd"
+												d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
+												clip-rule="evenodd"
+											/>
+										</svg>
+									</button>
+								</div>
+							{/if}
+
+							<div class="space-y-1">
+								{#if pipelines[selectedPipelineIdx].valves}
+									{#if valves}
+										{#each Object.keys(valves_spec.properties) as property, idx}
+											<div class=" py-0.5 w-full justify-between">
+												<div class="flex w-full justify-between">
+													<div class=" self-center text-xs font-medium">
+														{valves_spec.properties[property].title}
+													</div>
+
+													<button
+														class="p-1 px-3 text-xs flex rounded transition"
+														type="button"
+														on:click={() => {
+															valves[property] = (valves[property] ?? null) === null ? '' : null;
+														}}
+													>
+														{#if (valves[property] ?? null) === null}
+															<span class="ml-2 self-center"> {$i18n.t('None')} </span>
+														{:else}
+															<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
+														{/if}
+													</button>
+												</div>
+
+												{#if (valves[property] ?? null) !== null}
+													<!-- {valves[property]} -->
+													<div class="flex mt-0.5 mb-1.5 space-x-2">
+														<div class=" flex-1">
+															{#if valves_spec.properties[property]?.enum ?? null}
+																<select
+																	class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+																	bind:value={valves[property]}
+																>
+																	{#each valves_spec.properties[property].enum as option}
+																		<option value={option} selected={option === valves[property]}>
+																			{option}
+																		</option>
+																	{/each}
+																</select>
+															{:else if (valves_spec.properties[property]?.type ?? null) === 'boolean'}
+																<div class="flex justify-between items-center">
+																	<div class="text-xs text-gray-500">
+																		{valves[property] ? 'Enabled' : 'Disabled'}
+																	</div>
+
+																	<div class=" pr-2">
+																		<Switch bind:state={valves[property]} />
+																	</div>
+																</div>
+															{:else}
+																<input
+																	class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+																	type="text"
+																	placeholder={valves_spec.properties[property].title}
+																	bind:value={valves[property]}
+																	autocomplete="off"
+																	required
+																/>
+															{/if}
+														</div>
+													</div>
+												{/if}
+											</div>
+										{/each}
+									{:else}
+										<Spinner className="size-5" />
+									{/if}
+								{:else}
+									<div>No valves</div>
+								{/if}
+							</div>
+						</div>
+					{:else if pipelines.length === 0}
+						<div>Pipelines Not Detected</div>
+					{/if}
+				{:else}
+					<div class="flex justify-center">
+						<div class="my-auto">
+							<Spinner className="size-4" />
+						</div>
+					</div>
+				{/if}
+			{:else}
+				<div>{$i18n.t('Pipelines Not Detected')}</div>
+			{/if}
+		{:else}
+			<div class="flex justify-center h-full">
+				<div class="my-auto">
+					<Spinner className="size-6" />
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	{#if PIPELINES_LIST !== null && PIPELINES_LIST.length > 0}
+		<div class="flex justify-end pt-3 text-sm font-medium">
+			<button
+				class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+				type="submit"
+			>
+				{$i18n.t('Save')}
+			</button>
+		</div>
+	{/if}
+</form>
diff --git a/src/lib/components/admin/Settings/Users.svelte b/src/lib/components/admin/Settings/Users.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..04f780759afb0cbda504aab3e4f99b07160ef2c1
--- /dev/null
+++ b/src/lib/components/admin/Settings/Users.svelte
@@ -0,0 +1,214 @@
+<script lang="ts">
+	import { getBackendConfig, getModelFilterConfig, updateModelFilterConfig } from '$lib/apis';
+	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
+	import { getUserPermissions, updateUserPermissions } from '$lib/apis/users';
+
+	import { onMount, getContext } from 'svelte';
+	import { models, config } from '$lib/stores';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import { setDefaultModels } from '$lib/apis/configs';
+
+	const i18n = getContext('i18n');
+
+	export let saveHandler: Function;
+
+	let defaultModelId = '';
+
+	let whitelistEnabled = false;
+	let whitelistModels = [''];
+	let permissions = {
+		chat: {
+			deletion: true,
+			edit: true,
+			temporary: true
+		}
+	};
+
+	let chatDeletion = true;
+	let chatEdit = true;
+	let chatTemporary = true;
+
+	onMount(async () => {
+		permissions = await getUserPermissions(localStorage.token);
+
+		chatDeletion = permissions?.chat?.deletion ?? true;
+		chatEdit = permissions?.chat?.editing ?? true;
+		chatTemporary = permissions?.chat?.temporary ?? true;
+
+		const res = await getModelFilterConfig(localStorage.token);
+		if (res) {
+			whitelistEnabled = res.enabled;
+			whitelistModels = res.models.length > 0 ? res.models : [''];
+		}
+
+		defaultModelId = $config.default_models ? $config?.default_models.split(',')[0] : '';
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={async () => {
+		// console.log('submit');
+
+		await setDefaultModels(localStorage.token, defaultModelId);
+		await updateUserPermissions(localStorage.token, {
+			chat: {
+				deletion: chatDeletion,
+				editing: chatEdit,
+				temporary: chatTemporary
+			}
+		});
+		await updateModelFilterConfig(localStorage.token, whitelistEnabled, whitelistModels);
+		saveHandler();
+
+		await config.set(await getBackendConfig());
+	}}
+>
+	<div class=" space-y-3 overflow-y-scroll max-h-full">
+		<div>
+			<div class=" mb-2 text-sm font-medium">{$i18n.t('User Permissions')}</div>
+
+			<div class="  flex w-full justify-between my-2 pr-2">
+				<div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div>
+
+				<Switch bind:state={chatDeletion} />
+			</div>
+
+			<div class="  flex w-full justify-between my-2 pr-2">
+				<div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Editing')}</div>
+
+				<Switch bind:state={chatEdit} />
+			</div>
+
+			<div class="  flex w-full justify-between my-2 pr-2">
+				<div class=" self-center text-xs font-medium">{$i18n.t('Allow Temporary Chat')}</div>
+
+				<Switch bind:state={chatTemporary} />
+			</div>
+		</div>
+
+		<hr class=" dark:border-gray-850 my-2" />
+
+		<div class="mt-2 space-y-3">
+			<div>
+				<div class="mb-2">
+					<div class="flex justify-between items-center text-xs">
+						<div class=" text-sm font-medium">{$i18n.t('Manage Models')}</div>
+					</div>
+				</div>
+				<div class=" space-y-1 mb-3">
+					<div class="mb-2">
+						<div class="flex justify-between items-center text-xs">
+							<div class=" text-xs font-medium">{$i18n.t('Default Model')}</div>
+						</div>
+					</div>
+
+					<div class="flex-1 mr-2">
+						<select
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							bind:value={defaultModelId}
+							placeholder="Select a model"
+						>
+							<option value="" disabled selected>{$i18n.t('Select a model')}</option>
+							{#each $models.filter((model) => model.id) as model}
+								<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
+							{/each}
+						</select>
+					</div>
+				</div>
+
+				<div class=" space-y-1">
+					<div class="mb-2">
+						<div class="flex justify-between items-center text-xs my-3 pr-2">
+							<div class=" text-xs font-medium">{$i18n.t('Model Whitelisting')}</div>
+
+							<Switch bind:state={whitelistEnabled} />
+						</div>
+					</div>
+
+					{#if whitelistEnabled}
+						<div>
+							<div class=" space-y-1.5">
+								{#each whitelistModels as modelId, modelIdx}
+									<div class="flex w-full">
+										<div class="flex-1 mr-2">
+											<select
+												class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+												bind:value={modelId}
+												placeholder="Select a model"
+											>
+												<option value="" disabled selected>{$i18n.t('Select a model')}</option>
+												{#each $models.filter((model) => model.id) as model}
+													<option value={model.id} class="bg-gray-100 dark:bg-gray-700"
+														>{model.name}</option
+													>
+												{/each}
+											</select>
+										</div>
+
+										{#if modelIdx === 0}
+											<button
+												class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-900 dark:text-white rounded-lg transition"
+												type="button"
+												on:click={() => {
+													if (whitelistModels.at(-1) !== '') {
+														whitelistModels = [...whitelistModels, ''];
+													}
+												}}
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+													/>
+												</svg>
+											</button>
+										{:else}
+											<button
+												class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-900 dark:text-white rounded-lg transition"
+												type="button"
+												on:click={() => {
+													whitelistModels.splice(modelIdx, 1);
+													whitelistModels = whitelistModels;
+												}}
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
+												</svg>
+											</button>
+										{/if}
+									</div>
+								{/each}
+							</div>
+
+							<div class="flex justify-end items-center text-xs mt-1.5 text-right">
+								<div class=" text-xs font-medium">
+									{whitelistModels.length}
+									{$i18n.t('Model(s) Whitelisted')}
+								</div>
+							</div>
+						</div>
+					{/if}
+				</div>
+			</div>
+		</div>
+	</div>
+
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/admin/Settings/WebSearch.svelte b/src/lib/components/admin/Settings/WebSearch.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..69f1f91043971ed3c866b9058bdd68d7881e68e3
--- /dev/null
+++ b/src/lib/components/admin/Settings/WebSearch.svelte
@@ -0,0 +1,319 @@
+<script lang="ts">
+	import { getRAGConfig, updateRAGConfig } from '$lib/apis/retrieval';
+	import Switch from '$lib/components/common/Switch.svelte';
+
+	import { models } from '$lib/stores';
+	import { onMount, getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let saveHandler: Function;
+
+	let webConfig = null;
+	let webSearchEngines = [
+		'searxng',
+		'google_pse',
+		'brave',
+		'serpstack',
+		'serper',
+		'serply',
+		'searchapi',
+		'duckduckgo',
+		'tavily',
+		'jina'
+	];
+
+	let youtubeLanguage = 'en';
+	let youtubeTranslation = null;
+
+	const submitHandler = async () => {
+		const res = await updateRAGConfig(localStorage.token, {
+			web: webConfig,
+			youtube: {
+				language: youtubeLanguage.split(',').map((lang) => lang.trim()),
+				translation: youtubeTranslation
+			}
+		});
+	};
+
+	onMount(async () => {
+		const res = await getRAGConfig(localStorage.token);
+
+		if (res) {
+			webConfig = res.web;
+
+			youtubeLanguage = res.youtube.language.join(',');
+			youtubeTranslation = res.youtube.translation;
+		}
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={async () => {
+		await submitHandler();
+		saveHandler();
+	}}
+>
+	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
+		{#if webConfig}
+			<div>
+				<div class=" mb-1 text-sm font-medium">
+					{$i18n.t('Web Search')}
+				</div>
+
+				<div>
+					<div class=" py-0.5 flex w-full justify-between">
+						<div class=" self-center text-xs font-medium">
+							{$i18n.t('Enable Web Search')}
+						</div>
+
+						<Switch bind:state={webConfig.search.enabled} />
+					</div>
+				</div>
+
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Web Search Engine')}</div>
+					<div class="flex items-center relative">
+						<select
+							class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							bind:value={webConfig.search.engine}
+							placeholder={$i18n.t('Select a engine')}
+							required
+						>
+							<option disabled selected value="">{$i18n.t('Select a engine')}</option>
+							{#each webSearchEngines as engine}
+								<option value={engine}>{engine}</option>
+							{/each}
+						</select>
+					</div>
+				</div>
+
+				{#if webConfig.search.engine !== ''}
+					<div class="mt-1.5">
+						{#if webConfig.search.engine === 'searxng'}
+							<div>
+								<div class=" self-center text-xs font-medium mb-1">
+									{$i18n.t('Searxng Query URL')}
+								</div>
+
+								<div class="flex w-full">
+									<div class="flex-1">
+										<input
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											type="text"
+											placeholder={$i18n.t('Enter Searxng Query URL')}
+											bind:value={webConfig.search.searxng_query_url}
+											autocomplete="off"
+										/>
+									</div>
+								</div>
+							</div>
+						{:else if webConfig.search.engine === 'google_pse'}
+							<div>
+								<div class=" self-center text-xs font-medium mb-1">
+									{$i18n.t('Google PSE API Key')}
+								</div>
+
+								<SensitiveInput
+									placeholder={$i18n.t('Enter Google PSE API Key')}
+									bind:value={webConfig.search.google_pse_api_key}
+								/>
+							</div>
+							<div class="mt-1.5">
+								<div class=" self-center text-xs font-medium mb-1">
+									{$i18n.t('Google PSE Engine Id')}
+								</div>
+
+								<div class="flex w-full">
+									<div class="flex-1">
+										<input
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											type="text"
+											placeholder={$i18n.t('Enter Google PSE Engine Id')}
+											bind:value={webConfig.search.google_pse_engine_id}
+											autocomplete="off"
+										/>
+									</div>
+								</div>
+							</div>
+						{:else if webConfig.search.engine === 'brave'}
+							<div>
+								<div class=" self-center text-xs font-medium mb-1">
+									{$i18n.t('Brave Search API Key')}
+								</div>
+
+								<SensitiveInput
+									placeholder={$i18n.t('Enter Brave Search API Key')}
+									bind:value={webConfig.search.brave_search_api_key}
+								/>
+							</div>
+						{:else if webConfig.search.engine === 'serpstack'}
+							<div>
+								<div class=" self-center text-xs font-medium mb-1">
+									{$i18n.t('Serpstack API Key')}
+								</div>
+
+								<SensitiveInput
+									placeholder={$i18n.t('Enter Serpstack API Key')}
+									bind:value={webConfig.search.serpstack_api_key}
+								/>
+							</div>
+						{:else if webConfig.search.engine === 'serper'}
+							<div>
+								<div class=" self-center text-xs font-medium mb-1">
+									{$i18n.t('Serper API Key')}
+								</div>
+
+								<SensitiveInput
+									placeholder={$i18n.t('Enter Serper API Key')}
+									bind:value={webConfig.search.serper_api_key}
+								/>
+							</div>
+						{:else if webConfig.search.engine === 'serply'}
+							<div>
+								<div class=" self-center text-xs font-medium mb-1">
+									{$i18n.t('Serply API Key')}
+								</div>
+
+								<SensitiveInput
+									placeholder={$i18n.t('Enter Serply API Key')}
+									bind:value={webConfig.search.serply_api_key}
+								/>
+							</div>
+						{:else if webConfig.search.engine === 'searchapi'}
+							<div>
+								<div class=" self-center text-xs font-medium mb-1">
+									{$i18n.t('SearchApi API Key')}
+								</div>
+
+								<SensitiveInput
+									placeholder={$i18n.t('Enter SearchApi API Key')}
+									bind:value={webConfig.search.searchapi_api_key}
+								/>
+							</div>
+							<div class="mt-1.5">
+								<div class=" self-center text-xs font-medium mb-1">
+									{$i18n.t('SearchApi Engine')}
+								</div>
+
+								<div class="flex w-full">
+									<div class="flex-1">
+										<input
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											type="text"
+											placeholder={$i18n.t('Enter SearchApi Engine')}
+											bind:value={webConfig.search.searchapi_engine}
+											autocomplete="off"
+										/>
+									</div>
+								</div>
+							</div>
+						{:else if webConfig.search.engine === 'tavily'}
+							<div>
+								<div class=" self-center text-xs font-medium mb-1">
+									{$i18n.t('Tavily API Key')}
+								</div>
+
+								<SensitiveInput
+									placeholder={$i18n.t('Enter Tavily API Key')}
+									bind:value={webConfig.search.tavily_api_key}
+								/>
+							</div>
+						{/if}
+					</div>
+				{/if}
+
+				{#if webConfig.search.enabled}
+					<div class="mt-2 flex gap-2 mb-1">
+						<div class="w-full">
+							<div class=" self-center text-xs font-medium mb-1">
+								{$i18n.t('Search Result Count')}
+							</div>
+
+							<input
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								placeholder={$i18n.t('Search Result Count')}
+								bind:value={webConfig.search.result_count}
+								required
+							/>
+						</div>
+
+						<div class="w-full">
+							<div class=" self-center text-xs font-medium mb-1">
+								{$i18n.t('Concurrent Requests')}
+							</div>
+
+							<input
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								placeholder={$i18n.t('Concurrent Requests')}
+								bind:value={webConfig.search.concurrent_requests}
+								required
+							/>
+						</div>
+					</div>
+				{/if}
+			</div>
+
+			<hr class=" dark:border-gray-850 my-2" />
+
+			<div>
+				<div class=" mb-1 text-sm font-medium">
+					{$i18n.t('Web Loader Settings')}
+				</div>
+
+				<div>
+					<div class=" py-0.5 flex w-full justify-between">
+						<div class=" self-center text-xs font-medium">
+							{$i18n.t('Bypass SSL verification for Websites')}
+						</div>
+
+						<button
+							class="p-1 px-3 text-xs flex rounded transition"
+							on:click={() => {
+								webConfig.ssl_verification = !webConfig.ssl_verification;
+								submitHandler();
+							}}
+							type="button"
+						>
+							{#if webConfig.ssl_verification === true}
+								<span class="ml-2 self-center">{$i18n.t('On')}</span>
+							{:else}
+								<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+							{/if}
+						</button>
+					</div>
+				</div>
+
+				<div class=" mt-2 mb-1 text-sm font-medium">
+					{$i18n.t('Youtube Loader Settings')}
+				</div>
+
+				<div>
+					<div class=" py-0.5 flex w-full justify-between">
+						<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Language')}</div>
+						<div class=" flex-1 self-center">
+							<input
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								type="text"
+								placeholder={$i18n.t('Enter language codes')}
+								bind:value={youtubeLanguage}
+								autocomplete="off"
+							/>
+						</div>
+					</div>
+				</div>
+			</div>
+		{/if}
+	</div>
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/admin/UserChatsModal.svelte b/src/lib/components/admin/UserChatsModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..535dee0740334b5e805c7c013b2e46e8f02b7231
--- /dev/null
+++ b/src/lib/components/admin/UserChatsModal.svelte
@@ -0,0 +1,198 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import dayjs from 'dayjs';
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	import Modal from '$lib/components/common/Modal.svelte';
+	import { getChatListByUserId, deleteChatById, getArchivedChatList } from '$lib/apis/chats';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let show = false;
+	export let user;
+
+	let chats = [];
+
+	const deleteChatHandler = async (chatId) => {
+		const res = await deleteChatById(localStorage.token, chatId).catch((error) => {
+			toast.error(error);
+		});
+
+		chats = await getChatListByUserId(localStorage.token, user.id);
+	};
+
+	$: if (show) {
+		(async () => {
+			if (user.id) {
+				chats = await getChatListByUserId(localStorage.token, user.id);
+			}
+		})();
+	}
+
+	let sortKey = 'updated_at'; // default sort key
+	let sortOrder = 'desc'; // default sort order
+	function setSortKey(key) {
+		if (sortKey === key) {
+			sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
+		} else {
+			sortKey = key;
+			sortOrder = 'asc';
+		}
+	}
+</script>
+
+<Modal size="lg" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
+			<div class=" text-lg font-medium self-center capitalize">
+				{$i18n.t("{{user}}'s Chats", { user: user.name })}
+			</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+		<hr class=" dark:border-gray-850" />
+
+		<div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				{#if chats.length > 0}
+					<div class="text-left text-sm w-full mb-4 max-h-[22rem] overflow-y-scroll">
+						<div class="relative overflow-x-auto">
+							<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
+								<thead
+									class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-800"
+								>
+									<tr>
+										<th
+											scope="col"
+											class="px-3 py-2 cursor-pointer select-none"
+											on:click={() => setSortKey('title')}
+										>
+											{$i18n.t('Title')}
+											{#if sortKey === 'title'}
+												{sortOrder === 'asc' ? '▲' : '▼'}
+											{:else}
+												<span class="invisible">▲</span>
+											{/if}
+										</th>
+										<th
+											scope="col"
+											class="px-3 py-2 cursor-pointer select-none"
+											on:click={() => setSortKey('created_at')}
+										>
+											{$i18n.t('Created at')}
+											{#if sortKey === 'created_at'}
+												{sortOrder === 'asc' ? '▲' : '▼'}
+											{:else}
+												<span class="invisible">▲</span>
+											{/if}
+										</th>
+										<th
+											scope="col"
+											class="px-3 py-2 hidden md:flex cursor-pointer select-none"
+											on:click={() => setSortKey('updated_at')}
+										>
+											{$i18n.t('Updated at')}
+											{#if sortKey === 'updated_at'}
+												{sortOrder === 'asc' ? '▲' : '▼'}
+											{:else}
+												<span class="invisible">▲</span>
+											{/if}
+										</th>
+										<th scope="col" class="px-3 py-2 text-right" />
+									</tr>
+								</thead>
+								<tbody>
+									{#each chats.sort((a, b) => {
+										if (a[sortKey] < b[sortKey]) return sortOrder === 'asc' ? -1 : 1;
+										if (a[sortKey] > b[sortKey]) return sortOrder === 'asc' ? 1 : -1;
+										return 0;
+									}) as chat, idx}
+										<tr
+											class="bg-transparent {idx !== chats.length - 1 &&
+												'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs"
+										>
+											<td class="px-3 py-1">
+												<a href="/s/{chat.id}" target="_blank">
+													<div class=" underline line-clamp-1">
+														{chat.title}
+													</div>
+												</a>
+											</td>
+
+											<td class=" px-3 py-1 h-[2.5rem]">
+												<div class="my-auto">
+													{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
+												</div>
+											</td>
+											<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
+												<div class="my-auto">
+													{dayjs(chat.updated_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
+												</div>
+											</td>
+
+											<td class="px-3 py-1 text-right">
+												<div class="flex justify-end w-full">
+													<Tooltip content={$i18n.t('Delete Chat')}>
+														<button
+															class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+															on:click={async () => {
+																deleteChatHandler(chat.id);
+															}}
+														>
+															<svg
+																xmlns="http://www.w3.org/2000/svg"
+																fill="none"
+																viewBox="0 0 24 24"
+																stroke-width="1.5"
+																stroke="currentColor"
+																class="w-4 h-4"
+															>
+																<path
+																	stroke-linecap="round"
+																	stroke-linejoin="round"
+																	d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
+																/>
+															</svg>
+														</button>
+													</Tooltip>
+												</div>
+											</td>
+										</tr>
+									{/each}
+								</tbody>
+							</table>
+						</div>
+						<!-- {#each chats as chat}
+							<div>
+								{JSON.stringify(chat)}
+							</div>
+						{/each} -->
+					</div>
+				{:else}
+					<div class="text-left text-sm w-full mb-8">
+						{user.name}
+						{$i18n.t('has no conversations.')}
+					</div>
+				{/if}
+			</div>
+		</div>
+	</div>
+</Modal>
diff --git a/src/lib/components/chat/Artifacts.svelte b/src/lib/components/chat/Artifacts.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..0a8ab956aba2fce6d2f1b7be263ba83245b380a4
--- /dev/null
+++ b/src/lib/components/chat/Artifacts.svelte
@@ -0,0 +1,322 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { onMount, getContext, createEventDispatcher } from 'svelte';
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import { chatId, showArtifacts, showControls } from '$lib/stores';
+	import XMark from '../icons/XMark.svelte';
+	import { copyToClipboard, createMessagesList } from '$lib/utils';
+	import ArrowsPointingOut from '../icons/ArrowsPointingOut.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import SvgPanZoom from '../common/SVGPanZoom.svelte';
+	import ArrowLeft from '../icons/ArrowLeft.svelte';
+
+	export let overlay = false;
+	export let history;
+	let messages = [];
+
+	let contents: Array<{ type: string; content: string }> = [];
+	let selectedContentIdx = 0;
+
+	let copied = false;
+	let iframeElement: HTMLIFrameElement;
+
+	$: if (history) {
+		messages = createMessagesList(history, history.currentId);
+		getContents();
+	} else {
+		messages = [];
+		getContents();
+	}
+
+	const getContents = () => {
+		contents = [];
+		messages.forEach((message) => {
+			if (message?.role !== 'user' && message?.content) {
+				const codeBlockContents = message.content.match(/```[\s\S]*?```/g);
+				let codeBlocks = [];
+
+				if (codeBlockContents) {
+					codeBlockContents.forEach((block) => {
+						const lang = block.split('\n')[0].replace('```', '').trim().toLowerCase();
+						const code = block.replace(/```[\s\S]*?\n/, '').replace(/```$/, '');
+						codeBlocks.push({ lang, code });
+					});
+				}
+
+				let htmlContent = '';
+				let cssContent = '';
+				let jsContent = '';
+
+				codeBlocks.forEach((block) => {
+					const { lang, code } = block;
+
+					if (lang === 'html') {
+						htmlContent += code + '\n';
+					} else if (lang === 'css') {
+						cssContent += code + '\n';
+					} else if (lang === 'javascript' || lang === 'js') {
+						jsContent += code + '\n';
+					}
+				});
+
+				const inlineHtml = message.content.match(/<html>[\s\S]*?<\/html>/gi);
+				const inlineCss = message.content.match(/<style>[\s\S]*?<\/style>/gi);
+				const inlineJs = message.content.match(/<script>[\s\S]*?<\/script>/gi);
+
+				if (inlineHtml) {
+					inlineHtml.forEach((block) => {
+						const content = block.replace(/<\/?html>/gi, ''); // Remove <html> tags
+						htmlContent += content + '\n';
+					});
+				}
+				if (inlineCss) {
+					inlineCss.forEach((block) => {
+						const content = block.replace(/<\/?style>/gi, ''); // Remove <style> tags
+						cssContent += content + '\n';
+					});
+				}
+				if (inlineJs) {
+					inlineJs.forEach((block) => {
+						const content = block.replace(/<\/?script>/gi, ''); // Remove <script> tags
+						jsContent += content + '\n';
+					});
+				}
+
+				if (htmlContent || cssContent || jsContent) {
+					const renderedContent = `
+                        <!DOCTYPE html>
+                        <html lang="en">
+                        <head>
+                            <meta charset="UTF-8">
+                            <meta name="viewport" content="width=device-width, initial-scale=1.0">
+							<${''}style>
+								body {
+									background-color: white; /* Ensure the iframe has a white background */
+								}
+
+								${cssContent}
+							</${''}style>
+                        </head>
+                        <body>
+                            ${htmlContent}
+
+							<${''}script>
+                            	${jsContent}
+							</${''}script>
+                        </body>
+                        </html>
+                    `;
+					contents = [...contents, { type: 'iframe', content: renderedContent }];
+				} else {
+					// Check for SVG content
+					for (const block of codeBlocks) {
+						if (block.lang === 'svg' || (block.lang === 'xml' && block.code.includes('<svg'))) {
+							contents = [...contents, { type: 'svg', content: block.code }];
+						}
+					}
+				}
+			}
+		});
+
+		if (contents.length === 0) {
+			showControls.set(false);
+			showArtifacts.set(false);
+		}
+
+		selectedContentIdx = contents ? contents.length - 1 : 0;
+	};
+
+	function navigateContent(direction: 'prev' | 'next') {
+		console.log(selectedContentIdx);
+
+		selectedContentIdx =
+			direction === 'prev'
+				? Math.max(selectedContentIdx - 1, 0)
+				: Math.min(selectedContentIdx + 1, contents.length - 1);
+
+		console.log(selectedContentIdx);
+	}
+
+	const iframeLoadHandler = () => {
+		iframeElement.contentWindow.addEventListener(
+			'click',
+			function (e) {
+				const target = e.target.closest('a');
+				if (target && target.href) {
+					e.preventDefault();
+					const url = new URL(target.href, iframeElement.baseURI);
+					if (url.origin === window.location.origin) {
+						iframeElement.contentWindow.history.pushState(
+							null,
+							'',
+							url.pathname + url.search + url.hash
+						);
+					} else {
+						console.log('External navigation blocked:', url.href);
+					}
+				}
+			},
+			true
+		);
+
+		// Cancel drag when hovering over iframe
+		iframeElement.contentWindow.addEventListener('mouseenter', function (e) {
+			e.preventDefault();
+			iframeElement.contentWindow.addEventListener('dragstart', (event) => {
+				event.preventDefault();
+			});
+		});
+	};
+
+	const showFullScreen = () => {
+		if (iframeElement.requestFullscreen) {
+			iframeElement.requestFullscreen();
+		} else if (iframeElement.webkitRequestFullscreen) {
+			iframeElement.webkitRequestFullscreen();
+		} else if (iframeElement.msRequestFullscreen) {
+			iframeElement.msRequestFullscreen();
+		}
+	};
+
+	onMount(() => {});
+</script>
+
+<div class=" w-full h-full relative flex flex-col bg-gray-50 dark:bg-gray-850">
+	<div class="w-full h-full flex-1 relative">
+		{#if overlay}
+			<div class=" absolute top-0 left-0 right-0 bottom-0 z-10"></div>
+		{/if}
+
+		<div class="absolute pointer-events-none z-50 w-full flex items-center justify-start p-4">
+			<button
+				class="self-center pointer-events-auto p-1 rounded-full bg-white dark:bg-gray-850"
+				on:click={() => {
+					showArtifacts.set(false);
+				}}
+			>
+				<ArrowLeft className="size-3.5  text-gray-900 dark:text-white" />
+			</button>
+		</div>
+
+		<div class=" absolute pointer-events-none z-50 w-full flex items-center justify-end p-4">
+			<button
+				class="self-center pointer-events-auto p-1 rounded-full bg-white dark:bg-gray-850"
+				on:click={() => {
+					dispatch('close');
+					showControls.set(false);
+					showArtifacts.set(false);
+				}}
+			>
+				<XMark className="size-3.5 text-gray-900 dark:text-white" />
+			</button>
+		</div>
+
+		<div class="flex-1 w-full h-full">
+			<div class=" h-full flex flex-col">
+				{#if contents.length > 0}
+					<div class="max-w-full w-full h-full">
+						{#if contents[selectedContentIdx].type === 'iframe'}
+							<iframe
+								bind:this={iframeElement}
+								title="Content"
+								srcdoc={contents[selectedContentIdx].content}
+								class="w-full border-0 h-full rounded-none"
+								sandbox="allow-scripts allow-forms allow-same-origin"
+								on:load={iframeLoadHandler}
+							></iframe>
+						{:else if contents[selectedContentIdx].type === 'svg'}
+							<SvgPanZoom
+								className=" w-full h-full max-h-full overflow-hidden"
+								svg={contents[selectedContentIdx].content}
+							/>
+						{/if}
+					</div>
+				{:else}
+					<div class="m-auto font-medium text-xs text-gray-900 dark:text-white">
+						{$i18n.t('No HTML, CSS, or JavaScript content found.')}
+					</div>
+				{/if}
+			</div>
+		</div>
+	</div>
+
+	{#if contents.length > 0}
+		<div class="flex justify-between items-center p-2.5 font-primar text-gray-900 dark:text-white">
+			<div class="flex items-center space-x-2">
+				<div class="flex items-center gap-0.5 self-center min-w-fit" dir="ltr">
+					<button
+						class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition disabled:cursor-not-allowed"
+						on:click={() => navigateContent('prev')}
+						disabled={contents.length <= 1}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke="currentColor"
+							stroke-width="2.5"
+							class="size-3.5"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="M15.75 19.5 8.25 12l7.5-7.5"
+							/>
+						</svg>
+					</button>
+
+					<div class="text-xs self-center dark:text-gray-100 min-w-fit">
+						{$i18n.t('Version {{selectedVersion}} of {{totalVersions}}', {
+							selectedVersion: selectedContentIdx + 1,
+							totalVersions: contents.length
+						})}
+					</div>
+
+					<button
+						class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition disabled:cursor-not-allowed"
+						on:click={() => navigateContent('next')}
+						disabled={contents.length <= 1}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke="currentColor"
+							stroke-width="2.5"
+							class="size-3.5"
+						>
+							<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
+						</svg>
+					</button>
+				</div>
+			</div>
+
+			<div class="flex items-center gap-1">
+				<button
+					class="copy-code-button bg-none border-none text-xs bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-md px-1.5 py-0.5"
+					on:click={() => {
+						copyToClipboard(contents[selectedContentIdx].content);
+						copied = true;
+
+						setTimeout(() => {
+							copied = false;
+						}, 2000);
+					}}>{copied ? $i18n.t('Copied') : $i18n.t('Copy')}</button
+				>
+
+				{#if contents[selectedContentIdx].type === 'iframe'}
+					<Tooltip content={$i18n.t('Open in full screen')}>
+						<button
+							class=" bg-none border-none text-xs bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-md p-0.5"
+							on:click={showFullScreen}
+						>
+							<ArrowsPointingOut className="size-3.5" />
+						</button>
+					</Tooltip>
+				{/if}
+			</div>
+		</div>
+	{/if}
+</div>
diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a0feda05769fd476f1bcf221c3b168fdd5455ba6
--- /dev/null
+++ b/src/lib/components/chat/Chat.svelte
@@ -0,0 +1,2352 @@
+<script lang="ts">
+	import { v4 as uuidv4 } from 'uuid';
+	import { toast } from 'svelte-sonner';
+	import mermaid from 'mermaid';
+	import { PaneGroup, Pane, PaneResizer } from 'paneforge';
+
+	import { getContext, onDestroy, onMount, tick } from 'svelte';
+	const i18n: Writable<i18nType> = getContext('i18n');
+
+	import { goto } from '$app/navigation';
+	import { page } from '$app/stores';
+
+	import { get, type Unsubscriber, type Writable } from 'svelte/store';
+	import type { i18n as i18nType } from 'i18next';
+	import { WEBUI_BASE_URL } from '$lib/constants';
+
+	import {
+		chatId,
+		chats,
+		config,
+		type Model,
+		models,
+		tags as allTags,
+		settings,
+		showSidebar,
+		WEBUI_NAME,
+		banners,
+		user,
+		socket,
+		showControls,
+		showCallOverlay,
+		currentChatPage,
+		temporaryChatEnabled,
+		mobile,
+		showOverview,
+		chatTitle,
+		showArtifacts
+	} from '$lib/stores';
+	import {
+		convertMessagesToHistory,
+		copyToClipboard,
+		getMessageContentParts,
+		extractSentencesForAudio,
+		promptTemplate,
+		splitStream
+	} from '$lib/utils';
+
+	import { generateChatCompletion } from '$lib/apis/ollama';
+	import {
+		addTagById,
+		createNewChat,
+		deleteTagById,
+		deleteTagsById,
+		getAllTags,
+		getChatById,
+		getChatList,
+		getTagsById,
+		updateChatById
+	} from '$lib/apis/chats';
+	import { generateOpenAIChatCompletion } from '$lib/apis/openai';
+	import { processWeb, processWebSearch, processYoutubeVideo } from '$lib/apis/retrieval';
+	import { createOpenAITextStream } from '$lib/apis/streaming';
+	import { queryMemory } from '$lib/apis/memories';
+	import { getAndUpdateUserLocation, getUserSettings } from '$lib/apis/users';
+	import {
+		chatCompleted,
+		generateTitle,
+		generateSearchQuery,
+		chatAction,
+		generateMoACompletion,
+		generateTags
+	} from '$lib/apis';
+
+	import Banner from '../common/Banner.svelte';
+	import MessageInput from '$lib/components/chat/MessageInput.svelte';
+	import Messages from '$lib/components/chat/Messages.svelte';
+	import Navbar from '$lib/components/layout/Navbar.svelte';
+	import ChatControls from './ChatControls.svelte';
+	import EventConfirmDialog from '../common/ConfirmDialog.svelte';
+	import Placeholder from './Placeholder.svelte';
+
+	export let chatIdProp = '';
+
+	let loaded = false;
+	const eventTarget = new EventTarget();
+	let controlPane;
+	let controlPaneComponent;
+
+	let stopResponseFlag = false;
+	let autoScroll = true;
+	let processing = '';
+	let messagesContainerElement: HTMLDivElement;
+
+	let navbarElement;
+
+	let showEventConfirmation = false;
+	let eventConfirmationTitle = '';
+	let eventConfirmationMessage = '';
+	let eventConfirmationInput = false;
+	let eventConfirmationInputPlaceholder = '';
+	let eventConfirmationInputValue = '';
+	let eventCallback = null;
+
+	let chatIdUnsubscriber: Unsubscriber | undefined;
+
+	let selectedModels = [''];
+	let atSelectedModel: Model | undefined;
+	let selectedModelIds = [];
+	$: selectedModelIds = atSelectedModel !== undefined ? [atSelectedModel.id] : selectedModels;
+
+	let selectedToolIds = [];
+	let webSearchEnabled = false;
+
+	let chat = null;
+	let tags = [];
+
+	let history = {
+		messages: {},
+		currentId: null
+	};
+
+	// Chat Input
+	let prompt = '';
+	let chatFiles = [];
+	let files = [];
+	let params = {};
+
+	$: if (chatIdProp) {
+		(async () => {
+			console.log(chatIdProp);
+			if (chatIdProp && (await loadChat())) {
+				await tick();
+				loaded = true;
+
+				window.setTimeout(() => scrollToBottom(), 0);
+				const chatInput = document.getElementById('chat-input');
+				chatInput?.focus();
+			} else {
+				await goto('/');
+			}
+		})();
+	}
+
+	const showMessage = async (message) => {
+		const _chatId = JSON.parse(JSON.stringify($chatId));
+		let _messageId = JSON.parse(JSON.stringify(message.id));
+
+		let messageChildrenIds = history.messages[_messageId].childrenIds;
+
+		while (messageChildrenIds.length !== 0) {
+			_messageId = messageChildrenIds.at(-1);
+			messageChildrenIds = history.messages[_messageId].childrenIds;
+		}
+
+		history.currentId = _messageId;
+
+		await tick();
+		await tick();
+		await tick();
+
+		const messageElement = document.getElementById(`message-${message.id}`);
+		if (messageElement) {
+			messageElement.scrollIntoView({ behavior: 'smooth' });
+		}
+
+		await tick();
+		saveChatHandler(_chatId);
+	};
+
+	const chatEventHandler = async (event, cb) => {
+		if (event.chat_id === $chatId) {
+			await tick();
+			console.log(event);
+			let message = history.messages[event.message_id];
+
+			const type = event?.data?.type ?? null;
+			const data = event?.data?.data ?? null;
+
+			if (type === 'status') {
+				if (message?.statusHistory) {
+					message.statusHistory.push(data);
+				} else {
+					message.statusHistory = [data];
+				}
+			} else if (type === 'citation') {
+				if (data?.type === 'code_execution') {
+					// Code execution; update existing code execution by ID, or add new one.
+					if (!message?.code_executions) {
+						message.code_executions = [];
+					}
+
+					const existingCodeExecutionIndex = message.code_executions.findIndex(
+						(execution) => execution.id === data.id
+					);
+
+					if (existingCodeExecutionIndex !== -1) {
+						message.code_executions[existingCodeExecutionIndex] = data;
+					} else {
+						message.code_executions.push(data);
+					}
+
+					message.code_executions = message.code_executions;
+				} else {
+					// Regular citation.
+					if (message?.citations) {
+						message.citations.push(data);
+					} else {
+						message.citations = [data];
+					}
+				}
+			} else if (type === 'message') {
+				message.content += data.content;
+			} else if (type === 'replace') {
+				message.content = data.content;
+			} else if (type === 'action') {
+				if (data.action === 'continue') {
+					const continueButton = document.getElementById('continue-response-button');
+
+					if (continueButton) {
+						continueButton.click();
+					}
+				}
+			} else if (type === 'confirmation') {
+				eventCallback = cb;
+
+				eventConfirmationInput = false;
+				showEventConfirmation = true;
+
+				eventConfirmationTitle = data.title;
+				eventConfirmationMessage = data.message;
+			} else if (type === 'execute') {
+				eventCallback = cb;
+
+				try {
+					// Use Function constructor to evaluate code in a safer way
+					const asyncFunction = new Function(`return (async () => { ${data.code} })()`);
+					const result = await asyncFunction(); // Await the result of the async function
+
+					if (cb) {
+						cb(result);
+					}
+				} catch (error) {
+					console.error('Error executing code:', error);
+				}
+			} else if (type === 'input') {
+				eventCallback = cb;
+
+				eventConfirmationInput = true;
+				showEventConfirmation = true;
+
+				eventConfirmationTitle = data.title;
+				eventConfirmationMessage = data.message;
+				eventConfirmationInputPlaceholder = data.placeholder;
+				eventConfirmationInputValue = data?.value ?? '';
+			} else {
+				console.log('Unknown message type', data);
+			}
+
+			history.messages[event.message_id] = message;
+		}
+	};
+
+	const onMessageHandler = async (event: {
+		origin: string;
+		data: { type: string; text: string };
+	}) => {
+		if (event.origin !== window.origin) {
+			return;
+		}
+
+		// Replace with your iframe's origin
+		if (event.data.type === 'input:prompt') {
+			console.debug(event.data.text);
+
+			const inputElement = document.getElementById('chat-input');
+
+			if (inputElement) {
+				prompt = event.data.text;
+				inputElement.focus();
+			}
+		}
+
+		if (event.data.type === 'action:submit') {
+			console.debug(event.data.text);
+
+			if (prompt !== '') {
+				await tick();
+				submitPrompt(prompt);
+			}
+		}
+
+		if (event.data.type === 'input:prompt:submit') {
+			console.debug(event.data.text);
+
+			if (prompt !== '') {
+				await tick();
+				submitPrompt(event.data.text);
+			}
+		}
+	};
+
+	onMount(async () => {
+		window.addEventListener('message', onMessageHandler);
+		$socket?.on('chat-events', chatEventHandler);
+
+		if (!$chatId) {
+			chatIdUnsubscriber = chatId.subscribe(async (value) => {
+				if (!value) {
+					await initNewChat();
+				}
+			});
+		} else {
+			if ($temporaryChatEnabled) {
+				await goto('/');
+			}
+		}
+
+		showControls.subscribe(async (value) => {
+			if (controlPane && !$mobile) {
+				try {
+					if (value) {
+						controlPaneComponent.openPane();
+					} else {
+						controlPane.collapse();
+					}
+				} catch (e) {
+					// ignore
+				}
+			}
+
+			if (!value) {
+				showCallOverlay.set(false);
+				showOverview.set(false);
+				showArtifacts.set(false);
+			}
+		});
+
+		const chatInput = document.getElementById('chat-input');
+		chatInput?.focus();
+
+		chats.subscribe(() => {});
+	});
+
+	onDestroy(() => {
+		chatIdUnsubscriber?.();
+		window.removeEventListener('message', onMessageHandler);
+		$socket?.off('chat-events');
+	});
+
+	// File upload functions
+
+	const uploadWeb = async (url) => {
+		console.log(url);
+
+		const fileItem = {
+			type: 'doc',
+			name: url,
+			collection_name: '',
+			status: 'uploading',
+			url: url,
+			error: ''
+		};
+
+		try {
+			files = [...files, fileItem];
+			const res = await processWeb(localStorage.token, '', url);
+
+			if (res) {
+				fileItem.status = 'uploaded';
+				fileItem.collection_name = res.collection_name;
+				fileItem.file = {
+					...res.file,
+					...fileItem.file
+				};
+
+				files = files;
+			}
+		} catch (e) {
+			// Remove the failed doc from the files array
+			files = files.filter((f) => f.name !== url);
+			toast.error(JSON.stringify(e));
+		}
+	};
+
+	const uploadYoutubeTranscription = async (url) => {
+		console.log(url);
+
+		const fileItem = {
+			type: 'doc',
+			name: url,
+			collection_name: '',
+			status: 'uploading',
+			context: 'full',
+			url: url,
+			error: ''
+		};
+
+		try {
+			files = [...files, fileItem];
+			const res = await processYoutubeVideo(localStorage.token, url);
+
+			if (res) {
+				fileItem.status = 'uploaded';
+				fileItem.collection_name = res.collection_name;
+				fileItem.file = {
+					...res.file,
+					...fileItem.file
+				};
+				files = files;
+			}
+		} catch (e) {
+			// Remove the failed doc from the files array
+			files = files.filter((f) => f.name !== url);
+			toast.error(e);
+		}
+	};
+
+	//////////////////////////
+	// Web functions
+	//////////////////////////
+
+	const initNewChat = async () => {
+		await showControls.set(false);
+		await showCallOverlay.set(false);
+		await showOverview.set(false);
+		await showArtifacts.set(false);
+
+		if ($page.url.pathname.includes('/c/')) {
+			window.history.replaceState(history.state, '', `/`);
+		}
+
+		autoScroll = true;
+
+		await chatId.set('');
+		await chatTitle.set('');
+
+		history = {
+			messages: {},
+			currentId: null
+		};
+
+		chatFiles = [];
+		params = {};
+
+		if ($page.url.searchParams.get('models')) {
+			selectedModels = $page.url.searchParams.get('models')?.split(',');
+		} else if ($page.url.searchParams.get('model')) {
+			const urlModels = $page.url.searchParams.get('model')?.split(',');
+
+			if (urlModels.length === 1) {
+				const m = $models.find((m) => m.id === urlModels[0]);
+				if (!m) {
+					const modelSelectorButton = document.getElementById('model-selector-0-button');
+					if (modelSelectorButton) {
+						modelSelectorButton.click();
+						await tick();
+
+						const modelSelectorInput = document.getElementById('model-search-input');
+						if (modelSelectorInput) {
+							modelSelectorInput.focus();
+							modelSelectorInput.value = urlModels[0];
+							modelSelectorInput.dispatchEvent(new Event('input'));
+						}
+					}
+				} else {
+					selectedModels = urlModels;
+				}
+			} else {
+				selectedModels = urlModels;
+			}
+		} else if ($settings?.models) {
+			selectedModels = $settings?.models;
+		} else if ($config?.default_models) {
+			console.log($config?.default_models.split(',') ?? '');
+			selectedModels = $config?.default_models.split(',');
+		}
+
+		selectedModels = selectedModels.filter((modelId) => $models.map((m) => m.id).includes(modelId));
+
+		if (selectedModels.length === 0 || (selectedModels.length === 1 && selectedModels[0] === '')) {
+			if ($models.length > 0) {
+				selectedModels = [$models[0].id];
+			} else {
+				selectedModels = [''];
+			}
+		}
+
+		console.log(selectedModels);
+
+		if ($page.url.searchParams.get('youtube')) {
+			uploadYoutubeTranscription(
+				`https://www.youtube.com/watch?v=${$page.url.searchParams.get('youtube')}`
+			);
+		}
+		if ($page.url.searchParams.get('web-search') === 'true') {
+			webSearchEnabled = true;
+		}
+
+		if ($page.url.searchParams.get('tools')) {
+			selectedToolIds = $page.url.searchParams
+				.get('tools')
+				?.split(',')
+				.map((id) => id.trim())
+				.filter((id) => id);
+		} else if ($page.url.searchParams.get('tool-ids')) {
+			selectedToolIds = $page.url.searchParams
+				.get('tool-ids')
+				?.split(',')
+				.map((id) => id.trim())
+				.filter((id) => id);
+		}
+
+		if ($page.url.searchParams.get('call') === 'true') {
+			showCallOverlay.set(true);
+			showControls.set(true);
+		}
+
+		if ($page.url.searchParams.get('q')) {
+			prompt = $page.url.searchParams.get('q') ?? '';
+
+			if (prompt) {
+				await tick();
+				submitPrompt(prompt);
+			}
+		}
+
+		selectedModels = selectedModels.map((modelId) =>
+			$models.map((m) => m.id).includes(modelId) ? modelId : ''
+		);
+
+		const userSettings = await getUserSettings(localStorage.token);
+
+		if (userSettings) {
+			settings.set(userSettings.ui);
+		} else {
+			settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
+		}
+
+		const chatInput = document.getElementById('chat-input');
+		setTimeout(() => chatInput?.focus(), 0);
+	};
+
+	const loadChat = async () => {
+		chatId.set(chatIdProp);
+		chat = await getChatById(localStorage.token, $chatId).catch(async (error) => {
+			await goto('/');
+			return null;
+		});
+
+		if (chat) {
+			tags = await getTagsById(localStorage.token, $chatId).catch(async (error) => {
+				return [];
+			});
+
+			const chatContent = chat.chat;
+
+			if (chatContent) {
+				console.log(chatContent);
+
+				selectedModels =
+					(chatContent?.models ?? undefined) !== undefined
+						? chatContent.models
+						: [chatContent.models ?? ''];
+				history =
+					(chatContent?.history ?? undefined) !== undefined
+						? chatContent.history
+						: convertMessagesToHistory(chatContent.messages);
+
+				chatTitle.set(chatContent.title);
+
+				const userSettings = await getUserSettings(localStorage.token);
+
+				if (userSettings) {
+					await settings.set(userSettings.ui);
+				} else {
+					await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
+				}
+
+				params = chatContent?.params ?? {};
+				chatFiles = chatContent?.files ?? [];
+
+				autoScroll = true;
+				await tick();
+
+				if (history.currentId) {
+					history.messages[history.currentId].done = true;
+				}
+				await tick();
+
+				return true;
+			} else {
+				return null;
+			}
+		}
+	};
+
+	const scrollToBottom = async () => {
+		await tick();
+		if (messagesContainerElement) {
+			messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight;
+		}
+	};
+
+	const createMessagesList = (responseMessageId) => {
+		if (responseMessageId === null) {
+			return [];
+		}
+
+		const message = history.messages[responseMessageId];
+		if (message?.parentId) {
+			return [...createMessagesList(message.parentId), message];
+		} else {
+			return [message];
+		}
+	};
+
+	const chatCompletedHandler = async (chatId, modelId, responseMessageId, messages) => {
+		await mermaid.run({
+			querySelector: '.mermaid'
+		});
+
+		const res = await chatCompleted(localStorage.token, {
+			model: modelId,
+			messages: messages.map((m) => ({
+				id: m.id,
+				role: m.role,
+				content: m.content,
+				info: m.info ? m.info : undefined,
+				timestamp: m.timestamp
+			})),
+			chat_id: chatId,
+			session_id: $socket?.id,
+			id: responseMessageId
+		}).catch((error) => {
+			toast.error(error);
+			messages.at(-1).error = { content: error };
+
+			return null;
+		});
+
+		if (res !== null) {
+			// Update chat history with the new messages
+			for (const message of res.messages) {
+				history.messages[message.id] = {
+					...history.messages[message.id],
+					...(history.messages[message.id].content !== message.content
+						? { originalContent: history.messages[message.id].content }
+						: {}),
+					...message
+				};
+			}
+		}
+
+		await tick();
+
+		if ($chatId == chatId) {
+			if (!$temporaryChatEnabled) {
+				chat = await updateChatById(localStorage.token, chatId, {
+					models: selectedModels,
+					messages: messages,
+					history: history,
+					params: params,
+					files: chatFiles
+				});
+
+				currentChatPage.set(1);
+				await chats.set(await getChatList(localStorage.token, $currentChatPage));
+			}
+		}
+	};
+
+	const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => {
+		const messages = createMessagesList(responseMessageId);
+
+		const res = await chatAction(localStorage.token, actionId, {
+			model: modelId,
+			messages: messages.map((m) => ({
+				id: m.id,
+				role: m.role,
+				content: m.content,
+				info: m.info ? m.info : undefined,
+				timestamp: m.timestamp
+			})),
+			...(event ? { event: event } : {}),
+			chat_id: chatId,
+			session_id: $socket?.id,
+			id: responseMessageId
+		}).catch((error) => {
+			toast.error(error);
+			messages.at(-1).error = { content: error };
+			return null;
+		});
+
+		if (res !== null) {
+			// Update chat history with the new messages
+			for (const message of res.messages) {
+				history.messages[message.id] = {
+					...history.messages[message.id],
+					...(history.messages[message.id].content !== message.content
+						? { originalContent: history.messages[message.id].content }
+						: {}),
+					...message
+				};
+			}
+		}
+
+		if ($chatId == chatId) {
+			if (!$temporaryChatEnabled) {
+				chat = await updateChatById(localStorage.token, chatId, {
+					models: selectedModels,
+					messages: messages,
+					history: history,
+					params: params,
+					files: chatFiles
+				});
+
+				currentChatPage.set(1);
+				await chats.set(await getChatList(localStorage.token, $currentChatPage));
+			}
+		}
+	};
+
+	const getChatEventEmitter = async (modelId: string, chatId: string = '') => {
+		return setInterval(() => {
+			$socket?.emit('usage', {
+				action: 'chat',
+				model: modelId,
+				chat_id: chatId
+			});
+		}, 1000);
+	};
+
+	const createMessagePair = async (userPrompt) => {
+		prompt = '';
+		if (selectedModels.length === 0) {
+			toast.error($i18n.t('Model not selected'));
+		} else {
+			const modelId = selectedModels[0];
+			const model = $models.filter((m) => m.id === modelId).at(0);
+
+			const messages = createMessagesList(history.currentId);
+			const parentMessage = messages.length !== 0 ? messages.at(-1) : null;
+
+			const userMessageId = uuidv4();
+			const responseMessageId = uuidv4();
+
+			const userMessage = {
+				id: userMessageId,
+				parentId: parentMessage ? parentMessage.id : null,
+				childrenIds: [responseMessageId],
+				role: 'user',
+				content: userPrompt ? userPrompt : `[PROMPT] ${userMessageId}`,
+				timestamp: Math.floor(Date.now() / 1000)
+			};
+
+			const responseMessage = {
+				id: responseMessageId,
+				parentId: userMessageId,
+				childrenIds: [],
+				role: 'assistant',
+				content: `[RESPONSE] ${responseMessageId}`,
+				done: true,
+
+				model: modelId,
+				modelName: model.name ?? model.id,
+				modelIdx: 0,
+				timestamp: Math.floor(Date.now() / 1000)
+			};
+
+			if (parentMessage) {
+				parentMessage.childrenIds.push(userMessageId);
+				history.messages[parentMessage.id] = parentMessage;
+			}
+			history.messages[userMessageId] = userMessage;
+			history.messages[responseMessageId] = responseMessage;
+
+			history.currentId = responseMessageId;
+
+			await tick();
+
+			if (autoScroll) {
+				scrollToBottom();
+			}
+
+			if (messages.length === 0) {
+				await initChatHandler();
+			} else {
+				await saveChatHandler($chatId);
+			}
+		}
+	};
+
+	//////////////////////////
+	// Chat functions
+	//////////////////////////
+
+	const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
+		console.log('submitPrompt', userPrompt, $chatId);
+
+		const messages = createMessagesList(history.currentId);
+		selectedModels = selectedModels.map((modelId) =>
+			$models.map((m) => m.id).includes(modelId) ? modelId : ''
+		);
+
+		if (userPrompt === '') {
+			toast.error($i18n.t('Please enter a prompt'));
+			return;
+		}
+		if (selectedModels.includes('')) {
+			toast.error($i18n.t('Model not selected'));
+			return;
+		}
+
+		if (messages.length != 0 && messages.at(-1).done != true) {
+			// Response not done
+			return;
+		}
+		if (messages.length != 0 && messages.at(-1).error) {
+			// Error in response
+			toast.error($i18n.t(`Oops! There was an error in the previous response.`));
+			return;
+		}
+		if (
+			files.length > 0 &&
+			files.filter((file) => file.type !== 'image' && file.status === 'uploading').length > 0
+		) {
+			toast.error(
+				$i18n.t(`Oops! There are files still uploading. Please wait for the upload to complete.`)
+			);
+			return;
+		}
+		if (
+			($config?.file?.max_count ?? null) !== null &&
+			files.length + chatFiles.length > $config?.file?.max_count
+		) {
+			toast.error(
+				$i18n.t(`You can only chat with a maximum of {{maxCount}} file(s) at a time.`, {
+					maxCount: $config?.file?.max_count
+				})
+			);
+			return;
+		}
+
+		let _responses = [];
+		prompt = '';
+		await tick();
+
+		// Reset chat input textarea
+		const chatInputContainer = document.getElementById('chat-input-container');
+
+		if (chatInputContainer) {
+			chatInputContainer.value = '';
+			chatInputContainer.style.height = '';
+		}
+
+		const _files = JSON.parse(JSON.stringify(files));
+		chatFiles.push(..._files.filter((item) => ['doc', 'file', 'collection'].includes(item.type)));
+		chatFiles = chatFiles.filter(
+			// Remove duplicates
+			(item, index, array) =>
+				array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
+		);
+
+		files = [];
+		prompt = '';
+
+		// Create user message
+		let userMessageId = uuidv4();
+		let userMessage = {
+			id: userMessageId,
+			parentId: messages.length !== 0 ? messages.at(-1).id : null,
+			childrenIds: [],
+			role: 'user',
+			content: userPrompt,
+			files: _files.length > 0 ? _files : undefined,
+			timestamp: Math.floor(Date.now() / 1000), // Unix epoch
+			models: selectedModels
+		};
+
+		// Add message to history and Set currentId to messageId
+		history.messages[userMessageId] = userMessage;
+		history.currentId = userMessageId;
+
+		// Append messageId to childrenIds of parent message
+		if (messages.length !== 0) {
+			history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
+		}
+
+		// Wait until history/message have been updated
+		await tick();
+
+		// focus on chat input
+		const chatInput = document.getElementById('chat-input');
+		chatInput?.focus();
+
+		_responses = await sendPrompt(userPrompt, userMessageId, { newChat: true });
+
+		return _responses;
+	};
+
+	const sendPrompt = async (
+		prompt: string,
+		parentId: string,
+		{ modelId = null, modelIdx = null, newChat = false } = {}
+	) => {
+		// Create new chat if newChat is true and first user message
+		if (
+			newChat &&
+			history.messages[history.currentId].parentId === null &&
+			history.messages[history.currentId].role === 'user'
+		) {
+			await initChatHandler();
+		}
+
+		let _responses: string[] = [];
+		// If modelId is provided, use it, else use selected model
+		let selectedModelIds = modelId
+			? [modelId]
+			: atSelectedModel !== undefined
+				? [atSelectedModel.id]
+				: selectedModels;
+
+		// Create response messages for each selected model
+		const responseMessageIds: Record<PropertyKey, string> = {};
+		for (const [_modelIdx, modelId] of selectedModelIds.entries()) {
+			const model = $models.filter((m) => m.id === modelId).at(0);
+
+			if (model) {
+				let responseMessageId = uuidv4();
+				let responseMessage = {
+					parentId: parentId,
+					id: responseMessageId,
+					childrenIds: [],
+					role: 'assistant',
+					content: '',
+					model: model.id,
+					modelName: model.name ?? model.id,
+					modelIdx: modelIdx ? modelIdx : _modelIdx,
+					userContext: null,
+					timestamp: Math.floor(Date.now() / 1000) // Unix epoch
+				};
+
+				// Add message to history and Set currentId to messageId
+				history.messages[responseMessageId] = responseMessage;
+				history.currentId = responseMessageId;
+
+				// Append messageId to childrenIds of parent message
+				if (parentId !== null) {
+					history.messages[parentId].childrenIds = [
+						...history.messages[parentId].childrenIds,
+						responseMessageId
+					];
+				}
+
+				responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`] = responseMessageId;
+			}
+		}
+		await tick();
+
+		const _chatId = JSON.parse(JSON.stringify($chatId));
+		await Promise.all(
+			selectedModelIds.map(async (modelId, _modelIdx) => {
+				console.log('modelId', modelId);
+				const model = $models.filter((m) => m.id === modelId).at(0);
+
+				if (model) {
+					const messages = createMessagesList(parentId);
+					// If there are image files, check if model is vision capable
+					const hasImages = messages.some((message) =>
+						message.files?.some((file) => file.type === 'image')
+					);
+
+					if (hasImages && !(model.info?.meta?.capabilities?.vision ?? true)) {
+						toast.error(
+							$i18n.t('Model {{modelName}} is not vision capable', {
+								modelName: model.name ?? model.id
+							})
+						);
+					}
+
+					let responseMessageId =
+						responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`];
+					let responseMessage = history.messages[responseMessageId];
+
+					let userContext = null;
+					if ($settings?.memory ?? false) {
+						if (userContext === null) {
+							const res = await queryMemory(localStorage.token, prompt).catch((error) => {
+								toast.error(error);
+								return null;
+							});
+							if (res) {
+								if (res.documents[0].length > 0) {
+									userContext = res.documents[0].reduce((acc, doc, index) => {
+										const createdAtTimestamp = res.metadatas[0][index].created_at;
+										const createdAtDate = new Date(createdAtTimestamp * 1000)
+											.toISOString()
+											.split('T')[0];
+										return `${acc}${index + 1}. [${createdAtDate}]. ${doc}\n`;
+									}, '');
+								}
+
+								console.log(userContext);
+							}
+						}
+					}
+					responseMessage.userContext = userContext;
+
+					const chatEventEmitter = await getChatEventEmitter(model.id, _chatId);
+
+					scrollToBottom();
+					if (webSearchEnabled) {
+						await getWebSearchResults(model.id, parentId, responseMessageId);
+					}
+
+					let _response = null;
+					if (model?.owned_by === 'ollama') {
+						_response = await sendPromptOllama(model, prompt, responseMessageId, _chatId);
+					} else if (model) {
+						_response = await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
+					}
+					_responses.push(_response);
+
+					if (chatEventEmitter) clearInterval(chatEventEmitter);
+				} else {
+					toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
+				}
+			})
+		);
+
+		currentChatPage.set(1);
+		chats.set(await getChatList(localStorage.token, $currentChatPage));
+
+		return _responses;
+	};
+
+	const sendPromptOllama = async (model, userPrompt, responseMessageId, _chatId) => {
+		let _response: string | null = null;
+
+		const responseMessage = history.messages[responseMessageId];
+		const userMessage = history.messages[responseMessage.parentId];
+
+		// Wait until history/message have been updated
+		await tick();
+
+		// Scroll down
+		scrollToBottom();
+
+		const messagesBody = [
+			params?.system || $settings.system || (responseMessage?.userContext ?? null)
+				? {
+						role: 'system',
+						content: `${promptTemplate(
+							params?.system ?? $settings?.system ?? '',
+							$user.name,
+							$settings?.userLocation
+								? await getAndUpdateUserLocation(localStorage.token)
+								: undefined
+						)}${
+							(responseMessage?.userContext ?? null)
+								? `\n\nUser Context:\n${responseMessage?.userContext ?? ''}`
+								: ''
+						}`
+					}
+				: undefined,
+			...createMessagesList(responseMessageId)
+		]
+			.filter((message) => message?.content?.trim())
+			.map((message) => {
+				// Prepare the base message object
+				const baseMessage = {
+					role: message.role,
+					content: message?.merged?.content ?? message.content
+				};
+
+				// Extract and format image URLs if any exist
+				const imageUrls = message.files
+					?.filter((file) => file.type === 'image')
+					.map((file) => file.url.slice(file.url.indexOf(',') + 1));
+
+				// Add images array only if it contains elements
+				if (imageUrls && imageUrls.length > 0 && message.role === 'user') {
+					baseMessage.images = imageUrls;
+				}
+				return baseMessage;
+			});
+
+		let lastImageIndex = -1;
+
+		// Find the index of the last object with images
+		messagesBody.forEach((item, index) => {
+			if (item.images) {
+				lastImageIndex = index;
+			}
+		});
+
+		// Remove images from all but the last one
+		messagesBody.forEach((item, index) => {
+			if (index !== lastImageIndex) {
+				delete item.images;
+			}
+		});
+
+		let files = JSON.parse(JSON.stringify(chatFiles));
+		if (model?.info?.meta?.knowledge ?? false) {
+			// Only initialize and add status if knowledge exists
+			responseMessage.statusHistory = [
+				{
+					action: 'knowledge_search',
+					description: $i18n.t(`Searching Knowledge for "{{searchQuery}}"`, {
+						searchQuery: userMessage.content
+					}),
+					done: false
+				}
+			];
+			files.push(
+				...model.info.meta.knowledge.map((item) => {
+					if (item?.collection_name) {
+						return {
+							id: item.collection_name,
+							name: item.name,
+							legacy: true
+						};
+					} else if (item?.collection_names) {
+						return {
+							name: item.name,
+							type: 'collection',
+							collection_names: item.collection_names,
+							legacy: true
+						};
+					} else {
+						return item;
+					}
+				})
+			);
+			history.messages[responseMessageId] = responseMessage;
+		}
+		files.push(
+			...(userMessage?.files ?? []).filter((item) =>
+				['doc', 'file', 'collection'].includes(item.type)
+			),
+			...(responseMessage?.files ?? []).filter((item) => ['web_search_results'].includes(item.type))
+		);
+
+		// Remove duplicates
+		files = files.filter(
+			(item, index, array) =>
+				array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
+		);
+
+		scrollToBottom();
+
+		eventTarget.dispatchEvent(
+			new CustomEvent('chat:start', {
+				detail: {
+					id: responseMessageId
+				}
+			})
+		);
+
+		await tick();
+
+		const stream =
+			model?.info?.params?.stream_response ??
+			$settings?.params?.stream_response ??
+			params?.stream_response ??
+			true;
+		const [res, controller] = await generateChatCompletion(localStorage.token, {
+			stream: stream,
+			model: model.id,
+			messages: messagesBody,
+			options: {
+				...{ ...($settings?.params ?? {}), ...params },
+				stop:
+					(params?.stop ?? $settings?.params?.stop ?? undefined)
+						? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map(
+								(str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
+							)
+						: undefined,
+				num_predict: params?.max_tokens ?? $settings?.params?.max_tokens ?? undefined,
+				repeat_penalty:
+					params?.frequency_penalty ?? $settings?.params?.frequency_penalty ?? undefined
+			},
+			format: $settings.requestFormat ?? undefined,
+			keep_alive: $settings.keepAlive ?? undefined,
+			tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
+			files: files.length > 0 ? files : undefined,
+			session_id: $socket?.id,
+			chat_id: $chatId,
+			id: responseMessageId
+		});
+
+		if (res && res.ok) {
+			if (!stream) {
+				const response = await res.json();
+				console.log(response);
+
+				responseMessage.content = response.message.content;
+				responseMessage.info = {
+					eval_count: response.eval_count,
+					eval_duration: response.eval_duration,
+					load_duration: response.load_duration,
+					prompt_eval_count: response.prompt_eval_count,
+					prompt_eval_duration: response.prompt_eval_duration,
+					total_duration: response.total_duration
+				};
+				responseMessage.done = true;
+			} else {
+				console.log('controller', controller);
+
+				const reader = res.body
+					.pipeThrough(new TextDecoderStream())
+					.pipeThrough(splitStream('\n'))
+					.getReader();
+
+				while (true) {
+					const { value, done } = await reader.read();
+					if (done || stopResponseFlag || _chatId !== $chatId) {
+						responseMessage.done = true;
+						history.messages[responseMessageId] = responseMessage;
+
+						if (stopResponseFlag) {
+							controller.abort('User: Stop Response');
+						}
+
+						_response = responseMessage.content;
+						break;
+					}
+
+					try {
+						let lines = value.split('\n');
+
+						for (const line of lines) {
+							if (line !== '') {
+								console.log(line);
+								let data = JSON.parse(line);
+
+								if ('citations' in data) {
+									responseMessage.citations = data.citations;
+									// Only remove status if it was initially set
+									if (model?.info?.meta?.knowledge ?? false) {
+										responseMessage.statusHistory = responseMessage.statusHistory.filter(
+											(status) => status.action !== 'knowledge_search'
+										);
+									}
+									continue;
+								}
+
+								if ('detail' in data) {
+									throw data;
+								}
+
+								if (data.done == false) {
+									if (responseMessage.content == '' && data.message.content == '\n') {
+										continue;
+									} else {
+										responseMessage.content += data.message.content;
+
+										if (navigator.vibrate && ($settings?.hapticFeedback ?? false)) {
+											navigator.vibrate(5);
+										}
+
+										const messageContentParts = getMessageContentParts(
+											responseMessage.content,
+											$config?.audio?.tts?.split_on ?? 'punctuation'
+										);
+										messageContentParts.pop();
+
+										// dispatch only last sentence and make sure it hasn't been dispatched before
+										if (
+											messageContentParts.length > 0 &&
+											messageContentParts[messageContentParts.length - 1] !==
+												responseMessage.lastSentence
+										) {
+											responseMessage.lastSentence =
+												messageContentParts[messageContentParts.length - 1];
+											eventTarget.dispatchEvent(
+												new CustomEvent('chat', {
+													detail: {
+														id: responseMessageId,
+														content: messageContentParts[messageContentParts.length - 1]
+													}
+												})
+											);
+										}
+
+										history.messages[responseMessageId] = responseMessage;
+									}
+								} else {
+									responseMessage.done = true;
+
+									if (responseMessage.content == '') {
+										responseMessage.error = {
+											code: 400,
+											content: `Oops! No text generated from Ollama, Please try again.`
+										};
+									}
+
+									responseMessage.context = data.context ?? null;
+									responseMessage.info = {
+										total_duration: data.total_duration,
+										load_duration: data.load_duration,
+										sample_count: data.sample_count,
+										sample_duration: data.sample_duration,
+										prompt_eval_count: data.prompt_eval_count,
+										prompt_eval_duration: data.prompt_eval_duration,
+										eval_count: data.eval_count,
+										eval_duration: data.eval_duration
+									};
+
+									history.messages[responseMessageId] = responseMessage;
+
+									if ($settings.notificationEnabled && !document.hasFocus()) {
+										const notification = new Notification(`${model.id}`, {
+											body: responseMessage.content,
+											icon: `${WEBUI_BASE_URL}/static/favicon.png`
+										});
+									}
+
+									if ($settings?.responseAutoCopy ?? false) {
+										copyToClipboard(responseMessage.content);
+									}
+
+									if ($settings.responseAutoPlayback && !$showCallOverlay) {
+										await tick();
+										document.getElementById(`speak-button-${responseMessage.id}`)?.click();
+									}
+								}
+							}
+						}
+					} catch (error) {
+						console.log(error);
+						if ('detail' in error) {
+							toast.error(error.detail);
+						}
+						break;
+					}
+
+					if (autoScroll) {
+						scrollToBottom();
+					}
+				}
+			}
+		} else {
+			if (res !== null) {
+				const error = await res.json();
+				console.log(error);
+				if ('detail' in error) {
+					toast.error(error.detail);
+					responseMessage.error = { content: error.detail };
+				} else {
+					toast.error(error.error);
+					responseMessage.error = { content: error.error };
+				}
+			} else {
+				toast.error(
+					$i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, { provider: 'Ollama' })
+				);
+				responseMessage.error = {
+					content: $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
+						provider: 'Ollama'
+					})
+				};
+			}
+			responseMessage.done = true;
+
+			if (responseMessage.statusHistory) {
+				responseMessage.statusHistory = responseMessage.statusHistory.filter(
+					(status) => status.action !== 'knowledge_search'
+				);
+			}
+		}
+		await saveChatHandler(_chatId);
+
+		history.messages[responseMessageId] = responseMessage;
+
+		await chatCompletedHandler(
+			_chatId,
+			model.id,
+			responseMessageId,
+			createMessagesList(responseMessageId)
+		);
+
+		stopResponseFlag = false;
+		await tick();
+
+		let lastMessageContentPart =
+			getMessageContentParts(
+				responseMessage.content,
+				$config?.audio?.tts?.split_on ?? 'punctuation'
+			)?.at(-1) ?? '';
+		if (lastMessageContentPart) {
+			eventTarget.dispatchEvent(
+				new CustomEvent('chat', {
+					detail: { id: responseMessageId, content: lastMessageContentPart }
+				})
+			);
+		}
+
+		eventTarget.dispatchEvent(
+			new CustomEvent('chat:finish', {
+				detail: {
+					id: responseMessageId,
+					content: responseMessage.content
+				}
+			})
+		);
+
+		if (autoScroll) {
+			scrollToBottom();
+		}
+
+		const messages = createMessagesList(responseMessageId);
+		if (messages.length == 2 && messages.at(-1).content !== '' && selectedModels[0] === model.id) {
+			window.history.replaceState(history.state, '', `/c/${_chatId}`);
+
+			const title = await generateChatTitle(messages);
+			await setChatTitle(_chatId, title);
+
+			if ($settings?.autoTags ?? true) {
+				await setChatTags(messages);
+			}
+		}
+
+		return _response;
+	};
+
+	const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
+		let _response = null;
+
+		const responseMessage = history.messages[responseMessageId];
+		const userMessage = history.messages[responseMessage.parentId];
+
+		let files = JSON.parse(JSON.stringify(chatFiles));
+		if (model?.info?.meta?.knowledge ?? false) {
+			// Only initialize and add status if knowledge exists
+			responseMessage.statusHistory = [
+				{
+					action: 'knowledge_search',
+					description: $i18n.t(`Searching Knowledge for "{{searchQuery}}"`, {
+						searchQuery: userMessage.content
+					}),
+					done: false
+				}
+			];
+			files.push(
+				...model.info.meta.knowledge.map((item) => {
+					if (item?.collection_name) {
+						return {
+							id: item.collection_name,
+							name: item.name,
+							legacy: true
+						};
+					} else if (item?.collection_names) {
+						return {
+							name: item.name,
+							type: 'collection',
+							collection_names: item.collection_names,
+							legacy: true
+						};
+					} else {
+						return item;
+					}
+				})
+			);
+			history.messages[responseMessageId] = responseMessage;
+		}
+		files.push(
+			...(userMessage?.files ?? []).filter((item) =>
+				['doc', 'file', 'collection'].includes(item.type)
+			),
+			...(responseMessage?.files ?? []).filter((item) => ['web_search_results'].includes(item.type))
+		);
+		// Remove duplicates
+		files = files.filter(
+			(item, index, array) =>
+				array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
+		);
+
+		scrollToBottom();
+
+		eventTarget.dispatchEvent(
+			new CustomEvent('chat:start', {
+				detail: {
+					id: responseMessageId
+				}
+			})
+		);
+		await tick();
+
+		try {
+			const stream =
+				model?.info?.params?.stream_response ??
+				$settings?.params?.stream_response ??
+				params?.stream_response ??
+				true;
+
+			const [res, controller] = await generateOpenAIChatCompletion(
+				localStorage.token,
+				{
+					stream: stream,
+					model: model.id,
+					...(stream && (model.info?.meta?.capabilities?.usage ?? false)
+						? {
+								stream_options: {
+									include_usage: true
+								}
+							}
+						: {}),
+					messages: [
+						params?.system || $settings.system || (responseMessage?.userContext ?? null)
+							? {
+									role: 'system',
+									content: `${promptTemplate(
+										params?.system ?? $settings?.system ?? '',
+										$user.name,
+										$settings?.userLocation
+											? await getAndUpdateUserLocation(localStorage.token)
+											: undefined
+									)}${
+										(responseMessage?.userContext ?? null)
+											? `\n\nUser Context:\n${responseMessage?.userContext ?? ''}`
+											: ''
+									}`
+								}
+							: undefined,
+						...createMessagesList(responseMessageId)
+					]
+						.filter((message) => message?.content?.trim())
+						.map((message, idx, arr) => ({
+							role: message.role,
+							...((message.files?.filter((file) => file.type === 'image').length > 0 ?? false) &&
+							message.role === 'user'
+								? {
+										content: [
+											{
+												type: 'text',
+												text: message?.merged?.content ?? message.content
+											},
+											...message.files
+												.filter((file) => file.type === 'image')
+												.map((file) => ({
+													type: 'image_url',
+													image_url: {
+														url: file.url
+													}
+												}))
+										]
+									}
+								: {
+										content: message?.merged?.content ?? message.content
+									})
+						})),
+					seed: params?.seed ?? $settings?.params?.seed ?? undefined,
+					stop:
+						(params?.stop ?? $settings?.params?.stop ?? undefined)
+							? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map(
+									(str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
+								)
+							: undefined,
+					temperature: params?.temperature ?? $settings?.params?.temperature ?? undefined,
+					top_p: params?.top_p ?? $settings?.params?.top_p ?? undefined,
+					frequency_penalty:
+						params?.frequency_penalty ?? $settings?.params?.frequency_penalty ?? undefined,
+					max_tokens: params?.max_tokens ?? $settings?.params?.max_tokens ?? undefined,
+					tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
+					files: files.length > 0 ? files : undefined,
+					session_id: $socket?.id,
+					chat_id: $chatId,
+					id: responseMessageId
+				},
+				`${WEBUI_BASE_URL}/api`
+			);
+
+			// Wait until history/message have been updated
+			await tick();
+
+			scrollToBottom();
+
+			if (res && res.ok && res.body) {
+				if (!stream) {
+					const response = await res.json();
+					console.log(response);
+
+					responseMessage.content = response.choices[0].message.content;
+					responseMessage.info = { ...response.usage, openai: true };
+					responseMessage.done = true;
+				} else {
+					const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
+
+					for await (const update of textStream) {
+						const { value, done, citations, selectedModelId, error, usage } = update;
+						if (error) {
+							await handleOpenAIError(error, null, model, responseMessage);
+							break;
+						}
+						if (done || stopResponseFlag || _chatId !== $chatId) {
+							responseMessage.done = true;
+							history.messages[responseMessageId] = responseMessage;
+
+							if (stopResponseFlag) {
+								controller.abort('User: Stop Response');
+							}
+							_response = responseMessage.content;
+							break;
+						}
+
+						if (usage) {
+							responseMessage.info = { ...usage, openai: true, usage };
+						}
+
+						if (selectedModelId) {
+							responseMessage.selectedModelId = selectedModelId;
+							responseMessage.arena = true;
+							continue;
+						}
+
+						if (citations) {
+							responseMessage.citations = citations;
+							// Only remove status if it was initially set
+							if (model?.info?.meta?.knowledge ?? false) {
+								responseMessage.statusHistory = responseMessage.statusHistory.filter(
+									(status) => status.action !== 'knowledge_search'
+								);
+							}
+							continue;
+						}
+
+						if (responseMessage.content == '' && value == '\n') {
+							continue;
+						} else {
+							responseMessage.content += value;
+
+							if (navigator.vibrate && ($settings?.hapticFeedback ?? false)) {
+								navigator.vibrate(5);
+							}
+
+							const messageContentParts = getMessageContentParts(
+								responseMessage.content,
+								$config?.audio?.tts?.split_on ?? 'punctuation'
+							);
+							messageContentParts.pop();
+
+							// dispatch only last sentence and make sure it hasn't been dispatched before
+							if (
+								messageContentParts.length > 0 &&
+								messageContentParts[messageContentParts.length - 1] !== responseMessage.lastSentence
+							) {
+								responseMessage.lastSentence = messageContentParts[messageContentParts.length - 1];
+								eventTarget.dispatchEvent(
+									new CustomEvent('chat', {
+										detail: {
+											id: responseMessageId,
+											content: messageContentParts[messageContentParts.length - 1]
+										}
+									})
+								);
+							}
+
+							history.messages[responseMessageId] = responseMessage;
+						}
+
+						if (autoScroll) {
+							scrollToBottom();
+						}
+					}
+				}
+
+				if ($settings.notificationEnabled && !document.hasFocus()) {
+					const notification = new Notification(`${model.id}`, {
+						body: responseMessage.content,
+						icon: `${WEBUI_BASE_URL}/static/favicon.png`
+					});
+				}
+
+				if ($settings.responseAutoCopy) {
+					copyToClipboard(responseMessage.content);
+				}
+
+				if ($settings.responseAutoPlayback && !$showCallOverlay) {
+					await tick();
+
+					document.getElementById(`speak-button-${responseMessage.id}`)?.click();
+				}
+			} else {
+				await handleOpenAIError(null, res, model, responseMessage);
+			}
+		} catch (error) {
+			await handleOpenAIError(error, null, model, responseMessage);
+		}
+
+		await saveChatHandler(_chatId);
+
+		history.messages[responseMessageId] = responseMessage;
+
+		await chatCompletedHandler(
+			_chatId,
+			model.id,
+			responseMessageId,
+			createMessagesList(responseMessageId)
+		);
+
+		stopResponseFlag = false;
+		await tick();
+
+		let lastMessageContentPart =
+			getMessageContentParts(
+				responseMessage.content,
+				$config?.audio?.tts?.split_on ?? 'punctuation'
+			)?.at(-1) ?? '';
+		if (lastMessageContentPart) {
+			eventTarget.dispatchEvent(
+				new CustomEvent('chat', {
+					detail: { id: responseMessageId, content: lastMessageContentPart }
+				})
+			);
+		}
+
+		eventTarget.dispatchEvent(
+			new CustomEvent('chat:finish', {
+				detail: {
+					id: responseMessageId,
+					content: responseMessage.content
+				}
+			})
+		);
+
+		if (autoScroll) {
+			scrollToBottom();
+		}
+
+		const messages = createMessagesList(responseMessageId);
+		if (messages.length == 2 && selectedModels[0] === model.id) {
+			window.history.replaceState(history.state, '', `/c/${_chatId}`);
+
+			const title = await generateChatTitle(messages);
+			await setChatTitle(_chatId, title);
+
+			if ($settings?.autoTags ?? true) {
+				await setChatTags(messages);
+			}
+		}
+
+		return _response;
+	};
+
+	const handleOpenAIError = async (error, res: Response | null, model, responseMessage) => {
+		let errorMessage = '';
+		let innerError;
+
+		if (error) {
+			innerError = error;
+		} else if (res !== null) {
+			innerError = await res.json();
+		}
+		console.error(innerError);
+		if ('detail' in innerError) {
+			toast.error(innerError.detail);
+			errorMessage = innerError.detail;
+		} else if ('error' in innerError) {
+			if ('message' in innerError.error) {
+				toast.error(innerError.error.message);
+				errorMessage = innerError.error.message;
+			} else {
+				toast.error(innerError.error);
+				errorMessage = innerError.error;
+			}
+		} else if ('message' in innerError) {
+			toast.error(innerError.message);
+			errorMessage = innerError.message;
+		}
+
+		responseMessage.error = {
+			content:
+				$i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
+					provider: model.name ?? model.id
+				}) +
+				'\n' +
+				errorMessage
+		};
+		responseMessage.done = true;
+
+		if (responseMessage.statusHistory) {
+			responseMessage.statusHistory = responseMessage.statusHistory.filter(
+				(status) => status.action !== 'knowledge_search'
+			);
+		}
+
+		history.messages[responseMessage.id] = responseMessage;
+	};
+
+	const stopResponse = () => {
+		stopResponseFlag = true;
+		console.log('stopResponse');
+	};
+
+	const regenerateResponse = async (message) => {
+		console.log('regenerateResponse');
+
+		if (history.currentId) {
+			let userMessage = history.messages[message.parentId];
+			let userPrompt = userMessage.content;
+
+			if ((userMessage?.models ?? [...selectedModels]).length == 1) {
+				// If user message has only one model selected, sendPrompt automatically selects it for regeneration
+				await sendPrompt(userPrompt, userMessage.id);
+			} else {
+				// If there are multiple models selected, use the model of the response message for regeneration
+				// e.g. many model chat
+				await sendPrompt(userPrompt, userMessage.id, {
+					modelId: message.model,
+					modelIdx: message.modelIdx
+				});
+			}
+		}
+	};
+
+	const continueResponse = async () => {
+		console.log('continueResponse');
+		const _chatId = JSON.parse(JSON.stringify($chatId));
+
+		if (history.currentId && history.messages[history.currentId].done == true) {
+			const responseMessage = history.messages[history.currentId];
+			responseMessage.done = false;
+			await tick();
+
+			const model = $models.filter((m) => m.id === responseMessage.model).at(0);
+
+			if (model) {
+				if (model?.owned_by === 'openai') {
+					await sendPromptOpenAI(
+						model,
+						history.messages[responseMessage.parentId].content,
+						responseMessage.id,
+						_chatId
+					);
+				} else
+					await sendPromptOllama(
+						model,
+						history.messages[responseMessage.parentId].content,
+						responseMessage.id,
+						_chatId
+					);
+			}
+		} else {
+			toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
+		}
+	};
+
+	const mergeResponses = async (messageId, responses, _chatId) => {
+		console.log('mergeResponses', messageId, responses);
+		const message = history.messages[messageId];
+		const mergedResponse = {
+			status: true,
+			content: ''
+		};
+		message.merged = mergedResponse;
+		history.messages[messageId] = message;
+
+		try {
+			const [res, controller] = await generateMoACompletion(
+				localStorage.token,
+				message.model,
+				history.messages[message.parentId].content,
+				responses
+			);
+
+			if (res && res.ok && res.body) {
+				const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
+				for await (const update of textStream) {
+					const { value, done, citations, error, usage } = update;
+					if (error || done) {
+						break;
+					}
+
+					if (mergedResponse.content == '' && value == '\n') {
+						continue;
+					} else {
+						mergedResponse.content += value;
+						history.messages[messageId] = message;
+					}
+
+					if (autoScroll) {
+						scrollToBottom();
+					}
+				}
+
+				await saveChatHandler(_chatId);
+			} else {
+				console.error(res);
+			}
+		} catch (e) {
+			console.error(e);
+		}
+	};
+
+	const generateChatTitle = async (messages) => {
+		if ($settings?.title?.auto ?? true) {
+			const lastMessage = messages.at(-1);
+			const modelId = selectedModels[0];
+
+			const title = await generateTitle(localStorage.token, modelId, messages, $chatId).catch(
+				(error) => {
+					console.error(error);
+					return 'New Chat';
+				}
+			);
+
+			return title;
+		} else {
+			return 'New Chat';
+		}
+	};
+
+	const setChatTitle = async (_chatId, title) => {
+		if (_chatId === $chatId) {
+			chatTitle.set(title);
+		}
+
+		if (!$temporaryChatEnabled) {
+			chat = await updateChatById(localStorage.token, _chatId, { title: title });
+
+			currentChatPage.set(1);
+			await chats.set(await getChatList(localStorage.token, $currentChatPage));
+		}
+	};
+
+	const setChatTags = async (messages) => {
+		if (!$temporaryChatEnabled) {
+			const currentTags = await getTagsById(localStorage.token, $chatId);
+			if (currentTags.length > 0) {
+				const res = await deleteTagsById(localStorage.token, $chatId);
+				if (res) {
+					allTags.set(await getAllTags(localStorage.token));
+				}
+			}
+
+			const lastMessage = messages.at(-1);
+			const modelId = selectedModels[0];
+
+			let generatedTags = await generateTags(localStorage.token, modelId, messages, $chatId).catch(
+				(error) => {
+					console.error(error);
+					return [];
+				}
+			);
+
+			generatedTags = generatedTags.filter(
+				(tag) => !currentTags.find((t) => t.id === tag.replaceAll(' ', '_').toLowerCase())
+			);
+			console.log(generatedTags);
+
+			for (const tag of generatedTags) {
+				await addTagById(localStorage.token, $chatId, tag);
+			}
+
+			chat = await getChatById(localStorage.token, $chatId);
+			allTags.set(await getAllTags(localStorage.token));
+		}
+	};
+
+	const getWebSearchResults = async (
+		model: string,
+		parentId: string,
+		responseMessageId: string
+	) => {
+		const responseMessage = history.messages[responseMessageId];
+		const userMessage = history.messages[parentId];
+		const messages = createMessagesList(history.currentId);
+
+		responseMessage.statusHistory = [
+			{
+				done: false,
+				action: 'web_search',
+				description: $i18n.t('Generating search query')
+			}
+		];
+		history.messages[responseMessageId] = responseMessage;
+
+		const prompt = userMessage.content;
+		let searchQuery = await generateSearchQuery(
+			localStorage.token,
+			model,
+			messages.filter((message) => message?.content?.trim()),
+			prompt
+		).catch((error) => {
+			console.log(error);
+			return prompt;
+		});
+
+		if (!searchQuery || searchQuery == '') {
+			responseMessage.statusHistory.push({
+				done: true,
+				error: true,
+				action: 'web_search',
+				description: $i18n.t('No search query generated')
+			});
+			history.messages[responseMessageId] = responseMessage;
+			return;
+		}
+
+		responseMessage.statusHistory.push({
+			done: false,
+			action: 'web_search',
+			description: $i18n.t(`Searching "{{searchQuery}}"`, { searchQuery })
+		});
+		history.messages[responseMessageId] = responseMessage;
+
+		const results = await processWebSearch(localStorage.token, searchQuery).catch((error) => {
+			console.log(error);
+			toast.error(error);
+
+			return null;
+		});
+
+		if (results) {
+			responseMessage.statusHistory.push({
+				done: true,
+				action: 'web_search',
+				description: $i18n.t('Searched {{count}} sites', { count: results.filenames.length }),
+				query: searchQuery,
+				urls: results.filenames
+			});
+
+			if (responseMessage?.files ?? undefined === undefined) {
+				responseMessage.files = [];
+			}
+
+			responseMessage.files.push({
+				collection_name: results.collection_name,
+				name: searchQuery,
+				type: 'web_search_results',
+				urls: results.filenames
+			});
+			history.messages[responseMessageId] = responseMessage;
+		} else {
+			responseMessage.statusHistory.push({
+				done: true,
+				error: true,
+				action: 'web_search',
+				description: 'No search results found'
+			});
+			history.messages[responseMessageId] = responseMessage;
+		}
+	};
+
+	const initChatHandler = async () => {
+		if (!$temporaryChatEnabled) {
+			chat = await createNewChat(localStorage.token, {
+				id: $chatId,
+				title: $i18n.t('New Chat'),
+				models: selectedModels,
+				system: $settings.system ?? undefined,
+				params: params,
+				history: history,
+				messages: createMessagesList(history.currentId),
+				tags: [],
+				timestamp: Date.now()
+			});
+
+			currentChatPage.set(1);
+			await chats.set(await getChatList(localStorage.token, $currentChatPage));
+			await chatId.set(chat.id);
+		} else {
+			await chatId.set('local');
+		}
+		await tick();
+	};
+
+	const saveChatHandler = async (_chatId) => {
+		if ($chatId == _chatId) {
+			if (!$temporaryChatEnabled) {
+				chat = await updateChatById(localStorage.token, _chatId, {
+					models: selectedModels,
+					history: history,
+					messages: createMessagesList(history.currentId),
+					params: params,
+					files: chatFiles
+				});
+
+				currentChatPage.set(1);
+				await chats.set(await getChatList(localStorage.token, $currentChatPage));
+			}
+		}
+	};
+</script>
+
+<svelte:head>
+	<title>
+		{$chatTitle
+			? `${$chatTitle.length > 30 ? `${$chatTitle.slice(0, 30)}...` : $chatTitle} | ${$WEBUI_NAME}`
+			: `${$WEBUI_NAME}`}
+	</title>
+</svelte:head>
+
+<audio id="audioElement" src="" style="display: none;" />
+
+<EventConfirmDialog
+	bind:show={showEventConfirmation}
+	title={eventConfirmationTitle}
+	message={eventConfirmationMessage}
+	input={eventConfirmationInput}
+	inputPlaceholder={eventConfirmationInputPlaceholder}
+	inputValue={eventConfirmationInputValue}
+	on:confirm={(e) => {
+		if (e.detail) {
+			eventCallback(e.detail);
+		} else {
+			eventCallback(true);
+		}
+	}}
+	on:cancel={() => {
+		eventCallback(false);
+	}}
+/>
+
+{#if !chatIdProp || (loaded && chatIdProp)}
+	<div
+		class="h-screen max-h-[100dvh] {$showSidebar
+			? 'md:max-w-[calc(100%-260px)]'
+			: ''} w-full max-w-full flex flex-col"
+		id="chat-container"
+	>
+		{#if $settings?.backgroundImageUrl ?? null}
+			<div
+				class="absolute {$showSidebar
+					? 'md:max-w-[calc(100%-260px)] md:translate-x-[260px]'
+					: ''} top-0 left-0 w-full h-full bg-cover bg-center bg-no-repeat"
+				style="background-image: url({$settings.backgroundImageUrl})  "
+			/>
+
+			<div
+				class="absolute top-0 left-0 w-full h-full bg-gradient-to-t from-white to-white/85 dark:from-gray-900 dark:to-[#171717]/90 z-0"
+			/>
+		{/if}
+
+		<Navbar
+			bind:this={navbarElement}
+			chat={{
+				id: $chatId,
+				chat: {
+					title: $chatTitle,
+					models: selectedModels,
+					system: $settings.system ?? undefined,
+					params: params,
+					history: history,
+					timestamp: Date.now()
+				}
+			}}
+			title={$chatTitle}
+			bind:selectedModels
+			shareEnabled={!!history.currentId}
+			{initNewChat}
+		/>
+
+		<PaneGroup direction="horizontal" class="w-full h-full">
+			<Pane defaultSize={50} class="h-full flex w-full relative">
+				{#if $banners.length > 0 && !history.currentId && !$chatId && selectedModels.length <= 1}
+					<div class="absolute top-12 left-0 right-0 w-full z-30">
+						<div class=" flex flex-col gap-1 w-full">
+							{#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner}
+								<Banner
+									{banner}
+									on:dismiss={(e) => {
+										const bannerId = e.detail;
+
+										localStorage.setItem(
+											'dismissedBannerIds',
+											JSON.stringify(
+												[
+													bannerId,
+													...JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]')
+												].filter((id) => $banners.find((b) => b.id === id))
+											)
+										);
+									}}
+								/>
+							{/each}
+						</div>
+					</div>
+				{/if}
+
+				<div class="flex flex-col flex-auto z-10 w-full">
+					{#if $settings?.landingPageMode === 'chat' || createMessagesList(history.currentId).length > 0}
+						<div
+							class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full z-10 scrollbar-hidden"
+							id="messages-container"
+							bind:this={messagesContainerElement}
+							on:scroll={(e) => {
+								autoScroll =
+									messagesContainerElement.scrollHeight - messagesContainerElement.scrollTop <=
+									messagesContainerElement.clientHeight + 5;
+							}}
+						>
+							<div class=" h-full w-full flex flex-col">
+								<Messages
+									chatId={$chatId}
+									bind:history
+									bind:autoScroll
+									bind:prompt
+									{selectedModels}
+									{sendPrompt}
+									{showMessage}
+									{continueResponse}
+									{regenerateResponse}
+									{mergeResponses}
+									{chatActionHandler}
+									bottomPadding={files.length > 0}
+									on:submit={async (e) => {
+										if (e.detail) {
+											// New user message
+											let userPrompt = e.detail.prompt;
+											let userMessageId = uuidv4();
+
+											let userMessage = {
+												id: userMessageId,
+												parentId: e.detail.parentId,
+												childrenIds: [],
+												role: 'user',
+												content: userPrompt,
+												models: selectedModels
+											};
+
+											let messageParentId = e.detail.parentId;
+
+											if (messageParentId !== null) {
+												history.messages[messageParentId].childrenIds = [
+													...history.messages[messageParentId].childrenIds,
+													userMessageId
+												];
+											}
+
+											history.messages[userMessageId] = userMessage;
+											history.currentId = userMessageId;
+
+											await tick();
+											await sendPrompt(userPrompt, userMessageId);
+										}
+									}}
+								/>
+							</div>
+						</div>
+
+						<div class=" pb-[1.6rem]">
+							<MessageInput
+								{history}
+								{selectedModels}
+								bind:files
+								bind:prompt
+								bind:autoScroll
+								bind:selectedToolIds
+								bind:webSearchEnabled
+								bind:atSelectedModel
+								availableToolIds={selectedModelIds.reduce((a, e, i, arr) => {
+									const model = $models.find((m) => m.id === e);
+									if (model?.info?.meta?.toolIds ?? false) {
+										return [...new Set([...a, ...model.info.meta.toolIds])];
+									}
+									return a;
+								}, [])}
+								transparentBackground={$settings?.backgroundImageUrl ?? false}
+								{stopResponse}
+								{createMessagePair}
+								on:upload={async (e) => {
+									const { type, data } = e.detail;
+
+									if (type === 'web') {
+										await uploadWeb(data);
+									} else if (type === 'youtube') {
+										await uploadYoutubeTranscription(data);
+									}
+								}}
+								on:submit={async (e) => {
+									if (e.detail) {
+										await tick();
+										submitPrompt(e.detail.replaceAll('\n\n', '\n'));
+									}
+								}}
+							/>
+
+							<div
+								class="absolute bottom-1.5 text-xs text-gray-500 text-center line-clamp-1 right-0 left-0"
+							>
+								{$i18n.t('LLMs can make mistakes. Verify important information.')}
+							</div>
+						</div>
+					{:else}
+						<div class="overflow-auto w-full h-full flex items-center">
+							<Placeholder
+								{history}
+								{selectedModels}
+								bind:files
+								bind:prompt
+								bind:autoScroll
+								bind:selectedToolIds
+								bind:webSearchEnabled
+								bind:atSelectedModel
+								availableToolIds={selectedModelIds.reduce((a, e, i, arr) => {
+									const model = $models.find((m) => m.id === e);
+									if (model?.info?.meta?.toolIds ?? false) {
+										return [...new Set([...a, ...model.info.meta.toolIds])];
+									}
+									return a;
+								}, [])}
+								transparentBackground={$settings?.backgroundImageUrl ?? false}
+								{stopResponse}
+								{createMessagePair}
+								on:upload={async (e) => {
+									const { type, data } = e.detail;
+
+									if (type === 'web') {
+										await uploadWeb(data);
+									} else if (type === 'youtube') {
+										await uploadYoutubeTranscription(data);
+									}
+								}}
+								on:submit={async (e) => {
+									if (e.detail) {
+										await tick();
+										submitPrompt(e.detail.replaceAll('\n\n', '\n'));
+									}
+								}}
+							/>
+						</div>
+					{/if}
+				</div>
+			</Pane>
+
+			<ChatControls
+				bind:this={controlPaneComponent}
+				bind:history
+				bind:chatFiles
+				bind:params
+				bind:files
+				bind:pane={controlPane}
+				chatId={$chatId}
+				modelId={selectedModelIds?.at(0) ?? null}
+				models={selectedModelIds.reduce((a, e, i, arr) => {
+					const model = $models.find((m) => m.id === e);
+					if (model) {
+						return [...a, model];
+					}
+					return a;
+				}, [])}
+				{submitPrompt}
+				{stopResponse}
+				{showMessage}
+				{eventTarget}
+			/>
+		</PaneGroup>
+	</div>
+{/if}
diff --git a/src/lib/components/chat/ChatControls.svelte b/src/lib/components/chat/ChatControls.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..5b0135105a485dc2a49244d6feb2314022086057
--- /dev/null
+++ b/src/lib/components/chat/ChatControls.svelte
@@ -0,0 +1,282 @@
+<script lang="ts">
+	import { SvelteFlowProvider } from '@xyflow/svelte';
+	import { slide } from 'svelte/transition';
+	import { Pane, PaneResizer } from 'paneforge';
+
+	import { onDestroy, onMount, tick } from 'svelte';
+	import { mobile, showControls, showCallOverlay, showOverview, showArtifacts } from '$lib/stores';
+
+	import Modal from '../common/Modal.svelte';
+	import Controls from './Controls/Controls.svelte';
+	import CallOverlay from './MessageInput/CallOverlay.svelte';
+	import Drawer from '../common/Drawer.svelte';
+	import Overview from './Overview.svelte';
+	import EllipsisVertical from '../icons/EllipsisVertical.svelte';
+	import Artifacts from './Artifacts.svelte';
+	import { min } from '@floating-ui/utils';
+
+	export let history;
+	export let models = [];
+
+	export let chatId = null;
+
+	export let chatFiles = [];
+	export let params = {};
+
+	export let eventTarget: EventTarget;
+	export let submitPrompt: Function;
+	export let stopResponse: Function;
+	export let showMessage: Function;
+	export let files;
+	export let modelId;
+
+	export let pane;
+
+	let mediaQuery;
+	let largeScreen = false;
+	let dragged = false;
+
+	let minSize = 0;
+
+	export const openPane = () => {
+		if (parseInt(localStorage?.chatControlsSize)) {
+			pane.resize(parseInt(localStorage?.chatControlsSize));
+		} else {
+			pane.resize(minSize);
+		}
+	};
+
+	const handleMediaQuery = async (e) => {
+		if (e.matches) {
+			largeScreen = true;
+
+			if ($showCallOverlay) {
+				showCallOverlay.set(false);
+				await tick();
+				showCallOverlay.set(true);
+			}
+		} else {
+			largeScreen = false;
+
+			if ($showCallOverlay) {
+				showCallOverlay.set(false);
+				await tick();
+				showCallOverlay.set(true);
+			}
+			pane = null;
+		}
+	};
+
+	const onMouseDown = (event) => {
+		dragged = true;
+	};
+
+	const onMouseUp = (event) => {
+		dragged = false;
+	};
+
+	onMount(() => {
+		// listen to resize 1024px
+		mediaQuery = window.matchMedia('(min-width: 1024px)');
+
+		mediaQuery.addEventListener('change', handleMediaQuery);
+		handleMediaQuery(mediaQuery);
+
+		// Select the container element you want to observe
+		const container = document.getElementById('chat-container');
+
+		// initialize the minSize based on the container width
+		minSize = Math.floor((350 / container.clientWidth) * 100);
+
+		// Create a new ResizeObserver instance
+		const resizeObserver = new ResizeObserver((entries) => {
+			for (let entry of entries) {
+				const width = entry.contentRect.width;
+				// calculate the percentage of 200px
+				const percentage = (350 / width) * 100;
+				// set the minSize to the percentage, must be an integer
+				minSize = Math.floor(percentage);
+
+				if ($showControls) {
+					if (pane && pane.isExpanded() && pane.getSize() < minSize) {
+						pane.resize(minSize);
+					}
+				}
+			}
+		});
+
+		// Start observing the container's size changes
+		resizeObserver.observe(container);
+
+		document.addEventListener('mousedown', onMouseDown);
+		document.addEventListener('mouseup', onMouseUp);
+	});
+
+	onDestroy(() => {
+		showControls.set(false);
+
+		mediaQuery.removeEventListener('change', handleMediaQuery);
+		document.removeEventListener('mousedown', onMouseDown);
+		document.removeEventListener('mouseup', onMouseUp);
+	});
+
+	const closeHandler = () => {
+		showControls.set(false);
+		showOverview.set(false);
+		showArtifacts.set(false);
+
+		if ($showCallOverlay) {
+			showCallOverlay.set(false);
+		}
+	};
+
+	$: if (!chatId) {
+		closeHandler();
+	}
+</script>
+
+<SvelteFlowProvider>
+	{#if !largeScreen}
+		{#if $showControls}
+			<Drawer
+				show={$showControls}
+				on:close={() => {
+					showControls.set(false);
+				}}
+			>
+				<div
+					class=" {$showCallOverlay || $showOverview || $showArtifacts
+						? ' h-screen  w-screen'
+						: 'px-6 py-4'} h-full"
+				>
+					{#if $showCallOverlay}
+						<div
+							class=" h-full max-h-[100dvh] bg-white text-gray-700 dark:bg-black dark:text-gray-300 flex justify-center"
+						>
+							<CallOverlay
+								bind:files
+								{submitPrompt}
+								{stopResponse}
+								{modelId}
+								{chatId}
+								{eventTarget}
+								on:close={() => {
+									showControls.set(false);
+								}}
+							/>
+						</div>
+					{:else if $showArtifacts}
+						<Artifacts {history} />
+					{:else if $showOverview}
+						<Overview
+							{history}
+							on:nodeclick={(e) => {
+								showMessage(e.detail.node.data.message);
+							}}
+							on:close={() => {
+								showControls.set(false);
+							}}
+						/>
+					{:else}
+						<Controls
+							on:close={() => {
+								showControls.set(false);
+							}}
+							{models}
+							bind:chatFiles
+							bind:params
+						/>
+					{/if}
+				</div>
+			</Drawer>
+		{/if}
+	{:else}
+		<!-- if $showControls -->
+
+		{#if $showControls}
+			<PaneResizer class="relative flex w-2 items-center justify-center bg-background group">
+				<div class="z-10 flex h-7 w-5 items-center justify-center rounded-sm">
+					<EllipsisVertical className="size-4 invisible group-hover:visible" />
+				</div>
+			</PaneResizer>
+		{/if}
+
+		<Pane
+			bind:pane
+			defaultSize={0}
+			onResize={(size) => {
+				console.log('size', size, minSize);
+
+				if ($showControls && pane.isExpanded()) {
+					if (size < minSize) {
+						pane.resize(minSize);
+					}
+
+					if (size < minSize) {
+						localStorage.chatControlsSize = 0;
+					} else {
+						localStorage.chatControlsSize = size;
+					}
+				}
+			}}
+			onCollapse={() => {
+				showControls.set(false);
+			}}
+			collapsible={true}
+			class="pt-8"
+		>
+			{#if $showControls}
+				<div class="pr-4 pb-8 flex max-h-full min-h-full">
+					<div
+						class="w-full {($showOverview || $showArtifacts) && !$showCallOverlay
+							? ' '
+							: 'px-4 py-4 bg-white dark:shadow-lg dark:bg-gray-850  border border-gray-50 dark:border-gray-850'}  rounded-xl z-40 pointer-events-auto overflow-y-auto scrollbar-hidden"
+					>
+						{#if $showCallOverlay}
+							<div class="w-full h-full flex justify-center">
+								<CallOverlay
+									bind:files
+									{submitPrompt}
+									{stopResponse}
+									{modelId}
+									{chatId}
+									{eventTarget}
+									on:close={() => {
+										showControls.set(false);
+									}}
+								/>
+							</div>
+						{:else if $showArtifacts}
+							<Artifacts {history} overlay={dragged} />
+						{:else if $showOverview}
+							<Overview
+								{history}
+								on:nodeclick={(e) => {
+									if (e.detail.node.data.message.favorite) {
+										history.messages[e.detail.node.data.message.id].favorite = true;
+									} else {
+										history.messages[e.detail.node.data.message.id].favorite = null;
+									}
+
+									showMessage(e.detail.node.data.message);
+								}}
+								on:close={() => {
+									showControls.set(false);
+								}}
+							/>
+						{:else}
+							<Controls
+								on:close={() => {
+									showControls.set(false);
+								}}
+								{models}
+								bind:chatFiles
+								bind:params
+							/>
+						{/if}
+					</div>
+				</div>
+			{/if}
+		</Pane>
+	{/if}
+</SvelteFlowProvider>
diff --git a/src/lib/components/chat/ChatPlaceholder.svelte b/src/lib/components/chat/ChatPlaceholder.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e30213ebb89d0f42267cbbefdf674fbcc0eccf61
--- /dev/null
+++ b/src/lib/components/chat/ChatPlaceholder.svelte
@@ -0,0 +1,138 @@
+<script lang="ts">
+	import { WEBUI_BASE_URL } from '$lib/constants';
+	import { marked } from 'marked';
+
+	import { config, user, models as _models, temporaryChatEnabled } from '$lib/stores';
+	import { onMount, getContext } from 'svelte';
+
+	import { blur, fade } from 'svelte/transition';
+
+	import Suggestions from './Suggestions.svelte';
+	import { sanitizeResponseContent } from '$lib/utils';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import EyeSlash from '$lib/components/icons/EyeSlash.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let modelIds = [];
+	export let models = [];
+
+	export let submitPrompt;
+
+	let mounted = false;
+	let selectedModelIdx = 0;
+
+	$: if (modelIds.length > 0) {
+		selectedModelIdx = models.length - 1;
+	}
+
+	$: models = modelIds.map((id) => $_models.find((m) => m.id === id));
+
+	onMount(() => {
+		mounted = true;
+	});
+</script>
+
+{#key mounted}
+	<div class="m-auto w-full max-w-6xl px-8 lg:px-20">
+		<div class="flex justify-start">
+			<div class="flex -space-x-4 mb-0.5" in:fade={{ duration: 200 }}>
+				{#each models as model, modelIdx}
+					<button
+						on:click={() => {
+							selectedModelIdx = modelIdx;
+						}}
+					>
+						<Tooltip
+							content={marked.parse(
+								sanitizeResponseContent(models[selectedModelIdx]?.info?.meta?.description ?? '')
+							)}
+							placement="right"
+						>
+							<img
+								crossorigin="anonymous"
+								src={model?.info?.meta?.profile_image_url ??
+									($i18n.language === 'dg-DG'
+										? `/doge.png`
+										: `${WEBUI_BASE_URL}/static/favicon.png`)}
+								class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
+								alt="logo"
+								draggable="false"
+							/>
+						</Tooltip>
+					</button>
+				{/each}
+			</div>
+		</div>
+
+		{#if $temporaryChatEnabled}
+			<Tooltip
+				content="This chat won't appear in history and your messages will not be saved."
+				className="w-fit"
+				placement="top-start"
+			>
+				<div class="flex items-center gap-2 text-gray-500 font-medium text-lg my-2 w-fit">
+					<EyeSlash strokeWidth="2.5" className="size-5" /> Temporary Chat
+				</div>
+			</Tooltip>
+		{/if}
+
+		<div
+			class=" mt-2 mb-4 text-3xl text-gray-800 dark:text-gray-100 font-medium text-left flex items-center gap-4 font-primary"
+		>
+			<div>
+				<div class=" capitalize line-clamp-1" in:fade={{ duration: 200 }}>
+					{#if models[selectedModelIdx]?.name}
+						{models[selectedModelIdx]?.name}
+					{:else}
+						{$i18n.t('Hello, {{name}}', { name: $user.name })}
+					{/if}
+				</div>
+
+				<div in:fade={{ duration: 200, delay: 200 }}>
+					{#if models[selectedModelIdx]?.info?.meta?.description ?? null}
+						<div
+							class="mt-0.5 text-base font-normal text-gray-500 dark:text-gray-400 line-clamp-3 markdown"
+						>
+							{@html marked.parse(
+								sanitizeResponseContent(models[selectedModelIdx]?.info?.meta?.description)
+							)}
+						</div>
+						{#if models[selectedModelIdx]?.info?.meta?.user}
+							<div class="mt-0.5 text-sm font-normal text-gray-400 dark:text-gray-500">
+								By
+								{#if models[selectedModelIdx]?.info?.meta?.user.community}
+									<a
+										href="https://openwebui.com/m/{models[selectedModelIdx]?.info?.meta?.user
+											.username}"
+										>{models[selectedModelIdx]?.info?.meta?.user.name
+											? models[selectedModelIdx]?.info?.meta?.user.name
+											: `@${models[selectedModelIdx]?.info?.meta?.user.username}`}</a
+									>
+								{:else}
+									{models[selectedModelIdx]?.info?.meta?.user.name}
+								{/if}
+							</div>
+						{/if}
+					{:else}
+						<div class=" font-medium text-gray-400 dark:text-gray-500 line-clamp-1 font-p">
+							{$i18n.t('How can I help you today?')}
+						</div>
+					{/if}
+				</div>
+			</div>
+		</div>
+
+		<div class=" w-full font-primary" in:fade={{ duration: 200, delay: 300 }}>
+			<Suggestions
+				className="grid grid-cols-2"
+				suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
+					$config?.default_prompt_suggestions ??
+					[]}
+				on:select={(e) => {
+					submitPrompt(e.detail);
+				}}
+			/>
+		</div>
+	</div>
+{/key}
diff --git a/src/lib/components/chat/Controls/Controls.svelte b/src/lib/components/chat/Controls/Controls.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e2b166fb34802b4c4e7295afbc1ed73c885ccbfe
--- /dev/null
+++ b/src/lib/components/chat/Controls/Controls.svelte
@@ -0,0 +1,91 @@
+<script lang="ts">
+	import { createEventDispatcher, getContext } from 'svelte';
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	import XMark from '$lib/components/icons/XMark.svelte';
+	import AdvancedParams from '../Settings/Advanced/AdvancedParams.svelte';
+	import Valves from '$lib/components/chat/Controls/Valves.svelte';
+	import FileItem from '$lib/components/common/FileItem.svelte';
+	import Collapsible from '$lib/components/common/Collapsible.svelte';
+
+	import { user } from '$lib/stores';
+	export let models = [];
+	export let chatFiles = [];
+	export let params = {};
+</script>
+
+<div class=" dark:text-white">
+	<div class=" flex items-center justify-between dark:text-gray-100 mb-2">
+		<div class=" text-lg font-medium self-center font-primary">{$i18n.t('Chat Controls')}</div>
+		<button
+			class="self-center"
+			on:click={() => {
+				dispatch('close');
+			}}
+		>
+			<XMark className="size-3.5" />
+		</button>
+	</div>
+
+	<div class=" dark:text-gray-200 text-sm font-primary py-0.5 px-0.5">
+		{#if chatFiles.length > 0}
+			<Collapsible title={$i18n.t('Files')} open={true} buttonClassName="w-full">
+				<div class="flex flex-col gap-1 mt-1.5" slot="content">
+					{#each chatFiles as file, fileIdx}
+						<FileItem
+							className="w-full"
+							item={file}
+							edit={true}
+							url={file?.url ? file.url : null}
+							name={file.name}
+							type={file.type}
+							size={file?.size}
+							dismissible={true}
+							on:dismiss={() => {
+								// Remove the file from the chatFiles array
+
+								chatFiles.splice(fileIdx, 1);
+								chatFiles = chatFiles;
+							}}
+							on:click={() => {
+								console.log(file);
+							}}
+						/>
+					{/each}
+				</div>
+			</Collapsible>
+
+			<hr class="my-2 border-gray-50 dark:border-gray-700/10" />
+		{/if}
+
+		<Collapsible title={$i18n.t('Valves')} buttonClassName="w-full">
+			<div class="text-sm" slot="content">
+				<Valves />
+			</div>
+		</Collapsible>
+
+		<hr class="my-2 border-gray-50 dark:border-gray-700/10" />
+
+		<Collapsible title={$i18n.t('System Prompt')} open={true} buttonClassName="w-full">
+			<div class="" slot="content">
+				<textarea
+					bind:value={params.system}
+					class="w-full text-xs py-1.5 bg-transparent outline-none resize-none"
+					rows="4"
+					placeholder={$i18n.t('Enter system prompt')}
+				/>
+			</div>
+		</Collapsible>
+
+		<hr class="my-2 border-gray-50 dark:border-gray-700/10" />
+
+		<Collapsible title={$i18n.t('Advanced Params')} open={true} buttonClassName="w-full">
+			<div class="text-sm mt-1.5" slot="content">
+				<div>
+					<AdvancedParams admin={$user?.role === 'admin'} bind:params />
+				</div>
+			</div>
+		</Collapsible>
+	</div>
+</div>
diff --git a/src/lib/components/chat/Controls/Valves.svelte b/src/lib/components/chat/Controls/Valves.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..8cf7ac39bd00f52c47ef7cd293bdfab04de3b8bc
--- /dev/null
+++ b/src/lib/components/chat/Controls/Valves.svelte
@@ -0,0 +1,188 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+
+	import { config, functions, models, settings, tools, user } from '$lib/stores';
+	import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
+
+	import {
+		getUserValvesSpecById as getToolUserValvesSpecById,
+		getUserValvesById as getToolUserValvesById,
+		updateUserValvesById as updateToolUserValvesById
+	} from '$lib/apis/tools';
+	import {
+		getUserValvesSpecById as getFunctionUserValvesSpecById,
+		getUserValvesById as getFunctionUserValvesById,
+		updateUserValvesById as updateFunctionUserValvesById
+	} from '$lib/apis/functions';
+
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import Valves from '$lib/components/common/Valves.svelte';
+
+	const dispatch = createEventDispatcher();
+
+	const i18n = getContext('i18n');
+
+	let tab = 'tools';
+	let selectedId = '';
+
+	let loading = false;
+
+	let valvesSpec = null;
+	let valves = {};
+
+	let debounceTimer;
+
+	const debounceSubmitHandler = async () => {
+		if (debounceTimer) {
+			clearTimeout(debounceTimer);
+		}
+
+		// Set a new timer
+		debounceTimer = setTimeout(() => {
+			submitHandler();
+		}, 500); // 0.5 second debounce
+	};
+
+	const getUserValves = async () => {
+		loading = true;
+		if (tab === 'tools') {
+			valves = await getToolUserValvesById(localStorage.token, selectedId);
+			valvesSpec = await getToolUserValvesSpecById(localStorage.token, selectedId);
+		} else if (tab === 'functions') {
+			valves = await getFunctionUserValvesById(localStorage.token, selectedId);
+			valvesSpec = await getFunctionUserValvesSpecById(localStorage.token, selectedId);
+		}
+
+		if (valvesSpec) {
+			// Convert array to string
+			for (const property in valvesSpec.properties) {
+				if (valvesSpec.properties[property]?.type === 'array') {
+					valves[property] = (valves[property] ?? []).join(',');
+				}
+			}
+		}
+
+		loading = false;
+	};
+
+	const submitHandler = async () => {
+		if (valvesSpec) {
+			// Convert string to array
+			for (const property in valvesSpec.properties) {
+				if (valvesSpec.properties[property]?.type === 'array') {
+					valves[property] = (valves[property] ?? '').split(',').map((v) => v.trim());
+				}
+			}
+
+			if (tab === 'tools') {
+				const res = await updateToolUserValvesById(localStorage.token, selectedId, valves).catch(
+					(error) => {
+						toast.error(error);
+						return null;
+					}
+				);
+
+				if (res) {
+					toast.success($i18n.t('Valves updated'));
+					valves = res;
+				}
+			} else if (tab === 'functions') {
+				const res = await updateFunctionUserValvesById(
+					localStorage.token,
+					selectedId,
+					valves
+				).catch((error) => {
+					toast.error(error);
+					return null;
+				});
+
+				if (res) {
+					toast.success($i18n.t('Valves updated'));
+					valves = res;
+				}
+			}
+		}
+	};
+
+	$: if (tab) {
+		selectedId = '';
+	}
+
+	$: if (selectedId) {
+		getUserValves();
+	}
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={() => {
+		submitHandler();
+		dispatch('save');
+	}}
+>
+	<div class="flex flex-col">
+		<div class="space-y-1">
+			<div class="flex gap-2">
+				<div class="flex-1">
+					<select
+						class="  w-full rounded text-xs py-2 px-1 bg-transparent outline-none"
+						bind:value={tab}
+						placeholder="Select"
+					>
+						<option value="tools" class="bg-gray-100 dark:bg-gray-800">{$i18n.t('Tools')}</option>
+						<option value="functions" class="bg-gray-100 dark:bg-gray-800"
+							>{$i18n.t('Functions')}</option
+						>
+					</select>
+				</div>
+
+				<div class="flex-1">
+					<select
+						class="w-full rounded py-2 px-1 text-xs bg-transparent outline-none"
+						bind:value={selectedId}
+						on:change={async () => {
+							await tick();
+						}}
+					>
+						{#if tab === 'tools'}
+							<option value="" selected disabled class="bg-gray-100 dark:bg-gray-800"
+								>{$i18n.t('Select a tool')}</option
+							>
+
+							{#each $tools as tool, toolIdx}
+								<option value={tool.id} class="bg-gray-100 dark:bg-gray-800">{tool.name}</option>
+							{/each}
+						{:else if tab === 'functions'}
+							<option value="" selected disabled class="bg-gray-100 dark:bg-gray-800"
+								>{$i18n.t('Select a function')}</option
+							>
+
+							{#each $functions as func, funcIdx}
+								<option value={func.id} class="bg-gray-100 dark:bg-gray-800">{func.name}</option>
+							{/each}
+						{/if}
+					</select>
+				</div>
+			</div>
+		</div>
+
+		{#if selectedId}
+			<hr class="dark:border-gray-800 my-1 w-full" />
+
+			<div class="my-2 text-xs">
+				{#if !loading}
+					<Valves
+						{valvesSpec}
+						bind:valves
+						on:change={() => {
+							debounceSubmitHandler();
+						}}
+					/>
+				{:else}
+					<Spinner className="size-5" />
+				{/if}
+			</div>
+		{/if}
+	</div>
+</form>
diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6dd6ff258f30e260cee79b15cd3c01fb813916a5
--- /dev/null
+++ b/src/lib/components/chat/MessageInput.svelte
@@ -0,0 +1,1025 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { v4 as uuidv4 } from 'uuid';
+
+	import { onMount, tick, getContext, createEventDispatcher, onDestroy } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	import {
+		type Model,
+		mobile,
+		settings,
+		showSidebar,
+		models,
+		config,
+		showCallOverlay,
+		tools,
+		user as _user,
+		showControls
+	} from '$lib/stores';
+	import { blobToFile, findWordIndices } from '$lib/utils';
+	import { transcribeAudio } from '$lib/apis/audio';
+	import { uploadFile } from '$lib/apis/files';
+
+	import { WEBUI_BASE_URL, WEBUI_API_BASE_URL } from '$lib/constants';
+
+	import Tooltip from '../common/Tooltip.svelte';
+	import InputMenu from './MessageInput/InputMenu.svelte';
+	import Headphone from '../icons/Headphone.svelte';
+	import VoiceRecording from './MessageInput/VoiceRecording.svelte';
+	import FileItem from '../common/FileItem.svelte';
+	import FilesOverlay from './MessageInput/FilesOverlay.svelte';
+	import Commands from './MessageInput/Commands.svelte';
+	import XMark from '../icons/XMark.svelte';
+	import RichTextInput from '../common/RichTextInput.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let transparentBackground = false;
+
+	export let createMessagePair: Function;
+	export let stopResponse: Function;
+
+	export let autoScroll = false;
+
+	export let atSelectedModel: Model | undefined;
+	export let selectedModels: [''];
+
+	export let history;
+
+	export let prompt = '';
+	export let files = [];
+	export let availableToolIds = [];
+	export let selectedToolIds = [];
+	export let webSearchEnabled = false;
+
+	let recording = false;
+
+	let chatInputContainerElement;
+	let chatInputElement;
+
+	let filesInputElement;
+	let commandsElement;
+
+	let inputFiles;
+	let dragged = false;
+
+	let user = null;
+	export let placeholder = '';
+
+	let visionCapableModels = [];
+	$: visionCapableModels = [...(atSelectedModel ? [atSelectedModel] : selectedModels)].filter(
+		(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.vision ?? true
+	);
+
+	$: if (prompt) {
+		if (chatInputContainerElement) {
+			chatInputContainerElement.style.height = '';
+			chatInputContainerElement.style.height =
+				Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
+		}
+	}
+
+	const scrollToBottom = () => {
+		const element = document.getElementById('messages-container');
+		element.scrollTo({
+			top: element.scrollHeight,
+			behavior: 'smooth'
+		});
+	};
+
+	const uploadFileHandler = async (file) => {
+		console.log(file);
+
+		const tempItemId = uuidv4();
+		const fileItem = {
+			type: 'file',
+			file: '',
+			id: null,
+			url: '',
+			name: file.name,
+			collection_name: '',
+			status: 'uploading',
+			size: file.size,
+			error: '',
+			itemId: tempItemId
+		};
+
+		if (fileItem.size == 0) {
+			toast.error($i18n.t('You cannot upload an empty file.'));
+			return null;
+		}
+
+		files = [...files, fileItem];
+		// Check if the file is an audio file and transcribe/convert it to text file
+		if (['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/x-m4a'].includes(file['type'])) {
+			const res = await transcribeAudio(localStorage.token, file).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+
+			if (res) {
+				console.log(res);
+				const blob = new Blob([res.text], { type: 'text/plain' });
+				file = blobToFile(blob, `${file.name}.txt`);
+
+				fileItem.name = file.name;
+				fileItem.size = file.size;
+			}
+		}
+
+		try {
+			// During the file upload, file content is automatically extracted.
+			const uploadedFile = await uploadFile(localStorage.token, file);
+
+			if (uploadedFile) {
+				if (uploadedFile.error) {
+					toast.warning(uploadedFile.error);
+				}
+
+				fileItem.status = 'uploaded';
+				fileItem.file = uploadedFile;
+				fileItem.id = uploadedFile.id;
+				fileItem.collection_name = uploadedFile?.meta?.collection_name;
+				fileItem.url = `${WEBUI_API_BASE_URL}/files/${uploadedFile.id}`;
+
+				files = files;
+			} else {
+				files = files.filter((item) => item?.itemId !== tempItemId);
+			}
+		} catch (e) {
+			toast.error(e);
+			files = files.filter((item) => item?.itemId !== tempItemId);
+		}
+	};
+
+	const inputFilesHandler = async (inputFiles) => {
+		inputFiles.forEach((file) => {
+			console.log(file, file.name.split('.').at(-1));
+
+			if (
+				($config?.file?.max_size ?? null) !== null &&
+				file.size > ($config?.file?.max_size ?? 0) * 1024 * 1024
+			) {
+				toast.error(
+					$i18n.t(`File size should not exceed {{maxSize}} MB.`, {
+						maxSize: $config?.file?.max_size
+					})
+				);
+				return;
+			}
+
+			if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
+				if (visionCapableModels.length === 0) {
+					toast.error($i18n.t('Selected model(s) do not support image inputs'));
+					return;
+				}
+				let reader = new FileReader();
+				reader.onload = (event) => {
+					files = [
+						...files,
+						{
+							type: 'image',
+							url: `${event.target.result}`
+						}
+					];
+				};
+				reader.readAsDataURL(file);
+			} else {
+				uploadFileHandler(file);
+			}
+		});
+	};
+
+	const handleKeyDown = (event: KeyboardEvent) => {
+		if (event.key === 'Escape') {
+			console.log('Escape');
+			dragged = false;
+		}
+	};
+
+	const onDragOver = (e) => {
+		e.preventDefault();
+
+		// Check if a file is being dragged.
+		if (e.dataTransfer?.types?.includes('Files')) {
+			dragged = true;
+		} else {
+			dragged = false;
+		}
+	};
+
+	const onDragLeave = () => {
+		dragged = false;
+	};
+
+	const onDrop = async (e) => {
+		e.preventDefault();
+		console.log(e);
+
+		if (e.dataTransfer?.files) {
+			const inputFiles = Array.from(e.dataTransfer?.files);
+			if (inputFiles && inputFiles.length > 0) {
+				console.log(inputFiles);
+				inputFilesHandler(inputFiles);
+			}
+		}
+
+		dragged = false;
+	};
+
+	onMount(() => {
+		window.setTimeout(() => {
+			const chatInput = document.getElementById('chat-input');
+			chatInput?.focus();
+		}, 0);
+
+		window.addEventListener('keydown', handleKeyDown);
+
+		const dropZone = document.getElementById('chat-container');
+
+		dropZone?.addEventListener('dragover', onDragOver);
+		dropZone?.addEventListener('drop', onDrop);
+		dropZone?.addEventListener('dragleave', onDragLeave);
+	});
+
+	onDestroy(() => {
+		window.removeEventListener('keydown', handleKeyDown);
+
+		const dropZone = document.getElementById('chat-container');
+
+		dropZone?.removeEventListener('dragover', onDragOver);
+		dropZone?.removeEventListener('drop', onDrop);
+		dropZone?.removeEventListener('dragleave', onDragLeave);
+	});
+</script>
+
+<FilesOverlay show={dragged} />
+
+<div class="w-full font-primary">
+	<div class=" -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
+		<div class="flex flex-col px-2.5 max-w-6xl w-full">
+			<div class="relative">
+				{#if autoScroll === false && history?.currentId}
+					<div
+						class=" absolute -top-12 left-0 right-0 flex justify-center z-30 pointer-events-none"
+					>
+						<button
+							class=" bg-white border border-gray-100 dark:border-none dark:bg-white/20 p-1.5 rounded-full pointer-events-auto"
+							on:click={() => {
+								autoScroll = true;
+								scrollToBottom();
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-5 h-5"
+							>
+								<path
+									fill-rule="evenodd"
+									d="M10 3a.75.75 0 01.75.75v10.638l3.96-4.158a.75.75 0 111.08 1.04l-5.25 5.5a.75.75 0 01-1.08 0l-5.25-5.5a.75.75 0 111.08-1.04l3.96 4.158V3.75A.75.75 0 0110 3z"
+									clip-rule="evenodd"
+								/>
+							</svg>
+						</button>
+					</div>
+				{/if}
+			</div>
+
+			<div class="w-full relative">
+				{#if atSelectedModel !== undefined}
+					<div
+						class="px-3 py-1 text-left w-full flex justify-between items-center absolute bottom-0 left-0 right-0 bg-gradient-to-t from-white dark:from-gray-900 z-10"
+					>
+						<div class="flex items-center gap-2 text-sm dark:text-gray-500">
+							<img
+								crossorigin="anonymous"
+								alt="model profile"
+								class="size-4 max-w-[28px] object-cover rounded-full"
+								src={$models.find((model) => model.id === atSelectedModel.id)?.info?.meta
+									?.profile_image_url ??
+									($i18n.language === 'dg-DG'
+										? `/doge.png`
+										: `${WEBUI_BASE_URL}/static/favicon.png`)}
+							/>
+							<div>
+								Talking to <span class=" font-medium">{atSelectedModel.name}</span>
+							</div>
+						</div>
+						<div>
+							<button
+								class="flex items-center"
+								on:click={() => {
+									atSelectedModel = undefined;
+								}}
+							>
+								<XMark />
+							</button>
+						</div>
+					</div>
+				{/if}
+
+				<Commands
+					bind:this={commandsElement}
+					bind:prompt
+					bind:files
+					on:upload={(e) => {
+						dispatch('upload', e.detail);
+					}}
+					on:select={(e) => {
+						const data = e.detail;
+
+						if (data?.type === 'model') {
+							atSelectedModel = data.data;
+						}
+
+						const chatInputElement = document.getElementById('chat-input');
+						chatInputElement?.focus();
+					}}
+				/>
+			</div>
+		</div>
+	</div>
+
+	<div class="{transparentBackground ? 'bg-transparent' : 'bg-white dark:bg-gray-900'} ">
+		<div class="max-w-6xl px-4 mx-auto inset-x-0">
+			<div class="">
+				<input
+					bind:this={filesInputElement}
+					bind:files={inputFiles}
+					type="file"
+					hidden
+					multiple
+					on:change={async () => {
+						if (inputFiles && inputFiles.length > 0) {
+							const _inputFiles = Array.from(inputFiles);
+							inputFilesHandler(_inputFiles);
+						} else {
+							toast.error($i18n.t(`File not found.`));
+						}
+
+						filesInputElement.value = '';
+					}}
+				/>
+
+				{#if recording}
+					<VoiceRecording
+						bind:recording
+						on:cancel={async () => {
+							recording = false;
+
+							await tick();
+							document.getElementById('chat-input')?.focus();
+						}}
+						on:confirm={async (e) => {
+							const { text, filename } = e.detail;
+							prompt = `${prompt}${text} `;
+
+							recording = false;
+
+							await tick();
+							document.getElementById('chat-input')?.focus();
+
+							if ($settings?.speechAutoSend ?? false) {
+								dispatch('submit', prompt);
+							}
+						}}
+					/>
+				{:else}
+					<form
+						class="w-full flex gap-1.5"
+						on:submit|preventDefault={() => {
+							// check if selectedModels support image input
+							dispatch('submit', prompt);
+						}}
+					>
+						<div
+							class="flex-1 flex flex-col relative w-full rounded-3xl px-1.5 bg-gray-50 dark:bg-gray-850 dark:text-gray-100"
+							dir={$settings?.chatDirection ?? 'LTR'}
+						>
+							{#if files.length > 0}
+								<div class="mx-1 mt-2.5 mb-1 flex flex-wrap gap-2">
+									{#each files as file, fileIdx}
+										{#if file.type === 'image'}
+											<div class=" relative group">
+												<div class="relative">
+													<img
+														src={file.url}
+														alt="input"
+														class=" h-16 w-16 rounded-xl object-cover"
+													/>
+													{#if atSelectedModel ? visionCapableModels.length === 0 : selectedModels.length !== visionCapableModels.length}
+														<Tooltip
+															className=" absolute top-1 left-1"
+															content={$i18n.t('{{ models }}', {
+																models: [...(atSelectedModel ? [atSelectedModel] : selectedModels)]
+																	.filter((id) => !visionCapableModels.includes(id))
+																	.join(', ')
+															})}
+														>
+															<svg
+																xmlns="http://www.w3.org/2000/svg"
+																viewBox="0 0 24 24"
+																fill="currentColor"
+																class="size-4 fill-yellow-300"
+															>
+																<path
+																	fill-rule="evenodd"
+																	d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
+																	clip-rule="evenodd"
+																/>
+															</svg>
+														</Tooltip>
+													{/if}
+												</div>
+												<div class=" absolute -top-1 -right-1">
+													<button
+														class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
+														type="button"
+														on:click={() => {
+															files.splice(fileIdx, 1);
+															files = files;
+														}}
+													>
+														<svg
+															xmlns="http://www.w3.org/2000/svg"
+															viewBox="0 0 20 20"
+															fill="currentColor"
+															class="w-4 h-4"
+														>
+															<path
+																d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+															/>
+														</svg>
+													</button>
+												</div>
+											</div>
+										{:else}
+											<FileItem
+												item={file}
+												name={file.name}
+												type={file.type}
+												size={file?.size}
+												loading={file.status === 'uploading'}
+												dismissible={true}
+												edit={true}
+												on:dismiss={() => {
+													files.splice(fileIdx, 1);
+													files = files;
+												}}
+												on:click={() => {
+													console.log(file);
+												}}
+											/>
+										{/if}
+									{/each}
+								</div>
+							{/if}
+
+							<div class=" flex">
+								<div class=" ml-0.5 self-end mb-1.5 flex space-x-1">
+									<InputMenu
+										bind:webSearchEnabled
+										bind:selectedToolIds
+										tools={$tools.reduce((a, e, i, arr) => {
+											if (availableToolIds.includes(e.id) || ($_user?.role ?? 'user') === 'admin') {
+												a[e.id] = {
+													name: e.name,
+													description: e.meta.description,
+													enabled: false
+												};
+											}
+											return a;
+										}, {})}
+										uploadFilesHandler={() => {
+											filesInputElement.click();
+										}}
+										onClose={async () => {
+											await tick();
+
+											const chatInput = document.getElementById('chat-input');
+											chatInput?.focus();
+										}}
+									>
+										<button
+											class="bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-2 outline-none focus:outline-none"
+											type="button"
+											aria-label="More"
+										>
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												viewBox="0 0 16 16"
+												fill="currentColor"
+												class="size-5"
+											>
+												<path
+													d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+												/>
+											</svg>
+										</button>
+									</InputMenu>
+								</div>
+
+								{#if $settings?.richTextInput ?? true}
+									<div
+										bind:this={chatInputContainerElement}
+										id="chat-input-container"
+										class="scrollbar-hidden text-left bg-gray-50 dark:bg-gray-850 dark:text-gray-100 outline-none w-full py-2.5 px-1 rounded-xl resize-none h-[48px] overflow-auto"
+									>
+										<RichTextInput
+											bind:this={chatInputElement}
+											id="chat-input"
+											trim={true}
+											placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
+											bind:value={prompt}
+											shiftEnter={!$mobile ||
+												!(
+													'ontouchstart' in window ||
+													navigator.maxTouchPoints > 0 ||
+													navigator.msMaxTouchPoints > 0
+												)}
+											on:enter={async (e) => {
+												if (prompt !== '') {
+													dispatch('submit', prompt);
+												}
+											}}
+											on:input={async (e) => {
+												if (chatInputContainerElement) {
+													chatInputContainerElement.style.height = '';
+													chatInputContainerElement.style.height =
+														Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
+												}
+											}}
+											on:focus={async (e) => {
+												if (chatInputContainerElement) {
+													chatInputContainerElement.style.height = '';
+													chatInputContainerElement.style.height =
+														Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
+												}
+											}}
+											on:keypress={(e) => {
+												e = e.detail.event;
+											}}
+											on:keydown={async (e) => {
+												e = e.detail.event;
+
+												if (chatInputContainerElement) {
+													chatInputContainerElement.style.height = '';
+													chatInputContainerElement.style.height =
+														Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
+												}
+
+												const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
+												const commandsContainerElement =
+													document.getElementById('commands-container');
+
+												// Command/Ctrl + Shift + Enter to submit a message pair
+												if (isCtrlPressed && e.key === 'Enter' && e.shiftKey) {
+													e.preventDefault();
+													createMessagePair(prompt);
+												}
+
+												// Check if Ctrl + R is pressed
+												if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') {
+													e.preventDefault();
+													console.log('regenerate');
+
+													const regenerateButton = [
+														...document.getElementsByClassName('regenerate-response-button')
+													]?.at(-1);
+
+													regenerateButton?.click();
+												}
+
+												if (prompt === '' && e.key == 'ArrowUp') {
+													e.preventDefault();
+
+													const userMessageElement = [
+														...document.getElementsByClassName('user-message')
+													]?.at(-1);
+
+													const editButton = [
+														...document.getElementsByClassName('edit-user-message-button')
+													]?.at(-1);
+
+													console.log(userMessageElement);
+
+													userMessageElement.scrollIntoView({ block: 'center' });
+													editButton?.click();
+												}
+
+												if (commandsContainerElement && e.key === 'ArrowUp') {
+													e.preventDefault();
+													commandsElement.selectUp();
+
+													const commandOptionButton = [
+														...document.getElementsByClassName('selected-command-option-button')
+													]?.at(-1);
+													commandOptionButton.scrollIntoView({ block: 'center' });
+												}
+
+												if (commandsContainerElement && e.key === 'ArrowDown') {
+													e.preventDefault();
+													commandsElement.selectDown();
+
+													const commandOptionButton = [
+														...document.getElementsByClassName('selected-command-option-button')
+													]?.at(-1);
+													commandOptionButton.scrollIntoView({ block: 'center' });
+												}
+
+												if (commandsContainerElement && e.key === 'Enter') {
+													e.preventDefault();
+
+													const commandOptionButton = [
+														...document.getElementsByClassName('selected-command-option-button')
+													]?.at(-1);
+
+													if (e.shiftKey) {
+														prompt = `${prompt}\n`;
+													} else if (commandOptionButton) {
+														commandOptionButton?.click();
+													} else {
+														document.getElementById('send-message-button')?.click();
+													}
+												}
+
+												if (commandsContainerElement && e.key === 'Tab') {
+													e.preventDefault();
+
+													const commandOptionButton = [
+														...document.getElementsByClassName('selected-command-option-button')
+													]?.at(-1);
+
+													commandOptionButton?.click();
+												}
+
+												if (e.key === 'Escape') {
+													console.log('Escape');
+													atSelectedModel = undefined;
+												}
+											}}
+											on:paste={async (e) => {
+												e = e.detail.event;
+												console.log(e);
+
+												const clipboardData = e.clipboardData || window.clipboardData;
+
+												if (clipboardData && clipboardData.items) {
+													for (const item of clipboardData.items) {
+														if (item.type.indexOf('image') !== -1) {
+															const blob = item.getAsFile();
+															const reader = new FileReader();
+
+															reader.onload = function (e) {
+																files = [
+																	...files,
+																	{
+																		type: 'image',
+																		url: `${e.target.result}`
+																	}
+																];
+															};
+
+															reader.readAsDataURL(blob);
+														}
+													}
+												}
+											}}
+										/>
+									</div>
+								{:else}
+									<textarea
+										id="chat-input"
+										bind:this={chatInputElement}
+										class="scrollbar-hidden bg-gray-50 dark:bg-gray-850 dark:text-gray-100 outline-none w-full py-3 px-1 rounded-xl resize-none h-[48px]"
+										placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
+										bind:value={prompt}
+										on:keypress={(e) => {
+											if (
+												!$mobile ||
+												!(
+													'ontouchstart' in window ||
+													navigator.maxTouchPoints > 0 ||
+													navigator.msMaxTouchPoints > 0
+												)
+											) {
+												// Prevent Enter key from creating a new line
+												if (e.key === 'Enter' && !e.shiftKey) {
+													e.preventDefault();
+												}
+
+												// Submit the prompt when Enter key is pressed
+												if (prompt !== '' && e.key === 'Enter' && !e.shiftKey) {
+													dispatch('submit', prompt);
+												}
+											}
+										}}
+										on:keydown={async (e) => {
+											const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
+											const commandsContainerElement =
+												document.getElementById('commands-container');
+
+											// Command/Ctrl + Shift + Enter to submit a message pair
+											if (isCtrlPressed && e.key === 'Enter' && e.shiftKey) {
+												e.preventDefault();
+												createMessagePair(prompt);
+											}
+
+											// Check if Ctrl + R is pressed
+											if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') {
+												e.preventDefault();
+												console.log('regenerate');
+
+												const regenerateButton = [
+													...document.getElementsByClassName('regenerate-response-button')
+												]?.at(-1);
+
+												regenerateButton?.click();
+											}
+
+											if (prompt === '' && e.key == 'ArrowUp') {
+												e.preventDefault();
+
+												const userMessageElement = [
+													...document.getElementsByClassName('user-message')
+												]?.at(-1);
+
+												const editButton = [
+													...document.getElementsByClassName('edit-user-message-button')
+												]?.at(-1);
+
+												console.log(userMessageElement);
+
+												userMessageElement.scrollIntoView({ block: 'center' });
+												editButton?.click();
+											}
+
+											if (commandsContainerElement && e.key === 'ArrowUp') {
+												e.preventDefault();
+												commandsElement.selectUp();
+
+												const commandOptionButton = [
+													...document.getElementsByClassName('selected-command-option-button')
+												]?.at(-1);
+												commandOptionButton.scrollIntoView({ block: 'center' });
+											}
+
+											if (commandsContainerElement && e.key === 'ArrowDown') {
+												e.preventDefault();
+												commandsElement.selectDown();
+
+												const commandOptionButton = [
+													...document.getElementsByClassName('selected-command-option-button')
+												]?.at(-1);
+												commandOptionButton.scrollIntoView({ block: 'center' });
+											}
+
+											if (commandsContainerElement && e.key === 'Enter') {
+												e.preventDefault();
+
+												const commandOptionButton = [
+													...document.getElementsByClassName('selected-command-option-button')
+												]?.at(-1);
+
+												if (e.shiftKey) {
+													prompt = `${prompt}\n`;
+												} else if (commandOptionButton) {
+													commandOptionButton?.click();
+												} else {
+													document.getElementById('send-message-button')?.click();
+												}
+											}
+
+											if (commandsContainerElement && e.key === 'Tab') {
+												e.preventDefault();
+
+												const commandOptionButton = [
+													...document.getElementsByClassName('selected-command-option-button')
+												]?.at(-1);
+
+												commandOptionButton?.click();
+											} else if (e.key === 'Tab') {
+												const words = findWordIndices(prompt);
+
+												if (words.length > 0) {
+													const word = words.at(0);
+													const fullPrompt = prompt;
+
+													prompt = prompt.substring(0, word?.endIndex + 1);
+													await tick();
+
+													e.target.scrollTop = e.target.scrollHeight;
+													prompt = fullPrompt;
+													await tick();
+
+													e.preventDefault();
+													e.target.setSelectionRange(word?.startIndex, word.endIndex + 1);
+												}
+
+												e.target.style.height = '';
+												e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
+											}
+
+											if (e.key === 'Escape') {
+												console.log('Escape');
+												atSelectedModel = undefined;
+											}
+										}}
+										rows="1"
+										on:input={async (e) => {
+											e.target.style.height = '';
+											e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
+											user = null;
+										}}
+										on:focus={async (e) => {
+											e.target.style.height = '';
+											e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
+										}}
+										on:paste={async (e) => {
+											const clipboardData = e.clipboardData || window.clipboardData;
+
+											if (clipboardData && clipboardData.items) {
+												for (const item of clipboardData.items) {
+													if (item.type.indexOf('image') !== -1) {
+														const blob = item.getAsFile();
+														const reader = new FileReader();
+
+														reader.onload = function (e) {
+															files = [
+																...files,
+																{
+																	type: 'image',
+																	url: `${e.target.result}`
+																}
+															];
+														};
+
+														reader.readAsDataURL(blob);
+													}
+												}
+											}
+										}}
+									/>
+								{/if}
+
+								<div class="self-end mb-2 flex space-x-1 mr-1">
+									{#if !history?.currentId || history.messages[history.currentId]?.done == true}
+										<Tooltip content={$i18n.t('Record voice')}>
+											<button
+												id="voice-input-button"
+												class=" text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-full p-1.5 mr-0.5 self-center"
+												type="button"
+												on:click={async () => {
+													try {
+														let stream = await navigator.mediaDevices
+															.getUserMedia({ audio: true })
+															.catch(function (err) {
+																toast.error(
+																	$i18n.t(
+																		`Permission denied when accessing microphone: {{error}}`,
+																		{
+																			error: err
+																		}
+																	)
+																);
+																return null;
+															});
+
+														if (stream) {
+															recording = true;
+															const tracks = stream.getTracks();
+															tracks.forEach((track) => track.stop());
+														}
+														stream = null;
+													} catch {
+														toast.error($i18n.t('Permission denied when accessing microphone'));
+													}
+												}}
+												aria-label="Voice Input"
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 20 20"
+													fill="currentColor"
+													class="w-5 h-5 translate-y-[0.5px]"
+												>
+													<path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z" />
+													<path
+														d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+									{/if}
+								</div>
+							</div>
+						</div>
+						<div class="flex items-end w-10">
+							{#if !history.currentId || history.messages[history.currentId]?.done == true}
+								{#if prompt === ''}
+									<div class=" flex items-center mb-1">
+										<Tooltip content={$i18n.t('Call')}>
+											<button
+												class=" text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-full p-2 self-center"
+												type="button"
+												on:click={async () => {
+													if (selectedModels.length > 1) {
+														toast.error($i18n.t('Select only one model to call'));
+
+														return;
+													}
+
+													if ($config.audio.stt.engine === 'web') {
+														toast.error(
+															$i18n.t('Call feature is not supported when using Web STT engine')
+														);
+
+														return;
+													}
+													// check if user has access to getUserMedia
+													try {
+														let stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+														// If the user grants the permission, proceed to show the call overlay
+
+														if (stream) {
+															const tracks = stream.getTracks();
+															tracks.forEach((track) => track.stop());
+														}
+
+														stream = null;
+
+														showCallOverlay.set(true);
+														showControls.set(true);
+													} catch (err) {
+														// If the user denies the permission or an error occurs, show an error message
+														toast.error($i18n.t('Permission denied when accessing media devices'));
+													}
+												}}
+												aria-label="Call"
+											>
+												<Headphone className="size-6" />
+											</button>
+										</Tooltip>
+									</div>
+								{:else}
+									<div class=" flex items-center mb-1">
+										<Tooltip content={$i18n.t('Send message')}>
+											<button
+												id="send-message-button"
+												class="{prompt !== ''
+													? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
+													: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1.5 m-0.5 self-center"
+												type="submit"
+												disabled={prompt === ''}
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="size-6"
+												>
+													<path
+														fill-rule="evenodd"
+														d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
+														clip-rule="evenodd"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+									</div>
+								{/if}
+							{:else}
+								<div class=" flex items-center mb-1.5">
+									<Tooltip content={$i18n.t('Stop')}>
+										<button
+											class="bg-white hover:bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
+											on:click={() => {
+												stopResponse();
+											}}
+										>
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												viewBox="0 0 24 24"
+												fill="currentColor"
+												class="size-6"
+											>
+												<path
+													fill-rule="evenodd"
+													d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm6-2.438c0-.724.588-1.312 1.313-1.312h4.874c.725 0 1.313.588 1.313 1.313v4.874c0 .725-.588 1.313-1.313 1.313H9.564a1.312 1.312 0 01-1.313-1.313V9.564z"
+													clip-rule="evenodd"
+												/>
+											</svg>
+										</button>
+									</Tooltip>
+								</div>
+							{/if}
+						</div>
+					</form>
+				{/if}
+			</div>
+		</div>
+	</div>
+</div>
diff --git a/src/lib/components/chat/MessageInput/CallOverlay.svelte b/src/lib/components/chat/MessageInput/CallOverlay.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6f3b465a6c4424c5a1d70374590299c5b0a0805b
--- /dev/null
+++ b/src/lib/components/chat/MessageInput/CallOverlay.svelte
@@ -0,0 +1,974 @@
+<script lang="ts">
+	import { config, models, settings, showCallOverlay } from '$lib/stores';
+	import { onMount, tick, getContext, onDestroy, createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	import { blobToFile } from '$lib/utils';
+	import { generateEmoji } from '$lib/apis';
+	import { synthesizeOpenAISpeech, transcribeAudio } from '$lib/apis/audio';
+
+	import { toast } from 'svelte-sonner';
+
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import VideoInputMenu from './CallOverlay/VideoInputMenu.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let eventTarget: EventTarget;
+	export let submitPrompt: Function;
+	export let stopResponse: Function;
+	export let files;
+	export let chatId;
+	export let modelId;
+
+	let wakeLock = null;
+
+	let model = null;
+
+	let loading = false;
+	let confirmed = false;
+	let interrupted = false;
+	let assistantSpeaking = false;
+
+	let emoji = null;
+	let camera = false;
+	let cameraStream = null;
+
+	let chatStreaming = false;
+	let rmsLevel = 0;
+	let hasStartedSpeaking = false;
+	let mediaRecorder;
+	let audioStream = null;
+	let audioChunks = [];
+
+	let videoInputDevices = [];
+	let selectedVideoInputDeviceId = null;
+
+	const getVideoInputDevices = async () => {
+		const devices = await navigator.mediaDevices.enumerateDevices();
+		videoInputDevices = devices.filter((device) => device.kind === 'videoinput');
+
+		if (!!navigator.mediaDevices.getDisplayMedia) {
+			videoInputDevices = [
+				...videoInputDevices,
+				{
+					deviceId: 'screen',
+					label: 'Screen Share'
+				}
+			];
+		}
+
+		console.log(videoInputDevices);
+		if (selectedVideoInputDeviceId === null && videoInputDevices.length > 0) {
+			selectedVideoInputDeviceId = videoInputDevices[0].deviceId;
+		}
+	};
+
+	const startCamera = async () => {
+		await getVideoInputDevices();
+
+		if (cameraStream === null) {
+			camera = true;
+			await tick();
+			try {
+				await startVideoStream();
+			} catch (err) {
+				console.error('Error accessing webcam: ', err);
+			}
+		}
+	};
+
+	const startVideoStream = async () => {
+		const video = document.getElementById('camera-feed');
+		if (video) {
+			if (selectedVideoInputDeviceId === 'screen') {
+				cameraStream = await navigator.mediaDevices.getDisplayMedia({
+					video: {
+						cursor: 'always'
+					},
+					audio: false
+				});
+			} else {
+				cameraStream = await navigator.mediaDevices.getUserMedia({
+					video: {
+						deviceId: selectedVideoInputDeviceId ? { exact: selectedVideoInputDeviceId } : undefined
+					}
+				});
+			}
+
+			if (cameraStream) {
+				await getVideoInputDevices();
+				video.srcObject = cameraStream;
+				await video.play();
+			}
+		}
+	};
+
+	const stopVideoStream = async () => {
+		if (cameraStream) {
+			const tracks = cameraStream.getTracks();
+			tracks.forEach((track) => track.stop());
+		}
+
+		cameraStream = null;
+	};
+
+	const takeScreenshot = () => {
+		const video = document.getElementById('camera-feed');
+		const canvas = document.getElementById('camera-canvas');
+
+		if (!canvas) {
+			return;
+		}
+
+		const context = canvas.getContext('2d');
+
+		// Make the canvas match the video dimensions
+		canvas.width = video.videoWidth;
+		canvas.height = video.videoHeight;
+
+		// Draw the image from the video onto the canvas
+		context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
+
+		// Convert the canvas to a data base64 URL and console log it
+		const dataURL = canvas.toDataURL('image/png');
+		console.log(dataURL);
+
+		return dataURL;
+	};
+
+	const stopCamera = async () => {
+		await stopVideoStream();
+		camera = false;
+	};
+
+	const MIN_DECIBELS = -55;
+	const VISUALIZER_BUFFER_LENGTH = 300;
+
+	const transcribeHandler = async (audioBlob) => {
+		// Create a blob from the audio chunks
+
+		await tick();
+		const file = blobToFile(audioBlob, 'recording.wav');
+
+		const res = await transcribeAudio(localStorage.token, file).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			console.log(res.text);
+
+			if (res.text !== '') {
+				const _responses = await submitPrompt(res.text, { _raw: true });
+				console.log(_responses);
+			}
+		}
+	};
+
+	const stopRecordingCallback = async (_continue = true) => {
+		if ($showCallOverlay) {
+			console.log('%c%s', 'color: red; font-size: 20px;', '🚨 stopRecordingCallback 🚨');
+
+			// deep copy the audioChunks array
+			const _audioChunks = audioChunks.slice(0);
+
+			audioChunks = [];
+			mediaRecorder = false;
+
+			if (_continue) {
+				startRecording();
+			}
+
+			if (confirmed) {
+				loading = true;
+				emoji = null;
+
+				if (cameraStream) {
+					const imageUrl = takeScreenshot();
+
+					files = [
+						{
+							type: 'image',
+							url: imageUrl
+						}
+					];
+				}
+
+				const audioBlob = new Blob(_audioChunks, { type: 'audio/wav' });
+				await transcribeHandler(audioBlob);
+
+				confirmed = false;
+				loading = false;
+			}
+		} else {
+			audioChunks = [];
+			mediaRecorder = false;
+
+			if (audioStream) {
+				const tracks = audioStream.getTracks();
+				tracks.forEach((track) => track.stop());
+			}
+			audioStream = null;
+		}
+	};
+
+	const startRecording = async () => {
+		if ($showCallOverlay) {
+			if (!audioStream) {
+				audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
+			}
+			mediaRecorder = new MediaRecorder(audioStream);
+
+			mediaRecorder.onstart = () => {
+				console.log('Recording started');
+				audioChunks = [];
+				analyseAudio(audioStream);
+			};
+
+			mediaRecorder.ondataavailable = (event) => {
+				if (hasStartedSpeaking) {
+					audioChunks.push(event.data);
+				}
+			};
+
+			mediaRecorder.onstop = (e) => {
+				console.log('Recording stopped', audioStream, e);
+				stopRecordingCallback();
+			};
+
+			mediaRecorder.start();
+		}
+	};
+
+	const stopAudioStream = async () => {
+		try {
+			if (mediaRecorder) {
+				mediaRecorder.stop();
+			}
+		} catch (error) {
+			console.log('Error stopping audio stream:', error);
+		}
+
+		if (!audioStream) return;
+
+		audioStream.getAudioTracks().forEach(function (track) {
+			track.stop();
+		});
+
+		audioStream = null;
+	};
+
+	// Function to calculate the RMS level from time domain data
+	const calculateRMS = (data: Uint8Array) => {
+		let sumSquares = 0;
+		for (let i = 0; i < data.length; i++) {
+			const normalizedValue = (data[i] - 128) / 128; // Normalize the data
+			sumSquares += normalizedValue * normalizedValue;
+		}
+		return Math.sqrt(sumSquares / data.length);
+	};
+
+	const analyseAudio = (stream) => {
+		const audioContext = new AudioContext();
+		const audioStreamSource = audioContext.createMediaStreamSource(stream);
+
+		const analyser = audioContext.createAnalyser();
+		analyser.minDecibels = MIN_DECIBELS;
+		audioStreamSource.connect(analyser);
+
+		const bufferLength = analyser.frequencyBinCount;
+
+		const domainData = new Uint8Array(bufferLength);
+		const timeDomainData = new Uint8Array(analyser.fftSize);
+
+		let lastSoundTime = Date.now();
+		hasStartedSpeaking = false;
+
+		console.log('🔊 Sound detection started', lastSoundTime, hasStartedSpeaking);
+
+		const detectSound = () => {
+			const processFrame = () => {
+				if (!mediaRecorder || !$showCallOverlay) {
+					return;
+				}
+
+				if (assistantSpeaking && !($settings?.voiceInterruption ?? false)) {
+					// Mute the audio if the assistant is speaking
+					analyser.maxDecibels = 0;
+					analyser.minDecibels = -1;
+				} else {
+					analyser.minDecibels = MIN_DECIBELS;
+					analyser.maxDecibels = -30;
+				}
+
+				analyser.getByteTimeDomainData(timeDomainData);
+				analyser.getByteFrequencyData(domainData);
+
+				// Calculate RMS level from time domain data
+				rmsLevel = calculateRMS(timeDomainData);
+
+				// Check if initial speech/noise has started
+				const hasSound = domainData.some((value) => value > 0);
+				if (hasSound) {
+					// BIG RED TEXT
+					console.log('%c%s', 'color: red; font-size: 20px;', '🔊 Sound detected');
+
+					if (!hasStartedSpeaking) {
+						hasStartedSpeaking = true;
+						stopAllAudio();
+					}
+
+					lastSoundTime = Date.now();
+				}
+
+				// Start silence detection only after initial speech/noise has been detected
+				if (hasStartedSpeaking) {
+					if (Date.now() - lastSoundTime > 2000) {
+						confirmed = true;
+
+						if (mediaRecorder) {
+							console.log('%c%s', 'color: red; font-size: 20px;', '🔇 Silence detected');
+							mediaRecorder.stop();
+							return;
+						}
+					}
+				}
+
+				window.requestAnimationFrame(processFrame);
+			};
+
+			window.requestAnimationFrame(processFrame);
+		};
+
+		detectSound();
+	};
+
+	let finishedMessages = {};
+	let currentMessageId = null;
+	let currentUtterance = null;
+
+	const speakSpeechSynthesisHandler = (content) => {
+		if ($showCallOverlay) {
+			return new Promise((resolve) => {
+				let voices = [];
+				const getVoicesLoop = setInterval(async () => {
+					voices = await speechSynthesis.getVoices();
+					if (voices.length > 0) {
+						clearInterval(getVoicesLoop);
+
+						const voice =
+							voices
+								?.filter(
+									(v) => v.voiceURI === ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
+								)
+								?.at(0) ?? undefined;
+
+						currentUtterance = new SpeechSynthesisUtterance(content);
+						currentUtterance.rate = $settings.audio?.tts?.playbackRate ?? 1;
+
+						if (voice) {
+							currentUtterance.voice = voice;
+						}
+
+						speechSynthesis.speak(currentUtterance);
+						currentUtterance.onend = async (e) => {
+							await new Promise((r) => setTimeout(r, 200));
+							resolve(e);
+						};
+					}
+				}, 100);
+			});
+		} else {
+			return Promise.resolve();
+		}
+	};
+
+	const playAudio = (audio) => {
+		if ($showCallOverlay) {
+			return new Promise((resolve) => {
+				const audioElement = document.getElementById('audioElement') as HTMLAudioElement;
+
+				if (audioElement) {
+					audioElement.src = audio.src;
+					audioElement.muted = true;
+					audioElement.playbackRate = $settings.audio?.tts?.playbackRate ?? 1;
+
+					audioElement
+						.play()
+						.then(() => {
+							audioElement.muted = false;
+						})
+						.catch((error) => {
+							console.error(error);
+						});
+
+					audioElement.onended = async (e) => {
+						await new Promise((r) => setTimeout(r, 100));
+						resolve(e);
+					};
+				}
+			});
+		} else {
+			return Promise.resolve();
+		}
+	};
+
+	const stopAllAudio = async () => {
+		assistantSpeaking = false;
+		interrupted = true;
+
+		if (chatStreaming) {
+			stopResponse();
+		}
+
+		if (currentUtterance) {
+			speechSynthesis.cancel();
+			currentUtterance = null;
+		}
+
+		const audioElement = document.getElementById('audioElement');
+		if (audioElement) {
+			audioElement.muted = true;
+			audioElement.pause();
+			audioElement.currentTime = 0;
+		}
+	};
+
+	let audioAbortController = new AbortController();
+
+	// Audio cache map where key is the content and value is the Audio object.
+	const audioCache = new Map();
+	const emojiCache = new Map();
+
+	const fetchAudio = async (content) => {
+		if (!audioCache.has(content)) {
+			try {
+				// Set the emoji for the content if needed
+				if ($settings?.showEmojiInCall ?? false) {
+					const emoji = await generateEmoji(localStorage.token, modelId, content, chatId);
+					if (emoji) {
+						emojiCache.set(content, emoji);
+					}
+				}
+
+				if ($config.audio.tts.engine !== '') {
+					const res = await synthesizeOpenAISpeech(
+						localStorage.token,
+						$settings?.audio?.tts?.defaultVoice === $config.audio.tts.voice
+							? ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
+							: $config?.audio?.tts?.voice,
+						content
+					).catch((error) => {
+						console.error(error);
+						return null;
+					});
+
+					if (res) {
+						const blob = await res.blob();
+						const blobUrl = URL.createObjectURL(blob);
+						audioCache.set(content, new Audio(blobUrl));
+					}
+				} else {
+					audioCache.set(content, true);
+				}
+			} catch (error) {
+				console.error('Error synthesizing speech:', error);
+			}
+		}
+
+		return audioCache.get(content);
+	};
+
+	let messages = {};
+
+	const monitorAndPlayAudio = async (id, signal) => {
+		while (!signal.aborted) {
+			if (messages[id] && messages[id].length > 0) {
+				// Retrieve the next content string from the queue
+				const content = messages[id].shift(); // Dequeues the content for playing
+
+				if (audioCache.has(content)) {
+					// If content is available in the cache, play it
+
+					// Set the emoji for the content if available
+					if (($settings?.showEmojiInCall ?? false) && emojiCache.has(content)) {
+						emoji = emojiCache.get(content);
+					} else {
+						emoji = null;
+					}
+
+					if ($config.audio.tts.engine !== '') {
+						try {
+							console.log(
+								'%c%s',
+								'color: red; font-size: 20px;',
+								`Playing audio for content: ${content}`
+							);
+
+							const audio = audioCache.get(content);
+							await playAudio(audio); // Here ensure that playAudio is indeed correct method to execute
+							console.log(`Played audio for content: ${content}`);
+							await new Promise((resolve) => setTimeout(resolve, 200)); // Wait before retrying to reduce tight loop
+						} catch (error) {
+							console.error('Error playing audio:', error);
+						}
+					} else {
+						await speakSpeechSynthesisHandler(content);
+					}
+				} else {
+					// If not available in the cache, push it back to the queue and delay
+					messages[id].unshift(content); // Re-queue the content at the start
+					console.log(`Audio for "${content}" not yet available in the cache, re-queued...`);
+					await new Promise((resolve) => setTimeout(resolve, 200)); // Wait before retrying to reduce tight loop
+				}
+			} else if (finishedMessages[id] && messages[id] && messages[id].length === 0) {
+				// If the message is finished and there are no more messages to process, break the loop
+				assistantSpeaking = false;
+				break;
+			} else {
+				// No messages to process, sleep for a bit
+				await new Promise((resolve) => setTimeout(resolve, 200));
+			}
+		}
+		console.log(`Audio monitoring and playing stopped for message ID ${id}`);
+	};
+
+	const chatStartHandler = async (e) => {
+		const { id } = e.detail;
+
+		chatStreaming = true;
+
+		if (currentMessageId !== id) {
+			console.log(`Received chat start event for message ID ${id}`);
+
+			currentMessageId = id;
+			if (audioAbortController) {
+				audioAbortController.abort();
+			}
+			audioAbortController = new AbortController();
+
+			assistantSpeaking = true;
+			// Start monitoring and playing audio for the message ID
+			monitorAndPlayAudio(id, audioAbortController.signal);
+		}
+	};
+
+	const chatEventHandler = async (e) => {
+		const { id, content } = e.detail;
+		// "id" here is message id
+		// if "id" is not the same as "currentMessageId" then do not process
+		// "content" here is a sentence from the assistant,
+		// there will be many sentences for the same "id"
+
+		if (currentMessageId === id) {
+			console.log(`Received chat event for message ID ${id}: ${content}`);
+
+			try {
+				if (messages[id] === undefined) {
+					messages[id] = [content];
+				} else {
+					messages[id].push(content);
+				}
+
+				console.log(content);
+
+				fetchAudio(content);
+			} catch (error) {
+				console.error('Failed to fetch or play audio:', error);
+			}
+		}
+	};
+
+	const chatFinishHandler = async (e) => {
+		const { id, content } = e.detail;
+		// "content" here is the entire message from the assistant
+		finishedMessages[id] = true;
+
+		chatStreaming = false;
+	};
+
+	onMount(async () => {
+		const setWakeLock = async () => {
+			try {
+				wakeLock = await navigator.wakeLock.request('screen');
+			} catch (err) {
+				// The Wake Lock request has failed - usually system related, such as battery.
+				console.log(err);
+			}
+
+			if (wakeLock) {
+				// Add a listener to release the wake lock when the page is unloaded
+				wakeLock.addEventListener('release', () => {
+					// the wake lock has been released
+					console.log('Wake Lock released');
+				});
+			}
+		};
+
+		if ('wakeLock' in navigator) {
+			await setWakeLock();
+
+			document.addEventListener('visibilitychange', async () => {
+				// Re-request the wake lock if the document becomes visible
+				if (wakeLock !== null && document.visibilityState === 'visible') {
+					await setWakeLock();
+				}
+			});
+		}
+
+		model = $models.find((m) => m.id === modelId);
+
+		startRecording();
+
+		eventTarget.addEventListener('chat:start', chatStartHandler);
+		eventTarget.addEventListener('chat', chatEventHandler);
+		eventTarget.addEventListener('chat:finish', chatFinishHandler);
+
+		return async () => {
+			await stopAllAudio();
+
+			stopAudioStream();
+
+			eventTarget.removeEventListener('chat:start', chatStartHandler);
+			eventTarget.removeEventListener('chat', chatEventHandler);
+			eventTarget.removeEventListener('chat:finish', chatFinishHandler);
+
+			audioAbortController.abort();
+			await tick();
+
+			await stopAllAudio();
+
+			await stopRecordingCallback(false);
+			await stopCamera();
+		};
+	});
+
+	onDestroy(async () => {
+		await stopAllAudio();
+		await stopRecordingCallback(false);
+		await stopCamera();
+
+		await stopAudioStream();
+		eventTarget.removeEventListener('chat:start', chatStartHandler);
+		eventTarget.removeEventListener('chat', chatEventHandler);
+		eventTarget.removeEventListener('chat:finish', chatFinishHandler);
+		audioAbortController.abort();
+
+		await tick();
+
+		await stopAllAudio();
+	});
+</script>
+
+{#if $showCallOverlay}
+	<div class="max-w-lg w-full h-full max-h-[100dvh] flex flex-col justify-between p-3 md:p-6">
+		{#if camera}
+			<button
+				type="button"
+				class="flex justify-center items-center w-full h-20 min-h-20"
+				on:click={() => {
+					if (assistantSpeaking) {
+						stopAllAudio();
+					}
+				}}
+			>
+				{#if emoji}
+					<div
+						class="  transition-all rounded-full"
+						style="font-size:{rmsLevel * 100 > 4
+							? '4.5'
+							: rmsLevel * 100 > 2
+								? '4.25'
+								: rmsLevel * 100 > 1
+									? '3.75'
+									: '3.5'}rem;width: 100%; text-align:center;"
+					>
+						{emoji}
+					</div>
+				{:else if loading || assistantSpeaking}
+					<svg
+						class="size-12 text-gray-900 dark:text-gray-400"
+						viewBox="0 0 24 24"
+						fill="currentColor"
+						xmlns="http://www.w3.org/2000/svg"
+						><style>
+							.spinner_qM83 {
+								animation: spinner_8HQG 1.05s infinite;
+							}
+							.spinner_oXPr {
+								animation-delay: 0.1s;
+							}
+							.spinner_ZTLf {
+								animation-delay: 0.2s;
+							}
+							@keyframes spinner_8HQG {
+								0%,
+								57.14% {
+									animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
+									transform: translate(0);
+								}
+								28.57% {
+									animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
+									transform: translateY(-6px);
+								}
+								100% {
+									transform: translate(0);
+								}
+							}
+						</style><circle class="spinner_qM83" cx="4" cy="12" r="3" /><circle
+							class="spinner_qM83 spinner_oXPr"
+							cx="12"
+							cy="12"
+							r="3"
+						/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="3" /></svg
+					>
+				{:else}
+					<div
+						class=" {rmsLevel * 100 > 4
+							? ' size-[4.5rem]'
+							: rmsLevel * 100 > 2
+								? ' size-16'
+								: rmsLevel * 100 > 1
+									? 'size-14'
+									: 'size-12'}  transition-all rounded-full {(model?.info?.meta
+							?.profile_image_url ?? '/static/favicon.png') !== '/static/favicon.png'
+							? ' bg-cover bg-center bg-no-repeat'
+							: 'bg-black dark:bg-white'}  bg-black dark:bg-white"
+						style={(model?.info?.meta?.profile_image_url ?? '/static/favicon.png') !==
+						'/static/favicon.png'
+							? `background-image: url('${model?.info?.meta?.profile_image_url}');`
+							: ''}
+					/>
+				{/if}
+				<!-- navbar -->
+			</button>
+		{/if}
+
+		<div class="flex justify-center items-center flex-1 h-full w-full max-h-full">
+			{#if !camera}
+				<button
+					type="button"
+					on:click={() => {
+						if (assistantSpeaking) {
+							stopAllAudio();
+						}
+					}}
+				>
+					{#if emoji}
+						<div
+							class="  transition-all rounded-full"
+							style="font-size:{rmsLevel * 100 > 4
+								? '13'
+								: rmsLevel * 100 > 2
+									? '12'
+									: rmsLevel * 100 > 1
+										? '11.5'
+										: '11'}rem;width:100%;text-align:center;"
+						>
+							{emoji}
+						</div>
+					{:else if loading || assistantSpeaking}
+						<svg
+							class="size-44 text-gray-900 dark:text-gray-400"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							xmlns="http://www.w3.org/2000/svg"
+							><style>
+								.spinner_qM83 {
+									animation: spinner_8HQG 1.05s infinite;
+								}
+								.spinner_oXPr {
+									animation-delay: 0.1s;
+								}
+								.spinner_ZTLf {
+									animation-delay: 0.2s;
+								}
+								@keyframes spinner_8HQG {
+									0%,
+									57.14% {
+										animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
+										transform: translate(0);
+									}
+									28.57% {
+										animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
+										transform: translateY(-6px);
+									}
+									100% {
+										transform: translate(0);
+									}
+								}
+							</style><circle class="spinner_qM83" cx="4" cy="12" r="3" /><circle
+								class="spinner_qM83 spinner_oXPr"
+								cx="12"
+								cy="12"
+								r="3"
+							/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="3" /></svg
+						>
+					{:else}
+						<div
+							class=" {rmsLevel * 100 > 4
+								? ' size-52'
+								: rmsLevel * 100 > 2
+									? 'size-48'
+									: rmsLevel * 100 > 1
+										? 'size-44'
+										: 'size-40'}  transition-all rounded-full {(model?.info?.meta
+								?.profile_image_url ?? '/static/favicon.png') !== '/static/favicon.png'
+								? ' bg-cover bg-center bg-no-repeat'
+								: 'bg-black dark:bg-white'} "
+							style={(model?.info?.meta?.profile_image_url ?? '/static/favicon.png') !==
+							'/static/favicon.png'
+								? `background-image: url('${model?.info?.meta?.profile_image_url}');`
+								: ''}
+						/>
+					{/if}
+				</button>
+			{:else}
+				<div class="relative flex video-container w-full max-h-full pt-2 pb-4 md:py-6 px-2 h-full">
+					<video
+						id="camera-feed"
+						autoplay
+						class="rounded-2xl h-full min-w-full object-cover object-center"
+						playsinline
+					/>
+
+					<canvas id="camera-canvas" style="display:none;" />
+
+					<div class=" absolute top-4 md:top-8 left-4">
+						<button
+							type="button"
+							class="p-1.5 text-white cursor-pointer backdrop-blur-xl bg-black/10 rounded-full"
+							on:click={() => {
+								stopCamera();
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 16 16"
+								fill="currentColor"
+								class="size-6"
+							>
+								<path
+									d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z"
+								/>
+							</svg>
+						</button>
+					</div>
+				</div>
+			{/if}
+		</div>
+
+		<div class="flex justify-between items-center pb-2 w-full">
+			<div>
+				{#if camera}
+					<VideoInputMenu
+						devices={videoInputDevices}
+						on:change={async (e) => {
+							console.log(e.detail);
+							selectedVideoInputDeviceId = e.detail;
+							await stopVideoStream();
+							await startVideoStream();
+						}}
+					>
+						<button class=" p-3 rounded-full bg-gray-50 dark:bg-gray-900" type="button">
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="size-5"
+							>
+								<path
+									fill-rule="evenodd"
+									d="M15.312 11.424a5.5 5.5 0 0 1-9.201 2.466l-.312-.311h2.433a.75.75 0 0 0 0-1.5H3.989a.75.75 0 0 0-.75.75v4.242a.75.75 0 0 0 1.5 0v-2.43l.31.31a7 7 0 0 0 11.712-3.138.75.75 0 0 0-1.449-.39Zm1.23-3.723a.75.75 0 0 0 .219-.53V2.929a.75.75 0 0 0-1.5 0V5.36l-.31-.31A7 7 0 0 0 3.239 8.188a.75.75 0 1 0 1.448.389A5.5 5.5 0 0 1 13.89 6.11l.311.31h-2.432a.75.75 0 0 0 0 1.5h4.243a.75.75 0 0 0 .53-.219Z"
+									clip-rule="evenodd"
+								/>
+							</svg>
+						</button>
+					</VideoInputMenu>
+				{:else}
+					<Tooltip content={$i18n.t('Camera')}>
+						<button
+							class=" p-3 rounded-full bg-gray-50 dark:bg-gray-900"
+							type="button"
+							on:click={async () => {
+								await navigator.mediaDevices.getUserMedia({ video: true });
+								startCamera();
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								fill="none"
+								viewBox="0 0 24 24"
+								stroke-width="1.5"
+								stroke="currentColor"
+								class="size-5"
+							>
+								<path
+									stroke-linecap="round"
+									stroke-linejoin="round"
+									d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z"
+								/>
+								<path
+									stroke-linecap="round"
+									stroke-linejoin="round"
+									d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0ZM18.75 10.5h.008v.008h-.008V10.5Z"
+								/>
+							</svg>
+						</button>
+					</Tooltip>
+				{/if}
+			</div>
+
+			<div>
+				<button
+					type="button"
+					on:click={() => {
+						if (assistantSpeaking) {
+							stopAllAudio();
+						}
+					}}
+				>
+					<div class=" line-clamp-1 text-sm font-medium">
+						{#if loading}
+							{$i18n.t('Thinking...')}
+						{:else if assistantSpeaking}
+							{$i18n.t('Tap to interrupt')}
+						{:else}
+							{$i18n.t('Listening...')}
+						{/if}
+					</div>
+				</button>
+			</div>
+
+			<div>
+				<button
+					class=" p-3 rounded-full bg-gray-50 dark:bg-gray-900"
+					on:click={async () => {
+						await stopAudioStream();
+						await stopVideoStream();
+
+						console.log(audioStream);
+						console.log(cameraStream);
+
+						showCallOverlay.set(false);
+						dispatch('close');
+					}}
+					type="button"
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="size-5"
+					>
+						<path
+							d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z"
+						/>
+					</svg>
+				</button>
+			</div>
+		</div>
+	</div>
+{/if}
diff --git a/src/lib/components/chat/MessageInput/CallOverlay/VideoInputMenu.svelte b/src/lib/components/chat/MessageInput/CallOverlay/VideoInputMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..3b0cd0559272bcc2da2485eee0dbf68ae38408d8
--- /dev/null
+++ b/src/lib/components/chat/MessageInput/CallOverlay/VideoInputMenu.svelte
@@ -0,0 +1,51 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+
+	export let onClose: Function = () => {};
+	export let devices: any;
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+>
+	<slot />
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[180px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-[9999] bg-white dark:bg-gray-900 dark:text-white shadow-sm"
+			sideOffset={6}
+			side="top"
+			align="start"
+			transition={flyAndScale}
+		>
+			{#each devices as device}
+				<DropdownMenu.Item
+					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					on:click={() => {
+						dispatch('change', device.deviceId);
+					}}
+				>
+					<div class="flex items-center">
+						<div class=" line-clamp-1">
+							{device?.label ?? 'Camera'}
+						</div>
+					</div>
+				</DropdownMenu.Item>
+			{/each}
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/chat/MessageInput/Commands.svelte b/src/lib/components/chat/MessageInput/Commands.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..09a2f819cde600cdb7ea829bbc02197ae5e1717d
--- /dev/null
+++ b/src/lib/components/chat/MessageInput/Commands.svelte
@@ -0,0 +1,80 @@
+<script>
+	import { createEventDispatcher } from 'svelte';
+	import { toast } from 'svelte-sonner';
+
+	const dispatch = createEventDispatcher();
+
+	import Prompts from './Commands/Prompts.svelte';
+	import Knowledge from './Commands/Knowledge.svelte';
+	import Models from './Commands/Models.svelte';
+
+	import { removeLastWordFromString } from '$lib/utils';
+	import { processWeb, processYoutubeVideo } from '$lib/apis/retrieval';
+
+	export let prompt = '';
+	export let files = [];
+
+	let commandElement = null;
+
+	export const selectUp = () => {
+		commandElement?.selectUp();
+	};
+
+	export const selectDown = () => {
+		commandElement?.selectDown();
+	};
+
+	let command = '';
+	$: command = prompt?.split('\n').pop()?.split(' ')?.pop() ?? '';
+</script>
+
+{#if ['/', '#', '@'].includes(command?.charAt(0)) || '\\#' === command.slice(0, 2)}
+	{#if command?.charAt(0) === '/'}
+		<Prompts bind:this={commandElement} bind:prompt bind:files {command} />
+	{:else if (command?.charAt(0) === '#' && command.startsWith('#') && !command.includes('# ')) || ('\\#' === command.slice(0, 2) && command.startsWith('#') && !command.includes('# '))}
+		<Knowledge
+			bind:this={commandElement}
+			bind:prompt
+			command={command.includes('\\#') ? command.slice(2) : command}
+			on:youtube={(e) => {
+				console.log(e);
+				dispatch('upload', {
+					type: 'youtube',
+					data: e.detail
+				});
+			}}
+			on:url={(e) => {
+				console.log(e);
+				dispatch('upload', {
+					type: 'web',
+					data: e.detail
+				});
+			}}
+			on:select={(e) => {
+				console.log(e);
+				files = [
+					...files,
+					{
+						...e.detail,
+						status: 'processed'
+					}
+				];
+
+				dispatch('select');
+			}}
+		/>
+	{:else if command?.charAt(0) === '@'}
+		<Models
+			bind:this={commandElement}
+			{command}
+			on:select={(e) => {
+				prompt = removeLastWordFromString(prompt, command);
+
+				dispatch('select', {
+					type: 'model',
+					data: e.detail
+				});
+			}}
+		/>
+	{/if}
+{/if}
diff --git a/src/lib/components/chat/MessageInput/Commands/Knowledge.svelte b/src/lib/components/chat/MessageInput/Commands/Knowledge.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7bc69f8459dc1b07676e3de83ee46e9535145d1a
--- /dev/null
+++ b/src/lib/components/chat/MessageInput/Commands/Knowledge.svelte
@@ -0,0 +1,321 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import Fuse from 'fuse.js';
+
+	import dayjs from 'dayjs';
+	import relativeTime from 'dayjs/plugin/relativeTime';
+	dayjs.extend(relativeTime);
+
+	import { createEventDispatcher, tick, getContext, onMount } from 'svelte';
+	import { removeLastWordFromString, isValidHttpUrl } from '$lib/utils';
+	import { knowledge } from '$lib/stores';
+
+	const i18n = getContext('i18n');
+
+	export let prompt = '';
+	export let command = '';
+
+	const dispatch = createEventDispatcher();
+	let selectedIdx = 0;
+
+	let items = [];
+	let fuse = null;
+
+	let filteredItems = [];
+	$: if (fuse) {
+		filteredItems = command.slice(1)
+			? fuse.search(command).map((e) => {
+					return e.item;
+				})
+			: items;
+	}
+
+	$: if (command) {
+		selectedIdx = 0;
+	}
+
+	export const selectUp = () => {
+		selectedIdx = Math.max(0, selectedIdx - 1);
+	};
+
+	export const selectDown = () => {
+		selectedIdx = Math.min(selectedIdx + 1, filteredItems.length - 1);
+	};
+
+	const confirmSelect = async (item) => {
+		dispatch('select', item);
+
+		prompt = removeLastWordFromString(prompt, command);
+		const chatInputElement = document.getElementById('chat-input');
+
+		await tick();
+		chatInputElement?.focus();
+		await tick();
+	};
+
+	const confirmSelectWeb = async (url) => {
+		dispatch('url', url);
+
+		prompt = removeLastWordFromString(prompt, command);
+		const chatInputElement = document.getElementById('chat-input');
+
+		await tick();
+		chatInputElement?.focus();
+		await tick();
+	};
+
+	const confirmSelectYoutube = async (url) => {
+		dispatch('youtube', url);
+
+		prompt = removeLastWordFromString(prompt, command);
+		const chatInputElement = document.getElementById('chat-input');
+
+		await tick();
+		chatInputElement?.focus();
+		await tick();
+	};
+
+	onMount(() => {
+		let legacy_documents = $knowledge
+			.filter((item) => item?.meta?.document)
+			.map((item) => ({
+				...item,
+				type: 'file'
+			}));
+
+		let legacy_collections =
+			legacy_documents.length > 0
+				? [
+						{
+							name: 'All Documents',
+							legacy: true,
+							type: 'collection',
+							description: 'Deprecated (legacy collection), please create a new knowledge base.',
+							title: $i18n.t('All Documents'),
+							collection_names: legacy_documents.map((item) => item.id)
+						},
+
+						...legacy_documents
+							.reduce((a, item) => {
+								return [...new Set([...a, ...(item?.meta?.tags ?? []).map((tag) => tag.name)])];
+							}, [])
+							.map((tag) => ({
+								name: tag,
+								legacy: true,
+								type: 'collection',
+								description: 'Deprecated (legacy collection), please create a new knowledge base.',
+								collection_names: legacy_documents
+									.filter((item) => (item?.meta?.tags ?? []).map((tag) => tag.name).includes(tag))
+									.map((item) => item.id)
+							}))
+					]
+				: [];
+
+		let collections = $knowledge
+			.filter((item) => !item?.meta?.document)
+			.map((item) => ({
+				...item,
+				type: 'collection'
+			}));
+		let collection_files =
+			$knowledge.length > 0
+				? [
+						...$knowledge
+							.reduce((a, item) => {
+								return [
+									...new Set([
+										...a,
+										...(item?.files ?? []).map((file) => ({
+											...file,
+											collection: { name: item.name, description: item.description }
+										}))
+									])
+								];
+							}, [])
+							.map((file) => ({
+								...file,
+								name: file?.meta?.name,
+								description: `${file?.collection?.name} - ${file?.collection?.description}`,
+								type: 'file'
+							}))
+					]
+				: [];
+
+		items = [...collections, ...collection_files, ...legacy_collections, ...legacy_documents].map(
+			(item) => {
+				return {
+					...item,
+					...(item?.legacy || item?.meta?.legacy || item?.meta?.document ? { legacy: true } : {})
+				};
+			}
+		);
+
+		fuse = new Fuse(items, {
+			keys: ['name', 'description']
+		});
+	});
+</script>
+
+{#if filteredItems.length > 0 || prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
+	<div
+		id="commands-container"
+		class="pl-3 pr-14 mb-3 text-left w-full absolute bottom-0 left-0 right-0 z-10"
+	>
+		<div class="flex w-full rounded-xl border border-gray-50 dark:border-gray-850">
+			<div
+				class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
+			>
+				<div class="m-1 overflow-y-auto p-1 rounded-r-xl space-y-0.5 scrollbar-hidden">
+					{#each filteredItems as item, idx}
+						<button
+							class=" px-3 py-1.5 rounded-xl w-full text-left flex justify-between items-center {idx ===
+							selectedIdx
+								? ' bg-gray-50 dark:bg-gray-850 dark:text-gray-100 selected-command-option-button'
+								: ''}"
+							type="button"
+							on:click={() => {
+								console.log(item);
+								confirmSelect(item);
+							}}
+							on:mousemove={() => {
+								selectedIdx = idx;
+							}}
+						>
+							<div>
+								<div class=" font-medium text-black dark:text-gray-100 flex items-center gap-1">
+									{#if item.legacy}
+										<div
+											class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1 flex-shrink-0"
+										>
+											Legacy
+										</div>
+									{:else if item?.meta?.document}
+										<div
+											class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1 flex-shrink-0"
+										>
+											Document
+										</div>
+									{:else if item?.type === 'file'}
+										<div
+											class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1 flex-shrink-0"
+										>
+											File
+										</div>
+									{:else}
+										<div
+											class="bg-green-500/20 text-green-700 dark:text-green-200 rounded uppercase text-xs font-bold px-1 flex-shrink-0"
+										>
+											Collection
+										</div>
+									{/if}
+
+									<div class="line-clamp-1">
+										{item?.name}
+									</div>
+								</div>
+
+								<div class=" text-xs text-gray-600 dark:text-gray-100 line-clamp-1">
+									{item?.description}
+								</div>
+							</div>
+						</button>
+
+						<!-- <div slot="content" class=" pl-2 pt-1 flex flex-col gap-0.5">
+								{#if !item.legacy && (item?.files ?? []).length > 0}
+									{#each item?.files ?? [] as file, fileIdx}
+										<button
+											class=" px-3 py-1.5 rounded-xl w-full text-left flex justify-between items-center hover:bg-gray-50 dark:hover:bg-gray-850 dark:hover:text-gray-100 selected-command-option-button"
+											type="button"
+											on:click={() => {
+												console.log(file);
+											}}
+											on:mousemove={() => {
+												selectedIdx = idx;
+											}}
+										>
+											<div>
+												<div
+													class=" font-medium text-black dark:text-gray-100 flex items-center gap-1"
+												>
+													<div
+														class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1 flex-shrink-0"
+													>
+														File
+													</div>
+
+													<div class="line-clamp-1">
+														{file?.meta?.name}
+													</div>
+												</div>
+
+												<div class=" text-xs text-gray-600 dark:text-gray-100 line-clamp-1">
+													{$i18n.t('Updated')}
+													{dayjs(file.updated_at * 1000).fromNow()}
+												</div>
+											</div>
+										</button>
+									{/each}
+								{:else}
+									<div class=" text-gray-500 text-xs mt-1 mb-2">
+										{$i18n.t('No files found.')}
+									</div>
+								{/if}
+							</div> -->
+					{/each}
+
+					{#if prompt
+						.split(' ')
+						.some((s) => s.substring(1).startsWith('https://www.youtube.com') || s
+									.substring(1)
+									.startsWith('https://youtu.be'))}
+						<button
+							class="px-3 py-1.5 rounded-xl w-full text-left bg-gray-50 dark:bg-gray-850 dark:text-gray-100 selected-command-option-button"
+							type="button"
+							on:click={() => {
+								const url = prompt.split(' ')?.at(0)?.substring(1);
+								if (isValidHttpUrl(url)) {
+									confirmSelectYoutube(url);
+								} else {
+									toast.error(
+										$i18n.t(
+											'Oops! Looks like the URL is invalid. Please double-check and try again.'
+										)
+									);
+								}
+							}}
+						>
+							<div class=" font-medium text-black dark:text-gray-100 line-clamp-1">
+								{prompt.split(' ')?.at(0)?.substring(1)}
+							</div>
+
+							<div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Youtube')}</div>
+						</button>
+					{:else if prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
+						<button
+							class="px-3 py-1.5 rounded-xl w-full text-left bg-gray-50 dark:bg-gray-850 dark:text-gray-100 selected-command-option-button"
+							type="button"
+							on:click={() => {
+								const url = prompt.split(' ')?.at(0)?.substring(1);
+								if (isValidHttpUrl(url)) {
+									confirmSelectWeb(url);
+								} else {
+									toast.error(
+										$i18n.t(
+											'Oops! Looks like the URL is invalid. Please double-check and try again.'
+										)
+									);
+								}
+							}}
+						>
+							<div class=" font-medium text-black dark:text-gray-100 line-clamp-1">
+								{prompt.split(' ')?.at(0)?.substring(1)}
+							</div>
+
+							<div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Web')}</div>
+						</button>
+					{/if}
+				</div>
+			</div>
+		</div>
+	</div>
+{/if}
diff --git a/src/lib/components/chat/MessageInput/Commands/Models.svelte b/src/lib/components/chat/MessageInput/Commands/Models.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..169b127a7442ac6f4324fceea755abc441da184b
--- /dev/null
+++ b/src/lib/components/chat/MessageInput/Commands/Models.svelte
@@ -0,0 +1,106 @@
+<script lang="ts">
+	import Fuse from 'fuse.js';
+
+	import { createEventDispatcher, onMount } from 'svelte';
+	import { tick, getContext } from 'svelte';
+
+	import { models } from '$lib/stores';
+
+	const i18n = getContext('i18n');
+
+	const dispatch = createEventDispatcher();
+
+	export let command = '';
+
+	let selectedIdx = 0;
+	let filteredItems = [];
+
+	let fuse = new Fuse(
+		$models
+			.filter((model) => !model?.info?.meta?.hidden)
+			.map((model) => {
+				const _item = {
+					...model,
+					modelName: model?.name,
+					tags: model?.info?.meta?.tags?.map((tag) => tag.name).join(' '),
+					desc: model?.info?.meta?.description
+				};
+				return _item;
+			}),
+		{
+			keys: ['value', 'tags', 'modelName'],
+			threshold: 0.3
+		}
+	);
+
+	$: filteredItems = command.slice(1)
+		? fuse.search(command).map((e) => {
+				return e.item;
+			})
+		: $models.filter((model) => !model?.info?.meta?.hidden);
+
+	$: if (command) {
+		selectedIdx = 0;
+	}
+
+	export const selectUp = () => {
+		selectedIdx = Math.max(0, selectedIdx - 1);
+	};
+
+	export const selectDown = () => {
+		selectedIdx = Math.min(selectedIdx + 1, filteredItems.length - 1);
+	};
+
+	const confirmSelect = async (model) => {
+		command = '';
+		dispatch('select', model);
+	};
+
+	onMount(async () => {
+		await tick();
+		const chatInputElement = document.getElementById('chat-input');
+		await tick();
+		chatInputElement?.focus();
+		await tick();
+	});
+</script>
+
+{#if filteredItems.length > 0}
+	<div
+		id="commands-container"
+		class="pl-3 pr-14 mb-3 text-left w-full absolute bottom-0 left-0 right-0 z-10"
+	>
+		<div class="flex w-full rounded-xl border border-gray-50 dark:border-gray-850">
+			<div
+				class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
+			>
+				<div class="m-1 overflow-y-auto p-1 rounded-r-lg space-y-0.5 scrollbar-hidden">
+					{#each filteredItems as model, modelIdx}
+						<button
+							class="px-3 py-1.5 rounded-xl w-full text-left {modelIdx === selectedIdx
+								? 'bg-gray-50 dark:bg-gray-850 selected-command-option-button'
+								: ''}"
+							type="button"
+							on:click={() => {
+								confirmSelect(model);
+							}}
+							on:mousemove={() => {
+								selectedIdx = modelIdx;
+							}}
+							on:focus={() => {}}
+						>
+							<div class="flex font-medium text-black dark:text-gray-100 line-clamp-1">
+								<img
+									src={model?.info?.meta?.profile_image_url ?? '/static/favicon.png'}
+									alt={model?.name ?? model.id}
+									class="rounded-full size-6 items-center mr-2"
+								/>
+								{model.name}
+							</div>
+						</button>
+					{/each}
+				</div>
+			</div>
+		</div>
+	</div>
+{/if}
diff --git a/src/lib/components/chat/MessageInput/Commands/Prompts.svelte b/src/lib/components/chat/MessageInput/Commands/Prompts.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a290e81ae807f067cd8ecde0f8b26474725e6fdb
--- /dev/null
+++ b/src/lib/components/chat/MessageInput/Commands/Prompts.svelte
@@ -0,0 +1,201 @@
+<script lang="ts">
+	import { prompts, user } from '$lib/stores';
+	import {
+		findWordIndices,
+		getUserPosition,
+		getFormattedDate,
+		getFormattedTime,
+		getCurrentDateTime,
+		getUserTimezone,
+		getWeekday
+	} from '$lib/utils';
+	import { tick, getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+
+	const i18n = getContext('i18n');
+
+	export let files;
+
+	export let prompt = '';
+	export let command = '';
+
+	let selectedPromptIdx = 0;
+	let filteredPrompts = [];
+
+	$: filteredPrompts = $prompts
+		.filter((p) => p.command.toLowerCase().includes(command.toLowerCase()))
+		.sort((a, b) => a.title.localeCompare(b.title));
+
+	$: if (command) {
+		selectedPromptIdx = 0;
+	}
+
+	export const selectUp = () => {
+		selectedPromptIdx = Math.max(0, selectedPromptIdx - 1);
+	};
+
+	export const selectDown = () => {
+		selectedPromptIdx = Math.min(selectedPromptIdx + 1, filteredPrompts.length - 1);
+	};
+
+	const confirmPrompt = async (command) => {
+		let text = command.content;
+
+		if (command.content.includes('{{CLIPBOARD}}')) {
+			const clipboardText = await navigator.clipboard.readText().catch((err) => {
+				toast.error($i18n.t('Failed to read clipboard contents'));
+				return '{{CLIPBOARD}}';
+			});
+
+			const clipboardItems = await navigator.clipboard.read();
+
+			let imageUrl = null;
+			for (const item of clipboardItems) {
+				// Check for known image types
+				for (const type of item.types) {
+					if (type.startsWith('image/')) {
+						const blob = await item.getType(type);
+						imageUrl = URL.createObjectURL(blob);
+					}
+				}
+			}
+
+			if (imageUrl) {
+				files = [
+					...files,
+					{
+						type: 'image',
+						url: imageUrl
+					}
+				];
+			}
+
+			text = text.replaceAll('{{CLIPBOARD}}', clipboardText);
+		}
+
+		if (command.content.includes('{{USER_LOCATION}}')) {
+			const location = await getUserPosition();
+			text = text.replaceAll('{{USER_LOCATION}}', String(location));
+		}
+
+		if (command.content.includes('{{USER_NAME}}')) {
+			console.log($user);
+			const name = $user.name || 'User';
+			text = text.replaceAll('{{USER_NAME}}', name);
+		}
+
+		if (command.content.includes('{{USER_LANGUAGE}}')) {
+			const language = localStorage.getItem('locale') || 'en-US';
+			text = text.replaceAll('{{USER_LANGUAGE}}', language);
+		}
+
+		if (command.content.includes('{{CURRENT_DATE}}')) {
+			const date = getFormattedDate();
+			text = text.replaceAll('{{CURRENT_DATE}}', date);
+		}
+
+		if (command.content.includes('{{CURRENT_TIME}}')) {
+			const time = getFormattedTime();
+			text = text.replaceAll('{{CURRENT_TIME}}', time);
+		}
+
+		if (command.content.includes('{{CURRENT_DATETIME}}')) {
+			const dateTime = getCurrentDateTime();
+			text = text.replaceAll('{{CURRENT_DATETIME}}', dateTime);
+		}
+
+		if (command.content.includes('{{CURRENT_TIMEZONE}}')) {
+			const timezone = getUserTimezone();
+			text = text.replaceAll('{{CURRENT_TIMEZONE}}', timezone);
+		}
+
+		if (command.content.includes('{{CURRENT_WEEKDAY}}')) {
+			const weekday = getWeekday();
+			text = text.replaceAll('{{CURRENT_WEEKDAY}}', weekday);
+		}
+
+		prompt = text;
+
+		const chatInputContainerElement = document.getElementById('chat-input-container');
+		const chatInputElement = document.getElementById('chat-input');
+
+		await tick();
+		if (chatInputContainerElement) {
+			chatInputContainerElement.style.height = '';
+			chatInputContainerElement.style.height =
+				Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
+		}
+
+		await tick();
+		if (chatInputElement) {
+			chatInputElement.focus();
+			chatInputElement.dispatchEvent(new Event('input'));
+		}
+	};
+</script>
+
+{#if filteredPrompts.length > 0}
+	<div
+		id="commands-container"
+		class="pl-3 pr-14 mb-3 text-left w-full absolute bottom-0 left-0 right-0 z-10"
+	>
+		<div class="flex w-full rounded-xl border border-gray-50 dark:border-gray-850">
+			<div
+				class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
+			>
+				<div class="m-1 overflow-y-auto p-1 space-y-0.5 scrollbar-hidden">
+					{#each filteredPrompts as prompt, promptIdx}
+						<button
+							class=" px-3 py-1.5 rounded-xl w-full text-left {promptIdx === selectedPromptIdx
+								? '  bg-gray-50 dark:bg-gray-850 selected-command-option-button'
+								: ''}"
+							type="button"
+							on:click={() => {
+								confirmPrompt(prompt);
+							}}
+							on:mousemove={() => {
+								selectedPromptIdx = promptIdx;
+							}}
+							on:focus={() => {}}
+						>
+							<div class=" font-medium text-black dark:text-gray-100">
+								{prompt.command}
+							</div>
+
+							<div class=" text-xs text-gray-600 dark:text-gray-100">
+								{prompt.title}
+							</div>
+						</button>
+					{/each}
+				</div>
+
+				<div
+					class=" px-2 pt-0.5 pb-1 text-xs text-gray-600 dark:text-gray-100 bg-white dark:bg-gray-900 rounded-b-xl flex items-center space-x-1"
+				>
+					<div>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="1.5"
+							stroke="currentColor"
+							class="w-3 h-3"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
+							/>
+						</svg>
+					</div>
+
+					<div class="line-clamp-1">
+						{$i18n.t(
+							'Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.'
+						)}
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+{/if}
diff --git a/src/lib/components/chat/MessageInput/FilesOverlay.svelte b/src/lib/components/chat/MessageInput/FilesOverlay.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a8acc6a3e22309d9840331bd93e0c434e6745274
--- /dev/null
+++ b/src/lib/components/chat/MessageInput/FilesOverlay.svelte
@@ -0,0 +1,35 @@
+<script lang="ts">
+	import { showSidebar } from '$lib/stores';
+	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
+
+	export let show = false;
+	let overlayElement = null;
+
+	$: if (show && overlayElement) {
+		document.body.appendChild(overlayElement);
+		document.body.style.overflow = 'hidden';
+	} else if (overlayElement) {
+		document.body.removeChild(overlayElement);
+		document.body.style.overflow = 'unset';
+	}
+</script>
+
+{#if show}
+	<div
+		bind:this={overlayElement}
+		class="fixed {$showSidebar
+			? 'left-0 md:left-[260px] md:w-[calc(100%-260px)]'
+			: 'left-0'}  fixed top-0 right-0 bottom-0 w-full h-full flex z-[9999] touch-none pointer-events-none"
+		id="dropzone"
+		role="region"
+		aria-label="Drag and Drop Container"
+	>
+		<div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
+			<div class="m-auto pt-64 flex flex-col justify-center">
+				<div class="max-w-md">
+					<AddFilesPlaceholder />
+				</div>
+			</div>
+		</div>
+	</div>
+{/if}
diff --git a/src/lib/components/chat/MessageInput/InputMenu.svelte b/src/lib/components/chat/MessageInput/InputMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9cb1b619888429595035058dedeb11c4e23779b8
--- /dev/null
+++ b/src/lib/components/chat/MessageInput/InputMenu.svelte
@@ -0,0 +1,117 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext } from 'svelte';
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import DocumentArrowUpSolid from '$lib/components/icons/DocumentArrowUpSolid.svelte';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import GlobeAltSolid from '$lib/components/icons/GlobeAltSolid.svelte';
+	import { config } from '$lib/stores';
+	import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let uploadFilesHandler: Function;
+
+	export let selectedToolIds: string[] = [];
+	export let webSearchEnabled: boolean;
+
+	export let tools = {};
+	export let onClose: Function;
+
+	$: tools = Object.fromEntries(
+		Object.keys(tools).map((toolId) => [
+			toolId,
+			{
+				...tools[toolId],
+				enabled: selectedToolIds.includes(toolId)
+			}
+		])
+	);
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+>
+	<Tooltip content={$i18n.t('More')}>
+		<slot />
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[200px] rounded-xl px-1 py-1  border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			sideOffset={15}
+			alignOffset={-8}
+			side="top"
+			align="start"
+			transition={flyAndScale}
+		>
+			{#if Object.keys(tools).length > 0}
+				<div class="  max-h-28 overflow-y-auto scrollbar-hidden">
+					{#each Object.keys(tools) as toolId}
+						<div
+							class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
+						>
+							<div class="flex-1">
+								<Tooltip
+									content={tools[toolId]?.description ?? ''}
+									placement="top-start"
+									className="flex flex-1  gap-2 items-center"
+								>
+									<WrenchSolid />
+
+									<div class=" line-clamp-1">{tools[toolId].name}</div>
+								</Tooltip>
+							</div>
+
+							<Switch
+								bind:state={tools[toolId].enabled}
+								on:change={(e) => {
+									selectedToolIds = e.detail
+										? [...selectedToolIds, toolId]
+										: selectedToolIds.filter((id) => id !== toolId);
+								}}
+							/>
+						</div>
+					{/each}
+				</div>
+
+				<hr class="border-gray-100 dark:border-gray-800 my-1" />
+			{/if}
+
+			{#if $config?.features?.enable_web_search}
+				<div
+					class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
+				>
+					<div class="flex-1 flex items-center gap-2">
+						<GlobeAltSolid />
+						<div class=" line-clamp-1">{$i18n.t('Web Search')}</div>
+					</div>
+
+					<Switch bind:state={webSearchEnabled} />
+				</div>
+
+				<hr class="border-gray-100 dark:border-gray-800 my-1" />
+			{/if}
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800  rounded-xl"
+				on:click={() => {
+					uploadFilesHandler();
+				}}
+			>
+				<DocumentArrowUpSolid />
+				<div class=" line-clamp-1">{$i18n.t('Upload Files')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/chat/MessageInput/VoiceRecording.svelte b/src/lib/components/chat/MessageInput/VoiceRecording.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..03b5cec1e9d61267860faf5b2a0601af83d9b363
--- /dev/null
+++ b/src/lib/components/chat/MessageInput/VoiceRecording.svelte
@@ -0,0 +1,508 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { createEventDispatcher, tick, getContext, onMount, onDestroy } from 'svelte';
+	import { config, settings } from '$lib/stores';
+	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
+
+	import { transcribeAudio } from '$lib/apis/audio';
+
+	const i18n = getContext('i18n');
+
+	const dispatch = createEventDispatcher();
+
+	export let recording = false;
+	export let className = ' p-2.5 w-full max-w-full';
+
+	let loading = false;
+	let confirmed = false;
+
+	let durationSeconds = 0;
+	let durationCounter = null;
+
+	let transcription = '';
+
+	const startDurationCounter = () => {
+		durationCounter = setInterval(() => {
+			durationSeconds++;
+		}, 1000);
+	};
+
+	const stopDurationCounter = () => {
+		clearInterval(durationCounter);
+		durationSeconds = 0;
+	};
+
+	$: if (recording) {
+		startRecording();
+	} else {
+		stopRecording();
+	}
+
+	const formatSeconds = (seconds) => {
+		const minutes = Math.floor(seconds / 60);
+		const remainingSeconds = seconds % 60;
+		const formattedSeconds = remainingSeconds < 10 ? `0${remainingSeconds}` : remainingSeconds;
+		return `${minutes}:${formattedSeconds}`;
+	};
+
+	let stream;
+	let speechRecognition;
+
+	let mediaRecorder;
+	let audioChunks = [];
+
+	const MIN_DECIBELS = -45;
+	let VISUALIZER_BUFFER_LENGTH = 300;
+
+	let visualizerData = Array(VISUALIZER_BUFFER_LENGTH).fill(0);
+
+	// Function to calculate the RMS level from time domain data
+	const calculateRMS = (data: Uint8Array) => {
+		let sumSquares = 0;
+		for (let i = 0; i < data.length; i++) {
+			const normalizedValue = (data[i] - 128) / 128; // Normalize the data
+			sumSquares += normalizedValue * normalizedValue;
+		}
+		return Math.sqrt(sumSquares / data.length);
+	};
+
+	const normalizeRMS = (rms) => {
+		rms = rms * 10;
+		const exp = 1.5; // Adjust exponent value; values greater than 1 expand larger numbers more and compress smaller numbers more
+		const scaledRMS = Math.pow(rms, exp);
+
+		// Scale between 0.01 (1%) and 1.0 (100%)
+		return Math.min(1.0, Math.max(0.01, scaledRMS));
+	};
+
+	const analyseAudio = (stream) => {
+		const audioContext = new AudioContext();
+		const audioStreamSource = audioContext.createMediaStreamSource(stream);
+
+		const analyser = audioContext.createAnalyser();
+		analyser.minDecibels = MIN_DECIBELS;
+		audioStreamSource.connect(analyser);
+
+		const bufferLength = analyser.frequencyBinCount;
+
+		const domainData = new Uint8Array(bufferLength);
+		const timeDomainData = new Uint8Array(analyser.fftSize);
+
+		let lastSoundTime = Date.now();
+
+		const detectSound = () => {
+			const processFrame = () => {
+				if (!recording || loading) return;
+
+				if (recording && !loading) {
+					analyser.getByteTimeDomainData(timeDomainData);
+					analyser.getByteFrequencyData(domainData);
+
+					// Calculate RMS level from time domain data
+					const rmsLevel = calculateRMS(timeDomainData);
+					// Push the calculated decibel level to visualizerData
+					visualizerData.push(normalizeRMS(rmsLevel));
+
+					// Ensure visualizerData array stays within the buffer length
+					if (visualizerData.length >= VISUALIZER_BUFFER_LENGTH) {
+						visualizerData.shift();
+					}
+
+					visualizerData = visualizerData;
+
+					// if (domainData.some((value) => value > 0)) {
+					// 	lastSoundTime = Date.now();
+					// }
+
+					// if (recording && Date.now() - lastSoundTime > 3000) {
+					// 	if ($settings?.speechAutoSend ?? false) {
+					// 		confirmRecording();
+					// 	}
+					// }
+				}
+
+				window.requestAnimationFrame(processFrame);
+			};
+
+			window.requestAnimationFrame(processFrame);
+		};
+
+		detectSound();
+	};
+
+	const transcribeHandler = async (audioBlob) => {
+		// Create a blob from the audio chunks
+
+		await tick();
+		const file = blobToFile(audioBlob, 'recording.wav');
+
+		const res = await transcribeAudio(localStorage.token, file).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			console.log(res);
+			dispatch('confirm', res);
+		}
+	};
+
+	const saveRecording = (blob) => {
+		const url = URL.createObjectURL(blob);
+		const a = document.createElement('a');
+		document.body.appendChild(a);
+		a.style = 'display: none';
+		a.href = url;
+		a.download = 'recording.wav';
+		a.click();
+		window.URL.revokeObjectURL(url);
+	};
+
+	const startRecording = async () => {
+		startDurationCounter();
+
+		stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+		mediaRecorder = new MediaRecorder(stream);
+		mediaRecorder.onstart = () => {
+			console.log('Recording started');
+			audioChunks = [];
+			analyseAudio(stream);
+		};
+		mediaRecorder.ondataavailable = (event) => audioChunks.push(event.data);
+		mediaRecorder.onstop = async () => {
+			console.log('Recording stopped');
+			if (($settings?.audio?.stt?.engine ?? '') === 'web') {
+				audioChunks = [];
+			} else {
+				if (confirmed) {
+					const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
+
+					await transcribeHandler(audioBlob);
+
+					confirmed = false;
+					loading = false;
+				}
+				audioChunks = [];
+				recording = false;
+			}
+		};
+		mediaRecorder.start();
+		if ($config.audio.stt.engine === 'web' || ($settings?.audio?.stt?.engine ?? '') === 'web') {
+			if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
+				// Create a SpeechRecognition object
+				speechRecognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
+
+				// Set continuous to true for continuous recognition
+				speechRecognition.continuous = true;
+
+				// Set the timeout for turning off the recognition after inactivity (in milliseconds)
+				const inactivityTimeout = 2000; // 3 seconds
+
+				let timeoutId;
+				// Start recognition
+				speechRecognition.start();
+
+				// Event triggered when speech is recognized
+				speechRecognition.onresult = async (event) => {
+					// Clear the inactivity timeout
+					clearTimeout(timeoutId);
+
+					// Handle recognized speech
+					console.log(event);
+					const transcript = event.results[Object.keys(event.results).length - 1][0].transcript;
+
+					transcription = `${transcription}${transcript}`;
+
+					await tick();
+					document.getElementById('chat-input')?.focus();
+
+					// Restart the inactivity timeout
+					timeoutId = setTimeout(() => {
+						console.log('Speech recognition turned off due to inactivity.');
+						speechRecognition.stop();
+					}, inactivityTimeout);
+				};
+
+				// Event triggered when recognition is ended
+				speechRecognition.onend = function () {
+					// Restart recognition after it ends
+					console.log('recognition ended');
+
+					confirmRecording();
+					dispatch('confirm', transcription);
+
+					confirmed = false;
+					loading = false;
+				};
+
+				// Event triggered when an error occurs
+				speechRecognition.onerror = function (event) {
+					console.log(event);
+					toast.error($i18n.t(`Speech recognition error: {{error}}`, { error: event.error }));
+					dispatch('cancel');
+
+					stopRecording();
+				};
+			}
+		}
+	};
+
+	const stopRecording = async () => {
+		if (recording && mediaRecorder) {
+			await mediaRecorder.stop();
+		}
+		stopDurationCounter();
+		audioChunks = [];
+
+		if (stream) {
+			const tracks = stream.getTracks();
+			tracks.forEach((track) => track.stop());
+		}
+
+		stream = null;
+	};
+
+	const confirmRecording = async () => {
+		loading = true;
+		confirmed = true;
+
+		if (recording && mediaRecorder) {
+			await mediaRecorder.stop();
+		}
+		clearInterval(durationCounter);
+
+		if (stream) {
+			const tracks = stream.getTracks();
+			tracks.forEach((track) => track.stop());
+		}
+
+		stream = null;
+	};
+
+	let resizeObserver;
+	let containerWidth;
+
+	let maxVisibleItems = 300;
+	$: maxVisibleItems = Math.floor(containerWidth / 5); // 2px width + 0.5px gap
+
+	onMount(() => {
+		// listen to width changes
+		resizeObserver = new ResizeObserver(() => {
+			VISUALIZER_BUFFER_LENGTH = Math.floor(window.innerWidth / 4);
+			if (visualizerData.length > VISUALIZER_BUFFER_LENGTH) {
+				visualizerData = visualizerData.slice(visualizerData.length - VISUALIZER_BUFFER_LENGTH);
+			} else {
+				visualizerData = Array(VISUALIZER_BUFFER_LENGTH - visualizerData.length)
+					.fill(0)
+					.concat(visualizerData);
+			}
+		});
+
+		resizeObserver.observe(document.body);
+	});
+
+	onDestroy(() => {
+		// remove resize observer
+		resizeObserver.disconnect();
+	});
+</script>
+
+<div
+	bind:clientWidth={containerWidth}
+	class="{loading
+		? ' bg-gray-100/50 dark:bg-gray-850/50'
+		: 'bg-indigo-300/10 dark:bg-indigo-500/10 '} rounded-full flex justify-between {className}"
+>
+	<div class="flex items-center mr-1">
+		<button
+			type="button"
+			class="p-1.5
+
+            {loading
+				? ' bg-gray-200 dark:bg-gray-700/50'
+				: 'bg-indigo-400/20 text-indigo-600 dark:text-indigo-300 '} 
+
+
+             rounded-full"
+			on:click={async () => {
+				dispatch('cancel');
+				stopRecording();
+			}}
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				fill="none"
+				viewBox="0 0 24 24"
+				stroke-width="3"
+				stroke="currentColor"
+				class="size-4"
+			>
+				<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
+			</svg>
+		</button>
+	</div>
+
+	<div
+		class="flex flex-1 self-center items-center justify-between ml-2 mx-1 overflow-hidden h-6"
+		dir="rtl"
+	>
+		<div
+			class="flex items-center gap-0.5 h-6 w-full max-w-full overflow-hidden overflow-x-hidden flex-wrap"
+		>
+			{#each visualizerData.slice().reverse() as rms}
+				<div class="flex items-center h-full">
+					<div
+						class="w-[2px] flex-shrink-0
+                    
+                    {loading
+							? ' bg-gray-500 dark:bg-gray-400   '
+							: 'bg-indigo-500 dark:bg-indigo-400  '} 
+                    
+                    inline-block h-full"
+						style="height: {Math.min(100, Math.max(14, rms * 100))}%;"
+					/>
+				</div>
+			{/each}
+		</div>
+	</div>
+
+	<div class="flex">
+		<div class="  mx-1.5 pr-1 flex justify-center items-center">
+			<div
+				class="text-sm
+        
+        
+        {loading ? ' text-gray-500  dark:text-gray-400  ' : ' text-indigo-400 '} 
+       font-medium flex-1 mx-auto text-center"
+			>
+				{formatSeconds(durationSeconds)}
+			</div>
+		</div>
+
+		<div class="flex items-center">
+			{#if loading}
+				<div class=" text-gray-500 rounded-full cursor-not-allowed">
+					<svg
+						width="24"
+						height="24"
+						viewBox="0 0 24 24"
+						xmlns="http://www.w3.org/2000/svg"
+						fill="currentColor"
+						><style>
+							.spinner_OSmW {
+								transform-origin: center;
+								animation: spinner_T6mA 0.75s step-end infinite;
+							}
+							@keyframes spinner_T6mA {
+								8.3% {
+									transform: rotate(30deg);
+								}
+								16.6% {
+									transform: rotate(60deg);
+								}
+								25% {
+									transform: rotate(90deg);
+								}
+								33.3% {
+									transform: rotate(120deg);
+								}
+								41.6% {
+									transform: rotate(150deg);
+								}
+								50% {
+									transform: rotate(180deg);
+								}
+								58.3% {
+									transform: rotate(210deg);
+								}
+								66.6% {
+									transform: rotate(240deg);
+								}
+								75% {
+									transform: rotate(270deg);
+								}
+								83.3% {
+									transform: rotate(300deg);
+								}
+								91.6% {
+									transform: rotate(330deg);
+								}
+								100% {
+									transform: rotate(360deg);
+								}
+							}
+						</style><g class="spinner_OSmW"
+							><rect x="11" y="1" width="2" height="5" opacity=".14" /><rect
+								x="11"
+								y="1"
+								width="2"
+								height="5"
+								transform="rotate(30 12 12)"
+								opacity=".29"
+							/><rect
+								x="11"
+								y="1"
+								width="2"
+								height="5"
+								transform="rotate(60 12 12)"
+								opacity=".43"
+							/><rect
+								x="11"
+								y="1"
+								width="2"
+								height="5"
+								transform="rotate(90 12 12)"
+								opacity=".57"
+							/><rect
+								x="11"
+								y="1"
+								width="2"
+								height="5"
+								transform="rotate(120 12 12)"
+								opacity=".71"
+							/><rect
+								x="11"
+								y="1"
+								width="2"
+								height="5"
+								transform="rotate(150 12 12)"
+								opacity=".86"
+							/><rect x="11" y="1" width="2" height="5" transform="rotate(180 12 12)" /></g
+						></svg
+					>
+				</div>
+			{:else}
+				<button
+					type="button"
+					class="p-1.5 bg-indigo-500 text-white dark:bg-indigo-500 dark:text-blue-950 rounded-full"
+					on:click={async () => {
+						await confirmRecording();
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="2.5"
+						stroke="currentColor"
+						class="size-4"
+					>
+						<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
+					</svg>
+				</button>
+			{/if}
+		</div>
+	</div>
+</div>
+
+<style>
+	.visualizer {
+		display: flex;
+		height: 100%;
+	}
+
+	.visualizer-bar {
+		width: 2px;
+		background-color: #4a5aba; /* or whatever color you need */
+	}
+</style>
diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7a95575e9c7c4c79f8c4977cb981a8f168e4d9b8
--- /dev/null
+++ b/src/lib/components/chat/Messages.svelte
@@ -0,0 +1,416 @@
+<script lang="ts">
+	import { v4 as uuidv4 } from 'uuid';
+	import { chats, config, settings, user as _user, mobile, currentChatPage } from '$lib/stores';
+	import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	import { toast } from 'svelte-sonner';
+	import { getChatList, updateChatById } from '$lib/apis/chats';
+	import { copyToClipboard, findWordIndices } from '$lib/utils';
+
+	import Message from './Messages/Message.svelte';
+	import Loader from '../common/Loader.svelte';
+	import Spinner from '../common/Spinner.svelte';
+
+	import ChatPlaceholder from './ChatPlaceholder.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let chatId = '';
+	export let user = $_user;
+
+	export let prompt;
+	export let history = {};
+	export let selectedModels;
+
+	let messages = [];
+
+	export let sendPrompt: Function;
+	export let continueResponse: Function;
+	export let regenerateResponse: Function;
+	export let mergeResponses: Function;
+	export let chatActionHandler: Function;
+	export let showMessage: Function = () => {};
+
+	export let readOnly = false;
+
+	export let bottomPadding = false;
+	export let autoScroll;
+
+	let messagesCount = 20;
+	let messagesLoading = false;
+
+	const loadMoreMessages = async () => {
+		// scroll slightly down to disable continuous loading
+		const element = document.getElementById('messages-container');
+		element.scrollTop = element.scrollTop + 100;
+
+		messagesLoading = true;
+		messagesCount += 20;
+
+		await tick();
+
+		messagesLoading = false;
+	};
+
+	$: if (history.currentId) {
+		let _messages = [];
+
+		let message = history.messages[history.currentId];
+		while (message && _messages.length <= messagesCount) {
+			_messages.unshift({ ...message });
+			message = message.parentId !== null ? history.messages[message.parentId] : null;
+		}
+
+		messages = _messages;
+	} else {
+		messages = [];
+	}
+
+	$: if (autoScroll && bottomPadding) {
+		(async () => {
+			await tick();
+			scrollToBottom();
+		})();
+	}
+
+	const scrollToBottom = () => {
+		const element = document.getElementById('messages-container');
+		element.scrollTop = element.scrollHeight;
+	};
+
+	const updateChatHistory = async () => {
+		await tick();
+		history = history;
+		await updateChatById(localStorage.token, chatId, {
+			history: history,
+			messages: messages
+		});
+
+		currentChatPage.set(1);
+		await chats.set(await getChatList(localStorage.token, $currentChatPage));
+	};
+
+	const showPreviousMessage = async (message) => {
+		if (message.parentId !== null) {
+			let messageId =
+				history.messages[message.parentId].childrenIds[
+					Math.max(history.messages[message.parentId].childrenIds.indexOf(message.id) - 1, 0)
+				];
+
+			if (message.id !== messageId) {
+				let messageChildrenIds = history.messages[messageId].childrenIds;
+
+				while (messageChildrenIds.length !== 0) {
+					messageId = messageChildrenIds.at(-1);
+					messageChildrenIds = history.messages[messageId].childrenIds;
+				}
+
+				history.currentId = messageId;
+			}
+		} else {
+			let childrenIds = Object.values(history.messages)
+				.filter((message) => message.parentId === null)
+				.map((message) => message.id);
+			let messageId = childrenIds[Math.max(childrenIds.indexOf(message.id) - 1, 0)];
+
+			if (message.id !== messageId) {
+				let messageChildrenIds = history.messages[messageId].childrenIds;
+
+				while (messageChildrenIds.length !== 0) {
+					messageId = messageChildrenIds.at(-1);
+					messageChildrenIds = history.messages[messageId].childrenIds;
+				}
+
+				history.currentId = messageId;
+			}
+		}
+
+		await tick();
+
+		if ($settings?.scrollOnBranchChange ?? true) {
+			const element = document.getElementById('messages-container');
+			autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
+
+			setTimeout(() => {
+				scrollToBottom();
+			}, 100);
+		}
+	};
+
+	const showNextMessage = async (message) => {
+		if (message.parentId !== null) {
+			let messageId =
+				history.messages[message.parentId].childrenIds[
+					Math.min(
+						history.messages[message.parentId].childrenIds.indexOf(message.id) + 1,
+						history.messages[message.parentId].childrenIds.length - 1
+					)
+				];
+
+			if (message.id !== messageId) {
+				let messageChildrenIds = history.messages[messageId].childrenIds;
+
+				while (messageChildrenIds.length !== 0) {
+					messageId = messageChildrenIds.at(-1);
+					messageChildrenIds = history.messages[messageId].childrenIds;
+				}
+
+				history.currentId = messageId;
+			}
+		} else {
+			let childrenIds = Object.values(history.messages)
+				.filter((message) => message.parentId === null)
+				.map((message) => message.id);
+			let messageId =
+				childrenIds[Math.min(childrenIds.indexOf(message.id) + 1, childrenIds.length - 1)];
+
+			if (message.id !== messageId) {
+				let messageChildrenIds = history.messages[messageId].childrenIds;
+
+				while (messageChildrenIds.length !== 0) {
+					messageId = messageChildrenIds.at(-1);
+					messageChildrenIds = history.messages[messageId].childrenIds;
+				}
+
+				history.currentId = messageId;
+			}
+		}
+
+		await tick();
+
+		if ($settings?.scrollOnBranchChange ?? true) {
+			const element = document.getElementById('messages-container');
+			autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
+
+			setTimeout(() => {
+				scrollToBottom();
+			}, 100);
+		}
+	};
+
+	const rateMessage = async (messageId, rating) => {
+		history.messages[messageId].annotation = {
+			...history.messages[messageId].annotation,
+			rating: rating
+		};
+
+		await updateChatHistory();
+	};
+
+	const editMessage = async (messageId, content, submit = true) => {
+		if (history.messages[messageId].role === 'user') {
+			if (submit) {
+				// New user message
+				let userPrompt = content;
+				let userMessageId = uuidv4();
+
+				let userMessage = {
+					id: userMessageId,
+					parentId: history.messages[messageId].parentId,
+					childrenIds: [],
+					role: 'user',
+					content: userPrompt,
+					...(history.messages[messageId].files && { files: history.messages[messageId].files }),
+					models: selectedModels
+				};
+
+				let messageParentId = history.messages[messageId].parentId;
+
+				if (messageParentId !== null) {
+					history.messages[messageParentId].childrenIds = [
+						...history.messages[messageParentId].childrenIds,
+						userMessageId
+					];
+				}
+
+				history.messages[userMessageId] = userMessage;
+				history.currentId = userMessageId;
+
+				await tick();
+				await sendPrompt(userPrompt, userMessageId);
+			} else {
+				// Edit user message
+				history.messages[messageId].content = content;
+				await updateChatHistory();
+			}
+		} else {
+			if (submit) {
+				// New response message
+				const responseMessageId = uuidv4();
+				const message = history.messages[messageId];
+				const parentId = message.parentId;
+
+				const responseMessage = {
+					...message,
+					id: responseMessageId,
+					parentId: parentId,
+					childrenIds: [],
+					content: content,
+					timestamp: Math.floor(Date.now() / 1000) // Unix epoch
+				};
+
+				history.messages[responseMessageId] = responseMessage;
+				history.currentId = responseMessageId;
+
+				// Append messageId to childrenIds of parent message
+				if (parentId !== null) {
+					history.messages[parentId].childrenIds = [
+						...history.messages[parentId].childrenIds,
+						responseMessageId
+					];
+				}
+
+				await updateChatHistory();
+			} else {
+				// Edit response message
+				history.messages[messageId].originalContent = history.messages[messageId].content;
+				history.messages[messageId].content = content;
+				await updateChatHistory();
+			}
+		}
+	};
+
+	const deleteMessage = async (messageId) => {
+		const messageToDelete = history.messages[messageId];
+		const parentMessageId = messageToDelete.parentId;
+		const childMessageIds = messageToDelete.childrenIds ?? [];
+
+		// Collect all grandchildren
+		const grandchildrenIds = childMessageIds.flatMap(
+			(childId) => history.messages[childId]?.childrenIds ?? []
+		);
+
+		// Update parent's children
+		if (parentMessageId && history.messages[parentMessageId]) {
+			history.messages[parentMessageId].childrenIds = [
+				...history.messages[parentMessageId].childrenIds.filter((id) => id !== messageId),
+				...grandchildrenIds
+			];
+		}
+
+		// Update grandchildren's parent
+		grandchildrenIds.forEach((grandchildId) => {
+			if (history.messages[grandchildId]) {
+				history.messages[grandchildId].parentId = parentMessageId;
+			}
+		});
+
+		// Delete the message and its children
+		[messageId, ...childMessageIds].forEach((id) => {
+			delete history.messages[id];
+		});
+
+		await tick();
+
+		showMessage({ id: parentMessageId });
+
+		// Update the chat
+		await updateChatHistory();
+	};
+</script>
+
+<div class="h-full flex pt-8">
+	{#if Object.keys(history?.messages ?? {}).length == 0}
+		<ChatPlaceholder
+			modelIds={selectedModels}
+			submitPrompt={async (p) => {
+				let text = p;
+
+				if (p.includes('{{CLIPBOARD}}')) {
+					const clipboardText = await navigator.clipboard.readText().catch((err) => {
+						toast.error($i18n.t('Failed to read clipboard contents'));
+						return '{{CLIPBOARD}}';
+					});
+
+					text = p.replaceAll('{{CLIPBOARD}}', clipboardText);
+				}
+
+				prompt = text;
+
+				await tick();
+
+				const chatInputContainerElement = document.getElementById('chat-input-container');
+				if (chatInputContainerElement) {
+					prompt = p;
+
+					chatInputContainerElement.style.height = '';
+					chatInputContainerElement.style.height =
+						Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
+					chatInputContainerElement.focus();
+				}
+
+				await tick();
+			}}
+		/>
+	{:else}
+		<div class="w-full pt-2">
+			{#key chatId}
+				<div class="w-full">
+					{#if messages.at(0)?.parentId !== null}
+						<Loader
+							on:visible={(e) => {
+								console.log('visible');
+								if (!messagesLoading) {
+									loadMoreMessages();
+								}
+							}}
+						>
+							<div class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2">
+								<Spinner className=" size-4" />
+								<div class=" ">Loading...</div>
+							</div>
+						</Loader>
+					{/if}
+
+					{#each messages as message, messageIdx (message.id)}
+						<Message
+							{chatId}
+							bind:history
+							messageId={message.id}
+							idx={messageIdx}
+							{user}
+							{showPreviousMessage}
+							{showNextMessage}
+							{editMessage}
+							{deleteMessage}
+							{rateMessage}
+							{regenerateResponse}
+							{continueResponse}
+							{mergeResponses}
+							{readOnly}
+							on:submit={async (e) => {
+								dispatch('submit', e.detail);
+							}}
+							on:action={async (e) => {
+								if (typeof e.detail === 'string') {
+									await chatActionHandler(chatId, e.detail, message.model, message.id);
+								} else {
+									const { id, event } = e.detail;
+									await chatActionHandler(chatId, id, message.model, message.id, event);
+								}
+							}}
+							on:update={() => {
+								updateChatHistory();
+							}}
+							on:scroll={() => {
+								if (autoScroll) {
+									const element = document.getElementById('messages-container');
+									autoScroll =
+										element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
+									setTimeout(() => {
+										scrollToBottom();
+									}, 100);
+								}
+							}}
+						/>
+					{/each}
+				</div>
+				<div class="pb-12" />
+				{#if bottomPadding}
+					<div class="  pb-6" />
+				{/if}
+			{/key}
+		</div>
+	{/if}
+</div>
diff --git a/src/lib/components/chat/Messages/Citations.svelte b/src/lib/components/chat/Messages/Citations.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..c5a2d6ea4e648b1e510f41da09ac0a0145c527fc
--- /dev/null
+++ b/src/lib/components/chat/Messages/Citations.svelte
@@ -0,0 +1,219 @@
+<script lang="ts">
+	import { getContext } from 'svelte';
+	import CitationsModal from './CitationsModal.svelte';
+	import Collapsible from '$lib/components/common/Collapsible.svelte';
+	import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
+	import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let citations = [];
+
+	let _citations = [];
+	let showPercentage = false;
+	let showRelevance = true;
+
+	let showCitationModal = false;
+	let selectedCitation: any = null;
+	let isCollapsibleOpen = false;
+
+	function calculateShowRelevance(citations: any[]) {
+		const distances = citations.flatMap((citation) => citation.distances ?? []);
+		const inRange = distances.filter((d) => d !== undefined && d >= -1 && d <= 1).length;
+		const outOfRange = distances.filter((d) => d !== undefined && (d < -1 || d > 1)).length;
+
+		if (distances.length === 0) {
+			return false;
+		}
+
+		if (
+			(inRange === distances.length - 1 && outOfRange === 1) ||
+			(outOfRange === distances.length - 1 && inRange === 1)
+		) {
+			return false;
+		}
+
+		return true;
+	}
+
+	function shouldShowPercentage(citations: any[]) {
+		const distances = citations.flatMap((citation) => citation.distances ?? []);
+		return distances.every((d) => d !== undefined && d >= -1 && d <= 1);
+	}
+
+	$: {
+		_citations = citations.reduce((acc, citation) => {
+			citation.document.forEach((document, index) => {
+				const metadata = citation.metadata?.[index];
+				const distance = citation.distances?.[index];
+				const id = metadata?.source ?? 'N/A';
+				let source = citation?.source;
+
+				if (metadata?.name) {
+					source = { ...source, name: metadata.name };
+				}
+
+				if (id.startsWith('http://') || id.startsWith('https://')) {
+					source = { name: id, ...source, url: id };
+				}
+
+				const existingSource = acc.find((item) => item.id === id);
+
+				if (existingSource) {
+					existingSource.document.push(document);
+					existingSource.metadata.push(metadata);
+					if (distance !== undefined) existingSource.distances.push(distance);
+				} else {
+					acc.push({
+						id: id,
+						source: source,
+						document: [document],
+						metadata: metadata ? [metadata] : [],
+						distances: distance !== undefined ? [distance] : undefined
+					});
+				}
+			});
+			return acc;
+		}, []);
+
+		showRelevance = calculateShowRelevance(_citations);
+		showPercentage = shouldShowPercentage(_citations);
+	}
+</script>
+
+<CitationsModal
+	bind:show={showCitationModal}
+	citation={selectedCitation}
+	{showPercentage}
+	{showRelevance}
+/>
+
+{#if _citations.length > 0}
+	<div class="mt-1 mb-2 w-full flex gap-1 items-center flex-wrap">
+		{#if _citations.length <= 3}
+			{#each _citations as citation, idx}
+				<div class="flex gap-1 text-xs font-semibold">
+					<button
+						class="no-toggle flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96"
+						on:click={() => {
+							showCitationModal = true;
+							selectedCitation = citation;
+						}}
+					>
+						{#if _citations.every((c) => c.distances !== undefined)}
+							<div class="bg-white dark:bg-gray-700 rounded-full size-4">
+								{idx + 1}
+							</div>
+						{/if}
+						<div class="flex-1 mx-2 line-clamp-1 truncate">
+							{citation.source.name}
+						</div>
+					</button>
+				</div>
+			{/each}
+		{:else}
+			<Collapsible bind:open={isCollapsibleOpen} className="w-full">
+				<div
+					class="flex items-center gap-1 text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 transition cursor-pointer"
+				>
+					<div class="flex-grow flex items-center gap-1 overflow-hidden">
+						<span class="whitespace-nowrap hidden sm:inline">{$i18n.t('References from')}</span>
+						<div class="flex items-center">
+							{#if _citations.length > 1 && _citations
+									.slice(0, 2)
+									.reduce((acc, citation) => acc + citation.source.name.length, 0) <= 50}
+								{#each _citations.slice(0, 2) as citation, idx}
+									<div class="flex items-center">
+										<button
+											class="no-toggle flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96 text-xs font-semibold"
+											on:click={() => {
+												showCitationModal = true;
+												selectedCitation = citation;
+											}}
+										>
+											{#if _citations.every((c) => c.distances !== undefined)}
+												<div class="bg-white dark:bg-gray-700 rounded-full size-4">
+													{idx + 1}
+												</div>
+											{/if}
+											<div class="flex-1 mx-2 line-clamp-1">
+												{citation.source.name}
+											</div>
+										</button>
+										{#if idx === 0}<span class="mr-1">,</span>
+										{/if}
+									</div>
+								{/each}
+							{:else}
+								{#each _citations.slice(0, 1) as citation, idx}
+									<div class="flex items-center">
+										<button
+											class="no-toggle flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96 text-xs font-semibold"
+											on:click={() => {
+												showCitationModal = true;
+												selectedCitation = citation;
+											}}
+										>
+											{#if _citations.every((c) => c.distances !== undefined)}
+												<div class="bg-white dark:bg-gray-700 rounded-full size-4">
+													{idx + 1}
+												</div>
+											{/if}
+											<div class="flex-1 mx-2 line-clamp-1">
+												{citation.source.name}
+											</div>
+										</button>
+									</div>
+								{/each}
+							{/if}
+						</div>
+						<div class="flex items-center gap-1 whitespace-nowrap">
+							<span class="hidden sm:inline">{$i18n.t('and')}</span>
+							<span class="text-gray-600 dark:text-gray-400">
+								{_citations.length -
+									(_citations.length > 1 &&
+									_citations
+										.slice(0, 2)
+										.reduce((acc, citation) => acc + citation.source.name.length, 0) <= 50
+										? 2
+										: 1)}
+							</span>
+							<span>{$i18n.t('more')}</span>
+						</div>
+					</div>
+					<div class="flex-shrink-0">
+						{#if isCollapsibleOpen}
+							<ChevronUp strokeWidth="3.5" className="size-3.5" />
+						{:else}
+							<ChevronDown strokeWidth="3.5" className="size-3.5" />
+						{/if}
+					</div>
+				</div>
+				<div slot="content" class="mt-2">
+					<div class="flex flex-wrap gap-2">
+						{#each _citations as citation, idx}
+							<div class="flex gap-1 text-xs font-semibold">
+								<button
+									class="no-toggle flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96"
+									on:click={() => {
+										showCitationModal = true;
+										selectedCitation = citation;
+									}}
+								>
+									{#if _citations.every((c) => c.distances !== undefined)}
+										<div class="bg-white dark:bg-gray-700 rounded-full size-4">
+											{idx + 1}
+										</div>
+									{/if}
+									<div class="flex-1 mx-2 line-clamp-1">
+										{citation.source.name}
+									</div>
+								</button>
+							</div>
+						{/each}
+					</div>
+				</div>
+			</Collapsible>
+		{/if}
+	</div>
+{/if}
diff --git a/src/lib/components/chat/Messages/CitationsModal.svelte b/src/lib/components/chat/Messages/CitationsModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..bb48a3f53d446673669c2915de80d7183bc634a0
--- /dev/null
+++ b/src/lib/components/chat/Messages/CitationsModal.svelte
@@ -0,0 +1,163 @@
+<script lang="ts">
+	import { getContext, onMount, tick } from 'svelte';
+	import Modal from '$lib/components/common/Modal.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let show = false;
+	export let citation;
+	export let showPercentage = false;
+	export let showRelevance = true;
+
+	let mergedDocuments = [];
+
+	function calculatePercentage(distance: number) {
+		if (distance < 0) return 100;
+		if (distance > 1) return 0;
+		return Math.round((1 - distance) * 10000) / 100;
+	}
+
+	function getRelevanceColor(percentage: number) {
+		if (percentage >= 80)
+			return 'bg-green-200 dark:bg-green-800 text-green-800 dark:text-green-200';
+		if (percentage >= 60)
+			return 'bg-yellow-200 dark:bg-yellow-800 text-yellow-800 dark:text-yellow-200';
+		if (percentage >= 40)
+			return 'bg-orange-200 dark:bg-orange-800 text-orange-800 dark:text-orange-200';
+		return 'bg-red-200 dark:bg-red-800 text-red-800 dark:text-red-200';
+	}
+
+	$: if (citation) {
+		mergedDocuments = citation.document?.map((c, i) => {
+			return {
+				source: citation.source,
+				document: c,
+				metadata: citation.metadata?.[i],
+				distance: citation.distances?.[i]
+			};
+		});
+		if (mergedDocuments.every((doc) => doc.distance !== undefined)) {
+			mergedDocuments.sort((a, b) => (a.distance ?? Infinity) - (b.distance ?? Infinity));
+		}
+	}
+</script>
+
+<Modal size="lg" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
+			<div class=" text-lg font-medium self-center capitalize">
+				{$i18n.t('Citation')}
+			</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full px-6 pb-5 md:space-x-4">
+			<div
+				class="flex flex-col w-full dark:text-gray-200 overflow-y-scroll max-h-[22rem] scrollbar-hidden"
+			>
+				{#each mergedDocuments as document, documentIdx}
+					<div class="flex flex-col w-full">
+						<div class="text-sm font-medium dark:text-gray-300">
+							{$i18n.t('Source')}
+						</div>
+
+						{#if document.source?.name}
+							<Tooltip
+								className="w-fit"
+								content={$i18n.t('Open file')}
+								placement="top-start"
+								tippyOptions={{ duration: [500, 0] }}
+							>
+								<div class="text-sm dark:text-gray-400 flex items-center gap-2 w-fit">
+									<a
+										class="hover:text-gray-500 hover:dark:text-gray-100 underline flex-grow"
+										href={document?.metadata?.file_id
+											? `/api/v1/files/${document?.metadata?.file_id}/content${document?.metadata?.page !== undefined ? `#page=${document.metadata.page + 1}` : ''}`
+											: document.source?.url?.includes('http')
+												? document.source.url
+												: `#`}
+										target="_blank"
+									>
+										{document?.metadata?.name ?? document.source.name}
+									</a>
+									{#if document?.metadata?.page}
+										<span class="text-xs text-gray-500 dark:text-gray-400">
+											({$i18n.t('page')}
+											{document.metadata.page + 1})
+										</span>
+									{/if}
+								</div>
+							</Tooltip>
+							{#if showRelevance}
+								<div class="text-sm font-medium dark:text-gray-300 mt-2">
+									{$i18n.t('Relevance')}
+								</div>
+								{#if document.distance !== undefined}
+									<Tooltip
+										className="w-fit"
+										content={$i18n.t('Semantic distance to query')}
+										placement="top-start"
+										tippyOptions={{ duration: [500, 0] }}
+									>
+										<div class="text-sm my-1 dark:text-gray-400 flex items-center gap-2 w-fit">
+											{#if showPercentage}
+												{@const percentage = calculatePercentage(document.distance)}
+												<span class={`px-1 rounded font-medium ${getRelevanceColor(percentage)}`}>
+													{percentage.toFixed(2)}%
+												</span>
+												<span class="text-gray-500 dark:text-gray-500">
+													({document.distance.toFixed(4)})
+												</span>
+											{:else}
+												<span class="text-gray-500 dark:text-gray-500">
+													{document.distance.toFixed(4)}
+												</span>
+											{/if}
+										</div>
+									</Tooltip>
+								{:else}
+									<div class="text-sm dark:text-gray-400">
+										{$i18n.t('No distance available')}
+									</div>
+								{/if}
+							{/if}
+						{:else}
+							<div class="text-sm dark:text-gray-400">
+								{$i18n.t('No source available')}
+							</div>
+						{/if}
+					</div>
+					<div class="flex flex-col w-full">
+						<div class=" text-sm font-medium dark:text-gray-300 mt-2">
+							{$i18n.t('Content')}
+						</div>
+						<pre class="text-sm dark:text-gray-400 whitespace-pre-line">
+							{document.document}
+						</pre>
+					</div>
+
+					{#if documentIdx !== mergedDocuments.length - 1}
+						<hr class=" dark:border-gray-850 my-3" />
+					{/if}
+				{/each}
+			</div>
+		</div>
+	</div>
+</Modal>
diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..90e747ce9a0be9d042e2aedff870e98ce734e164
--- /dev/null
+++ b/src/lib/components/chat/Messages/CodeBlock.svelte
@@ -0,0 +1,395 @@
+<script lang="ts">
+	import hljs from 'highlight.js';
+	import { loadPyodide } from 'pyodide';
+	import mermaid from 'mermaid';
+
+	import { v4 as uuidv4 } from 'uuid';
+
+	import { getContext, getAllContexts, onMount, tick, createEventDispatcher } from 'svelte';
+	import { copyToClipboard } from '$lib/utils';
+
+	import 'highlight.js/styles/github-dark.min.css';
+
+	import PyodideWorker from '$lib/workers/pyodide.worker?worker';
+	import CodeEditor from '$lib/components/common/CodeEditor.svelte';
+	import SvgPanZoom from '$lib/components/common/SVGPanZoom.svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	export let id = '';
+
+	export let save = false;
+	export let run = true;
+
+	export let token;
+	export let lang = '';
+	export let code = '';
+
+	export let className = 'my-2';
+	export let editorClassName = '';
+	export let stickyButtonsClassName = 'top-8';
+
+	let _code = '';
+	$: if (code) {
+		updateCode();
+	}
+
+	const updateCode = () => {
+		_code = code;
+	};
+
+	let _token = null;
+
+	let mermaidHtml = null;
+
+	let highlightedCode = null;
+	let executing = false;
+
+	let stdout = null;
+	let stderr = null;
+	let result = null;
+
+	let copied = false;
+	let saved = false;
+
+	const saveCode = () => {
+		saved = true;
+
+		code = _code;
+		dispatch('save', code);
+
+		setTimeout(() => {
+			saved = false;
+		}, 1000);
+	};
+
+	const copyCode = async () => {
+		copied = true;
+		await copyToClipboard(code);
+
+		setTimeout(() => {
+			copied = false;
+		}, 1000);
+	};
+
+	const checkPythonCode = (str) => {
+		// Check if the string contains typical Python syntax characters
+		const pythonSyntax = [
+			'def ',
+			'else:',
+			'elif ',
+			'try:',
+			'except:',
+			'finally:',
+			'yield ',
+			'lambda ',
+			'assert ',
+			'nonlocal ',
+			'del ',
+			'True',
+			'False',
+			'None',
+			' and ',
+			' or ',
+			' not ',
+			' in ',
+			' is ',
+			' with '
+		];
+
+		for (let syntax of pythonSyntax) {
+			if (str.includes(syntax)) {
+				return true;
+			}
+		}
+
+		// If none of the above conditions met, it's probably not Python code
+		return false;
+	};
+
+	const executePython = async (code) => {
+		if (!code.includes('input') && !code.includes('matplotlib')) {
+			executePythonAsWorker(code);
+		} else {
+			result = null;
+			stdout = null;
+			stderr = null;
+
+			executing = true;
+
+			document.pyodideMplTarget = document.getElementById(`plt-canvas-${id}`);
+
+			let pyodide = await loadPyodide({
+				indexURL: '/pyodide/',
+				stdout: (text) => {
+					console.log('Python output:', text);
+
+					if (stdout) {
+						stdout += `${text}\n`;
+					} else {
+						stdout = `${text}\n`;
+					}
+				},
+				stderr: (text) => {
+					console.log('An error occurred:', text);
+					if (stderr) {
+						stderr += `${text}\n`;
+					} else {
+						stderr = `${text}\n`;
+					}
+				},
+				packages: ['micropip']
+			});
+
+			try {
+				const micropip = pyodide.pyimport('micropip');
+
+				// await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
+
+				let packages = [
+					code.includes('requests') ? 'requests' : null,
+					code.includes('bs4') ? 'beautifulsoup4' : null,
+					code.includes('numpy') ? 'numpy' : null,
+					code.includes('pandas') ? 'pandas' : null,
+					code.includes('matplotlib') ? 'matplotlib' : null,
+					code.includes('sklearn') ? 'scikit-learn' : null,
+					code.includes('scipy') ? 'scipy' : null,
+					code.includes('re') ? 'regex' : null,
+					code.includes('seaborn') ? 'seaborn' : null
+				].filter(Boolean);
+
+				console.log(packages);
+				await micropip.install(packages);
+
+				result = await pyodide.runPythonAsync(`from js import prompt
+def input(p):
+    return prompt(p)
+__builtins__.input = input`);
+
+				result = await pyodide.runPython(code);
+
+				if (!result) {
+					result = '[NO OUTPUT]';
+				}
+
+				console.log(result);
+				console.log(stdout);
+				console.log(stderr);
+
+				const pltCanvasElement = document.getElementById(`plt-canvas-${id}`);
+
+				if (pltCanvasElement?.innerHTML !== '') {
+					pltCanvasElement.classList.add('pt-4');
+				}
+			} catch (error) {
+				console.error('Error:', error);
+				stderr = error;
+			}
+
+			executing = false;
+		}
+	};
+
+	const executePythonAsWorker = async (code) => {
+		result = null;
+		stdout = null;
+		stderr = null;
+
+		executing = true;
+
+		let packages = [
+			code.includes('requests') ? 'requests' : null,
+			code.includes('bs4') ? 'beautifulsoup4' : null,
+			code.includes('numpy') ? 'numpy' : null,
+			code.includes('pandas') ? 'pandas' : null,
+			code.includes('sklearn') ? 'scikit-learn' : null,
+			code.includes('scipy') ? 'scipy' : null,
+			code.includes('re') ? 'regex' : null,
+			code.includes('seaborn') ? 'seaborn' : null
+		].filter(Boolean);
+
+		console.log(packages);
+
+		const pyodideWorker = new PyodideWorker();
+
+		pyodideWorker.postMessage({
+			id: id,
+			code: code,
+			packages: packages
+		});
+
+		setTimeout(() => {
+			if (executing) {
+				executing = false;
+				stderr = 'Execution Time Limit Exceeded';
+				pyodideWorker.terminate();
+			}
+		}, 60000);
+
+		pyodideWorker.onmessage = (event) => {
+			console.log('pyodideWorker.onmessage', event);
+			const { id, ...data } = event.data;
+
+			console.log(id, data);
+
+			data['stdout'] && (stdout = data['stdout']);
+			data['stderr'] && (stderr = data['stderr']);
+			data['result'] && (result = data['result']);
+
+			executing = false;
+		};
+
+		pyodideWorker.onerror = (event) => {
+			console.log('pyodideWorker.onerror', event);
+			executing = false;
+		};
+	};
+
+	let debounceTimeout;
+
+	const drawMermaidDiagram = async () => {
+		try {
+			if (await mermaid.parse(code)) {
+				const { svg } = await mermaid.render(`mermaid-${uuidv4()}`, code);
+				mermaidHtml = svg;
+			}
+		} catch (error) {
+			console.log('Error:', error);
+		}
+	};
+
+	const render = async () => {
+		if (lang === 'mermaid' && (token?.raw ?? '').slice(-4).includes('```')) {
+			(async () => {
+				await drawMermaidDiagram();
+			})();
+		}
+	};
+
+	$: if (token) {
+		if (JSON.stringify(token) !== JSON.stringify(_token)) {
+			_token = token;
+		}
+	}
+
+	$: if (_token) {
+		render();
+	}
+
+	$: dispatch('code', { lang, code });
+
+	onMount(async () => {
+		console.log('codeblock', lang, code);
+
+		if (lang) {
+			dispatch('code', { lang, code });
+		}
+		if (document.documentElement.classList.contains('dark')) {
+			mermaid.initialize({
+				startOnLoad: true,
+				theme: 'dark',
+				securityLevel: 'loose'
+			});
+		} else {
+			mermaid.initialize({
+				startOnLoad: true,
+				theme: 'default',
+				securityLevel: 'loose'
+			});
+		}
+	});
+</script>
+
+<div>
+	<div class="relative {className} flex flex-col rounded-lg" dir="ltr">
+		{#if lang === 'mermaid'}
+			{#if mermaidHtml}
+				<SvgPanZoom
+					className=" border border-gray-50 dark:border-gray-850 rounded-lg max-h-fit overflow-hidden"
+					svg={mermaidHtml}
+					content={_token.text}
+				/>
+			{:else}
+				<pre class="mermaid">{code}</pre>
+			{/if}
+		{:else}
+			<div class="text-text-300 absolute pl-4 py-1.5 text-xs font-medium dark:text-white">
+				{lang}
+			</div>
+
+			<div
+				class="sticky {stickyButtonsClassName} mb-1 py-1 pr-2.5 flex items-center justify-end z-10 text-xs text-black dark:text-white"
+			>
+				<div class="flex items-center gap-0.5 translate-y-[1px]">
+					{#if lang.toLowerCase() === 'python' || lang.toLowerCase() === 'py' || (lang === '' && checkPythonCode(code))}
+						{#if executing}
+							<div class="run-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
+						{:else if run}
+							<button
+								class="run-code-button bg-none border-none bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-md px-1.5 py-0.5"
+								on:click={async () => {
+									code = _code;
+									await tick();
+									executePython(code);
+								}}>{$i18n.t('Run')}</button
+							>
+						{/if}
+					{/if}
+
+					{#if save}
+						<button
+							class="save-code-button bg-none border-none bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-md px-1.5 py-0.5"
+							on:click={saveCode}
+						>
+							{saved ? $i18n.t('Saved') : $i18n.t('Save')}
+						</button>
+					{/if}
+
+					<button
+						class="copy-code-button bg-none border-none bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-md px-1.5 py-0.5"
+						on:click={copyCode}>{copied ? $i18n.t('Copied') : $i18n.t('Copy')}</button
+					>
+				</div>
+			</div>
+
+			<div
+				class="language-{lang} rounded-t-lg -mt-8 {editorClassName
+					? editorClassName
+					: executing || stdout || stderr || result
+						? ''
+						: 'rounded-b-lg'} overflow-hidden"
+			>
+				<div class=" pt-7 bg-gray-50 dark:bg-gray-850"></div>
+				<CodeEditor
+					value={code}
+					{id}
+					{lang}
+					on:save={() => {
+						saveCode();
+					}}
+					on:change={(e) => {
+						_code = e.detail.value;
+					}}
+				/>
+			</div>
+
+			<div
+				id="plt-canvas-{id}"
+				class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
+			/>
+
+			{#if executing}
+				<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
+					<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
+					<div class="text-sm">Running...</div>
+				</div>
+			{:else if stdout || stderr || result}
+				<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
+					<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
+					<div class="text-sm">{stdout || stderr || result}</div>
+				</div>
+			{/if}
+		{/if}
+	</div>
+</div>
diff --git a/src/lib/components/chat/Messages/CodeExecutionModal.svelte b/src/lib/components/chat/Messages/CodeExecutionModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..1be9391d805a601917e749d62809ec6b9a8adedc
--- /dev/null
+++ b/src/lib/components/chat/Messages/CodeExecutionModal.svelte
@@ -0,0 +1,118 @@
+<script lang="ts">
+	import { getContext } from 'svelte';
+	import CodeBlock from './CodeBlock.svelte';
+	import Modal from '$lib/components/common/Modal.svelte';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import Badge from '$lib/components/common/Badge.svelte';
+	const i18n = getContext('i18n');
+
+	export let show = false;
+	export let codeExecution = null;
+</script>
+
+<Modal size="lg" bind:show>
+	<div>
+		<div class="flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
+			<div class="text-lg font-medium self-center flex flex-col gap-0.5 capitalize">
+				{#if codeExecution?.result}
+					<div>
+						{#if codeExecution.result?.error}
+							<Badge type="error" content="error" />
+						{:else if codeExecution.result?.output}
+							<Badge type="success" content="success" />
+						{:else}
+							<Badge type="warning" content="incomplete" />
+						{/if}
+					</div>
+				{/if}
+
+				<div class="flex gap-2 items-center">
+					{#if !codeExecution?.result}
+						<div>
+							<Spinner className="size-4" />
+						</div>
+					{/if}
+
+					<div>
+						{#if codeExecution?.name}
+							{$i18n.t('Code execution')}: {codeExecution?.name}
+						{:else}
+							{$i18n.t('Code execution')}
+						{/if}
+					</div>
+				</div>
+			</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+					codeExecution = null;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full px-4 pb-5">
+			<div
+				class="flex flex-col w-full dark:text-gray-200 overflow-y-scroll max-h-[22rem] scrollbar-hidden"
+			>
+				<div class="flex flex-col w-full">
+					<CodeBlock
+						id="code-exec-{codeExecution?.id}-code"
+						lang={codeExecution?.language ?? ''}
+						code={codeExecution?.code ?? ''}
+						className=""
+						editorClassName={codeExecution?.result &&
+						(codeExecution?.result?.error || codeExecution?.result?.output)
+							? 'rounded-b-none'
+							: ''}
+						stickyButtonsClassName="top-0"
+						run={false}
+					/>
+				</div>
+
+				{#if codeExecution?.result && (codeExecution?.result?.error || codeExecution?.result?.output)}
+					<div class="dark:bg-[#202123] dark:text-white px-4 py-4 rounded-b-lg flex flex-col gap-3">
+						{#if codeExecution?.result?.error}
+							<div>
+								<div class=" text-gray-500 text-xs mb-1">{$i18n.t('ERROR')}</div>
+								<div class="text-sm">{codeExecution?.result?.error}</div>
+							</div>
+						{/if}
+						{#if codeExecution?.result?.output}
+							<div>
+								<div class=" text-gray-500 text-xs mb-1">{$i18n.t('OUTPUT')}</div>
+								<div class="text-sm">{codeExecution?.result?.output}</div>
+							</div>
+						{/if}
+					</div>
+				{/if}
+				{#if codeExecution?.result?.files && codeExecution?.result?.files.length > 0}
+					<div class="flex flex-col w-full">
+						<hr class=" dark:border-gray-850 my-2" />
+						<div class=" text-sm font-medium dark:text-gray-300">
+							{$i18n.t('Files')}
+						</div>
+						<ul class="mt-1 list-disc pl-4 text-xs">
+							{#each codeExecution?.result?.files as file}
+								<li>
+									<a href={file.url} target="_blank">{file.name}</a>
+								</li>
+							{/each}
+						</ul>
+					</div>
+				{/if}
+			</div>
+		</div>
+	</div>
+</Modal>
diff --git a/src/lib/components/chat/Messages/CodeExecutions.svelte b/src/lib/components/chat/Messages/CodeExecutions.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2c53eaeb3898576cf083c865d356203f61b6f2ce
--- /dev/null
+++ b/src/lib/components/chat/Messages/CodeExecutions.svelte
@@ -0,0 +1,80 @@
+<script lang="ts">
+	import CodeExecutionModal from './CodeExecutionModal.svelte';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import Check from '$lib/components/icons/Check.svelte';
+	import XMark from '$lib/components/icons/XMark.svelte';
+	import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
+
+	export let codeExecutions = [];
+
+	let selectedCodeExecution = null;
+	let showCodeExecutionModal = false;
+
+	$: if (codeExecutions) {
+		updateSelectedCodeExecution();
+	}
+
+	const updateSelectedCodeExecution = () => {
+		if (selectedCodeExecution) {
+			selectedCodeExecution = codeExecutions.find(
+				(execution) => execution.id === selectedCodeExecution.id
+			);
+		}
+	};
+</script>
+
+<CodeExecutionModal bind:show={showCodeExecutionModal} codeExecution={selectedCodeExecution} />
+
+{#if codeExecutions.length > 0}
+	<div class="mt-1 mb-2 w-full flex gap-1 items-center flex-wrap">
+		{#each codeExecutions as execution (execution.id)}
+			<div class="flex gap-1 text-xs font-semibold">
+				<button
+					class="flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96"
+					on:click={() => {
+						selectedCodeExecution = execution;
+						showCodeExecutionModal = true;
+					}}
+				>
+					<div
+						class="bg-white dark:bg-gray-700 rounded-full size-4 flex items-center justify-center"
+					>
+						{#if execution?.result}
+							{#if execution.result?.error}
+								<XMark />
+							{:else if execution.result?.output}
+								<Check strokeWidth="3" className="size-3" />
+							{:else}
+								<EllipsisHorizontal />
+							{/if}
+						{:else}
+							<Spinner className="size-4" />
+						{/if}
+					</div>
+					<div
+						class="flex-1 mx-2 line-clamp-1 code-execution-name {execution?.result ? '' : 'pulse'}"
+					>
+						{execution.name}
+					</div>
+				</button>
+			</div>
+		{/each}
+	</div>
+{/if}
+
+<style>
+	@keyframes pulse {
+		0%,
+		100% {
+			opacity: 1;
+		}
+		50% {
+			opacity: 0.6;
+		}
+	}
+
+	.pulse {
+		opacity: 1;
+		animation: pulse 1.5s ease;
+	}
+</style>
diff --git a/src/lib/components/chat/Messages/ContentRenderer.svelte b/src/lib/components/chat/Messages/ContentRenderer.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ccbc3fd2705a8a7df56ba3bff1c319bbc14304b4
--- /dev/null
+++ b/src/lib/components/chat/Messages/ContentRenderer.svelte
@@ -0,0 +1,232 @@
+<script>
+	import { onDestroy, onMount, tick, getContext, createEventDispatcher } from 'svelte';
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import Markdown from './Markdown.svelte';
+	import LightBlub from '$lib/components/icons/LightBlub.svelte';
+	import { chatId, mobile, showArtifacts, showControls, showOverview } from '$lib/stores';
+	import ChatBubble from '$lib/components/icons/ChatBubble.svelte';
+
+	export let id;
+	export let content;
+	export let model = null;
+
+	export let save = false;
+	export let floatingButtons = true;
+
+	let contentContainerElement;
+	let buttonsContainerElement;
+
+	let selectedText = '';
+	let floatingInput = false;
+	let floatingInputValue = '';
+
+	const updateButtonPosition = (event) => {
+		if (
+			!contentContainerElement?.contains(event.target) &&
+			!buttonsContainerElement?.contains(event.target)
+		) {
+			closeFloatingButtons();
+			return;
+		}
+
+		setTimeout(async () => {
+			await tick();
+
+			if (!contentContainerElement?.contains(event.target)) return;
+
+			let selection = window.getSelection();
+
+			if (selection.toString().trim().length > 0) {
+				floatingInput = false;
+				const range = selection.getRangeAt(0);
+				const rect = range.getBoundingClientRect();
+
+				const parentRect = contentContainerElement.getBoundingClientRect();
+
+				// Adjust based on parent rect
+				const top = rect.bottom - parentRect.top;
+				const left = rect.left - parentRect.left;
+
+				if (buttonsContainerElement) {
+					buttonsContainerElement.style.display = 'block';
+
+					// Calculate space available on the right
+					const spaceOnRight = parentRect.width - (left + buttonsContainerElement.offsetWidth);
+
+					let thirdScreenWidth = window.innerWidth / 3;
+
+					if (spaceOnRight < thirdScreenWidth) {
+						const right = parentRect.right - rect.right;
+						buttonsContainerElement.style.right = `${right}px`;
+						buttonsContainerElement.style.left = 'auto'; // Reset left
+					} else {
+						// Enough space, position using 'left'
+						buttonsContainerElement.style.left = `${left}px`;
+						buttonsContainerElement.style.right = 'auto'; // Reset right
+					}
+
+					buttonsContainerElement.style.top = `${top + 5}px`; // +5 to add some spacing
+				}
+			} else {
+				closeFloatingButtons();
+			}
+		}, 0);
+	};
+
+	const closeFloatingButtons = () => {
+		if (buttonsContainerElement) {
+			buttonsContainerElement.style.display = 'none';
+			selectedText = '';
+			floatingInput = false;
+			floatingInputValue = '';
+		}
+	};
+
+	const selectAskHandler = () => {
+		dispatch('select', {
+			type: 'ask',
+			content: selectedText,
+			input: floatingInputValue
+		});
+
+		floatingInput = false;
+		floatingInputValue = '';
+		selectedText = '';
+
+		// Clear selection
+		window.getSelection().removeAllRanges();
+		buttonsContainerElement.style.display = 'none';
+	};
+
+	const keydownHandler = (e) => {
+		if (e.key === 'Escape') {
+			closeFloatingButtons();
+		}
+	};
+
+	onMount(() => {
+		if (floatingButtons) {
+			contentContainerElement?.addEventListener('mouseup', updateButtonPosition);
+			document.addEventListener('mouseup', updateButtonPosition);
+			document.addEventListener('keydown', keydownHandler);
+		}
+	});
+
+	onDestroy(() => {
+		if (floatingButtons) {
+			contentContainerElement?.removeEventListener('mouseup', updateButtonPosition);
+			document.removeEventListener('mouseup', updateButtonPosition);
+			document.removeEventListener('keydown', keydownHandler);
+		}
+	});
+</script>
+
+<div bind:this={contentContainerElement}>
+	<Markdown
+		{id}
+		{content}
+		{model}
+		{save}
+		on:update={(e) => {
+			dispatch('update', e.detail);
+		}}
+		on:code={(e) => {
+			const { lang, code } = e.detail;
+
+			if (
+				(['html', 'svg'].includes(lang) || (lang === 'xml' && code.includes('svg'))) &&
+				!$mobile &&
+				$chatId
+			) {
+				showArtifacts.set(true);
+				showControls.set(true);
+			}
+		}}
+	/>
+</div>
+
+{#if floatingButtons}
+	<div
+		bind:this={buttonsContainerElement}
+		class="absolute rounded-lg mt-1 text-xs z-[9999]"
+		style="display: none"
+	>
+		{#if !floatingInput}
+			<div
+				class="flex flex-row gap-0.5 shrink-0 p-1 bg-white dark:bg-gray-850 dark:text-gray-100 text-medium rounded-lg shadow-xl"
+			>
+				<button
+					class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded flex items-center gap-1 min-w-fit"
+					on:click={() => {
+						selectedText = window.getSelection().toString();
+						floatingInput = true;
+					}}
+				>
+					<ChatBubble className="size-3 shrink-0" />
+
+					<div class="shrink-0">Ask</div>
+				</button>
+				<button
+					class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded flex items-center gap-1 min-w-fit"
+					on:click={() => {
+						const selection = window.getSelection();
+						dispatch('select', {
+							type: 'explain',
+							content: selection.toString()
+						});
+
+						// Clear selection
+						selection.removeAllRanges();
+						buttonsContainerElement.style.display = 'none';
+					}}
+				>
+					<LightBlub className="size-3 shrink-0" />
+
+					<div class="shrink-0">Explain</div>
+				</button>
+			</div>
+		{:else}
+			<div
+				class="py-1 flex dark:text-gray-100 bg-gray-50 dark:bg-gray-800 border dark:border-gray-800 w-72 rounded-full shadow-xl"
+			>
+				<input
+					type="text"
+					class="ml-5 bg-transparent outline-none w-full flex-1 text-sm"
+					placeholder={$i18n.t('Ask a question')}
+					bind:value={floatingInputValue}
+					on:keydown={(e) => {
+						if (e.key === 'Enter') {
+							selectAskHandler();
+						}
+					}}
+				/>
+
+				<div class="ml-1 mr-2">
+					<button
+						class="{floatingInputValue !== ''
+							? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
+							: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1.5 m-0.5 self-center"
+						on:click={() => {
+							selectAskHandler();
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="size-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</button>
+				</div>
+			</div>
+		{/if}
+	</div>
+{/if}
diff --git a/src/lib/components/chat/Messages/Error.svelte b/src/lib/components/chat/Messages/Error.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..3a6d7cc30d8f0b3512d36d246ca5b34ac7c18aa2
--- /dev/null
+++ b/src/lib/components/chat/Messages/Error.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	import Info from '$lib/components/icons/Info.svelte';
+
+	export let content = '';
+</script>
+
+<div class="flex my-2 gap-2.5 border px-4 py-3 border-red-800 bg-red-800/30 rounded-lg">
+	<div class=" self-start mt-0.5">
+		<Info className="size-5" />
+	</div>
+
+	<div class=" self-center text-sm">
+		{typeof content === 'string' ? content : JSON.stringify(content)}
+	</div>
+</div>
diff --git a/src/lib/components/chat/Messages/Markdown.svelte b/src/lib/components/chat/Messages/Markdown.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..1c627919ae04e0432461e598fdd1bf3e1644fe85
--- /dev/null
+++ b/src/lib/components/chat/Messages/Markdown.svelte
@@ -0,0 +1,49 @@
+<script>
+	import { marked } from 'marked';
+	import { replaceTokens, processResponseContent } from '$lib/utils';
+	import { user } from '$lib/stores';
+
+	import markedExtension from '$lib/utils/marked/extension';
+	import markedKatexExtension from '$lib/utils/marked/katex-extension';
+
+	import MarkdownTokens from './Markdown/MarkdownTokens.svelte';
+	import { createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	export let id;
+	export let content;
+	export let model = null;
+	export let save = false;
+
+	let tokens = [];
+
+	const options = {
+		throwOnError: false
+	};
+
+	marked.use(markedKatexExtension(options));
+	marked.use(markedExtension(options));
+
+	$: (async () => {
+		if (content) {
+			tokens = marked.lexer(
+				replaceTokens(processResponseContent(content), model?.name, $user?.name)
+			);
+		}
+	})();
+</script>
+
+{#key id}
+	<MarkdownTokens
+		{tokens}
+		{id}
+		{save}
+		on:update={(e) => {
+			dispatch('update', e.detail);
+		}}
+		on:code={(e) => {
+			dispatch('code', e.detail);
+		}}
+	/>
+{/key}
diff --git a/src/lib/components/chat/Messages/Markdown/KatexRenderer.svelte b/src/lib/components/chat/Messages/Markdown/KatexRenderer.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..4dfb9f2c5bf86f0c2f2353ced968a48d22a87df1
--- /dev/null
+++ b/src/lib/components/chat/Messages/Markdown/KatexRenderer.svelte
@@ -0,0 +1,10 @@
+<script lang="ts">
+	import katex from 'katex';
+	import 'katex/contrib/mhchem';
+	import 'katex/dist/katex.min.css';
+
+	export let content: string;
+	export let displayMode: boolean = false;
+</script>
+
+{@html katex.renderToString(content, { displayMode, throwOnError: false })}
diff --git a/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte b/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d192194be4762ef45dbc9ea1ba81c99f5e5f9d77
--- /dev/null
+++ b/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte
@@ -0,0 +1,81 @@
+<script lang="ts">
+	import DOMPurify from 'dompurify';
+	import { toast } from 'svelte-sonner';
+
+	import type { Token } from 'marked';
+	import { getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
+
+	import { WEBUI_BASE_URL } from '$lib/constants';
+	import { copyToClipboard, revertSanitizedResponseContent, unescapeHtml } from '$lib/utils';
+
+	import Image from '$lib/components/common/Image.svelte';
+	import KatexRenderer from './KatexRenderer.svelte';
+
+	export let id: string;
+	export let tokens: Token[];
+</script>
+
+{#each tokens as token}
+	{#if token.type === 'escape'}
+		{unescapeHtml(token.text)}
+	{:else if token.type === 'html'}
+		{@const html = DOMPurify.sanitize(token.text)}
+		{#if html && html.includes('<video')}
+			{@html html}
+		{:else if token.text.includes(`<iframe src="${WEBUI_BASE_URL}/api/v1/files/`)}
+			{@html `${token.text}`}
+		{:else}
+			{token.text}
+		{/if}
+	{:else if token.type === 'link'}
+		{#if token.tokens}
+			<a href={token.href} target="_blank" rel="nofollow" title={token.title}>
+				<svelte:self id={`${id}-a`} tokens={token.tokens} />
+			</a>
+		{:else}
+			<a href={token.href} target="_blank" rel="nofollow" title={token.title}>{token.text}</a>
+		{/if}
+	{:else if token.type === 'image'}
+		<Image src={token.href} alt={token.text} />
+	{:else if token.type === 'strong'}
+		<strong>
+			<svelte:self id={`${id}-strong`} tokens={token.tokens} />
+		</strong>
+	{:else if token.type === 'em'}
+		<em>
+			<svelte:self id={`${id}-em`} tokens={token.tokens} />
+		</em>
+	{:else if token.type === 'codespan'}
+		<!-- svelte-ignore a11y-click-events-have-key-events -->
+		<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
+		<code
+			class="codespan cursor-pointer"
+			on:click={() => {
+				copyToClipboard(unescapeHtml(token.text));
+				toast.success($i18n.t('Copied to clipboard'));
+			}}>{unescapeHtml(token.text)}</code
+		>
+	{:else if token.type === 'br'}
+		<br />
+	{:else if token.type === 'del'}
+		<del>
+			<svelte:self id={`${id}-del`} tokens={token.tokens} />
+		</del>
+	{:else if token.type === 'inlineKatex'}
+		{#if token.text}
+			<KatexRenderer content={revertSanitizedResponseContent(token.text)} displayMode={false} />
+		{/if}
+	{:else if token.type === 'iframe'}
+		<iframe
+			src="{WEBUI_BASE_URL}/api/v1/files/{token.fileId}/content"
+			title={token.fileId}
+			width="100%"
+			frameborder="0"
+			onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';"
+		></iframe>
+	{:else if token.type === 'text'}
+		{token.raw}
+	{/if}
+{/each}
diff --git a/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte b/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7fc61c327033035cfde20881ff579df66f435a02
--- /dev/null
+++ b/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte
@@ -0,0 +1,178 @@
+<script lang="ts">
+	import DOMPurify from 'dompurify';
+	import { createEventDispatcher, onMount } from 'svelte';
+	import { marked, type Token } from 'marked';
+	import { revertSanitizedResponseContent, unescapeHtml } from '$lib/utils';
+
+	import { WEBUI_BASE_URL } from '$lib/constants';
+
+	import CodeBlock from '$lib/components/chat/Messages/CodeBlock.svelte';
+	import MarkdownInlineTokens from '$lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte';
+	import KatexRenderer from './KatexRenderer.svelte';
+	import Collapsible from '$lib/components/common/Collapsible.svelte';
+
+	const dispatch = createEventDispatcher();
+
+	export let id: string;
+	export let tokens: Token[];
+	export let top = true;
+
+	export let save = false;
+
+	const headerComponent = (depth: number) => {
+		return 'h' + depth;
+	};
+</script>
+
+<!-- {JSON.stringify(tokens)} -->
+{#each tokens as token, tokenIdx (tokenIdx)}
+	{#if token.type === 'hr'}
+		<hr />
+	{:else if token.type === 'heading'}
+		<svelte:element this={headerComponent(token.depth)}>
+			<MarkdownInlineTokens id={`${id}-${tokenIdx}-h`} tokens={token.tokens} />
+		</svelte:element>
+	{:else if token.type === 'code'}
+		{#if token.raw.includes('```')}
+			<CodeBlock
+				id={`${id}-${tokenIdx}`}
+				{token}
+				lang={token?.lang ?? ''}
+				code={revertSanitizedResponseContent(token?.text ?? '')}
+				{save}
+				on:code={(e) => {
+					dispatch('code', e.detail);
+				}}
+				on:save={(e) => {
+					dispatch('update', {
+						raw: token.raw,
+						oldContent: token.text,
+						newContent: e.detail
+					});
+				}}
+			/>
+		{:else}
+			{token.text}
+		{/if}
+	{:else if token.type === 'table'}
+		<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full">
+			<table class="w-full">
+				<thead>
+					<tr>
+						{#each token.header as header, headerIdx}
+							<th style={token.align[headerIdx] ? '' : `text-align: ${token.align[headerIdx]}`}>
+								<MarkdownInlineTokens
+									id={`${id}-${tokenIdx}-header-${headerIdx}`}
+									tokens={header.tokens}
+								/>
+							</th>
+						{/each}
+					</tr>
+				</thead>
+				<tbody>
+					{#each token.rows as row, rowIdx}
+						<tr>
+							{#each row ?? [] as cell, cellIdx}
+								<td style={token.align[cellIdx] ? '' : `text-align: ${token.align[cellIdx]}`}>
+									<MarkdownInlineTokens
+										id={`${id}-${tokenIdx}-row-${rowIdx}-${cellIdx}`}
+										tokens={cell.tokens}
+									/>
+								</td>
+							{/each}
+						</tr>
+					{/each}
+				</tbody>
+			</table>
+		</div>
+	{:else if token.type === 'blockquote'}
+		<blockquote>
+			<svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} />
+		</blockquote>
+	{:else if token.type === 'list'}
+		{#if token.ordered}
+			<ol start={token.start || 1}>
+				{#each token.items as item, itemIdx}
+					<li>
+						<svelte:self
+							id={`${id}-${tokenIdx}-${itemIdx}`}
+							tokens={item.tokens}
+							top={token.loose}
+						/>
+					</li>
+				{/each}
+			</ol>
+		{:else}
+			<ul>
+				{#each token.items as item, itemIdx}
+					<li>
+						<svelte:self
+							id={`${id}-${tokenIdx}-${itemIdx}`}
+							tokens={item.tokens}
+							top={token.loose}
+						/>
+					</li>
+				{/each}
+			</ul>
+		{/if}
+	{:else if token.type === 'details'}
+		<Collapsible title={token.summary} className="w-fit space-y-1">
+			<div class=" mb-1.5" slot="content">
+				<svelte:self id={`${id}-${tokenIdx}-d`} tokens={marked.lexer(token.text)} />
+			</div>
+		</Collapsible>
+	{:else if token.type === 'html'}
+		{@const html = DOMPurify.sanitize(token.text)}
+		{#if html && html.includes('<video')}
+			{@html html}
+		{:else if token.text.includes(`<iframe src="${WEBUI_BASE_URL}/api/v1/files/`)}
+			{@html `${token.text}`}
+		{:else}
+			{token.text}
+		{/if}
+	{:else if token.type === 'iframe'}
+		<iframe
+			src="{WEBUI_BASE_URL}/api/v1/files/{token.fileId}/content"
+			title={token.fileId}
+			width="100%"
+			frameborder="0"
+			onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';"
+		></iframe>
+	{:else if token.type === 'paragraph'}
+		<p>
+			<MarkdownInlineTokens id={`${id}-${tokenIdx}-p`} tokens={token.tokens ?? []} />
+		</p>
+	{:else if token.type === 'text'}
+		{#if top}
+			<p>
+				{#if token.tokens}
+					<MarkdownInlineTokens id={`${id}-${tokenIdx}-t`} tokens={token.tokens} />
+				{:else}
+					{unescapeHtml(token.text)}
+				{/if}
+			</p>
+		{:else if token.tokens}
+			<MarkdownInlineTokens id={`${id}-${tokenIdx}-p`} tokens={token.tokens ?? []} />
+		{:else}
+			{unescapeHtml(token.text)}
+		{/if}
+	{:else if token.type === 'inlineKatex'}
+		{#if token.text}
+			<KatexRenderer
+				content={revertSanitizedResponseContent(token.text)}
+				displayMode={token?.displayMode ?? false}
+			/>
+		{/if}
+	{:else if token.type === 'blockKatex'}
+		{#if token.text}
+			<KatexRenderer
+				content={revertSanitizedResponseContent(token.text)}
+				displayMode={token?.displayMode ?? false}
+			/>
+		{/if}
+	{:else if token.type === 'space'}
+		<div class="my-2" />
+	{:else}
+		{console.log('Unknown token', token)}
+	{/if}
+{/each}
diff --git a/src/lib/components/chat/Messages/Message.svelte b/src/lib/components/chat/Messages/Message.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..283af001a48202f2860ef1aba1f2686b38db39f6
--- /dev/null
+++ b/src/lib/components/chat/Messages/Message.svelte
@@ -0,0 +1,141 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+
+	import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	import { settings } from '$lib/stores';
+	import { copyToClipboard } from '$lib/utils';
+
+	import MultiResponseMessages from './MultiResponseMessages.svelte';
+	import ResponseMessage from './ResponseMessage.svelte';
+	import UserMessage from './UserMessage.svelte';
+
+	export let chatId;
+	export let idx = 0;
+
+	export let history;
+	export let messageId;
+
+	export let user;
+
+	export let showPreviousMessage;
+	export let showNextMessage;
+
+	export let editMessage;
+	export let deleteMessage;
+	export let rateMessage;
+
+	export let regenerateResponse;
+	export let continueResponse;
+
+	// MultiResponseMessages
+	export let mergeResponses;
+
+	export let autoScroll = false;
+	export let readOnly = false;
+
+	onMount(() => {
+		// console.log('message', idx);
+	});
+</script>
+
+<div
+	class="flex flex-col justify-between px-5 mb-3 w-full {($settings?.widescreenMode ?? null)
+		? 'max-w-full'
+		: 'max-w-5xl'} mx-auto rounded-lg group"
+>
+	{#if history.messages[messageId]}
+		{#if history.messages[messageId].role === 'user'}
+			<UserMessage
+				{user}
+				{history}
+				{messageId}
+				isFirstMessage={idx === 0}
+				siblings={history.messages[messageId].parentId !== null
+					? (history.messages[history.messages[messageId].parentId]?.childrenIds ?? [])
+					: (Object.values(history.messages)
+							.filter((message) => message.parentId === null)
+							.map((message) => message.id) ?? [])}
+				{showPreviousMessage}
+				{showNextMessage}
+				{editMessage}
+				on:delete={() => deleteMessage(messageId)}
+				{readOnly}
+			/>
+		{:else if (history.messages[history.messages[messageId].parentId]?.models?.length ?? 1) === 1}
+			<ResponseMessage
+				{chatId}
+				{history}
+				{messageId}
+				isLastMessage={messageId === history.currentId}
+				siblings={history.messages[history.messages[messageId].parentId]?.childrenIds ?? []}
+				{showPreviousMessage}
+				{showNextMessage}
+				{editMessage}
+				{rateMessage}
+				{continueResponse}
+				{regenerateResponse}
+				on:submit={async (e) => {
+					dispatch('submit', e.detail);
+				}}
+				on:action={async (e) => {
+					dispatch('action', e.detail);
+				}}
+				on:update={async (e) => {
+					dispatch('update');
+				}}
+				on:save={async (e) => {
+					console.log('save', e);
+
+					const message = e.detail;
+					if (message) {
+						history.messages[message.id] = message;
+						dispatch('update');
+					} else {
+						dispatch('update');
+					}
+				}}
+				{readOnly}
+			/>
+		{:else}
+			<MultiResponseMessages
+				bind:history
+				{chatId}
+				{messageId}
+				isLastMessage={messageId === history?.currentId}
+				{rateMessage}
+				{editMessage}
+				{continueResponse}
+				{regenerateResponse}
+				{mergeResponses}
+				on:submit={async (e) => {
+					dispatch('submit', e.detail);
+				}}
+				on:action={async (e) => {
+					dispatch('action', e.detail);
+				}}
+				on:update={async (e) => {
+					dispatch('update');
+				}}
+				on:save={async (e) => {
+					console.log('save', e);
+					const message = e.detail;
+					if (message) {
+						history.messages[message.id] = message;
+						dispatch('update');
+					} else {
+						dispatch('update');
+					}
+				}}
+				on:change={async () => {
+					await tick();
+					dispatch('update');
+					dispatch('scroll');
+				}}
+				{readOnly}
+			/>
+		{/if}
+	{/if}
+</div>
diff --git a/src/lib/components/chat/Messages/MultiResponseMessages.svelte b/src/lib/components/chat/Messages/MultiResponseMessages.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..438bf1b3417ea6b154c8d12d0f1a1165c2ebe712
--- /dev/null
+++ b/src/lib/components/chat/Messages/MultiResponseMessages.svelte
@@ -0,0 +1,295 @@
+<script lang="ts">
+	import dayjs from 'dayjs';
+	import { onMount, tick, getContext } from 'svelte';
+	import { createEventDispatcher } from 'svelte';
+
+	import { mobile, settings } from '$lib/stores';
+
+	import { generateMoACompletion } from '$lib/apis';
+	import { updateChatById } from '$lib/apis/chats';
+	import { createOpenAITextStream } from '$lib/apis/streaming';
+
+	import ResponseMessage from './ResponseMessage.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Merge from '$lib/components/icons/Merge.svelte';
+
+	import Markdown from './Markdown.svelte';
+	import Name from './Name.svelte';
+	import Skeleton from './Skeleton.svelte';
+	const i18n = getContext('i18n');
+
+	export let chatId;
+	export let history;
+	export let messageId;
+
+	export let isLastMessage;
+	export let readOnly = false;
+
+	export let editMessage: Function;
+	export let rateMessage: Function;
+
+	export let continueResponse: Function;
+	export let regenerateResponse: Function;
+	export let mergeResponses: Function;
+
+	const dispatch = createEventDispatcher();
+
+	let currentMessageId;
+	let parentMessage;
+	let groupedMessageIds = {};
+	let groupedMessageIdsIdx = {};
+
+	let message = JSON.parse(JSON.stringify(history.messages[messageId]));
+	$: if (history.messages) {
+		if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
+			message = JSON.parse(JSON.stringify(history.messages[messageId]));
+		}
+	}
+
+	const showPreviousMessage = (modelIdx) => {
+		groupedMessageIdsIdx[modelIdx] = Math.max(0, groupedMessageIdsIdx[modelIdx] - 1);
+
+		let messageId = groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]];
+		console.log(messageId);
+
+		let messageChildrenIds = history.messages[messageId].childrenIds;
+
+		while (messageChildrenIds.length !== 0) {
+			messageId = messageChildrenIds.at(-1);
+			messageChildrenIds = history.messages[messageId].childrenIds;
+		}
+
+		history.currentId = messageId;
+		dispatch('change');
+	};
+
+	const showNextMessage = (modelIdx) => {
+		groupedMessageIdsIdx[modelIdx] = Math.min(
+			groupedMessageIds[modelIdx].messageIds.length - 1,
+			groupedMessageIdsIdx[modelIdx] + 1
+		);
+
+		let messageId = groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]];
+		console.log(messageId);
+
+		let messageChildrenIds = history.messages[messageId].childrenIds;
+
+		while (messageChildrenIds.length !== 0) {
+			messageId = messageChildrenIds.at(-1);
+			messageChildrenIds = history.messages[messageId].childrenIds;
+		}
+
+		history.currentId = messageId;
+		dispatch('change');
+	};
+
+	const initHandler = async () => {
+		console.log('multiresponse:initHandler');
+		await tick();
+
+		currentMessageId = messageId;
+		parentMessage = history.messages[messageId].parentId
+			? history.messages[history.messages[messageId].parentId]
+			: null;
+
+		groupedMessageIds = parentMessage?.models.reduce((a, model, modelIdx) => {
+			// Find all messages that are children of the parent message and have the same model
+			let modelMessageIds = parentMessage?.childrenIds
+				.map((id) => history.messages[id])
+				.filter((m) => m?.modelIdx === modelIdx)
+				.map((m) => m.id);
+
+			// Legacy support for messages that don't have a modelIdx
+			// Find all messages that are children of the parent message and have the same model
+			if (modelMessageIds.length === 0) {
+				let modelMessages = parentMessage?.childrenIds
+					.map((id) => history.messages[id])
+					.filter((m) => m?.model === model);
+
+				modelMessages.forEach((m) => {
+					m.modelIdx = modelIdx;
+				});
+
+				modelMessageIds = modelMessages.map((m) => m.id);
+			}
+
+			return {
+				...a,
+				[modelIdx]: { messageIds: modelMessageIds }
+			};
+		}, {});
+
+		groupedMessageIdsIdx = parentMessage?.models.reduce((a, model, modelIdx) => {
+			const idx = groupedMessageIds[modelIdx].messageIds.findIndex((id) => id === messageId);
+			if (idx !== -1) {
+				return {
+					...a,
+					[modelIdx]: idx
+				};
+			} else {
+				return {
+					...a,
+					[modelIdx]: groupedMessageIds[modelIdx].messageIds.length - 1
+				};
+			}
+		}, {});
+
+		console.log(groupedMessageIds, groupedMessageIdsIdx);
+
+		await tick();
+	};
+
+	const mergeResponsesHandler = async () => {
+		const responses = Object.keys(groupedMessageIds).map((modelIdx) => {
+			const { messageIds } = groupedMessageIds[modelIdx];
+			const messageId = messageIds[groupedMessageIdsIdx[modelIdx]];
+
+			return history.messages[messageId].content;
+		});
+		mergeResponses(messageId, responses, chatId);
+	};
+
+	onMount(async () => {
+		await initHandler();
+		await tick();
+
+		const messageElement = document.getElementById(`message-${messageId}`);
+		if (messageElement) {
+			messageElement.scrollIntoView({ block: 'start' });
+		}
+	});
+</script>
+
+{#if parentMessage}
+	<div>
+		<div
+			class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
+			id="responses-container-{chatId}-{parentMessage.id}"
+		>
+			{#each Object.keys(groupedMessageIds) as modelIdx}
+				{#if groupedMessageIdsIdx[modelIdx] !== undefined && groupedMessageIds[modelIdx].messageIds.length > 0}
+					<!-- svelte-ignore a11y-no-static-element-interactions -->
+					<!-- svelte-ignore a11y-click-events-have-key-events -->
+					{@const _messageId =
+						groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]]}
+
+					<div
+						class=" snap-center w-full max-w-full m-1 border {history.messages[messageId]
+							?.modelIdx == modelIdx
+							? `border-gray-100 dark:border-gray-800 border-[1.5px] ${
+									$mobile ? 'min-w-full' : 'min-w-80'
+								}`
+							: `border-gray-50 dark:border-gray-850 border-dashed ${
+									$mobile ? 'min-w-full' : 'min-w-80'
+								}`} transition-all p-5 rounded-2xl"
+						on:click={() => {
+							if (messageId != _messageId) {
+								let currentMessageId = _messageId;
+								let messageChildrenIds = history.messages[currentMessageId].childrenIds;
+								while (messageChildrenIds.length !== 0) {
+									currentMessageId = messageChildrenIds.at(-1);
+									messageChildrenIds = history.messages[currentMessageId].childrenIds;
+								}
+								history.currentId = currentMessageId;
+								dispatch('change');
+							}
+						}}
+					>
+						{#key history.currentId}
+							{#if message}
+								<ResponseMessage
+									{chatId}
+									{history}
+									messageId={_messageId}
+									isLastMessage={true}
+									siblings={groupedMessageIds[modelIdx].messageIds}
+									showPreviousMessage={() => showPreviousMessage(modelIdx)}
+									showNextMessage={() => showNextMessage(modelIdx)}
+									{rateMessage}
+									{editMessage}
+									{continueResponse}
+									regenerateResponse={async (message) => {
+										regenerateResponse(message);
+										await tick();
+										groupedMessageIdsIdx[modelIdx] =
+											groupedMessageIds[modelIdx].messageIds.length - 1;
+									}}
+									on:submit={async (e) => {
+										dispatch('submit', e.detail);
+									}}
+									on:action={async (e) => {
+										dispatch('action', e.detail);
+									}}
+									on:update={async (e) => {
+										dispatch('update', e.detail);
+									}}
+									on:save={async (e) => {
+										dispatch('save', e.detail);
+									}}
+									{readOnly}
+								/>
+							{/if}
+						{/key}
+					</div>
+				{/if}
+			{/each}
+		</div>
+
+		{#if !readOnly}
+			{#if !Object.keys(groupedMessageIds).find((modelIdx) => {
+				const { messageIds } = groupedMessageIds[modelIdx];
+				const _messageId = messageIds[groupedMessageIdsIdx[modelIdx]];
+				return !history.messages[_messageId]?.done ?? false;
+			})}
+				<div class="flex justify-end">
+					<div class="w-full">
+						{#if history.messages[messageId]?.merged?.status}
+							{@const message = history.messages[messageId]?.merged}
+
+							<div class="w-full rounded-xl pl-5 pr-2 py-2">
+								<Name>
+									Merged Response
+
+									{#if message.timestamp}
+										<span
+											class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
+										>
+											{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
+										</span>
+									{/if}
+								</Name>
+
+								<div class="mt-1 markdown-prose w-full min-w-full">
+									{#if (message?.content ?? '') === ''}
+										<Skeleton />
+									{:else}
+										<Markdown id={`merged`} content={message.content ?? ''} />
+									{/if}
+								</div>
+							</div>
+						{/if}
+					</div>
+
+					{#if isLastMessage}
+						<div class=" flex-shrink-0 text-gray-600 dark:text-gray-500 mt-1">
+							<Tooltip content={$i18n.t('Merge Responses')} placement="bottom">
+								<button
+									type="button"
+									id="merge-response-button"
+									class="{true
+										? 'visible'
+										: 'invisible group-hover:visible'} p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
+									on:click={() => {
+										mergeResponsesHandler();
+									}}
+								>
+									<Merge className=" size-5 " />
+								</button>
+							</Tooltip>
+						</div>
+					{/if}
+				</div>
+			{/if}
+		{/if}
+	</div>
+{/if}
diff --git a/src/lib/components/chat/Messages/Name.svelte b/src/lib/components/chat/Messages/Name.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..53879b24adeb3a8b86ec8e956cf36cd6d7e1dfab
--- /dev/null
+++ b/src/lib/components/chat/Messages/Name.svelte
@@ -0,0 +1,3 @@
+<div class=" self-center font-semibold mb-0.5 line-clamp-1 contents">
+	<slot />
+</div>
diff --git a/src/lib/components/chat/Messages/ProfileImage.svelte b/src/lib/components/chat/Messages/ProfileImage.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7161ef92c138e5e776d5c7e0149075c4deacee83
--- /dev/null
+++ b/src/lib/components/chat/Messages/ProfileImage.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+	import { settings } from '$lib/stores';
+	import ProfileImageBase from './ProfileImageBase.svelte';
+
+	export let className = 'size-8';
+	export let src = '';
+</script>
+
+<div class={`flex-shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
+	<ProfileImageBase {src} {className} />
+</div>
diff --git a/src/lib/components/chat/Messages/ProfileImageBase.svelte b/src/lib/components/chat/Messages/ProfileImageBase.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..dce2385a5c5c29bb33bf112833d617f184b26e3c
--- /dev/null
+++ b/src/lib/components/chat/Messages/ProfileImageBase.svelte
@@ -0,0 +1,21 @@
+<script lang="ts">
+	import { WEBUI_BASE_URL } from '$lib/constants';
+
+	export let className = 'size-8';
+	export let src = `${WEBUI_BASE_URL}/static/favicon.png`;
+</script>
+
+<img
+	crossorigin="anonymous"
+	src={src === ''
+		? `${WEBUI_BASE_URL}/static/favicon.png`
+		: src.startsWith(WEBUI_BASE_URL) ||
+			  src.startsWith('https://www.gravatar.com/avatar/') ||
+			  src.startsWith('data:') ||
+			  src.startsWith('/')
+			? src
+			: `/user.png`}
+	class=" {className} object-cover rounded-full -translate-y-[1px]"
+	alt="profile"
+	draggable="false"
+/>
diff --git a/src/lib/components/chat/Messages/RateComment.svelte b/src/lib/components/chat/Messages/RateComment.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2cccf20d814bcb963ede238983a23088f6bff02a
--- /dev/null
+++ b/src/lib/components/chat/Messages/RateComment.svelte
@@ -0,0 +1,206 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+
+	import { createEventDispatcher, onMount, getContext } from 'svelte';
+	import { config, models } from '$lib/stores';
+	import Tags from '$lib/components/common/Tags.svelte';
+
+	const i18n = getContext('i18n');
+
+	const dispatch = createEventDispatcher();
+
+	export let message;
+	export let show = false;
+
+	let LIKE_REASONS = [
+		'accurate_information',
+		'followed_instructions_perfectly',
+		'showcased_creativity',
+		'positive_attitude',
+		'attention_to_detail',
+		'thorough_explanation',
+		'other'
+	];
+	let DISLIKE_REASONS = [
+		'dont_like_the_style',
+		'too_verbose',
+		'not_helpful',
+		'not_factually_correct',
+		'didnt_fully_follow_instructions',
+		'refused_when_it_shouldnt_have',
+		'being_lazy',
+		'other'
+	];
+
+	let tags = [];
+
+	let reasons = [];
+	let selectedReason = null;
+	let comment = '';
+
+	let selectedModel = null;
+
+	$: if (message?.annotation?.rating === 1) {
+		reasons = LIKE_REASONS;
+	} else if (message?.annotation?.rating === -1) {
+		reasons = DISLIKE_REASONS;
+	}
+
+	$: if (message) {
+		init();
+	}
+
+	const init = () => {
+		selectedReason = message?.annotation?.reason ?? '';
+		comment = message?.annotation?.comment ?? '';
+		tags = (message?.annotation?.tags ?? []).map((tag) => ({
+			name: tag
+		}));
+	};
+
+	onMount(() => {
+		if (message?.arena) {
+			selectedModel = $models.find((m) => m.id === message.selectedModelId);
+			toast.success(
+				$i18n.t('This response was generated by "{{model}}"', {
+					model: selectedModel ? (selectedModel?.name ?? selectedModel.id) : message.selectedModelId
+				})
+			);
+		}
+	});
+
+	const saveHandler = () => {
+		console.log('saveHandler');
+		// if (!selectedReason) {
+		// 	toast.error($i18n.t('Please select a reason'));
+		// 	return;
+		// }
+
+		dispatch('save', {
+			reason: selectedReason,
+			comment: comment,
+			tags: tags.map((tag) => tag.name)
+		});
+
+		toast.success($i18n.t('Thanks for your feedback!'));
+		show = false;
+	};
+</script>
+
+{#if message?.arena}
+	<div class="text-xs font-medium pt-1.5 -mb-0.5">
+		{$i18n.t('This response was generated by "{{model}}"', {
+			model: selectedModel ? (selectedModel?.name ?? selectedModel.id) : message.selectedModelId
+		})}
+	</div>
+{/if}
+
+<div
+	class=" my-2.5 rounded-xl px-4 py-3 border border-gray-50 dark:border-gray-850"
+	id="message-feedback-{message.id}"
+>
+	<div class="flex justify-between items-center">
+		<div class=" text-sm">{$i18n.t('Tell us more:')}</div>
+
+		<button
+			on:click={() => {
+				show = false;
+			}}
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				fill="none"
+				viewBox="0 0 24 24"
+				stroke-width="1.5"
+				stroke="currentColor"
+				class="size-4"
+			>
+				<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
+			</svg>
+		</button>
+	</div>
+
+	{#if reasons.length > 0}
+		<div class="flex flex-wrap gap-1.5 text-sm mt-2.5">
+			{#each reasons as reason}
+				<button
+					class="px-3 py-0.5 border border-gray-50 dark:border-gray-850 hover:bg-gray-100 dark:hover:bg-gray-850 {selectedReason ===
+					reason
+						? 'bg-gray-200 dark:bg-gray-800'
+						: ''} transition rounded-lg"
+					on:click={() => {
+						selectedReason = reason;
+					}}
+				>
+					{#if reason === 'accurate_information'}
+						{$i18n.t('Accurate information')}
+					{:else if reason === 'followed_instructions_perfectly'}
+						{$i18n.t('Followed instructions perfectly')}
+					{:else if reason === 'showcased_creativity'}
+						{$i18n.t('Showcased creativity')}
+					{:else if reason === 'positive_attitude'}
+						{$i18n.t('Positive attitude')}
+					{:else if reason === 'attention_to_detail'}
+						{$i18n.t('Attention to detail')}
+					{:else if reason === 'thorough_explanation'}
+						{$i18n.t('Thorough explanation')}
+					{:else if reason === 'dont_like_the_style'}
+						{$i18n.t("Don't like the style")}
+					{:else if reason === 'too_verbose'}
+						{$i18n.t('Too verbose')}
+					{:else if reason === 'not_helpful'}
+						{$i18n.t('Not helpful')}
+					{:else if reason === 'not_factually_correct'}
+						{$i18n.t('Not factually correct')}
+					{:else if reason === 'didnt_fully_follow_instructions'}
+						{$i18n.t("Didn't fully follow instructions")}
+					{:else if reason === 'refused_when_it_shouldnt_have'}
+						{$i18n.t("Refused when it shouldn't have")}
+					{:else if reason === 'being_lazy'}
+						{$i18n.t('Being lazy')}
+					{:else if reason === 'other'}
+						{$i18n.t('Other')}
+					{:else}
+						{reason}
+					{/if}
+				</button>
+			{/each}
+		</div>
+	{/if}
+
+	<div class="mt-2">
+		<textarea
+			bind:value={comment}
+			class="w-full text-sm px-1 py-2 bg-transparent outline-none resize-none rounded-xl"
+			placeholder={$i18n.t('Feel free to add specific details')}
+			rows="3"
+		/>
+	</div>
+
+	<div class="mt-2 gap-1.5 flex justify-between">
+		<div class="flex items-end group">
+			<Tags
+				{tags}
+				on:delete={(e) => {
+					tags = tags.filter(
+						(tag) =>
+							tag.name.replaceAll(' ', '_').toLowerCase() !==
+							e.detail.replaceAll(' ', '_').toLowerCase()
+					);
+				}}
+				on:add={(e) => {
+					tags = [...tags, { name: e.detail }];
+				}}
+			/>
+		</div>
+
+		<button
+			class=" bg-emerald-700 hover:bg-emerald-800 transition text-white text-sm font-medium rounded-xl px-3.5 py-1.5"
+			on:click={() => {
+				saveHandler();
+			}}
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</div>
diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6d987e8d5b6eb6fe561e08475bf1c4bf51479897
--- /dev/null
+++ b/src/lib/components/chat/Messages/ResponseMessage.svelte
@@ -0,0 +1,1320 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import dayjs from 'dayjs';
+
+	import { createEventDispatcher } from 'svelte';
+	import { onMount, tick, getContext } from 'svelte';
+
+	const i18n = getContext<Writable<i18nType>>('i18n');
+
+	const dispatch = createEventDispatcher();
+
+	import { config, models, settings, user } from '$lib/stores';
+	import { synthesizeOpenAISpeech } from '$lib/apis/audio';
+	import { imageGenerations } from '$lib/apis/images';
+	import {
+		copyToClipboard as _copyToClipboard,
+		approximateToHumanReadable,
+		extractParagraphsForAudio,
+		extractSentencesForAudio,
+		cleanText,
+		getMessageContentParts,
+		sanitizeResponseContent,
+		createMessagesList
+	} from '$lib/utils';
+	import { WEBUI_BASE_URL } from '$lib/constants';
+
+	import Name from './Name.svelte';
+	import ProfileImage from './ProfileImage.svelte';
+	import Skeleton from './Skeleton.svelte';
+	import Image from '$lib/components/common/Image.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import RateComment from './RateComment.svelte';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import WebSearchResults from './ResponseMessage/WebSearchResults.svelte';
+	import Sparkles from '$lib/components/icons/Sparkles.svelte';
+	import Markdown from './Markdown.svelte';
+	import Error from './Error.svelte';
+	import Citations from './Citations.svelte';
+	import CodeExecutions from './CodeExecutions.svelte';
+
+	import type { Writable } from 'svelte/store';
+	import type { i18n as i18nType } from 'i18next';
+	import ContentRenderer from './ContentRenderer.svelte';
+	import { createNewFeedback, getFeedbackById, updateFeedbackById } from '$lib/apis/evaluations';
+	import { getChatById } from '$lib/apis/chats';
+	import { generateTags } from '$lib/apis';
+
+	interface MessageType {
+		id: string;
+		model: string;
+		content: string;
+		files?: { type: string; url: string }[];
+		timestamp: number;
+		role: string;
+		statusHistory?: {
+			done: boolean;
+			action: string;
+			description: string;
+			urls?: string[];
+			query?: string;
+		}[];
+		status?: {
+			done: boolean;
+			action: string;
+			description: string;
+			urls?: string[];
+			query?: string;
+		};
+		done: boolean;
+		error?: boolean | { content: string };
+		citations?: string[];
+		code_executions?: {
+			uuid: string;
+			name: string;
+			code: string;
+			language?: string;
+			result?: {
+				error?: string;
+				output?: string;
+				files?: { name: string; url: string }[];
+			};
+		}[];
+		info?: {
+			openai?: boolean;
+			prompt_tokens?: number;
+			completion_tokens?: number;
+			total_tokens?: number;
+			eval_count?: number;
+			eval_duration?: number;
+			prompt_eval_count?: number;
+			prompt_eval_duration?: number;
+			total_duration?: number;
+			load_duration?: number;
+			usage?: unknown;
+		};
+		annotation?: { type: string; rating: number };
+	}
+
+	export let chatId = '';
+	export let history;
+	export let messageId;
+
+	let message: MessageType = JSON.parse(JSON.stringify(history.messages[messageId]));
+	$: if (history.messages) {
+		if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
+			message = JSON.parse(JSON.stringify(history.messages[messageId]));
+		}
+	}
+
+	export let siblings;
+
+	export let showPreviousMessage: Function;
+	export let showNextMessage: Function;
+
+	export let editMessage: Function;
+	export let rateMessage: Function;
+
+	export let continueResponse: Function;
+	export let regenerateResponse: Function;
+
+	export let isLastMessage = true;
+	export let readOnly = false;
+
+	let model = null;
+	$: model = $models.find((m) => m.id === message.model);
+
+	let edit = false;
+	let editedContent = '';
+	let editTextAreaElement: HTMLTextAreaElement;
+
+	let audioParts: Record<number, HTMLAudioElement | null> = {};
+	let speaking = false;
+	let speakingIdx: number | undefined;
+
+	let loadingSpeech = false;
+	let generatingImage = false;
+
+	let showRateComment = false;
+
+	const copyToClipboard = async (text) => {
+		const res = await _copyToClipboard(text);
+		if (res) {
+			toast.success($i18n.t('Copying to clipboard was successful!'));
+		}
+	};
+
+	const playAudio = (idx: number) => {
+		return new Promise<void>((res) => {
+			speakingIdx = idx;
+			const audio = audioParts[idx];
+
+			if (!audio) {
+				return res();
+			}
+
+			audio.play();
+			audio.onended = async () => {
+				await new Promise((r) => setTimeout(r, 300));
+
+				if (Object.keys(audioParts).length - 1 === idx) {
+					speaking = false;
+				}
+
+				res();
+			};
+		});
+	};
+
+	const toggleSpeakMessage = async () => {
+		if (speaking) {
+			try {
+				speechSynthesis.cancel();
+
+				if (speakingIdx !== undefined && audioParts[speakingIdx]) {
+					audioParts[speakingIdx]!.pause();
+					audioParts[speakingIdx]!.currentTime = 0;
+				}
+			} catch {}
+
+			speaking = false;
+			speakingIdx = undefined;
+			return;
+		}
+
+		if (!(message?.content ?? '').trim().length) {
+			toast.info($i18n.t('No content to speak'));
+			return;
+		}
+
+		speaking = true;
+
+		if ($config.audio.tts.engine !== '') {
+			loadingSpeech = true;
+
+			const messageContentParts: string[] = getMessageContentParts(
+				message.content,
+				$config?.audio?.tts?.split_on ?? 'punctuation'
+			);
+
+			if (!messageContentParts.length) {
+				console.log('No content to speak');
+				toast.info($i18n.t('No content to speak'));
+
+				speaking = false;
+				loadingSpeech = false;
+				return;
+			}
+
+			console.debug('Prepared message content for TTS', messageContentParts);
+
+			audioParts = messageContentParts.reduce(
+				(acc, _sentence, idx) => {
+					acc[idx] = null;
+					return acc;
+				},
+				{} as typeof audioParts
+			);
+
+			let lastPlayedAudioPromise = Promise.resolve(); // Initialize a promise that resolves immediately
+
+			for (const [idx, sentence] of messageContentParts.entries()) {
+				const res = await synthesizeOpenAISpeech(
+					localStorage.token,
+					$settings?.audio?.tts?.defaultVoice === $config.audio.tts.voice
+						? ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
+						: $config?.audio?.tts?.voice,
+					sentence
+				).catch((error) => {
+					console.error(error);
+					toast.error(error);
+
+					speaking = false;
+					loadingSpeech = false;
+				});
+
+				if (res) {
+					const blob = await res.blob();
+					const blobUrl = URL.createObjectURL(blob);
+					const audio = new Audio(blobUrl);
+					audio.playbackRate = $settings.audio?.tts?.playbackRate ?? 1;
+
+					audioParts[idx] = audio;
+					loadingSpeech = false;
+					lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx));
+				}
+			}
+		} else {
+			let voices = [];
+			const getVoicesLoop = setInterval(() => {
+				voices = speechSynthesis.getVoices();
+				if (voices.length > 0) {
+					clearInterval(getVoicesLoop);
+
+					const voice =
+						voices
+							?.filter(
+								(v) => v.voiceURI === ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
+							)
+							?.at(0) ?? undefined;
+
+					console.log(voice);
+
+					const speak = new SpeechSynthesisUtterance(message.content);
+					speak.rate = $settings.audio?.tts?.playbackRate ?? 1;
+
+					console.log(speak);
+
+					speak.onend = () => {
+						speaking = false;
+						if ($settings.conversationMode) {
+							document.getElementById('voice-input-button')?.click();
+						}
+					};
+
+					if (voice) {
+						speak.voice = voice;
+					}
+
+					speechSynthesis.speak(speak);
+				}
+			}, 100);
+		}
+	};
+
+	const editMessageHandler = async () => {
+		edit = true;
+		editedContent = message.content;
+
+		await tick();
+
+		editTextAreaElement.style.height = '';
+		editTextAreaElement.style.height = `${editTextAreaElement.scrollHeight}px`;
+	};
+
+	const editMessageConfirmHandler = async () => {
+		editMessage(message.id, editedContent ? editedContent : '', false);
+
+		edit = false;
+		editedContent = '';
+
+		await tick();
+	};
+
+	const saveAsCopyHandler = async () => {
+		editMessage(message.id, editedContent ? editedContent : '');
+
+		edit = false;
+		editedContent = '';
+
+		await tick();
+	};
+
+	const cancelEditMessage = async () => {
+		edit = false;
+		editedContent = '';
+		await tick();
+	};
+
+	const generateImage = async (message: MessageType) => {
+		generatingImage = true;
+		const res = await imageGenerations(localStorage.token, message.content).catch((error) => {
+			toast.error(error);
+		});
+		console.log(res);
+
+		if (res) {
+			const files = res.map((image) => ({
+				type: 'image',
+				url: `${image.url}`
+			}));
+
+			dispatch('save', { ...message, files: files });
+		}
+
+		generatingImage = false;
+	};
+
+	let feedbackLoading = false;
+
+	const feedbackHandler = async (
+		rating: number | null = null,
+		annotation: object | null = null
+	) => {
+		feedbackLoading = true;
+		console.log('Feedback', rating, annotation);
+
+		const updatedMessage = {
+			...message,
+			annotation: {
+				...(message?.annotation ?? {}),
+				...(rating !== null ? { rating: rating } : {}),
+				...(annotation ? annotation : {})
+			}
+		};
+
+		const chat = await getChatById(localStorage.token, chatId).catch((error) => {
+			toast.error(error);
+		});
+		if (!chat) {
+			return;
+		}
+
+		const messages = createMessagesList(history, message.id);
+
+		let feedbackItem = {
+			type: 'rating',
+			data: {
+				...(updatedMessage?.annotation ? updatedMessage.annotation : {}),
+				model_id: message?.selectedModelId ?? message.model,
+				...(history.messages[message.parentId].childrenIds.length > 1
+					? {
+							sibling_model_ids: history.messages[message.parentId].childrenIds
+								.filter((id) => id !== message.id)
+								.map((id) => history.messages[id]?.selectedModelId ?? history.messages[id].model)
+						}
+					: {})
+			},
+			meta: {
+				arena: message ? message.arena : false,
+				model_id: message.model,
+				message_id: message.id,
+				message_index: messages.length,
+				chat_id: chatId
+			},
+			snapshot: {
+				chat: chat
+			}
+		};
+
+		const baseModels = [
+			feedbackItem.data.model_id,
+			...(feedbackItem.data.sibling_model_ids ?? [])
+		].reduce((acc, modelId) => {
+			const model = $models.find((m) => m.id === modelId);
+			if (model) {
+				acc[model.id] = model?.info?.base_model_id ?? null;
+			} else {
+				// Log or handle cases where corresponding model is not found
+				console.warn(`Model with ID ${modelId} not found`);
+			}
+			return acc;
+		}, {});
+		feedbackItem.meta.base_models = baseModels;
+
+		let feedback = null;
+		if (message?.feedbackId) {
+			feedback = await updateFeedbackById(
+				localStorage.token,
+				message.feedbackId,
+				feedbackItem
+			).catch((error) => {
+				toast.error(error);
+			});
+		} else {
+			feedback = await createNewFeedback(localStorage.token, feedbackItem).catch((error) => {
+				toast.error(error);
+			});
+
+			if (feedback) {
+				updatedMessage.feedbackId = feedback.id;
+			}
+		}
+
+		console.log(updatedMessage);
+		dispatch('save', updatedMessage);
+
+		await tick();
+
+		if (!annotation) {
+			showRateComment = true;
+
+			if (!updatedMessage.annotation?.tags) {
+				// attempt to generate tags
+				const tags = await generateTags(localStorage.token, message.model, messages, chatId).catch(
+					(error) => {
+						console.error(error);
+						return [];
+					}
+				);
+				console.log(tags);
+
+				if (tags) {
+					updatedMessage.annotation.tags = tags;
+					feedbackItem.data.tags = tags;
+
+					dispatch('save', updatedMessage);
+					await updateFeedbackById(
+						localStorage.token,
+						updatedMessage.feedbackId,
+						feedbackItem
+					).catch((error) => {
+						toast.error(error);
+					});
+				}
+			}
+		}
+
+		feedbackLoading = false;
+	};
+
+	$: if (!edit) {
+		(async () => {
+			await tick();
+		})();
+	}
+
+	onMount(async () => {
+		console.log('ResponseMessage mounted');
+
+		await tick();
+	});
+</script>
+
+{#key message.id}
+	<div
+		class=" flex w-full message-{message.id}"
+		id="message-{message.id}"
+		dir={$settings.chatDirection}
+	>
+		<ProfileImage
+			src={model?.info?.meta?.profile_image_url ??
+				($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
+		/>
+
+		<div class="flex-auto w-0 pl-1">
+			<Name>
+				{model?.name ?? message.model}
+
+				{#if message.timestamp}
+					<span
+						class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
+					>
+						{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
+					</span>
+				{/if}
+			</Name>
+
+			<div>
+				{#if message?.files && message.files?.filter((f) => f.type === 'image').length > 0}
+					<div class="my-2.5 w-full flex overflow-x-auto gap-2 flex-wrap">
+						{#each message.files as file}
+							<div>
+								{#if file.type === 'image'}
+									<Image src={file.url} alt={message.content} />
+								{/if}
+							</div>
+						{/each}
+					</div>
+				{/if}
+
+				<div class="chat-{message.role} w-full min-w-full markdown-prose">
+					<div>
+						{#if (message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length > 0}
+							{@const status = (
+								message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]
+							).at(-1)}
+							<div class="status-description flex items-center gap-2 pt-0.5 pb-1">
+								{#if status?.done === false}
+									<div class="">
+										<Spinner className="size-4" />
+									</div>
+								{/if}
+
+								{#if status?.action === 'web_search' && status?.urls}
+									<WebSearchResults {status}>
+										<div class="flex flex-col justify-center -space-y-0.5">
+											<div
+												class="{status?.done === false
+													? 'shimmer'
+													: ''} text-base line-clamp-1 text-wrap"
+											>
+												{status?.description}
+											</div>
+										</div>
+									</WebSearchResults>
+								{:else}
+									<div class="flex flex-col justify-center -space-y-0.5">
+										<div
+											class="{status?.done === false
+												? 'shimmer'
+												: ''} text-gray-500 dark:text-gray-500 text-base line-clamp-1 text-wrap"
+										>
+											{status?.description}
+										</div>
+									</div>
+								{/if}
+							</div>
+						{/if}
+
+						{#if edit === true}
+							<div class="w-full bg-gray-50 dark:bg-gray-800 rounded-3xl px-5 py-3 my-2">
+								<textarea
+									id="message-edit-{message.id}"
+									bind:this={editTextAreaElement}
+									class=" bg-transparent outline-none w-full resize-none"
+									bind:value={editedContent}
+									on:input={(e) => {
+										e.target.style.height = '';
+										e.target.style.height = `${e.target.scrollHeight}px`;
+									}}
+									on:keydown={(e) => {
+										if (e.key === 'Escape') {
+											document.getElementById('close-edit-message-button')?.click();
+										}
+
+										const isCmdOrCtrlPressed = e.metaKey || e.ctrlKey;
+										const isEnterPressed = e.key === 'Enter';
+
+										if (isCmdOrCtrlPressed && isEnterPressed) {
+											document.getElementById('confirm-edit-message-button')?.click();
+										}
+									}}
+								/>
+
+								<div class=" mt-2 mb-1 flex justify-between text-sm font-medium">
+									<div>
+										<button
+											id="save-new-message-button"
+											class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
+											on:click={() => {
+												saveAsCopyHandler();
+											}}
+										>
+											{$i18n.t('Save As Copy')}
+										</button>
+									</div>
+
+									<div class="flex space-x-1.5">
+										<button
+											id="close-edit-message-button"
+											class="px-4 py-2 bg-white dark:bg-gray-900 hover:bg-gray-100 text-gray-800 dark:text-gray-100 transition rounded-3xl"
+											on:click={() => {
+												cancelEditMessage();
+											}}
+										>
+											{$i18n.t('Cancel')}
+										</button>
+
+										<button
+											id="confirm-edit-message-button"
+											class=" px-4 py-2 bg-gray-900 dark:bg-white hover:bg-gray-850 text-gray-100 dark:text-gray-800 transition rounded-3xl"
+											on:click={() => {
+												editMessageConfirmHandler();
+											}}
+										>
+											{$i18n.t('Save')}
+										</button>
+									</div>
+								</div>
+							</div>
+						{:else}
+							<div class="w-full flex flex-col relative" id="response-content-container">
+								{#if message.content === '' && !message.error}
+									<Skeleton />
+								{:else if message.content && message.error !== true}
+									<!-- always show message contents even if there's an error -->
+									<!-- unless message.error === true which is legacy error handling, where the error message is stored in message.content -->
+									<ContentRenderer
+										id={message.id}
+										content={message.content}
+										floatingButtons={message?.done}
+										save={!readOnly}
+										{model}
+										on:update={(e) => {
+											const { raw, oldContent, newContent } = e.detail;
+
+											history.messages[message.id].content = history.messages[
+												message.id
+											].content.replace(raw, raw.replace(oldContent, newContent));
+
+											dispatch('update');
+										}}
+										on:select={(e) => {
+											const { type, content } = e.detail;
+
+											if (type === 'explain') {
+												dispatch('submit', {
+													parentId: message.id,
+													prompt: `Explain this section to me in more detail\n\n\`\`\`\n${content}\n\`\`\``
+												});
+											} else if (type === 'ask') {
+												const input = e.detail?.input ?? '';
+												dispatch('submit', {
+													parentId: message.id,
+													prompt: `\`\`\`\n${content}\n\`\`\`\n${input}`
+												});
+											}
+										}}
+									/>
+								{/if}
+
+								{#if message.error}
+									<Error content={message?.error?.content ?? message.content} />
+								{/if}
+
+								{#if message.citations}
+									<Citations citations={message.citations} />
+								{/if}
+
+								{#if message.code_executions}
+									<CodeExecutions codeExecutions={message.code_executions} />
+								{/if}
+							</div>
+						{/if}
+					</div>
+				</div>
+
+				{#if !edit}
+					{#if message.done || siblings.length > 1}
+						<div
+							class=" flex justify-start overflow-x-auto buttons text-gray-600 dark:text-gray-500 mt-0.5"
+						>
+							{#if siblings.length > 1}
+								<div class="flex self-center min-w-fit" dir="ltr">
+									<button
+										class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
+										on:click={() => {
+											showPreviousMessage(message);
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											fill="none"
+											viewBox="0 0 24 24"
+											stroke="currentColor"
+											stroke-width="2.5"
+											class="size-3.5"
+										>
+											<path
+												stroke-linecap="round"
+												stroke-linejoin="round"
+												d="M15.75 19.5 8.25 12l7.5-7.5"
+											/>
+										</svg>
+									</button>
+
+									<div
+										class="text-sm tracking-widest font-semibold self-center dark:text-gray-100 min-w-fit"
+									>
+										{siblings.indexOf(message.id) + 1}/{siblings.length}
+									</div>
+
+									<button
+										class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
+										on:click={() => {
+											showNextMessage(message);
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											fill="none"
+											viewBox="0 0 24 24"
+											stroke="currentColor"
+											stroke-width="2.5"
+											class="size-3.5"
+										>
+											<path
+												stroke-linecap="round"
+												stroke-linejoin="round"
+												d="m8.25 4.5 7.5 7.5-7.5 7.5"
+											/>
+										</svg>
+									</button>
+								</div>
+							{/if}
+
+							{#if message.done}
+								{#if !readOnly}
+									{#if $user.role === 'user' ? ($config?.permissions?.chat?.editing ?? true) : true}
+										<Tooltip content={$i18n.t('Edit')} placement="bottom">
+											<button
+												class="{isLastMessage
+													? 'visible'
+													: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
+												on:click={() => {
+													editMessageHandler();
+												}}
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke-width="2.3"
+													stroke="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+									{/if}
+								{/if}
+
+								<Tooltip content={$i18n.t('Copy')} placement="bottom">
+									<button
+										class="{isLastMessage
+											? 'visible'
+											: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition copy-response-button"
+										on:click={() => {
+											copyToClipboard(message.content);
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											fill="none"
+											viewBox="0 0 24 24"
+											stroke-width="2.3"
+											stroke="currentColor"
+											class="w-4 h-4"
+										>
+											<path
+												stroke-linecap="round"
+												stroke-linejoin="round"
+												d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
+											/>
+										</svg>
+									</button>
+								</Tooltip>
+
+								<Tooltip content={$i18n.t('Read Aloud')} placement="bottom">
+									<button
+										id="speak-button-{message.id}"
+										class="{isLastMessage
+											? 'visible'
+											: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
+										on:click={() => {
+											if (!loadingSpeech) {
+												toggleSpeakMessage();
+											}
+										}}
+									>
+										{#if loadingSpeech}
+											<svg
+												class=" w-4 h-4"
+												fill="currentColor"
+												viewBox="0 0 24 24"
+												xmlns="http://www.w3.org/2000/svg"
+											>
+												<style>
+													.spinner_S1WN {
+														animation: spinner_MGfb 0.8s linear infinite;
+														animation-delay: -0.8s;
+													}
+
+													.spinner_Km9P {
+														animation-delay: -0.65s;
+													}
+
+													.spinner_JApP {
+														animation-delay: -0.5s;
+													}
+
+													@keyframes spinner_MGfb {
+														93.75%,
+														100% {
+															opacity: 0.2;
+														}
+													}
+												</style>
+												<circle class="spinner_S1WN" cx="4" cy="12" r="3" />
+												<circle class="spinner_S1WN spinner_Km9P" cx="12" cy="12" r="3" />
+												<circle class="spinner_S1WN spinner_JApP" cx="20" cy="12" r="3" />
+											</svg>
+										{:else if speaking}
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												fill="none"
+												viewBox="0 0 24 24"
+												stroke-width="2.3"
+												stroke="currentColor"
+												class="w-4 h-4"
+											>
+												<path
+													stroke-linecap="round"
+													stroke-linejoin="round"
+													d="M17.25 9.75 19.5 12m0 0 2.25 2.25M19.5 12l2.25-2.25M19.5 12l-2.25 2.25m-10.5-6 4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z"
+												/>
+											</svg>
+										{:else}
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												fill="none"
+												viewBox="0 0 24 24"
+												stroke-width="2.3"
+												stroke="currentColor"
+												class="w-4 h-4"
+											>
+												<path
+													stroke-linecap="round"
+													stroke-linejoin="round"
+													d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z"
+												/>
+											</svg>
+										{/if}
+									</button>
+								</Tooltip>
+
+								{#if $config?.features.enable_image_generation && !readOnly}
+									<Tooltip content={$i18n.t('Generate Image')} placement="bottom">
+										<button
+											class="{isLastMessage
+												? 'visible'
+												: 'invisible group-hover:visible'}  p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
+											on:click={() => {
+												if (!generatingImage) {
+													generateImage(message);
+												}
+											}}
+										>
+											{#if generatingImage}
+												<svg
+													class=" w-4 h-4"
+													fill="currentColor"
+													viewBox="0 0 24 24"
+													xmlns="http://www.w3.org/2000/svg"
+												>
+													<style>
+														.spinner_S1WN {
+															animation: spinner_MGfb 0.8s linear infinite;
+															animation-delay: -0.8s;
+														}
+
+														.spinner_Km9P {
+															animation-delay: -0.65s;
+														}
+
+														.spinner_JApP {
+															animation-delay: -0.5s;
+														}
+
+														@keyframes spinner_MGfb {
+															93.75%,
+															100% {
+																opacity: 0.2;
+															}
+														}
+													</style>
+													<circle class="spinner_S1WN" cx="4" cy="12" r="3" />
+													<circle class="spinner_S1WN spinner_Km9P" cx="12" cy="12" r="3" />
+													<circle class="spinner_S1WN spinner_JApP" cx="20" cy="12" r="3" />
+												</svg>
+											{:else}
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke-width="2.3"
+													stroke="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
+													/>
+												</svg>
+											{/if}
+										</button>
+									</Tooltip>
+								{/if}
+
+								{#if message.info}
+									<Tooltip
+										content={message.info.openai
+											? message.info.usage
+												? `<pre>${sanitizeResponseContent(
+														JSON.stringify(message.info.usage, null, 2)
+															.replace(/"([^(")"]+)":/g, '$1:')
+															.slice(1, -1)
+															.split('\n')
+															.map((line) => line.slice(2))
+															.map((line) => (line.endsWith(',') ? line.slice(0, -1) : line))
+															.join('\n')
+													)}</pre>`
+												: `prompt_tokens: ${message.info.prompt_tokens ?? 'N/A'}<br/>
+													completion_tokens: ${message.info.completion_tokens ?? 'N/A'}<br/>
+													total_tokens: ${message.info.total_tokens ?? 'N/A'}`
+											: `response_token/s: ${
+													`${
+														Math.round(
+															((message.info.eval_count ?? 0) /
+																((message.info.eval_duration ?? 0) / 1000000000)) *
+																100
+														) / 100
+													} tokens` ?? 'N/A'
+												}<br/>
+					prompt_token/s: ${
+						Math.round(
+							((message.info.prompt_eval_count ?? 0) /
+								((message.info.prompt_eval_duration ?? 0) / 1000000000)) *
+								100
+						) / 100 ?? 'N/A'
+					} tokens<br/>
+		            total_duration: ${
+									Math.round(((message.info.total_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
+								}ms<br/>
+		            load_duration: ${
+									Math.round(((message.info.load_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
+								}ms<br/>
+		            prompt_eval_count: ${message.info.prompt_eval_count ?? 'N/A'}<br/>
+		            prompt_eval_duration: ${
+									Math.round(((message.info.prompt_eval_duration ?? 0) / 1000000) * 100) / 100 ??
+									'N/A'
+								}ms<br/>
+		            eval_count: ${message.info.eval_count ?? 'N/A'}<br/>
+		            eval_duration: ${
+									Math.round(((message.info.eval_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
+								}ms<br/>
+		            approximate_total: ${approximateToHumanReadable(message.info.total_duration ?? 0)}`}
+										placement="top"
+									>
+										<Tooltip content={$i18n.t('Generation Info')} placement="bottom">
+											<button
+												class=" {isLastMessage
+													? 'visible'
+													: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition whitespace-pre-wrap"
+												on:click={() => {
+													console.log(message);
+												}}
+												id="info-{message.id}"
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke-width="2.3"
+													stroke="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+									</Tooltip>
+								{/if}
+
+								{#if !readOnly}
+									{#if $config?.features.enable_message_rating ?? true}
+										<Tooltip content={$i18n.t('Good Response')} placement="bottom">
+											<button
+												class="{isLastMessage
+													? 'visible'
+													: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg {(
+													message?.annotation?.rating ?? ''
+												).toString() === '1'
+													? 'bg-gray-100 dark:bg-gray-800'
+													: ''} dark:hover:text-white hover:text-black transition disabled:cursor-progress disabled:hover:bg-transparent"
+												disabled={feedbackLoading}
+												on:click={async () => {
+													await feedbackHandler(1);
+
+													(model?.actions ?? [])
+														.filter((action) => action?.__webui__ ?? false)
+														.forEach((action) => {
+															dispatch('action', {
+																id: action.id,
+																event: {
+																	id: 'good-response',
+																	data: {
+																		messageId: message.id
+																	}
+																}
+															});
+														});
+
+													window.setTimeout(() => {
+														document
+															.getElementById(`message-feedback-${message.id}`)
+															?.scrollIntoView();
+													}, 0);
+												}}
+											>
+												<svg
+													stroke="currentColor"
+													fill="none"
+													stroke-width="2.3"
+													viewBox="0 0 24 24"
+													stroke-linecap="round"
+													stroke-linejoin="round"
+													class="w-4 h-4"
+													xmlns="http://www.w3.org/2000/svg"
+												>
+													<path
+														d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+
+										<Tooltip content={$i18n.t('Bad Response')} placement="bottom">
+											<button
+												class="{isLastMessage
+													? 'visible'
+													: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg {(
+													message?.annotation?.rating ?? ''
+												).toString() === '-1'
+													? 'bg-gray-100 dark:bg-gray-800'
+													: ''} dark:hover:text-white hover:text-black transition disabled:cursor-progress disabled:hover:bg-transparent"
+												disabled={feedbackLoading}
+												on:click={async () => {
+													await feedbackHandler(-1);
+
+													(model?.actions ?? [])
+														.filter((action) => action?.__webui__ ?? false)
+														.forEach((action) => {
+															dispatch('action', {
+																id: action.id,
+																event: {
+																	id: 'bad-response',
+																	data: {
+																		messageId: message.id
+																	}
+																}
+															});
+														});
+
+													window.setTimeout(() => {
+														document
+															.getElementById(`message-feedback-${message.id}`)
+															?.scrollIntoView();
+													}, 0);
+												}}
+											>
+												<svg
+													stroke="currentColor"
+													fill="none"
+													stroke-width="2.3"
+													viewBox="0 0 24 24"
+													stroke-linecap="round"
+													stroke-linejoin="round"
+													class="w-4 h-4"
+													xmlns="http://www.w3.org/2000/svg"
+												>
+													<path
+														d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+									{/if}
+
+									{#if isLastMessage}
+										<Tooltip content={$i18n.t('Continue Response')} placement="bottom">
+											<button
+												type="button"
+												id="continue-response-button"
+												class="{isLastMessage
+													? 'visible'
+													: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
+												on:click={() => {
+													continueResponse();
+
+													(model?.actions ?? [])
+														.filter((action) => action?.__webui__ ?? false)
+														.forEach((action) => {
+															dispatch('action', {
+																id: action.id,
+																event: {
+																	id: 'continue-response',
+																	data: {
+																		messageId: message.id
+																	}
+																}
+															});
+														});
+												}}
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke-width="2.3"
+													stroke="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
+													/>
+													<path
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+
+										<Tooltip content={$i18n.t('Regenerate')} placement="bottom">
+											<button
+												type="button"
+												class="{isLastMessage
+													? 'visible'
+													: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
+												on:click={() => {
+													showRateComment = false;
+													regenerateResponse(message);
+
+													(model?.actions ?? [])
+														.filter((action) => action?.__webui__ ?? false)
+														.forEach((action) => {
+															dispatch('action', {
+																id: action.id,
+																event: {
+																	id: 'regenerate-response',
+																	data: {
+																		messageId: message.id
+																	}
+																}
+															});
+														});
+												}}
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke-width="2.3"
+													stroke="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
+
+										{#each (model?.actions ?? []).filter((action) => !(action?.__webui__ ?? false)) as action}
+											<Tooltip content={action.name} placement="bottom">
+												<button
+													type="button"
+													class="{isLastMessage
+														? 'visible'
+														: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
+													on:click={() => {
+														dispatch('action', action.id);
+													}}
+												>
+													{#if action.icon_url}
+														<img
+															src={action.icon_url}
+															class="w-4 h-4 {action.icon_url.includes('svg')
+																? 'dark:invert-[80%]'
+																: ''}"
+															style="fill: currentColor;"
+															alt={action.name}
+														/>
+													{:else}
+														<Sparkles strokeWidth="2.1" className="size-4" />
+													{/if}
+												</button>
+											</Tooltip>
+										{/each}
+									{/if}
+								{/if}
+							{/if}
+						</div>
+					{/if}
+
+					{#if message.done && showRateComment}
+						<RateComment
+							bind:message
+							bind:show={showRateComment}
+							on:save={async (e) => {
+								await feedbackHandler(null, {
+									tags: e.detail.tags,
+									comment: e.detail.comment,
+									reason: e.detail.reason
+								});
+
+								(model?.actions ?? [])
+									.filter((action) => action?.__webui__ ?? false)
+									.forEach((action) => {
+										dispatch('action', {
+											id: action.id,
+											event: {
+												id: 'rate-comment',
+												data: {
+													messageId: message.id,
+													comment: e.detail.comment,
+													reason: e.detail.reason
+												}
+											}
+										});
+									});
+							}}
+						/>
+					{/if}
+				{/if}
+			</div>
+		</div>
+	</div>
+{/key}
+
+<style>
+	.buttons::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.buttons {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+
+	@keyframes shimmer {
+		0% {
+			background-position: 200% 0;
+		}
+		100% {
+			background-position: -200% 0;
+		}
+	}
+
+	.shimmer {
+		background: linear-gradient(90deg, #9a9b9e 25%, #2a2929 50%, #9a9b9e 75%);
+		background-size: 200% 100%;
+		background-clip: text;
+		-webkit-background-clip: text;
+		-webkit-text-fill-color: transparent;
+		animation: shimmer 4s linear infinite;
+		color: #818286; /* Fallback color */
+	}
+
+	:global(.dark) .shimmer {
+		background: linear-gradient(90deg, #818286 25%, #eae5e5 50%, #818286 75%);
+		background-size: 200% 100%;
+		background-clip: text;
+		-webkit-background-clip: text;
+		-webkit-text-fill-color: transparent;
+		animation: shimmer 4s linear infinite;
+		color: #a1a3a7; /* Darker fallback color for dark mode */
+	}
+
+	@keyframes smoothFadeIn {
+		0% {
+			opacity: 0;
+			transform: translateY(-10px);
+		}
+		100% {
+			opacity: 1;
+			transform: translateY(0);
+		}
+	}
+
+	.status-description {
+		animation: smoothFadeIn 0.2s forwards;
+	}
+</style>
diff --git a/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte b/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..bb1a5a17d23a181c450b9ca0185d1a86907d246f
--- /dev/null
+++ b/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte
@@ -0,0 +1,93 @@
+<script lang="ts">
+	import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
+	import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
+	import MagnifyingGlass from '$lib/components/icons/MagnifyingGlass.svelte';
+	import Collapsible from '$lib/components/common/Collapsible.svelte';
+
+	export let status = { urls: [], query: '' };
+	let state = false;
+</script>
+
+<Collapsible bind:open={state} className="w-full space-y-1">
+	<div
+		class="flex items-center gap-2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition"
+	>
+		<slot />
+
+		{#if state}
+			<ChevronUp strokeWidth="3.5" className="size-3.5 " />
+		{:else}
+			<ChevronDown strokeWidth="3.5" className="size-3.5 " />
+		{/if}
+	</div>
+	<div
+		class="text-sm border border-gray-300/30 dark:border-gray-700/50 rounded-xl mb-1.5"
+		slot="content"
+	>
+		{#if status?.query}
+			<a
+				href="https://www.google.com/search?q={status.query}"
+				target="_blank"
+				class="flex w-full items-center p-3 px-4 border-b border-gray-300/30 dark:border-gray-700/50 group/item justify-between font-normal text-gray-800 dark:text-gray-300 no-underline"
+			>
+				<div class="flex gap-2 items-center">
+					<MagnifyingGlass />
+
+					<div class=" line-clamp-1">
+						{status.query}
+					</div>
+				</div>
+
+				<div
+					class=" ml-1 text-white dark:text-gray-900 group-hover/item:text-gray-600 dark:group-hover/item:text-white transition"
+				>
+					<!--  -->
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 16 16"
+						fill="currentColor"
+						class="size-4"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M4.22 11.78a.75.75 0 0 1 0-1.06L9.44 5.5H5.75a.75.75 0 0 1 0-1.5h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0V6.56l-5.22 5.22a.75.75 0 0 1-1.06 0Z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+			</a>
+		{/if}
+
+		{#each status.urls as url, urlIdx}
+			<a
+				href={url}
+				target="_blank"
+				class="flex w-full items-center p-3 px-4 {urlIdx === status.urls.length - 1
+					? ''
+					: 'border-b border-gray-300/30 dark:border-gray-700/50'} group/item justify-between font-normal text-gray-800 dark:text-gray-300"
+			>
+				<div class=" line-clamp-1">
+					{url}
+				</div>
+
+				<div
+					class=" ml-1 text-white dark:text-gray-900 group-hover/item:text-gray-600 dark:group-hover/item:text-white transition"
+				>
+					<!--  -->
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 16 16"
+						fill="currentColor"
+						class="size-4"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M4.22 11.78a.75.75 0 0 1 0-1.06L9.44 5.5H5.75a.75.75 0 0 1 0-1.5h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0V6.56l-5.22 5.22a.75.75 0 0 1-1.06 0Z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+			</a>
+		{/each}
+	</div>
+</Collapsible>
diff --git a/src/lib/components/chat/Messages/Skeleton.svelte b/src/lib/components/chat/Messages/Skeleton.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..364d0ecf7fcb62571629d4d1ce61d23722fdc3cf
--- /dev/null
+++ b/src/lib/components/chat/Messages/Skeleton.svelte
@@ -0,0 +1,19 @@
+<div class="w-full mt-2 mb-2">
+	<div class="animate-pulse flex w-full">
+		<div class="space-y-2 w-full">
+			<div class="h-2 bg-gray-200 dark:bg-gray-600 rounded mr-14" />
+
+			<div class="grid grid-cols-3 gap-4">
+				<div class="h-2 bg-gray-200 dark:bg-gray-600 rounded col-span-2" />
+				<div class="h-2 bg-gray-200 dark:bg-gray-600 rounded col-span-1" />
+			</div>
+			<div class="grid grid-cols-4 gap-4">
+				<div class="h-2 bg-gray-200 dark:bg-gray-600 rounded col-span-1" />
+				<div class="h-2 bg-gray-200 dark:bg-gray-600 rounded col-span-2" />
+				<div class="h-2 bg-gray-200 dark:bg-gray-600 rounded col-span-1 mr-4" />
+			</div>
+
+			<div class="h-2 bg-gray-200 dark:bg-gray-600 rounded" />
+		</div>
+	</div>
+</div>
diff --git a/src/lib/components/chat/Messages/UserMessage.svelte b/src/lib/components/chat/Messages/UserMessage.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..44d1cf3daf6628db6369d4c0a903789c4c9ea008
--- /dev/null
+++ b/src/lib/components/chat/Messages/UserMessage.svelte
@@ -0,0 +1,414 @@
+<script lang="ts">
+	import dayjs from 'dayjs';
+	import { toast } from 'svelte-sonner';
+	import { tick, createEventDispatcher, getContext, onMount } from 'svelte';
+
+	import { models, settings } from '$lib/stores';
+	import { user as _user } from '$lib/stores';
+	import {
+		copyToClipboard as _copyToClipboard,
+		processResponseContent,
+		replaceTokens
+	} from '$lib/utils';
+
+	import Name from './Name.svelte';
+	import ProfileImage from './ProfileImage.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import FileItem from '$lib/components/common/FileItem.svelte';
+	import Markdown from './Markdown.svelte';
+
+	const i18n = getContext('i18n');
+
+	const dispatch = createEventDispatcher();
+	export let user;
+
+	export let history;
+	export let messageId;
+
+	export let siblings;
+
+	export let showPreviousMessage: Function;
+	export let showNextMessage: Function;
+
+	export let editMessage: Function;
+
+	export let isFirstMessage: boolean;
+	export let readOnly: boolean;
+
+	let edit = false;
+	let editedContent = '';
+	let messageEditTextAreaElement: HTMLTextAreaElement;
+
+	let message = JSON.parse(JSON.stringify(history.messages[messageId]));
+	$: if (history.messages) {
+		if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
+			message = JSON.parse(JSON.stringify(history.messages[messageId]));
+		}
+	}
+
+	const copyToClipboard = async (text) => {
+		const res = await _copyToClipboard(text);
+		if (res) {
+			toast.success($i18n.t('Copying to clipboard was successful!'));
+		}
+	};
+
+	const editMessageHandler = async () => {
+		edit = true;
+		editedContent = message.content;
+
+		await tick();
+
+		messageEditTextAreaElement.style.height = '';
+		messageEditTextAreaElement.style.height = `${messageEditTextAreaElement.scrollHeight}px`;
+
+		messageEditTextAreaElement?.focus();
+	};
+
+	const editMessageConfirmHandler = async (submit = true) => {
+		editMessage(message.id, editedContent, submit);
+
+		edit = false;
+		editedContent = '';
+	};
+
+	const cancelEditMessage = () => {
+		edit = false;
+		editedContent = '';
+	};
+
+	const deleteMessageHandler = async () => {
+		dispatch('delete', message.id);
+	};
+
+	onMount(() => {
+		console.log('UserMessage mounted');
+	});
+</script>
+
+<div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">
+	{#if !($settings?.chatBubble ?? true)}
+		<ProfileImage
+			src={message.user
+				? ($models.find((m) => m.id === message.user)?.info?.meta?.profile_image_url ?? '/user.png')
+				: (user?.profile_image_url ?? '/user.png')}
+		/>
+	{/if}
+	<div class="flex-auto w-0 max-w-full pl-1">
+		{#if !($settings?.chatBubble ?? true)}
+			<div>
+				<Name>
+					{#if message.user}
+						{$i18n.t('You')}
+						<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
+					{:else if $settings.showUsername || $_user.name !== user.name}
+						{user.name}
+					{:else}
+						{$i18n.t('You')}
+					{/if}
+
+					{#if message.timestamp}
+						<span
+							class=" invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
+						>
+							{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
+						</span>
+					{/if}
+				</Name>
+			</div>
+		{/if}
+
+		<div class="chat-{message.role} w-full min-w-full markdown-prose">
+			{#if message.files}
+				<div class="mt-2.5 mb-1 w-full flex flex-col justify-end overflow-x-auto gap-1 flex-wrap">
+					{#each message.files as file}
+						<div class={($settings?.chatBubble ?? true) ? 'self-end' : ''}>
+							{#if file.type === 'image'}
+								<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
+							{:else}
+								<FileItem
+									item={file}
+									url={file.url}
+									name={file.name}
+									type={file.type}
+									size={file?.size}
+									colorClassName="bg-white dark:bg-gray-850 "
+								/>
+							{/if}
+						</div>
+					{/each}
+				</div>
+			{/if}
+
+			{#if edit === true}
+				<div class=" w-full bg-gray-50 dark:bg-gray-800 rounded-3xl px-5 py-3 mb-2">
+					<div class="max-h-96 overflow-auto">
+						<textarea
+							id="message-edit-{message.id}"
+							bind:this={messageEditTextAreaElement}
+							class=" bg-transparent outline-none w-full resize-none"
+							bind:value={editedContent}
+							on:input={(e) => {
+								e.target.style.height = '';
+								e.target.style.height = `${e.target.scrollHeight}px`;
+							}}
+							on:keydown={(e) => {
+								if (e.key === 'Escape') {
+									document.getElementById('close-edit-message-button')?.click();
+								}
+
+								const isCmdOrCtrlPressed = e.metaKey || e.ctrlKey;
+								const isEnterPressed = e.key === 'Enter';
+
+								if (isCmdOrCtrlPressed && isEnterPressed) {
+									document.getElementById('confirm-edit-message-button')?.click();
+								}
+							}}
+						/>
+					</div>
+
+					<div class=" mt-2 mb-1 flex justify-between text-sm font-medium">
+						<div>
+							<button
+								id="save-edit-message-button"
+								class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
+								on:click={() => {
+									editMessageConfirmHandler(false);
+								}}
+							>
+								{$i18n.t('Save')}
+							</button>
+						</div>
+
+						<div class="flex space-x-1.5">
+							<button
+								id="close-edit-message-button"
+								class="px-4 py-2 bg-white dark:bg-gray-900 hover:bg-gray-100 text-gray-800 dark:text-gray-100 transition rounded-3xl"
+								on:click={() => {
+									cancelEditMessage();
+								}}
+							>
+								{$i18n.t('Cancel')}
+							</button>
+
+							<button
+								id="confirm-edit-message-button"
+								class=" px-4 py-2 bg-gray-900 dark:bg-white hover:bg-gray-850 text-gray-100 dark:text-gray-800 transition rounded-3xl"
+								on:click={() => {
+									editMessageConfirmHandler();
+								}}
+							>
+								{$i18n.t('Send')}
+							</button>
+						</div>
+					</div>
+				</div>
+			{:else}
+				<div class="w-full">
+					<div class="flex {($settings?.chatBubble ?? true) ? 'justify-end pb-1' : 'w-full'}">
+						<div
+							class="rounded-3xl {($settings?.chatBubble ?? true)
+								? `max-w-[90%] px-5 py-2  bg-gray-50 dark:bg-gray-850 ${
+										message.files ? 'rounded-tr-lg' : ''
+									}`
+								: ' w-full'}"
+						>
+							{#if message.content}
+								<Markdown id={message.id} content={message.content} />
+							{/if}
+						</div>
+					</div>
+
+					<div
+						class=" flex {($settings?.chatBubble ?? true)
+							? 'justify-end'
+							: ''}  text-gray-600 dark:text-gray-500"
+					>
+						{#if !($settings?.chatBubble ?? true)}
+							{#if siblings.length > 1}
+								<div class="flex self-center" dir="ltr">
+									<button
+										class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
+										on:click={() => {
+											showPreviousMessage(message);
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											fill="none"
+											viewBox="0 0 24 24"
+											stroke="currentColor"
+											stroke-width="2.5"
+											class="size-3.5"
+										>
+											<path
+												stroke-linecap="round"
+												stroke-linejoin="round"
+												d="M15.75 19.5 8.25 12l7.5-7.5"
+											/>
+										</svg>
+									</button>
+
+									<div class="text-sm tracking-widest font-semibold self-center dark:text-gray-100">
+										{siblings.indexOf(message.id) + 1}/{siblings.length}
+									</div>
+
+									<button
+										class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
+										on:click={() => {
+											showNextMessage(message);
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											fill="none"
+											viewBox="0 0 24 24"
+											stroke="currentColor"
+											stroke-width="2.5"
+											class="size-3.5"
+										>
+											<path
+												stroke-linecap="round"
+												stroke-linejoin="round"
+												d="m8.25 4.5 7.5 7.5-7.5 7.5"
+											/>
+										</svg>
+									</button>
+								</div>
+							{/if}
+						{/if}
+						{#if !readOnly}
+							<Tooltip content={$i18n.t('Edit')} placement="bottom">
+								<button
+									class="invisible group-hover:visible p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition edit-user-message-button"
+									on:click={() => {
+										editMessageHandler();
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										fill="none"
+										viewBox="0 0 24 24"
+										stroke-width="2.3"
+										stroke="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											stroke-linecap="round"
+											stroke-linejoin="round"
+											d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
+										/>
+									</svg>
+								</button>
+							</Tooltip>
+						{/if}
+
+						<Tooltip content={$i18n.t('Copy')} placement="bottom">
+							<button
+								class="invisible group-hover:visible p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
+								on:click={() => {
+									copyToClipboard(message.content);
+								}}
+							>
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									fill="none"
+									viewBox="0 0 24 24"
+									stroke-width="2.3"
+									stroke="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										stroke-linecap="round"
+										stroke-linejoin="round"
+										d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
+									/>
+								</svg>
+							</button>
+						</Tooltip>
+
+						{#if !isFirstMessage && !readOnly}
+							<Tooltip content={$i18n.t('Delete')} placement="bottom">
+								<button
+									class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
+									on:click={() => {
+										deleteMessageHandler();
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										fill="none"
+										viewBox="0 0 24 24"
+										stroke-width="2"
+										stroke="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											stroke-linecap="round"
+											stroke-linejoin="round"
+											d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
+										/>
+									</svg>
+								</button>
+							</Tooltip>
+						{/if}
+
+						{#if $settings?.chatBubble ?? true}
+							{#if siblings.length > 1}
+								<div class="flex self-center" dir="ltr">
+									<button
+										class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
+										on:click={() => {
+											showPreviousMessage(message);
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											fill="none"
+											viewBox="0 0 24 24"
+											stroke="currentColor"
+											stroke-width="2.5"
+											class="size-3.5"
+										>
+											<path
+												stroke-linecap="round"
+												stroke-linejoin="round"
+												d="M15.75 19.5 8.25 12l7.5-7.5"
+											/>
+										</svg>
+									</button>
+
+									<div class="text-sm tracking-widest font-semibold self-center dark:text-gray-100">
+										{siblings.indexOf(message.id) + 1}/{siblings.length}
+									</div>
+
+									<button
+										class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
+										on:click={() => {
+											showNextMessage(message);
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											fill="none"
+											viewBox="0 0 24 24"
+											stroke="currentColor"
+											stroke-width="2.5"
+											class="size-3.5"
+										>
+											<path
+												stroke-linecap="round"
+												stroke-linejoin="round"
+												d="m8.25 4.5 7.5 7.5-7.5 7.5"
+											/>
+										</svg>
+									</button>
+								</div>
+							{/if}
+						{/if}
+					</div>
+				</div>
+			{/if}
+		</div>
+	</div>
+</div>
diff --git a/src/lib/components/chat/ModelSelector.svelte b/src/lib/components/chat/ModelSelector.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..631ac68628cb2c9f6ae5d2504a03b6a631a4b63d
--- /dev/null
+++ b/src/lib/components/chat/ModelSelector.svelte
@@ -0,0 +1,119 @@
+<script lang="ts">
+	import { models, showSettings, settings, user, mobile, config } from '$lib/stores';
+	import { onMount, tick, getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+	import Selector from './ModelSelector/Selector.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+
+	import { setDefaultModels } from '$lib/apis/configs';
+	import { updateUserSettings } from '$lib/apis/users';
+
+	const i18n = getContext('i18n');
+
+	export let selectedModels = [''];
+	export let disabled = false;
+
+	export let showSetDefault = true;
+
+	const saveDefaultModel = async () => {
+		const hasEmptyModel = selectedModels.filter((it) => it === '');
+		if (hasEmptyModel.length) {
+			toast.error($i18n.t('Choose a model before saving...'));
+			return;
+		}
+		settings.set({ ...$settings, models: selectedModels });
+		await updateUserSettings(localStorage.token, { ui: $settings });
+
+		toast.success($i18n.t('Default model updated'));
+	};
+
+	$: if (selectedModels.length > 0 && $models.length > 0) {
+		selectedModels = selectedModels.map((model) =>
+			$models.map((m) => m.id).includes(model) ? model : ''
+		);
+	}
+</script>
+
+<div class="flex flex-col w-full items-start">
+	{#each selectedModels as selectedModel, selectedModelIdx}
+		<div class="flex w-full max-w-fit">
+			<div class="overflow-hidden w-full">
+				<div class="mr-1 max-w-full">
+					<Selector
+						id={`${selectedModelIdx}`}
+						placeholder={$i18n.t('Select a model')}
+						items={$models.map((model) => ({
+							value: model.id,
+							label: model.name,
+							model: model
+						}))}
+						showTemporaryChatControl={$user.role === 'user'
+							? ($config?.permissions?.chat?.temporary ?? true)
+							: true}
+						bind:value={selectedModel}
+					/>
+				</div>
+			</div>
+
+			{#if selectedModelIdx === 0}
+				<div
+					class="  self-center mx-1 disabled:text-gray-600 disabled:hover:text-gray-600 -translate-y-[0.5px]"
+				>
+					<Tooltip content={$i18n.t('Add Model')}>
+						<button
+							class=" "
+							{disabled}
+							on:click={() => {
+								selectedModels = [...selectedModels, ''];
+							}}
+							aria-label="Add Model"
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								fill="none"
+								viewBox="0 0 24 24"
+								stroke-width="2"
+								stroke="currentColor"
+								class="size-3.5"
+							>
+								<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6" />
+							</svg>
+						</button>
+					</Tooltip>
+				</div>
+			{:else}
+				<div
+					class="  self-center mx-1 disabled:text-gray-600 disabled:hover:text-gray-600 -translate-y-[0.5px]"
+				>
+					<Tooltip content={$i18n.t('Remove Model')}>
+						<button
+							{disabled}
+							on:click={() => {
+								selectedModels.splice(selectedModelIdx, 1);
+								selectedModels = selectedModels;
+							}}
+							aria-label="Remove Model"
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								fill="none"
+								viewBox="0 0 24 24"
+								stroke-width="2"
+								stroke="currentColor"
+								class="size-3"
+							>
+								<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
+							</svg>
+						</button>
+					</Tooltip>
+				</div>
+			{/if}
+		</div>
+	{/each}
+</div>
+
+{#if showSetDefault}
+	<div class=" absolute text-left mt-[1px] ml-1 text-[0.7rem] text-gray-500 font-primary">
+		<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
+	</div>
+{/if}
diff --git a/src/lib/components/chat/ModelSelector/Selector.svelte b/src/lib/components/chat/ModelSelector/Selector.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..38332fb0e173f14475d3df620911af66869c97f1
--- /dev/null
+++ b/src/lib/components/chat/ModelSelector/Selector.svelte
@@ -0,0 +1,587 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { marked } from 'marked';
+	import Fuse from 'fuse.js';
+
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
+
+	import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
+	import Check from '$lib/components/icons/Check.svelte';
+	import Search from '$lib/components/icons/Search.svelte';
+
+	import { deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
+
+	import { user, MODEL_DOWNLOAD_POOL, models, mobile, temporaryChatEnabled } from '$lib/stores';
+	import { toast } from 'svelte-sonner';
+	import { capitalizeFirstLetter, sanitizeResponseContent, splitStream } from '$lib/utils';
+	import { getModels } from '$lib/apis';
+
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import ChatBubbleOval from '$lib/components/icons/ChatBubbleOval.svelte';
+	import { goto } from '$app/navigation';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	export let id = '';
+	export let value = '';
+	export let placeholder = 'Select a model';
+	export let searchEnabled = true;
+	export let searchPlaceholder = $i18n.t('Search a model');
+
+	export let showTemporaryChatControl = false;
+
+	export let items: {
+		label: string;
+		value: string;
+		model: Model;
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any
+		[key: string]: any;
+	}[] = [];
+
+	export let className = 'w-[32rem]';
+	export let triggerClassName = 'text-lg';
+
+	let show = false;
+
+	let selectedModel = '';
+	$: selectedModel = items.find((item) => item.value === value) ?? '';
+
+	let searchValue = '';
+	let ollamaVersion = null;
+
+	let selectedModelIdx = 0;
+
+	const fuse = new Fuse(
+		items
+			.filter((item) => !item.model?.info?.meta?.hidden)
+			.map((item) => {
+				const _item = {
+					...item,
+					modelName: item.model?.name,
+					tags: item.model?.info?.meta?.tags?.map((tag) => tag.name).join(' '),
+					desc: item.model?.info?.meta?.description
+				};
+				return _item;
+			}),
+		{
+			keys: ['value', 'tags', 'modelName'],
+			threshold: 0.3
+		}
+	);
+
+	$: filteredItems = searchValue
+		? fuse.search(searchValue).map((e) => {
+				return e.item;
+			})
+		: items.filter((item) => !item.model?.info?.meta?.hidden);
+
+	const pullModelHandler = async () => {
+		const sanitizedModelTag = searchValue.trim().replace(/^ollama\s+(run|pull)\s+/, '');
+
+		console.log($MODEL_DOWNLOAD_POOL);
+		if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) {
+			toast.error(
+				$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, {
+					modelTag: sanitizedModelTag
+				})
+			);
+			return;
+		}
+		if (Object.keys($MODEL_DOWNLOAD_POOL).length === 3) {
+			toast.error(
+				$i18n.t('Maximum of 3 models can be downloaded simultaneously. Please try again later.')
+			);
+			return;
+		}
+
+		const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, '0').catch(
+			(error) => {
+				toast.error(error);
+				return null;
+			}
+		);
+
+		if (res) {
+			const reader = res.body
+				.pipeThrough(new TextDecoderStream())
+				.pipeThrough(splitStream('\n'))
+				.getReader();
+
+			MODEL_DOWNLOAD_POOL.set({
+				...$MODEL_DOWNLOAD_POOL,
+				[sanitizedModelTag]: {
+					...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
+					abortController: controller,
+					reader,
+					done: false
+				}
+			});
+
+			while (true) {
+				try {
+					const { value, done } = await reader.read();
+					if (done) break;
+
+					let lines = value.split('\n');
+
+					for (const line of lines) {
+						if (line !== '') {
+							let data = JSON.parse(line);
+							console.log(data);
+							if (data.error) {
+								throw data.error;
+							}
+							if (data.detail) {
+								throw data.detail;
+							}
+
+							if (data.status) {
+								if (data.digest) {
+									let downloadProgress = 0;
+									if (data.completed) {
+										downloadProgress = Math.round((data.completed / data.total) * 1000) / 10;
+									} else {
+										downloadProgress = 100;
+									}
+
+									MODEL_DOWNLOAD_POOL.set({
+										...$MODEL_DOWNLOAD_POOL,
+										[sanitizedModelTag]: {
+											...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
+											pullProgress: downloadProgress,
+											digest: data.digest
+										}
+									});
+								} else {
+									toast.success(data.status);
+
+									MODEL_DOWNLOAD_POOL.set({
+										...$MODEL_DOWNLOAD_POOL,
+										[sanitizedModelTag]: {
+											...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
+											done: data.status === 'success'
+										}
+									});
+								}
+							}
+						}
+					}
+				} catch (error) {
+					console.log(error);
+					if (typeof error !== 'string') {
+						error = error.message;
+					}
+
+					toast.error(error);
+					// opts.callback({ success: false, error, modelName: opts.modelName });
+					break;
+				}
+			}
+
+			if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag].done) {
+				toast.success(
+					$i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, {
+						modelName: sanitizedModelTag
+					})
+				);
+
+				models.set(await getModels(localStorage.token));
+			} else {
+				toast.error($i18n.t('Download canceled'));
+			}
+
+			delete $MODEL_DOWNLOAD_POOL[sanitizedModelTag];
+
+			MODEL_DOWNLOAD_POOL.set({
+				...$MODEL_DOWNLOAD_POOL
+			});
+		}
+	};
+
+	onMount(async () => {
+		ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
+	});
+
+	const cancelModelPullHandler = async (model: string) => {
+		const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model];
+		if (abortController) {
+			abortController.abort();
+		}
+		if (reader) {
+			await reader.cancel();
+			delete $MODEL_DOWNLOAD_POOL[model];
+			MODEL_DOWNLOAD_POOL.set({
+				...$MODEL_DOWNLOAD_POOL
+			});
+			await deleteModel(localStorage.token, model);
+			toast.success(`${model} download has been canceled`);
+		}
+	};
+</script>
+
+<DropdownMenu.Root
+	bind:open={show}
+	onOpenChange={async () => {
+		searchValue = '';
+		selectedModelIdx = 0;
+		window.setTimeout(() => document.getElementById('model-search-input')?.focus(), 0);
+	}}
+	closeFocus={false}
+>
+	<DropdownMenu.Trigger
+		class="relative w-full font-primary"
+		aria-label={placeholder}
+		id="model-selector-{id}-button"
+	>
+		<div
+			class="flex w-full text-left px-0.5 outline-none bg-transparent truncate {triggerClassName} justify-between font-medium placeholder-gray-400 focus:outline-none"
+		>
+			{#if selectedModel}
+				{selectedModel.label}
+			{:else}
+				{placeholder}
+			{/if}
+			<ChevronDown className=" self-center ml-2 size-3" strokeWidth="2.5" />
+		</div>
+	</DropdownMenu.Trigger>
+
+	<DropdownMenu.Content
+		class=" z-40 {$mobile
+			? `w-full`
+			: `${className}`} max-w-[calc(100vw-1rem)] justify-start rounded-xl  bg-white dark:bg-gray-850 dark:text-white shadow-lg  outline-none"
+		transition={flyAndScale}
+		side={$mobile ? 'bottom' : 'bottom-start'}
+		sideOffset={3}
+	>
+		<slot>
+			{#if searchEnabled}
+				<div class="flex items-center gap-2.5 px-5 mt-3.5 mb-3">
+					<Search className="size-4" strokeWidth="2.5" />
+
+					<input
+						id="model-search-input"
+						bind:value={searchValue}
+						class="w-full text-sm bg-transparent outline-none"
+						placeholder={searchPlaceholder}
+						autocomplete="off"
+						on:keydown={(e) => {
+							if (e.code === 'Enter' && filteredItems.length > 0) {
+								value = filteredItems[selectedModelIdx].value;
+								show = false;
+								return; // dont need to scroll on selection
+							} else if (e.code === 'ArrowDown') {
+								selectedModelIdx = Math.min(selectedModelIdx + 1, filteredItems.length - 1);
+							} else if (e.code === 'ArrowUp') {
+								selectedModelIdx = Math.max(selectedModelIdx - 1, 0);
+							} else {
+								// if the user types something, reset to the top selection.
+								selectedModelIdx = 0;
+							}
+
+							const item = document.querySelector(`[data-arrow-selected="true"]`);
+							item?.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' });
+						}}
+					/>
+				</div>
+
+				<hr class="border-gray-50 dark:border-gray-800" />
+			{/if}
+
+			<div class="px-3 my-2 max-h-64 overflow-y-auto scrollbar-hidden group">
+				{#each filteredItems as item, index}
+					<button
+						aria-label="model-item"
+						class="flex w-full text-left font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-[highlighted]:bg-muted {index ===
+						selectedModelIdx
+							? 'bg-gray-100 dark:bg-gray-800 group-hover:bg-transparent'
+							: ''}"
+						data-arrow-selected={index === selectedModelIdx}
+						on:click={() => {
+							value = item.value;
+							selectedModelIdx = index;
+
+							show = false;
+						}}
+					>
+						<div class="flex flex-col">
+							{#if $mobile && (item?.model?.info?.meta?.tags ?? []).length > 0}
+								<div class="flex gap-0.5 self-start h-full mb-1.5 -translate-x-1">
+									{#each item.model?.info?.meta.tags as tag}
+										<div
+											class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+										>
+											{tag.name}
+										</div>
+									{/each}
+								</div>
+							{/if}
+							<div class="flex items-center gap-2">
+								<div class="flex items-center min-w-fit">
+									<div class="line-clamp-1">
+										<div class="flex items-center min-w-fit">
+											<img
+												src={item.model?.info?.meta?.profile_image_url ?? '/static/favicon.png'}
+												alt="Model"
+												class="rounded-full size-5 flex items-center mr-2"
+											/>
+											{item.label}
+										</div>
+									</div>
+									{#if item.model.owned_by === 'ollama' && (item.model.ollama?.details?.parameter_size ?? '') !== ''}
+										<div class="flex ml-1 items-center translate-y-[0.5px]">
+											<Tooltip
+												content={`${
+													item.model.ollama?.details?.quantization_level
+														? item.model.ollama?.details?.quantization_level + ' '
+														: ''
+												}${
+													item.model.ollama?.size
+														? `(${(item.model.ollama?.size / 1024 ** 3).toFixed(1)}GB)`
+														: ''
+												}`}
+												className="self-end"
+											>
+												<span
+													class=" text-xs font-medium text-gray-600 dark:text-gray-400 line-clamp-1"
+													>{item.model.ollama?.details?.parameter_size ?? ''}</span
+												>
+											</Tooltip>
+										</div>
+									{/if}
+								</div>
+
+								<!-- {JSON.stringify(item.info)} -->
+
+								{#if item.model.owned_by === 'openai'}
+									<Tooltip content={`${'External'}`}>
+										<div class="translate-y-[1px]">
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												viewBox="0 0 16 16"
+												fill="currentColor"
+												class="size-3"
+											>
+												<path
+													fill-rule="evenodd"
+													d="M8.914 6.025a.75.75 0 0 1 1.06 0 3.5 3.5 0 0 1 0 4.95l-2 2a3.5 3.5 0 0 1-5.396-4.402.75.75 0 0 1 1.251.827 2 2 0 0 0 3.085 2.514l2-2a2 2 0 0 0 0-2.828.75.75 0 0 1 0-1.06Z"
+													clip-rule="evenodd"
+												/>
+												<path
+													fill-rule="evenodd"
+													d="M7.086 9.975a.75.75 0 0 1-1.06 0 3.5 3.5 0 0 1 0-4.95l2-2a3.5 3.5 0 0 1 5.396 4.402.75.75 0 0 1-1.251-.827 2 2 0 0 0-3.085-2.514l-2 2a2 2 0 0 0 0 2.828.75.75 0 0 1 0 1.06Z"
+													clip-rule="evenodd"
+												/>
+											</svg>
+										</div>
+									</Tooltip>
+								{/if}
+
+								{#if item.model?.info?.meta?.description}
+									<Tooltip
+										content={`${marked.parse(
+											sanitizeResponseContent(item.model?.info?.meta?.description).replaceAll(
+												'\n',
+												'<br>'
+											)
+										)}`}
+									>
+										<div class=" translate-y-[1px]">
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												fill="none"
+												viewBox="0 0 24 24"
+												stroke-width="1.5"
+												stroke="currentColor"
+												class="w-4 h-4"
+											>
+												<path
+													stroke-linecap="round"
+													stroke-linejoin="round"
+													d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
+												/>
+											</svg>
+										</div>
+									</Tooltip>
+								{/if}
+
+								{#if !$mobile && (item?.model?.info?.meta?.tags ?? []).length > 0}
+									<div class="flex gap-0.5 self-center items-center h-full translate-y-[0.5px]">
+										{#each item.model?.info?.meta.tags as tag}
+											<Tooltip content={tag.name}>
+												<div
+													class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+												>
+													{tag.name}
+												</div>
+											</Tooltip>
+										{/each}
+									</div>
+								{/if}
+							</div>
+						</div>
+
+						{#if value === item.value}
+							<div class="ml-auto pl-2 pr-2 md:pr-0">
+								<Check />
+							</div>
+						{/if}
+					</button>
+				{:else}
+					<div>
+						<div class="block px-3 py-2 text-sm text-gray-700 dark:text-gray-100">
+							{$i18n.t('No results found')}
+						</div>
+					</div>
+				{/each}
+
+				{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user.role === 'admin'}
+					<button
+						class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+						on:click={() => {
+							pullModelHandler();
+						}}
+					>
+						{$i18n.t(`Pull "{{searchValue}}" from Ollama.com`, { searchValue: searchValue })}
+					</button>
+				{/if}
+
+				{#each Object.keys($MODEL_DOWNLOAD_POOL) as model}
+					<div
+						class="flex w-full justify-between font-medium select-none rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+					>
+						<div class="flex">
+							<div class="-ml-2 mr-2.5 translate-y-0.5">
+								<svg
+									class="size-4"
+									viewBox="0 0 24 24"
+									fill="currentColor"
+									xmlns="http://www.w3.org/2000/svg"
+									><style>
+										.spinner_ajPY {
+											transform-origin: center;
+											animation: spinner_AtaB 0.75s infinite linear;
+										}
+										@keyframes spinner_AtaB {
+											100% {
+												transform: rotate(360deg);
+											}
+										}
+									</style><path
+										d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+										opacity=".25"
+									/><path
+										d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+										class="spinner_ajPY"
+									/></svg
+								>
+							</div>
+
+							<div class="flex flex-col self-start">
+								<div class="flex gap-1">
+									<div class="line-clamp-1">
+										Downloading "{model}"
+									</div>
+
+									<div class="flex-shrink-0">
+										{'pullProgress' in $MODEL_DOWNLOAD_POOL[model]
+											? `(${$MODEL_DOWNLOAD_POOL[model].pullProgress}%)`
+											: ''}
+									</div>
+								</div>
+
+								{#if 'digest' in $MODEL_DOWNLOAD_POOL[model] && $MODEL_DOWNLOAD_POOL[model].digest}
+									<div class="-mt-1 h-fit text-[0.7rem] dark:text-gray-500 line-clamp-1">
+										{$MODEL_DOWNLOAD_POOL[model].digest}
+									</div>
+								{/if}
+							</div>
+						</div>
+
+						<div class="mr-2 ml-1 translate-y-0.5">
+							<Tooltip content={$i18n.t('Cancel')}>
+								<button
+									class="text-gray-800 dark:text-gray-100"
+									on:click={() => {
+										cancelModelPullHandler(model);
+									}}
+								>
+									<svg
+										class="w-4 h-4 text-gray-800 dark:text-white"
+										aria-hidden="true"
+										xmlns="http://www.w3.org/2000/svg"
+										width="24"
+										height="24"
+										fill="currentColor"
+										viewBox="0 0 24 24"
+									>
+										<path
+											stroke="currentColor"
+											stroke-linecap="round"
+											stroke-linejoin="round"
+											stroke-width="2"
+											d="M6 18 17.94 6M18 18 6.06 6"
+										/>
+									</svg>
+								</button>
+							</Tooltip>
+						</div>
+					</div>
+				{/each}
+			</div>
+
+			{#if showTemporaryChatControl}
+				<hr class="border-gray-50 dark:border-gray-800" />
+
+				<div class="flex items-center mx-2 my-2">
+					<button
+						class="flex justify-between w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 px-3 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+						on:click={async () => {
+							temporaryChatEnabled.set(!$temporaryChatEnabled);
+							await goto('/');
+							const newChatButton = document.getElementById('new-chat-button');
+							setTimeout(() => {
+								newChatButton?.click();
+							}, 0);
+
+							// add 'temporary-chat=true' to the URL
+							if ($temporaryChatEnabled) {
+								history.replaceState(null, '', '?temporary-chat=true');
+							} else {
+								history.replaceState(null, '', location.pathname);
+							}
+
+							show = false;
+						}}
+					>
+						<div class="flex gap-2.5 items-center">
+							<ChatBubbleOval className="size-4" strokeWidth="2.5" />
+
+							{$i18n.t(`Temporary Chat`)}
+						</div>
+
+						<div>
+							<Switch state={$temporaryChatEnabled} />
+						</div>
+					</button>
+				</div>
+			{/if}
+
+			<div class="hidden w-[42rem]" />
+			<div class="hidden w-[32rem]" />
+		</slot>
+	</DropdownMenu.Content>
+</DropdownMenu.Root>
+
+<style>
+	.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
+		visibility: visible;
+	}
+	.scrollbar-hidden::-webkit-scrollbar-thumb {
+		visibility: hidden;
+	}
+</style>
diff --git a/src/lib/components/chat/Overview.svelte b/src/lib/components/chat/Overview.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..5592e013999569e7f95e7e6d1317dfdfb4c5a013
--- /dev/null
+++ b/src/lib/components/chat/Overview.svelte
@@ -0,0 +1,199 @@
+<script lang="ts">
+	import { getContext, createEventDispatcher, onDestroy } from 'svelte';
+	import { useSvelteFlow, useNodesInitialized, useStore } from '@xyflow/svelte';
+
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	import { onMount, tick } from 'svelte';
+
+	import { writable } from 'svelte/store';
+	import { models, showOverview, theme, user } from '$lib/stores';
+
+	import '@xyflow/svelte/dist/style.css';
+
+	import CustomNode from './Overview/Node.svelte';
+	import Flow from './Overview/Flow.svelte';
+	import XMark from '../icons/XMark.svelte';
+	import ArrowLeft from '../icons/ArrowLeft.svelte';
+
+	const { width, height } = useStore();
+
+	const { fitView, getViewport } = useSvelteFlow();
+	const nodesInitialized = useNodesInitialized();
+
+	export let history;
+
+	let selectedMessageId = null;
+
+	const nodes = writable([]);
+	const edges = writable([]);
+
+	const nodeTypes = {
+		custom: CustomNode
+	};
+
+	$: if (history) {
+		drawFlow();
+	}
+
+	$: if (history && history.currentId) {
+		focusNode();
+	}
+
+	const focusNode = async () => {
+		if (selectedMessageId === null) {
+			await fitView({ nodes: [{ id: history.currentId }] });
+		} else {
+			await fitView({ nodes: [{ id: selectedMessageId }] });
+		}
+
+		selectedMessageId = null;
+	};
+
+	const drawFlow = async () => {
+		const nodeList = [];
+		const edgeList = [];
+		const levelOffset = 150; // Vertical spacing between layers
+		const siblingOffset = 250; // Horizontal spacing between nodes at the same layer
+
+		// Map to keep track of node positions at each level
+		let positionMap = new Map();
+
+		// Helper function to truncate labels
+		function createLabel(content) {
+			const maxLength = 100;
+			return content.length > maxLength ? content.substr(0, maxLength) + '...' : content;
+		}
+
+		// Create nodes and map children to ensure alignment in width
+		let layerWidths = {}; // Track widths of each layer
+
+		Object.keys(history.messages).forEach((id) => {
+			const message = history.messages[id];
+			const level = message.parentId ? (positionMap.get(message.parentId)?.level ?? -1) + 1 : 0;
+			if (!layerWidths[level]) layerWidths[level] = 0;
+
+			positionMap.set(id, {
+				id: message.id,
+				level,
+				position: layerWidths[level]++
+			});
+		});
+
+		// Adjust positions based on siblings count to centralize vertical spacing
+		Object.keys(history.messages).forEach((id) => {
+			const pos = positionMap.get(id);
+			const xOffset = pos.position * siblingOffset;
+			const y = pos.level * levelOffset;
+			const x = xOffset;
+
+			nodeList.push({
+				id: pos.id,
+				type: 'custom',
+				data: {
+					user: $user,
+					message: history.messages[id],
+					model: $models.find((model) => model.id === history.messages[id].model)
+				},
+				position: { x, y }
+			});
+
+			// Create edges
+			const parentId = history.messages[id].parentId;
+			if (parentId) {
+				edgeList.push({
+					id: parentId + '-' + pos.id,
+					source: parentId,
+					target: pos.id,
+					selectable: false,
+					class: ' dark:fill-gray-300 fill-gray-300',
+					type: 'smoothstep',
+					animated: history.currentId === id || recurseCheckChild(id, history.currentId)
+				});
+			}
+		});
+
+		await edges.set([...edgeList]);
+		await nodes.set([...nodeList]);
+	};
+
+	const recurseCheckChild = (nodeId, currentId) => {
+		const node = history.messages[nodeId];
+		return (
+			node.childrenIds &&
+			node.childrenIds.some((id) => id === currentId || recurseCheckChild(id, currentId))
+		);
+	};
+
+	onMount(() => {
+		drawFlow();
+
+		nodesInitialized.subscribe(async (initialized) => {
+			if (initialized) {
+				await tick();
+				const res = await fitView({ nodes: [{ id: history.currentId }] });
+			}
+		});
+
+		width.subscribe((value) => {
+			if (value) {
+				// fitView();
+				fitView({ nodes: [{ id: history.currentId }] });
+			}
+		});
+
+		height.subscribe((value) => {
+			if (value) {
+				// fitView();
+				fitView({ nodes: [{ id: history.currentId }] });
+			}
+		});
+	});
+
+	onDestroy(() => {
+		console.log('Overview destroyed');
+
+		nodes.set([]);
+		edges.set([]);
+	});
+</script>
+
+<div class="w-full h-full relative">
+	<div class=" absolute z-50 w-full flex justify-between dark:text-gray-100 px-4 py-3.5">
+		<div class="flex items-center gap-2.5">
+			<button
+				class="self-center p-0.5"
+				on:click={() => {
+					showOverview.set(false);
+				}}
+			>
+				<ArrowLeft className="size-3.5" />
+			</button>
+			<div class=" text-lg font-medium self-center font-primary">{$i18n.t('Chat Overview')}</div>
+		</div>
+		<button
+			class="self-center p-0.5"
+			on:click={() => {
+				dispatch('close');
+				showOverview.set(false);
+			}}
+		>
+			<XMark className="size-3.5" />
+		</button>
+	</div>
+
+	{#if $nodes.length > 0}
+		<Flow
+			{nodes}
+			{nodeTypes}
+			{edges}
+			on:nodeclick={(e) => {
+				console.log(e.detail.node.data);
+				dispatch('nodeclick', e.detail);
+				selectedMessageId = e.detail.node.data.message.id;
+				fitView({ nodes: [{ id: selectedMessageId }] });
+			}}
+		/>
+	{/if}
+</div>
diff --git a/src/lib/components/chat/Overview/Flow.svelte b/src/lib/components/chat/Overview/Flow.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f7ff307567e4a97462c5fba341479be6b9e965a2
--- /dev/null
+++ b/src/lib/components/chat/Overview/Flow.svelte
@@ -0,0 +1,36 @@
+<script>
+	import { createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	import { theme } from '$lib/stores';
+	import { Background, Controls, SvelteFlow, BackgroundVariant } from '@xyflow/svelte';
+
+	export let nodes;
+	export let nodeTypes;
+	export let edges;
+</script>
+
+<SvelteFlow
+	{nodes}
+	{nodeTypes}
+	{edges}
+	fitView
+	minZoom={0.001}
+	colorMode={$theme.includes('dark')
+		? 'dark'
+		: $theme === 'system'
+			? window.matchMedia('(prefers-color-scheme: dark)').matches
+				? 'dark'
+				: 'light'
+			: 'light'}
+	nodesConnectable={false}
+	nodesDraggable={false}
+	on:nodeclick={(e) => dispatch('nodeclick', e.detail)}
+	oninit={() => {
+		console.log('Flow initialized');
+	}}
+>
+	<Controls showLock={false} />
+	<Background variant={BackgroundVariant.Dots} />
+</SvelteFlow>
diff --git a/src/lib/components/chat/Overview/Node.svelte b/src/lib/components/chat/Overview/Node.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e00f53653a807eb123a72ff00007ba5aebac6c05
--- /dev/null
+++ b/src/lib/components/chat/Overview/Node.svelte
@@ -0,0 +1,82 @@
+<script lang="ts">
+	import { WEBUI_BASE_URL } from '$lib/constants';
+	import { Handle, Position, type NodeProps } from '@xyflow/svelte';
+
+	import ProfileImageBase from '../Messages/ProfileImageBase.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Heart from '$lib/components/icons/Heart.svelte';
+
+	type $$Props = NodeProps;
+	export let data: $$Props['data'];
+</script>
+
+<div
+	class="px-4 py-3 shadow-md rounded-xl dark:bg-black bg-white border dark:border-gray-900 w-60 h-20 group"
+>
+	<Tooltip
+		content={data?.message?.error ? data.message.error.content : data.message.content}
+		class="w-full"
+		allowHTML={false}
+	>
+		{#if data.message.role === 'user'}
+			<div class="flex w-full">
+				<ProfileImageBase
+					src={data.user?.profile_image_url ?? '/user.png'}
+					className={'size-5 -translate-y-[1px]'}
+				/>
+				<div class="ml-2">
+					<div class=" flex justify-between items-center">
+						<div class="text-xs text-black dark:text-white font-medium line-clamp-1">
+							{data?.user?.name ?? 'User'}
+						</div>
+					</div>
+
+					{#if data?.message?.error}
+						<div class="text-red-500 line-clamp-2 text-xs mt-0.5">{data.message.error.content}</div>
+					{:else}
+						<div class="text-gray-500 line-clamp-2 text-xs mt-0.5">{data.message.content}</div>
+					{/if}
+				</div>
+			</div>
+		{:else}
+			<div class="flex w-full">
+				<ProfileImageBase
+					src={data?.model?.info?.meta?.profile_image_url ?? ''}
+					className={'size-5 -translate-y-[1px]'}
+				/>
+
+				<div class="ml-2">
+					<div class=" flex justify-between items-center">
+						<div class="text-xs text-black dark:text-white font-medium line-clamp-1">
+							{data?.model?.name ?? data?.message?.model ?? 'Assistant'}
+						</div>
+
+						<button
+							class={data?.message?.favorite ? '' : 'invisible group-hover:visible'}
+							on:click={() => {
+								data.message.favorite = !(data?.message?.favorite ?? false);
+							}}
+						>
+							<Heart
+								className="size-3 {data?.message?.favorite
+									? 'fill-red-500 stroke-red-500'
+									: 'hover:fill-red-500 hover:stroke-red-500'} "
+								strokeWidth="2.5"
+							/>
+						</button>
+					</div>
+
+					{#if data?.message?.error}
+						<div class="text-red-500 line-clamp-2 text-xs mt-0.5">
+							{data.message.error.content}
+						</div>
+					{:else}
+						<div class="text-gray-500 line-clamp-2 text-xs mt-0.5">{data.message.content}</div>
+					{/if}
+				</div>
+			</div>
+		{/if}
+	</Tooltip>
+	<Handle type="target" position={Position.Top} class="w-2 rounded-full dark:bg-gray-900" />
+	<Handle type="source" position={Position.Bottom} class="w-2 rounded-full dark:bg-gray-900" />
+</div>
diff --git a/src/lib/components/chat/Placeholder.svelte b/src/lib/components/chat/Placeholder.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..21d615f2e245c38b2900cc4b6bc76022b8fbf33b
--- /dev/null
+++ b/src/lib/components/chat/Placeholder.svelte
@@ -0,0 +1,231 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { marked } from 'marked';
+
+	import { onMount, getContext, tick, createEventDispatcher } from 'svelte';
+	import { blur, fade } from 'svelte/transition';
+
+	const dispatch = createEventDispatcher();
+
+	import { config, user, models as _models, temporaryChatEnabled } from '$lib/stores';
+	import { sanitizeResponseContent, findWordIndices } from '$lib/utils';
+	import { WEBUI_BASE_URL } from '$lib/constants';
+
+	import Suggestions from './Suggestions.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import EyeSlash from '$lib/components/icons/EyeSlash.svelte';
+	import MessageInput from './MessageInput.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let transparentBackground = false;
+
+	export let createMessagePair: Function;
+	export let stopResponse: Function;
+
+	export let autoScroll = false;
+
+	export let atSelectedModel: Model | undefined;
+	export let selectedModels: [''];
+
+	export let history;
+
+	export let prompt = '';
+	export let files = [];
+	export let availableToolIds = [];
+	export let selectedToolIds = [];
+	export let webSearchEnabled = false;
+
+	let models = [];
+
+	const selectSuggestionPrompt = async (p) => {
+		let text = p;
+
+		if (p.includes('{{CLIPBOARD}}')) {
+			const clipboardText = await navigator.clipboard.readText().catch((err) => {
+				toast.error($i18n.t('Failed to read clipboard contents'));
+				return '{{CLIPBOARD}}';
+			});
+
+			text = p.replaceAll('{{CLIPBOARD}}', clipboardText);
+
+			console.log('Clipboard text:', clipboardText, text);
+		}
+
+		prompt = text;
+
+		console.log(prompt);
+		await tick();
+
+		const chatInputContainerElement = document.getElementById('chat-input-container');
+		const chatInputElement = document.getElementById('chat-input');
+
+		if (chatInputContainerElement) {
+			chatInputContainerElement.style.height = '';
+			chatInputContainerElement.style.height =
+				Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
+		}
+
+		await tick();
+		if (chatInputElement) {
+			chatInputElement.focus();
+			chatInputElement.dispatchEvent(new Event('input'));
+		}
+
+		await tick();
+	};
+
+	let mounted = false;
+	let selectedModelIdx = 0;
+
+	$: if (selectedModels.length > 0) {
+		selectedModelIdx = models.length - 1;
+	}
+
+	$: models = selectedModels.map((id) => $_models.find((m) => m.id === id));
+
+	onMount(() => {
+		mounted = true;
+	});
+</script>
+
+{#key mounted}
+	<div class="m-auto w-full max-w-6xl px-2 xl:px-20 translate-y-6 py-24 text-center">
+		{#if $temporaryChatEnabled}
+			<Tooltip
+				content="This chat won't appear in history and your messages will not be saved."
+				className="w-full flex justify-center mb-0.5"
+				placement="top"
+			>
+				<div class="flex items-center gap-2 text-gray-500 font-medium text-lg my-2 w-fit">
+					<EyeSlash strokeWidth="2.5" className="size-5" /> Temporary Chat
+				</div>
+			</Tooltip>
+		{/if}
+
+		<div
+			class="w-full text-3xl text-gray-800 dark:text-gray-100 font-medium text-center flex items-center gap-4 font-primary"
+		>
+			<div class="w-full flex flex-col justify-center items-center">
+				<div class="flex flex-row justify-center gap-3 sm:gap-3.5 w-fit px-5">
+					<div class="flex flex-shrink-0 justify-center">
+						<div class="flex -space-x-4 mb-0.5" in:fade={{ duration: 100 }}>
+							{#each models as model, modelIdx}
+								<Tooltip
+									content={(models[modelIdx]?.info?.meta?.tags ?? [])
+										.map((tag) => tag.name.toUpperCase())
+										.join(', ')}
+									placement="top"
+								>
+									<button
+										on:click={() => {
+											selectedModelIdx = modelIdx;
+										}}
+									>
+										<img
+											crossorigin="anonymous"
+											src={model?.info?.meta?.profile_image_url ??
+												($i18n.language === 'dg-DG'
+													? `/doge.png`
+													: `${WEBUI_BASE_URL}/static/favicon.png`)}
+											class=" size-9 sm:size-10 rounded-full border-[1px] border-gray-200 dark:border-none"
+											alt="logo"
+											draggable="false"
+										/>
+									</button>
+								</Tooltip>
+							{/each}
+						</div>
+					</div>
+
+					<div class=" capitalize text-3xl sm:text-4xl line-clamp-1" in:fade={{ duration: 100 }}>
+						{#if models[selectedModelIdx]?.name}
+							{models[selectedModelIdx]?.name}
+						{:else}
+							{$i18n.t('Hello, {{name}}', { name: $user.name })}
+						{/if}
+					</div>
+				</div>
+
+				<div class="flex mt-1 mb-2">
+					<div in:fade={{ duration: 100, delay: 50 }}>
+						{#if models[selectedModelIdx]?.info?.meta?.description ?? null}
+							<Tooltip
+								className=" w-fit"
+								content={marked.parse(
+									sanitizeResponseContent(models[selectedModelIdx]?.info?.meta?.description ?? '')
+								)}
+								placement="top"
+							>
+								<div
+									class="mt-0.5 px-2 text-sm font-normal text-gray-500 dark:text-gray-400 line-clamp-2 max-w-xl markdown"
+								>
+									{@html marked.parse(
+										sanitizeResponseContent(models[selectedModelIdx]?.info?.meta?.description)
+									)}
+								</div>
+							</Tooltip>
+
+							{#if models[selectedModelIdx]?.info?.meta?.user}
+								<div class="mt-0.5 text-sm font-normal text-gray-400 dark:text-gray-500">
+									By
+									{#if models[selectedModelIdx]?.info?.meta?.user.community}
+										<a
+											href="https://openwebui.com/m/{models[selectedModelIdx]?.info?.meta?.user
+												.username}"
+											>{models[selectedModelIdx]?.info?.meta?.user.name
+												? models[selectedModelIdx]?.info?.meta?.user.name
+												: `@${models[selectedModelIdx]?.info?.meta?.user.username}`}</a
+										>
+									{:else}
+										{models[selectedModelIdx]?.info?.meta?.user.name}
+									{/if}
+								</div>
+							{/if}
+						{/if}
+					</div>
+				</div>
+
+				<div
+					class="text-base font-normal xl:translate-x-6 md:max-w-3xl w-full py-3 {atSelectedModel
+						? 'mt-2'
+						: ''}"
+				>
+					<MessageInput
+						{history}
+						{selectedModels}
+						bind:files
+						bind:prompt
+						bind:autoScroll
+						bind:selectedToolIds
+						bind:webSearchEnabled
+						bind:atSelectedModel
+						{availableToolIds}
+						{transparentBackground}
+						{stopResponse}
+						{createMessagePair}
+						placeholder={$i18n.t('How can I help you today?')}
+						on:upload={(e) => {
+							dispatch('upload', e.detail);
+						}}
+						on:submit={(e) => {
+							dispatch('submit', e.detail);
+						}}
+					/>
+				</div>
+			</div>
+		</div>
+		<div class="mx-auto max-w-2xl font-primary" in:fade={{ duration: 200, delay: 200 }}>
+			<div class="mx-5">
+				<Suggestions
+					suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
+						$config?.default_prompt_suggestions ??
+						[]}
+					on:select={(e) => {
+						selectSuggestionPrompt(e.detail);
+					}}
+				/>
+			</div>
+		</div>
+	</div>
+{/key}
diff --git a/src/lib/components/chat/Settings/About.svelte b/src/lib/components/chat/Settings/About.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2c21e392605e5a80d4efdc107264e5b5e71baadf
--- /dev/null
+++ b/src/lib/components/chat/Settings/About.svelte
@@ -0,0 +1,144 @@
+<script lang="ts">
+	import { getVersionUpdates } from '$lib/apis';
+	import { getOllamaVersion } from '$lib/apis/ollama';
+	import { WEBUI_BUILD_HASH, WEBUI_VERSION } from '$lib/constants';
+	import { WEBUI_NAME, config, showChangelog } from '$lib/stores';
+	import { compareVersion } from '$lib/utils';
+	import { onMount, getContext } from 'svelte';
+
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+
+	const i18n = getContext('i18n');
+
+	let ollamaVersion = '';
+
+	let updateAvailable = null;
+	let version = {
+		current: '',
+		latest: ''
+	};
+
+	const checkForVersionUpdates = async () => {
+		updateAvailable = null;
+		version = await getVersionUpdates(localStorage.token).catch((error) => {
+			return {
+				current: WEBUI_VERSION,
+				latest: WEBUI_VERSION
+			};
+		});
+
+		console.log(version);
+
+		updateAvailable = compareVersion(version.latest, version.current);
+		console.log(updateAvailable);
+	};
+
+	onMount(async () => {
+		ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => {
+			return '';
+		});
+
+		checkForVersionUpdates();
+	});
+</script>
+
+<div class="flex flex-col h-full justify-between space-y-3 text-sm mb-6">
+	<div class=" space-y-3">
+		<div>
+			<div class=" mb-2.5 text-sm font-medium flex space-x-2 items-center">
+				<div>
+					{$WEBUI_NAME}
+					{$i18n.t('Version')}
+				</div>
+			</div>
+			<div class="flex w-full justify-between items-center">
+				<div class="flex flex-col text-xs text-gray-700 dark:text-gray-200">
+					<div class="flex gap-1">
+						<Tooltip content={WEBUI_BUILD_HASH}>
+							v{WEBUI_VERSION}
+						</Tooltip>
+
+						<a
+							href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}"
+							target="_blank"
+						>
+							{updateAvailable === null
+								? $i18n.t('Checking for updates...')
+								: updateAvailable
+									? `(v${version.latest} ${$i18n.t('available!')})`
+									: $i18n.t('(latest)')}
+						</a>
+					</div>
+
+					<button
+						class=" underline flex items-center space-x-1 text-xs text-gray-500 dark:text-gray-500"
+						on:click={() => {
+							showChangelog.set(true);
+						}}
+					>
+						<div>{$i18n.t("See what's new")}</div>
+					</button>
+				</div>
+
+				<button
+					class=" text-xs px-3 py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-lg font-medium"
+					on:click={() => {
+						checkForVersionUpdates();
+					}}
+				>
+					{$i18n.t('Check for updates')}
+				</button>
+			</div>
+		</div>
+
+		{#if ollamaVersion}
+			<hr class=" dark:border-gray-850" />
+
+			<div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Version')}</div>
+				<div class="flex w-full">
+					<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
+						{ollamaVersion ?? 'N/A'}
+					</div>
+				</div>
+			</div>
+		{/if}
+
+		<hr class=" dark:border-gray-850" />
+
+		<div class="flex space-x-1">
+			<a href="https://discord.gg/5rJgQTnV4s" target="_blank">
+				<img
+					alt="Discord"
+					src="https://img.shields.io/badge/Discord-Open_WebUI-blue?logo=discord&logoColor=white"
+				/>
+			</a>
+
+			<a href="https://twitter.com/OpenWebUI" target="_blank">
+				<img
+					alt="X (formerly Twitter) Follow"
+					src="https://img.shields.io/twitter/follow/OpenWebUI"
+				/>
+			</a>
+
+			<a href="https://github.com/open-webui/open-webui" target="_blank">
+				<img
+					alt="Github Repo"
+					src="https://img.shields.io/github/stars/open-webui/open-webui?style=social&label=Star us on Github"
+				/>
+			</a>
+		</div>
+
+		<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+			{#if !$WEBUI_NAME.includes('Open WebUI')}
+				<span class=" text-gray-500 dark:text-gray-300 font-medium">{$WEBUI_NAME}</span> -
+			{/if}
+			{$i18n.t('Created by')}
+			<a
+				class=" text-gray-500 dark:text-gray-300 font-medium"
+				href="https://github.com/tjbck"
+				target="_blank">Timothy J. Baek</a
+			>
+		</div>
+	</div>
+</div>
diff --git a/src/lib/components/chat/Settings/Account.svelte b/src/lib/components/chat/Settings/Account.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e76a5467946576fd424512c09ae5be82e6cb1862
--- /dev/null
+++ b/src/lib/components/chat/Settings/Account.svelte
@@ -0,0 +1,412 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { onMount, getContext } from 'svelte';
+
+	import { user } from '$lib/stores';
+	import { updateUserProfile, createAPIKey, getAPIKey } from '$lib/apis/auths';
+
+	import UpdatePassword from './Account/UpdatePassword.svelte';
+	import { getGravatarUrl } from '$lib/apis/utils';
+	import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
+	import { copyToClipboard } from '$lib/utils';
+	import Plus from '$lib/components/icons/Plus.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let saveHandler: Function;
+
+	let profileImageUrl = '';
+	let name = '';
+
+	let showAPIKeys = false;
+
+	let JWTTokenCopied = false;
+
+	let APIKey = '';
+	let APIKeyCopied = false;
+
+	let profileImageInputElement: HTMLInputElement;
+
+	const submitHandler = async () => {
+		if (name !== $user.name) {
+			if (profileImageUrl === generateInitialsImage($user.name) || profileImageUrl === '') {
+				profileImageUrl = generateInitialsImage(name);
+			}
+		}
+
+		const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch(
+			(error) => {
+				toast.error(error);
+			}
+		);
+
+		if (updatedUser) {
+			await user.set(updatedUser);
+			return true;
+		}
+		return false;
+	};
+
+	const createAPIKeyHandler = async () => {
+		APIKey = await createAPIKey(localStorage.token);
+		if (APIKey) {
+			toast.success($i18n.t('API Key created.'));
+		} else {
+			toast.error($i18n.t('Failed to create API Key.'));
+		}
+	};
+
+	onMount(async () => {
+		name = $user.name;
+		profileImageUrl = $user.profile_image_url;
+
+		APIKey = await getAPIKey(localStorage.token).catch((error) => {
+			console.log(error);
+			return '';
+		});
+	});
+</script>
+
+<div class="flex flex-col h-full justify-between text-sm">
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
+		<input
+			id="profile-image-input"
+			bind:this={profileImageInputElement}
+			type="file"
+			hidden
+			accept="image/*"
+			on:change={(e) => {
+				const files = profileImageInputElement.files ?? [];
+				let reader = new FileReader();
+				reader.onload = (event) => {
+					let originalImageUrl = `${event.target.result}`;
+
+					const img = new Image();
+					img.src = originalImageUrl;
+
+					img.onload = function () {
+						const canvas = document.createElement('canvas');
+						const ctx = canvas.getContext('2d');
+
+						// Calculate the aspect ratio of the image
+						const aspectRatio = img.width / img.height;
+
+						// Calculate the new width and height to fit within 250x250
+						let newWidth, newHeight;
+						if (aspectRatio > 1) {
+							newWidth = 250 * aspectRatio;
+							newHeight = 250;
+						} else {
+							newWidth = 250;
+							newHeight = 250 / aspectRatio;
+						}
+
+						// Set the canvas size
+						canvas.width = 250;
+						canvas.height = 250;
+
+						// Calculate the position to center the image
+						const offsetX = (250 - newWidth) / 2;
+						const offsetY = (250 - newHeight) / 2;
+
+						// Draw the image on the canvas
+						ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
+
+						// Get the base64 representation of the compressed image
+						const compressedSrc = canvas.toDataURL('image/jpeg');
+
+						// Display the compressed image
+						profileImageUrl = compressedSrc;
+
+						profileImageInputElement.files = null;
+					};
+				};
+
+				if (
+					files.length > 0 &&
+					['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(files[0]['type'])
+				) {
+					reader.readAsDataURL(files[0]);
+				}
+			}}
+		/>
+
+		<div class="space-y-1">
+			<!-- <div class=" text-sm font-medium">{$i18n.t('Account')}</div> -->
+
+			<div class="flex space-x-5">
+				<div class="flex flex-col">
+					<div class="self-center mt-2">
+						<button
+							class="relative rounded-full dark:bg-gray-700"
+							type="button"
+							on:click={() => {
+								profileImageInputElement.click();
+							}}
+						>
+							<img
+								src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(name)}
+								alt="profile"
+								class=" rounded-full size-16 object-cover"
+							/>
+
+							<div
+								class="absolute flex justify-center rounded-full bottom-0 left-0 right-0 top-0 h-full w-full overflow-hidden bg-gray-700 bg-fixed opacity-0 transition duration-300 ease-in-out hover:opacity-50"
+							>
+								<div class="my-auto text-gray-100">
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 20 20"
+										fill="currentColor"
+										class="w-5 h-5"
+									>
+										<path
+											d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
+										/>
+									</svg>
+								</div>
+							</div>
+						</button>
+					</div>
+				</div>
+
+				<div class="flex-1 flex flex-col self-center gap-0.5">
+					<div class=" mb-0.5 text-sm font-medium">{$i18n.t('Profile Image')}</div>
+
+					<div>
+						<button
+							class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850"
+							on:click={async () => {
+								if (canvasPixelTest()) {
+									profileImageUrl = generateInitialsImage(name);
+								} else {
+									toast.info(
+										$i18n.t(
+											'Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.'
+										),
+										{
+											duration: 1000 * 10
+										}
+									);
+								}
+							}}>{$i18n.t('Use Initials')}</button
+						>
+
+						<button
+							class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850"
+							on:click={async () => {
+								const url = await getGravatarUrl($user.email);
+
+								profileImageUrl = url;
+							}}>{$i18n.t('Use Gravatar')}</button
+						>
+
+						<button
+							class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg px-2 py-1"
+							on:click={async () => {
+								profileImageUrl = '/user.png';
+							}}>{$i18n.t('Remove')}</button
+						>
+					</div>
+				</div>
+			</div>
+
+			<div class="pt-0.5">
+				<div class="flex flex-col w-full">
+					<div class=" mb-1 text-xs font-medium">{$i18n.t('Name')}</div>
+
+					<div class="flex-1">
+						<input
+							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+							type="text"
+							bind:value={name}
+							required
+						/>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<div class="py-0.5">
+			<UpdatePassword />
+		</div>
+
+		<hr class=" dark:border-gray-850 my-4" />
+
+		<div class="flex justify-between items-center text-sm">
+			<div class="  font-medium">{$i18n.t('API keys')}</div>
+			<button
+				class=" text-xs font-medium text-gray-500"
+				type="button"
+				on:click={() => {
+					showAPIKeys = !showAPIKeys;
+				}}>{showAPIKeys ? $i18n.t('Hide') : $i18n.t('Show')}</button
+			>
+		</div>
+
+		{#if showAPIKeys}
+			<div class="flex flex-col gap-4">
+				<div class="justify-between w-full">
+					<div class="flex justify-between w-full">
+						<div class="self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
+					</div>
+
+					<div class="flex mt-2">
+						<SensitiveInput value={localStorage.token} readOnly={true} />
+
+						<button
+							class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 transition rounded-lg"
+							on:click={() => {
+								copyToClipboard(localStorage.token);
+								JWTTokenCopied = true;
+								setTimeout(() => {
+									JWTTokenCopied = false;
+								}, 2000);
+							}}
+						>
+							{#if JWTTokenCopied}
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 20 20"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										fill-rule="evenodd"
+										d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
+										clip-rule="evenodd"
+									/>
+								</svg>
+							{:else}
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										fill-rule="evenodd"
+										d="M11.986 3H12a2 2 0 0 1 2 2v6a2 2 0 0 1-1.5 1.937V7A2.5 2.5 0 0 0 10 4.5H4.063A2 2 0 0 1 6 3h.014A2.25 2.25 0 0 1 8.25 1h1.5a2.25 2.25 0 0 1 2.236 2ZM10.5 4v-.75a.75.75 0 0 0-.75-.75h-1.5a.75.75 0 0 0-.75.75V4h3Z"
+										clip-rule="evenodd"
+									/>
+									<path
+										fill-rule="evenodd"
+										d="M3 6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H3Zm1.75 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5ZM4 11.75a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1-.75-.75Z"
+										clip-rule="evenodd"
+									/>
+								</svg>
+							{/if}
+						</button>
+					</div>
+				</div>
+				<div class="justify-between w-full">
+					<div class="flex justify-between w-full">
+						<div class="self-center text-xs font-medium">{$i18n.t('API Key')}</div>
+					</div>
+
+					<div class="flex mt-2">
+						{#if APIKey}
+							<SensitiveInput value={APIKey} readOnly={true} />
+
+							<button
+								class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 transition rounded-lg"
+								on:click={() => {
+									copyToClipboard(APIKey);
+									APIKeyCopied = true;
+									setTimeout(() => {
+										APIKeyCopied = false;
+									}, 2000);
+								}}
+							>
+								{#if APIKeyCopied}
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 20 20"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											fill-rule="evenodd"
+											d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
+											clip-rule="evenodd"
+										/>
+									</svg>
+								{:else}
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 16 16"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											fill-rule="evenodd"
+											d="M11.986 3H12a2 2 0 0 1 2 2v6a2 2 0 0 1-1.5 1.937V7A2.5 2.5 0 0 0 10 4.5H4.063A2 2 0 0 1 6 3h.014A2.25 2.25 0 0 1 8.25 1h1.5a2.25 2.25 0 0 1 2.236 2ZM10.5 4v-.75a.75.75 0 0 0-.75-.75h-1.5a.75.75 0 0 0-.75.75V4h3Z"
+											clip-rule="evenodd"
+										/>
+										<path
+											fill-rule="evenodd"
+											d="M3 6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H3Zm1.75 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5ZM4 11.75a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1-.75-.75Z"
+											clip-rule="evenodd"
+										/>
+									</svg>
+								{/if}
+							</button>
+
+							<Tooltip content={$i18n.t('Create new key')}>
+								<button
+									class=" px-1.5 py-1 dark:hover:bg-gray-850transition rounded-lg"
+									on:click={() => {
+										createAPIKeyHandler();
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										fill="none"
+										viewBox="0 0 24 24"
+										stroke-width="2"
+										stroke="currentColor"
+										class="size-4"
+									>
+										<path
+											stroke-linecap="round"
+											stroke-linejoin="round"
+											d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
+										/>
+									</svg>
+								</button>
+							</Tooltip>
+						{:else}
+							<button
+								class="flex gap-1.5 items-center font-medium px-3.5 py-1.5 rounded-lg bg-gray-100/70 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-850 transition"
+								on:click={() => {
+									createAPIKeyHandler();
+								}}
+							>
+								<Plus strokeWidth="2" className=" size-3.5" />
+
+								{$i18n.t('Create new secret key')}</button
+							>
+						{/if}
+					</div>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			on:click={async () => {
+				const res = await submitHandler();
+
+				if (res) {
+					saveHandler();
+				}
+			}}
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</div>
diff --git a/src/lib/components/chat/Settings/Account/UpdatePassword.svelte b/src/lib/components/chat/Settings/Account/UpdatePassword.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..175ee61ebbed6d21c0bd6fb650e7cc04bd3b3176
--- /dev/null
+++ b/src/lib/components/chat/Settings/Account/UpdatePassword.svelte
@@ -0,0 +1,109 @@
+<script lang="ts">
+	import { getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+	import { updateUserPassword } from '$lib/apis/auths';
+
+	const i18n = getContext('i18n');
+
+	let show = false;
+	let currentPassword = '';
+	let newPassword = '';
+	let newPasswordConfirm = '';
+
+	const updatePasswordHandler = async () => {
+		if (newPassword === newPasswordConfirm) {
+			const res = await updateUserPassword(localStorage.token, currentPassword, newPassword).catch(
+				(error) => {
+					toast.error(error);
+					return null;
+				}
+			);
+
+			if (res) {
+				toast.success($i18n.t('Successfully updated.'));
+			}
+
+			currentPassword = '';
+			newPassword = '';
+			newPasswordConfirm = '';
+		} else {
+			toast.error(
+				`The passwords you entered don't quite match. Please double-check and try again.`
+			);
+			newPassword = '';
+			newPasswordConfirm = '';
+		}
+	};
+</script>
+
+<form
+	class="flex flex-col text-sm"
+	on:submit|preventDefault={() => {
+		updatePasswordHandler();
+	}}
+>
+	<div class="flex justify-between items-center text-sm">
+		<div class="  font-medium">{$i18n.t('Change Password')}</div>
+		<button
+			class=" text-xs font-medium text-gray-500"
+			type="button"
+			on:click={() => {
+				show = !show;
+			}}>{show ? $i18n.t('Hide') : $i18n.t('Show')}</button
+		>
+	</div>
+
+	{#if show}
+		<div class=" py-2.5 space-y-1.5">
+			<div class="flex flex-col w-full">
+				<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Current Password')}</div>
+
+				<div class="flex-1">
+					<input
+						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+						type="password"
+						bind:value={currentPassword}
+						autocomplete="current-password"
+						required
+					/>
+				</div>
+			</div>
+
+			<div class="flex flex-col w-full">
+				<div class=" mb-1 text-xs text-gray-500">{$i18n.t('New Password')}</div>
+
+				<div class="flex-1">
+					<input
+						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+						type="password"
+						bind:value={newPassword}
+						autocomplete="new-password"
+						required
+					/>
+				</div>
+			</div>
+
+			<div class="flex flex-col w-full">
+				<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Confirm Password')}</div>
+
+				<div class="flex-1">
+					<input
+						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+						type="password"
+						bind:value={newPasswordConfirm}
+						autocomplete="off"
+						required
+					/>
+				</div>
+			</div>
+		</div>
+
+		<div class="mt-3 flex justify-end">
+			<button
+				class=" px-4 py-2 text-xs bg-gray-800 hover:bg-gray-900 dark:bg-gray-700 dark:hover:bg-gray-800 text-gray-100 transition rounded-md font-medium"
+			>
+				{$i18n.t('Update password')}
+			</button>
+		</div>
+	{/if}
+</form>
diff --git a/src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte b/src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..95b27b889b683f59dafd6e8a8ba89a453a4e3e97
--- /dev/null
+++ b/src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
@@ -0,0 +1,976 @@
+<script lang="ts">
+	import Switch from '$lib/components/common/Switch.svelte';
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	const i18n = getContext('i18n');
+
+	export let admin = false;
+
+	export let params = {
+		// Advanced
+		stream_response: null, // Set stream responses for this model individually
+		seed: null,
+		stop: null,
+		temperature: null,
+		frequency_penalty: null,
+		repeat_last_n: null,
+		mirostat: null,
+		mirostat_eta: null,
+		mirostat_tau: null,
+		top_k: null,
+		top_p: null,
+		min_p: null,
+		tfs_z: null,
+		num_ctx: null,
+		num_batch: null,
+		num_keep: null,
+		max_tokens: null,
+		use_mmap: null,
+		use_mlock: null,
+		num_thread: null,
+		num_gpu: null,
+		template: null
+	};
+
+	let customFieldName = '';
+	let customFieldValue = '';
+
+	$: if (params) {
+		dispatch('change', params);
+	}
+</script>
+
+<div class=" space-y-1 text-xs pb-safe-bottom">
+	<div>
+		<div class=" py-0.5 flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">
+				{$i18n.t('Stream Chat Response')}
+			</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition"
+				on:click={() => {
+					params.stream_response =
+						(params?.stream_response ?? null) === null
+							? true
+							: params.stream_response
+								? false
+								: null;
+				}}
+				type="button"
+			>
+				{#if params.stream_response === true}
+					<span class="ml-2 self-center">{$i18n.t('On')}</span>
+				{:else if params.stream_response === false}
+					<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{/if}
+			</button>
+		</div>
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Seed')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.seed = (params?.seed ?? null) === null ? 0 : null;
+				}}
+			>
+				{#if (params?.seed ?? null) === null}
+					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
+				{:else}
+					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.seed ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+						type="number"
+						placeholder="Enter Seed"
+						bind:value={params.seed}
+						autocomplete="off"
+						min="0"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Stop Sequence')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.stop = (params?.stop ?? null) === null ? '' : null;
+				}}
+			>
+				{#if (params?.stop ?? null) === null}
+					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
+				{:else}
+					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.stop ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+						type="text"
+						placeholder={$i18n.t('Enter stop sequence')}
+						bind:value={params.stop}
+						autocomplete="off"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Temperature')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.temperature = (params?.temperature ?? null) === null ? 0.8 : null;
+				}}
+			>
+				{#if (params?.temperature ?? null) === null}
+					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
+				{:else}
+					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.temperature ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="0"
+						max="1"
+						step="0.05"
+						bind:value={params.temperature}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div>
+					<input
+						bind:value={params.temperature}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="0"
+						max="1"
+						step="any"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Mirostat')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.mirostat = (params?.mirostat ?? null) === null ? 0 : null;
+				}}
+			>
+				{#if (params?.mirostat ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.mirostat ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="0"
+						max="2"
+						step="1"
+						bind:value={params.mirostat}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div>
+					<input
+						bind:value={params.mirostat}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="0"
+						max="2"
+						step="1"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Mirostat Eta')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.mirostat_eta = (params?.mirostat_eta ?? null) === null ? 0.1 : null;
+				}}
+			>
+				{#if (params?.mirostat_eta ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.mirostat_eta ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="0"
+						max="1"
+						step="0.05"
+						bind:value={params.mirostat_eta}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div>
+					<input
+						bind:value={params.mirostat_eta}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="0"
+						max="1"
+						step="any"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Mirostat Tau')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.mirostat_tau = (params?.mirostat_tau ?? null) === null ? 5.0 : null;
+				}}
+			>
+				{#if (params?.mirostat_tau ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.mirostat_tau ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="0"
+						max="10"
+						step="0.5"
+						bind:value={params.mirostat_tau}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div>
+					<input
+						bind:value={params.mirostat_tau}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="0"
+						max="10"
+						step="any"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Top K')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.top_k = (params?.top_k ?? null) === null ? 40 : null;
+				}}
+			>
+				{#if (params?.top_k ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.top_k ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="0"
+						max="100"
+						step="0.5"
+						bind:value={params.top_k}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div>
+					<input
+						bind:value={params.top_k}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="0"
+						max="100"
+						step="any"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Top P')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.top_p = (params?.top_p ?? null) === null ? 0.9 : null;
+				}}
+			>
+				{#if (params?.top_p ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.top_p ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="0"
+						max="1"
+						step="0.05"
+						bind:value={params.top_p}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div>
+					<input
+						bind:value={params.top_p}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="0"
+						max="1"
+						step="any"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Min P')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.min_p = (params?.min_p ?? null) === null ? 0.0 : null;
+				}}
+			>
+				{#if (params?.min_p ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.min_p ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="0"
+						max="1"
+						step="0.05"
+						bind:value={params.min_p}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div>
+					<input
+						bind:value={params.min_p}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="0"
+						max="1"
+						step="any"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Frequency Penalty')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.frequency_penalty = (params?.frequency_penalty ?? null) === null ? 1.1 : null;
+				}}
+			>
+				{#if (params?.frequency_penalty ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.frequency_penalty ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="0"
+						max="2"
+						step="0.05"
+						bind:value={params.frequency_penalty}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div>
+					<input
+						bind:value={params.frequency_penalty}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="0"
+						max="2"
+						step="any"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Repeat Last N')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.repeat_last_n = (params?.repeat_last_n ?? null) === null ? 64 : null;
+				}}
+			>
+				{#if (params?.repeat_last_n ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.repeat_last_n ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="-1"
+						max="128"
+						step="1"
+						bind:value={params.repeat_last_n}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div>
+					<input
+						bind:value={params.repeat_last_n}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="-1"
+						max="128"
+						step="1"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Tfs Z')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.tfs_z = (params?.tfs_z ?? null) === null ? 1 : null;
+				}}
+			>
+				{#if (params?.tfs_z ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.tfs_z ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="0"
+						max="2"
+						step="0.05"
+						bind:value={params.tfs_z}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div>
+					<input
+						bind:value={params.tfs_z}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="0"
+						max="2"
+						step="any"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Context Length')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.num_ctx = (params?.num_ctx ?? null) === null ? 2048 : null;
+				}}
+			>
+				{#if (params?.num_ctx ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.num_ctx ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="-1"
+						max="10240000"
+						step="1"
+						bind:value={params.num_ctx}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div class="">
+					<input
+						bind:value={params.num_ctx}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="-1"
+						step="1"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Batch Size (num_batch)')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.num_batch = (params?.num_batch ?? null) === null ? 512 : null;
+				}}
+			>
+				{#if (params?.num_batch ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.num_batch ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="256"
+						max="8192"
+						step="256"
+						bind:value={params.num_batch}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div class="">
+					<input
+						bind:value={params.num_batch}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="256"
+						step="256"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">
+				{$i18n.t('Tokens To Keep On Context Refresh (num_keep)')}
+			</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.num_keep = (params?.num_keep ?? null) === null ? 24 : null;
+				}}
+			>
+				{#if (params?.num_keep ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.num_keep ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="-1"
+						max="10240000"
+						step="1"
+						bind:value={params.num_keep}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div class="">
+					<input
+						bind:value={params.num_keep}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="-1"
+						step="1"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<div class="flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">{$i18n.t('Max Tokens (num_predict)')}</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+				type="button"
+				on:click={() => {
+					params.max_tokens = (params?.max_tokens ?? null) === null ? 128 : null;
+				}}
+			>
+				{#if (params?.max_tokens ?? null) === null}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+				{/if}
+			</button>
+		</div>
+
+		{#if (params?.max_tokens ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<div class=" flex-1">
+					<input
+						id="steps-range"
+						type="range"
+						min="-2"
+						max="131072"
+						step="1"
+						bind:value={params.max_tokens}
+						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+					/>
+				</div>
+				<div class="">
+					<input
+						bind:value={params.max_tokens}
+						type="number"
+						class=" bg-transparent text-center w-14"
+						min="-2"
+						step="1"
+					/>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	{#if admin}
+		<div class=" py-0.5 w-full justify-between">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('use_mmap (Ollama)')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+					type="button"
+					on:click={() => {
+						params.use_mmap = (params?.use_mmap ?? null) === null ? true : null;
+					}}
+				>
+					{#if (params?.use_mmap ?? null) === null}
+						<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+					{/if}
+				</button>
+			</div>
+
+			{#if (params?.use_mmap ?? null) !== null}
+				<div class="flex justify-between items-center mt-1">
+					<div class="text-xs text-gray-500">
+						{params.use_mmap ? 'Enabled' : 'Disabled'}
+					</div>
+
+					<div class=" pr-2">
+						<Switch bind:state={params.use_mmap} />
+					</div>
+				</div>
+			{/if}
+		</div>
+
+		<div class=" py-0.5 w-full justify-between">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('use_mlock (Ollama)')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+					type="button"
+					on:click={() => {
+						params.use_mlock = (params?.use_mlock ?? null) === null ? true : null;
+					}}
+				>
+					{#if (params?.use_mlock ?? null) === null}
+						<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+					{/if}
+				</button>
+			</div>
+
+			{#if (params?.use_mlock ?? null) !== null}
+				<div class="flex justify-between items-center mt-1">
+					<div class="text-xs text-gray-500">
+						{params.use_mlock ? 'Enabled' : 'Disabled'}
+					</div>
+
+					<div class=" pr-2">
+						<Switch bind:state={params.use_mlock} />
+					</div>
+				</div>
+			{/if}
+		</div>
+
+		<div class=" py-0.5 w-full justify-between">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('num_thread (Ollama)')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+					type="button"
+					on:click={() => {
+						params.num_thread = (params?.num_thread ?? null) === null ? 2 : null;
+					}}
+				>
+					{#if (params?.num_thread ?? null) === null}
+						<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+					{/if}
+				</button>
+			</div>
+
+			{#if (params?.num_thread ?? null) !== null}
+				<div class="flex mt-0.5 space-x-2">
+					<div class=" flex-1">
+						<input
+							id="steps-range"
+							type="range"
+							min="1"
+							max="256"
+							step="1"
+							bind:value={params.num_thread}
+							class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+						/>
+					</div>
+					<div class="">
+						<input
+							bind:value={params.num_thread}
+							type="number"
+							class=" bg-transparent text-center w-14"
+							min="1"
+							max="256"
+							step="1"
+						/>
+					</div>
+				</div>
+			{/if}
+		</div>
+
+		<div class=" py-0.5 w-full justify-between">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('num_gpu (Ollama)')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+					type="button"
+					on:click={() => {
+						params.num_gpu = (params?.num_gpu ?? null) === null ? 0 : null;
+					}}
+				>
+					{#if (params?.num_gpu ?? null) === null}
+						<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+					{/if}
+				</button>
+			</div>
+
+			{#if (params?.num_gpu ?? null) !== null}
+				<div class="flex mt-0.5 space-x-2">
+					<div class=" flex-1">
+						<input
+							id="steps-range"
+							type="range"
+							min="0"
+							max="256"
+							step="1"
+							bind:value={params.num_gpu}
+							class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
+						/>
+					</div>
+					<div class="">
+						<input
+							bind:value={params.num_gpu}
+							type="number"
+							class=" bg-transparent text-center w-14"
+							min="0"
+							max="256"
+							step="1"
+						/>
+					</div>
+				</div>
+			{/if}
+		</div>
+
+		<!-- <div class=" py-0.5 w-full justify-between">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('Template')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
+					type="button"
+					on:click={() => {
+						params.template = (params?.template ?? null) === null ? '' : null;
+					}}
+				>
+					{#if (params?.template ?? null) === null}
+						<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+					{/if}
+				</button>
+			</div>
+
+			{#if (params?.template ?? null) !== null}
+				<div class="flex mt-0.5 space-x-2">
+					<div class=" flex-1">
+						<textarea
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
+							placeholder="Write your model template content here"
+							rows="4"
+							bind:value={params.template}
+						/>
+					</div>
+				</div>
+			{/if}
+		</div> -->
+	{/if}
+</div>
diff --git a/src/lib/components/chat/Settings/Audio.svelte b/src/lib/components/chat/Settings/Audio.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..677cfa8e07af4844a5f0c9d3474635f6dded308f
--- /dev/null
+++ b/src/lib/components/chat/Settings/Audio.svelte
@@ -0,0 +1,242 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { createEventDispatcher, onMount, getContext } from 'svelte';
+
+	import { user, settings, config } from '$lib/stores';
+	import { getVoices as _getVoices } from '$lib/apis/audio';
+
+	import Switch from '$lib/components/common/Switch.svelte';
+	const dispatch = createEventDispatcher();
+
+	const i18n = getContext('i18n');
+
+	export let saveSettings: Function;
+
+	// Audio
+	let conversationMode = false;
+	let speechAutoSend = false;
+	let responseAutoPlayback = false;
+	let nonLocalVoices = false;
+
+	let STTEngine = '';
+
+	let voices = [];
+	let voice = '';
+
+	// Audio speed control
+	let playbackRate = 1;
+	const speedOptions = [2, 1.75, 1.5, 1.25, 1, 0.75, 0.5];
+
+	const getVoices = async () => {
+		if ($config.audio.tts.engine === '') {
+			const getVoicesLoop = setInterval(async () => {
+				voices = await speechSynthesis.getVoices();
+
+				// do your loop
+				if (voices.length > 0) {
+					clearInterval(getVoicesLoop);
+				}
+			}, 100);
+		} else {
+			const res = await _getVoices(localStorage.token).catch((e) => {
+				toast.error(e);
+			});
+
+			if (res) {
+				console.log(res);
+				voices = res.voices;
+			}
+		}
+	};
+
+	const toggleResponseAutoPlayback = async () => {
+		responseAutoPlayback = !responseAutoPlayback;
+		saveSettings({ responseAutoPlayback: responseAutoPlayback });
+	};
+
+	const toggleSpeechAutoSend = async () => {
+		speechAutoSend = !speechAutoSend;
+		saveSettings({ speechAutoSend: speechAutoSend });
+	};
+
+	onMount(async () => {
+		playbackRate = $settings.audio?.tts?.playbackRate ?? 1;
+		conversationMode = $settings.conversationMode ?? false;
+		speechAutoSend = $settings.speechAutoSend ?? false;
+		responseAutoPlayback = $settings.responseAutoPlayback ?? false;
+
+		STTEngine = $settings?.audio?.stt?.engine ?? '';
+
+		if ($settings?.audio?.tts?.defaultVoice === $config.audio.tts.voice) {
+			voice = $settings?.audio?.tts?.voice ?? $config.audio.tts.voice ?? '';
+		} else {
+			voice = $config.audio.tts.voice ?? '';
+		}
+
+		nonLocalVoices = $settings.audio?.tts?.nonLocalVoices ?? false;
+
+		await getVoices();
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={async () => {
+		saveSettings({
+			audio: {
+				stt: {
+					engine: STTEngine !== '' ? STTEngine : undefined
+				},
+				tts: {
+					playbackRate: playbackRate,
+					voice: voice !== '' ? voice : undefined,
+					defaultVoice: $config?.audio?.tts?.voice ?? '',
+					nonLocalVoices: $config.audio.tts.engine === '' ? nonLocalVoices : undefined
+				}
+			}
+		});
+		dispatch('save');
+	}}
+>
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
+		<div>
+			<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
+
+			{#if $config.audio.stt.engine !== 'web'}
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
+					<div class="flex items-center relative">
+						<select
+							class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							bind:value={STTEngine}
+							placeholder="Select an engine"
+						>
+							<option value="">{$i18n.t('Default')}</option>
+							<option value="web">{$i18n.t('Web API')}</option>
+						</select>
+					</div>
+				</div>
+			{/if}
+
+			<div class=" py-0.5 flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">
+					{$i18n.t('Instant Auto-Send After Voice Transcription')}
+				</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					on:click={() => {
+						toggleSpeechAutoSend();
+					}}
+					type="button"
+				>
+					{#if speechAutoSend === true}
+						<span class="ml-2 self-center">{$i18n.t('On')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+					{/if}
+				</button>
+			</div>
+		</div>
+
+		<div>
+			<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
+
+			<div class=" py-0.5 flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('Auto-playback response')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					on:click={() => {
+						toggleResponseAutoPlayback();
+					}}
+					type="button"
+				>
+					{#if responseAutoPlayback === true}
+						<span class="ml-2 self-center">{$i18n.t('On')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+					{/if}
+				</button>
+			</div>
+
+			<div class=" py-0.5 flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('Speech Playback Speed')}</div>
+
+				<div class="flex items-center relative">
+					<select
+						class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+						bind:value={playbackRate}
+					>
+						{#each speedOptions as option}
+							<option value={option} selected={playbackRate === option}>{option}x</option>
+						{/each}
+					</select>
+				</div>
+			</div>
+		</div>
+
+		<hr class=" dark:border-gray-850" />
+
+		{#if $config.audio.tts.engine === ''}
+			<div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
+				<div class="flex w-full">
+					<div class="flex-1">
+						<select
+							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+							bind:value={voice}
+						>
+							<option value="" selected={voice !== ''}>{$i18n.t('Default')}</option>
+							{#each voices.filter((v) => nonLocalVoices || v.localService === true) as _voice}
+								<option
+									value={_voice.name}
+									class="bg-gray-100 dark:bg-gray-700"
+									selected={voice === _voice.name}>{_voice.name}</option
+								>
+							{/each}
+						</select>
+					</div>
+				</div>
+				<div class="flex items-center justify-between my-1.5">
+					<div class="text-xs">
+						{$i18n.t('Allow non-local voices')}
+					</div>
+
+					<div class="mt-1">
+						<Switch bind:state={nonLocalVoices} />
+					</div>
+				</div>
+			</div>
+		{:else if $config.audio.tts.engine !== ''}
+			<div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
+				<div class="flex w-full">
+					<div class="flex-1">
+						<input
+							list="voice-list"
+							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+							bind:value={voice}
+							placeholder="Select a voice"
+						/>
+
+						<datalist id="voice-list">
+							{#each voices as voice}
+								<option value={voice.id}>{voice.name}</option>
+							{/each}
+						</datalist>
+					</div>
+				</div>
+			</div>
+		{/if}
+	</div>
+
+	<div class="flex justify-end text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/chat/Settings/Chats.svelte b/src/lib/components/chat/Settings/Chats.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..709d3d24a8b0a2ce982274f0df80d6abe01f739e
--- /dev/null
+++ b/src/lib/components/chat/Settings/Chats.svelte
@@ -0,0 +1,332 @@
+<script lang="ts">
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { chats, user, settings, scrollPaginationEnabled, currentChatPage } from '$lib/stores';
+
+	import {
+		archiveAllChats,
+		createNewChat,
+		deleteAllChats,
+		getAllChats,
+		getAllUserChats,
+		getChatList
+	} from '$lib/apis/chats';
+	import { getImportOrigin, convertOpenAIChats } from '$lib/utils';
+	import { onMount, getContext } from 'svelte';
+	import { goto } from '$app/navigation';
+	import { toast } from 'svelte-sonner';
+
+	const i18n = getContext('i18n');
+
+	export let saveSettings: Function;
+
+	// Chats
+	let importFiles;
+
+	let showArchiveConfirm = false;
+	let showDeleteConfirm = false;
+
+	let chatImportInputElement: HTMLInputElement;
+
+	$: if (importFiles) {
+		console.log(importFiles);
+
+		let reader = new FileReader();
+		reader.onload = (event) => {
+			let chats = JSON.parse(event.target.result);
+			console.log(chats);
+			if (getImportOrigin(chats) == 'openai') {
+				try {
+					chats = convertOpenAIChats(chats);
+				} catch (error) {
+					console.log('Unable to import chats:', error);
+				}
+			}
+			importChats(chats);
+		};
+
+		if (importFiles.length > 0) {
+			reader.readAsText(importFiles[0]);
+		}
+	}
+
+	const importChats = async (_chats) => {
+		for (const chat of _chats) {
+			console.log(chat);
+
+			if (chat.chat) {
+				await createNewChat(localStorage.token, chat.chat);
+			} else {
+				await createNewChat(localStorage.token, chat);
+			}
+		}
+
+		currentChatPage.set(1);
+		await chats.set(await getChatList(localStorage.token, $currentChatPage));
+		scrollPaginationEnabled.set(true);
+	};
+
+	const exportChats = async () => {
+		let blob = new Blob([JSON.stringify(await getAllChats(localStorage.token))], {
+			type: 'application/json'
+		});
+		saveAs(blob, `chat-export-${Date.now()}.json`);
+	};
+
+	const archiveAllChatsHandler = async () => {
+		await goto('/');
+		await archiveAllChats(localStorage.token).catch((error) => {
+			toast.error(error);
+		});
+
+		currentChatPage.set(1);
+		await chats.set(await getChatList(localStorage.token, $currentChatPage));
+		scrollPaginationEnabled.set(true);
+	};
+
+	const deleteAllChatsHandler = async () => {
+		await goto('/');
+		await deleteAllChats(localStorage.token).catch((error) => {
+			toast.error(error);
+		});
+
+		currentChatPage.set(1);
+		await chats.set(await getChatList(localStorage.token, $currentChatPage));
+		scrollPaginationEnabled.set(true);
+	};
+</script>
+
+<div class="flex flex-col h-full justify-between space-y-3 text-sm max-h-[22rem]">
+	<div class=" space-y-2">
+		<div class="flex flex-col">
+			<input
+				id="chat-import-input"
+				bind:this={chatImportInputElement}
+				bind:files={importFiles}
+				type="file"
+				accept=".json"
+				hidden
+			/>
+			<button
+				class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+				on:click={() => {
+					chatImportInputElement.click();
+				}}
+			>
+				<div class=" self-center mr-3">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 16 16"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+				<div class=" self-center text-sm font-medium">{$i18n.t('Import Chats')}</div>
+			</button>
+			<button
+				class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+				on:click={() => {
+					exportChats();
+				}}
+			>
+				<div class=" self-center mr-3">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 16 16"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+				<div class=" self-center text-sm font-medium">{$i18n.t('Export Chats')}</div>
+			</button>
+		</div>
+
+		<hr class=" dark:border-gray-850" />
+
+		<div class="flex flex-col">
+			{#if showArchiveConfirm}
+				<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
+					<div class="flex items-center space-x-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
+							<path
+								fill-rule="evenodd"
+								d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+						<span>{$i18n.t('Are you sure?')}</span>
+					</div>
+
+					<div class="flex space-x-1.5 items-center">
+						<button
+							class="hover:text-white transition"
+							on:click={() => {
+								archiveAllChatsHandler();
+								showArchiveConfirm = false;
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-4 h-4"
+							>
+								<path
+									fill-rule="evenodd"
+									d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
+									clip-rule="evenodd"
+								/>
+							</svg>
+						</button>
+						<button
+							class="hover:text-white transition"
+							on:click={() => {
+								showArchiveConfirm = false;
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-4 h-4"
+							>
+								<path
+									d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+								/>
+							</svg>
+						</button>
+					</div>
+				</div>
+			{:else}
+				<button
+					class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+					on:click={() => {
+						showArchiveConfirm = true;
+					}}
+				>
+					<div class=" self-center mr-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							class="size-4"
+						>
+							<path
+								d="M3.375 3C2.339 3 1.5 3.84 1.5 4.875v.75c0 1.036.84 1.875 1.875 1.875h17.25c1.035 0 1.875-.84 1.875-1.875v-.75C22.5 3.839 21.66 3 20.625 3H3.375Z"
+							/>
+							<path
+								fill-rule="evenodd"
+								d="m3.087 9 .54 9.176A3 3 0 0 0 6.62 21h10.757a3 3 0 0 0 2.995-2.824L20.913 9H3.087Zm6.163 3.75A.75.75 0 0 1 10 12h4a.75.75 0 0 1 0 1.5h-4a.75.75 0 0 1-.75-.75Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center text-sm font-medium">{$i18n.t('Archive All Chats')}</div>
+				</button>
+			{/if}
+
+			{#if showDeleteConfirm}
+				<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
+					<div class="flex items-center space-x-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
+							<path
+								fill-rule="evenodd"
+								d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+						<span>{$i18n.t('Are you sure?')}</span>
+					</div>
+
+					<div class="flex space-x-1.5 items-center">
+						<button
+							class="hover:text-white transition"
+							on:click={() => {
+								deleteAllChatsHandler();
+								showDeleteConfirm = false;
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-4 h-4"
+							>
+								<path
+									fill-rule="evenodd"
+									d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
+									clip-rule="evenodd"
+								/>
+							</svg>
+						</button>
+						<button
+							class="hover:text-white transition"
+							on:click={() => {
+								showDeleteConfirm = false;
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-4 h-4"
+							>
+								<path
+									d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+								/>
+							</svg>
+						</button>
+					</div>
+				</div>
+			{:else}
+				<button
+					class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
+					on:click={() => {
+						showDeleteConfirm = true;
+					}}
+				>
+					<div class=" self-center mr-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm7 7a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1 0-1.5h4.5A.75.75 0 0 1 11 9Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center text-sm font-medium">{$i18n.t('Delete All Chats')}</div>
+				</button>
+			{/if}
+		</div>
+	</div>
+</div>
diff --git a/src/lib/components/chat/Settings/General.svelte b/src/lib/components/chat/Settings/General.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..bff0ceb3e8940b59605637ef19123cfb11c18718
--- /dev/null
+++ b/src/lib/components/chat/Settings/General.svelte
@@ -0,0 +1,361 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { createEventDispatcher, onMount, getContext } from 'svelte';
+	import { getLanguages } from '$lib/i18n';
+	const dispatch = createEventDispatcher();
+
+	import { models, settings, theme, user } from '$lib/stores';
+
+	const i18n = getContext('i18n');
+
+	import AdvancedParams from './Advanced/AdvancedParams.svelte';
+
+	export let saveSettings: Function;
+	export let getModels: Function;
+
+	// General
+	let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light', 'oled-dark'];
+	let selectedTheme = 'system';
+
+	let languages: Awaited<ReturnType<typeof getLanguages>> = [];
+	let lang = $i18n.language;
+	let notificationEnabled = false;
+	let system = '';
+
+	let showAdvanced = false;
+
+	const toggleNotification = async () => {
+		const permission = await Notification.requestPermission();
+
+		if (permission === 'granted') {
+			notificationEnabled = !notificationEnabled;
+			saveSettings({ notificationEnabled: notificationEnabled });
+		} else {
+			toast.error(
+				$i18n.t(
+					'Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.'
+				)
+			);
+		}
+	};
+
+	// Advanced
+	let requestFormat = '';
+	let keepAlive: string | null = null;
+
+	let params = {
+		// Advanced
+		stream_response: null,
+		seed: null,
+		temperature: null,
+		frequency_penalty: null,
+		repeat_last_n: null,
+		mirostat: null,
+		mirostat_eta: null,
+		mirostat_tau: null,
+		top_k: null,
+		top_p: null,
+		stop: null,
+		tfs_z: null,
+		num_ctx: null,
+		num_batch: null,
+		num_keep: null,
+		max_tokens: null,
+		num_gpu: null
+	};
+
+	const toggleRequestFormat = async () => {
+		if (requestFormat === '') {
+			requestFormat = 'json';
+		} else {
+			requestFormat = '';
+		}
+
+		saveSettings({ requestFormat: requestFormat !== '' ? requestFormat : undefined });
+	};
+
+	onMount(async () => {
+		selectedTheme = localStorage.theme ?? 'system';
+
+		languages = await getLanguages();
+
+		notificationEnabled = $settings.notificationEnabled ?? false;
+		system = $settings.system ?? '';
+
+		requestFormat = $settings.requestFormat ?? '';
+		keepAlive = $settings.keepAlive ?? null;
+
+		params = { ...params, ...$settings.params };
+		params.stop = $settings?.params?.stop ? ($settings?.params?.stop ?? []).join(',') : null;
+	});
+
+	const applyTheme = (_theme: string) => {
+		let themeToApply = _theme === 'oled-dark' ? 'dark' : _theme;
+
+		if (_theme === 'system') {
+			themeToApply = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+		}
+
+		if (themeToApply === 'dark' && !_theme.includes('oled')) {
+			document.documentElement.style.setProperty('--color-gray-800', '#333');
+			document.documentElement.style.setProperty('--color-gray-850', '#262626');
+			document.documentElement.style.setProperty('--color-gray-900', '#171717');
+			document.documentElement.style.setProperty('--color-gray-950', '#0d0d0d');
+		}
+
+		themes
+			.filter((e) => e !== themeToApply)
+			.forEach((e) => {
+				e.split(' ').forEach((e) => {
+					document.documentElement.classList.remove(e);
+				});
+			});
+
+		themeToApply.split(' ').forEach((e) => {
+			document.documentElement.classList.add(e);
+		});
+
+		const metaThemeColor = document.querySelector('meta[name="theme-color"]');
+		if (metaThemeColor) {
+			if (_theme.includes('system')) {
+				const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
+					? 'dark'
+					: 'light';
+				console.log('Setting system meta theme color: ' + systemTheme);
+				metaThemeColor.setAttribute('content', systemTheme === 'light' ? '#ffffff' : '#171717');
+			} else {
+				console.log('Setting meta theme color: ' + _theme);
+				metaThemeColor.setAttribute(
+					'content',
+					_theme === 'dark'
+						? '#171717'
+						: _theme === 'oled-dark'
+							? '#000000'
+							: _theme === 'her'
+								? '#983724'
+								: '#ffffff'
+				);
+			}
+		}
+
+		console.log(_theme);
+	};
+
+	const themeChangeHandler = (_theme: string) => {
+		theme.set(_theme);
+		localStorage.setItem('theme', _theme);
+		if (_theme.includes('oled')) {
+			document.documentElement.style.setProperty('--color-gray-800', '#101010');
+			document.documentElement.style.setProperty('--color-gray-850', '#050505');
+			document.documentElement.style.setProperty('--color-gray-900', '#000000');
+			document.documentElement.style.setProperty('--color-gray-950', '#000000');
+			document.documentElement.classList.add('dark');
+		}
+		applyTheme(_theme);
+	};
+</script>
+
+<div class="flex flex-col h-full justify-between text-sm">
+	<div class="  pr-1.5 overflow-y-scroll max-h-[25rem]">
+		<div class="">
+			<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Settings')}</div>
+
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('Theme')}</div>
+				<div class="flex items-center relative">
+					<select
+						class=" dark:bg-gray-900 w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
+						bind:value={selectedTheme}
+						placeholder="Select a theme"
+						on:change={() => themeChangeHandler(selectedTheme)}
+					>
+						<option value="system">⚙️ {$i18n.t('System')}</option>
+						<option value="dark">🌑 {$i18n.t('Dark')}</option>
+						<option value="oled-dark">🌃 {$i18n.t('OLED Dark')}</option>
+						<option value="light">☀️ {$i18n.t('Light')}</option>
+						<option value="her">🌷 Her</option>
+						<!-- <option value="rose-pine dark">🪻 {$i18n.t('Rosé Pine')}</option>
+						<option value="rose-pine-dawn light">🌷 {$i18n.t('Rosé Pine Dawn')}</option> -->
+					</select>
+				</div>
+			</div>
+
+			<div class=" flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">{$i18n.t('Language')}</div>
+				<div class="flex items-center relative">
+					<select
+						class=" dark:bg-gray-900 w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
+						bind:value={lang}
+						placeholder="Select a language"
+						on:change={(e) => {
+							$i18n.changeLanguage(lang);
+						}}
+					>
+						{#each languages as language}
+							<option value={language['code']}>{language['title']}</option>
+						{/each}
+					</select>
+				</div>
+			</div>
+			{#if $i18n.language === 'en-US'}
+				<div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
+					Couldn't find your language?
+					<a
+						class=" text-gray-300 font-medium underline"
+						href="https://github.com/open-webui/open-webui/blob/main/docs/CONTRIBUTING.md#-translations-and-internationalization"
+						target="_blank"
+					>
+						Help us translate Open WebUI!
+					</a>
+				</div>
+			{/if}
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Notifications')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleNotification();
+						}}
+						type="button"
+					>
+						{#if notificationEnabled === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+		</div>
+
+		<hr class=" dark:border-gray-850 my-3" />
+
+		<div>
+			<div class=" my-2.5 text-sm font-medium">{$i18n.t('System Prompt')}</div>
+			<textarea
+				bind:value={system}
+				class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
+				rows="4"
+			/>
+		</div>
+
+		<div class="mt-2 space-y-3 pr-1.5">
+			<div class="flex justify-between items-center text-sm">
+				<div class="  font-medium">{$i18n.t('Advanced Parameters')}</div>
+				<button
+					class=" text-xs font-medium text-gray-500"
+					type="button"
+					on:click={() => {
+						showAdvanced = !showAdvanced;
+					}}>{showAdvanced ? $i18n.t('Hide') : $i18n.t('Show')}</button
+				>
+			</div>
+
+			{#if showAdvanced}
+				<AdvancedParams admin={$user?.role === 'admin'} bind:params />
+				<hr class=" dark:border-gray-850" />
+
+				<div class=" py-1 w-full justify-between">
+					<div class="flex w-full justify-between">
+						<div class=" self-center text-xs font-medium">{$i18n.t('Keep Alive')}</div>
+
+						<button
+							class="p-1 px-3 text-xs flex rounded transition"
+							type="button"
+							on:click={() => {
+								keepAlive = keepAlive === null ? '5m' : null;
+							}}
+						>
+							{#if keepAlive === null}
+								<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
+							{:else}
+								<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
+							{/if}
+						</button>
+					</div>
+
+					{#if keepAlive !== null}
+						<div class="flex mt-1 space-x-2">
+							<input
+								class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+								type="text"
+								placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
+								bind:value={keepAlive}
+							/>
+						</div>
+					{/if}
+				</div>
+
+				<div>
+					<div class=" py-1 flex w-full justify-between">
+						<div class=" self-center text-sm font-medium">{$i18n.t('Request Mode')}</div>
+
+						<button
+							class="p-1 px-3 text-xs flex rounded transition"
+							on:click={() => {
+								toggleRequestFormat();
+							}}
+						>
+							{#if requestFormat === ''}
+								<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
+							{:else if requestFormat === 'json'}
+								<!-- <svg
+                            xmlns="http://www.w3.org/2000/svg"
+                            viewBox="0 0 20 20"
+                            fill="currentColor"
+                            class="w-4 h-4 self-center"
+                        >
+                            <path
+                                d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
+                            />
+                        </svg> -->
+								<span class="ml-2 self-center"> {$i18n.t('JSON')} </span>
+							{/if}
+						</button>
+					</div>
+				</div>
+			{/if}
+		</div>
+	</div>
+
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			on:click={() => {
+				saveSettings({
+					system: system !== '' ? system : undefined,
+					params: {
+						stream_response: params.stream_response !== null ? params.stream_response : undefined,
+						seed: (params.seed !== null ? params.seed : undefined) ?? undefined,
+						stop: params.stop ? params.stop.split(',').filter((e) => e) : undefined,
+						temperature: params.temperature !== null ? params.temperature : undefined,
+						frequency_penalty:
+							params.frequency_penalty !== null ? params.frequency_penalty : undefined,
+						repeat_last_n: params.repeat_last_n !== null ? params.repeat_last_n : undefined,
+						mirostat: params.mirostat !== null ? params.mirostat : undefined,
+						mirostat_eta: params.mirostat_eta !== null ? params.mirostat_eta : undefined,
+						mirostat_tau: params.mirostat_tau !== null ? params.mirostat_tau : undefined,
+						top_k: params.top_k !== null ? params.top_k : undefined,
+						top_p: params.top_p !== null ? params.top_p : undefined,
+						tfs_z: params.tfs_z !== null ? params.tfs_z : undefined,
+						num_ctx: params.num_ctx !== null ? params.num_ctx : undefined,
+						num_batch: params.num_batch !== null ? params.num_batch : undefined,
+						num_keep: params.num_keep !== null ? params.num_keep : undefined,
+						max_tokens: params.max_tokens !== null ? params.max_tokens : undefined,
+						use_mmap: params.use_mmap !== null ? params.use_mmap : undefined,
+						use_mlock: params.use_mlock !== null ? params.use_mlock : undefined,
+						num_thread: params.num_thread !== null ? params.num_thread : undefined,
+						num_gpu: params.num_gpu !== null ? params.num_gpu : undefined
+					},
+					keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
+				});
+				dispatch('save');
+			}}
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</div>
diff --git a/src/lib/components/chat/Settings/Interface.svelte b/src/lib/components/chat/Settings/Interface.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ce663bba6fc12950635419421680f9de3762682d
--- /dev/null
+++ b/src/lib/components/chat/Settings/Interface.svelte
@@ -0,0 +1,637 @@
+<script lang="ts">
+	import { getBackendConfig } from '$lib/apis';
+	import { setDefaultPromptSuggestions } from '$lib/apis/configs';
+	import { config, models, settings, user } from '$lib/stores';
+	import { createEventDispatcher, onMount, getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import { updateUserInfo } from '$lib/apis/users';
+	import { getUserPosition } from '$lib/utils';
+	const dispatch = createEventDispatcher();
+
+	const i18n = getContext('i18n');
+
+	export let saveSettings: Function;
+
+	let backgroundImageUrl = null;
+	let inputFiles = null;
+	let filesInputElement;
+
+	// Addons
+	let titleAutoGenerate = true;
+	let autoTags = true;
+
+	let responseAutoCopy = false;
+	let widescreenMode = false;
+	let splitLargeChunks = false;
+	let scrollOnBranchChange = true;
+	let userLocation = false;
+
+	// Interface
+	let defaultModelId = '';
+	let showUsername = false;
+	let richTextInput = true;
+
+	let landingPageMode = '';
+	let chatBubble = true;
+	let chatDirection: 'LTR' | 'RTL' = 'LTR';
+	let showUpdateToast = true;
+
+	let showEmojiInCall = false;
+	let voiceInterruption = false;
+	let hapticFeedback = false;
+
+	const toggleSplitLargeChunks = async () => {
+		splitLargeChunks = !splitLargeChunks;
+		saveSettings({ splitLargeChunks: splitLargeChunks });
+	};
+
+	const togglesScrollOnBranchChange = async () => {
+		scrollOnBranchChange = !scrollOnBranchChange;
+		saveSettings({ scrollOnBranchChange: scrollOnBranchChange });
+	};
+
+	const toggleWidescreenMode = async () => {
+		widescreenMode = !widescreenMode;
+		saveSettings({ widescreenMode: widescreenMode });
+	};
+
+	const toggleChatBubble = async () => {
+		chatBubble = !chatBubble;
+		saveSettings({ chatBubble: chatBubble });
+	};
+
+	const toggleLandingPageMode = async () => {
+		landingPageMode = landingPageMode === '' ? 'chat' : '';
+		saveSettings({ landingPageMode: landingPageMode });
+	};
+
+	const toggleShowUpdateToast = async () => {
+		showUpdateToast = !showUpdateToast;
+		saveSettings({ showUpdateToast: showUpdateToast });
+	};
+
+	const toggleShowUsername = async () => {
+		showUsername = !showUsername;
+		saveSettings({ showUsername: showUsername });
+	};
+
+	const toggleEmojiInCall = async () => {
+		showEmojiInCall = !showEmojiInCall;
+		saveSettings({ showEmojiInCall: showEmojiInCall });
+	};
+
+	const toggleVoiceInterruption = async () => {
+		voiceInterruption = !voiceInterruption;
+		saveSettings({ voiceInterruption: voiceInterruption });
+	};
+
+	const toggleHapticFeedback = async () => {
+		hapticFeedback = !hapticFeedback;
+		saveSettings({ hapticFeedback: hapticFeedback });
+	};
+
+	const toggleUserLocation = async () => {
+		userLocation = !userLocation;
+
+		if (userLocation) {
+			const position = await getUserPosition().catch((error) => {
+				toast.error(error.message);
+				return null;
+			});
+
+			if (position) {
+				await updateUserInfo(localStorage.token, { location: position });
+				toast.success($i18n.t('User location successfully retrieved.'));
+			} else {
+				userLocation = false;
+			}
+		}
+
+		saveSettings({ userLocation });
+	};
+
+	const toggleTitleAutoGenerate = async () => {
+		titleAutoGenerate = !titleAutoGenerate;
+		saveSettings({
+			title: {
+				...$settings.title,
+				auto: titleAutoGenerate
+			}
+		});
+	};
+
+	const toggleAutoTags = async () => {
+		autoTags = !autoTags;
+		saveSettings({ autoTags });
+	};
+
+	const toggleRichTextInput = async () => {
+		richTextInput = !richTextInput;
+		saveSettings({ richTextInput });
+	};
+
+	const toggleResponseAutoCopy = async () => {
+		const permission = await navigator.clipboard
+			.readText()
+			.then(() => {
+				return 'granted';
+			})
+			.catch(() => {
+				return '';
+			});
+
+		console.log(permission);
+
+		if (permission === 'granted') {
+			responseAutoCopy = !responseAutoCopy;
+			saveSettings({ responseAutoCopy: responseAutoCopy });
+		} else {
+			toast.error(
+				$i18n.t(
+					'Clipboard write permission denied. Please check your browser settings to grant the necessary access.'
+				)
+			);
+		}
+	};
+
+	const toggleChangeChatDirection = async () => {
+		chatDirection = chatDirection === 'LTR' ? 'RTL' : 'LTR';
+		saveSettings({ chatDirection });
+	};
+
+	const updateInterfaceHandler = async () => {
+		saveSettings({
+			models: [defaultModelId]
+		});
+	};
+
+	onMount(async () => {
+		titleAutoGenerate = $settings?.title?.auto ?? true;
+		autoTags = $settings.autoTags ?? true;
+
+		responseAutoCopy = $settings.responseAutoCopy ?? false;
+
+		showUsername = $settings.showUsername ?? false;
+		showUpdateToast = $settings.showUpdateToast ?? true;
+
+		showEmojiInCall = $settings.showEmojiInCall ?? false;
+		voiceInterruption = $settings.voiceInterruption ?? false;
+
+		richTextInput = $settings.richTextInput ?? true;
+		landingPageMode = $settings.landingPageMode ?? '';
+		chatBubble = $settings.chatBubble ?? true;
+		widescreenMode = $settings.widescreenMode ?? false;
+		splitLargeChunks = $settings.splitLargeChunks ?? false;
+		scrollOnBranchChange = $settings.scrollOnBranchChange ?? true;
+		chatDirection = $settings.chatDirection ?? 'LTR';
+		userLocation = $settings.userLocation ?? false;
+
+		hapticFeedback = $settings.hapticFeedback ?? false;
+
+		defaultModelId = $settings?.models?.at(0) ?? '';
+		if ($config?.default_models) {
+			defaultModelId = $config.default_models.split(',')[0];
+		}
+
+		backgroundImageUrl = $settings.backgroundImageUrl ?? null;
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={() => {
+		updateInterfaceHandler();
+		dispatch('save');
+	}}
+>
+	<input
+		bind:this={filesInputElement}
+		bind:files={inputFiles}
+		type="file"
+		hidden
+		accept="image/*"
+		on:change={() => {
+			let reader = new FileReader();
+			reader.onload = (event) => {
+				let originalImageUrl = `${event.target.result}`;
+
+				backgroundImageUrl = originalImageUrl;
+				saveSettings({ backgroundImageUrl });
+			};
+
+			if (
+				inputFiles &&
+				inputFiles.length > 0 &&
+				['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
+			) {
+				reader.readAsDataURL(inputFiles[0]);
+			} else {
+				console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
+				inputFiles = null;
+			}
+		}}
+	/>
+
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem] scrollbar-hidden">
+		<div class=" space-y-1 mb-3">
+			<div class="mb-2">
+				<div class="flex justify-between items-center text-xs">
+					<div class=" text-sm font-medium">{$i18n.t('Default Model')}</div>
+				</div>
+			</div>
+
+			<div class="flex-1 mr-2">
+				<select
+					class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+					bind:value={defaultModelId}
+					placeholder="Select a model"
+				>
+					<option value="" disabled selected>{$i18n.t('Select a model')}</option>
+					{#each $models.filter((model) => model.id) as model}
+						<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
+					{/each}
+				</select>
+			</div>
+		</div>
+		<hr class=" dark:border-gray-850" />
+
+		<div>
+			<div class=" mb-1.5 text-sm font-medium">{$i18n.t('UI')}</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">{$i18n.t('Landing Page Mode')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleLandingPageMode();
+						}}
+						type="button"
+					>
+						{#if landingPageMode === ''}
+							<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Chat')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">{$i18n.t('Chat Bubble UI')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleChatBubble();
+						}}
+						type="button"
+					>
+						{#if chatBubble === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			{#if !$settings.chatBubble}
+				<div>
+					<div class=" py-0.5 flex w-full justify-between">
+						<div class=" self-center text-xs">
+							{$i18n.t('Display the username instead of You in the Chat')}
+						</div>
+
+						<button
+							class="p-1 px-3 text-xs flex rounded transition"
+							on:click={() => {
+								toggleShowUsername();
+							}}
+							type="button"
+						>
+							{#if showUsername === true}
+								<span class="ml-2 self-center">{$i18n.t('On')}</span>
+							{:else}
+								<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+							{/if}
+						</button>
+					</div>
+				</div>
+			{/if}
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">{$i18n.t('Widescreen Mode')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleWidescreenMode();
+						}}
+						type="button"
+					>
+						{#if widescreenMode === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">{$i18n.t('Chat direction')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={toggleChangeChatDirection}
+						type="button"
+					>
+						{#if chatDirection === 'LTR'}
+							<span class="ml-2 self-center">{$i18n.t('LTR')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('RTL')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			{#if $user.role === 'admin'}
+				<div>
+					<div class=" py-0.5 flex w-full justify-between">
+						<div class=" self-center text-xs">
+							{$i18n.t('Toast notifications for new updates')}
+						</div>
+
+						<button
+							class="p-1 px-3 text-xs flex rounded transition"
+							on:click={() => {
+								toggleShowUpdateToast();
+							}}
+							type="button"
+						>
+							{#if showUpdateToast === true}
+								<span class="ml-2 self-center">{$i18n.t('On')}</span>
+							{:else}
+								<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+							{/if}
+						</button>
+					</div>
+				</div>
+			{/if}
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">
+						{$i18n.t('Fluidly stream large external response chunks')}
+					</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleSplitLargeChunks();
+						}}
+						type="button"
+					>
+						{#if splitLargeChunks === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">
+						{$i18n.t('Scroll to bottom when switching between branches')}
+					</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							togglesScrollOnBranchChange();
+						}}
+						type="button"
+					>
+						{#if scrollOnBranchChange === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">
+						{$i18n.t('Rich Text Input for Chat')}
+					</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleRichTextInput();
+						}}
+						type="button"
+					>
+						{#if richTextInput === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">
+						{$i18n.t('Chat Background Image')}
+					</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							if (backgroundImageUrl !== null) {
+								backgroundImageUrl = null;
+								saveSettings({ backgroundImageUrl });
+							} else {
+								filesInputElement.click();
+							}
+						}}
+						type="button"
+					>
+						{#if backgroundImageUrl !== null}
+							<span class="ml-2 self-center">{$i18n.t('Reset')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Upload')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div class=" my-1.5 text-sm font-medium">{$i18n.t('Chat')}</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">{$i18n.t('Title Auto-Generation')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleTitleAutoGenerate();
+						}}
+						type="button"
+					>
+						{#if titleAutoGenerate === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">{$i18n.t('Chat Tags Auto-Generation')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleAutoTags();
+						}}
+						type="button"
+					>
+						{#if autoTags === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">
+						{$i18n.t('Response AutoCopy to Clipboard')}
+					</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleResponseAutoCopy();
+						}}
+						type="button"
+					>
+						{#if responseAutoCopy === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">{$i18n.t('Allow User Location')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleUserLocation();
+						}}
+						type="button"
+					>
+						{#if userLocation === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">{$i18n.t('Haptic Feedback')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleHapticFeedback();
+						}}
+						type="button"
+					>
+						{#if hapticFeedback === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div class=" my-1.5 text-sm font-medium">{$i18n.t('Voice')}</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">{$i18n.t('Allow Voice Interruption in Call')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleVoiceInterruption();
+						}}
+						type="button"
+					>
+						{#if voiceInterruption === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">{$i18n.t('Display Emoji in Call')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleEmojiInCall();
+						}}
+						type="button"
+					>
+						{#if showEmojiInCall === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+		</div>
+	</div>
+
+	<div class="flex justify-end text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/chat/Settings/Models.svelte b/src/lib/components/chat/Settings/Models.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..3a81effe3838b859c257f357e51af294aac5a167
--- /dev/null
+++ b/src/lib/components/chat/Settings/Models.svelte
@@ -0,0 +1,1075 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+
+	import {
+		createModel,
+		deleteModel,
+		downloadModel,
+		getOllamaUrls,
+		getOllamaVersion,
+		pullModel,
+		uploadModel,
+		getOllamaConfig
+	} from '$lib/apis/ollama';
+
+	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
+	import { WEBUI_NAME, models, MODEL_DOWNLOAD_POOL, user, config } from '$lib/stores';
+	import { splitStream } from '$lib/utils';
+	import { onMount, getContext } from 'svelte';
+
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let getModels: Function;
+
+	let modelUploadInputElement: HTMLInputElement;
+
+	// Models
+
+	let ollamaEnabled = null;
+
+	let OLLAMA_URLS = [];
+	let selectedOllamaUrlIdx: number | null = null;
+
+	let updateModelId = null;
+	let updateProgress = null;
+
+	let showExperimentalOllama = false;
+
+	let ollamaVersion = null;
+	const MAX_PARALLEL_DOWNLOADS = 3;
+
+	let modelTransferring = false;
+	let modelTag = '';
+
+	let createModelLoading = false;
+	let createModelTag = '';
+	let createModelContent = '';
+	let createModelDigest = '';
+	let createModelPullProgress = null;
+
+	let digest = '';
+	let pullProgress = null;
+
+	let modelUploadMode = 'file';
+	let modelInputFile: File[] | null = null;
+	let modelFileUrl = '';
+	let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`;
+	let modelFileDigest = '';
+
+	let uploadProgress = null;
+	let uploadMessage = '';
+
+	let deleteModelTag = '';
+
+	const updateModelsHandler = async () => {
+		for (const model of $models.filter(
+			(m) =>
+				!(m?.preset ?? false) &&
+				m.owned_by === 'ollama' &&
+				(selectedOllamaUrlIdx === null
+					? true
+					: (m?.ollama?.urls ?? []).includes(selectedOllamaUrlIdx))
+		)) {
+			console.log(model);
+
+			updateModelId = model.id;
+			const [res, controller] = await pullModel(
+				localStorage.token,
+				model.id,
+				selectedOllamaUrlIdx
+			).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+
+			if (res) {
+				const reader = res.body
+					.pipeThrough(new TextDecoderStream())
+					.pipeThrough(splitStream('\n'))
+					.getReader();
+
+				while (true) {
+					try {
+						const { value, done } = await reader.read();
+						if (done) break;
+
+						let lines = value.split('\n');
+
+						for (const line of lines) {
+							if (line !== '') {
+								let data = JSON.parse(line);
+
+								console.log(data);
+								if (data.error) {
+									throw data.error;
+								}
+								if (data.detail) {
+									throw data.detail;
+								}
+								if (data.status) {
+									if (data.digest) {
+										updateProgress = 0;
+										if (data.completed) {
+											updateProgress = Math.round((data.completed / data.total) * 1000) / 10;
+										} else {
+											updateProgress = 100;
+										}
+									} else {
+										toast.success(data.status);
+									}
+								}
+							}
+						}
+					} catch (error) {
+						console.log(error);
+					}
+				}
+			}
+		}
+
+		updateModelId = null;
+		updateProgress = null;
+	};
+
+	const pullModelHandler = async () => {
+		const sanitizedModelTag = modelTag.trim().replace(/^ollama\s+(run|pull)\s+/, '');
+		console.log($MODEL_DOWNLOAD_POOL);
+		if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) {
+			toast.error(
+				$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, {
+					modelTag: sanitizedModelTag
+				})
+			);
+			return;
+		}
+		if (Object.keys($MODEL_DOWNLOAD_POOL).length === MAX_PARALLEL_DOWNLOADS) {
+			toast.error(
+				$i18n.t('Maximum of 3 models can be downloaded simultaneously. Please try again later.')
+			);
+			return;
+		}
+
+		const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, '0').catch(
+			(error) => {
+				toast.error(error);
+				return null;
+			}
+		);
+
+		if (res) {
+			const reader = res.body
+				.pipeThrough(new TextDecoderStream())
+				.pipeThrough(splitStream('\n'))
+				.getReader();
+
+			MODEL_DOWNLOAD_POOL.set({
+				...$MODEL_DOWNLOAD_POOL,
+				[sanitizedModelTag]: {
+					...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
+					abortController: controller,
+					reader,
+					done: false
+				}
+			});
+
+			while (true) {
+				try {
+					const { value, done } = await reader.read();
+					if (done) break;
+
+					let lines = value.split('\n');
+
+					for (const line of lines) {
+						if (line !== '') {
+							let data = JSON.parse(line);
+							console.log(data);
+							if (data.error) {
+								throw data.error;
+							}
+							if (data.detail) {
+								throw data.detail;
+							}
+
+							if (data.status) {
+								if (data.digest) {
+									let downloadProgress = 0;
+									if (data.completed) {
+										downloadProgress = Math.round((data.completed / data.total) * 1000) / 10;
+									} else {
+										downloadProgress = 100;
+									}
+
+									MODEL_DOWNLOAD_POOL.set({
+										...$MODEL_DOWNLOAD_POOL,
+										[sanitizedModelTag]: {
+											...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
+											pullProgress: downloadProgress,
+											digest: data.digest
+										}
+									});
+								} else {
+									toast.success(data.status);
+
+									MODEL_DOWNLOAD_POOL.set({
+										...$MODEL_DOWNLOAD_POOL,
+										[sanitizedModelTag]: {
+											...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
+											done: data.status === 'success'
+										}
+									});
+								}
+							}
+						}
+					}
+				} catch (error) {
+					console.log(error);
+					if (typeof error !== 'string') {
+						error = error.message;
+					}
+
+					toast.error(error);
+					// opts.callback({ success: false, error, modelName: opts.modelName });
+				}
+			}
+
+			console.log($MODEL_DOWNLOAD_POOL[sanitizedModelTag]);
+
+			if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag].done) {
+				toast.success(
+					$i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, {
+						modelName: sanitizedModelTag
+					})
+				);
+
+				models.set(await getModels(localStorage.token));
+			} else {
+				toast.error($i18n.t('Download canceled'));
+			}
+
+			delete $MODEL_DOWNLOAD_POOL[sanitizedModelTag];
+
+			MODEL_DOWNLOAD_POOL.set({
+				...$MODEL_DOWNLOAD_POOL
+			});
+		}
+
+		modelTag = '';
+		modelTransferring = false;
+	};
+
+	const uploadModelHandler = async () => {
+		modelTransferring = true;
+
+		let uploaded = false;
+		let fileResponse = null;
+		let name = '';
+
+		if (modelUploadMode === 'file') {
+			const file = modelInputFile ? modelInputFile[0] : null;
+
+			if (file) {
+				uploadMessage = 'Uploading...';
+
+				fileResponse = await uploadModel(localStorage.token, file, selectedOllamaUrlIdx).catch(
+					(error) => {
+						toast.error(error);
+						return null;
+					}
+				);
+			}
+		} else {
+			uploadProgress = 0;
+			fileResponse = await downloadModel(
+				localStorage.token,
+				modelFileUrl,
+				selectedOllamaUrlIdx
+			).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+		}
+
+		if (fileResponse && fileResponse.ok) {
+			const reader = fileResponse.body
+				.pipeThrough(new TextDecoderStream())
+				.pipeThrough(splitStream('\n'))
+				.getReader();
+
+			while (true) {
+				const { value, done } = await reader.read();
+				if (done) break;
+
+				try {
+					let lines = value.split('\n');
+
+					for (const line of lines) {
+						if (line !== '') {
+							let data = JSON.parse(line.replace(/^data: /, ''));
+
+							if (data.progress) {
+								if (uploadMessage) {
+									uploadMessage = '';
+								}
+								uploadProgress = data.progress;
+							}
+
+							if (data.error) {
+								throw data.error;
+							}
+
+							if (data.done) {
+								modelFileDigest = data.blob;
+								name = data.name;
+								uploaded = true;
+							}
+						}
+					}
+				} catch (error) {
+					console.log(error);
+				}
+			}
+		} else {
+			const error = await fileResponse?.json();
+			toast.error(error?.detail ?? error);
+		}
+
+		if (uploaded) {
+			const res = await createModel(
+				localStorage.token,
+				`${name}:latest`,
+				`FROM @${modelFileDigest}\n${modelFileContent}`
+			);
+
+			if (res && res.ok) {
+				const reader = res.body
+					.pipeThrough(new TextDecoderStream())
+					.pipeThrough(splitStream('\n'))
+					.getReader();
+
+				while (true) {
+					const { value, done } = await reader.read();
+					if (done) break;
+
+					try {
+						let lines = value.split('\n');
+
+						for (const line of lines) {
+							if (line !== '') {
+								console.log(line);
+								let data = JSON.parse(line);
+								console.log(data);
+
+								if (data.error) {
+									throw data.error;
+								}
+								if (data.detail) {
+									throw data.detail;
+								}
+
+								if (data.status) {
+									if (
+										!data.digest &&
+										!data.status.includes('writing') &&
+										!data.status.includes('sha256')
+									) {
+										toast.success(data.status);
+									} else {
+										if (data.digest) {
+											digest = data.digest;
+
+											if (data.completed) {
+												pullProgress = Math.round((data.completed / data.total) * 1000) / 10;
+											} else {
+												pullProgress = 100;
+											}
+										}
+									}
+								}
+							}
+						}
+					} catch (error) {
+						console.log(error);
+						toast.error(error);
+					}
+				}
+			}
+		}
+
+		modelFileUrl = '';
+
+		if (modelUploadInputElement) {
+			modelUploadInputElement.value = '';
+		}
+		modelInputFile = null;
+		modelTransferring = false;
+		uploadProgress = null;
+
+		models.set(await getModels());
+	};
+
+	const deleteModelHandler = async () => {
+		const res = await deleteModel(localStorage.token, deleteModelTag, selectedOllamaUrlIdx).catch(
+			(error) => {
+				toast.error(error);
+			}
+		);
+
+		if (res) {
+			toast.success($i18n.t(`Deleted {{deleteModelTag}}`, { deleteModelTag }));
+		}
+
+		deleteModelTag = '';
+		models.set(await getModels());
+	};
+
+	const cancelModelPullHandler = async (model: string) => {
+		const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model];
+		if (abortController) {
+			abortController.abort();
+		}
+		if (reader) {
+			await reader.cancel();
+			delete $MODEL_DOWNLOAD_POOL[model];
+			MODEL_DOWNLOAD_POOL.set({
+				...$MODEL_DOWNLOAD_POOL
+			});
+			await deleteModel(localStorage.token, model);
+			toast.success(`${model} download has been canceled`);
+		}
+	};
+
+	const createModelHandler = async () => {
+		createModelLoading = true;
+		const res = await createModel(
+			localStorage.token,
+			createModelTag,
+			createModelContent,
+			selectedOllamaUrlIdx
+		).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res && res.ok) {
+			const reader = res.body
+				.pipeThrough(new TextDecoderStream())
+				.pipeThrough(splitStream('\n'))
+				.getReader();
+
+			while (true) {
+				const { value, done } = await reader.read();
+				if (done) break;
+
+				try {
+					let lines = value.split('\n');
+
+					for (const line of lines) {
+						if (line !== '') {
+							console.log(line);
+							let data = JSON.parse(line);
+							console.log(data);
+
+							if (data.error) {
+								throw data.error;
+							}
+							if (data.detail) {
+								throw data.detail;
+							}
+
+							if (data.status) {
+								if (
+									!data.digest &&
+									!data.status.includes('writing') &&
+									!data.status.includes('sha256')
+								) {
+									toast.success(data.status);
+								} else {
+									if (data.digest) {
+										createModelDigest = data.digest;
+
+										if (data.completed) {
+											createModelPullProgress =
+												Math.round((data.completed / data.total) * 1000) / 10;
+										} else {
+											createModelPullProgress = 100;
+										}
+									}
+								}
+							}
+						}
+					}
+				} catch (error) {
+					console.log(error);
+					toast.error(error);
+				}
+			}
+		}
+
+		models.set(await getModels());
+
+		createModelLoading = false;
+
+		createModelTag = '';
+		createModelContent = '';
+		createModelDigest = '';
+		createModelPullProgress = null;
+	};
+
+	onMount(async () => {
+		const ollamaConfig = await getOllamaConfig(localStorage.token);
+
+		if (ollamaConfig.ENABLE_OLLAMA_API) {
+			ollamaEnabled = true;
+
+			await Promise.all([
+				(async () => {
+					OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
+						toast.error(error);
+						return [];
+					});
+
+					if (OLLAMA_URLS.length > 0) {
+						selectedOllamaUrlIdx = 0;
+					}
+				})(),
+				(async () => {
+					ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
+				})()
+			]);
+		} else {
+			ollamaEnabled = false;
+			toast.error($i18n.t('Ollama API is disabled'));
+		}
+	});
+</script>
+
+<div class="flex flex-col h-full justify-between text-sm">
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[27rem]">
+		{#if ollamaEnabled}
+			{#if ollamaVersion !== null}
+				<div class="space-y-2 pr-1.5">
+					<div class="text-sm font-medium">{$i18n.t('Manage Ollama Models')}</div>
+
+					{#if OLLAMA_URLS.length > 0}
+						<div class="flex gap-2">
+							<div class="flex-1 pb-1">
+								<select
+									class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+									bind:value={selectedOllamaUrlIdx}
+									placeholder={$i18n.t('Select an Ollama instance')}
+								>
+									{#each OLLAMA_URLS as url, idx}
+										<option value={idx} class="bg-gray-100 dark:bg-gray-700">{url}</option>
+									{/each}
+								</select>
+							</div>
+
+							<div>
+								<div class="flex w-full justify-end">
+									<Tooltip content="Update All Models" placement="top">
+										<button
+											class="p-2.5 flex gap-2 items-center bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+											on:click={() => {
+												updateModelsHandler();
+											}}
+										>
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												viewBox="0 0 16 16"
+												fill="currentColor"
+												class="w-4 h-4"
+											>
+												<path
+													d="M7 1a.75.75 0 0 1 .75.75V6h-1.5V1.75A.75.75 0 0 1 7 1ZM6.25 6v2.94L5.03 7.72a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06L7.75 8.94V6H10a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.25Z"
+												/>
+												<path
+													d="M4.268 14A2 2 0 0 0 6 15h6a2 2 0 0 0 2-2v-3a2 2 0 0 0-1-1.732V11a3 3 0 0 1-3 3H4.268Z"
+												/>
+											</svg>
+										</button>
+									</Tooltip>
+								</div>
+							</div>
+						</div>
+
+						{#if updateModelId}
+							Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''}
+						{/if}
+					{/if}
+
+					<div class="space-y-2">
+						<div>
+							<div class=" mb-2 text-sm font-medium">{$i18n.t('Pull a model from Ollama.com')}</div>
+							<div class="flex w-full">
+								<div class="flex-1 mr-2">
+									<input
+										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+										placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
+											modelTag: 'mistral:7b'
+										})}
+										bind:value={modelTag}
+									/>
+								</div>
+								<button
+									class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+									on:click={() => {
+										pullModelHandler();
+									}}
+									disabled={modelTransferring}
+								>
+									{#if modelTransferring}
+										<div class="self-center">
+											<svg
+												class=" w-4 h-4"
+												viewBox="0 0 24 24"
+												fill="currentColor"
+												xmlns="http://www.w3.org/2000/svg"
+											>
+												<style>
+													.spinner_ajPY {
+														transform-origin: center;
+														animation: spinner_AtaB 0.75s infinite linear;
+													}
+
+													@keyframes spinner_AtaB {
+														100% {
+															transform: rotate(360deg);
+														}
+													}
+												</style>
+												<path
+													d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+													opacity=".25"
+												/>
+												<path
+													d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+													class="spinner_ajPY"
+												/>
+											</svg>
+										</div>
+									{:else}
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											viewBox="0 0 16 16"
+											fill="currentColor"
+											class="w-4 h-4"
+										>
+											<path
+												d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
+											/>
+											<path
+												d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+											/>
+										</svg>
+									{/if}
+								</button>
+							</div>
+
+							<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
+								{$i18n.t('To access the available model names for downloading,')}
+								<a
+									class=" text-gray-500 dark:text-gray-300 font-medium underline"
+									href="https://ollama.com/library"
+									target="_blank">{$i18n.t('click here.')}</a
+								>
+							</div>
+
+							{#if Object.keys($MODEL_DOWNLOAD_POOL).length > 0}
+								{#each Object.keys($MODEL_DOWNLOAD_POOL) as model}
+									{#if 'pullProgress' in $MODEL_DOWNLOAD_POOL[model]}
+										<div class="flex flex-col">
+											<div class="font-medium mb-1">{model}</div>
+											<div class="">
+												<div class="flex flex-row justify-between space-x-4 pr-2">
+													<div class=" flex-1">
+														<div
+															class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
+															style="width: {Math.max(
+																15,
+																$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0
+															)}%"
+														>
+															{$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0}%
+														</div>
+													</div>
+
+													<Tooltip content={$i18n.t('Cancel')}>
+														<button
+															class="text-gray-800 dark:text-gray-100"
+															on:click={() => {
+																cancelModelPullHandler(model);
+															}}
+														>
+															<svg
+																class="w-4 h-4 text-gray-800 dark:text-white"
+																aria-hidden="true"
+																xmlns="http://www.w3.org/2000/svg"
+																width="24"
+																height="24"
+																fill="currentColor"
+																viewBox="0 0 24 24"
+															>
+																<path
+																	stroke="currentColor"
+																	stroke-linecap="round"
+																	stroke-linejoin="round"
+																	stroke-width="2"
+																	d="M6 18 17.94 6M18 18 6.06 6"
+																/>
+															</svg>
+														</button>
+													</Tooltip>
+												</div>
+												{#if 'digest' in $MODEL_DOWNLOAD_POOL[model]}
+													<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+														{$MODEL_DOWNLOAD_POOL[model].digest}
+													</div>
+												{/if}
+											</div>
+										</div>
+									{/if}
+								{/each}
+							{/if}
+						</div>
+
+						<div>
+							<div class=" mb-2 text-sm font-medium">{$i18n.t('Delete a model')}</div>
+							<div class="flex w-full">
+								<div class="flex-1 mr-2">
+									<select
+										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+										bind:value={deleteModelTag}
+										placeholder={$i18n.t('Select a model')}
+									>
+										{#if !deleteModelTag}
+											<option value="" disabled selected>{$i18n.t('Select a model')}</option>
+										{/if}
+										{#each $models.filter((m) => !(m?.preset ?? false) && m.owned_by === 'ollama' && (selectedOllamaUrlIdx === null ? true : (m?.ollama?.urls ?? []).includes(selectedOllamaUrlIdx))) as model}
+											<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
+												>{model.name +
+													' (' +
+													(model.ollama.size / 1024 ** 3).toFixed(1) +
+													' GB)'}</option
+											>
+										{/each}
+									</select>
+								</div>
+								<button
+									class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
+									on:click={() => {
+										deleteModelHandler();
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 16 16"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											fill-rule="evenodd"
+											d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
+											clip-rule="evenodd"
+										/>
+									</svg>
+								</button>
+							</div>
+						</div>
+
+						<div>
+							<div class=" mb-2 text-sm font-medium">{$i18n.t('Create a model')}</div>
+							<div class="flex w-full">
+								<div class="flex-1 mr-2 flex flex-col gap-2">
+									<input
+										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+										placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
+											modelTag: 'my-modelfile'
+										})}
+										bind:value={createModelTag}
+										disabled={createModelLoading}
+									/>
+
+									<textarea
+										bind:value={createModelContent}
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-100 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none scrollbar-hidden"
+										rows="6"
+										placeholder={`TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`}
+										disabled={createModelLoading}
+									/>
+								</div>
+
+								<div class="flex self-start">
+									<button
+										class="px-2.5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition disabled:cursor-not-allowed"
+										on:click={() => {
+											createModelHandler();
+										}}
+										disabled={createModelLoading}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											viewBox="0 0 16 16"
+											fill="currentColor"
+											class="size-4"
+										>
+											<path
+												d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
+											/>
+											<path
+												d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+											/>
+										</svg>
+									</button>
+								</div>
+							</div>
+
+							{#if createModelDigest !== ''}
+								<div class="flex flex-col mt-1">
+									<div class="font-medium mb-1">{createModelTag}</div>
+									<div class="">
+										<div class="flex flex-row justify-between space-x-4 pr-2">
+											<div class=" flex-1">
+												<div
+													class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
+													style="width: {Math.max(15, createModelPullProgress ?? 0)}%"
+												>
+													{createModelPullProgress ?? 0}%
+												</div>
+											</div>
+										</div>
+										{#if createModelDigest}
+											<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+												{createModelDigest}
+											</div>
+										{/if}
+									</div>
+								</div>
+							{/if}
+						</div>
+
+						<div class="pt-1">
+							<div class="flex justify-between items-center text-xs">
+								<div class=" text-sm font-medium">{$i18n.t('Experimental')}</div>
+								<button
+									class=" text-xs font-medium text-gray-500"
+									type="button"
+									on:click={() => {
+										showExperimentalOllama = !showExperimentalOllama;
+									}}>{showExperimentalOllama ? $i18n.t('Hide') : $i18n.t('Show')}</button
+								>
+							</div>
+						</div>
+
+						{#if showExperimentalOllama}
+							<form
+								on:submit|preventDefault={() => {
+									uploadModelHandler();
+								}}
+							>
+								<div class=" mb-2 flex w-full justify-between">
+									<div class="  text-sm font-medium">{$i18n.t('Upload a GGUF model')}</div>
+
+									<button
+										class="p-1 px-3 text-xs flex rounded transition"
+										on:click={() => {
+											if (modelUploadMode === 'file') {
+												modelUploadMode = 'url';
+											} else {
+												modelUploadMode = 'file';
+											}
+										}}
+										type="button"
+									>
+										{#if modelUploadMode === 'file'}
+											<span class="ml-2 self-center">{$i18n.t('File Mode')}</span>
+										{:else}
+											<span class="ml-2 self-center">{$i18n.t('URL Mode')}</span>
+										{/if}
+									</button>
+								</div>
+
+								<div class="flex w-full mb-1.5">
+									<div class="flex flex-col w-full">
+										{#if modelUploadMode === 'file'}
+											<div
+												class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}"
+											>
+												<input
+													id="model-upload-input"
+													bind:this={modelUploadInputElement}
+													type="file"
+													bind:files={modelInputFile}
+													on:change={() => {
+														console.log(modelInputFile);
+													}}
+													accept=".gguf,.safetensors"
+													required
+													hidden
+												/>
+
+												<button
+													type="button"
+													class="w-full rounded-lg text-left py-2 px-4 bg-white dark:text-gray-300 dark:bg-gray-850"
+													on:click={() => {
+														modelUploadInputElement.click();
+													}}
+												>
+													{#if modelInputFile && modelInputFile.length > 0}
+														{modelInputFile[0].name}
+													{:else}
+														{$i18n.t('Click here to select')}
+													{/if}
+												</button>
+											</div>
+										{:else}
+											<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
+												<input
+													class="w-full rounded-lg text-left py-2 px-4 bg-white dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
+													''
+														? 'mr-2'
+														: ''}"
+													type="url"
+													required
+													bind:value={modelFileUrl}
+													placeholder={$i18n.t('Type Hugging Face Resolve (Download) URL')}
+												/>
+											</div>
+										{/if}
+									</div>
+
+									{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
+										<button
+											class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg disabled:cursor-not-allowed transition"
+											type="submit"
+											disabled={modelTransferring}
+										>
+											{#if modelTransferring}
+												<div class="self-center">
+													<svg
+														class=" w-4 h-4"
+														viewBox="0 0 24 24"
+														fill="currentColor"
+														xmlns="http://www.w3.org/2000/svg"
+													>
+														<style>
+															.spinner_ajPY {
+																transform-origin: center;
+																animation: spinner_AtaB 0.75s infinite linear;
+															}
+
+															@keyframes spinner_AtaB {
+																100% {
+																	transform: rotate(360deg);
+																}
+															}
+														</style>
+														<path
+															d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+															opacity=".25"
+														/>
+														<path
+															d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+															class="spinner_ajPY"
+														/>
+													</svg>
+												</div>
+											{:else}
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
+													/>
+													<path
+														d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+													/>
+												</svg>
+											{/if}
+										</button>
+									{/if}
+								</div>
+
+								{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
+									<div>
+										<div>
+											<div class=" my-2.5 text-sm font-medium">{$i18n.t('Modelfile Content')}</div>
+											<textarea
+												bind:value={modelFileContent}
+												class="w-full rounded-lg py-2 px-4 text-sm bg-gray-100 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none"
+												rows="6"
+											/>
+										</div>
+									</div>
+								{/if}
+								<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
+									{$i18n.t('To access the GGUF models available for downloading,')}
+									<a
+										class=" text-gray-500 dark:text-gray-300 font-medium underline"
+										href="https://huggingface.co/models?search=gguf"
+										target="_blank">{$i18n.t('click here.')}</a
+									>
+								</div>
+
+								{#if uploadMessage}
+									<div class="mt-2">
+										<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
+
+										<div class="w-full rounded-full dark:bg-gray-800">
+											<div
+												class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
+												style="width: 100%"
+											>
+												{uploadMessage}
+											</div>
+										</div>
+										<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+											{modelFileDigest}
+										</div>
+									</div>
+								{:else if uploadProgress !== null}
+									<div class="mt-2">
+										<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
+
+										<div class="w-full rounded-full dark:bg-gray-800">
+											<div
+												class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
+												style="width: {Math.max(15, uploadProgress ?? 0)}%"
+											>
+												{uploadProgress ?? 0}%
+											</div>
+										</div>
+										<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+											{modelFileDigest}
+										</div>
+									</div>
+								{/if}
+							</form>
+						{/if}
+					</div>
+				</div>
+			{:else if ollamaVersion === false}
+				<div>Ollama Not Detected</div>
+			{:else}
+				<div class="flex h-full justify-center">
+					<div class="my-auto">
+						<Spinner className="size-6" />
+					</div>
+				</div>
+			{/if}
+		{:else if ollamaEnabled === false}
+			<div>{$i18n.t('Ollama API is disabled')}</div>
+		{:else}
+			<div class="flex h-full justify-center">
+				<div class="my-auto">
+					<Spinner className="size-6" />
+				</div>
+			</div>
+		{/if}
+	</div>
+</div>
diff --git a/src/lib/components/chat/Settings/Personalization.svelte b/src/lib/components/chat/Settings/Personalization.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d728218c2cd4c627094160f8637d05e801ba192d
--- /dev/null
+++ b/src/lib/components/chat/Settings/Personalization.svelte
@@ -0,0 +1,98 @@
+<script lang="ts">
+	import { getBackendConfig } from '$lib/apis';
+	import { setDefaultPromptSuggestions } from '$lib/apis/configs';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import { config, models, settings, user } from '$lib/stores';
+	import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
+	import { toast } from 'svelte-sonner';
+	import ManageModal from './Personalization/ManageModal.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	const dispatch = createEventDispatcher();
+
+	const i18n = getContext('i18n');
+
+	export let saveSettings: Function;
+
+	let showManageModal = false;
+
+	// Addons
+	let enableMemory = false;
+
+	onMount(async () => {
+		enableMemory = $settings?.memory ?? false;
+	});
+</script>
+
+<ManageModal bind:show={showManageModal} />
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={() => {
+		dispatch('save');
+	}}
+>
+	<div class="  pr-1.5 py-1 overflow-y-scroll max-h-[25rem]">
+		<div>
+			<div class="flex items-center justify-between mb-1">
+				<Tooltip
+					content={$i18n.t(
+						'This is an experimental feature, it may not function as expected and is subject to change at any time.'
+					)}
+				>
+					<div class="text-sm font-medium">
+						{$i18n.t('Memory')}
+
+						<span class=" text-xs text-gray-500">({$i18n.t('Experimental')})</span>
+					</div>
+				</Tooltip>
+
+				<div class="">
+					<Switch
+						bind:state={enableMemory}
+						on:change={async () => {
+							saveSettings({ memory: enableMemory });
+						}}
+					/>
+				</div>
+			</div>
+		</div>
+
+		<div class="text-xs text-gray-600 dark:text-gray-400">
+			<div>
+				{$i18n.t(
+					"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you."
+				)}
+			</div>
+
+			<!-- <div class="mt-3">
+				To understand what LLM remembers or teach it something new, just chat with it:
+
+				<div>- “Remember that I like concise responses.”</div>
+				<div>- “I just got a puppy!”</div>
+				<div>- “What do you remember about me?”</div>
+				<div>- “Where did we leave off on my last project?”</div>
+			</div> -->
+		</div>
+
+		<div class="mt-3 mb-1 ml-1">
+			<button
+				type="button"
+				class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
+				on:click={() => {
+					showManageModal = true;
+				}}
+			>
+				{$i18n.t('Manage')}
+			</button>
+		</div>
+	</div>
+
+	<div class="flex justify-end text-sm font-medium">
+		<button
+			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+			type="submit"
+		>
+			{$i18n.t('Save')}
+		</button>
+	</div>
+</form>
diff --git a/src/lib/components/chat/Settings/Personalization/AddMemoryModal.svelte b/src/lib/components/chat/Settings/Personalization/AddMemoryModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6e16576b0c3b883304bcad5c7c663e0154446ca2
--- /dev/null
+++ b/src/lib/components/chat/Settings/Personalization/AddMemoryModal.svelte
@@ -0,0 +1,126 @@
+<script>
+	import { createEventDispatcher, getContext } from 'svelte';
+
+	import Modal from '$lib/components/common/Modal.svelte';
+	import { addNewMemory, updateMemoryById } from '$lib/apis/memories';
+	import { toast } from 'svelte-sonner';
+
+	const dispatch = createEventDispatcher();
+
+	export let show;
+	const i18n = getContext('i18n');
+
+	let loading = false;
+	let content = '';
+
+	const submitHandler = async () => {
+		loading = true;
+
+		const res = await addNewMemory(localStorage.token, content).catch((error) => {
+			toast.error(error);
+
+			return null;
+		});
+
+		if (res) {
+			console.log(res);
+			toast.success($i18n.t('Memory added successfully'));
+			content = '';
+			show = false;
+			dispatch('save');
+		}
+
+		loading = false;
+	};
+</script>
+
+<Modal bind:show size="sm">
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
+			<div class=" text-lg font-medium self-center">
+				{$i18n.t('Add Memory')}
+			</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full px-5 pb-4 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				<form
+					class="flex flex-col w-full"
+					on:submit|preventDefault={() => {
+						submitHandler();
+					}}
+				>
+					<div class="">
+						<textarea
+							bind:value={content}
+							class=" bg-transparent w-full text-sm resize-none rounded-xl p-3 outline outline-1 outline-gray-100 dark:outline-gray-800"
+							rows="3"
+							placeholder={$i18n.t('Enter a detail about yourself for your LLMs to recall')}
+						/>
+
+						<div class="text-xs text-gray-500">
+							ⓘ {$i18n.t('Refer to yourself as "User" (e.g., "User is learning Spanish")')}
+						</div>
+					</div>
+
+					<div class="flex justify-end pt-1 text-sm font-medium">
+						<button
+							class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-3xl flex flex-row space-x-1 items-center {loading
+								? ' cursor-not-allowed'
+								: ''}"
+							type="submit"
+							disabled={loading}
+						>
+							{$i18n.t('Add')}
+
+							{#if loading}
+								<div class="ml-2 self-center">
+									<svg
+										class=" w-4 h-4"
+										viewBox="0 0 24 24"
+										fill="currentColor"
+										xmlns="http://www.w3.org/2000/svg"
+										><style>
+											.spinner_ajPY {
+												transform-origin: center;
+												animation: spinner_AtaB 0.75s infinite linear;
+											}
+											@keyframes spinner_AtaB {
+												100% {
+													transform: rotate(360deg);
+												}
+											}
+										</style><path
+											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+											opacity=".25"
+										/><path
+											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+											class="spinner_ajPY"
+										/></svg
+									>
+								</div>
+							{/if}
+						</button>
+					</div>
+				</form>
+			</div>
+		</div>
+	</div>
+</Modal>
diff --git a/src/lib/components/chat/Settings/Personalization/EditMemoryModal.svelte b/src/lib/components/chat/Settings/Personalization/EditMemoryModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..773309ff9e12db5d4d53c1714a5819ea6b52786b
--- /dev/null
+++ b/src/lib/components/chat/Settings/Personalization/EditMemoryModal.svelte
@@ -0,0 +1,136 @@
+<script>
+	import { createEventDispatcher, getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+
+	import { updateMemoryById } from '$lib/apis/memories';
+
+	import Modal from '$lib/components/common/Modal.svelte';
+
+	const dispatch = createEventDispatcher();
+
+	export let show;
+	export let memory = {};
+
+	const i18n = getContext('i18n');
+
+	let loading = false;
+	let content = '';
+
+	$: if (show) {
+		setContent();
+	}
+
+	const setContent = () => {
+		content = memory.content;
+	};
+
+	const submitHandler = async () => {
+		loading = true;
+
+		const res = await updateMemoryById(localStorage.token, memory.id, content).catch((error) => {
+			toast.error(error);
+
+			return null;
+		});
+
+		if (res) {
+			console.log(res);
+			toast.success($i18n.t('Memory updated successfully'));
+			dispatch('save');
+			show = false;
+		}
+
+		loading = false;
+	};
+</script>
+
+<Modal bind:show size="sm">
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
+			<div class=" text-lg font-medium self-center">
+				{$i18n.t('Edit Memory')}
+			</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full px-5 pb-4 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				<form
+					class="flex flex-col w-full"
+					on:submit|preventDefault={() => {
+						submitHandler();
+					}}
+				>
+					<div class="">
+						<textarea
+							bind:value={content}
+							class=" bg-transparent w-full text-sm resize-none rounded-xl p-3 outline outline-1 outline-gray-100 dark:outline-gray-800"
+							rows="3"
+							placeholder={$i18n.t('Enter a detail about yourself for your LLMs to recall')}
+						/>
+
+						<div class="text-xs text-gray-500">
+							ⓘ {$i18n.t('Refer to yourself as "User" (e.g., "User is learning Spanish")')}
+						</div>
+					</div>
+
+					<div class="flex justify-end pt-1 text-sm font-medium">
+						<button
+							class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-3xl flex flex-row space-x-1 items-center {loading
+								? ' cursor-not-allowed'
+								: ''}"
+							type="submit"
+							disabled={loading}
+						>
+							{$i18n.t('Update')}
+
+							{#if loading}
+								<div class="ml-2 self-center">
+									<svg
+										class=" w-4 h-4"
+										viewBox="0 0 24 24"
+										fill="currentColor"
+										xmlns="http://www.w3.org/2000/svg"
+										><style>
+											.spinner_ajPY {
+												transform-origin: center;
+												animation: spinner_AtaB 0.75s infinite linear;
+											}
+											@keyframes spinner_AtaB {
+												100% {
+													transform: rotate(360deg);
+												}
+											}
+										</style><path
+											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+											opacity=".25"
+										/><path
+											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+											class="spinner_ajPY"
+										/></svg
+									>
+								</div>
+							{/if}
+						</button>
+					</div>
+				</form>
+			</div>
+		</div>
+	</div>
+</Modal>
diff --git a/src/lib/components/chat/Settings/Personalization/ManageModal.svelte b/src/lib/components/chat/Settings/Personalization/ManageModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..92b4936e91b4ca8eb75e5f7ed990ebc1d2cdc0fe
--- /dev/null
+++ b/src/lib/components/chat/Settings/Personalization/ManageModal.svelte
@@ -0,0 +1,208 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import dayjs from 'dayjs';
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	import Modal from '$lib/components/common/Modal.svelte';
+	import AddMemoryModal from './AddMemoryModal.svelte';
+	import { deleteMemoriesByUserId, deleteMemoryById, getMemories } from '$lib/apis/memories';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import { error } from '@sveltejs/kit';
+	import EditMemoryModal from './EditMemoryModal.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let show = false;
+
+	let memories = [];
+	let loading = true;
+
+	let showAddMemoryModal = false;
+	let showEditMemoryModal = false;
+
+	let selectedMemory = null;
+
+	$: if (show && memories.length === 0 && loading) {
+		(async () => {
+			memories = await getMemories(localStorage.token);
+			loading = false;
+		})();
+	}
+</script>
+
+<Modal size="xl" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
+			<div class=" text-lg font-medium self-center">{$i18n.t('Memory')}</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col w-full px-5 pb-5 dark:text-gray-200">
+			<div
+				class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6 h-[28rem] max-h-screen outline outline-1 rounded-xl outline-gray-100 dark:outline-gray-800 mb-4 mt-1"
+			>
+				{#if memories.length > 0}
+					<div class="text-left text-sm w-full mb-4 overflow-y-scroll">
+						<div class="relative overflow-x-auto">
+							<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
+								<thead
+									class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-800"
+								>
+									<tr>
+										<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
+										<th scope="col" class="px-3 py-2 hidden md:flex">
+											{$i18n.t('Last Modified')}
+										</th>
+										<th scope="col" class="px-3 py-2 text-right" />
+									</tr>
+								</thead>
+								<tbody>
+									{#each memories as memory}
+										<tr class="border-b dark:border-gray-800 items-center">
+											<td class="px-3 py-1">
+												<div class="line-clamp-1">
+													{memory.content}
+												</div>
+											</td>
+											<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
+												<div class="my-auto whitespace-nowrap">
+													{dayjs(memory.updated_at * 1000).format(
+														$i18n.t('MMMM DD, YYYY hh:mm:ss A')
+													)}
+												</div>
+											</td>
+											<td class="px-3 py-1">
+												<div class="flex justify-end w-full">
+													<Tooltip content="Edit">
+														<button
+															class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+															on:click={() => {
+																selectedMemory = memory;
+																showEditMemoryModal = true;
+															}}
+														>
+															<svg
+																xmlns="http://www.w3.org/2000/svg"
+																fill="none"
+																viewBox="0 0 24 24"
+																stroke-width="1.5"
+																stroke="currentColor"
+																class="w-4 h-4 s-FoVA_WMOgxUD"
+																><path
+																	stroke-linecap="round"
+																	stroke-linejoin="round"
+																	d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
+																	class="s-FoVA_WMOgxUD"
+																/></svg
+															>
+														</button>
+													</Tooltip>
+
+													<Tooltip content="Delete">
+														<button
+															class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+															on:click={async () => {
+																const res = await deleteMemoryById(
+																	localStorage.token,
+																	memory.id
+																).catch((error) => {
+																	toast.error(error);
+																	return null;
+																});
+
+																if (res) {
+																	toast.success($i18n.t('Memory deleted successfully'));
+																	memories = await getMemories(localStorage.token);
+																}
+															}}
+														>
+															<svg
+																xmlns="http://www.w3.org/2000/svg"
+																fill="none"
+																viewBox="0 0 24 24"
+																stroke-width="1.5"
+																stroke="currentColor"
+																class="w-4 h-4"
+															>
+																<path
+																	stroke-linecap="round"
+																	stroke-linejoin="round"
+																	d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
+																/>
+															</svg>
+														</button>
+													</Tooltip>
+												</div>
+											</td>
+										</tr>
+									{/each}
+								</tbody>
+							</table>
+						</div>
+					</div>
+				{:else}
+					<div class="text-center flex h-full text-sm w-full">
+						<div class=" my-auto pb-10 px-4 w-full text-gray-500">
+							{$i18n.t('Memories accessible by LLMs will be shown here.')}
+						</div>
+					</div>
+				{/if}
+			</div>
+			<div class="flex text-sm font-medium gap-1.5">
+				<button
+					class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
+					on:click={() => {
+						showAddMemoryModal = true;
+					}}>{$i18n.t('Add Memory')}</button
+				>
+				<button
+					class=" px-3.5 py-1.5 font-medium text-red-500 hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-red-300 dark:outline-red-800 rounded-3xl"
+					on:click={async () => {
+						const res = await deleteMemoriesByUserId(localStorage.token).catch((error) => {
+							toast.error(error);
+							return null;
+						});
+
+						if (res) {
+							toast.success($i18n.t('Memory cleared successfully'));
+							memories = [];
+						}
+					}}>{$i18n.t('Clear memory')}</button
+				>
+			</div>
+		</div>
+	</div>
+</Modal>
+
+<AddMemoryModal
+	bind:show={showAddMemoryModal}
+	on:save={async () => {
+		memories = await getMemories(localStorage.token);
+	}}
+/>
+
+<EditMemoryModal
+	bind:show={showEditMemoryModal}
+	memory={selectedMemory}
+	on:save={async () => {
+		memories = await getMemories(localStorage.token);
+	}}
+/>
diff --git a/src/lib/components/chat/SettingsModal.svelte b/src/lib/components/chat/SettingsModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a2791c41d49dada4c7caa74a45751b1ea1b37185
--- /dev/null
+++ b/src/lib/components/chat/SettingsModal.svelte
@@ -0,0 +1,363 @@
+<script lang="ts">
+	import { getContext, tick } from 'svelte';
+	import { toast } from 'svelte-sonner';
+	import { models, settings, user } from '$lib/stores';
+	import { updateUserSettings } from '$lib/apis/users';
+	import { getModels as _getModels } from '$lib/apis';
+	import { goto } from '$app/navigation';
+
+	import Modal from '../common/Modal.svelte';
+	import Account from './Settings/Account.svelte';
+	import About from './Settings/About.svelte';
+	import General from './Settings/General.svelte';
+	import Interface from './Settings/Interface.svelte';
+	import Audio from './Settings/Audio.svelte';
+	import Chats from './Settings/Chats.svelte';
+	import User from '../icons/User.svelte';
+	import Personalization from './Settings/Personalization.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let show = false;
+
+	const saveSettings = async (updated) => {
+		console.log(updated);
+		await settings.set({ ...$settings, ...updated });
+		await models.set(await getModels());
+		await updateUserSettings(localStorage.token, { ui: $settings });
+	};
+
+	const getModels = async () => {
+		return await _getModels(localStorage.token);
+	};
+
+	let selectedTab = 'general';
+
+	// Function to handle sideways scrolling
+	const scrollHandler = (event) => {
+		const settingsTabsContainer = document.getElementById('settings-tabs-container');
+		if (settingsTabsContainer) {
+			event.preventDefault(); // Prevent default vertical scrolling
+			settingsTabsContainer.scrollLeft += event.deltaY; // Scroll sideways
+		}
+	};
+
+	const addScrollListener = async () => {
+		await tick();
+		const settingsTabsContainer = document.getElementById('settings-tabs-container');
+		if (settingsTabsContainer) {
+			settingsTabsContainer.addEventListener('wheel', scrollHandler);
+		}
+	};
+
+	const removeScrollListener = async () => {
+		await tick();
+		const settingsTabsContainer = document.getElementById('settings-tabs-container');
+		if (settingsTabsContainer) {
+			settingsTabsContainer.removeEventListener('wheel', scrollHandler);
+		}
+	};
+
+	$: if (show) {
+		addScrollListener();
+	} else {
+		removeScrollListener();
+	}
+</script>
+
+<Modal bind:show>
+	<div class="text-gray-700 dark:text-gray-100">
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
+			<div class=" text-lg font-medium self-center">{$i18n.t('Settings')}</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full px-4 pt-2 pb-4 md:space-x-4">
+			<div
+				id="settings-tabs-container"
+				class="tabs flex flex-row overflow-x-auto space-x-1 md:space-x-0 md:space-y-1 md:flex-col flex-1 md:flex-none md:w-40 dark:text-gray-200 text-xs text-left mb-3 md:mb-0"
+			>
+				<button
+					class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
+					'general'
+						? 'bg-gray-100 dark:bg-gray-800'
+						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
+					on:click={() => {
+						selectedTab = 'general';
+					}}
+				>
+					<div class=" self-center mr-2">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 20 20"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M8.34 1.804A1 1 0 019.32 1h1.36a1 1 0 01.98.804l.295 1.473c.497.144.971.342 1.416.587l1.25-.834a1 1 0 011.262.125l.962.962a1 1 0 01.125 1.262l-.834 1.25c.245.445.443.919.587 1.416l1.473.294a1 1 0 01.804.98v1.361a1 1 0 01-.804.98l-1.473.295a6.95 6.95 0 01-.587 1.416l.834 1.25a1 1 0 01-.125 1.262l-.962.962a1 1 0 01-1.262.125l-1.25-.834a6.953 6.953 0 01-1.416.587l-.294 1.473a1 1 0 01-.98.804H9.32a1 1 0 01-.98-.804l-.295-1.473a6.957 6.957 0 01-1.416-.587l-1.25.834a1 1 0 01-1.262-.125l-.962-.962a1 1 0 01-.125-1.262l.834-1.25a6.957 6.957 0 01-.587-1.416l-1.473-.294A1 1 0 011 10.68V9.32a1 1 0 01.804-.98l1.473-.295c.144-.497.342-.971.587-1.416l-.834-1.25a1 1 0 01.125-1.262l.962-.962A1 1 0 015.38 3.03l1.25.834a6.957 6.957 0 011.416-.587l.294-1.473zM13 10a3 3 0 11-6 0 3 3 0 016 0z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">{$i18n.t('General')}</div>
+				</button>
+
+				<button
+					class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
+					'interface'
+						? 'bg-gray-100 dark:bg-gray-800'
+						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
+					on:click={() => {
+						selectedTab = 'interface';
+					}}
+				>
+					<div class=" self-center mr-2">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M2 4.25A2.25 2.25 0 0 1 4.25 2h7.5A2.25 2.25 0 0 1 14 4.25v5.5A2.25 2.25 0 0 1 11.75 12h-1.312c.1.128.21.248.328.36a.75.75 0 0 1 .234.545v.345a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1-.75-.75v-.345a.75.75 0 0 1 .234-.545c.118-.111.228-.232.328-.36H4.25A2.25 2.25 0 0 1 2 9.75v-5.5Zm2.25-.75a.75.75 0 0 0-.75.75v4.5c0 .414.336.75.75.75h7.5a.75.75 0 0 0 .75-.75v-4.5a.75.75 0 0 0-.75-.75h-7.5Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">{$i18n.t('Interface')}</div>
+				</button>
+
+				<button
+					class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
+					'personalization'
+						? 'bg-gray-100 dark:bg-gray-800'
+						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
+					on:click={() => {
+						selectedTab = 'personalization';
+					}}
+				>
+					<div class=" self-center mr-2">
+						<User />
+					</div>
+					<div class=" self-center">{$i18n.t('Personalization')}</div>
+				</button>
+
+				<button
+					class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
+					'audio'
+						? 'bg-gray-100 dark:bg-gray-800'
+						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
+					on:click={() => {
+						selectedTab = 'audio';
+					}}
+				>
+					<div class=" self-center mr-2">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M7.557 2.066A.75.75 0 0 1 8 2.75v10.5a.75.75 0 0 1-1.248.56L3.59 11H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.59l3.162-2.81a.75.75 0 0 1 .805-.124ZM12.95 3.05a.75.75 0 1 0-1.06 1.06 5.5 5.5 0 0 1 0 7.78.75.75 0 1 0 1.06 1.06 7 7 0 0 0 0-9.9Z"
+							/>
+							<path
+								d="M10.828 5.172a.75.75 0 1 0-1.06 1.06 2.5 2.5 0 0 1 0 3.536.75.75 0 1 0 1.06 1.06 4 4 0 0 0 0-5.656Z"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">{$i18n.t('Audio')}</div>
+				</button>
+
+				<button
+					class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
+					'chats'
+						? 'bg-gray-100 dark:bg-gray-800'
+						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
+					on:click={() => {
+						selectedTab = 'chats';
+					}}
+				>
+					<div class=" self-center mr-2">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M8 2C4.262 2 1 4.57 1 8c0 1.86.98 3.486 2.455 4.566a3.472 3.472 0 0 1-.469 1.26.75.75 0 0 0 .713 1.14 6.961 6.961 0 0 0 3.06-1.06c.403.062.818.094 1.241.094 3.738 0 7-2.57 7-6s-3.262-6-7-6ZM5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm7-1a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM8 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">{$i18n.t('Chats')}</div>
+				</button>
+
+				<button
+					class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
+					'account'
+						? 'bg-gray-100 dark:bg-gray-800'
+						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
+					on:click={() => {
+						selectedTab = 'account';
+					}}
+				>
+					<div class=" self-center mr-2">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0Zm-5-2a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM8 9c-1.825 0-3.422.977-4.295 2.437A5.49 5.49 0 0 0 8 13.5a5.49 5.49 0 0 0 4.294-2.063A4.997 4.997 0 0 0 8 9Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">{$i18n.t('Account')}</div>
+				</button>
+
+				{#if $user.role === 'admin'}
+					<button
+						class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
+						'admin'
+							? 'bg-gray-100 dark:bg-gray-800'
+							: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
+						on:click={async () => {
+							await goto('/admin/settings');
+							show = false;
+						}}
+					>
+						<div class=" self-center mr-2">
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 24 24"
+								fill="currentColor"
+								class="size-4"
+							>
+								<path
+									fill-rule="evenodd"
+									d="M4.5 3.75a3 3 0 0 0-3 3v10.5a3 3 0 0 0 3 3h15a3 3 0 0 0 3-3V6.75a3 3 0 0 0-3-3h-15Zm4.125 3a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Zm-3.873 8.703a4.126 4.126 0 0 1 7.746 0 .75.75 0 0 1-.351.92 7.47 7.47 0 0 1-3.522.877 7.47 7.47 0 0 1-3.522-.877.75.75 0 0 1-.351-.92ZM15 8.25a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 0-1.5H15ZM14.25 12a.75.75 0 0 1 .75-.75h3.75a.75.75 0 0 1 0 1.5H15a.75.75 0 0 1-.75-.75Zm.75 2.25a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 0-1.5H15Z"
+									clip-rule="evenodd"
+								/>
+							</svg>
+						</div>
+						<div class=" self-center">{$i18n.t('Admin Settings')}</div>
+					</button>
+				{/if}
+
+				<button
+					class="px-2.5 py-2 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
+					'about'
+						? 'bg-gray-100 dark:bg-gray-800'
+						: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
+					on:click={() => {
+						selectedTab = 'about';
+					}}
+				>
+					<div class=" self-center mr-2">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 20 20"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">{$i18n.t('About')}</div>
+				</button>
+			</div>
+			<div class="flex-1 md:min-h-[28rem]">
+				{#if selectedTab === 'general'}
+					<General
+						{getModels}
+						{saveSettings}
+						on:save={() => {
+							toast.success($i18n.t('Settings saved successfully!'));
+						}}
+					/>
+				{:else if selectedTab === 'interface'}
+					<Interface
+						{saveSettings}
+						on:save={() => {
+							toast.success($i18n.t('Settings saved successfully!'));
+						}}
+					/>
+				{:else if selectedTab === 'personalization'}
+					<Personalization
+						{saveSettings}
+						on:save={() => {
+							toast.success($i18n.t('Settings saved successfully!'));
+						}}
+					/>
+				{:else if selectedTab === 'audio'}
+					<Audio
+						{saveSettings}
+						on:save={() => {
+							toast.success($i18n.t('Settings saved successfully!'));
+						}}
+					/>
+				{:else if selectedTab === 'chats'}
+					<Chats {saveSettings} />
+				{:else if selectedTab === 'account'}
+					<Account
+						saveHandler={() => {
+							toast.success($i18n.t('Settings saved successfully!'));
+						}}
+					/>
+				{:else if selectedTab === 'about'}
+					<About />
+				{/if}
+			</div>
+		</div>
+	</div>
+</Modal>
+
+<style>
+	input::-webkit-outer-spin-button,
+	input::-webkit-inner-spin-button {
+		/* display: none; <- Crashes Chrome on hover */
+		-webkit-appearance: none;
+		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
+	}
+
+	.tabs::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.tabs {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+
+	input[type='number'] {
+		-moz-appearance: textfield; /* Firefox */
+	}
+</style>
diff --git a/src/lib/components/chat/ShareChatModal.svelte b/src/lib/components/chat/ShareChatModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e6ccc1323f2c7d6ccb38f58a286e25ff7399ac3c
--- /dev/null
+++ b/src/lib/components/chat/ShareChatModal.svelte
@@ -0,0 +1,202 @@
+<script lang="ts">
+	import { getContext, onMount } from 'svelte';
+	import { models, config } from '$lib/stores';
+
+	import { toast } from 'svelte-sonner';
+	import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
+	import { copyToClipboard } from '$lib/utils';
+
+	import Modal from '../common/Modal.svelte';
+	import Link from '../icons/Link.svelte';
+
+	export let chatId;
+
+	let chat = null;
+	let shareUrl = null;
+	const i18n = getContext('i18n');
+
+	const shareLocalChat = async () => {
+		const _chat = chat;
+
+		const sharedChat = await shareChatById(localStorage.token, chatId);
+		shareUrl = `${window.location.origin}/s/${sharedChat.id}`;
+		console.log(shareUrl);
+		chat = await getChatById(localStorage.token, chatId);
+
+		return shareUrl;
+	};
+
+	const shareChat = async () => {
+		const _chat = chat.chat;
+		console.log('share', _chat);
+
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
+		const url = 'https://openwebui.com';
+		// const url = 'http://localhost:5173';
+
+		const tab = await window.open(`${url}/chats/upload`, '_blank');
+		window.addEventListener(
+			'message',
+			(event) => {
+				if (event.origin !== url) return;
+				if (event.data === 'loaded') {
+					tab.postMessage(
+						JSON.stringify({
+							chat: _chat,
+							models: $models.filter((m) => _chat.models.includes(m.id))
+						}),
+						'*'
+					);
+				}
+			},
+			false
+		);
+	};
+
+	export let show = false;
+
+	const isDifferentChat = (_chat) => {
+		if (!chat) {
+			return true;
+		}
+		if (!_chat) {
+			return false;
+		}
+		return chat.id !== _chat.id || chat.share_id !== _chat.share_id;
+	};
+
+	$: if (show) {
+		(async () => {
+			if (chatId) {
+				const _chat = await getChatById(localStorage.token, chatId);
+				if (isDifferentChat(_chat)) {
+					chat = _chat;
+				}
+			} else {
+				chat = null;
+				console.log(chat);
+			}
+		})();
+	}
+</script>
+
+<Modal bind:show size="sm">
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-0.5">
+			<div class=" text-lg font-medium self-center">{$i18n.t('Share Chat')}</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		{#if chat}
+			<div class="px-5 pt-4 pb-5 w-full flex flex-col justify-center">
+				<div class=" text-sm dark:text-gray-300 mb-1">
+					{#if chat.share_id}
+						<a href="/s/{chat.share_id}" target="_blank"
+							>{$i18n.t('You have shared this chat')}
+							<span class=" underline">{$i18n.t('before')}</span>.</a
+						>
+						{$i18n.t('Click here to')}
+						<button
+							class="underline"
+							on:click={async () => {
+								const res = await deleteSharedChatById(localStorage.token, chatId);
+
+								if (res) {
+									chat = await getChatById(localStorage.token, chatId);
+								}
+							}}
+							>{$i18n.t('delete this link')}
+						</button>
+						{$i18n.t('and create a new shared link.')}
+					{:else}
+						{$i18n.t(
+							"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat."
+						)}
+					{/if}
+				</div>
+
+				<div class="flex justify-end">
+					<div class="flex flex-col items-end space-x-1 mt-1.5">
+						<div class="flex gap-1">
+							{#if $config?.features.enable_community_sharing}
+								<button
+									class=" self-center px-3.5 py-2 rounded-xl text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white"
+									type="button"
+									on:click={() => {
+										shareChat();
+										show = false;
+									}}
+								>
+									{$i18n.t('Share to OpenWebUI Community')}
+								</button>
+							{/if}
+
+							<button
+								class=" self-center flex items-center gap-1 px-3.5 py-2 rounded-xl text-sm font-medium bg-emerald-600 hover:bg-emerald-500 text-white"
+								type="button"
+								id="copy-and-share-chat-button"
+								on:click={async () => {
+									const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
+
+									if (isSafari) {
+										// Oh, Safari, you're so special, let's give you some extra love and attention
+										console.log('isSafari');
+
+										const getUrlPromise = async () => {
+											const url = await shareLocalChat();
+											return new Blob([url], { type: 'text/plain' });
+										};
+
+										navigator.clipboard
+											.write([
+												new ClipboardItem({
+													'text/plain': getUrlPromise()
+												})
+											])
+											.then(() => {
+												console.log('Async: Copying to clipboard was successful!');
+												return true;
+											})
+											.catch((error) => {
+												console.error('Async: Could not copy text: ', error);
+												return false;
+											});
+									} else {
+										copyToClipboard(await shareLocalChat());
+									}
+
+									toast.success($i18n.t('Copied shared chat URL to clipboard!'));
+									show = false;
+								}}
+							>
+								<Link />
+
+								{#if chat.share_id}
+									{$i18n.t('Update and Copy Link')}
+								{:else}
+									{$i18n.t('Copy Link')}
+								{/if}
+							</button>
+						</div>
+					</div>
+				</div>
+			</div>
+		{/if}
+	</div>
+</Modal>
diff --git a/src/lib/components/chat/ShortcutsModal.svelte b/src/lib/components/chat/ShortcutsModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7a0ec21ab0f4d23f0a101dc7ba47b2e23e40b35f
--- /dev/null
+++ b/src/lib/components/chat/ShortcutsModal.svelte
@@ -0,0 +1,287 @@
+<script lang="ts">
+	import { getContext } from 'svelte';
+	import Modal from '../common/Modal.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let show = false;
+</script>
+
+<Modal bind:show>
+	<div class="text-gray-700 dark:text-gray-100">
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4">
+			<div class=" text-lg font-medium self-center">{$i18n.t('Keyboard shortcuts')}</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				<div class="flex flex-col space-y-3 w-full self-start">
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">{$i18n.t('Open new chat')}</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Ctrl/⌘
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Shift
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								O
+							</div>
+						</div>
+					</div>
+
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">{$i18n.t('Focus chat input')}</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Shift
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Esc
+							</div>
+						</div>
+					</div>
+
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">{$i18n.t('Copy last code block')}</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Ctrl/⌘
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Shift
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								;
+							</div>
+						</div>
+					</div>
+
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">{$i18n.t('Copy last response')}</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Ctrl/⌘
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Shift
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								C
+							</div>
+						</div>
+					</div>
+				</div>
+
+				<div class="flex flex-col space-y-3 w-full self-start">
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">{$i18n.t('Toggle settings')}</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Ctrl/⌘
+							</div>
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								.
+							</div>
+						</div>
+					</div>
+
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">{$i18n.t('Toggle sidebar')}</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Ctrl/⌘
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Shift
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								S
+							</div>
+						</div>
+					</div>
+
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">{$i18n.t('Delete chat')}</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Ctrl/⌘
+							</div>
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Shift
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								⌫/Delete
+							</div>
+						</div>
+					</div>
+
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">{$i18n.t('Show shortcuts')}</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Ctrl/⌘
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								/
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<div class=" flex justify-between dark:text-gray-300 px-5">
+			<div class=" text-lg font-medium self-center">{$i18n.t('Input commands')}</div>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				<div class="flex flex-col space-y-3 w-full self-start">
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">
+							{$i18n.t('Attach file')}
+						</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								#
+							</div>
+						</div>
+					</div>
+
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">
+							{$i18n.t('Add custom prompt')}
+						</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								/
+							</div>
+						</div>
+					</div>
+
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">
+							{$i18n.t('Select model')}
+						</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								@
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</Modal>
+
+<style>
+	input::-webkit-outer-spin-button,
+	input::-webkit-inner-spin-button {
+		/* display: none; <- Crashes Chrome on hover */
+		-webkit-appearance: none;
+		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
+	}
+
+	.tabs::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.tabs {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+
+	input[type='number'] {
+		-moz-appearance: textfield; /* Firefox */
+	}
+</style>
diff --git a/src/lib/components/chat/Suggestions.svelte b/src/lib/components/chat/Suggestions.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a73d7961715d7b1b61a97ea8e93ef6008e253eaa
--- /dev/null
+++ b/src/lib/components/chat/Suggestions.svelte
@@ -0,0 +1,53 @@
+<script lang="ts">
+	import Bolt from '$lib/components/icons/Bolt.svelte';
+	import { onMount, getContext, createEventDispatcher } from 'svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	export let suggestionPrompts = [];
+	export let className = '';
+
+	let prompts = [];
+
+	$: prompts = (suggestionPrompts ?? [])
+		.reduce((acc, current) => [...acc, ...[current]], [])
+		.sort(() => Math.random() - 0.5);
+</script>
+
+{#if prompts.length > 0}
+	<div class="mb-1 flex gap-1 text-sm font-medium items-center text-gray-400 dark:text-gray-600">
+		<Bolt />
+		{$i18n.t('Suggested')}
+	</div>
+{/if}
+
+<div class=" h-40 max-h-full overflow-auto scrollbar-none {className}">
+	{#each prompts as prompt, promptIdx}
+		<button
+			class="flex flex-col flex-1 shrink-0 w-full justify-between px-3 py-2 rounded-xl bg-transparent hover:bg-black/5 dark:hover:bg-white/5 transition group"
+			on:click={() => {
+				dispatch('select', prompt.content);
+			}}
+		>
+			<div class="flex flex-col text-left">
+				{#if prompt.title && prompt.title[0] !== ''}
+					<div
+						class="  font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
+					>
+						{prompt.title[0]}
+					</div>
+					<div class="text-xs text-gray-500 font-normal line-clamp-1">{prompt.title[1]}</div>
+				{:else}
+					<div
+						class="  font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
+					>
+						{prompt.content}
+					</div>
+
+					<div class="text-xs text-gray-500 font-normal line-clamp-1">Prompt</div>
+				{/if}
+			</div>
+		</button>
+	{/each}
+</div>
diff --git a/src/lib/components/chat/TagChatModal.svelte b/src/lib/components/chat/TagChatModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f12247dfadd477241f0b933f85c3c3ef9c5bfbba
--- /dev/null
+++ b/src/lib/components/chat/TagChatModal.svelte
@@ -0,0 +1,28 @@
+<script lang="ts">
+	import { getContext } from 'svelte';
+	import Modal from '../common/Modal.svelte';
+
+	import Tags from '../common/Tags.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let tags;
+	export let deleteTag: Function;
+	export let addTag: Function;
+
+	export let show = false;
+</script>
+
+<Modal bind:show size="xs">
+	<div class="px-4 pt-4 pb-5 w-full flex flex-col justify-center">
+		<Tags
+			{tags}
+			on:delete={(e) => {
+				deleteTag(e.detail);
+			}}
+			on:add={(e) => {
+				addTag(e.detail);
+			}}
+		/>
+	</div>
+</Modal>
diff --git a/src/lib/components/chat/Tags.svelte b/src/lib/components/chat/Tags.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..cea7b548e1d0dd1a32e42107f479c8789718f4df
--- /dev/null
+++ b/src/lib/components/chat/Tags.svelte
@@ -0,0 +1,82 @@
+<script>
+	import {
+		addTagById,
+		deleteTagById,
+		getAllTags,
+		getChatList,
+		getChatListByTagName,
+		getTagsById,
+		updateChatById
+	} from '$lib/apis/chats';
+	import {
+		tags as _tags,
+		chats,
+		pinnedChats,
+		currentChatPage,
+		scrollPaginationEnabled
+	} from '$lib/stores';
+	import { createEventDispatcher, onMount } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	import Tags from '../common/Tags.svelte';
+	import { toast } from 'svelte-sonner';
+
+	export let chatId = '';
+	let tags = [];
+
+	const getTags = async () => {
+		return await getTagsById(localStorage.token, chatId).catch(async (error) => {
+			return [];
+		});
+	};
+
+	const addTag = async (tagName) => {
+		const res = await addTagById(localStorage.token, chatId, tagName).catch(async (error) => {
+			toast.error(error);
+			return null;
+		});
+		if (!res) {
+			return;
+		}
+
+		tags = await getTags();
+		await updateChatById(localStorage.token, chatId, {
+			tags: tags
+		});
+
+		await _tags.set(await getAllTags(localStorage.token));
+		dispatch('add', {
+			name: tagName
+		});
+	};
+
+	const deleteTag = async (tagName) => {
+		const res = await deleteTagById(localStorage.token, chatId, tagName);
+		tags = await getTags();
+		await updateChatById(localStorage.token, chatId, {
+			tags: tags
+		});
+
+		await _tags.set(await getAllTags(localStorage.token));
+		dispatch('delete', {
+			name: tagName
+		});
+	};
+
+	onMount(async () => {
+		if (chatId) {
+			tags = await getTags();
+		}
+	});
+</script>
+
+<Tags
+	{tags}
+	on:delete={(e) => {
+		deleteTag(e.detail);
+	}}
+	on:add={(e) => {
+		addTag(e.detail);
+	}}
+/>
diff --git a/src/lib/components/common/Badge.svelte b/src/lib/components/common/Badge.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..12ad872c5ee96489be251bb2eb1d36163693dc89
--- /dev/null
+++ b/src/lib/components/common/Badge.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let type = 'info';
+	export let content = '';
+
+	const classNames: Record<string, string> = {
+		info: 'bg-blue-500/20 text-blue-700 dark:text-blue-200 ',
+		success: 'bg-green-500/20 text-green-700 dark:text-green-200',
+		warning: 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-200',
+		error: 'bg-red-500/20 text-red-700 dark:text-red-200',
+		muted: 'bg-gray-500/20 text-gray-700 dark:text-gray-200'
+	};
+</script>
+
+<div
+	class=" text-xs font-bold {classNames[type] ??
+		classNames['info']}  w-fit px-2 rounded uppercase line-clamp-1 mr-0.5"
+>
+	{content}
+</div>
diff --git a/src/lib/components/common/Banner.svelte b/src/lib/components/common/Banner.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a11df90579e1bf6581f4661912563d9e9927f313
--- /dev/null
+++ b/src/lib/components/common/Banner.svelte
@@ -0,0 +1,126 @@
+<script lang="ts">
+	import type { Banner } from '$lib/types';
+	import { onMount, createEventDispatcher } from 'svelte';
+	import { fade } from 'svelte/transition';
+
+	const dispatch = createEventDispatcher();
+
+	export let banner: Banner = {
+		id: '',
+		type: 'info',
+		title: '',
+		content: '',
+		url: '',
+		dismissable: true,
+		timestamp: Math.floor(Date.now() / 1000)
+	};
+
+	export let dismissed = false;
+
+	let mounted = false;
+
+	const classNames: Record<string, string> = {
+		info: 'bg-blue-500/20 text-blue-700 dark:text-blue-200 ',
+		success: 'bg-green-500/20 text-green-700 dark:text-green-200',
+		warning: 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-200',
+		error: 'bg-red-500/20 text-red-700 dark:text-red-200'
+	};
+
+	const dismiss = (id) => {
+		dismissed = true;
+		dispatch('dismiss', id);
+	};
+
+	onMount(() => {
+		mounted = true;
+	});
+</script>
+
+{#if !dismissed}
+	{#if mounted}
+		<div
+			class=" top-0 left-0 right-0 p-2 mx-4 px-3 flex justify-center items-center relative rounded-xl border border-gray-50 dark:border-gray-850 text-gray-800 dark:text-gary-100 bg-white dark:bg-gray-900 backdrop-blur-xl z-30"
+			transition:fade={{ delay: 100, duration: 300 }}
+		>
+			<div class=" flex flex-col md:flex-row md:items-center flex-1 text-sm w-fit gap-1.5">
+				<div class="flex justify-between self-start">
+					<div
+						class=" text-xs font-bold {classNames[banner.type] ??
+							classNames['info']}  w-fit px-2 rounded uppercase line-clamp-1 mr-0.5"
+					>
+						{banner.type}
+					</div>
+
+					{#if banner.url}
+						<div class="flex md:hidden group w-fit md:items-center">
+							<a
+								class="text-gray-700 dark:text-white text-xs font-semibold underline"
+								href="/assets/files/whitepaper.pdf"
+								target="_blank">Learn More</a
+							>
+
+							<div
+								class=" ml-1 text-gray-400 group-hover:text-gray-600 dark:group-hover:text-white"
+							>
+								<!--  -->
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										fill-rule="evenodd"
+										d="M4.22 11.78a.75.75 0 0 1 0-1.06L9.44 5.5H5.75a.75.75 0 0 1 0-1.5h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0V6.56l-5.22 5.22a.75.75 0 0 1-1.06 0Z"
+										clip-rule="evenodd"
+									/>
+								</svg>
+							</div>
+						</div>
+					{/if}
+				</div>
+
+				<div class="flex-1 text-xs text-gray-700 dark:text-white">
+					{banner.content}
+				</div>
+			</div>
+
+			{#if banner.url}
+				<div class="hidden md:flex group w-fit md:items-center">
+					<a
+						class="text-gray-700 dark:text-white text-xs font-semibold underline"
+						href="/"
+						target="_blank">Learn More</a
+					>
+
+					<div class=" ml-1 text-gray-400 group-hover:text-gray-600 dark:group-hover:text-white">
+						<!--  -->
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="size-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M4.22 11.78a.75.75 0 0 1 0-1.06L9.44 5.5H5.75a.75.75 0 0 1 0-1.5h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0V6.56l-5.22 5.22a.75.75 0 0 1-1.06 0Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+				</div>
+			{/if}
+			<div class="flex self-start">
+				{#if banner.dismissible}
+					<button
+						on:click={() => {
+							dismiss(banner.id);
+						}}
+						class="  -mt-1 -mb-2 -translate-y-[1px] ml-1.5 mr-1 text-gray-400 dark:hover:text-white"
+						>&times;</button
+					>
+				{/if}
+			</div>
+		</div>
+	{/if}
+{/if}
diff --git a/src/lib/components/common/Checkbox.svelte b/src/lib/components/common/Checkbox.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..3d43a69509db387136209b670ba6d2eea873546f
--- /dev/null
+++ b/src/lib/components/common/Checkbox.svelte
@@ -0,0 +1,71 @@
+<script lang="ts">
+	import { createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	export let state = 'unchecked';
+	export let indeterminate = false;
+
+	let _state = 'unchecked';
+
+	$: _state = state;
+</script>
+
+<button
+	class=" outline -outline-offset-1 outline-[1.5px] outline-gray-200 dark:outline-gray-600 {state !==
+	'unchecked'
+		? 'bg-black outline-black '
+		: 'hover:outline-gray-500 hover:bg-gray-50 dark:hover:bg-gray-800'} text-white transition-all rounded inline-block w-3.5 h-3.5 relative"
+	on:click={() => {
+		if (_state === 'unchecked') {
+			_state = 'checked';
+			dispatch('change', _state);
+		} else if (_state === 'checked') {
+			_state = 'unchecked';
+			if (!indeterminate) {
+				dispatch('change', _state);
+			}
+		} else if (indeterminate) {
+			_state = 'checked';
+			dispatch('change', _state);
+		}
+	}}
+	type="button"
+>
+	<div class="top-0 left-0 absolute w-full flex justify-center">
+		{#if _state === 'checked'}
+			<svg
+				class="w-3.5 h-3.5"
+				aria-hidden="true"
+				xmlns="http://www.w3.org/2000/svg"
+				fill="none"
+				viewBox="0 0 24 24"
+			>
+				<path
+					stroke="currentColor"
+					stroke-linecap="round"
+					stroke-linejoin="round"
+					stroke-width="3"
+					d="m5 12 4.7 4.5 9.3-9"
+				/>
+			</svg>
+		{:else if indeterminate}
+			<svg
+				class="w-3 h-3.5 text-gray-800 dark:text-white"
+				aria-hidden="true"
+				xmlns="http://www.w3.org/2000/svg"
+				fill="none"
+				viewBox="0 0 24 24"
+			>
+				<path
+					stroke="currentColor"
+					stroke-linecap="round"
+					stroke-linejoin="round"
+					stroke-width="3"
+					d="M5 12h14"
+				/>
+			</svg>
+		{/if}
+	</div>
+
+	<!-- {checked} -->
+</button>
diff --git a/src/lib/components/common/CodeEditor.svelte b/src/lib/components/common/CodeEditor.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e9769e0c638e24bfaea11b4338396807e8c603d1
--- /dev/null
+++ b/src/lib/components/common/CodeEditor.svelte
@@ -0,0 +1,186 @@
+<script lang="ts">
+	import { basicSetup, EditorView } from 'codemirror';
+	import { keymap, placeholder } from '@codemirror/view';
+	import { Compartment, EditorState } from '@codemirror/state';
+
+	import { acceptCompletion } from '@codemirror/autocomplete';
+	import { indentWithTab } from '@codemirror/commands';
+
+	import { indentUnit } from '@codemirror/language';
+	import { languages } from '@codemirror/language-data';
+
+	// import { python } from '@codemirror/lang-python';
+	// import { javascript } from '@codemirror/lang-javascript';
+
+	import { oneDark } from '@codemirror/theme-one-dark';
+
+	import { onMount, createEventDispatcher, getContext, tick } from 'svelte';
+
+	import { formatPythonCode } from '$lib/apis/utils';
+	import { toast } from 'svelte-sonner';
+
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	export let boilerplate = '';
+	export let value = '';
+	let _value = '';
+
+	$: if (value) {
+		updateValue();
+	}
+
+	const updateValue = () => {
+		if (_value !== value) {
+			_value = value;
+			if (codeEditor) {
+				codeEditor.dispatch({
+					changes: [{ from: 0, to: codeEditor.state.doc.length, insert: _value }]
+				});
+			}
+		}
+	};
+
+	export let id = '';
+	export let lang = '';
+
+	let codeEditor;
+
+	let isDarkMode = false;
+	let editorTheme = new Compartment();
+	let editorLanguage = new Compartment();
+
+	const getLang = async () => {
+		const language = languages.find((l) => l.alias.includes(lang));
+		return await language?.load();
+	};
+
+	export const formatPythonCodeHandler = async () => {
+		if (codeEditor) {
+			const res = await formatPythonCode(_value).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+
+			if (res && res.code) {
+				const formattedCode = res.code;
+				codeEditor.dispatch({
+					changes: [{ from: 0, to: codeEditor.state.doc.length, insert: formattedCode }]
+				});
+
+				_value = formattedCode;
+				dispatch('change', { value: _value });
+				await tick();
+
+				toast.success($i18n.t('Code formatted successfully'));
+				return true;
+			}
+			return false;
+		}
+		return false;
+	};
+
+	let extensions = [
+		basicSetup,
+		keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]),
+		indentUnit.of('    '),
+		placeholder('Enter your code here...'),
+		EditorView.updateListener.of((e) => {
+			if (e.docChanged) {
+				_value = e.state.doc.toString();
+				dispatch('change', { value: _value });
+			}
+		}),
+		editorTheme.of([]),
+		editorLanguage.of([])
+	];
+
+	$: if (lang) {
+		setLanguage();
+	}
+
+	const setLanguage = async () => {
+		const language = await getLang();
+		if (language) {
+			codeEditor.dispatch({
+				effects: editorLanguage.reconfigure(language)
+			});
+		}
+	};
+
+	onMount(() => {
+		console.log(value);
+		if (value === '') {
+			value = boilerplate;
+		}
+
+		_value = value;
+
+		// Check if html class has dark mode
+		isDarkMode = document.documentElement.classList.contains('dark');
+
+		// python code editor, highlight python code
+		codeEditor = new EditorView({
+			state: EditorState.create({
+				doc: _value,
+				extensions: extensions
+			}),
+			parent: document.getElementById(`code-textarea-${id}`)
+		});
+
+		if (isDarkMode) {
+			codeEditor.dispatch({
+				effects: editorTheme.reconfigure(oneDark)
+			});
+		}
+
+		// listen to html class changes this should fire only when dark mode is toggled
+		const observer = new MutationObserver((mutations) => {
+			mutations.forEach((mutation) => {
+				if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
+					const _isDarkMode = document.documentElement.classList.contains('dark');
+
+					if (_isDarkMode !== isDarkMode) {
+						isDarkMode = _isDarkMode;
+						if (_isDarkMode) {
+							codeEditor.dispatch({
+								effects: editorTheme.reconfigure(oneDark)
+							});
+						} else {
+							codeEditor.dispatch({
+								effects: editorTheme.reconfigure()
+							});
+						}
+					}
+				}
+			});
+		});
+
+		observer.observe(document.documentElement, {
+			attributes: true,
+			attributeFilter: ['class']
+		});
+
+		const keydownHandler = async (e) => {
+			if ((e.ctrlKey || e.metaKey) && e.key === 's') {
+				e.preventDefault();
+				dispatch('save');
+			}
+
+			// Format code when Ctrl + Shift + F is pressed
+			if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'f') {
+				e.preventDefault();
+				await formatPythonCodeHandler();
+			}
+		};
+
+		document.addEventListener('keydown', keydownHandler);
+
+		return () => {
+			observer.disconnect();
+			document.removeEventListener('keydown', keydownHandler);
+		};
+	});
+</script>
+
+<div id="code-textarea-{id}" class="h-full w-full" />
diff --git a/src/lib/components/common/Collapsible.svelte b/src/lib/components/common/Collapsible.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a167e3cf260b50a85729cf866b6c6dd64fc6fa5c
--- /dev/null
+++ b/src/lib/components/common/Collapsible.svelte
@@ -0,0 +1,88 @@
+<script lang="ts">
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+	$: dispatch('change', open);
+
+	import { slide } from 'svelte/transition';
+	import { quintOut } from 'svelte/easing';
+
+	import ChevronUp from '../icons/ChevronUp.svelte';
+	import ChevronDown from '../icons/ChevronDown.svelte';
+
+	export let open = false;
+	export let className = '';
+	export let buttonClassName =
+		'w-fit text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition';
+	export let title = null;
+
+	export let grow = false;
+
+	export let disabled = false;
+	export let hide = false;
+</script>
+
+<div class={className}>
+	{#if title !== null}
+		<!-- svelte-ignore a11y-no-static-element-interactions -->
+		<!-- svelte-ignore a11y-click-events-have-key-events -->
+		<div
+			class="{buttonClassName} cursor-pointer"
+			on:pointerup={() => {
+				if (!disabled) {
+					open = !open;
+				}
+			}}
+		>
+			<div class=" w-full font-medium flex items-center justify-between gap-2">
+				<div class="">
+					{title}
+				</div>
+
+				<div>
+					{#if open}
+						<ChevronUp strokeWidth="3.5" className="size-3.5" />
+					{:else}
+						<ChevronDown strokeWidth="3.5" className="size-3.5" />
+					{/if}
+				</div>
+			</div>
+		</div>
+	{:else}
+		<!-- svelte-ignore a11y-no-static-element-interactions -->
+		<!-- svelte-ignore a11y-click-events-have-key-events -->
+		<div
+			class="{buttonClassName} cursor-pointer"
+			on:pointerup={() => {
+				if (!disabled) {
+					open = !open;
+				}
+			}}
+		>
+			<div>
+				<slot />
+
+				{#if grow}
+					{#if open && !hide}
+						<div
+							transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}
+							on:pointerup={(e) => {
+								e.stopPropagation();
+							}}
+						>
+							<slot name="content" />
+						</div>
+					{/if}
+				{/if}
+			</div>
+		</div>
+	{/if}
+
+	{#if !grow}
+		{#if open && !hide}
+			<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
+				<slot name="content" />
+			</div>
+		{/if}
+	{/if}
+</div>
diff --git a/src/lib/components/common/ConfirmDialog.svelte b/src/lib/components/common/ConfirmDialog.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..1156d2df08182bdac5985e12fbbbc494dc3e16d1
--- /dev/null
+++ b/src/lib/components/common/ConfirmDialog.svelte
@@ -0,0 +1,141 @@
+<script lang="ts">
+	import { onMount, getContext, createEventDispatcher } from 'svelte';
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import { fade } from 'svelte/transition';
+	import { flyAndScale } from '$lib/utils/transitions';
+
+	export let title = '';
+	export let message = '';
+
+	export let cancelLabel = $i18n.t('Cancel');
+	export let confirmLabel = $i18n.t('Confirm');
+
+	export let input = false;
+	export let inputPlaceholder = '';
+	export let inputValue = '';
+
+	export let show = false;
+
+	let modalElement = null;
+	let mounted = false;
+
+	const handleKeyDown = (event: KeyboardEvent) => {
+		if (event.key === 'Escape') {
+			console.log('Escape');
+			show = false;
+		}
+
+		if (event.key === 'Enter') {
+			console.log('Enter');
+			show = false;
+			dispatch('confirm', inputValue);
+		}
+	};
+
+	onMount(() => {
+		mounted = true;
+	});
+
+	$: if (mounted) {
+		if (show) {
+			window.addEventListener('keydown', handleKeyDown);
+			document.body.style.overflow = 'hidden';
+		} else {
+			window.removeEventListener('keydown', handleKeyDown);
+			document.body.style.overflow = 'unset';
+		}
+	}
+</script>
+
+{#if show}
+	<!-- svelte-ignore a11y-click-events-have-key-events -->
+	<!-- svelte-ignore a11y-no-static-element-interactions -->
+	<div
+		bind:this={modalElement}
+		class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-[9999] overflow-hidden overscroll-contain"
+		in:fade={{ duration: 10 }}
+		on:mousedown={() => {
+			show = false;
+		}}
+	>
+		<div
+			class=" m-auto rounded-2xl max-w-full w-[32rem] mx-2 bg-gray-50 dark:bg-gray-950 max-h-[100dvh] shadow-3xl"
+			in:flyAndScale
+			on:mousedown={(e) => {
+				e.stopPropagation();
+			}}
+		>
+			<div class="px-[1.75rem] py-6 flex flex-col">
+				<div class=" text-lg font-semibold dark:text-gray-200 mb-2.5">
+					{#if title !== ''}
+						{title}
+					{:else}
+						{$i18n.t('Confirm your action')}
+					{/if}
+				</div>
+
+				<slot>
+					<div class=" text-sm text-gray-500 flex-1">
+						{#if message !== ''}
+							{message}
+						{:else}
+							{$i18n.t('This action cannot be undone. Do you wish to continue?')}
+						{/if}
+
+						{#if input}
+							<textarea
+								bind:value={inputValue}
+								placeholder={inputPlaceholder ? inputPlaceholder : $i18n.t('Enter your message')}
+								class="w-full mt-2 rounded-lg px-4 py-2 text-sm dark:text-gray-300 dark:bg-gray-900 outline-none resize-none"
+								rows="3"
+								required
+							/>
+						{/if}
+					</div>
+				</slot>
+
+				<div class="mt-6 flex justify-between gap-1.5">
+					<button
+						class="bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white font-medium w-full py-2.5 rounded-lg transition"
+						on:click={() => {
+							show = false;
+							dispatch('cancel');
+						}}
+						type="button"
+					>
+						{cancelLabel}
+					</button>
+					<button
+						class="bg-gray-900 hover:bg-gray-850 text-gray-100 dark:bg-gray-100 dark:hover:bg-white dark:text-gray-800 font-medium w-full py-2.5 rounded-lg transition"
+						on:click={() => {
+							show = false;
+							dispatch('confirm', inputValue);
+						}}
+						type="button"
+					>
+						{confirmLabel}
+					</button>
+				</div>
+			</div>
+		</div>
+	</div>
+{/if}
+
+<style>
+	.modal-content {
+		animation: scaleUp 0.1s ease-out forwards;
+	}
+
+	@keyframes scaleUp {
+		from {
+			transform: scale(0.985);
+			opacity: 0;
+		}
+		to {
+			transform: scale(1);
+			opacity: 1;
+		}
+	}
+</style>
diff --git a/src/lib/components/common/DragGhost.svelte b/src/lib/components/common/DragGhost.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7169d72f06fd4f1ae121c8b05c0d0012eeddeccc
--- /dev/null
+++ b/src/lib/components/common/DragGhost.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+	import { onDestroy, onMount } from 'svelte';
+
+	export let x;
+	export let y;
+
+	let popupElement = null;
+
+	onMount(() => {
+		document.body.appendChild(popupElement);
+		document.body.style.overflow = 'hidden';
+	});
+
+	onDestroy(() => {
+		document.body.removeChild(popupElement);
+		document.body.style.overflow = 'unset';
+	});
+</script>
+
+<!-- svelte-ignore a11y-click-events-have-key-events -->
+<!-- svelte-ignore a11y-no-static-element-interactions -->
+
+<div
+	bind:this={popupElement}
+	class="fixed top-0 left-0 w-screen h-[100dvh] z-50 touch-none pointer-events-none"
+>
+	<div class=" absolute text-white z-[99999]" style="top: {y + 10}px; left: {x + 10}px;">
+		<slot></slot>
+	</div>
+</div>
diff --git a/src/lib/components/common/Drawer.svelte b/src/lib/components/common/Drawer.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f28b624fb2a25dbfe9dfda7333ea3775933cd3b7
--- /dev/null
+++ b/src/lib/components/common/Drawer.svelte
@@ -0,0 +1,91 @@
+<script lang="ts">
+	import { onDestroy, onMount, createEventDispatcher } from 'svelte';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { fade, fly, slide } from 'svelte/transition';
+
+	const dispatch = createEventDispatcher();
+
+	export let show = false;
+	export let className = '';
+
+	let modalElement = null;
+	let mounted = false;
+
+	const handleKeyDown = (event: KeyboardEvent) => {
+		if (event.key === 'Escape' && isTopModal()) {
+			console.log('Escape');
+			show = false;
+		}
+	};
+
+	const isTopModal = () => {
+		const modals = document.getElementsByClassName('modal');
+		return modals.length && modals[modals.length - 1] === modalElement;
+	};
+
+	onMount(() => {
+		mounted = true;
+	});
+
+	$: if (show && modalElement) {
+		document.body.appendChild(modalElement);
+		window.addEventListener('keydown', handleKeyDown);
+		document.body.style.overflow = 'hidden';
+	} else if (modalElement) {
+		dispatch('close');
+		window.removeEventListener('keydown', handleKeyDown);
+
+		if (document.body.contains(modalElement)) {
+			document.body.removeChild(modalElement);
+			document.body.style.overflow = 'unset';
+		}
+	}
+
+	onDestroy(() => {
+		show = false;
+		if (modalElement) {
+			if (document.body.contains(modalElement)) {
+				document.body.removeChild(modalElement);
+				document.body.style.overflow = 'unset';
+			}
+		}
+	});
+</script>
+
+<!-- svelte-ignore a11y-click-events-have-key-events -->
+<!-- svelte-ignore a11y-no-static-element-interactions -->
+
+<div
+	bind:this={modalElement}
+	class="modal fixed right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-[9999] overflow-hidden overscroll-contain"
+	in:fly={{ y: 100, duration: 100 }}
+	on:mousedown={() => {
+		show = false;
+	}}
+>
+	<div
+		class=" mt-auto max-w-full w-full bg-gray-50 dark:bg-gray-900 dark:text-gray-100 {className} max-h-[100dvh] overflow-y-auto scrollbar-hidden"
+		on:mousedown={(e) => {
+			e.stopPropagation();
+		}}
+	>
+		<slot />
+	</div>
+</div>
+
+<style>
+	.modal-content {
+		animation: scaleUp 0.1s ease-out forwards;
+	}
+
+	@keyframes scaleUp {
+		from {
+			transform: scale(0.985);
+			opacity: 0;
+		}
+		to {
+			transform: scale(1);
+			opacity: 1;
+		}
+	}
+</style>
diff --git a/src/lib/components/common/Dropdown.svelte b/src/lib/components/common/Dropdown.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..b01bceaceb939885ea09f0481191da585e4fc960
--- /dev/null
+++ b/src/lib/components/common/Dropdown.svelte
@@ -0,0 +1,44 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { createEventDispatcher } from 'svelte';
+
+	import { flyAndScale } from '$lib/utils/transitions';
+
+	export let show = false;
+	const dispatch = createEventDispatcher();
+</script>
+
+<DropdownMenu.Root
+	bind:open={show}
+	closeFocus={false}
+	onOpenChange={(state) => {
+		dispatch('change', state);
+	}}
+	typeahead={false}
+>
+	<DropdownMenu.Trigger>
+		<slot />
+	</DropdownMenu.Trigger>
+
+	<slot name="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[130px] rounded-lg px-1 py-1.5 border border-gray-900 z-50 bg-gray-850 text-white"
+			sideOffset={8}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item class="flex items-center px-3 py-2 text-sm  font-medium">
+				<div class="flex items-center">Profile</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item class="flex items-center px-3 py-2 text-sm  font-medium">
+				<div class="flex items-center">Profile</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item class="flex items-center px-3 py-2 text-sm  font-medium">
+				<div class="flex items-center">Profile</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</slot>
+</DropdownMenu.Root>
diff --git a/src/lib/components/common/FileItem.svelte b/src/lib/components/common/FileItem.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..733623c1793c71c907485abd22114e70d9d0e103
--- /dev/null
+++ b/src/lib/components/common/FileItem.svelte
@@ -0,0 +1,126 @@
+<script lang="ts">
+	import { createEventDispatcher, getContext } from 'svelte';
+	import { formatFileSize } from '$lib/utils';
+
+	import FileItemModal from './FileItemModal.svelte';
+	import GarbageBin from '../icons/GarbageBin.svelte';
+	import Spinner from './Spinner.svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	export let className = 'w-60';
+	export let colorClassName = 'bg-white dark:bg-gray-850 border border-gray-50 dark:border-white/5';
+	export let url: string | null = null;
+
+	export let dismissible = false;
+	export let loading = false;
+
+	export let item = null;
+	export let edit = false;
+
+	export let name: string;
+	export let type: string;
+	export let size: number;
+
+	let showModal = false;
+</script>
+
+{#if item}
+	<FileItemModal bind:show={showModal} bind:item {edit} />
+{/if}
+
+<button
+	class="relative group p-1.5 {className} flex items-center {colorClassName} rounded-2xl text-left"
+	type="button"
+	on:click={async () => {
+		if (item?.file?.data?.content) {
+			showModal = !showModal;
+		} else {
+			if (url) {
+				if (type === 'file') {
+					window.open(`${url}/content`, '_blank').focus();
+				} else {
+					window.open(`${url}`, '_blank').focus();
+				}
+			}
+		}
+
+		dispatch('click');
+	}}
+>
+	<div class="p-3 bg-black/20 dark:bg-white/10 text-white rounded-xl">
+		{#if !loading}
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 24 24"
+				fill="currentColor"
+				class=" size-5"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
+					clip-rule="evenodd"
+				/>
+				<path
+					d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
+				/>
+			</svg>
+		{:else}
+			<Spinner />
+		{/if}
+	</div>
+
+	<div class="flex flex-col justify-center -space-y-0.5 ml-1 px-2.5 w-full">
+		<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1 mb-1">
+			{name}
+		</div>
+
+		<div class=" flex justify-between text-gray-500 text-xs line-clamp-1">
+			{#if type === 'file'}
+				{$i18n.t('File')}
+			{:else if type === 'doc'}
+				{$i18n.t('Document')}
+			{:else if type === 'collection'}
+				{$i18n.t('Collection')}
+			{:else}
+				<span class=" capitalize line-clamp-1">{type}</span>
+			{/if}
+			{#if size}
+				<span class="capitalize">{formatFileSize(size)}</span>
+			{/if}
+		</div>
+	</div>
+
+	{#if dismissible}
+		<div class=" absolute -top-1 -right-1">
+			<button
+				class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
+				type="button"
+				on:click|stopPropagation={() => {
+					dispatch('dismiss');
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+
+			<!-- <button
+				class=" p-1 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-full group-hover:visible invisible transition"
+				type="button"
+				on:click={() => {
+				}}
+			>
+				<GarbageBin />
+			</button> -->
+		</div>
+	{/if}
+</button>
diff --git a/src/lib/components/common/FileItemModal.svelte b/src/lib/components/common/FileItemModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6408ad05dad20153f72cccf30eba63a3a56cf7a6
--- /dev/null
+++ b/src/lib/components/common/FileItemModal.svelte
@@ -0,0 +1,108 @@
+<script lang="ts">
+	import { getContext, onMount } from 'svelte';
+	import { formatFileSize, getLineCount } from '$lib/utils';
+
+	const i18n = getContext('i18n');
+
+	import Modal from './Modal.svelte';
+	import XMark from '../icons/XMark.svelte';
+	import Info from '../icons/Info.svelte';
+	import Switch from './Switch.svelte';
+	import Tooltip from './Tooltip.svelte';
+
+	export let item;
+	export let show = false;
+
+	export let edit = false;
+
+	let enableFullContent = false;
+
+	onMount(() => {
+		console.log(item);
+
+		if (item?.context === 'full') {
+			enableFullContent = true;
+		}
+	});
+</script>
+
+<Modal bind:show size="md">
+	<div class="font-primary px-6 py-5 w-full flex flex-col justify-center dark:text-gray-400">
+		<div class=" pb-2">
+			<div class="flex items-start justify-between">
+				<div>
+					<div class=" font-medium text-lg dark:text-gray-100">
+						<a
+							href={item.url ? (item.type === 'file' ? `${item.url}/content` : `${item.url}`) : '#'}
+							target="_blank"
+							class="hover:underline line-clamp-1"
+						>
+							{item?.name ?? 'File'}
+						</a>
+					</div>
+				</div>
+
+				<div>
+					<button
+						on:click={() => {
+							show = false;
+						}}
+					>
+						<XMark />
+					</button>
+				</div>
+			</div>
+
+			<div>
+				<div class="flex flex-col items-center md:flex-row gap-1 justify-between w-full">
+					<div class=" flex flex-wrap text-sm gap-1 text-gray-500">
+						{#if item.size}
+							<div class="capitalize shrink-0">{formatFileSize(item.size)}</div>
+							•
+						{/if}
+
+						{#if item?.file?.data?.content}
+							<div class="capitalize shrink-0">
+								{getLineCount(item?.file?.data?.content ?? '')} extracted lines
+							</div>
+
+							<div class="flex items-center gap-1 shrink-0">
+								<Info />
+
+								Formatting may be inconsistent from source.
+							</div>
+						{/if}
+					</div>
+
+					{#if edit}
+						<div>
+							<Tooltip
+								content={enableFullContent
+									? 'Inject the entire document as context for comprehensive processing, this is recommended for complex queries.'
+									: 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'}
+							>
+								<div class="flex items-center gap-1.5 text-xs">
+									{#if enableFullContent}
+										Using Entire Document
+									{:else}
+										Using Focused Retrieval
+									{/if}
+									<Switch
+										bind:state={enableFullContent}
+										on:change={(e) => {
+											item.context = e.detail ? 'full' : undefined;
+										}}
+									/>
+								</div>
+							</Tooltip>
+						</div>
+					{/if}
+				</div>
+			</div>
+		</div>
+
+		<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
+			{item?.file?.data?.content ?? 'No content'}
+		</div>
+	</div>
+</Modal>
diff --git a/src/lib/components/common/Folder.svelte b/src/lib/components/common/Folder.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ab15075452007b37a892d75ae1885600b9b7f3ca
--- /dev/null
+++ b/src/lib/components/common/Folder.svelte
@@ -0,0 +1,141 @@
+<script>
+	import { getContext, createEventDispatcher, onMount, onDestroy } from 'svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import ChevronDown from '../icons/ChevronDown.svelte';
+	import ChevronRight from '../icons/ChevronRight.svelte';
+	import Collapsible from './Collapsible.svelte';
+
+	export let open = true;
+
+	export let id = '';
+	export let name = '';
+	export let collapsible = true;
+
+	export let className = '';
+
+	let folderElement;
+
+	let draggedOver = false;
+
+	const onDragOver = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		draggedOver = true;
+	};
+
+	const onDrop = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+
+		if (folderElement.contains(e.target)) {
+			console.log('Dropped on the Button');
+
+			if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
+				// Iterate over all items in the DataTransferItemList use functional programming
+				for (const item of Array.from(e.dataTransfer.items)) {
+					// If dropped items aren't files, reject them
+					if (item.kind === 'file') {
+						const file = item.getAsFile();
+						if (file && file.type === 'application/json') {
+							console.log('Dropped file is a JSON file!');
+
+							// Read the JSON file with FileReader
+							const reader = new FileReader();
+							reader.onload = async function (event) {
+								try {
+									const fileContent = JSON.parse(event.target.result);
+									console.log('Parsed JSON Content: ', fileContent);
+									open = true;
+									dispatch('import', fileContent);
+								} catch (error) {
+									console.error('Error parsing JSON file:', error);
+								}
+							};
+
+							// Start reading the file
+							reader.readAsText(file);
+						} else {
+							console.error('Only JSON file types are supported.');
+						}
+					} else {
+						open = true;
+
+						const dataTransfer = e.dataTransfer.getData('text/plain');
+						const data = JSON.parse(dataTransfer);
+
+						console.log(data);
+						dispatch('drop', data);
+					}
+				}
+			}
+
+			draggedOver = false;
+		}
+	};
+
+	const onDragLeave = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+
+		draggedOver = false;
+	};
+
+	onMount(() => {
+		folderElement.addEventListener('dragover', onDragOver);
+		folderElement.addEventListener('drop', onDrop);
+		folderElement.addEventListener('dragleave', onDragLeave);
+	});
+
+	onDestroy(() => {
+		folderElement.addEventListener('dragover', onDragOver);
+		folderElement.removeEventListener('drop', onDrop);
+		folderElement.removeEventListener('dragleave', onDragLeave);
+	});
+</script>
+
+<div bind:this={folderElement} class="relative {className}">
+	{#if draggedOver}
+		<div
+			class="absolute top-0 left-0 w-full h-full rounded-sm bg-[hsla(260,85%,65%,0.1)] bg-opacity-50 dark:bg-opacity-10 z-50 pointer-events-none touch-none"
+		></div>
+	{/if}
+
+	{#if collapsible}
+		<Collapsible
+			bind:open
+			className="w-full "
+			buttonClassName="w-full"
+			on:change={(e) => {
+				dispatch('change', e.detail);
+			}}
+		>
+			<!-- svelte-ignore a11y-no-static-element-interactions -->
+			<div class="w-full">
+				<button
+					class="w-full py-1.5 px-2 rounded-md flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-500 font-medium hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+				>
+					<div class="text-gray-300 dark:text-gray-600">
+						{#if open}
+							<ChevronDown className=" size-3" strokeWidth="2.5" />
+						{:else}
+							<ChevronRight className=" size-3" strokeWidth="2.5" />
+						{/if}
+					</div>
+
+					<div class="translate-y-[0.5px]">
+						{name}
+					</div>
+				</button>
+			</div>
+
+			<div slot="content" class="w-full">
+				<slot></slot>
+			</div>
+		</Collapsible>
+	{:else}
+		<slot></slot>
+	{/if}
+</div>
diff --git a/src/lib/components/common/Image.svelte b/src/lib/components/common/Image.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e6b8f34a23e894591c557fa9cfa2e51cb5b0b856
--- /dev/null
+++ b/src/lib/components/common/Image.svelte
@@ -0,0 +1,25 @@
+<script lang="ts">
+	import { WEBUI_BASE_URL } from '$lib/constants';
+	import ImagePreview from './ImagePreview.svelte';
+
+	export let src = '';
+	export let alt = '';
+
+	export let className = ' w-full';
+
+	let _src = '';
+	$: _src = src.startsWith('/') ? `${WEBUI_BASE_URL}${src}` : src;
+
+	let showImagePreview = false;
+</script>
+
+<button
+	class={className}
+	on:click={() => {
+		showImagePreview = true;
+	}}
+>
+	<img src={_src} {alt} class=" rounded-lg cursor-pointer" draggable="false" data-cy="image" />
+</button>
+
+<ImagePreview bind:show={showImagePreview} src={_src} {alt} />
diff --git a/src/lib/components/common/ImagePreview.svelte b/src/lib/components/common/ImagePreview.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..8f20a32d764d39723d22e3d3e05bcd5b50b35edb
--- /dev/null
+++ b/src/lib/components/common/ImagePreview.svelte
@@ -0,0 +1,111 @@
+<script lang="ts">
+	import { onDestroy, onMount } from 'svelte';
+
+	export let show = false;
+	export let src = '';
+	export let alt = '';
+
+	let mounted = false;
+
+	let previewElement = null;
+
+	const downloadImage = (url, filename, prefixName = '') => {
+		fetch(url)
+			.then((response) => response.blob())
+			.then((blob) => {
+				const objectUrl = window.URL.createObjectURL(blob);
+				const link = document.createElement('a');
+				link.href = objectUrl;
+				link.download = `${prefixName}${filename}`;
+				document.body.appendChild(link);
+				link.click();
+				document.body.removeChild(link);
+				window.URL.revokeObjectURL(objectUrl);
+			})
+			.catch((error) => console.error('Error downloading image:', error));
+	};
+
+	const handleKeyDown = (event: KeyboardEvent) => {
+		if (event.key === 'Escape') {
+			console.log('Escape');
+			show = false;
+		}
+	};
+
+	onMount(() => {
+		mounted = true;
+	});
+
+	$: if (show && previewElement) {
+		document.body.appendChild(previewElement);
+		window.addEventListener('keydown', handleKeyDown);
+		document.body.style.overflow = 'hidden';
+	} else if (previewElement) {
+		window.removeEventListener('keydown', handleKeyDown);
+		document.body.removeChild(previewElement);
+		document.body.style.overflow = 'unset';
+	}
+
+	onDestroy(() => {
+		show = false;
+
+		if (previewElement) {
+			document.body.removeChild(previewElement);
+		}
+	});
+</script>
+
+{#if show}
+	<!-- svelte-ignore a11y-click-events-have-key-events -->
+	<!-- svelte-ignore a11y-no-static-element-interactions -->
+	<div
+		bind:this={previewElement}
+		class="modal fixed top-0 right-0 left-0 bottom-0 bg-black text-white w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain"
+	>
+		<div class=" absolute left-0 w-full flex justify-between select-none">
+			<div>
+				<button
+					class=" p-5"
+					on:click={() => {
+						show = false;
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="2"
+						stroke="currentColor"
+						class="w-6 h-6"
+					>
+						<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
+					</svg>
+				</button>
+			</div>
+
+			<div>
+				<button
+					class=" p-5"
+					on:click={() => {
+						downloadImage(src, src.substring(src.lastIndexOf('/') + 1), alt);
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="w-6 h-6"
+					>
+						<path
+							d="M10.75 2.75a.75.75 0 0 0-1.5 0v8.614L6.295 8.235a.75.75 0 1 0-1.09 1.03l4.25 4.5a.75.75 0 0 0 1.09 0l4.25-4.5a.75.75 0 0 0-1.09-1.03l-2.955 3.129V2.75Z"
+						/>
+						<path
+							d="M3.5 12.75a.75.75 0 0 0-1.5 0v2.5A2.75 2.75 0 0 0 4.75 18h10.5A2.75 2.75 0 0 0 18 15.25v-2.5a.75.75 0 0 0-1.5 0v2.5c0 .69-.56 1.25-1.25 1.25H4.75c-.69 0-1.25-.56-1.25-1.25v-2.5Z"
+						/>
+					</svg>
+				</button>
+			</div>
+		</div>
+		<img {src} {alt} class=" mx-auto h-full object-scale-down select-none" draggable="false" />
+	</div>
+{/if}
diff --git a/src/lib/components/common/Loader.svelte b/src/lib/components/common/Loader.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ac7ecaf28a5dff995a41107e7166a33bd6d33660
--- /dev/null
+++ b/src/lib/components/common/Loader.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+	import { createEventDispatcher, onDestroy, onMount } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	let loaderElement: HTMLElement;
+
+	let observer;
+	let intervalId;
+
+	onMount(() => {
+		observer = new IntersectionObserver(
+			(entries, observer) => {
+				entries.forEach((entry) => {
+					if (entry.isIntersecting) {
+						intervalId = setInterval(() => {
+							dispatch('visible');
+						}, 100);
+						// dispatch('visible');
+						// observer.unobserve(loaderElement); // Stop observing until content is loaded
+					} else {
+						clearInterval(intervalId);
+					}
+				});
+			},
+			{
+				root: null, // viewport
+				rootMargin: '0px',
+				threshold: 0.1 // When 10% of the loader is visible
+			}
+		);
+
+		observer.observe(loaderElement);
+	});
+
+	onDestroy(() => {
+		observer.disconnect();
+
+		if (intervalId) {
+			clearInterval(intervalId);
+		}
+	});
+</script>
+
+<div bind:this={loaderElement}>
+	<slot />
+</div>
diff --git a/src/lib/components/common/Modal.svelte b/src/lib/components/common/Modal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9d77f9e2f13a902c7f56bbdd10f3aec56608d2b0
--- /dev/null
+++ b/src/lib/components/common/Modal.svelte
@@ -0,0 +1,103 @@
+<script lang="ts">
+	import { onDestroy, onMount } from 'svelte';
+	import { fade } from 'svelte/transition';
+
+	import { flyAndScale } from '$lib/utils/transitions';
+
+	export let show = true;
+	export let size = 'md';
+	export let className = 'bg-gray-50 dark:bg-gray-900  rounded-2xl';
+
+	let modalElement = null;
+	let mounted = false;
+
+	const sizeToWidth = (size) => {
+		if (size === 'full') {
+			return 'w-full';
+		}
+		if (size === 'xs') {
+			return 'w-[16rem]';
+		} else if (size === 'sm') {
+			return 'w-[30rem]';
+		} else if (size === 'md') {
+			return 'w-[48rem]';
+		} else {
+			return 'w-[56rem]';
+		}
+	};
+
+	const handleKeyDown = (event: KeyboardEvent) => {
+		if (event.key === 'Escape' && isTopModal()) {
+			console.log('Escape');
+			show = false;
+		}
+	};
+
+	const isTopModal = () => {
+		const modals = document.getElementsByClassName('modal');
+		return modals.length && modals[modals.length - 1] === modalElement;
+	};
+
+	onMount(() => {
+		mounted = true;
+	});
+
+	$: if (show && modalElement) {
+		document.body.appendChild(modalElement);
+		window.addEventListener('keydown', handleKeyDown);
+		document.body.style.overflow = 'hidden';
+	} else if (modalElement) {
+		window.removeEventListener('keydown', handleKeyDown);
+		document.body.removeChild(modalElement);
+		document.body.style.overflow = 'unset';
+	}
+
+	onDestroy(() => {
+		show = false;
+		if (modalElement) {
+			document.body.removeChild(modalElement);
+		}
+	});
+</script>
+
+{#if show}
+	<!-- svelte-ignore a11y-click-events-have-key-events -->
+	<!-- svelte-ignore a11y-no-static-element-interactions -->
+	<div
+		bind:this={modalElement}
+		class="modal fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-[9999] overflow-hidden overscroll-contain"
+		in:fade={{ duration: 10 }}
+		on:mousedown={() => {
+			show = false;
+		}}
+	>
+		<div
+			class=" m-auto max-w-full {sizeToWidth(size)} {size !== 'full'
+				? 'mx-2'
+				: ''} shadow-3xl max-h-[100dvh] overflow-y-auto scrollbar-hidden {className}"
+			in:flyAndScale
+			on:mousedown={(e) => {
+				e.stopPropagation();
+			}}
+		>
+			<slot />
+		</div>
+	</div>
+{/if}
+
+<style>
+	.modal-content {
+		animation: scaleUp 0.1s ease-out forwards;
+	}
+
+	@keyframes scaleUp {
+		from {
+			transform: scale(0.985);
+			opacity: 0;
+		}
+		to {
+			transform: scale(1);
+			opacity: 1;
+		}
+	}
+</style>
diff --git a/src/lib/components/common/Overlay.svelte b/src/lib/components/common/Overlay.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d38358212ae03a767b4718c74124af0d78b4bffb
--- /dev/null
+++ b/src/lib/components/common/Overlay.svelte
@@ -0,0 +1,33 @@
+<script>
+	import Spinner from './Spinner.svelte';
+
+	export let show = false;
+	export let content = '';
+
+	export let opacity = 1;
+</script>
+
+<div class="relative">
+	{#if show}
+		<div class="absolute w-full h-full flex">
+			<div
+				class="absolute rounded"
+				style="inset: -10px; opacity: {opacity}; backdrop-filter: blur(5px);"
+			/>
+
+			<div class="flex w-full flex-col justify-center">
+				<div class=" py-3">
+					<Spinner className="ml-2" />
+				</div>
+
+				{#if content !== ''}
+					<div class="text-center text-gray-100 text-xs font-medium z-50">
+						{content}
+					</div>
+				{/if}
+			</div>
+		</div>
+	{/if}
+
+	<slot />
+</div>
diff --git a/src/lib/components/common/Pagination.svelte b/src/lib/components/common/Pagination.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e1ee4766dcf8441ccf3a9b8e8fb27f8fdf02a3f2
--- /dev/null
+++ b/src/lib/components/common/Pagination.svelte
@@ -0,0 +1,42 @@
+<script lang="ts">
+	import { Pagination } from 'bits-ui';
+	import { createEventDispatcher } from 'svelte';
+
+	import ChevronLeft from '../icons/ChevronLeft.svelte';
+	import ChevronRight from '../icons/ChevronRight.svelte';
+
+	export let page = 0;
+	export let count = 0;
+	export let perPage = 20;
+</script>
+
+<div class="flex justify-center">
+	<Pagination.Root bind:page {count} {perPage} let:pages>
+		<div class="my-2 flex items-center">
+			<Pagination.PrevButton
+				class="mr-[25px] inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-50 dark:hover:bg-gray-850 active:scale-98 disabled:cursor-not-allowed disabled:text-gray-400 dark:disabled:text-gray-700 hover:disabled:bg-transparent dark:hover:disabled:bg-transparent"
+			>
+				<ChevronLeft className="size-4" strokeWidth="2" />
+			</Pagination.PrevButton>
+			<div class="flex items-center gap-2.5">
+				{#each pages as page (page.key)}
+					{#if page.type === 'ellipsis'}
+						<div class="text-sm font-medium text-foreground-alt">...</div>
+					{:else}
+						<Pagination.Page
+							{page}
+							class="inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-50 dark:hover:bg-gray-850 text-sm font-medium hover:bg-dark-10 active:scale-98 disabled:cursor-not-allowed disabled:opacity-50 hover:disabled:bg-transparent data-[selected]:bg-gray-50 data-[selected]:text-gray-700 data-[selected]:hover:bg-gray-100 dark:data-[selected]:bg-gray-850 dark:data-[selected]:text-gray-50 dark:data-[selected]:hover:bg-gray-800 transition"
+						>
+							{page.value}
+						</Pagination.Page>
+					{/if}
+				{/each}
+			</div>
+			<Pagination.NextButton
+				class="ml-[25px]  inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-50 dark:hover:bg-gray-850 active:scale-98 disabled:cursor-not-allowed disabled:text-gray-400 dark:disabled:text-gray-700 hover:disabled:bg-transparent dark:hover:disabled:bg-transparent"
+			>
+				<ChevronRight className="size-4" strokeWidth="2" />
+			</Pagination.NextButton>
+		</div>
+	</Pagination.Root>
+</div>
diff --git a/src/lib/components/common/RichTextInput.svelte b/src/lib/components/common/RichTextInput.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d6c4d7e40dcec9d8803c4dd014081a99b7a21ddb
--- /dev/null
+++ b/src/lib/components/common/RichTextInput.svelte
@@ -0,0 +1,498 @@
+<script lang="ts">
+	import { onDestroy, onMount } from 'svelte';
+	import { createEventDispatcher } from 'svelte';
+	const eventDispatch = createEventDispatcher();
+
+	import { EditorState, Plugin, TextSelection } from 'prosemirror-state';
+	import { EditorView, Decoration, DecorationSet } from 'prosemirror-view';
+	import { undo, redo, history } from 'prosemirror-history';
+	import {
+		schema,
+		defaultMarkdownParser,
+		MarkdownParser,
+		defaultMarkdownSerializer
+	} from 'prosemirror-markdown';
+
+	import {
+		inputRules,
+		wrappingInputRule,
+		textblockTypeInputRule,
+		InputRule
+	} from 'prosemirror-inputrules'; // Import input rules
+	import { splitListItem, liftListItem, sinkListItem } from 'prosemirror-schema-list'; // Import from prosemirror-schema-list
+	import { keymap } from 'prosemirror-keymap';
+	import { baseKeymap, chainCommands } from 'prosemirror-commands';
+	import { DOMParser, DOMSerializer, Schema, Fragment } from 'prosemirror-model';
+
+	export let className = 'input-prose';
+	export let shiftEnter = false;
+
+	export let id = '';
+	export let value = '';
+	export let placeholder = 'Type here...';
+	export let trim = false;
+
+	let element: HTMLElement; // Element where ProseMirror will attach
+	let state;
+	let view;
+
+	// Plugin to add placeholder when the content is empty
+	function placeholderPlugin(placeholder: string) {
+		return new Plugin({
+			props: {
+				decorations(state) {
+					const doc = state.doc;
+					if (
+						doc.childCount === 1 &&
+						doc.firstChild.isTextblock &&
+						doc.firstChild?.textContent === ''
+					) {
+						// If there's nothing in the editor, show the placeholder decoration
+						const decoration = Decoration.node(0, doc.content.size, {
+							'data-placeholder': placeholder,
+							class: 'placeholder'
+						});
+						return DecorationSet.create(doc, [decoration]);
+					}
+					return DecorationSet.empty;
+				}
+			}
+		});
+	}
+
+	function unescapeMarkdown(text: string): string {
+		return text
+			.replace(/\\([\\`*{}[\]()#+\-.!_>])/g, '$1') // unescape backslashed characters
+			.replace(/&amp;/g, '&')
+			.replace(/</g, '<')
+			.replace(/>/g, '>')
+			.replace(/&quot;/g, '"')
+			.replace(/&#39;/g, "'");
+	}
+
+	// Custom parsing rule that creates proper paragraphs for newlines and empty lines
+	function markdownToProseMirrorDoc(markdown: string) {
+		// Split the markdown into lines
+		const lines = markdown.split('\n\n');
+
+		// Create an array to hold our paragraph nodes
+		const paragraphs = [];
+
+		// Process each line
+		lines.forEach((line) => {
+			if (line.trim() === '') {
+				// For empty lines, create an empty paragraph
+				paragraphs.push(schema.nodes.paragraph.create());
+			} else {
+				// For non-empty lines, parse as usual
+				const doc = defaultMarkdownParser.parse(line);
+				// Extract the content of the parsed document
+				doc.content.forEach((node) => {
+					paragraphs.push(node);
+				});
+			}
+		});
+
+		// Create a new document with these paragraphs
+		return schema.node('doc', null, paragraphs);
+	}
+
+	// Create a custom serializer for paragraphs
+	// Custom paragraph serializer to preserve newlines for empty paragraphs (empty block).
+	function serializeParagraph(state, node: Node) {
+		const content = node.textContent.trim();
+
+		// If the paragraph is empty, just add an empty line.
+		if (content === '') {
+			state.write('\n\n');
+		} else {
+			state.renderInline(node);
+			state.closeBlock(node);
+		}
+	}
+
+	const customMarkdownSerializer = new defaultMarkdownSerializer.constructor(
+		{
+			...defaultMarkdownSerializer.nodes,
+
+			paragraph: (state, node) => {
+				serializeParagraph(state, node); // Use custom paragraph serialization
+			}
+
+			// Customize other block formats if needed
+		},
+
+		// Copy marks directly from the original serializer (or customize them if necessary)
+		defaultMarkdownSerializer.marks
+	);
+
+	// Utility function to convert ProseMirror content back to markdown text
+	function serializeEditorContent(doc) {
+		const markdown = customMarkdownSerializer.serialize(doc);
+		if (trim) {
+			return unescapeMarkdown(markdown).trim();
+		} else {
+			return unescapeMarkdown(markdown);
+		}
+	}
+
+	// ---- Input Rules ----
+	// Input rule for heading (e.g., # Headings)
+	function headingRule(schema) {
+		return textblockTypeInputRule(/^(#{1,6})\s$/, schema.nodes.heading, (match) => ({
+			level: match[1].length
+		}));
+	}
+
+	// Input rule for bullet list (e.g., `- item`)
+	function bulletListRule(schema) {
+		return wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list);
+	}
+
+	// Input rule for ordered list (e.g., `1. item`)
+	function orderedListRule(schema) {
+		return wrappingInputRule(/^(\d+)\.\s$/, schema.nodes.ordered_list, (match) => ({
+			order: +match[1]
+		}));
+	}
+
+	// Custom input rules for Bold/Italic (using * or _)
+	function markInputRule(regexp: RegExp, markType: any) {
+		return new InputRule(regexp, (state, match, start, end) => {
+			const { tr } = state;
+			if (match) {
+				tr.replaceWith(start, end, schema.text(match[1], [markType.create()]));
+			}
+			return tr;
+		});
+	}
+
+	function boldRule(schema) {
+		return markInputRule(/(?<=^|\s)\*([^*]+)\*(?=\s|$)/, schema.marks.strong);
+	}
+
+	function italicRule(schema) {
+		// Using lookbehind and lookahead to prevent the space from being consumed
+		return markInputRule(/(?<=^|\s)_([^*_]+)_(?=\s|$)/, schema.marks.em);
+	}
+
+	// Initialize Editor State and View
+	function afterSpacePress(state, dispatch) {
+		// Get the position right after the space was naturally inserted by the browser.
+		let { from, to, empty } = state.selection;
+
+		if (dispatch && empty) {
+			let tr = state.tr;
+
+			// Check for any active marks at `from - 1` (the space we just inserted)
+			const storedMarks = state.storedMarks || state.selection.$from.marks();
+
+			const hasBold = storedMarks.some((mark) => mark.type === state.schema.marks.strong);
+			const hasItalic = storedMarks.some((mark) => mark.type === state.schema.marks.em);
+
+			// Remove marks from the space character (marks applied to the space character will be marked as false)
+			if (hasBold) {
+				tr = tr.removeMark(from - 1, from, state.schema.marks.strong);
+			}
+			if (hasItalic) {
+				tr = tr.removeMark(from - 1, from, state.schema.marks.em);
+			}
+
+			// Dispatch the resulting transaction to update the editor state
+			dispatch(tr);
+		}
+
+		return true;
+	}
+
+	function toggleMark(markType) {
+		return (state, dispatch) => {
+			const { from, to } = state.selection;
+			if (state.doc.rangeHasMark(from, to, markType)) {
+				if (dispatch) dispatch(state.tr.removeMark(from, to, markType));
+				return true;
+			} else {
+				if (dispatch) dispatch(state.tr.addMark(from, to, markType.create()));
+				return true;
+			}
+		};
+	}
+
+	function isInList(state) {
+		const { $from } = state.selection;
+		return (
+			$from.parent.type === schema.nodes.paragraph && $from.node(-1).type === schema.nodes.list_item
+		);
+	}
+
+	function isEmptyListItem(state) {
+		const { $from } = state.selection;
+		return isInList(state) && $from.parent.content.size === 0 && $from.node(-1).childCount === 1;
+	}
+
+	function exitList(state, dispatch) {
+		return liftListItem(schema.nodes.list_item)(state, dispatch);
+	}
+
+	function findNextTemplate(doc, from = 0) {
+		const patterns = [
+			{ start: '[', end: ']' },
+			{ start: '{{', end: '}}' }
+		];
+
+		let result = null;
+
+		doc.nodesBetween(from, doc.content.size, (node, pos) => {
+			if (result) return false; // Stop if we've found a match
+			if (node.isText) {
+				const text = node.text;
+				let index = Math.max(0, from - pos);
+				while (index < text.length) {
+					for (const pattern of patterns) {
+						if (text.startsWith(pattern.start, index)) {
+							const endIndex = text.indexOf(pattern.end, index + pattern.start.length);
+							if (endIndex !== -1) {
+								result = {
+									from: pos + index,
+									to: pos + endIndex + pattern.end.length
+								};
+								return false; // Stop searching
+							}
+						}
+					}
+					index++;
+				}
+			}
+		});
+
+		return result;
+	}
+
+	function selectNextTemplate(state, dispatch) {
+		const { doc, selection } = state;
+		const from = selection.to;
+		let template = findNextTemplate(doc, from);
+
+		if (!template) {
+			// If not found, search from the beginning
+			template = findNextTemplate(doc, 0);
+		}
+
+		if (template) {
+			if (dispatch) {
+				const tr = state.tr.setSelection(TextSelection.create(doc, template.from, template.to));
+				dispatch(tr);
+			}
+			return true;
+		}
+		return false;
+	}
+
+	// Replace tabs with four spaces
+	function handleTabIndentation(text: string): string {
+		// Replace each tab character with four spaces
+		return text.replace(/\t/g, '    ');
+	}
+
+	onMount(() => {
+		const initialDoc = markdownToProseMirrorDoc(value || ''); // Convert the initial content
+
+		state = EditorState.create({
+			doc: initialDoc,
+			schema,
+			plugins: [
+				history(),
+				placeholderPlugin(placeholder),
+				inputRules({
+					rules: [
+						headingRule(schema), // Handle markdown-style headings (# H1, ## H2, etc.)
+						bulletListRule(schema), // Handle `-` or `*` input to start bullet list
+						orderedListRule(schema), // Handle `1.` input to start ordered list
+						boldRule(schema), // Bold input rule
+						italicRule(schema) // Italic input rule
+					]
+				}),
+				keymap({
+					...baseKeymap,
+					'Mod-z': undo,
+					'Mod-y': redo,
+					Enter: (state, dispatch, view) => {
+						if (shiftEnter) {
+							eventDispatch('enter');
+							return true;
+						}
+						return chainCommands(
+							(state, dispatch, view) => {
+								if (isEmptyListItem(state)) {
+									return exitList(state, dispatch);
+								}
+								return false;
+							},
+							(state, dispatch, view) => {
+								if (isInList(state)) {
+									return splitListItem(schema.nodes.list_item)(state, dispatch);
+								}
+								return false;
+							},
+							baseKeymap.Enter
+						)(state, dispatch, view);
+					},
+
+					'Shift-Enter': (state, dispatch, view) => {
+						if (shiftEnter) {
+							return chainCommands(
+								(state, dispatch, view) => {
+									if (isEmptyListItem(state)) {
+										return exitList(state, dispatch);
+									}
+									return false;
+								},
+								(state, dispatch, view) => {
+									if (isInList(state)) {
+										return splitListItem(schema.nodes.list_item)(state, dispatch);
+									}
+									return false;
+								},
+								baseKeymap.Enter
+							)(state, dispatch, view);
+						} else {
+							return baseKeymap.Enter(state, dispatch, view);
+						}
+						return false;
+					},
+
+					// Prevent default tab navigation and provide indent/outdent behavior inside lists:
+					Tab: chainCommands((state, dispatch, view) => {
+						const { $from } = state.selection;
+						if (isInList(state)) {
+							return sinkListItem(schema.nodes.list_item)(state, dispatch);
+						} else {
+							return selectNextTemplate(state, dispatch);
+						}
+						return true; // Prevent Tab from moving the focus
+					}),
+					'Shift-Tab': (state, dispatch, view) => {
+						const { $from } = state.selection;
+						if (isInList(state)) {
+							return liftListItem(schema.nodes.list_item)(state, dispatch);
+						}
+						return true; // Prevent Shift-Tab from moving the focus
+					},
+					'Mod-b': toggleMark(schema.marks.strong),
+					'Mod-i': toggleMark(schema.marks.em)
+				})
+			]
+		});
+
+		view = new EditorView(element, {
+			state,
+			dispatchTransaction(transaction) {
+				// Update editor state
+				let newState = view.state.apply(transaction);
+				view.updateState(newState);
+
+				value = serializeEditorContent(newState.doc); // Convert ProseMirror content to markdown text
+				eventDispatch('input', { value });
+			},
+			handleDOMEvents: {
+				focus: (view, event) => {
+					eventDispatch('focus', { event });
+					return false;
+				},
+				keypress: (view, event) => {
+					eventDispatch('keypress', { event });
+					return false;
+				},
+				keydown: (view, event) => {
+					eventDispatch('keydown', { event });
+					return false;
+				},
+				paste: (view, event) => {
+					if (event.clipboardData) {
+						// Extract plain text from clipboard and paste it without formatting
+						const plainText = event.clipboardData.getData('text/plain');
+						if (plainText) {
+							const modifiedText = handleTabIndentation(plainText);
+							console.log(modifiedText);
+
+							// Replace the current selection with the plain text content
+							const tr = view.state.tr.replaceSelectionWith(
+								view.state.schema.text(modifiedText),
+								false
+							);
+							view.dispatch(tr.scrollIntoView());
+							event.preventDefault(); // Prevent the default paste behavior
+							return true;
+						}
+
+						// Check if the pasted content contains image files
+						const hasImageFile = Array.from(event.clipboardData.files).some((file) =>
+							file.type.startsWith('image/')
+						);
+
+						// Check for image in dataTransfer items (for cases where files are not available)
+						const hasImageItem = Array.from(event.clipboardData.items).some((item) =>
+							item.type.startsWith('image/')
+						);
+						if (hasImageFile) {
+							// If there's an image, dispatch the event to the parent
+							eventDispatch('paste', { event });
+							event.preventDefault();
+							return true;
+						}
+
+						if (hasImageItem) {
+							// If there's an image item, dispatch the event to the parent
+							eventDispatch('paste', { event });
+							event.preventDefault();
+							return true;
+						}
+					}
+
+					// For all other cases (text, formatted text, etc.), let ProseMirror handle it
+					return false;
+				},
+				// Handle space input after browser has completed it
+				keyup: (view, event) => {
+					if (event.key === ' ' && event.code === 'Space') {
+						afterSpacePress(view.state, view.dispatch);
+					}
+					return false;
+				}
+			},
+			attributes: { id }
+		});
+	});
+
+	// Reinitialize the editor if the value is externally changed (i.e. when `value` is updated)
+	$: if (view && value !== serializeEditorContent(view.state.doc)) {
+		const newDoc = markdownToProseMirrorDoc(value || '');
+
+		const newState = EditorState.create({
+			doc: newDoc,
+			schema,
+			plugins: view.state.plugins,
+			selection: TextSelection.atEnd(newDoc) // This sets the cursor at the end
+		});
+		view.updateState(newState);
+
+		if (value !== '') {
+			// After updating the state, try to find and select the next template
+			setTimeout(() => {
+				const templateFound = selectNextTemplate(view.state, view.dispatch);
+				if (!templateFound) {
+					// If no template found, set cursor at the end
+					const endPos = view.state.doc.content.size;
+					view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, endPos)));
+				}
+			}, 0);
+		}
+	}
+
+	// Destroy ProseMirror instance on unmount
+	onDestroy(() => {
+		view?.destroy();
+	});
+</script>
+
+<div bind:this={element} class="relative w-full min-w-full h-full min-h-fit {className}"></div>
diff --git a/src/lib/components/common/SVGPanZoom.svelte b/src/lib/components/common/SVGPanZoom.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e576ffb06c39d1289f504375049b39340a203906
--- /dev/null
+++ b/src/lib/components/common/SVGPanZoom.svelte
@@ -0,0 +1,53 @@
+<script lang="ts">
+	import { onMount, getContext } from 'svelte';
+	const i18n = getContext('i18n');
+
+	import panzoom from 'panzoom';
+
+	import DOMPurify from 'dompurify';
+	import DocumentDuplicate from '../icons/DocumentDuplicate.svelte';
+	import { copyToClipboard } from '$lib/utils';
+	import { toast } from 'svelte-sonner';
+	import Tooltip from './Tooltip.svelte';
+	import Clipboard from '../icons/Clipboard.svelte';
+
+	export let className = '';
+	export let svg = '';
+	export let content = '';
+
+	let instance;
+
+	let sceneParentElement: HTMLElement;
+	let sceneElement: HTMLElement;
+
+	$: if (sceneElement) {
+		instance = panzoom(sceneElement, {
+			bounds: true,
+			boundsPadding: 0.1,
+
+			zoomSpeed: 0.065
+		});
+	}
+</script>
+
+<div bind:this={sceneParentElement} class="relative {className}">
+	<div bind:this={sceneElement} class="flex h-full max-h-full justify-center items-center">
+		{@html svg}
+	</div>
+
+	{#if content}
+		<div class=" absolute top-1 right-1">
+			<Tooltip content={$i18n.t('Copy to clipboard')}>
+				<button
+					class="p-1.5 rounded-lg border border-gray-100 dark:border-none dark:bg-gray-850 hover:bg-gray-50 dark:hover:bg-gray-800 transition"
+					on:click={() => {
+						copyToClipboard(content);
+						toast.success($i18n.t('Copied to clipboard'));
+					}}
+				>
+					<Clipboard className=" size-4" strokeWidth="1.5" />
+				</button>
+			</Tooltip>
+		</div>
+	{/if}
+</div>
diff --git a/src/lib/components/common/Selector.svelte b/src/lib/components/common/Selector.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a449f5ca80d5d082e8a122e5c2d05b4d4571bf05
--- /dev/null
+++ b/src/lib/components/common/Selector.svelte
@@ -0,0 +1,95 @@
+<script lang="ts">
+	import { Select } from 'bits-ui';
+
+	import { flyAndScale } from '$lib/utils/transitions';
+
+	import { createEventDispatcher } from 'svelte';
+	import ChevronDown from '../icons/ChevronDown.svelte';
+	import Check from '../icons/Check.svelte';
+	import Search from '../icons/Search.svelte';
+
+	const dispatch = createEventDispatcher();
+
+	export let value = '';
+	export let placeholder = 'Select a model';
+	export let searchEnabled = true;
+	export let searchPlaceholder = 'Search a model';
+
+	export let items = [
+		{ value: 'mango', label: 'Mango' },
+		{ value: 'watermelon', label: 'Watermelon' },
+		{ value: 'apple', label: 'Apple' },
+		{ value: 'pineapple', label: 'Pineapple' },
+		{ value: 'orange', label: 'Orange' }
+	];
+
+	let searchValue = '';
+
+	$: filteredItems = searchValue
+		? items.filter((item) => item.value.toLowerCase().includes(searchValue.toLowerCase()))
+		: items;
+</script>
+
+<Select.Root
+	{items}
+	onOpenChange={() => {
+		searchValue = '';
+	}}
+	selected={items.find((item) => item.value === value)}
+	onSelectedChange={(selectedItem) => {
+		value = selectedItem.value;
+	}}
+>
+	<Select.Trigger class="relative w-full" aria-label={placeholder}>
+		<Select.Value
+			class="inline-flex h-input px-0.5 w-full outline-none bg-transparent truncate text-lg font-semibold placeholder-gray-400  focus:outline-none"
+			{placeholder}
+		/>
+		<ChevronDown className="absolute end-2 top-1/2 -translate-y-[45%] size-3.5" strokeWidth="2.5" />
+	</Select.Trigger>
+	<Select.Content
+		class="w-full rounded-lg  bg-white dark:bg-gray-900 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-700/40  outline-none"
+		transition={flyAndScale}
+		sideOffset={4}
+	>
+		<slot>
+			{#if searchEnabled}
+				<div class="flex items-center gap-2.5 px-5 mt-3.5 mb-3">
+					<Search className="size-4" strokeWidth="2.5" />
+
+					<input
+						bind:value={searchValue}
+						class="w-full text-sm bg-transparent outline-none"
+						placeholder={searchPlaceholder}
+					/>
+				</div>
+
+				<hr class="border-gray-100 dark:border-gray-800" />
+			{/if}
+
+			<div class="px-3 my-2 max-h-80 overflow-y-auto">
+				{#each filteredItems as item}
+					<Select.Item
+						class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm  text-gray-700 dark:text-gray-100  outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+						value={item.value}
+						label={item.label}
+					>
+						{item.label}
+
+						{#if value === item.value}
+							<div class="ml-auto">
+								<Check />
+							</div>
+						{/if}
+					</Select.Item>
+				{:else}
+					<div>
+						<div class="block px-5 py-2 text-sm text-gray-700 dark:text-gray-100">
+							No results found
+						</div>
+					</div>
+				{/each}
+			</div>
+		</slot>
+	</Select.Content>
+</Select.Root>
diff --git a/src/lib/components/common/SensitiveInput.svelte b/src/lib/components/common/SensitiveInput.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..76a6266500f7197c9fc24fb2a53710a890f1605e
--- /dev/null
+++ b/src/lib/components/common/SensitiveInput.svelte
@@ -0,0 +1,63 @@
+<script lang="ts">
+	export let value: string = '';
+	export let placeholder = '';
+	export let required = true;
+	export let readOnly = false;
+	export let outerClassName = 'flex flex-1';
+	export let inputClassName =
+		'w-full rounded-l-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none';
+	export let showButtonClassName = 'px-2 transition rounded-r-lg bg-gray-50 dark:bg-gray-850';
+
+	let show = false;
+</script>
+
+<div class={outerClassName}>
+	<input
+		class={`${inputClassName} ${show ? '' : 'password'}`}
+		{placeholder}
+		bind:value
+		required={required && !readOnly}
+		disabled={readOnly}
+		autocomplete="off"
+		type="text"
+	/>
+	<button
+		class={showButtonClassName}
+		on:click={(e) => {
+			e.preventDefault();
+			show = !show;
+		}}
+	>
+		{#if show}
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M3.28 2.22a.75.75 0 0 0-1.06 1.06l10.5 10.5a.75.75 0 1 0 1.06-1.06l-1.322-1.323a7.012 7.012 0 0 0 2.16-3.11.87.87 0 0 0 0-.567A7.003 7.003 0 0 0 4.82 3.76l-1.54-1.54Zm3.196 3.195 1.135 1.136A1.502 1.502 0 0 1 9.45 8.389l1.136 1.135a3 3 0 0 0-4.109-4.109Z"
+					clip-rule="evenodd"
+				/>
+				<path
+					d="m7.812 10.994 1.816 1.816A7.003 7.003 0 0 1 1.38 8.28a.87.87 0 0 1 0-.566 6.985 6.985 0 0 1 1.113-2.039l2.513 2.513a3 3 0 0 0 2.806 2.806Z"
+				/>
+			</svg>
+		{:else}
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" />
+				<path
+					fill-rule="evenodd"
+					d="M1.38 8.28a.87.87 0 0 1 0-.566 7.003 7.003 0 0 1 13.238.006.87.87 0 0 1 0 .566A7.003 7.003 0 0 1 1.379 8.28ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		{/if}
+	</button>
+</div>
diff --git a/src/lib/components/common/Sidebar.svelte b/src/lib/components/common/Sidebar.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..0902954b3d23766e54c2da386ec6699f965d7b96
--- /dev/null
+++ b/src/lib/components/common/Sidebar.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+	import { quadInOut, quintIn } from 'svelte/easing';
+	import { fade, slide } from 'svelte/transition';
+
+	export let show = false;
+	export let side = 'right';
+	export let width = '200px';
+
+	export let className = '';
+</script>
+
+{#if show}
+	<!-- svelte-ignore a11y-no-static-element-interactions -->
+	<div
+		class="absolute z-20 top-0 right-0 left-0 bottom-0 bg-white/20 dark:bg-black/5 w-full min-h-full h-full flex justify-center overflow-hidden overscroll-contain"
+		on:mousedown={() => {
+			show = false;
+		}}
+		transition:fade
+	/>
+
+	<div
+		class="absolute z-30 shadow-xl {side === 'right' ? 'right-0' : 'left-0'} top-0 bottom-0"
+		transition:slide={{ easing: quadInOut, axis: side === 'right' ? 'x' : 'y' }}
+	>
+		<div class="{className} h-full" style="width: {show ? width : '0px'}">
+			<slot />
+		</div>
+	</div>
+{/if}
diff --git a/src/lib/components/common/Spinner.svelte b/src/lib/components/common/Spinner.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a22f56dcb2c0d9c4451f42876902777db0b128ad
--- /dev/null
+++ b/src/lib/components/common/Spinner.svelte
@@ -0,0 +1,25 @@
+<script lang="ts">
+	export let className: string = 'size-5';
+</script>
+
+<div class="flex justify-center text-center">
+	<svg class={className} viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"
+		><style>
+			.spinner_ajPY {
+				transform-origin: center;
+				animation: spinner_AtaB 0.75s infinite linear;
+			}
+			@keyframes spinner_AtaB {
+				100% {
+					transform: rotate(360deg);
+				}
+			}
+		</style><path
+			d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+			opacity=".25"
+		/><path
+			d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+			class="spinner_ajPY"
+		/></svg
+	>
+</div>
diff --git a/src/lib/components/common/Switch.svelte b/src/lib/components/common/Switch.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..829b74629523becc3910a4dab60bc79465237b61
--- /dev/null
+++ b/src/lib/components/common/Switch.svelte
@@ -0,0 +1,22 @@
+<script lang="ts">
+	import { createEventDispatcher, tick } from 'svelte';
+	import { Switch } from 'bits-ui';
+	export let state = true;
+
+	const dispatch = createEventDispatcher();
+</script>
+
+<Switch.Root
+	bind:checked={state}
+	onCheckedChange={async (e) => {
+		await tick();
+		dispatch('change', e);
+	}}
+	class="flex h-5 min-h-5 w-9 shrink-0 cursor-pointer items-center rounded-full px-[3px] mx-[1px] transition  {state
+		? ' bg-emerald-600'
+		: 'bg-gray-200 dark:bg-transparent'} outline outline-1 outline-gray-100 dark:outline-gray-800"
+>
+	<Switch.Thumb
+		class="pointer-events-none block size-4 shrink-0 rounded-full bg-white transition-transform data-[state=checked]:translate-x-3.5 data-[state=unchecked]:translate-x-0 data-[state=unchecked]:shadow-mini "
+	/>
+</Switch.Root>
diff --git a/src/lib/components/common/Tags.svelte b/src/lib/components/common/Tags.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..0f19eed9d82d0db8a64a8d7f776896521c3a2dc9
--- /dev/null
+++ b/src/lib/components/common/Tags.svelte
@@ -0,0 +1,26 @@
+<script lang="ts">
+	import TagInput from './Tags/TagInput.svelte';
+	import TagList from './Tags/TagList.svelte';
+	import { getContext, createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	const i18n = getContext('i18n');
+
+	export let tags = [];
+</script>
+
+<div class="flex flex-row flex-wrap gap-1 line-clamp-1">
+	<TagList
+		{tags}
+		on:delete={(e) => {
+			dispatch('delete', e.detail);
+		}}
+	/>
+
+	<TagInput
+		label={tags.length == 0 ? $i18n.t('Add Tags') : ''}
+		on:add={(e) => {
+			dispatch('add', e.detail);
+		}}
+	/>
+</div>
diff --git a/src/lib/components/common/Tags/TagInput.svelte b/src/lib/components/common/Tags/TagInput.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d854a9c305c86023a884cac3c2e3192b27784ab2
--- /dev/null
+++ b/src/lib/components/common/Tags/TagInput.svelte
@@ -0,0 +1,88 @@
+<script lang="ts">
+	import { createEventDispatcher, getContext } from 'svelte';
+	import { tags } from '$lib/stores';
+	import { toast } from 'svelte-sonner';
+	const dispatch = createEventDispatcher();
+
+	const i18n = getContext('i18n');
+
+	export let label = '';
+	let showTagInput = false;
+	let tagName = '';
+
+	const addTagHandler = async () => {
+		tagName = tagName.trim();
+		if (tagName !== '') {
+			dispatch('add', tagName);
+			tagName = '';
+			showTagInput = false;
+		} else {
+			toast.error($i18n.t(`Invalid Tag`));
+		}
+	};
+</script>
+
+<div class="px-0.5 flex {showTagInput ? 'flex-row-reverse' : ''}">
+	{#if showTagInput}
+		<div class="flex items-center">
+			<input
+				bind:value={tagName}
+				class=" px-2 cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[6.5rem]"
+				placeholder={$i18n.t('Add a tag')}
+				list="tagOptions"
+				on:keydown={(event) => {
+					if (event.key === 'Enter') {
+						addTagHandler();
+					}
+				}}
+			/>
+			<datalist id="tagOptions">
+				{#each $tags as tag}
+					<option value={tag.name} />
+				{/each}
+			</datalist>
+
+			<button type="button" aria-label={$i18n.t('Save Tag')} on:click={addTagHandler}>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					stroke-width="2"
+					class="w-3 h-3"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</button>
+		</div>
+	{/if}
+
+	<button
+		class=" cursor-pointer self-center p-0.5 flex h-fit items-center dark:hover:bg-gray-700 rounded-full transition border dark:border-gray-600 border-dashed"
+		type="button"
+		aria-label={$i18n.t('Add Tag')}
+		on:click={() => {
+			showTagInput = !showTagInput;
+		}}
+	>
+		<div class=" m-auto self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-3 h-3 {showTagInput ? 'rotate-45' : ''} transition-all transform"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</div>
+	</button>
+
+	{#if label && !showTagInput}
+		<span class="text-xs pl-2 self-center">{label}</span>
+	{/if}
+</div>
diff --git a/src/lib/components/common/Tags/TagList.svelte b/src/lib/components/common/Tags/TagList.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9751da929c6ba35407a4dc0442ba42b4613ce0d5
--- /dev/null
+++ b/src/lib/components/common/Tags/TagList.svelte
@@ -0,0 +1,32 @@
+<script lang="ts">
+	import { createEventDispatcher } from 'svelte';
+	import Tooltip from '../Tooltip.svelte';
+	import XMark from '$lib/components/icons/XMark.svelte';
+	import Badge from '../Badge.svelte';
+	const dispatch = createEventDispatcher();
+
+	export let tags = [];
+</script>
+
+{#each tags as tag}
+	<Tooltip content={tag.name}>
+		<div
+			class="relative group/tags px-1.5 py-[0.2px] gap-0.5 flex justify-between h-fit max-h-fit w-fit items-center rounded-full bg-gray-500/20 text-gray-700 dark:text-gray-200 transition cursor-pointer"
+		>
+			<div class=" text-[0.7rem] font-medium self-center line-clamp-1 w-fit">
+				{tag.name}
+			</div>
+			<div class="absolute invisible right-0.5 group-hover/tags:visible transition">
+				<button
+					class="rounded-full border bg-white dark:bg-gray-700 h-full flex self-center cursor-pointer"
+					on:click={() => {
+						dispatch('delete', tag.name);
+					}}
+					type="button"
+				>
+					<XMark className="size-3" strokeWidth="2.5" />
+				</button>
+			</div>
+		</div>
+	</Tooltip>
+{/each}
diff --git a/src/lib/components/common/Textarea.svelte b/src/lib/components/common/Textarea.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..66270f1b3fcb0066c75f99825d8333170d366267
--- /dev/null
+++ b/src/lib/components/common/Textarea.svelte
@@ -0,0 +1,44 @@
+<script lang="ts">
+	import { onMount, tick } from 'svelte';
+
+	export let value = '';
+	export let placeholder = '';
+
+	export let rows = 1;
+	export let required = false;
+
+	export let className =
+		'w-full rounded-lg px-3 py-2 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none resize-none h-full';
+
+	let textareaElement;
+
+	onMount(async () => {
+		await tick();
+		if (textareaElement) {
+			await tick();
+			setTimeout(adjustHeight, 0);
+		}
+	});
+
+	$: if (value) {
+		setTimeout(adjustHeight, 0);
+	}
+
+	const adjustHeight = () => {
+		if (textareaElement) {
+			textareaElement.style.height = '';
+			textareaElement.style.height = `${textareaElement.scrollHeight}px`;
+		}
+	};
+</script>
+
+<textarea
+	bind:this={textareaElement}
+	bind:value
+	{placeholder}
+	on:input={adjustHeight}
+	on:focus={adjustHeight}
+	class={className}
+	{rows}
+	{required}
+/>
diff --git a/src/lib/components/common/Tooltip.svelte b/src/lib/components/common/Tooltip.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..23042752559cda22157605cff00519200aa822c5
--- /dev/null
+++ b/src/lib/components/common/Tooltip.svelte
@@ -0,0 +1,51 @@
+<script lang="ts">
+	import DOMPurify from 'dompurify';
+
+	import { onDestroy } from 'svelte';
+	import { marked } from 'marked';
+
+	import tippy from 'tippy.js';
+	import { roundArrow } from 'tippy.js';
+
+	export let placement = 'top';
+	export let content = `I'm a tooltip!`;
+	export let touch = true;
+	export let className = 'flex';
+	export let theme = '';
+	export let allowHTML = true;
+	export let tippyOptions = {};
+
+	let tooltipElement;
+	let tooltipInstance;
+
+	$: if (tooltipElement && content) {
+		if (tooltipInstance) {
+			tooltipInstance.setContent(DOMPurify.sanitize(content));
+		} else {
+			tooltipInstance = tippy(tooltipElement, {
+				content: DOMPurify.sanitize(content),
+				placement: placement,
+				allowHTML: allowHTML,
+				touch: touch,
+				...(theme !== '' ? { theme } : { theme: 'dark' }),
+				arrow: false,
+				offset: [0, 4],
+				...tippyOptions
+			});
+		}
+	} else if (tooltipInstance && content === '') {
+		if (tooltipInstance) {
+			tooltipInstance.destroy();
+		}
+	}
+
+	onDestroy(() => {
+		if (tooltipInstance) {
+			tooltipInstance.destroy();
+		}
+	});
+</script>
+
+<div bind:this={tooltipElement} aria-label={DOMPurify.sanitize(content)} class={className}>
+	<slot />
+</div>
diff --git a/src/lib/components/common/Valves.svelte b/src/lib/components/common/Valves.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e88253f3f56038c074fbedf3ab5bcdfc60d51fee
--- /dev/null
+++ b/src/lib/components/common/Valves.svelte
@@ -0,0 +1,109 @@
+<script>
+	import { onMount, getContext, createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	import Switch from './Switch.svelte';
+
+	export let valvesSpec = null;
+	export let valves = {};
+</script>
+
+{#if valvesSpec && Object.keys(valvesSpec?.properties ?? {}).length}
+	{#each Object.keys(valvesSpec.properties) as property, idx}
+		<div class=" py-0.5 w-full justify-between">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">
+					{valvesSpec.properties[property].title}
+
+					{#if (valvesSpec?.required ?? []).includes(property)}
+						<span class=" text-gray-500">*required</span>
+					{/if}
+				</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					type="button"
+					on:click={() => {
+						valves[property] =
+							(valves[property] ?? null) === null
+								? (valvesSpec.properties[property]?.default ?? '')
+								: null;
+
+						dispatch('change');
+					}}
+				>
+					{#if (valves[property] ?? null) === null}
+						<span class="ml-2 self-center">
+							{#if (valvesSpec?.required ?? []).includes(property)}
+								{$i18n.t('None')}
+							{:else}
+								{$i18n.t('Default')}
+							{/if}
+						</span>
+					{:else}
+						<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
+					{/if}
+				</button>
+			</div>
+
+			{#if (valves[property] ?? null) !== null}
+				<!-- {valves[property]} -->
+				<div class="flex mt-0.5 mb-1.5 space-x-2">
+					<div class=" flex-1">
+						{#if valvesSpec.properties[property]?.enum ?? null}
+							<select
+								class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none border border-gray-100 dark:border-gray-800"
+								bind:value={valves[property]}
+								on:change={() => {
+									dispatch('change');
+								}}
+							>
+								{#each valvesSpec.properties[property].enum as option}
+									<option value={option} selected={option === valves[property]}>
+										{option}
+									</option>
+								{/each}
+							</select>
+						{:else if (valvesSpec.properties[property]?.type ?? null) === 'boolean'}
+							<div class="flex justify-between items-center">
+								<div class="text-xs text-gray-500">
+									{valves[property] ? 'Enabled' : 'Disabled'}
+								</div>
+
+								<div class=" pr-2">
+									<Switch
+										bind:state={valves[property]}
+										on:change={() => {
+											dispatch('change');
+										}}
+									/>
+								</div>
+							</div>
+						{:else}
+							<input
+								class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none border border-gray-100 dark:border-gray-800"
+								type="text"
+								placeholder={valvesSpec.properties[property].title}
+								bind:value={valves[property]}
+								autocomplete="off"
+								required
+								on:change={() => {
+									dispatch('change');
+								}}
+							/>
+						{/if}
+					</div>
+				</div>
+			{/if}
+
+			{#if (valvesSpec.properties[property]?.description ?? null) !== null}
+				<div class="text-xs text-gray-500">
+					{valvesSpec.properties[property].description}
+				</div>
+			{/if}
+		</div>
+	{/each}
+{:else}
+	<div class="text-xs">No valves</div>
+{/if}
diff --git a/src/lib/components/icons/AdjustmentsHorizontal.svelte b/src/lib/components/icons/AdjustmentsHorizontal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..4cc509e8460877f15613d0e45575ba1a9425b7ad
--- /dev/null
+++ b/src/lib/components/icons/AdjustmentsHorizontal.svelte
@@ -0,0 +1,17 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	viewBox="0 0 24 24"
+	stroke="currentColor"
+	fill="currentColor"
+	class={className}
+	stroke-width={strokeWidth}
+>
+	<path
+		d="M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/ArchiveBox.svelte b/src/lib/components/icons/ArchiveBox.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d4f6e201abf2c226a8b820ab21a5c65c204087b4
--- /dev/null
+++ b/src/lib/components/icons/ArchiveBox.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-3.5';
+	export let strokeWidth = '2.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/ArrowDownTray.svelte b/src/lib/components/icons/ArrowDownTray.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..55620e9feaf4b1d92cd8373a1167ecc9ddfef289
--- /dev/null
+++ b/src/lib/components/icons/ArrowDownTray.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"
+	/>
+</svg>
diff --git a/src/lib/components/icons/ArrowLeft.svelte b/src/lib/components/icons/ArrowLeft.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..166aee7f6c92b59867b4beac7937f0a39596934c
--- /dev/null
+++ b/src/lib/components/icons/ArrowLeft.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
+</svg>
diff --git a/src/lib/components/icons/ArrowPath.svelte b/src/lib/components/icons/ArrowPath.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..5b31652d6d2028c0e80e7502ba23073dda1f4cc5
--- /dev/null
+++ b/src/lib/components/icons/ArrowPath.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
+	/>
+</svg>
diff --git a/src/lib/components/icons/ArrowRight.svelte b/src/lib/components/icons/ArrowRight.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a3ac1af4b47e5b53a9e5d85203ba629356f7a8da
--- /dev/null
+++ b/src/lib/components/icons/ArrowRight.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
+</svg>
diff --git a/src/lib/components/icons/ArrowUpCircle.svelte b/src/lib/components/icons/ArrowUpCircle.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..caa5f219b880dc8e9af89f62b30a3e1b98f48f68
--- /dev/null
+++ b/src/lib/components/icons/ArrowUpCircle.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="m15 11.25-3-3m0 0-3 3m3-3v7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/ArrowsPointingOut.svelte b/src/lib/components/icons/ArrowsPointingOut.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..efc7f98b6b96eb3ab8d8eac4bd49e4b8ab0a813d
--- /dev/null
+++ b/src/lib/components/icons/ArrowsPointingOut.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"
+	/>
+</svg>
diff --git a/src/lib/components/icons/BarsArrowUp.svelte b/src/lib/components/icons/BarsArrowUp.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d34dbde676aac1e5a915b66ac1fa3bdfc693ec09
--- /dev/null
+++ b/src/lib/components/icons/BarsArrowUp.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.25-.75L17.25 9m0 0L21 12.75M17.25 9v12"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Bolt.svelte b/src/lib/components/icons/Bolt.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..681ef53c9562d600163a16923500b55ed2f3fddc
--- /dev/null
+++ b/src/lib/components/icons/Bolt.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-3';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/BookOpen.svelte b/src/lib/components/icons/BookOpen.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..5a77433d5c0fcd2c2049c95d7f18d3bfe5a40140
--- /dev/null
+++ b/src/lib/components/icons/BookOpen.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '2';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Bookmark.svelte b/src/lib/components/icons/Bookmark.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ea8028457d0f4fdf08398513e192d69aa97f7c5f
--- /dev/null
+++ b/src/lib/components/icons/Bookmark.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/BookmarkSlash.svelte b/src/lib/components/icons/BookmarkSlash.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6b80ea3cab1b437c51834d5bb12464bdfc55169a
--- /dev/null
+++ b/src/lib/components/icons/BookmarkSlash.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="m3 3 1.664 1.664M21 21l-1.5-1.5m-5.485-1.242L12 17.25 4.5 21V8.742m.164-4.078a2.15 2.15 0 0 1 1.743-1.342 48.507 48.507 0 0 1 11.186 0c1.1.128 1.907 1.077 1.907 2.185V19.5M4.664 4.664 19.5 19.5"
+	/>
+</svg>
diff --git a/src/lib/components/icons/ChartBar.svelte b/src/lib/components/icons/ChartBar.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6f2d35821a4050fd6159c07ae77601f923ada073
--- /dev/null
+++ b/src/lib/components/icons/ChartBar.svelte
@@ -0,0 +1,10 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
+	<path
+		d="M18.375 2.25c-1.035 0-1.875.84-1.875 1.875v15.75c0 1.035.84 1.875 1.875 1.875h.75c1.035 0 1.875-.84 1.875-1.875V4.125c0-1.036-.84-1.875-1.875-1.875h-.75ZM9.75 8.625c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-.75a1.875 1.875 0 0 1-1.875-1.875V8.625ZM3 13.125c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v6.75c0 1.035-.84 1.875-1.875 1.875h-.75A1.875 1.875 0 0 1 3 19.875v-6.75Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/ChatBubble.svelte b/src/lib/components/icons/ChatBubble.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7ece2c4c7a658fd9d161da7a1f99b6f382dcfb63
--- /dev/null
+++ b/src/lib/components/icons/ChatBubble.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/ChatBubbleOval.svelte b/src/lib/components/icons/ChatBubbleOval.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..4edbbb61404680117c00834c6f26ab3bb344e92a
--- /dev/null
+++ b/src/lib/components/icons/ChatBubbleOval.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M12 20.25c4.97 0 9-3.694 9-8.25s-4.03-8.25-9-8.25S3 7.444 3 12c0 2.104.859 4.023 2.273 5.48.432.447.74 1.04.586 1.641a4.483 4.483 0 0 1-.923 1.785A5.969 5.969 0 0 0 6 21c1.282 0 2.47-.402 3.445-1.087.81.22 1.668.337 2.555.337Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/ChatBubbles.svelte b/src/lib/components/icons/ChatBubbles.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..1651746fd6fbedd745cdbeb7f078872ff3f1e036
--- /dev/null
+++ b/src/lib/components/icons/ChatBubbles.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 0 1-.825-.242m9.345-8.334a2.126 2.126 0 0 0-.476-.095 48.64 48.64 0 0 0-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0 0 11.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Check.svelte b/src/lib/components/icons/Check.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..37eb9d7139f53afd199c2e011ae6481b6286250a
--- /dev/null
+++ b/src/lib/components/icons/Check.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
+</svg>
diff --git a/src/lib/components/icons/ChevronDown.svelte b/src/lib/components/icons/ChevronDown.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..16686ea3a3be64ce629c8d020764f14f8b4184ae
--- /dev/null
+++ b/src/lib/components/icons/ChevronDown.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
+</svg>
diff --git a/src/lib/components/icons/ChevronLeft.svelte b/src/lib/components/icons/ChevronLeft.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..78ee64d2422a4729cbaa28fd7d86696781f848ab
--- /dev/null
+++ b/src/lib/components/icons/ChevronLeft.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
+</svg>
diff --git a/src/lib/components/icons/ChevronRight.svelte b/src/lib/components/icons/ChevronRight.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7daf4a14a5297f72d9aa1fcddedb6d17eb556456
--- /dev/null
+++ b/src/lib/components/icons/ChevronRight.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
+</svg>
diff --git a/src/lib/components/icons/ChevronUp.svelte b/src/lib/components/icons/ChevronUp.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6fe7ca1ccd0473c8dbd1d32bdc4ffb8abeb6d0b6
--- /dev/null
+++ b/src/lib/components/icons/ChevronUp.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" />
+</svg>
diff --git a/src/lib/components/icons/ChevronUpDown.svelte b/src/lib/components/icons/ChevronUpDown.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7f23435a2d0ca45e0295994428e467722cc79a93
--- /dev/null
+++ b/src/lib/components/icons/ChevronUpDown.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Clipboard.svelte b/src/lib/components/icons/Clipboard.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..b1a793a76e2f5ef95409fb920eb2a648648d9e48
--- /dev/null
+++ b/src/lib/components/icons/Clipboard.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '2';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184"
+	/>
+</svg>
diff --git a/src/lib/components/icons/CloudArrowUp.svelte b/src/lib/components/icons/CloudArrowUp.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..bb54d861fa2cc2824cb2dfedf1fc43c8d6ec76a2
--- /dev/null
+++ b/src/lib/components/icons/CloudArrowUp.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M12 16.5V9.75m0 0 3 3m-3-3-3 3M6.75 19.5a4.5 4.5 0 0 1-1.41-8.775 5.25 5.25 0 0 1 10.233-2.33 3 3 0 0 1 3.758 3.848A3.752 3.752 0 0 1 18 19.5H6.75Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Cog6.svelte b/src/lib/components/icons/Cog6.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..286b62624aecc15eb2588fd2764dca1d76ded88f
--- /dev/null
+++ b/src/lib/components/icons/Cog6.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
+	/>
+	<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
+</svg>
diff --git a/src/lib/components/icons/Cube.svelte b/src/lib/components/icons/Cube.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..4015b2d7cc93a1ecf1f673d11341627aa79b0dea
--- /dev/null
+++ b/src/lib/components/icons/Cube.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '2';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Document.svelte b/src/lib/components/icons/Document.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9ae719725bee33303f9f9b8310c0cc04e365c0e5
--- /dev/null
+++ b/src/lib/components/icons/Document.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/DocumentArrowDown.svelte b/src/lib/components/icons/DocumentArrowDown.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..c6d25da34ef71604e90bc937d2bc83fa0e5e60c8
--- /dev/null
+++ b/src/lib/components/icons/DocumentArrowDown.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m.75 12 3 3m0 0 3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/DocumentArrowUpSolid.svelte b/src/lib/components/icons/DocumentArrowUpSolid.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2690f55370964ddc86ee805145e29d470b25d8d8
--- /dev/null
+++ b/src/lib/components/icons/DocumentArrowUpSolid.svelte
@@ -0,0 +1,14 @@
+<script lang="ts">
+	export let className = 'size-4';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
+	<path
+		fill-rule="evenodd"
+		d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875Zm6.905 9.97a.75.75 0 0 0-1.06 0l-3 3a.75.75 0 1 0 1.06 1.06l1.72-1.72V18a.75.75 0 0 0 1.5 0v-4.19l1.72 1.72a.75.75 0 1 0 1.06-1.06l-3-3Z"
+		clip-rule="evenodd"
+	/>
+	<path
+		d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/DocumentChartBar.svelte b/src/lib/components/icons/DocumentChartBar.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..bc811bed1abcaae386c5746c61e93559a4d7cd3d
--- /dev/null
+++ b/src/lib/components/icons/DocumentChartBar.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
+	<path
+		fill-rule="evenodd"
+		d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875ZM9.75 17.25a.75.75 0 0 0-1.5 0V18a.75.75 0 0 0 1.5 0v-.75Zm2.25-3a.75.75 0 0 1 .75.75v3a.75.75 0 0 1-1.5 0v-3a.75.75 0 0 1 .75-.75Zm3.75-1.5a.75.75 0 0 0-1.5 0V18a.75.75 0 0 0 1.5 0v-5.25Z"
+		clip-rule="evenodd"
+	/>
+	<path
+		d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/DocumentDuplicate.svelte b/src/lib/components/icons/DocumentDuplicate.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a208fefc8ac1ecc3acedf9d6a0aa1013ea6bab18
--- /dev/null
+++ b/src/lib/components/icons/DocumentDuplicate.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Download.svelte b/src/lib/components/icons/Download.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..55620e9feaf4b1d92cd8373a1167ecc9ddfef289
--- /dev/null
+++ b/src/lib/components/icons/Download.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"
+	/>
+</svg>
diff --git a/src/lib/components/icons/EllipsisHorizontal.svelte b/src/lib/components/icons/EllipsisHorizontal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6a7d532e38cc572fadb6d43cf7f64527f1437c1f
--- /dev/null
+++ b/src/lib/components/icons/EllipsisHorizontal.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/EllipsisVertical.svelte b/src/lib/components/icons/EllipsisVertical.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7ccbea2947395ce6cc584917c886bc44169af935
--- /dev/null
+++ b/src/lib/components/icons/EllipsisVertical.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5ZM12 12.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5ZM12 18.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/EyeSlash.svelte b/src/lib/components/icons/EyeSlash.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ec53cfb09e48f612dbbef1b33a1929de5c139fb7
--- /dev/null
+++ b/src/lib/components/icons/EyeSlash.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"
+	/>
+</svg>
diff --git a/src/lib/components/icons/FloppyDisk.svelte b/src/lib/components/icons/FloppyDisk.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..bcb481e826541f74ddba12ad333c4dc497ef6312
--- /dev/null
+++ b/src/lib/components/icons/FloppyDisk.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	stroke="currentColor"
+	class={className}
+	aria-hidden="true"
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+>
+	<path
+		stroke="currentColor"
+		stroke-linecap="round"
+		stroke-width={strokeWidth}
+		d="M11 16h2m6.707-9.293-2.414-2.414A1 1 0 0 0 16.586 4H5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V7.414a1 1 0 0 0-.293-.707ZM16 20v-6a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v6h8ZM9 4h6v3a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1V4Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/FolderOpen.svelte b/src/lib/components/icons/FolderOpen.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f6b3c64b30149eaf8b75e3f8c75b4727d4ceb60e
--- /dev/null
+++ b/src/lib/components/icons/FolderOpen.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 0 0-1.883 2.542l.857 6a2.25 2.25 0 0 0 2.227 1.932H19.05a2.25 2.25 0 0 0 2.227-1.932l.857-6a2.25 2.25 0 0 0-1.883-2.542m-16.5 0V6A2.25 2.25 0 0 1 6 3.75h3.879a1.5 1.5 0 0 1 1.06.44l2.122 2.12a1.5 1.5 0 0 0 1.06.44H18A2.25 2.25 0 0 1 20.25 9v.776"
+	/>
+</svg>
diff --git a/src/lib/components/icons/GarbageBin.svelte b/src/lib/components/icons/GarbageBin.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..31530fc7210733a9fb8f8cc0064c5854a4859ec6
--- /dev/null
+++ b/src/lib/components/icons/GarbageBin.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
+	/>
+</svg>
diff --git a/src/lib/components/icons/GlobeAlt.svelte b/src/lib/components/icons/GlobeAlt.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d2f86f43801f5baaa7a6a83cbdc386a06c0305a4
--- /dev/null
+++ b/src/lib/components/icons/GlobeAlt.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418"
+	/>
+</svg>
diff --git a/src/lib/components/icons/GlobeAltSolid.svelte b/src/lib/components/icons/GlobeAltSolid.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7054d311f82bb8a8ed946abc4e81e0b258ddbdf8
--- /dev/null
+++ b/src/lib/components/icons/GlobeAltSolid.svelte
@@ -0,0 +1,9 @@
+<script lang="ts">
+	export let className = 'size-4';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
+	<path
+		d="M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Headphone.svelte b/src/lib/components/icons/Headphone.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..10902a7cced4fb8dfae3cb2e3eecd802529c0b30
--- /dev/null
+++ b/src/lib/components/icons/Headphone.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '0';
+</script>
+
+<svg
+	aria-hidden="true"
+	xmlns="http://www.w3.org/2000/svg"
+	fill="currentColor"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		fill-rule="evenodd"
+		d="M12 5a7 7 0 0 0-7 7v1.17c.313-.11.65-.17 1-.17h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H6a3 3 0 0 1-3-3v-6a9 9 0 0 1 18 0v6a3 3 0 0 1-3 3h-2a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h2c.35 0 .687.06 1 .17V12a7 7 0 0 0-7-7Z"
+		clip-rule="evenodd"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Heart.svelte b/src/lib/components/icons/Heart.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ba042ff6552108af5ec0e3aa75cdfb7a868c631c
--- /dev/null
+++ b/src/lib/components/icons/Heart.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Info.svelte b/src/lib/components/icons/Info.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2849ac532b4ea366399b4583e3f84c57c461a094
--- /dev/null
+++ b/src/lib/components/icons/Info.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Keyboard.svelte b/src/lib/components/icons/Keyboard.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..baf633c0d6c4b998557335472015f3df8ddf2027
--- /dev/null
+++ b/src/lib/components/icons/Keyboard.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '2';
+</script>
+
+<svg
+	aria-hidden="true"
+	xmlns="http://www.w3.org/2000/svg"
+	fill="currentColor"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	class={className}
+>
+	<path
+		fill-rule="evenodd"
+		d="M2 7a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V7Zm5.01 1H5v2.01h2.01V8Zm3 0H8v2.01h2.01V8Zm3 0H11v2.01h2.01V8Zm3 0H14v2.01h2.01V8Zm3 0H17v2.01h2.01V8Zm-12 3H5v2.01h2.01V11Zm3 0H8v2.01h2.01V11Zm3 0H11v2.01h2.01V11Zm3 0H14v2.01h2.01V11Zm3 0H17v2.01h2.01V11Zm-12 3H5v2.01h2.01V14ZM8 14l-.001 2 8.011.01V14H8Zm11.01 0H17v2.01h2.01V14Z"
+		clip-rule="evenodd"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Lifebuoy.svelte b/src/lib/components/icons/Lifebuoy.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d8c49b0ae3733e58eac0774bdb1d841097bbc8e0
--- /dev/null
+++ b/src/lib/components/icons/Lifebuoy.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '2';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M16.712 4.33a9.027 9.027 0 0 1 1.652 1.306c.51.51.944 1.064 1.306 1.652M16.712 4.33l-3.448 4.138m3.448-4.138a9.014 9.014 0 0 0-9.424 0M19.67 7.288l-4.138 3.448m4.138-3.448a9.014 9.014 0 0 1 0 9.424m-4.138-5.976a3.736 3.736 0 0 0-.88-1.388 3.737 3.737 0 0 0-1.388-.88m2.268 2.268a3.765 3.765 0 0 1 0 2.528m-2.268-4.796a3.765 3.765 0 0 0-2.528 0m4.796 4.796c-.181.506-.475.982-.88 1.388a3.736 3.736 0 0 1-1.388.88m2.268-2.268 4.138 3.448m0 0a9.027 9.027 0 0 1-1.306 1.652c-.51.51-1.064.944-1.652 1.306m0 0-3.448-4.138m3.448 4.138a9.014 9.014 0 0 1-9.424 0m5.976-4.138a3.765 3.765 0 0 1-2.528 0m0 0a3.736 3.736 0 0 1-1.388-.88 3.737 3.737 0 0 1-.88-1.388m2.268 2.268L7.288 19.67m0 0a9.024 9.024 0 0 1-1.652-1.306 9.027 9.027 0 0 1-1.306-1.652m0 0 4.138-3.448M4.33 16.712a9.014 9.014 0 0 1 0-9.424m4.138 5.976a3.765 3.765 0 0 1 0-2.528m0 0c.181-.506.475-.982.88-1.388a3.736 3.736 0 0 1 1.388-.88m-2.268 2.268L4.33 7.288m6.406 1.18L7.288 4.33m0 0a9.024 9.024 0 0 0-1.652 1.306A9.025 9.025 0 0 0 4.33 7.288"
+	/>
+</svg>
diff --git a/src/lib/components/icons/LightBlub.svelte b/src/lib/components/icons/LightBlub.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..0778e6dad3578189c1820c25aae7331776281e2c
--- /dev/null
+++ b/src/lib/components/icons/LightBlub.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Link.svelte b/src/lib/components/icons/Link.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9f1a7231105c131d01f76aaa115546af7618401a
--- /dev/null
+++ b/src/lib/components/icons/Link.svelte
@@ -0,0 +1,16 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class={className}>
+	<path
+		fill-rule="evenodd"
+		d="M8.914 6.025a.75.75 0 0 1 1.06 0 3.5 3.5 0 0 1 0 4.95l-2 2a3.5 3.5 0 0 1-5.396-4.402.75.75 0 0 1 1.251.827 2 2 0 0 0 3.085 2.514l2-2a2 2 0 0 0 0-2.828.75.75 0 0 1 0-1.06Z"
+		clip-rule="evenodd"
+	/>
+	<path
+		fill-rule="evenodd"
+		d="M7.086 9.975a.75.75 0 0 1-1.06 0 3.5 3.5 0 0 1 0-4.95l2-2a3.5 3.5 0 0 1 5.396 4.402.75.75 0 0 1-1.251-.827 2 2 0 0 0-3.085-2.514l-2 2a2 2 0 0 0 0 2.828.75.75 0 0 1 0 1.06Z"
+		clip-rule="evenodd"
+	/>
+</svg>
diff --git a/src/lib/components/icons/MagnifyingGlass.svelte b/src/lib/components/icons/MagnifyingGlass.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a61186b5d0f40ce156632a8c3172e5a79a72816f
--- /dev/null
+++ b/src/lib/components/icons/MagnifyingGlass.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '2';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Map.svelte b/src/lib/components/icons/Map.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2ae0ce6a820da2f8a75b1d04e761c6448d8c235e
--- /dev/null
+++ b/src/lib/components/icons/Map.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '2';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M9 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/MenuLines.svelte b/src/lib/components/icons/MenuLines.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..c2b27c45b72ca615c5d8608986aead7983fcd7e9
--- /dev/null
+++ b/src/lib/components/icons/MenuLines.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-5';
+	export let strokeWidth = '2';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Merge.svelte b/src/lib/components/icons/Merge.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..998303677050fdc7c7034133136ec386148a2810
--- /dev/null
+++ b/src/lib/components/icons/Merge.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M8 8v8m0-8a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm0 8a2 2 0 1 0 0 4 2 2 0 0 0 0-4Zm6-2a2 2 0 1 1 4 0 2 2 0 0 1-4 0Zm0 0h-1a5 5 0 0 1-5-5v-.5"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Mic.svelte b/src/lib/components/icons/Mic.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7967b6da24471affff7ddebe1403d78cfa26bf20
--- /dev/null
+++ b/src/lib/components/icons/Mic.svelte
@@ -0,0 +1,10 @@
+<script lang="ts">
+	export let className = 'size-4';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class={className}>
+	<path d="M7 4a3 3 0 0 1 6 0v6a3 3 0 1 1-6 0V4Z" />
+	<path
+		d="M5.5 9.643a.75.75 0 0 0-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 0 0 0 1.5h4.5a.75.75 0 0 0 0-1.5h-1.5v-1.546A6.001 6.001 0 0 0 16 10v-.357a.75.75 0 0 0-1.5 0V10a4.5 4.5 0 0 1-9 0v-.357Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Minus.svelte b/src/lib/components/icons/Minus.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9b2b7f4ad98c61ed1b057c52c9ca4525fef5bfd6
--- /dev/null
+++ b/src/lib/components/icons/Minus.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" />
+</svg>
diff --git a/src/lib/components/icons/Pencil.svelte b/src/lib/components/icons/Pencil.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..42b60916c4bb4aaf2d71c9684e764dd85fb54e51
--- /dev/null
+++ b/src/lib/components/icons/Pencil.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
+	/>
+</svg>
diff --git a/src/lib/components/icons/PencilSolid.svelte b/src/lib/components/icons/PencilSolid.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f986eeb1c2b6341d7e76edcb95a66128b4fea286
--- /dev/null
+++ b/src/lib/components/icons/PencilSolid.svelte
@@ -0,0 +1,10 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
+	<path
+		d="M21.731 2.269a2.625 2.625 0 0 0-3.712 0l-1.157 1.157 3.712 3.712 1.157-1.157a2.625 2.625 0 0 0 0-3.712ZM19.513 8.199l-3.712-3.712-12.15 12.15a5.25 5.25 0 0 0-1.32 2.214l-.8 2.685a.75.75 0 0 0 .933.933l2.685-.8a5.25 5.25 0 0 0 2.214-1.32L19.513 8.2Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/PencilSquare.svelte b/src/lib/components/icons/PencilSquare.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9d44ac64b25535c5dd8c3d232150f0045e76431a
--- /dev/null
+++ b/src/lib/components/icons/PencilSquare.svelte
@@ -0,0 +1,13 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class={className}>
+	<path
+		d="m5.433 13.917 1.262-3.155A4 4 0 0 1 7.58 9.42l6.92-6.918a2.121 2.121 0 0 1 3 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 0 1-.65-.65Z"
+	/>
+	<path
+		d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0 0 10 3H4.75A2.75 2.75 0 0 0 2 5.75v9.5A2.75 2.75 0 0 0 4.75 18h9.5A2.75 2.75 0 0 0 17 15.25V10a.75.75 0 0 0-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Plus.svelte b/src/lib/components/icons/Plus.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..bcfe4a8b23a9e78c62eece472f840a20f4b269e8
--- /dev/null
+++ b/src/lib/components/icons/Plus.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
+</svg>
diff --git a/src/lib/components/icons/QuestionMarkCircle.svelte b/src/lib/components/icons/QuestionMarkCircle.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..79c2e7d84c2e7a2f1271cfc0dcc644cbdf24127b
--- /dev/null
+++ b/src/lib/components/icons/QuestionMarkCircle.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '2';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Search.svelte b/src/lib/components/icons/Search.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..c2dc55845d54f1ad900ea75f3be5674b264d1955
--- /dev/null
+++ b/src/lib/components/icons/Search.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Share.svelte b/src/lib/components/icons/Share.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f098995c68da47b9261d492033b60780a033c587
--- /dev/null
+++ b/src/lib/components/icons/Share.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
+	<path
+		fill-rule="evenodd"
+		d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z"
+		clip-rule="evenodd"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Sparkles.svelte b/src/lib/components/icons/Sparkles.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..0f9034d261fa9f424a2e5b74d0366e0eeb539361
--- /dev/null
+++ b/src/lib/components/icons/Sparkles.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/SparklesSolid.svelte b/src/lib/components/icons/SparklesSolid.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d69e0c8b69a134186fca479b1641ad5fc6045969
--- /dev/null
+++ b/src/lib/components/icons/SparklesSolid.svelte
@@ -0,0 +1,12 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
+	<path
+		fill-rule="evenodd"
+		d="M9 4.5a.75.75 0 0 1 .721.544l.813 2.846a3.75 3.75 0 0 0 2.576 2.576l2.846.813a.75.75 0 0 1 0 1.442l-2.846.813a3.75 3.75 0 0 0-2.576 2.576l-.813 2.846a.75.75 0 0 1-1.442 0l-.813-2.846a3.75 3.75 0 0 0-2.576-2.576l-2.846-.813a.75.75 0 0 1 0-1.442l2.846-.813A3.75 3.75 0 0 0 7.466 7.89l.813-2.846A.75.75 0 0 1 9 4.5ZM18 1.5a.75.75 0 0 1 .728.568l.258 1.036c.236.94.97 1.674 1.91 1.91l1.036.258a.75.75 0 0 1 0 1.456l-1.036.258c-.94.236-1.674.97-1.91 1.91l-.258 1.036a.75.75 0 0 1-1.456 0l-.258-1.036a2.625 2.625 0 0 0-1.91-1.91l-1.036-.258a.75.75 0 0 1 0-1.456l1.036-.258a2.625 2.625 0 0 0 1.91-1.91l.258-1.036A.75.75 0 0 1 18 1.5ZM16.5 15a.75.75 0 0 1 .712.513l.394 1.183c.15.447.5.799.948.948l1.183.395a.75.75 0 0 1 0 1.422l-1.183.395c-.447.15-.799.5-.948.948l-.395 1.183a.75.75 0 0 1-1.422 0l-.395-1.183a1.5 1.5 0 0 0-.948-.948l-1.183-.395a.75.75 0 0 1 0-1.422l1.183-.395c.447-.15.799-.5.948-.948l.395-1.183A.75.75 0 0 1 16.5 15Z"
+		clip-rule="evenodd"
+	/>
+</svg>
diff --git a/src/lib/components/icons/Star.svelte b/src/lib/components/icons/Star.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..45faf808b679a4f2c9485eeeeab250de8b1ba2b7
--- /dev/null
+++ b/src/lib/components/icons/Star.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z"
+	/>
+</svg>
diff --git a/src/lib/components/icons/User.svelte b/src/lib/components/icons/User.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..71a87a2f52b1b194d9d72779a227464eb155f0d5
--- /dev/null
+++ b/src/lib/components/icons/User.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
+	<path
+		fill-rule="evenodd"
+		d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z"
+		clip-rule="evenodd"
+	/>
+</svg>
diff --git a/src/lib/components/icons/WrenchSolid.svelte b/src/lib/components/icons/WrenchSolid.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..66a2113018376da328c13f146ebbadf8c5511794
--- /dev/null
+++ b/src/lib/components/icons/WrenchSolid.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+	export let className = 'size-4';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
+	<path
+		fill-rule="evenodd"
+		d="M12 6.75a5.25 5.25 0 0 1 6.775-5.025.75.75 0 0 1 .313 1.248l-3.32 3.319c.063.475.276.934.641 1.299.365.365.824.578 1.3.64l3.318-3.319a.75.75 0 0 1 1.248.313 5.25 5.25 0 0 1-5.472 6.756c-1.018-.086-1.87.1-2.309.634L7.344 21.3A3.298 3.298 0 1 1 2.7 16.657l8.684-7.151c.533-.44.72-1.291.634-2.309A5.342 5.342 0 0 1 12 6.75ZM4.117 19.125a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75h-.008a.75.75 0 0 1-.75-.75v-.008Z"
+		clip-rule="evenodd"
+	/>
+</svg>
diff --git a/src/lib/components/icons/XMark.svelte b/src/lib/components/icons/XMark.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..b75be506ca797fdbb687b27be4118dadaeef4d4e
--- /dev/null
+++ b/src/lib/components/icons/XMark.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+	export let className = 'size-3.5';
+	export let strokeWidth = '2';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
+</svg>
diff --git a/src/lib/components/layout/Help.svelte b/src/lib/components/layout/Help.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9e0a18582e54ff1b2e32a0d30b9d730f12ee3272
--- /dev/null
+++ b/src/lib/components/layout/Help.svelte
@@ -0,0 +1,40 @@
+<script lang="ts">
+	import { onMount, tick, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
+
+	import ShortcutsModal from '../chat/ShortcutsModal.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import HelpMenu from './Help/HelpMenu.svelte';
+
+	let showShortcuts = false;
+</script>
+
+<div class=" hidden lg:flex fixed bottom-0 right-0 px-2 py-2 z-20">
+	<button
+		id="show-shortcuts-button"
+		class="hidden"
+		on:click={() => {
+			showShortcuts = !showShortcuts;
+		}}
+	/>
+
+	<HelpMenu
+		showDocsHandler={() => {
+			showShortcuts = !showShortcuts;
+		}}
+		showShortcutsHandler={() => {
+			showShortcuts = !showShortcuts;
+		}}
+	>
+		<Tooltip content={$i18n.t('Help')} placement="left">
+			<button
+				class="text-gray-600 dark:text-gray-300 bg-gray-300/20 size-5 flex items-center justify-center text-[0.7rem] rounded-full"
+			>
+				?
+			</button>
+		</Tooltip>
+	</HelpMenu>
+</div>
+
+<ShortcutsModal bind:show={showShortcuts} />
diff --git a/src/lib/components/layout/Help/HelpMenu.svelte b/src/lib/components/layout/Help/HelpMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7371f629c86e9079288f2a68610aec124198cfba
--- /dev/null
+++ b/src/lib/components/layout/Help/HelpMenu.svelte
@@ -0,0 +1,60 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { getContext } from 'svelte';
+
+	import { showSettings } from '$lib/stores';
+	import { flyAndScale } from '$lib/utils/transitions';
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import QuestionMarkCircle from '$lib/components/icons/QuestionMarkCircle.svelte';
+	import Lifebuoy from '$lib/components/icons/Lifebuoy.svelte';
+	import Keyboard from '$lib/components/icons/Keyboard.svelte';
+	const i18n = getContext('i18n');
+
+	export let showDocsHandler: Function;
+	export let showShortcutsHandler: Function;
+
+	export let onClose: Function = () => {};
+</script>
+
+<Dropdown
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+>
+	<slot />
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[200px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
+			sideOffset={4}
+			side="top"
+			align="end"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				id="chat-share-button"
+				on:click={() => {
+					window.open('https://docs.openwebui.com', '_blank');
+				}}
+			>
+				<QuestionMarkCircle className="size-5" />
+				<div class="flex items-center">{$i18n.t('Documentation')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				id="chat-share-button"
+				on:click={() => {
+					showShortcutsHandler();
+				}}
+			>
+				<Keyboard className="size-5" />
+				<div class="flex items-center">{$i18n.t('Keyboard shortcuts')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/layout/Navbar.svelte b/src/lib/components/layout/Navbar.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..4c1ddfb4056c026b781d5dd94c2a49e6f904fb22
--- /dev/null
+++ b/src/lib/components/layout/Navbar.svelte
@@ -0,0 +1,187 @@
+<script lang="ts">
+	import { getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+
+	import {
+		WEBUI_NAME,
+		chatId,
+		mobile,
+		settings,
+		showArchivedChats,
+		showControls,
+		showSidebar,
+		temporaryChatEnabled,
+		user
+	} from '$lib/stores';
+
+	import { slide } from 'svelte/transition';
+	import ShareChatModal from '../chat/ShareChatModal.svelte';
+	import ModelSelector from '../chat/ModelSelector.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import Menu from './Navbar/Menu.svelte';
+	import { page } from '$app/stores';
+	import UserMenu from './Sidebar/UserMenu.svelte';
+	import MenuLines from '../icons/MenuLines.svelte';
+	import AdjustmentsHorizontal from '../icons/AdjustmentsHorizontal.svelte';
+	import Map from '../icons/Map.svelte';
+	import { stringify } from 'postcss';
+
+	const i18n = getContext('i18n');
+
+	export let initNewChat: Function;
+	export let title: string = $WEBUI_NAME;
+	export let shareEnabled: boolean = false;
+
+	export let chat;
+	export let selectedModels;
+	export let showModelSelector = true;
+
+	let showShareChatModal = false;
+	let showDownloadChatModal = false;
+</script>
+
+<ShareChatModal bind:show={showShareChatModal} chatId={$chatId} />
+
+<div class="sticky top-0 z-30 w-full px-1 py-2 -mb-8 flex items-center">
+	<div
+		class=" bg-gradient-to-b via-50% from-white via-white to-transparent dark:from-gray-900 dark:via-gray-900 dark:to-transparent pointer-events-none absolute inset-0 -bottom-7 z-[-1] blur"
+	></div>
+
+	<div class=" flex max-w-full w-full mx-auto px-2 pt-0.5 bg-transparent">
+		<div class="flex items-center w-full max-w-full">
+			<div
+				class="{$showSidebar
+					? 'md:hidden'
+					: ''} mr-2 self-start flex flex-none items-center text-gray-600 dark:text-gray-400"
+			>
+				<button
+					id="sidebar-toggle-button"
+					class="cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition"
+					on:click={() => {
+						showSidebar.set(!$showSidebar);
+					}}
+					aria-label="Toggle Sidebar"
+				>
+					<div class=" m-auto self-center">
+						<MenuLines />
+					</div>
+				</button>
+			</div>
+
+			<div class="flex-1 overflow-hidden max-w-full">
+				{#if showModelSelector}
+					<ModelSelector bind:selectedModels showSetDefault={!shareEnabled} />
+				{/if}
+			</div>
+
+			<div class="self-start flex flex-none items-center text-gray-600 dark:text-gray-400">
+				<!-- <div class="md:hidden flex self-center w-[1px] h-5 mx-2 bg-gray-300 dark:bg-stone-700" /> -->
+				{#if shareEnabled && chat && (chat.id || $temporaryChatEnabled)}
+					<Menu
+						{chat}
+						{shareEnabled}
+						shareHandler={() => {
+							showShareChatModal = !showShareChatModal;
+						}}
+						downloadHandler={() => {
+							showDownloadChatModal = !showDownloadChatModal;
+						}}
+					>
+						<button
+							class="flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition"
+							id="chat-context-menu-button"
+						>
+							<div class=" m-auto self-center">
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									fill="none"
+									viewBox="0 0 24 24"
+									stroke-width="1.5"
+									stroke="currentColor"
+									class="size-5"
+								>
+									<path
+										stroke-linecap="round"
+										stroke-linejoin="round"
+										d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"
+									/>
+								</svg>
+							</div>
+						</button>
+					</Menu>
+				{/if}
+
+				{#if !$mobile}
+					<Tooltip content={$i18n.t('Controls')}>
+						<button
+							class=" flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition"
+							on:click={async () => {
+								await showControls.set(!$showControls);
+							}}
+							aria-label="Controls"
+						>
+							<div class=" m-auto self-center">
+								<AdjustmentsHorizontal className=" size-5" strokeWidth="0.5" />
+							</div>
+						</button>
+					</Tooltip>
+				{/if}
+
+				<Tooltip content={$i18n.t('New Chat')}>
+					<button
+						id="new-chat-button"
+						class=" flex {$showSidebar
+							? 'md:hidden'
+							: ''} cursor-pointer px-2 py-2 rounded-xl text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-850 transition"
+						on:click={() => {
+							initNewChat();
+						}}
+						aria-label="New Chat"
+					>
+						<div class=" m-auto self-center">
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-5 h-5"
+							>
+								<path
+									d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z"
+								/>
+								<path
+									d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0010 3H4.75A2.75 2.75 0 002 5.75v9.5A2.75 2.75 0 004.75 18h9.5A2.75 2.75 0 0017 15.25V10a.75.75 0 00-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5z"
+								/>
+							</svg>
+						</div>
+					</button>
+				</Tooltip>
+
+				{#if $user !== undefined}
+					<UserMenu
+						className="max-w-[200px]"
+						role={$user.role}
+						on:show={(e) => {
+							if (e.detail === 'archived-chat') {
+								showArchivedChats.set(true);
+							}
+						}}
+					>
+						<button
+							class="select-none flex rounded-xl p-1.5 w-full hover:bg-gray-50 dark:hover:bg-gray-850 transition"
+							aria-label="User Menu"
+						>
+							<div class=" self-center">
+								<img
+									src={$user.profile_image_url}
+									class="size-6 object-cover rounded-full"
+									alt="User profile"
+									draggable="false"
+								/>
+							</div>
+						</button>
+					</UserMenu>
+				{/if}
+			</div>
+		</div>
+	</div>
+</div>
diff --git a/src/lib/components/layout/Navbar/Menu.svelte b/src/lib/components/layout/Navbar/Menu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..67ac9dcb2b0a4a906534c4cb68dc46711f545f90
--- /dev/null
+++ b/src/lib/components/layout/Navbar/Menu.svelte
@@ -0,0 +1,285 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { DropdownMenu } from 'bits-ui';
+	import { getContext } from 'svelte';
+
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { downloadChatAsPDF } from '$lib/apis/utils';
+	import { copyToClipboard, createMessagesList } from '$lib/utils';
+
+	import {
+		showOverview,
+		showControls,
+		showArtifacts,
+		mobile,
+		temporaryChatEnabled
+	} from '$lib/stores';
+	import { flyAndScale } from '$lib/utils/transitions';
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import Tags from '$lib/components/chat/Tags.svelte';
+	import Map from '$lib/components/icons/Map.svelte';
+	import Clipboard from '$lib/components/icons/Clipboard.svelte';
+	import AdjustmentsHorizontal from '$lib/components/icons/AdjustmentsHorizontal.svelte';
+	import Cube from '$lib/components/icons/Cube.svelte';
+	import { getChatById } from '$lib/apis/chats';
+
+	const i18n = getContext('i18n');
+
+	export let shareEnabled: boolean = false;
+	export let shareHandler: Function;
+	export let downloadHandler: Function;
+
+	// export let tagHandler: Function;
+
+	export let chat;
+	export let onClose: Function = () => {};
+
+	const getChatAsText = async () => {
+		const history = chat.chat.history;
+		const messages = createMessagesList(history, history.currentId);
+		const chatText = messages.reduce((a, message, i, arr) => {
+			return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`;
+		}, '');
+
+		return chatText.trim();
+	};
+
+	const downloadTxt = async () => {
+		const chatText = await getChatAsText();
+
+		let blob = new Blob([chatText], {
+			type: 'text/plain'
+		});
+
+		saveAs(blob, `chat-${chat.chat.title}.txt`);
+	};
+
+	const downloadPdf = async () => {
+		const history = chat.chat.history;
+		const messages = createMessagesList(history, history.currentId);
+		const blob = await downloadChatAsPDF(chat.chat.title, messages);
+
+		// Create a URL for the blob
+		const url = window.URL.createObjectURL(blob);
+
+		// Create a link element to trigger the download
+		const a = document.createElement('a');
+		a.href = url;
+		a.download = `chat-${chat.chat.title}.pdf`;
+
+		// Append the link to the body and click it programmatically
+		document.body.appendChild(a);
+		a.click();
+
+		// Remove the link from the body
+		document.body.removeChild(a);
+
+		// Revoke the URL to release memory
+		window.URL.revokeObjectURL(url);
+	};
+
+	const downloadJSONExport = async () => {
+		if (chat.id) {
+			chat = await getChatById(localStorage.token, chat.id);
+		}
+		let blob = new Blob([JSON.stringify([chat])], {
+			type: 'application/json'
+		});
+		saveAs(blob, `chat-export-${Date.now()}.json`);
+	};
+</script>
+
+<Dropdown
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+>
+	<slot />
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[200px] rounded-xl px-1 py-1.5  z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
+			sideOffset={8}
+			side="bottom"
+			align="end"
+			transition={flyAndScale}
+		>
+			<!-- <DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
+				on:click={async () => {
+					await showSettings.set(!$showSettings);
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					fill="none"
+					viewBox="0 0 24 24"
+					stroke-width="1.5"
+					stroke="currentColor"
+					class="size-4"
+				>
+					<path
+						stroke-linecap="round"
+						stroke-linejoin="round"
+						d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
+					/>
+					<path
+						stroke-linecap="round"
+						stroke-linejoin="round"
+						d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
+					/>
+				</svg>
+				<div class="flex items-center">{$i18n.t('Settings')}</div>
+			</DropdownMenu.Item> -->
+
+			{#if $mobile}
+				<DropdownMenu.Item
+					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					id="chat-controls-button"
+					on:click={async () => {
+						await showControls.set(true);
+						await showOverview.set(false);
+						await showArtifacts.set(false);
+					}}
+				>
+					<AdjustmentsHorizontal className=" size-4" strokeWidth="0.5" />
+					<div class="flex items-center">{$i18n.t('Controls')}</div>
+				</DropdownMenu.Item>
+			{/if}
+
+			{#if !$temporaryChatEnabled}
+				<DropdownMenu.Item
+					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					id="chat-share-button"
+					on:click={() => {
+						shareHandler();
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 24 24"
+						fill="currentColor"
+						class="size-4"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+					<div class="flex items-center">{$i18n.t('Share')}</div>
+				</DropdownMenu.Item>
+			{/if}
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				id="chat-overview-button"
+				on:click={async () => {
+					await showControls.set(true);
+					await showOverview.set(true);
+					await showArtifacts.set(false);
+				}}
+			>
+				<Map className=" size-4" strokeWidth="1.5" />
+				<div class="flex items-center">{$i18n.t('Overview')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				id="chat-overview-button"
+				on:click={async () => {
+					await showControls.set(true);
+					await showArtifacts.set(true);
+					await showOverview.set(false);
+				}}
+			>
+				<Cube className=" size-4" strokeWidth="1.5" />
+				<div class="flex items-center">{$i18n.t('Artifacts')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Sub>
+				<DropdownMenu.SubTrigger
+					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="size-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"
+						/>
+					</svg>
+
+					<div class="flex items-center">{$i18n.t('Download')}</div>
+				</DropdownMenu.SubTrigger>
+				<DropdownMenu.SubContent
+					class="w-full rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
+					transition={flyAndScale}
+					sideOffset={8}
+				>
+					<DropdownMenu.Item
+						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+						on:click={() => {
+							downloadJSONExport();
+						}}
+					>
+						<div class="flex items-center line-clamp-1">{$i18n.t('Export chat (.json)')}</div>
+					</DropdownMenu.Item>
+					<DropdownMenu.Item
+						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+						on:click={() => {
+							downloadTxt();
+						}}
+					>
+						<div class="flex items-center line-clamp-1">{$i18n.t('Plain text (.txt)')}</div>
+					</DropdownMenu.Item>
+
+					<DropdownMenu.Item
+						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+						on:click={() => {
+							downloadPdf();
+						}}
+					>
+						<div class="flex items-center line-clamp-1">{$i18n.t('PDF document (.pdf)')}</div>
+					</DropdownMenu.Item>
+				</DropdownMenu.SubContent>
+			</DropdownMenu.Sub>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				id="chat-copy-button"
+				on:click={async () => {
+					const res = await copyToClipboard(await getChatAsText()).catch((e) => {
+						console.error(e);
+					});
+
+					if (res) {
+						toast.success($i18n.t('Copied to clipboard'));
+					}
+				}}
+			>
+				<Clipboard className=" size-4" strokeWidth="1.5" />
+				<div class="flex items-center">{$i18n.t('Copy')}</div>
+			</DropdownMenu.Item>
+
+			{#if !$temporaryChatEnabled}
+				<hr class="border-gray-50 dark:border-gray-850 my-0.5" />
+
+				<div class="flex p-1">
+					<Tags chatId={chat.id} />
+				</div>
+			{/if}
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/layout/Overlay/AccountPending.svelte b/src/lib/components/layout/Overlay/AccountPending.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..c272963e9b8738ba50645a2024046845590f9913
--- /dev/null
+++ b/src/lib/components/layout/Overlay/AccountPending.svelte
@@ -0,0 +1,62 @@
+<script lang="ts">
+	import { getAdminDetails } from '$lib/apis/auths';
+	import { onMount, tick, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
+
+	let adminDetails = null;
+
+	onMount(async () => {
+		adminDetails = await getAdminDetails(localStorage.token).catch((err) => {
+			console.error(err);
+			return null;
+		});
+	});
+</script>
+
+<div class="fixed w-full h-full flex z-[999]">
+	<div
+		class="absolute w-full h-full backdrop-blur-lg bg-white/10 dark:bg-gray-900/50 flex justify-center"
+	>
+		<div class="m-auto pb-10 flex flex-col justify-center">
+			<div class="max-w-md">
+				<div class="text-center dark:text-white text-2xl font-medium z-50">
+					{$i18n.t('Account Activation Pending')}<br />
+					{$i18n.t('Contact Admin for WebUI Access')}
+				</div>
+
+				<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
+					{$i18n.t('Your account status is currently pending activation.')}<br />
+					{$i18n.t(
+						'To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.'
+					)}
+				</div>
+
+				{#if adminDetails}
+					<div class="mt-4 text-sm font-medium text-center">
+						<div>{$i18n.t('Admin')}: {adminDetails.name} ({adminDetails.email})</div>
+					</div>
+				{/if}
+
+				<div class=" mt-6 mx-auto relative group w-fit">
+					<button
+						class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 text-gray-700 transition font-medium text-sm"
+						on:click={async () => {
+							location.href = '/';
+						}}
+					>
+						{$i18n.t('Check Again')}
+					</button>
+
+					<button
+						class="text-xs text-center w-full mt-2 text-gray-400 underline"
+						on:click={async () => {
+							localStorage.removeItem('token');
+							location.href = '/auth';
+						}}>{$i18n.t('Sign Out')}</button
+					>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..8b133bcae73e063280a3f5dbaba70fcdb972213b
--- /dev/null
+++ b/src/lib/components/layout/Sidebar.svelte
@@ -0,0 +1,832 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { v4 as uuidv4 } from 'uuid';
+
+	import { goto } from '$app/navigation';
+	import {
+		user,
+		chats,
+		settings,
+		showSettings,
+		chatId,
+		tags,
+		showSidebar,
+		mobile,
+		showArchivedChats,
+		pinnedChats,
+		scrollPaginationEnabled,
+		currentChatPage,
+		temporaryChatEnabled
+	} from '$lib/stores';
+	import { onMount, getContext, tick, onDestroy } from 'svelte';
+
+	const i18n = getContext('i18n');
+
+	import {
+		deleteChatById,
+		getChatList,
+		getAllTags,
+		getChatListBySearchText,
+		createNewChat,
+		getPinnedChatList,
+		toggleChatPinnedStatusById,
+		getChatPinnedStatusById,
+		getChatById,
+		updateChatFolderIdById,
+		importChat
+	} from '$lib/apis/chats';
+	import { WEBUI_BASE_URL } from '$lib/constants';
+
+	import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
+	import UserMenu from './Sidebar/UserMenu.svelte';
+	import ChatItem from './Sidebar/ChatItem.svelte';
+	import Spinner from '../common/Spinner.svelte';
+	import Loader from '../common/Loader.svelte';
+	import AddFilesPlaceholder from '../AddFilesPlaceholder.svelte';
+	import SearchInput from './Sidebar/SearchInput.svelte';
+	import Folder from '../common/Folder.svelte';
+	import Plus from '../icons/Plus.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import { createNewFolder, getFolders, updateFolderParentIdById } from '$lib/apis/folders';
+	import Folders from './Sidebar/Folders.svelte';
+
+	const BREAKPOINT = 768;
+
+	let navElement;
+	let search = '';
+
+	let shiftKey = false;
+
+	let selectedChatId = null;
+	let showDropdown = false;
+	let showPinnedChat = true;
+
+	// Pagination variables
+	let chatListLoading = false;
+	let allChatsLoaded = false;
+
+	let folders = {};
+
+	const initFolders = async () => {
+		const folderList = await getFolders(localStorage.token).catch((error) => {
+			toast.error(error);
+			return [];
+		});
+
+		folders = {};
+
+		// First pass: Initialize all folder entries
+		for (const folder of folderList) {
+			// Ensure folder is added to folders with its data
+			folders[folder.id] = { ...(folders[folder.id] || {}), ...folder };
+		}
+
+		// Second pass: Tie child folders to their parents
+		for (const folder of folderList) {
+			if (folder.parent_id) {
+				// Ensure the parent folder is initialized if it doesn't exist
+				if (!folders[folder.parent_id]) {
+					folders[folder.parent_id] = {}; // Create a placeholder if not already present
+				}
+
+				// Initialize childrenIds array if it doesn't exist and add the current folder id
+				folders[folder.parent_id].childrenIds = folders[folder.parent_id].childrenIds
+					? [...folders[folder.parent_id].childrenIds, folder.id]
+					: [folder.id];
+
+				// Sort the children by updated_at field
+				folders[folder.parent_id].childrenIds.sort((a, b) => {
+					return folders[b].updated_at - folders[a].updated_at;
+				});
+			}
+		}
+	};
+
+	const createFolder = async (name = 'Untitled') => {
+		if (name === '') {
+			toast.error($i18n.t('Folder name cannot be empty.'));
+			return;
+		}
+
+		const rootFolders = Object.values(folders).filter((folder) => folder.parent_id === null);
+		if (rootFolders.find((folder) => folder.name.toLowerCase() === name.toLowerCase())) {
+			// If a folder with the same name already exists, append a number to the name
+			let i = 1;
+			while (
+				rootFolders.find((folder) => folder.name.toLowerCase() === `${name} ${i}`.toLowerCase())
+			) {
+				i++;
+			}
+
+			name = `${name} ${i}`;
+		}
+
+		// Add a dummy folder to the list to show the user that the folder is being created
+		const tempId = uuidv4();
+		folders = {
+			...folders,
+			tempId: {
+				id: tempId,
+				name: name,
+				created_at: Date.now(),
+				updated_at: Date.now()
+			}
+		};
+
+		const res = await createNewFolder(localStorage.token, name).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			await initFolders();
+		}
+	};
+
+	const initChatList = async () => {
+		// Reset pagination variables
+		tags.set(await getAllTags(localStorage.token));
+		pinnedChats.set(await getPinnedChatList(localStorage.token));
+		initFolders();
+
+		currentChatPage.set(1);
+		allChatsLoaded = false;
+
+		if (search) {
+			await chats.set(await getChatListBySearchText(localStorage.token, search, $currentChatPage));
+		} else {
+			await chats.set(await getChatList(localStorage.token, $currentChatPage));
+		}
+
+		// Enable pagination
+		scrollPaginationEnabled.set(true);
+	};
+
+	const loadMoreChats = async () => {
+		chatListLoading = true;
+
+		currentChatPage.set($currentChatPage + 1);
+
+		let newChatList = [];
+
+		if (search) {
+			newChatList = await getChatListBySearchText(localStorage.token, search, $currentChatPage);
+		} else {
+			newChatList = await getChatList(localStorage.token, $currentChatPage);
+		}
+
+		// once the bottom of the list has been reached (no results) there is no need to continue querying
+		allChatsLoaded = newChatList.length === 0;
+		await chats.set([...($chats ? $chats : []), ...newChatList]);
+
+		chatListLoading = false;
+	};
+
+	let searchDebounceTimeout;
+
+	const searchDebounceHandler = async () => {
+		console.log('search', search);
+		chats.set(null);
+
+		if (searchDebounceTimeout) {
+			clearTimeout(searchDebounceTimeout);
+		}
+
+		if (search === '') {
+			await initChatList();
+			return;
+		} else {
+			searchDebounceTimeout = setTimeout(async () => {
+				allChatsLoaded = false;
+				currentChatPage.set(1);
+				await chats.set(await getChatListBySearchText(localStorage.token, search));
+
+				if ($chats.length === 0) {
+					tags.set(await getAllTags(localStorage.token));
+				}
+			}, 1000);
+		}
+	};
+
+	const importChatHandler = async (items, pinned = false, folderId = null) => {
+		console.log('importChatHandler', items, pinned, folderId);
+		for (const item of items) {
+			console.log(item);
+			if (item.chat) {
+				await importChat(localStorage.token, item.chat, item?.meta ?? {}, pinned, folderId);
+			}
+		}
+
+		initChatList();
+	};
+
+	const inputFilesHandler = async (files) => {
+		console.log(files);
+
+		for (const file of files) {
+			const reader = new FileReader();
+			reader.onload = async (e) => {
+				const content = e.target.result;
+
+				try {
+					const chatItems = JSON.parse(content);
+					importChatHandler(chatItems);
+				} catch {
+					toast.error($i18n.t(`Invalid file format.`));
+				}
+			};
+
+			reader.readAsText(file);
+		}
+	};
+
+	const tagEventHandler = async (type, tagName, chatId) => {
+		console.log(type, tagName, chatId);
+		if (type === 'delete') {
+			initChatList();
+		} else if (type === 'add') {
+			initChatList();
+		}
+	};
+
+	let draggedOver = false;
+
+	const onDragOver = (e) => {
+		e.preventDefault();
+
+		// Check if a file is being draggedOver.
+		if (e.dataTransfer?.types?.includes('Files')) {
+			draggedOver = true;
+		} else {
+			draggedOver = false;
+		}
+	};
+
+	const onDragLeave = () => {
+		draggedOver = false;
+	};
+
+	const onDrop = async (e) => {
+		e.preventDefault();
+		console.log(e); // Log the drop event
+
+		// Perform file drop check and handle it accordingly
+		if (e.dataTransfer?.files) {
+			const inputFiles = Array.from(e.dataTransfer?.files);
+
+			if (inputFiles && inputFiles.length > 0) {
+				console.log(inputFiles); // Log the dropped files
+				inputFilesHandler(inputFiles); // Handle the dropped files
+			}
+		}
+
+		draggedOver = false; // Reset draggedOver status after drop
+	};
+
+	let touchstart;
+	let touchend;
+
+	function checkDirection() {
+		const screenWidth = window.innerWidth;
+		const swipeDistance = Math.abs(touchend.screenX - touchstart.screenX);
+		if (touchstart.clientX < 40 && swipeDistance >= screenWidth / 8) {
+			if (touchend.screenX < touchstart.screenX) {
+				showSidebar.set(false);
+			}
+			if (touchend.screenX > touchstart.screenX) {
+				showSidebar.set(true);
+			}
+		}
+	}
+
+	const onTouchStart = (e) => {
+		touchstart = e.changedTouches[0];
+		console.log(touchstart.clientX);
+	};
+
+	const onTouchEnd = (e) => {
+		touchend = e.changedTouches[0];
+		checkDirection();
+	};
+
+	const onKeyDown = (e) => {
+		if (e.key === 'Shift') {
+			shiftKey = true;
+		}
+	};
+
+	const onKeyUp = (e) => {
+		if (e.key === 'Shift') {
+			shiftKey = false;
+		}
+	};
+
+	const onFocus = () => {};
+
+	const onBlur = () => {
+		shiftKey = false;
+		selectedChatId = null;
+	};
+
+	onMount(async () => {
+		showPinnedChat = localStorage?.showPinnedChat ? localStorage.showPinnedChat === 'true' : true;
+
+		mobile.subscribe((e) => {
+			if ($showSidebar && e) {
+				showSidebar.set(false);
+			}
+
+			if (!$showSidebar && !e) {
+				showSidebar.set(true);
+			}
+		});
+
+		showSidebar.set(!$mobile ? localStorage.sidebar === 'true' : false);
+		showSidebar.subscribe((value) => {
+			localStorage.sidebar = value;
+		});
+
+		await initChatList();
+
+		window.addEventListener('keydown', onKeyDown);
+		window.addEventListener('keyup', onKeyUp);
+
+		window.addEventListener('touchstart', onTouchStart);
+		window.addEventListener('touchend', onTouchEnd);
+
+		window.addEventListener('focus', onFocus);
+		window.addEventListener('blur', onBlur);
+
+		const dropZone = document.getElementById('sidebar');
+
+		dropZone?.addEventListener('dragover', onDragOver);
+		dropZone?.addEventListener('drop', onDrop);
+		dropZone?.addEventListener('dragleave', onDragLeave);
+	});
+
+	onDestroy(() => {
+		window.removeEventListener('keydown', onKeyDown);
+		window.removeEventListener('keyup', onKeyUp);
+
+		window.removeEventListener('touchstart', onTouchStart);
+		window.removeEventListener('touchend', onTouchEnd);
+
+		window.removeEventListener('focus', onFocus);
+		window.removeEventListener('blur', onBlur);
+
+		const dropZone = document.getElementById('sidebar');
+
+		dropZone?.removeEventListener('dragover', onDragOver);
+		dropZone?.removeEventListener('drop', onDrop);
+		dropZone?.removeEventListener('dragleave', onDragLeave);
+	});
+</script>
+
+<ArchivedChatsModal
+	bind:show={$showArchivedChats}
+	on:change={async () => {
+		await initChatList();
+	}}
+/>
+
+<!-- svelte-ignore a11y-no-static-element-interactions -->
+
+{#if $showSidebar}
+	<div
+		class=" fixed md:hidden z-40 top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center overflow-hidden overscroll-contain"
+		on:mousedown={() => {
+			showSidebar.set(!$showSidebar);
+		}}
+	/>
+{/if}
+
+<div
+	bind:this={navElement}
+	id="sidebar"
+	class="h-screen max-h-[100dvh] min-h-screen select-none {$showSidebar
+		? 'md:relative w-[260px] max-w-[260px]'
+		: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0 overflow-x-hidden
+        "
+	data-state={$showSidebar}
+>
+	<div
+		class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] overflow-x-hidden z-50 {$showSidebar
+			? ''
+			: 'invisible'}"
+	>
+		<div class="px-2.5 flex justify-between space-x-1 text-gray-600 dark:text-gray-400">
+			<a
+				id="sidebar-new-chat-button"
+				class="flex flex-1 justify-between rounded-lg px-2 h-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+				href="/"
+				draggable="false"
+				on:click={async () => {
+					selectedChatId = null;
+					await goto('/');
+					const newChatButton = document.getElementById('new-chat-button');
+					setTimeout(() => {
+						newChatButton?.click();
+						if ($mobile) {
+							showSidebar.set(false);
+						}
+					}, 0);
+				}}
+			>
+				<div class="self-center mx-1.5">
+					<img
+						crossorigin="anonymous"
+						src="{WEBUI_BASE_URL}/static/favicon.png"
+						class=" size-6 -translate-x-1.5 rounded-full"
+						alt="logo"
+					/>
+				</div>
+				<div class=" self-center font-medium text-sm text-gray-850 dark:text-white font-primary">
+					{$i18n.t('New Chat')}
+				</div>
+				<div class="self-center ml-auto">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="size-5"
+					>
+						<path
+							d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z"
+						/>
+						<path
+							d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0010 3H4.75A2.75 2.75 0 002 5.75v9.5A2.75 2.75 0 004.75 18h9.5A2.75 2.75 0 0017 15.25V10a.75.75 0 00-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5z"
+						/>
+					</svg>
+				</div>
+			</a>
+
+			<button
+				class=" cursor-pointer px-2 py-2 flex rounded-lg hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+				on:click={() => {
+					showSidebar.set(!$showSidebar);
+				}}
+			>
+				<div class=" m-auto self-center">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="2"
+						stroke="currentColor"
+						class="size-5"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
+						/>
+					</svg>
+				</div>
+			</button>
+		</div>
+
+		{#if $user?.role === 'admin'}
+			<div class="px-2.5 flex justify-center text-gray-800 dark:text-gray-200">
+				<a
+					class="flex-grow flex space-x-3 rounded-lg px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+					href="/workspace"
+					on:click={() => {
+						selectedChatId = null;
+						chatId.set('');
+
+						if ($mobile) {
+							showSidebar.set(false);
+						}
+					}}
+					draggable="false"
+				>
+					<div class="self-center">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="2"
+							stroke="currentColor"
+							class="size-[1.1rem]"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z"
+							/>
+						</svg>
+					</div>
+
+					<div class="flex self-center">
+						<div class=" self-center font-medium text-sm font-primary">{$i18n.t('Workspace')}</div>
+					</div>
+				</a>
+			</div>
+		{/if}
+
+		<div class="relative {$temporaryChatEnabled ? 'opacity-20' : ''}">
+			{#if $temporaryChatEnabled}
+				<div class="absolute z-40 w-full h-full flex justify-center"></div>
+			{/if}
+
+			<div class="absolute z-40 right-4 top-1">
+				<Tooltip content={$i18n.t('New folder')}>
+					<button
+						class="p-1 rounded-lg bg-gray-50 hover:bg-gray-100 dark:bg-gray-950 dark:hover:bg-gray-900 transition"
+						on:click={() => {
+							createFolder();
+						}}
+					>
+						<Plus />
+					</button>
+				</Tooltip>
+			</div>
+
+			<SearchInput
+				bind:value={search}
+				on:input={searchDebounceHandler}
+				placeholder={$i18n.t('Search')}
+			/>
+		</div>
+
+		<div
+			class="relative flex flex-col flex-1 overflow-y-auto {$temporaryChatEnabled
+				? 'opacity-20'
+				: ''}"
+		>
+			{#if $temporaryChatEnabled}
+				<div class="absolute z-40 w-full h-full flex justify-center"></div>
+			{/if}
+
+			{#if !search && $pinnedChats.length > 0}
+				<div class="flex flex-col space-y-1 rounded-xl">
+					<Folder
+						className="px-2"
+						bind:open={showPinnedChat}
+						on:change={(e) => {
+							localStorage.setItem('showPinnedChat', e.detail);
+							console.log(e.detail);
+						}}
+						on:import={(e) => {
+							importChatHandler(e.detail, true);
+						}}
+						on:drop={async (e) => {
+							const { type, id } = e.detail;
+
+							if (type === 'chat') {
+								const chat = await getChatById(localStorage.token, id);
+
+								if (chat) {
+									console.log(chat);
+									if (chat.folder_id) {
+										const res = await updateChatFolderIdById(
+											localStorage.token,
+											chat.id,
+											null
+										).catch((error) => {
+											toast.error(error);
+											return null;
+										});
+
+										if (res) {
+											initChatList();
+										}
+									}
+
+									if (!chat.pinned) {
+										const res = await toggleChatPinnedStatusById(localStorage.token, id);
+
+										if (res) {
+											initChatList();
+										}
+									}
+								}
+							}
+						}}
+						name={$i18n.t('Pinned')}
+					>
+						<div
+							class="ml-3 pl-1 mt-[1px] flex flex-col overflow-y-auto scrollbar-hidden border-s border-gray-100 dark:border-gray-900"
+						>
+							{#each $pinnedChats as chat, idx}
+								<ChatItem
+									className=""
+									id={chat.id}
+									title={chat.title}
+									{shiftKey}
+									selected={selectedChatId === chat.id}
+									on:select={() => {
+										selectedChatId = chat.id;
+									}}
+									on:unselect={() => {
+										selectedChatId = null;
+									}}
+									on:change={async () => {
+										initChatList();
+									}}
+									on:tag={(e) => {
+										const { type, name } = e.detail;
+										tagEventHandler(type, name, chat.id);
+									}}
+								/>
+							{/each}
+						</div>
+					</Folder>
+				</div>
+			{/if}
+
+			<div class=" flex-1 flex flex-col overflow-y-auto scrollbar-hidden">
+				{#if !search && folders}
+					<Folders
+						{folders}
+						on:import={(e) => {
+							const { folderId, items } = e.detail;
+							importChatHandler(items, false, folderId);
+						}}
+						on:update={async (e) => {
+							initChatList();
+						}}
+						on:change={async () => {
+							initChatList();
+						}}
+					/>
+				{/if}
+
+				<Folder
+					collapsible={!search}
+					className="px-2 mt-0.5"
+					name={$i18n.t('All chats')}
+					on:import={(e) => {
+						importChatHandler(e.detail);
+					}}
+					on:drop={async (e) => {
+						const { type, id } = e.detail;
+
+						if (type === 'chat') {
+							const chat = await getChatById(localStorage.token, id);
+
+							if (chat) {
+								console.log(chat);
+								if (chat.folder_id) {
+									const res = await updateChatFolderIdById(localStorage.token, chat.id, null).catch(
+										(error) => {
+											toast.error(error);
+											return null;
+										}
+									);
+
+									if (res) {
+										initChatList();
+									}
+								}
+
+								if (chat.pinned) {
+									const res = await toggleChatPinnedStatusById(localStorage.token, id);
+
+									if (res) {
+										initChatList();
+									}
+								}
+							}
+						} else if (type === 'folder') {
+							if (folders[id].parent_id === null) {
+								return;
+							}
+
+							const res = await updateFolderParentIdById(localStorage.token, id, null).catch(
+								(error) => {
+									toast.error(error);
+									return null;
+								}
+							);
+
+							if (res) {
+								await initFolders();
+							}
+						}
+					}}
+				>
+					<div class="pt-1.5">
+						{#if $chats}
+							{#each $chats as chat, idx}
+								{#if idx === 0 || (idx > 0 && chat.time_range !== $chats[idx - 1].time_range)}
+									<div
+										class="w-full pl-2.5 text-xs text-gray-500 dark:text-gray-500 font-medium {idx ===
+										0
+											? ''
+											: 'pt-5'} pb-1.5"
+									>
+										{$i18n.t(chat.time_range)}
+										<!-- localisation keys for time_range to be recognized from the i18next parser (so they don't get automatically removed):
+							{$i18n.t('Today')}
+							{$i18n.t('Yesterday')}
+							{$i18n.t('Previous 7 days')}
+							{$i18n.t('Previous 30 days')}
+							{$i18n.t('January')}
+							{$i18n.t('February')}
+							{$i18n.t('March')}
+							{$i18n.t('April')}
+							{$i18n.t('May')}
+							{$i18n.t('June')}
+							{$i18n.t('July')}
+							{$i18n.t('August')}
+							{$i18n.t('September')}
+							{$i18n.t('October')}
+							{$i18n.t('November')}
+							{$i18n.t('December')}
+							-->
+									</div>
+								{/if}
+
+								<ChatItem
+									className=""
+									id={chat.id}
+									title={chat.title}
+									{shiftKey}
+									selected={selectedChatId === chat.id}
+									on:select={() => {
+										selectedChatId = chat.id;
+									}}
+									on:unselect={() => {
+										selectedChatId = null;
+									}}
+									on:change={async () => {
+										initChatList();
+									}}
+									on:tag={(e) => {
+										const { type, name } = e.detail;
+										tagEventHandler(type, name, chat.id);
+									}}
+								/>
+							{/each}
+
+							{#if $scrollPaginationEnabled && !allChatsLoaded}
+								<Loader
+									on:visible={(e) => {
+										if (!chatListLoading) {
+											loadMoreChats();
+										}
+									}}
+								>
+									<div
+										class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2"
+									>
+										<Spinner className=" size-4" />
+										<div class=" ">Loading...</div>
+									</div>
+								</Loader>
+							{/if}
+						{:else}
+							<div class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2">
+								<Spinner className=" size-4" />
+								<div class=" ">Loading...</div>
+							</div>
+						{/if}
+					</div>
+				</Folder>
+			</div>
+		</div>
+
+		<div class="px-2">
+			<div class="flex flex-col font-primary">
+				{#if $user !== undefined}
+					<UserMenu
+						role={$user.role}
+						on:show={(e) => {
+							if (e.detail === 'archived-chat') {
+								showArchivedChats.set(true);
+							}
+						}}
+					>
+						<button
+							class=" flex items-center rounded-xl py-2.5 px-2.5 w-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+							on:click={() => {
+								showDropdown = !showDropdown;
+							}}
+						>
+							<div class=" self-center mr-3">
+								<img
+									src={$user.profile_image_url}
+									class=" max-w-[30px] object-cover rounded-full"
+									alt="User profile"
+								/>
+							</div>
+							<div class=" self-center font-medium">{$user.name}</div>
+						</button>
+					</UserMenu>
+				{/if}
+			</div>
+		</div>
+	</div>
+</div>
+
+<style>
+	.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
+		visibility: visible;
+	}
+	.scrollbar-hidden::-webkit-scrollbar-thumb {
+		visibility: hidden;
+	}
+</style>
diff --git a/src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte b/src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..b2ae110583b386c19fc2baf519101737acb1e682
--- /dev/null
+++ b/src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte
@@ -0,0 +1,220 @@
+<script lang="ts">
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+	import { toast } from 'svelte-sonner';
+	import dayjs from 'dayjs';
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	import Modal from '$lib/components/common/Modal.svelte';
+	import {
+		archiveChatById,
+		deleteChatById,
+		getAllArchivedChats,
+		getArchivedChatList
+	} from '$lib/apis/chats';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	const i18n = getContext('i18n');
+
+	export let show = false;
+
+	let searchValue = '';
+
+	let chats = [];
+
+	const unarchiveChatHandler = async (chatId) => {
+		const res = await archiveChatById(localStorage.token, chatId).catch((error) => {
+			toast.error(error);
+		});
+
+		chats = await getArchivedChatList(localStorage.token);
+		dispatch('change');
+	};
+
+	const deleteChatHandler = async (chatId) => {
+		const res = await deleteChatById(localStorage.token, chatId).catch((error) => {
+			toast.error(error);
+		});
+
+		chats = await getArchivedChatList(localStorage.token);
+	};
+
+	const exportChatsHandler = async () => {
+		const chats = await getAllArchivedChats(localStorage.token);
+		let blob = new Blob([JSON.stringify(chats)], {
+			type: 'application/json'
+		});
+		saveAs(blob, `archived-chat-export-${Date.now()}.json`);
+	};
+
+	$: if (show) {
+		(async () => {
+			chats = await getArchivedChatList(localStorage.token);
+		})();
+	}
+</script>
+
+<Modal size="lg" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
+			<div class=" text-lg font-medium self-center">{$i18n.t('Archived Chats')}</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col w-full px-5 pb-4 dark:text-gray-200">
+			<div class=" flex w-full mt-2 space-x-2">
+				<div class="flex flex-1">
+					<div class=" self-center ml-1 mr-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 20 20"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<input
+						class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+						bind:value={searchValue}
+						placeholder={$i18n.t('Search Chats')}
+					/>
+				</div>
+			</div>
+			<hr class=" dark:border-gray-850 my-2" />
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				{#if chats.length > 0}
+					<div class="w-full">
+						<div class="text-left text-sm w-full mb-3 max-h-[22rem] overflow-y-scroll">
+							<div class="relative overflow-x-auto">
+								<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
+									<thead
+										class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-800"
+									>
+										<tr>
+											<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
+											<th scope="col" class="px-3 py-2 hidden md:flex">
+												{$i18n.t('Created At')}
+											</th>
+											<th scope="col" class="px-3 py-2 text-right" />
+										</tr>
+									</thead>
+									<tbody>
+										{#each chats.filter((c) => searchValue === '' || c.title
+													.toLowerCase()
+													.includes(searchValue.toLowerCase())) as chat, idx}
+											<tr
+												class="bg-transparent {idx !== chats.length - 1 &&
+													'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs"
+											>
+												<td class="px-3 py-1 w-2/3">
+													<a href="/c/{chat.id}" target="_blank">
+														<div class=" underline line-clamp-1">
+															{chat.title}
+														</div>
+													</a>
+												</td>
+
+												<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
+													<div class="my-auto">
+														{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
+													</div>
+												</td>
+
+												<td class="px-3 py-1 text-right">
+													<div class="flex justify-end w-full">
+														<Tooltip content="Unarchive Chat">
+															<button
+																class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+																on:click={async () => {
+																	unarchiveChatHandler(chat.id);
+																}}
+															>
+																<svg
+																	xmlns="http://www.w3.org/2000/svg"
+																	fill="none"
+																	viewBox="0 0 24 24"
+																	stroke-width="1.5"
+																	stroke="currentColor"
+																	class="size-4"
+																>
+																	<path
+																		stroke-linecap="round"
+																		stroke-linejoin="round"
+																		d="M9 8.25H7.5a2.25 2.25 0 0 0-2.25 2.25v9a2.25 2.25 0 0 0 2.25 2.25h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25H15m0-3-3-3m0 0-3 3m3-3V15"
+																	/>
+																</svg>
+															</button>
+														</Tooltip>
+
+														<Tooltip content="Delete Chat">
+															<button
+																class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+																on:click={async () => {
+																	deleteChatHandler(chat.id);
+																}}
+															>
+																<svg
+																	xmlns="http://www.w3.org/2000/svg"
+																	fill="none"
+																	viewBox="0 0 24 24"
+																	stroke-width="1.5"
+																	stroke="currentColor"
+																	class="w-4 h-4"
+																>
+																	<path
+																		stroke-linecap="round"
+																		stroke-linejoin="round"
+																		d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
+																	/>
+																</svg>
+															</button>
+														</Tooltip>
+													</div>
+												</td>
+											</tr>
+										{/each}
+									</tbody>
+								</table>
+							</div>
+						</div>
+
+						<div class="flex flex-wrap text-sm font-medium gap-1.5 mt-2 m-1">
+							<button
+								class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
+								on:click={() => {
+									exportChatsHandler();
+								}}>Export All Archived Chats</button
+							>
+						</div>
+					</div>
+				{:else}
+					<div class="text-left text-sm w-full mb-8">
+						{$i18n.t('You have no archived conversations.')}
+					</div>
+				{/if}
+			</div>
+		</div>
+	</div>
+</Modal>
diff --git a/src/lib/components/layout/Sidebar/ChatItem.svelte b/src/lib/components/layout/Sidebar/ChatItem.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2ca8e640928a92a48057ca97c430c57ea34b6beb
--- /dev/null
+++ b/src/lib/components/layout/Sidebar/ChatItem.svelte
@@ -0,0 +1,410 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { goto, invalidate, invalidateAll } from '$app/navigation';
+	import { onMount, getContext, createEventDispatcher, tick, onDestroy } from 'svelte';
+	const i18n = getContext('i18n');
+
+	const dispatch = createEventDispatcher();
+
+	import {
+		archiveChatById,
+		cloneChatById,
+		deleteChatById,
+		getAllTags,
+		getChatList,
+		getChatListByTagName,
+		getPinnedChatList,
+		updateChatById
+	} from '$lib/apis/chats';
+	import {
+		chatId,
+		chatTitle as _chatTitle,
+		chats,
+		mobile,
+		pinnedChats,
+		showSidebar,
+		currentChatPage,
+		tags
+	} from '$lib/stores';
+
+	import ChatMenu from './ChatMenu.svelte';
+	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import ShareChatModal from '$lib/components/chat/ShareChatModal.svelte';
+	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
+	import DragGhost from '$lib/components/common/DragGhost.svelte';
+	import Check from '$lib/components/icons/Check.svelte';
+	import XMark from '$lib/components/icons/XMark.svelte';
+	import Document from '$lib/components/icons/Document.svelte';
+
+	export let className = '';
+
+	export let id;
+	export let title;
+
+	export let selected = false;
+	export let shiftKey = false;
+
+	let mouseOver = false;
+
+	let showShareChatModal = false;
+	let confirmEdit = false;
+
+	let chatTitle = title;
+
+	const editChatTitle = async (id, title) => {
+		if (title === '') {
+			toast.error($i18n.t('Title cannot be an empty string.'));
+		} else {
+			await updateChatById(localStorage.token, id, {
+				title: title
+			});
+
+			if (id === $chatId) {
+				_chatTitle.set(title);
+			}
+
+			currentChatPage.set(1);
+			await chats.set(await getChatList(localStorage.token, $currentChatPage));
+			await pinnedChats.set(await getPinnedChatList(localStorage.token));
+		}
+	};
+
+	const cloneChatHandler = async (id) => {
+		const res = await cloneChatById(localStorage.token, id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			goto(`/c/${res.id}`);
+
+			currentChatPage.set(1);
+			await chats.set(await getChatList(localStorage.token, $currentChatPage));
+			await pinnedChats.set(await getPinnedChatList(localStorage.token));
+		}
+	};
+
+	const deleteChatHandler = async (id) => {
+		const res = await deleteChatById(localStorage.token, id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			tags.set(await getAllTags(localStorage.token));
+			if ($chatId === id) {
+				await chatId.set('');
+				await tick();
+				goto('/');
+			}
+
+			dispatch('change');
+		}
+	};
+
+	const archiveChatHandler = async (id) => {
+		await archiveChatById(localStorage.token, id);
+		dispatch('change');
+	};
+
+	const focusEdit = async (node: HTMLInputElement) => {
+		node.focus();
+	};
+
+	let itemElement;
+
+	let dragged = false;
+	let x = 0;
+	let y = 0;
+
+	const dragImage = new Image();
+	dragImage.src =
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
+
+	const onDragStart = (event) => {
+		event.stopPropagation();
+
+		event.dataTransfer.setDragImage(dragImage, 0, 0);
+
+		// Set the data to be transferred
+		event.dataTransfer.setData(
+			'text/plain',
+			JSON.stringify({
+				type: 'chat',
+				id: id
+			})
+		);
+
+		dragged = true;
+		itemElement.style.opacity = '0.5'; // Optional: Visual cue to show it's being dragged
+	};
+
+	const onDrag = (event) => {
+		event.stopPropagation();
+
+		x = event.clientX;
+		y = event.clientY;
+	};
+
+	const onDragEnd = (event) => {
+		event.stopPropagation();
+
+		itemElement.style.opacity = '1'; // Reset visual cue after drag
+		dragged = false;
+	};
+
+	onMount(() => {
+		if (itemElement) {
+			// Event listener for when dragging starts
+			itemElement.addEventListener('dragstart', onDragStart);
+			// Event listener for when dragging occurs (optional)
+			itemElement.addEventListener('drag', onDrag);
+			// Event listener for when dragging ends
+			itemElement.addEventListener('dragend', onDragEnd);
+		}
+	});
+
+	onDestroy(() => {
+		if (itemElement) {
+			itemElement.removeEventListener('dragstart', onDragStart);
+			itemElement.removeEventListener('drag', onDrag);
+			itemElement.removeEventListener('dragend', onDragEnd);
+		}
+	});
+
+	let showDeleteConfirm = false;
+</script>
+
+<ShareChatModal bind:show={showShareChatModal} chatId={id} />
+
+<DeleteConfirmDialog
+	bind:show={showDeleteConfirm}
+	title={$i18n.t('Delete chat?')}
+	on:confirm={() => {
+		deleteChatHandler(id);
+	}}
+>
+	<div class=" text-sm text-gray-500 flex-1 line-clamp-3">
+		{$i18n.t('This will delete')} <span class="  font-semibold">{title}</span>.
+	</div>
+</DeleteConfirmDialog>
+
+{#if dragged && x && y}
+	<DragGhost {x} {y}>
+		<div class=" bg-black/80 backdrop-blur-2xl px-2 py-1 rounded-lg w-fit max-w-40">
+			<div class="flex items-center gap-1">
+				<Document className=" size-[18px]" strokeWidth="2" />
+				<div class=" text-xs text-white line-clamp-1">
+					{title}
+				</div>
+			</div>
+		</div>
+	</DragGhost>
+{/if}
+
+<div bind:this={itemElement} class=" w-full {className} relative group" draggable="true">
+	{#if confirmEdit}
+		<div
+			class=" w-full flex justify-between rounded-lg px-[11px] py-[6px] {id === $chatId ||
+			confirmEdit
+				? 'bg-gray-200 dark:bg-gray-900'
+				: selected
+					? 'bg-gray-100 dark:bg-gray-950'
+					: 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'}  whitespace-nowrap text-ellipsis"
+		>
+			<input
+				use:focusEdit
+				bind:value={chatTitle}
+				class=" bg-transparent w-full outline-none mr-10"
+			/>
+		</div>
+	{:else}
+		<a
+			class=" w-full flex justify-between rounded-lg px-[11px] py-[6px] {id === $chatId ||
+			confirmEdit
+				? 'bg-gray-200 dark:bg-gray-900'
+				: selected
+					? 'bg-gray-100 dark:bg-gray-950'
+					: ' group-hover:bg-gray-100 dark:group-hover:bg-gray-950'}  whitespace-nowrap text-ellipsis"
+			href="/c/{id}"
+			on:click={() => {
+				dispatch('select');
+
+				if ($mobile) {
+					showSidebar.set(false);
+				}
+			}}
+			on:dblclick={() => {
+				chatTitle = title;
+				confirmEdit = true;
+			}}
+			on:mouseenter={(e) => {
+				mouseOver = true;
+			}}
+			on:mouseleave={(e) => {
+				mouseOver = false;
+			}}
+			on:focus={(e) => {}}
+			draggable="false"
+		>
+			<div class=" flex self-center flex-1 w-full">
+				<div class=" text-left self-center overflow-hidden w-full h-[20px]">
+					{title}
+				</div>
+			</div>
+		</a>
+	{/if}
+
+	<!-- svelte-ignore a11y-no-static-element-interactions -->
+	<div
+		class="
+        {id === $chatId || confirmEdit
+			? 'from-gray-200 dark:from-gray-900'
+			: selected
+				? 'from-gray-100 dark:from-gray-950'
+				: 'invisible group-hover:visible from-gray-100 dark:from-gray-950'}
+            absolute {className === 'pr-2'
+			? 'right-[8px]'
+			: 'right-0'}  top-[4px] py-1 pr-0.5 mr-1.5 pl-5 bg-gradient-to-l from-80%
+
+              to-transparent"
+		on:mouseenter={(e) => {
+			mouseOver = true;
+		}}
+		on:mouseleave={(e) => {
+			mouseOver = false;
+		}}
+	>
+		{#if confirmEdit}
+			<div
+				class="flex self-center items-center space-x-1.5 z-10 translate-y-[0.5px] -translate-x-[0.5px]"
+			>
+				<Tooltip content={$i18n.t('Confirm')}>
+					<button
+						class=" self-center dark:hover:text-white transition"
+						on:click={() => {
+							editChatTitle(id, chatTitle);
+							confirmEdit = false;
+							chatTitle = '';
+						}}
+					>
+						<Check className=" size-3.5" strokeWidth="2.5" />
+					</button>
+				</Tooltip>
+
+				<Tooltip content={$i18n.t('Cancel')}>
+					<button
+						class=" self-center dark:hover:text-white transition"
+						on:click={() => {
+							confirmEdit = false;
+							chatTitle = '';
+						}}
+					>
+						<XMark strokeWidth="2.5" />
+					</button>
+				</Tooltip>
+			</div>
+		{:else if shiftKey && mouseOver}
+			<div class=" flex items-center self-center space-x-1.5">
+				<Tooltip content={$i18n.t('Archive')} className="flex items-center">
+					<button
+						class=" self-center dark:hover:text-white transition"
+						on:click={() => {
+							archiveChatHandler(id);
+						}}
+						type="button"
+					>
+						<ArchiveBox className="size-4  translate-y-[0.5px]" strokeWidth="2" />
+					</button>
+				</Tooltip>
+
+				<Tooltip content={$i18n.t('Delete')}>
+					<button
+						class=" self-center dark:hover:text-white transition"
+						on:click={() => {
+							deleteChatHandler(id);
+						}}
+						type="button"
+					>
+						<GarbageBin strokeWidth="2" />
+					</button>
+				</Tooltip>
+			</div>
+		{:else}
+			<div class="flex self-center space-x-1 z-10">
+				<ChatMenu
+					chatId={id}
+					cloneChatHandler={() => {
+						cloneChatHandler(id);
+					}}
+					shareHandler={() => {
+						showShareChatModal = true;
+					}}
+					archiveChatHandler={() => {
+						archiveChatHandler(id);
+					}}
+					renameHandler={() => {
+						chatTitle = title;
+
+						confirmEdit = true;
+					}}
+					deleteHandler={() => {
+						showDeleteConfirm = true;
+					}}
+					onClose={() => {
+						dispatch('unselect');
+					}}
+					on:change={async () => {
+						dispatch('change');
+					}}
+					on:tag={(e) => {
+						dispatch('tag', e.detail);
+					}}
+				>
+					<button
+						aria-label="Chat Menu"
+						class=" self-center dark:hover:text-white transition"
+						on:click={() => {
+							dispatch('select');
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
+							/>
+						</svg>
+					</button>
+				</ChatMenu>
+
+				{#if id === $chatId}
+					<!-- Shortcut support using "delete-chat-button" id -->
+					<button
+						id="delete-chat-button"
+						class="hidden"
+						on:click={() => {
+							showDeleteConfirm = true;
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
+							/>
+						</svg>
+					</button>
+				{/if}
+			</div>
+		{/if}
+	</div>
+</div>
diff --git a/src/lib/components/layout/Sidebar/ChatMenu.svelte b/src/lib/components/layout/Sidebar/ChatMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..391e60fb74e67e723bca1903fc5443f6cdfe839b
--- /dev/null
+++ b/src/lib/components/layout/Sidebar/ChatMenu.svelte
@@ -0,0 +1,277 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	const dispatch = createEventDispatcher();
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
+	import Pencil from '$lib/components/icons/Pencil.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Tags from '$lib/components/chat/Tags.svelte';
+	import Share from '$lib/components/icons/Share.svelte';
+	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
+	import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
+	import Bookmark from '$lib/components/icons/Bookmark.svelte';
+	import BookmarkSlash from '$lib/components/icons/BookmarkSlash.svelte';
+	import {
+		getChatById,
+		getChatPinnedStatusById,
+		toggleChatPinnedStatusById
+	} from '$lib/apis/chats';
+	import { chats } from '$lib/stores';
+	import { createMessagesList } from '$lib/utils';
+	import { downloadChatAsPDF } from '$lib/apis/utils';
+	import Download from '$lib/components/icons/Download.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let shareHandler: Function;
+	export let cloneChatHandler: Function;
+	export let archiveChatHandler: Function;
+	export let renameHandler: Function;
+	export let deleteHandler: Function;
+	export let onClose: Function;
+
+	export let chatId = '';
+
+	let show = false;
+	let pinned = false;
+
+	const pinHandler = async () => {
+		await toggleChatPinnedStatusById(localStorage.token, chatId);
+		dispatch('change');
+	};
+
+	const checkPinned = async () => {
+		pinned = await getChatPinnedStatusById(localStorage.token, chatId);
+	};
+
+	const getChatAsText = async (chat) => {
+		const history = chat.chat.history;
+		const messages = createMessagesList(history, history.currentId);
+		const chatText = messages.reduce((a, message, i, arr) => {
+			return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`;
+		}, '');
+
+		return chatText.trim();
+	};
+
+	const downloadTxt = async () => {
+		const chat = await getChatById(localStorage.token, chatId);
+		if (!chat) {
+			return;
+		}
+
+		const chatText = await getChatAsText(chat);
+		let blob = new Blob([chatText], {
+			type: 'text/plain'
+		});
+
+		saveAs(blob, `chat-${chat.chat.title}.txt`);
+	};
+
+	const downloadPdf = async () => {
+		const chat = await getChatById(localStorage.token, chatId);
+		if (!chat) {
+			return;
+		}
+
+		const history = chat.chat.history;
+		const messages = createMessagesList(history, history.currentId);
+		const blob = await downloadChatAsPDF(chat.chat.title, messages);
+
+		// Create a URL for the blob
+		const url = window.URL.createObjectURL(blob);
+
+		// Create a link element to trigger the download
+		const a = document.createElement('a');
+		a.href = url;
+		a.download = `chat-${chat.chat.title}.pdf`;
+
+		// Append the link to the body and click it programmatically
+		document.body.appendChild(a);
+		a.click();
+
+		// Remove the link from the body
+		document.body.removeChild(a);
+
+		// Revoke the URL to release memory
+		window.URL.revokeObjectURL(url);
+	};
+
+	const downloadJSONExport = async () => {
+		const chat = await getChatById(localStorage.token, chatId);
+
+		if (chat) {
+			let blob = new Blob([JSON.stringify([chat])], {
+				type: 'application/json'
+			});
+			saveAs(blob, `chat-export-${Date.now()}.json`);
+		}
+	};
+
+	$: if (show) {
+		checkPinned();
+	}
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+>
+	<Tooltip content={$i18n.t('More')}>
+		<slot />
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[200px] rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
+			sideOffset={-2}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					pinHandler();
+				}}
+			>
+				{#if pinned}
+					<BookmarkSlash strokeWidth="2" />
+					<div class="flex items-center">{$i18n.t('Unpin')}</div>
+				{:else}
+					<Bookmark strokeWidth="2" />
+					<div class="flex items-center">{$i18n.t('Pin')}</div>
+				{/if}
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					renameHandler();
+				}}
+			>
+				<Pencil strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Rename')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					cloneChatHandler();
+				}}
+			>
+				<DocumentDuplicate strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Clone')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					archiveChatHandler();
+				}}
+			>
+				<ArchiveBox strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Archive')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800  rounded-md"
+				on:click={() => {
+					shareHandler();
+				}}
+			>
+				<Share />
+				<div class="flex items-center">{$i18n.t('Share')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Sub>
+				<DropdownMenu.SubTrigger
+					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				>
+					<Download strokeWidth="2" />
+
+					<div class="flex items-center">{$i18n.t('Download')}</div>
+				</DropdownMenu.SubTrigger>
+				<DropdownMenu.SubContent
+					class="w-full rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
+					transition={flyAndScale}
+					sideOffset={8}
+				>
+					<DropdownMenu.Item
+						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+						on:click={() => {
+							downloadJSONExport();
+						}}
+					>
+						<div class="flex items-center line-clamp-1">{$i18n.t('Export chat (.json)')}</div>
+					</DropdownMenu.Item>
+					<DropdownMenu.Item
+						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+						on:click={() => {
+							downloadTxt();
+						}}
+					>
+						<div class="flex items-center line-clamp-1">{$i18n.t('Plain text (.txt)')}</div>
+					</DropdownMenu.Item>
+
+					<DropdownMenu.Item
+						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+						on:click={() => {
+							downloadPdf();
+						}}
+					>
+						<div class="flex items-center line-clamp-1">{$i18n.t('PDF document (.pdf)')}</div>
+					</DropdownMenu.Item>
+				</DropdownMenu.SubContent>
+			</DropdownMenu.Sub>
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					deleteHandler();
+				}}
+			>
+				<GarbageBin strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Delete')}</div>
+			</DropdownMenu.Item>
+
+			<hr class="border-gray-50 dark:border-gray-850 my-0.5" />
+
+			<div class="flex p-1">
+				<Tags
+					{chatId}
+					on:add={(e) => {
+						dispatch('tag', {
+							type: 'add',
+							name: e.detail.name
+						});
+
+						show = false;
+					}}
+					on:delete={(e) => {
+						dispatch('tag', {
+							type: 'delete',
+							name: e.detail.name
+						});
+
+						show = false;
+					}}
+					on:close={() => {
+						show = false;
+						onClose();
+					}}
+				/>
+			</div>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/layout/Sidebar/Folders.svelte b/src/lib/components/layout/Sidebar/Folders.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..fb0c955c572d7ce594a3d2f78911199615c74e81
--- /dev/null
+++ b/src/lib/components/layout/Sidebar/Folders.svelte
@@ -0,0 +1,35 @@
+<script lang="ts">
+	import { createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+	import RecursiveFolder from './RecursiveFolder.svelte';
+	export let folders = {};
+
+	let folderList = [];
+	// Get the list of folders that have no parent, sorted by name alphabetically
+	$: folderList = Object.keys(folders)
+		.filter((key) => folders[key].parent_id === null)
+		.sort((a, b) =>
+			folders[a].name.localeCompare(folders[b].name, undefined, {
+				numeric: true,
+				sensitivity: 'base'
+			})
+		);
+</script>
+
+{#each folderList as folderId (folderId)}
+	<RecursiveFolder
+		className="px-2"
+		{folders}
+		{folderId}
+		on:import={(e) => {
+			dispatch('import', e.detail);
+		}}
+		on:update={(e) => {
+			dispatch('update', e.detail);
+		}}
+		on:change={(e) => {
+			dispatch('change', e.detail);
+		}}
+	/>
+{/each}
diff --git a/src/lib/components/layout/Sidebar/Folders/FolderMenu.svelte b/src/lib/components/layout/Sidebar/Folders/FolderMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e485a95fd479dd287c46a4b438a93f7ff4176ae7
--- /dev/null
+++ b/src/lib/components/layout/Sidebar/Folders/FolderMenu.svelte
@@ -0,0 +1,70 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
+	import Pencil from '$lib/components/icons/Pencil.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Download from '$lib/components/icons/Download.svelte';
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			dispatch('close');
+		}
+	}}
+>
+	<Tooltip content={$i18n.t('More')}>
+		<slot />
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[160px] rounded-lg px-1 py-1.5  z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
+			sideOffset={-2}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					dispatch('rename');
+				}}
+			>
+				<Pencil strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Rename')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					dispatch('export');
+				}}
+			>
+				<Download strokeWidth="2" />
+
+				<div class="flex items-center">{$i18n.t('Export')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					dispatch('delete');
+				}}
+			>
+				<GarbageBin strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Delete')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..012d10015c0175cd8d223acaed126941bf8c4e19
--- /dev/null
+++ b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte
@@ -0,0 +1,483 @@
+<script>
+	import { getContext, createEventDispatcher, onMount, onDestroy, tick } from 'svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import DOMPurify from 'dompurify';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import ChevronDown from '../../icons/ChevronDown.svelte';
+	import ChevronRight from '../../icons/ChevronRight.svelte';
+	import Collapsible from '../../common/Collapsible.svelte';
+	import DragGhost from '$lib/components/common/DragGhost.svelte';
+
+	import FolderOpen from '$lib/components/icons/FolderOpen.svelte';
+	import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
+	import {
+		deleteFolderById,
+		updateFolderIsExpandedById,
+		updateFolderNameById,
+		updateFolderParentIdById
+	} from '$lib/apis/folders';
+	import { toast } from 'svelte-sonner';
+	import { getChatsByFolderId, updateChatFolderIdById } from '$lib/apis/chats';
+	import ChatItem from './ChatItem.svelte';
+	import FolderMenu from './Folders/FolderMenu.svelte';
+	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+
+	export let open = false;
+
+	export let folders;
+	export let folderId;
+
+	export let className = '';
+
+	export let parentDragged = false;
+
+	let folderElement;
+
+	let edit = false;
+
+	let draggedOver = false;
+	let dragged = false;
+
+	let name = '';
+
+	const onDragOver = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		if (dragged || parentDragged) {
+			return;
+		}
+		draggedOver = true;
+	};
+
+	const onDrop = async (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		if (dragged || parentDragged) {
+			return;
+		}
+
+		if (folderElement.contains(e.target)) {
+			console.log('Dropped on the Button');
+
+			if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
+				// Iterate over all items in the DataTransferItemList use functional programming
+				for (const item of Array.from(e.dataTransfer.items)) {
+					// If dropped items aren't files, reject them
+					if (item.kind === 'file') {
+						const file = item.getAsFile();
+						if (file && file.type === 'application/json') {
+							console.log('Dropped file is a JSON file!');
+
+							// Read the JSON file with FileReader
+							const reader = new FileReader();
+							reader.onload = async function (event) {
+								try {
+									const fileContent = JSON.parse(event.target.result);
+									open = true;
+									dispatch('import', {
+										folderId: folderId,
+										items: fileContent
+									});
+								} catch (error) {
+									console.error('Error parsing JSON file:', error);
+								}
+							};
+
+							// Start reading the file
+							reader.readAsText(file);
+						} else {
+							console.error('Only JSON file types are supported.');
+						}
+
+						console.log(file);
+					} else {
+						// Handle the drag-and-drop data for folders or chats (same as before)
+						const dataTransfer = e.dataTransfer.getData('text/plain');
+						const data = JSON.parse(dataTransfer);
+						console.log(data);
+
+						const { type, id } = data;
+
+						if (type === 'folder') {
+							open = true;
+							if (id === folderId) {
+								return;
+							}
+							// Move the folder
+							const res = await updateFolderParentIdById(localStorage.token, id, folderId).catch(
+								(error) => {
+									toast.error(error);
+									return null;
+								}
+							);
+
+							if (res) {
+								dispatch('update');
+							}
+						} else if (type === 'chat') {
+							open = true;
+
+							// Move the chat
+							const res = await updateChatFolderIdById(localStorage.token, id, folderId).catch(
+								(error) => {
+									toast.error(error);
+									return null;
+								}
+							);
+
+							if (res) {
+								dispatch('update');
+							}
+						}
+					}
+				}
+			}
+
+			draggedOver = false;
+		}
+	};
+
+	const onDragLeave = (e) => {
+		e.preventDefault();
+		if (dragged || parentDragged) {
+			return;
+		}
+
+		draggedOver = false;
+	};
+
+	const dragImage = new Image();
+	dragImage.src =
+		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
+
+	let x;
+	let y;
+
+	const onDragStart = (event) => {
+		event.stopPropagation();
+		event.dataTransfer.setDragImage(dragImage, 0, 0);
+
+		// Set the data to be transferred
+		event.dataTransfer.setData(
+			'text/plain',
+			JSON.stringify({
+				type: 'folder',
+				id: folderId
+			})
+		);
+
+		dragged = true;
+		folderElement.style.opacity = '0.5'; // Optional: Visual cue to show it's being dragged
+	};
+
+	const onDrag = (event) => {
+		event.stopPropagation();
+
+		x = event.clientX;
+		y = event.clientY;
+	};
+
+	const onDragEnd = (event) => {
+		event.stopPropagation();
+
+		folderElement.style.opacity = '1'; // Reset visual cue after drag
+		dragged = false;
+	};
+
+	onMount(() => {
+		open = folders[folderId].is_expanded;
+		if (folderElement) {
+			folderElement.addEventListener('dragover', onDragOver);
+			folderElement.addEventListener('drop', onDrop);
+			folderElement.addEventListener('dragleave', onDragLeave);
+
+			// Event listener for when dragging starts
+			folderElement.addEventListener('dragstart', onDragStart);
+			// Event listener for when dragging occurs (optional)
+			folderElement.addEventListener('drag', onDrag);
+			// Event listener for when dragging ends
+			folderElement.addEventListener('dragend', onDragEnd);
+		}
+	});
+
+	onDestroy(() => {
+		if (folderElement) {
+			folderElement.addEventListener('dragover', onDragOver);
+			folderElement.removeEventListener('drop', onDrop);
+			folderElement.removeEventListener('dragleave', onDragLeave);
+
+			folderElement.removeEventListener('dragstart', onDragStart);
+			folderElement.removeEventListener('drag', onDrag);
+			folderElement.removeEventListener('dragend', onDragEnd);
+		}
+	});
+
+	let showDeleteConfirm = false;
+
+	const deleteHandler = async () => {
+		const res = await deleteFolderById(localStorage.token, folderId).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Folder deleted successfully'));
+			dispatch('update');
+		}
+	};
+
+	const nameUpdateHandler = async () => {
+		if (name === '') {
+			toast.error($i18n.t('Folder name cannot be empty'));
+			return;
+		}
+
+		if (name === folders[folderId].name) {
+			edit = false;
+			return;
+		}
+
+		const currentName = folders[folderId].name;
+
+		name = name.trim();
+		folders[folderId].name = name;
+
+		const res = await updateFolderNameById(localStorage.token, folderId, name).catch((error) => {
+			toast.error(error);
+
+			folders[folderId].name = currentName;
+			return null;
+		});
+
+		if (res) {
+			folders[folderId].name = name;
+			toast.success($i18n.t('Folder name updated successfully'));
+			dispatch('update');
+		}
+	};
+
+	const isExpandedUpdateHandler = async () => {
+		const res = await updateFolderIsExpandedById(localStorage.token, folderId, open).catch(
+			(error) => {
+				toast.error(error);
+				return null;
+			}
+		);
+	};
+
+	let isExpandedUpdateTimeout;
+
+	const isExpandedUpdateDebounceHandler = (open) => {
+		clearTimeout(isExpandedUpdateTimeout);
+		isExpandedUpdateTimeout = setTimeout(() => {
+			isExpandedUpdateHandler();
+		}, 500);
+	};
+
+	$: isExpandedUpdateDebounceHandler(open);
+
+	const editHandler = async () => {
+		console.log('Edit');
+		await tick();
+		name = folders[folderId].name;
+		edit = true;
+
+		await tick();
+
+		// focus on the input
+		setTimeout(() => {
+			const input = document.getElementById(`folder-${folderId}-input`);
+			input.focus();
+		}, 100);
+	};
+
+	const exportHandler = async () => {
+		const chats = await getChatsByFolderId(localStorage.token, folderId).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+		if (!chats) {
+			return;
+		}
+
+		const blob = new Blob([JSON.stringify(chats)], {
+			type: 'application/json'
+		});
+
+		saveAs(blob, `folder-${folders[folderId].name}-export-${Date.now()}.json`);
+	};
+</script>
+
+<DeleteConfirmDialog
+	bind:show={showDeleteConfirm}
+	title={$i18n.t('Delete folder?')}
+	on:confirm={() => {
+		deleteHandler();
+	}}
+>
+	<div class=" text-sm text-gray-700 dark:text-gray-300 flex-1 line-clamp-3">
+		{@html DOMPurify.sanitize(
+			$i18n.t('This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.', {
+				NAME: folders[folderId].name
+			})
+		)}
+	</div>
+</DeleteConfirmDialog>
+
+{#if dragged && x && y}
+	<DragGhost {x} {y}>
+		<div class=" bg-black/80 backdrop-blur-2xl px-2 py-1 rounded-lg w-fit max-w-40">
+			<div class="flex items-center gap-1">
+				<FolderOpen className="size-3.5" strokeWidth="2" />
+				<div class=" text-xs text-white line-clamp-1">
+					{folders[folderId].name}
+				</div>
+			</div>
+		</div>
+	</DragGhost>
+{/if}
+
+<div bind:this={folderElement} class="relative {className}" draggable="true">
+	{#if draggedOver}
+		<div
+			class="absolute top-0 left-0 w-full h-full rounded-sm bg-[hsla(260,85%,65%,0.1)] bg-opacity-50 dark:bg-opacity-10 z-50 pointer-events-none touch-none"
+		></div>
+	{/if}
+
+	<Collapsible
+		bind:open
+		className="w-full"
+		buttonClassName="w-full"
+		hide={(folders[folderId]?.childrenIds ?? []).length === 0 &&
+			(folders[folderId].items?.chats ?? []).length === 0}
+		on:change={(e) => {
+			dispatch('open', e.detail);
+		}}
+	>
+		<!-- svelte-ignore a11y-no-static-element-interactions -->
+		<div class="w-full group">
+			<button
+				id="folder-{folderId}-button"
+				class="relative w-full py-1.5 px-2 rounded-md flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-500 font-medium hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+				on:dblclick={() => {
+					editHandler();
+				}}
+			>
+				<div class="text-gray-300 dark:text-gray-600">
+					{#if open}
+						<ChevronDown className=" size-3" strokeWidth="2.5" />
+					{:else}
+						<ChevronRight className=" size-3" strokeWidth="2.5" />
+					{/if}
+				</div>
+
+				<div class="translate-y-[0.5px] flex-1 justify-start text-start line-clamp-1">
+					{#if edit}
+						<input
+							id="folder-{folderId}-input"
+							type="text"
+							bind:value={name}
+							on:blur={() => {
+								nameUpdateHandler();
+								edit = false;
+							}}
+							on:click={(e) => {
+								// Prevent accidental collapse toggling when clicking inside input
+								e.stopPropagation();
+							}}
+							on:mousedown={(e) => {
+								// Prevent accidental collapse toggling when clicking inside input
+								e.stopPropagation();
+							}}
+							on:keydown={(e) => {
+								if (e.key === 'Enter') {
+									nameUpdateHandler();
+									edit = false;
+								}
+							}}
+							class="w-full h-full bg-transparent text-gray-500 dark:text-gray-500 outline-none"
+						/>
+					{:else}
+						{folders[folderId].name}
+					{/if}
+				</div>
+
+				<button
+					class="absolute z-10 right-2 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
+					on:pointerup={(e) => {
+						e.stopPropagation();
+					}}
+				>
+					<FolderMenu
+						on:rename={() => {
+							editHandler();
+						}}
+						on:delete={() => {
+							showDeleteConfirm = true;
+						}}
+						on:export={() => {
+							exportHandler();
+						}}
+					>
+						<button class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto" on:click={(e) => {}}>
+							<EllipsisHorizontal className="size-4" strokeWidth="2.5" />
+						</button>
+					</FolderMenu>
+				</button>
+			</button>
+		</div>
+
+		<div slot="content" class="w-full">
+			{#if (folders[folderId]?.childrenIds ?? []).length > 0 || (folders[folderId].items?.chats ?? []).length > 0}
+				<div
+					class="ml-3 pl-1 mt-[1px] flex flex-col overflow-y-auto scrollbar-hidden border-s border-gray-100 dark:border-gray-900"
+				>
+					{#if folders[folderId]?.childrenIds}
+						{@const children = folders[folderId]?.childrenIds
+							.map((id) => folders[id])
+							.sort((a, b) =>
+								a.name.localeCompare(b.name, undefined, {
+									numeric: true,
+									sensitivity: 'base'
+								})
+							)}
+
+						{#each children as childFolder (`${folderId}-${childFolder.id}`)}
+							<svelte:self
+								{folders}
+								folderId={childFolder.id}
+								parentDragged={dragged}
+								on:import={(e) => {
+									dispatch('import', e.detail);
+								}}
+								on:update={(e) => {
+									dispatch('update', e.detail);
+								}}
+								on:change={(e) => {
+									dispatch('change', e.detail);
+								}}
+							/>
+						{/each}
+					{/if}
+
+					{#if folders[folderId].items?.chats}
+						{#each folders[folderId].items.chats as chat (chat.id)}
+							<ChatItem
+								id={chat.id}
+								title={chat.title}
+								on:change={(e) => {
+									dispatch('change', e.detail);
+								}}
+							/>
+						{/each}
+					{/if}
+				</div>
+			{/if}
+		</div>
+	</Collapsible>
+</div>
diff --git a/src/lib/components/layout/Sidebar/SearchInput.svelte b/src/lib/components/layout/Sidebar/SearchInput.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..608ba7ce0fb2b9b8ef29b72bb75245b819401f79
--- /dev/null
+++ b/src/lib/components/layout/Sidebar/SearchInput.svelte
@@ -0,0 +1,218 @@
+<script lang="ts">
+	import { tags } from '$lib/stores';
+	import { stringify } from 'postcss';
+	import { getContext, createEventDispatcher, onMount, onDestroy, tick } from 'svelte';
+	import { fade } from 'svelte/transition';
+
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	export let placeholder = '';
+	export let value = '';
+
+	let selectedIdx = 0;
+
+	let lastWord = '';
+	$: lastWord = value ? value.split(' ').at(-1) : value;
+
+	let focused = false;
+	let options = [
+		{
+			name: 'tag:',
+			description: $i18n.t('search for tags')
+		}
+	];
+
+	let filteredOptions = options;
+	$: filteredOptions = options.filter((option) => {
+		return option.name.startsWith(lastWord);
+	});
+
+	let filteredTags = [];
+	$: filteredTags = lastWord.startsWith('tag:')
+		? [
+				...$tags,
+				{
+					id: 'none',
+					name: $i18n.t('Untagged')
+				}
+			].filter((tag) => {
+				const tagName = lastWord.slice(4);
+				if (tagName) {
+					const tagId = tagName.replace(' ', '_').toLowerCase();
+
+					if (tag.id !== tagId) {
+						return tag.id.startsWith(tagId);
+					} else {
+						return false;
+					}
+				} else {
+					return true;
+				}
+			})
+		: [];
+
+	const documentClickHandler = (e) => {
+		const searchContainer = document.getElementById('search-container');
+		const chatSearch = document.getElementById('chat-search');
+
+		if (!searchContainer.contains(e.target) && !chatSearch.contains(e.target)) {
+			if (e.target.id.startsWith('search-tag-') || e.target.id.startsWith('search-option-')) {
+				return;
+			}
+			focused = false;
+		}
+	};
+
+	onMount(() => {
+		document.addEventListener('click', documentClickHandler);
+	});
+
+	onDestroy(() => {
+		document.removeEventListener('click', documentClickHandler);
+	});
+</script>
+
+<div class="px-2 mb-1 flex justify-center space-x-2 relative z-10" id="search-container">
+	<div class="flex w-full rounded-xl" id="chat-search">
+		<div class="self-center pl-3 py-2 rounded-l-xl bg-transparent">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+
+		<input
+			class="w-full rounded-r-xl py-1.5 pl-2.5 pr-4 text-sm bg-transparent dark:text-gray-300 outline-none"
+			placeholder={placeholder ? placeholder : $i18n.t('Search')}
+			bind:value
+			on:input={() => {
+				dispatch('input');
+			}}
+			on:focus={() => {
+				focused = true;
+			}}
+			on:keydown={(e) => {
+				if (e.key === 'Enter') {
+					if (filteredTags.length > 0) {
+						const tagElement = document.getElementById(`search-tag-${selectedIdx}`);
+						tagElement.click();
+						return;
+					}
+
+					if (filteredOptions.length > 0) {
+						const optionElement = document.getElementById(`search-option-${selectedIdx}`);
+						optionElement.click();
+						return;
+					}
+				}
+
+				if (e.key === 'ArrowUp') {
+					e.preventDefault();
+					selectedIdx = Math.max(0, selectedIdx - 1);
+				} else if (e.key === 'ArrowDown') {
+					e.preventDefault();
+
+					if (filteredTags.length > 0) {
+						selectedIdx = Math.min(selectedIdx + 1, filteredTags.length - 1);
+					} else {
+						selectedIdx = Math.min(selectedIdx + 1, filteredOptions.length - 1);
+					}
+				} else {
+					// if the user types something, reset to the top selection.
+					selectedIdx = 0;
+				}
+			}}
+		/>
+	</div>
+
+	{#if focused && (filteredOptions.length > 0 || filteredTags.length > 0)}
+		<!-- svelte-ignore a11y-no-static-element-interactions -->
+		<div
+			class="absolute top-0 mt-8 left-0 right-1 border dark:border-gray-900 bg-gray-50 dark:bg-gray-950 rounded-lg z-10 shadow-lg"
+			in:fade={{ duration: 50 }}
+			on:mouseenter={() => {
+				selectedIdx = null;
+			}}
+			on:mouseleave={() => {
+				selectedIdx = 0;
+			}}
+		>
+			<div class="px-2 py-2 text-xs group">
+				{#if filteredTags.length > 0}
+					<div class="px-1 font-medium dark:text-gray-300 text-gray-700 mb-1">Tags</div>
+
+					<div class="max-h-60 overflow-auto">
+						{#each filteredTags as tag, tagIdx}
+							<button
+								class=" px-1.5 py-0.5 flex gap-1 hover:bg-gray-100 dark:hover:bg-gray-900 w-full rounded {selectedIdx ===
+								tagIdx
+									? 'bg-gray-100 dark:bg-gray-900'
+									: ''}"
+								id="search-tag-{tagIdx}"
+								on:click|stopPropagation={async () => {
+									const words = value.split(' ');
+
+									words.pop();
+									words.push(`tag:${tag.id} `);
+
+									value = words.join(' ');
+
+									dispatch('input');
+								}}
+							>
+								<div
+									class="dark:text-gray-300 text-gray-700 font-medium line-clamp-1 flex-shrink-0"
+								>
+									{tag.name}
+								</div>
+
+								<div class=" text-gray-500 line-clamp-1">
+									{tag.id}
+								</div>
+							</button>
+						{/each}
+					</div>
+				{:else if filteredOptions.length > 0}
+					<div class="px-1 font-medium dark:text-gray-300 text-gray-700 mb-1">Search options</div>
+
+					<div class=" max-h-60 overflow-auto">
+						{#each filteredOptions as option, optionIdx}
+							<button
+								class=" px-1.5 py-0.5 flex gap-1 hover:bg-gray-100 dark:hover:bg-gray-900 w-full rounded {selectedIdx ===
+								optionIdx
+									? 'bg-gray-100 dark:bg-gray-900'
+									: ''}"
+								id="search-option-{optionIdx}"
+								on:click|stopPropagation={async () => {
+									const words = value.split(' ');
+
+									words.pop();
+									words.push('tag:');
+
+									value = words.join(' ');
+
+									dispatch('input');
+								}}
+							>
+								<div class="dark:text-gray-300 text-gray-700 font-medium">{option.name}</div>
+
+								<div class=" text-gray-500 line-clamp-1">
+									{option.description}
+								</div>
+							</button>
+						{/each}
+					</div>
+				{/if}
+			</div>
+		</div>
+	{/if}
+</div>
diff --git a/src/lib/components/layout/Sidebar/UserMenu.svelte b/src/lib/components/layout/Sidebar/UserMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e56cb5efc770888893f794dd46ab3b60cf6cab47
--- /dev/null
+++ b/src/lib/components/layout/Sidebar/UserMenu.svelte
@@ -0,0 +1,222 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { createEventDispatcher, getContext, onMount } from 'svelte';
+
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { goto } from '$app/navigation';
+	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
+	import { showSettings, activeUserCount, USAGE_POOL, mobile, showSidebar } from '$lib/stores';
+	import { fade, slide } from 'svelte/transition';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import { userSignOut } from '$lib/apis/auths';
+
+	const i18n = getContext('i18n');
+
+	export let show = false;
+	export let role = '';
+	export let className = 'max-w-[240px]';
+
+	const dispatch = createEventDispatcher();
+</script>
+
+<DropdownMenu.Root
+	bind:open={show}
+	onOpenChange={(state) => {
+		dispatch('change', state);
+	}}
+>
+	<DropdownMenu.Trigger>
+		<slot />
+	</DropdownMenu.Trigger>
+
+	<slot name="content">
+		<DropdownMenu.Content
+			class="w-full {className} text-sm rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg font-primary"
+			sideOffset={8}
+			side="bottom"
+			align="start"
+			transition={(e) => fade(e, { duration: 100 })}
+		>
+			<button
+				class="flex rounded-md py-2 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
+				on:click={async () => {
+					await showSettings.set(true);
+					show = false;
+
+					if ($mobile) {
+						showSidebar.set(false);
+					}
+				}}
+			>
+				<div class=" self-center mr-3">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-5 h-5"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"
+						/>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
+						/>
+					</svg>
+				</div>
+				<div class=" self-center">{$i18n.t('Settings')}</div>
+			</button>
+
+			<button
+				class="flex rounded-md py-2 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
+				on:click={() => {
+					dispatch('show', 'archived-chat');
+					show = false;
+
+					if ($mobile) {
+						showSidebar.set(false);
+					}
+				}}
+			>
+				<div class=" self-center mr-3">
+					<ArchiveBox className="size-5" strokeWidth="1.5" />
+				</div>
+				<div class=" self-center">{$i18n.t('Archived Chats')}</div>
+			</button>
+
+			{#if role === 'admin'}
+				<button
+					class="flex rounded-md py-2 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
+					on:click={() => {
+						goto('/playground');
+						show = false;
+
+						if ($mobile) {
+							showSidebar.set(false);
+						}
+					}}
+				>
+					<div class=" self-center mr-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="1.5"
+							stroke="currentColor"
+							class="size-5"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="M14.25 9.75 16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0 0 20.25 18V6A2.25 2.25 0 0 0 18 3.75H6A2.25 2.25 0 0 0 3.75 6v12A2.25 2.25 0 0 0 6 20.25Z"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">{$i18n.t('Playground')}</div>
+				</button>
+
+				<button
+					class="flex rounded-md py-2 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
+					on:click={() => {
+						goto('/admin');
+						show = false;
+
+						if ($mobile) {
+							showSidebar.set(false);
+						}
+					}}
+				>
+					<div class=" self-center mr-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="1.5"
+							stroke="currentColor"
+							class="w-5 h-5"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">{$i18n.t('Admin Panel')}</div>
+				</button>
+			{/if}
+
+			<hr class=" border-gray-50 dark:border-gray-850 my-1 p-0" />
+
+			<button
+				class="flex rounded-md py-2 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
+				on:click={async () => {
+					await userSignOut();
+					localStorage.removeItem('token');
+					location.href = '/auth';
+					show = false;
+				}}
+			>
+				<div class=" self-center mr-3">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="w-5 h-5"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M3 4.25A2.25 2.25 0 015.25 2h5.5A2.25 2.25 0 0113 4.25v2a.75.75 0 01-1.5 0v-2a.75.75 0 00-.75-.75h-5.5a.75.75 0 00-.75.75v11.5c0 .414.336.75.75.75h5.5a.75.75 0 00.75-.75v-2a.75.75 0 011.5 0v2A2.25 2.25 0 0110.75 18h-5.5A2.25 2.25 0 013 15.75V4.25z"
+							clip-rule="evenodd"
+						/>
+						<path
+							fill-rule="evenodd"
+							d="M6 10a.75.75 0 01.75-.75h9.546l-1.048-.943a.75.75 0 111.004-1.114l2.5 2.25a.75.75 0 010 1.114l-2.5 2.25a.75.75 0 11-1.004-1.114l1.048-.943H6.75A.75.75 0 016 10z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+				<div class=" self-center">{$i18n.t('Sign Out')}</div>
+			</button>
+
+			{#if $activeUserCount}
+				<hr class=" border-gray-50 dark:border-gray-850 my-1 p-0" />
+
+				<Tooltip
+					content={$USAGE_POOL && $USAGE_POOL.length > 0
+						? `${$i18n.t('Running')}: ${$USAGE_POOL.join(', ')} ✨`
+						: ''}
+				>
+					<div class="flex rounded-md py-1.5 px-3 text-xs gap-2.5 items-center">
+						<div class=" flex items-center">
+							<span class="relative flex size-2">
+								<span
+									class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
+								/>
+								<span class="relative inline-flex rounded-full size-2 bg-green-500" />
+							</span>
+						</div>
+
+						<div class=" ">
+							<span class="">
+								{$i18n.t('Active Users')}:
+							</span>
+							<span class=" font-semibold">
+								{$activeUserCount}
+							</span>
+						</div>
+					</div>
+				</Tooltip>
+			{/if}
+
+			<!-- <DropdownMenu.Item class="flex items-center px-3 py-2 text-sm ">
+				<div class="flex items-center">Profile</div>
+			</DropdownMenu.Item> -->
+		</DropdownMenu.Content>
+	</slot>
+</DropdownMenu.Root>
diff --git a/src/lib/components/layout/UpdateInfoToast.svelte b/src/lib/components/layout/UpdateInfoToast.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d7e6b53dd7167d34919c92c051ecceb14b1f6c98
--- /dev/null
+++ b/src/lib/components/layout/UpdateInfoToast.svelte
@@ -0,0 +1,39 @@
+<script lang="ts">
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	import { WEBUI_VERSION } from '$lib/constants';
+	import XMark from '../icons/XMark.svelte';
+
+	export let version = {
+		current: WEBUI_VERSION,
+		latest: WEBUI_VERSION
+	};
+</script>
+
+<div
+	class="flex items-start bg-[#F1F8FE] dark:bg-[#020C1D] border border-[3371D5] dark:border-[#03113B] text-[#3371D5] dark:text-[#6795EC] rounded-lg px-3.5 py-3 text-xs max-w-80 pr-2 w-full shadow-lg"
+>
+	<div class="flex-1 font-medium">
+		{$i18n.t(`A new version (v{{LATEST_VERSION}}) is now available.`, {
+			LATEST_VERSION: version.latest
+		})}
+
+		<a href="https://github.com/open-webui/open-webui/releases" target="_blank" class="underline">
+			{$i18n.t('Update for the latest features and improvements.')}</a
+		>
+	</div>
+
+	<div class=" flex-shrink-0 pr-1">
+		<button
+			class=" hover:text-blue-900 dark:hover:text-blue-300 transition"
+			on:click={() => {
+				dispatch('close');
+			}}
+		>
+			<XMark />
+		</button>
+	</div>
+</div>
diff --git a/src/lib/components/playground/Chat.svelte b/src/lib/components/playground/Chat.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d03275ee971ed336073e2e778a295cb5d4de1a03
--- /dev/null
+++ b/src/lib/components/playground/Chat.svelte
@@ -0,0 +1,374 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+
+	import { goto } from '$app/navigation';
+	import { onMount, tick, getContext } from 'svelte';
+
+	import {
+		OLLAMA_API_BASE_URL,
+		OPENAI_API_BASE_URL,
+		WEBUI_API_BASE_URL,
+		WEBUI_BASE_URL
+	} from '$lib/constants';
+	import { WEBUI_NAME, config, user, models, settings } from '$lib/stores';
+
+	import { generateOpenAIChatCompletion } from '$lib/apis/openai';
+
+	import { splitStream } from '$lib/utils';
+	import Collapsible from '../common/Collapsible.svelte';
+
+	import Messages from '$lib/components/playground/Chat/Messages.svelte';
+	import ChevronUp from '../icons/ChevronUp.svelte';
+	import ChevronDown from '../icons/ChevronDown.svelte';
+	import Pencil from '../icons/Pencil.svelte';
+	import Cog6 from '../icons/Cog6.svelte';
+	import Sidebar from '../common/Sidebar.svelte';
+	import ArrowRight from '../icons/ArrowRight.svelte';
+
+	const i18n = getContext('i18n');
+
+	let loaded = false;
+
+	let selectedModelId = '';
+	let loading = false;
+	let stopResponseFlag = false;
+
+	let messagesContainerElement: HTMLDivElement;
+
+	let showSystem = false;
+	let showSettings = false;
+
+	let system = '';
+
+	let role = 'user';
+	let message = '';
+
+	let messages = [];
+
+	const scrollToBottom = () => {
+		const element = messagesContainerElement;
+
+		if (element) {
+			element.scrollTop = element?.scrollHeight;
+		}
+	};
+
+	const stopResponse = () => {
+		stopResponseFlag = true;
+		console.log('stopResponse');
+	};
+
+	const chatCompletionHandler = async () => {
+		const model = $models.find((model) => model.id === selectedModelId);
+
+		const [res, controller] = await generateOpenAIChatCompletion(
+			localStorage.token,
+			{
+				model: model.id,
+				stream: true,
+				messages: [
+					system
+						? {
+								role: 'system',
+								content: system
+							}
+						: undefined,
+					...messages
+				].filter((message) => message)
+			},
+			`${WEBUI_BASE_URL}/api`
+		);
+
+		let responseMessage;
+		if (messages.at(-1)?.role === 'assistant') {
+			responseMessage = messages.at(-1);
+		} else {
+			responseMessage = {
+				role: 'assistant',
+				content: ''
+			};
+			messages.push(responseMessage);
+			messages = messages;
+		}
+
+		await tick();
+		const textareaElement = document.getElementById(`assistant-${messages.length - 1}-textarea`);
+
+		if (res && res.ok) {
+			const reader = res.body
+				.pipeThrough(new TextDecoderStream())
+				.pipeThrough(splitStream('\n'))
+				.getReader();
+
+			while (true) {
+				const { value, done } = await reader.read();
+				if (done || stopResponseFlag) {
+					if (stopResponseFlag) {
+						controller.abort('User: Stop Response');
+					}
+					break;
+				}
+
+				try {
+					let lines = value.split('\n');
+
+					for (const line of lines) {
+						if (line !== '') {
+							console.log(line);
+							if (line === 'data: [DONE]') {
+								// responseMessage.done = true;
+								messages = messages;
+							} else {
+								let data = JSON.parse(line.replace(/^data: /, ''));
+								console.log(data);
+
+								if (responseMessage.content == '' && data.choices[0].delta.content == '\n') {
+									continue;
+								} else {
+									textareaElement.style.height = textareaElement.scrollHeight + 'px';
+
+									responseMessage.content += data.choices[0].delta.content ?? '';
+									messages = messages;
+
+									textareaElement.style.height = textareaElement.scrollHeight + 'px';
+
+									await tick();
+								}
+							}
+						}
+					}
+				} catch (error) {
+					console.log(error);
+				}
+
+				scrollToBottom();
+			}
+		}
+	};
+
+	const addHandler = async () => {
+		if (message) {
+			messages.push({
+				role: role,
+				content: message
+			});
+			messages = messages;
+			message = '';
+			await tick();
+			scrollToBottom();
+		}
+	};
+
+	const submitHandler = async () => {
+		if (selectedModelId) {
+			await addHandler();
+
+			loading = true;
+			await chatCompletionHandler();
+
+			loading = false;
+			stopResponseFlag = false;
+		}
+	};
+
+	onMount(async () => {
+		if ($user?.role !== 'admin') {
+			await goto('/');
+		}
+
+		if ($settings?.models) {
+			selectedModelId = $settings?.models[0];
+		} else if ($config?.default_models) {
+			selectedModelId = $config?.default_models.split(',')[0];
+		} else {
+			selectedModelId = '';
+		}
+		loaded = true;
+	});
+</script>
+
+<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
+	<div class="mx-auto w-full md:px-0 h-full relative">
+		<Sidebar bind:show={showSettings} className=" bg-white dark:bg-gray-900" width="300px">
+			<div class="flex flex-col px-5 py-3 text-sm">
+				<div class="flex justify-between items-center mb-2">
+					<div class=" font-medium text-base">Settings</div>
+
+					<div class=" translate-x-1.5">
+						<button
+							class="p-1.5 bg-transparent hover:bg-white/5 transition rounded-lg"
+							on:click={() => {
+								showSettings = !showSettings;
+							}}
+						>
+							<ArrowRight className="size-3" strokeWidth="2.5" />
+						</button>
+					</div>
+				</div>
+
+				<div class="mt-1">
+					<div>
+						<div class=" text-xs font-medium mb-1">Model</div>
+
+						<div class="w-full">
+							<select
+								class="w-full bg-transparent border border-gray-50 dark:border-gray-850 rounded-lg py-1 px-2 -mx-0.5 text-sm outline-none"
+								bind:value={selectedModelId}
+							>
+								{#each $models as model}
+									<option value={model.id} class="bg-gray-50 dark:bg-gray-700">{model.name}</option>
+								{/each}
+							</select>
+						</div>
+					</div>
+				</div>
+			</div>
+		</Sidebar>
+
+		<div class=" flex flex-col h-full px-4 py-1">
+			<div class="flex w-full items-start gap-1.5">
+				<Collapsible
+					className="w-full flex-1"
+					bind:open={showSystem}
+					buttonClassName="w-full rounded-lg text-sm border border-gray-50 dark:border-gray-850 w-full py-1 px-1.5"
+					grow={true}
+				>
+					<div class="flex gap-2 justify-between items-center">
+						<div class=" flex-shrink-0 font-medium ml-1.5">
+							{$i18n.t('System Instructions')}
+						</div>
+
+						{#if !showSystem}
+							<div class=" flex-1 text-gray-500 line-clamp-1">
+								{system}
+							</div>
+						{/if}
+
+						<div class="flex-shrink-0">
+							<button class="p-1.5 bg-transparent hover:bg-white/5 transition rounded-lg">
+								{#if showSystem}
+									<ChevronUp className="size-3.5" />
+								{:else}
+									<Pencil className="size-3.5" />
+								{/if}
+							</button>
+						</div>
+					</div>
+
+					<div slot="content">
+						<div class="pt-1 px-1.5">
+							<textarea
+								id="system-textarea"
+								class="w-full h-full bg-transparent resize-none outline-none text-sm"
+								bind:value={system}
+								placeholder={$i18n.t("You're a helpful assistant.")}
+								rows="4"
+							/>
+						</div>
+					</div>
+				</Collapsible>
+
+				<div class="translate-y-1">
+					<button
+						class="p-1.5 bg-transparent hover:bg-white/5 transition rounded-lg"
+						on:click={() => {
+							showSettings = !showSettings;
+						}}
+					>
+						<Cog6 />
+					</button>
+				</div>
+			</div>
+
+			<div
+				class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
+				id="messages-container"
+				bind:this={messagesContainerElement}
+			>
+				<div class=" h-full w-full flex flex-col">
+					<div class="flex-1 p-1">
+						<Messages bind:messages />
+					</div>
+				</div>
+			</div>
+
+			<div class="pb-3">
+				<div class="text-xs font-medium text-gray-500 px-2 py-1">
+					{selectedModelId}
+				</div>
+				<div class="border border-gray-50 dark:border-gray-850 w-full px-3 py-2.5 rounded-xl">
+					<div class="py-0.5">
+						<!-- $i18n.t('a user') -->
+						<!-- $i18n.t('an assistant') -->
+						<textarea
+							bind:value={message}
+							class=" w-full h-full bg-transparent resize-none outline-none text-sm"
+							placeholder={$i18n.t(`Enter {{role}} message here`, {
+								role: role === 'user' ? $i18n.t('a user') : $i18n.t('an assistant')
+							})}
+							on:input={(e) => {
+								e.target.style.height = '';
+								e.target.style.height = Math.min(e.target.scrollHeight, 150) + 'px';
+							}}
+							on:focus={(e) => {
+								e.target.style.height = '';
+								e.target.style.height = Math.min(e.target.scrollHeight, 150) + 'px';
+							}}
+							rows="2"
+						/>
+					</div>
+
+					<div class="flex justify-between">
+						<div>
+							<button
+								class="px-3.5 py-1.5 text-sm font-medium bg-gray-50 hover:bg-gray-100 text-gray-900 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition rounded-lg"
+								on:click={() => {
+									role = role === 'user' ? 'assistant' : 'user';
+								}}
+							>
+								{#if role === 'user'}
+									{$i18n.t('User')}
+								{:else}
+									{$i18n.t('Assistant')}
+								{/if}
+							</button>
+						</div>
+
+						<div>
+							{#if !loading}
+								<button
+									disabled={message === ''}
+									class="px-3.5 py-1.5 text-sm font-medium disabled:bg-gray-50 dark:disabled:hover:bg-gray-850 disabled:cursor-not-allowed bg-gray-50 hover:bg-gray-100 text-gray-900 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition rounded-lg"
+									on:click={() => {
+										addHandler();
+										role = role === 'user' ? 'assistant' : 'user';
+									}}
+								>
+									{$i18n.t('Add')}
+								</button>
+
+								<button
+									class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-lg"
+									on:click={() => {
+										submitHandler();
+									}}
+								>
+									{$i18n.t('Run')}
+								</button>
+							{:else}
+								<button
+									class="px-3 py-1.5 text-sm font-medium bg-gray-300 text-black transition rounded-lg"
+									on:click={() => {
+										stopResponse();
+									}}
+								>
+									{$i18n.t('Cancel')}
+								</button>
+							{/if}
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
diff --git a/src/lib/components/playground/Chat/Messages.svelte b/src/lib/components/playground/Chat/Messages.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..69574632f6dd51013dbbf75bb35c8f75f68a74cc
--- /dev/null
+++ b/src/lib/components/playground/Chat/Messages.svelte
@@ -0,0 +1,77 @@
+<script lang="ts">
+	import { onMount, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
+
+	export let messages = [];
+	let textAreaElement: HTMLTextAreaElement;
+	onMount(() => {
+		messages.forEach((message, idx) => {
+			textAreaElement.style.height = '';
+			textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
+		});
+	});
+</script>
+
+<div class="py-3 space-y-3">
+	{#each messages as message, idx}
+		<div class="flex gap-2 group">
+			<div class="flex items-start pt-1">
+				<div
+					class="px-2 py-1 text-sm font-semibold uppercase min-w-[6rem] text-left rounded-lg transition"
+				>
+					{$i18n.t(message.role)}
+				</div>
+			</div>
+
+			<div class="flex-1">
+				<!-- $i18n.t('a user') -->
+				<!-- $i18n.t('an assistant') -->
+				<textarea
+					id="{message.role}-{idx}-textarea"
+					bind:this={textAreaElement}
+					class="w-full bg-transparent outline-none rounded-lg p-2 text-sm resize-none overflow-hidden"
+					placeholder={$i18n.t(`Enter {{role}} message here`, {
+						role: message.role === 'user' ? $i18n.t('a user') : $i18n.t('an assistant')
+					})}
+					rows="1"
+					on:input={(e) => {
+						textAreaElement.style.height = '';
+						textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
+					}}
+					on:focus={(e) => {
+						textAreaElement.style.height = '';
+						textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
+
+						// e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
+					}}
+					bind:value={message.content}
+				/>
+			</div>
+
+			<div class=" pt-1">
+				<button
+					class=" group-hover:text-gray-500 dark:text-gray-900 dark:hover:text-gray-300 transition"
+					on:click={() => {
+						messages = messages.filter((message, messageIdx) => messageIdx !== idx);
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="2"
+						stroke="currentColor"
+						class="w-5 h-5"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M15 12H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
+						/>
+					</svg>
+				</button>
+			</div>
+		</div>
+	{/each}
+</div>
diff --git a/src/lib/components/playground/Completions.svelte b/src/lib/components/playground/Completions.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..abc3b8db1695cdf0fab234f8fd05ce16c9af5ded
--- /dev/null
+++ b/src/lib/components/playground/Completions.svelte
@@ -0,0 +1,197 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+
+	import { goto } from '$app/navigation';
+	import { onMount, tick, getContext } from 'svelte';
+
+	import { WEBUI_BASE_URL } from '$lib/constants';
+	import { WEBUI_NAME, config, user, models, settings, showSidebar } from '$lib/stores';
+	import { generateOpenAIChatCompletion } from '$lib/apis/openai';
+
+	import { splitStream } from '$lib/utils';
+	import Selector from '$lib/components/chat/ModelSelector/Selector.svelte';
+	import MenuLines from '../icons/MenuLines.svelte';
+
+	const i18n = getContext('i18n');
+
+	let loaded = false;
+	let text = '';
+
+	let selectedModelId = '';
+
+	let loading = false;
+	let stopResponseFlag = false;
+
+	let textCompletionAreaElement: HTMLTextAreaElement;
+
+	const scrollToBottom = () => {
+		const element = textCompletionAreaElement;
+
+		if (element) {
+			element.scrollTop = element?.scrollHeight;
+		}
+	};
+
+	const stopResponse = () => {
+		stopResponseFlag = true;
+		console.log('stopResponse');
+	};
+
+	const textCompletionHandler = async () => {
+		const model = $models.find((model) => model.id === selectedModelId);
+
+		const [res, controller] = await generateOpenAIChatCompletion(
+			localStorage.token,
+			{
+				model: model.id,
+				stream: true,
+				messages: [
+					{
+						role: 'assistant',
+						content: text
+					}
+				]
+			},
+			`${WEBUI_BASE_URL}/api`
+		);
+
+		if (res && res.ok) {
+			const reader = res.body
+				.pipeThrough(new TextDecoderStream())
+				.pipeThrough(splitStream('\n'))
+				.getReader();
+
+			while (true) {
+				const { value, done } = await reader.read();
+				if (done || stopResponseFlag) {
+					if (stopResponseFlag) {
+						controller.abort('User: Stop Response');
+					}
+					break;
+				}
+
+				try {
+					let lines = value.split('\n');
+
+					for (const line of lines) {
+						if (line !== '') {
+							if (line.includes('[DONE]')) {
+								console.log('done');
+							} else {
+								let data = JSON.parse(line.replace(/^data: /, ''));
+								console.log(data);
+
+								text += data.choices[0].delta.content ?? '';
+							}
+						}
+					}
+				} catch (error) {
+					console.log(error);
+				}
+
+				scrollToBottom();
+			}
+		}
+	};
+
+	const submitHandler = async () => {
+		if (selectedModelId) {
+			loading = true;
+			await textCompletionHandler();
+
+			loading = false;
+			stopResponseFlag = false;
+		}
+	};
+
+	onMount(async () => {
+		if ($user?.role !== 'admin') {
+			await goto('/');
+		}
+
+		if ($settings?.models) {
+			selectedModelId = $settings?.models[0];
+		} else if ($config?.default_models) {
+			selectedModelId = $config?.default_models.split(',')[0];
+		} else {
+			selectedModelId = '';
+		}
+		loaded = true;
+	});
+</script>
+
+<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
+	<div class="mx-auto w-full md:px-0 h-full">
+		<div class=" flex flex-col h-full px-4">
+			<div class="flex flex-col justify-between mb-1 gap-1">
+				<div class="flex flex-col gap-1 w-full">
+					<div class="flex w-full">
+						<div class="overflow-hidden w-full">
+							<div class="max-w-full">
+								<Selector
+									placeholder={$i18n.t('Select a model')}
+									items={$models.map((model) => ({
+										value: model.id,
+										label: model.name,
+										model: model
+									}))}
+									bind:value={selectedModelId}
+								/>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+
+			<div
+				class=" pt-0.5 pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
+				id="messages-container"
+			>
+				<div class=" h-full w-full flex flex-col">
+					<div class="flex-1">
+						<textarea
+							id="text-completion-textarea"
+							bind:this={textCompletionAreaElement}
+							class="w-full h-full p-3 bg-transparent border border-gray-50 dark:border-gray-850 outline-none resize-none rounded-lg text-sm"
+							bind:value={text}
+							placeholder={$i18n.t("You're a helpful assistant.")}
+						/>
+					</div>
+				</div>
+			</div>
+
+			<div class="pb-3 flex justify-end">
+				{#if !loading}
+					<button
+						class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+						on:click={() => {
+							submitHandler();
+						}}
+					>
+						{$i18n.t('Run')}
+					</button>
+				{:else}
+					<button
+						class="px-3 py-1.5 text-sm font-medium bg-gray-300 text-black transition rounded-full"
+						on:click={() => {
+							stopResponse();
+						}}
+					>
+						{$i18n.t('Cancel')}
+					</button>
+				{/if}
+			</div>
+		</div>
+	</div>
+</div>
+
+<style>
+	.scrollbar-hidden::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.scrollbar-hidden {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+</style>
diff --git a/src/lib/components/playground/Notes.svelte b/src/lib/components/playground/Notes.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6b616cf467643c289f31d341bde0595db450023c
--- /dev/null
+++ b/src/lib/components/playground/Notes.svelte
@@ -0,0 +1,121 @@
+<script>
+	import { getContext } from 'svelte';
+	const i18n = getContext('i18n');
+
+	import RichTextInput from '../common/RichTextInput.svelte';
+	import Spinner from '../common/Spinner.svelte';
+	import Sparkles from '../icons/Sparkles.svelte';
+	import SparklesSolid from '../icons/SparklesSolid.svelte';
+	import Mic from '../icons/Mic.svelte';
+	import VoiceRecording from '../chat/MessageInput/VoiceRecording.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import { toast } from 'svelte-sonner';
+
+	let name = '';
+	let content = '';
+
+	let voiceInput = false;
+	let loading = false;
+</script>
+
+<div class="relative flex-1 w-full h-full flex justify-center overflow-auto px-5 py-1">
+	{#if loading}
+		<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
+			<div class="m-auto">
+				<Spinner />
+			</div>
+		</div>
+	{/if}
+
+	<div class=" w-full flex flex-col gap-2 {loading ? 'opacity-20' : ''}">
+		<div class="flex-shrink-0 w-full flex justify-between items-center">
+			<div class="w-full">
+				<input
+					class="w-full text-2xl font-medium bg-transparent outline-none"
+					type="text"
+					bind:value={name}
+					placeholder={$i18n.t('Title')}
+					required
+				/>
+			</div>
+		</div>
+
+		<div class=" flex-1 w-full h-full">
+			<RichTextInput
+				className=" input-prose-sm"
+				bind:value={content}
+				placeholder={$i18n.t('Write something...')}
+			/>
+		</div>
+	</div>
+
+	<div class="absolute bottom-0 left-0 right-0 p-5 max-w-full flex justify-end">
+		<div class="flex gap-0.5 justify-end w-full">
+			{#if voiceInput}
+				<div class="flex-1 w-full">
+					<VoiceRecording
+						bind:recording={voiceInput}
+						className="p-1 w-full max-w-full"
+						on:cancel={() => {
+							voiceInput = false;
+						}}
+						on:confirm={(e) => {
+							const { text, filename } = e.detail;
+
+							// url is hostname + /cache/audio/transcription/ + filename
+							const url = `${window.location.origin}/cache/audio/transcription/${filename}`;
+
+							// Open in new tab
+
+							if (content.trim() !== '') {
+								content = `${content}\n\n${text}\n\nRecording: ${url}\n\n`;
+							} else {
+								content = `${content}${text}\n\nRecording: ${url}\n\n`;
+							}
+
+							voiceInput = false;
+						}}
+					/>
+				</div>
+			{:else}
+				<Tooltip content={$i18n.t('Voice Input')}>
+					<button
+						class="cursor-pointer p-2.5 flex rounded-full hover:bg-gray-100 dark:hover:bg-gray-850 transition shadow-xl"
+						type="button"
+						on:click={async () => {
+							try {
+								let stream = await navigator.mediaDevices
+									.getUserMedia({ audio: true })
+									.catch(function (err) {
+										toast.error(
+											$i18n.t(`Permission denied when accessing microphone: {{error}}`, {
+												error: err
+											})
+										);
+										return null;
+									});
+
+								if (stream) {
+									voiceInput = true;
+									const tracks = stream.getTracks();
+									tracks.forEach((track) => track.stop());
+								}
+								stream = null;
+							} catch {
+								toast.error($i18n.t('Permission denied when accessing microphone'));
+							}
+						}}
+					>
+						<Mic className="size-4" />
+					</button>
+				</Tooltip>
+			{/if}
+
+			<!-- <button
+				class="cursor-pointer p-2.5 flex rounded-full hover:bg-gray-100 dark:hover:bg-gray-850 transition shadow-xl"
+			>
+				<SparklesSolid className="size-4" />
+			</button> -->
+		</div>
+	</div>
+</div>
diff --git a/src/lib/components/workspace/Functions.svelte b/src/lib/components/workspace/Functions.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..c488c57c24684547bbe80fc359046ee169ca2ff4
--- /dev/null
+++ b/src/lib/components/workspace/Functions.svelte
@@ -0,0 +1,573 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { WEBUI_NAME, config, functions, models } from '$lib/stores';
+	import { onMount, getContext, tick } from 'svelte';
+	import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
+
+	import { goto } from '$app/navigation';
+	import {
+		createNewFunction,
+		deleteFunctionById,
+		exportFunctions,
+		getFunctionById,
+		getFunctions,
+		toggleFunctionById,
+		toggleGlobalById
+	} from '$lib/apis/functions';
+
+	import ArrowDownTray from '../icons/ArrowDownTray.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import ConfirmDialog from '../common/ConfirmDialog.svelte';
+	import { getModels } from '$lib/apis';
+	import FunctionMenu from './Functions/FunctionMenu.svelte';
+	import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
+	import Switch from '../common/Switch.svelte';
+	import ValvesModal from './common/ValvesModal.svelte';
+	import ManifestModal from './common/ManifestModal.svelte';
+	import Heart from '../icons/Heart.svelte';
+	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import GarbageBin from '../icons/GarbageBin.svelte';
+
+	const i18n = getContext('i18n');
+
+	let shiftKey = false;
+
+	let functionsImportInputElement: HTMLInputElement;
+	let importFiles;
+
+	let showConfirm = false;
+	let query = '';
+
+	let showManifestModal = false;
+	let showValvesModal = false;
+	let selectedFunction = null;
+
+	let showDeleteConfirm = false;
+
+	let filteredItems = [];
+	$: filteredItems = $functions.filter(
+		(f) =>
+			query === '' ||
+			f.name.toLowerCase().includes(query.toLowerCase()) ||
+			f.id.toLowerCase().includes(query.toLowerCase())
+	);
+
+	const shareHandler = async (func) => {
+		const item = await getFunctionById(localStorage.token, func.id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
+
+		const url = 'https://openwebui.com';
+
+		const tab = await window.open(`${url}/functions/create`, '_blank');
+
+		// Define the event handler function
+		const messageHandler = (event) => {
+			if (event.origin !== url) return;
+			if (event.data === 'loaded') {
+				tab.postMessage(JSON.stringify(item), '*');
+
+				// Remove the event listener after handling the message
+				window.removeEventListener('message', messageHandler);
+			}
+		};
+
+		window.addEventListener('message', messageHandler, false);
+		console.log(item);
+	};
+
+	const cloneHandler = async (func) => {
+		const _function = await getFunctionById(localStorage.token, func.id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (_function) {
+			sessionStorage.function = JSON.stringify({
+				..._function,
+				id: `${_function.id}_clone`,
+				name: `${_function.name} (Clone)`
+			});
+			goto('/workspace/functions/create');
+		}
+	};
+
+	const exportHandler = async (func) => {
+		const _function = await getFunctionById(localStorage.token, func.id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (_function) {
+			let blob = new Blob([JSON.stringify([_function])], {
+				type: 'application/json'
+			});
+			saveAs(blob, `function-${_function.id}-export-${Date.now()}.json`);
+		}
+	};
+
+	const deleteHandler = async (func) => {
+		const res = await deleteFunctionById(localStorage.token, func.id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Function deleted successfully'));
+
+			functions.set(await getFunctions(localStorage.token));
+			models.set(await getModels(localStorage.token));
+		}
+	};
+
+	const toggleGlobalHandler = async (func) => {
+		const res = await toggleGlobalById(localStorage.token, func.id).catch((error) => {
+			toast.error(error);
+		});
+
+		if (res) {
+			if (func.is_global) {
+				func.type === 'filter'
+					? toast.success($i18n.t('Filter is now globally enabled'))
+					: toast.success($i18n.t('Function is now globally enabled'));
+			} else {
+				func.type === 'filter'
+					? toast.success($i18n.t('Filter is now globally disabled'))
+					: toast.success($i18n.t('Function is now globally disabled'));
+			}
+
+			functions.set(await getFunctions(localStorage.token));
+			models.set(await getModels(localStorage.token));
+		}
+	};
+
+	onMount(() => {
+		const onKeyDown = (event) => {
+			if (event.key === 'Shift') {
+				shiftKey = true;
+			}
+		};
+
+		const onKeyUp = (event) => {
+			if (event.key === 'Shift') {
+				shiftKey = false;
+			}
+		};
+
+		const onBlur = () => {
+			shiftKey = false;
+		};
+
+		window.addEventListener('keydown', onKeyDown);
+		window.addEventListener('keyup', onKeyUp);
+		window.addEventListener('blur', onBlur);
+
+		return () => {
+			window.removeEventListener('keydown', onKeyDown);
+			window.removeEventListener('keyup', onKeyUp);
+			window.removeEventListener('blur', onBlur);
+		};
+	});
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Functions')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<div class=" flex w-full space-x-2 mb-2.5">
+	<div class="flex flex-1">
+		<div class=" self-center ml-1 mr-3">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<input
+			class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+			bind:value={query}
+			placeholder={$i18n.t('Search Functions')}
+		/>
+	</div>
+
+	<div>
+		<a
+			class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
+			href="/workspace/functions/create"
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</a>
+	</div>
+</div>
+
+<div class="mb-3.5">
+	<div class="flex justify-between items-center">
+		<div class="flex md:self-center text-base font-medium px-0.5">
+			{$i18n.t('Functions')}
+			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
+			<span class="text-base font-medium text-gray-500 dark:text-gray-300"
+				>{filteredItems.length}</span
+			>
+		</div>
+	</div>
+</div>
+
+<div class="my-3 mb-5">
+	{#each filteredItems as func}
+		<div
+			class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+		>
+			<a
+				class=" flex flex-1 space-x-3.5 cursor-pointer w-full"
+				href={`/workspace/functions/edit?id=${encodeURIComponent(func.id)}`}
+			>
+				<div class="flex items-center text-left">
+					<div class=" flex-1 self-center pl-1">
+						<div class=" font-semibold flex items-center gap-1.5">
+							<div
+								class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+							>
+								{func.type}
+							</div>
+
+							{#if func?.meta?.manifest?.version}
+								<div
+									class="text-xs font-bold px-1 rounded line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+								>
+									v{func?.meta?.manifest?.version ?? ''}
+								</div>
+							{/if}
+
+							<div class=" line-clamp-1">
+								{func.name}
+							</div>
+						</div>
+
+						<div class="flex gap-1.5 px-1">
+							<div class=" text-gray-500 text-xs font-medium flex-shrink-0">{func.id}</div>
+
+							<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
+								{func.meta.description}
+							</div>
+						</div>
+					</div>
+				</div>
+			</a>
+			<div class="flex flex-row gap-0.5 self-center">
+				{#if shiftKey}
+					<Tooltip content={$i18n.t('Delete')}>
+						<button
+							class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+							type="button"
+							on:click={() => {
+								deleteHandler(func);
+							}}
+						>
+							<GarbageBin />
+						</button>
+					</Tooltip>
+				{:else}
+					{#if func?.meta?.manifest?.funding_url ?? false}
+						<Tooltip content={$i18n.t('Support')}>
+							<button
+								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+								type="button"
+								on:click={() => {
+									selectedFunction = func;
+									showManifestModal = true;
+								}}
+							>
+								<Heart />
+							</button>
+						</Tooltip>
+					{/if}
+
+					<Tooltip content={$i18n.t('Valves')}>
+						<button
+							class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+							type="button"
+							on:click={() => {
+								selectedFunction = func;
+								showValvesModal = true;
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								fill="none"
+								viewBox="0 0 24 24"
+								stroke-width="1.5"
+								stroke="currentColor"
+								class="size-4"
+							>
+								<path
+									stroke-linecap="round"
+									stroke-linejoin="round"
+									d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
+								/>
+								<path
+									stroke-linecap="round"
+									stroke-linejoin="round"
+									d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
+								/>
+							</svg>
+						</button>
+					</Tooltip>
+
+					<FunctionMenu
+						{func}
+						editHandler={() => {
+							goto(`/workspace/functions/edit?id=${encodeURIComponent(func.id)}`);
+						}}
+						shareHandler={() => {
+							shareHandler(func);
+						}}
+						cloneHandler={() => {
+							cloneHandler(func);
+						}}
+						exportHandler={() => {
+							exportHandler(func);
+						}}
+						deleteHandler={async () => {
+							selectedFunction = func;
+							showDeleteConfirm = true;
+						}}
+						toggleGlobalHandler={() => {
+							if (['filter', 'action'].includes(func.type)) {
+								toggleGlobalHandler(func);
+							}
+						}}
+						onClose={() => {}}
+					>
+						<button
+							class="self-center w-fit text-sm p-1.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+							type="button"
+						>
+							<EllipsisHorizontal className="size-5" />
+						</button>
+					</FunctionMenu>
+				{/if}
+
+				<div class=" self-center mx-1">
+					<Tooltip content={func.is_active ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
+						<Switch
+							bind:state={func.is_active}
+							on:change={async (e) => {
+								toggleFunctionById(localStorage.token, func.id);
+								models.set(await getModels(localStorage.token));
+							}}
+						/>
+					</Tooltip>
+				</div>
+			</div>
+		</div>
+	{/each}
+</div>
+
+<!-- <div class=" text-gray-500 text-xs mt-1 mb-2">
+	ⓘ {$i18n.t(
+		'Admins have access to all tools at all times; users need tools assigned per model in the workspace.'
+	)}
+</div> -->
+
+<div class=" flex justify-end w-full mb-2">
+	<div class="flex space-x-2">
+		<input
+			id="documents-import-input"
+			bind:this={functionsImportInputElement}
+			bind:files={importFiles}
+			type="file"
+			accept=".json"
+			hidden
+			on:change={() => {
+				console.log(importFiles);
+				showConfirm = true;
+			}}
+		/>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={() => {
+				functionsImportInputElement.click();
+			}}
+		>
+			<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Import Functions')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={async () => {
+				const _functions = await exportFunctions(localStorage.token).catch((error) => {
+					toast.error(error);
+					return null;
+				});
+
+				if (_functions) {
+					let blob = new Blob([JSON.stringify(_functions)], {
+						type: 'application/json'
+					});
+					saveAs(blob, `functions-export-${Date.now()}.json`);
+				}
+			}}
+		>
+			<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Functions')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+	</div>
+</div>
+
+{#if $config?.features.enable_community_sharing}
+	<div class=" my-16">
+		<div class=" text-lg font-semibold mb-3 line-clamp-1">
+			{$i18n.t('Made by OpenWebUI Community')}
+		</div>
+
+		<a
+			class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2"
+			href="https://openwebui.com/#open-webui-community"
+			target="_blank"
+		>
+			<div class=" self-center w-10 flex-shrink-0">
+				<div
+					class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 24 24"
+						fill="currentColor"
+						class="w-6"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+			</div>
+
+			<div class=" self-center">
+				<div class=" font-semibold line-clamp-1">{$i18n.t('Discover a function')}</div>
+				<div class=" text-sm line-clamp-1">
+					{$i18n.t('Discover, download, and explore custom functions')}
+				</div>
+			</div>
+		</a>
+	</div>
+{/if}
+
+<DeleteConfirmDialog
+	bind:show={showDeleteConfirm}
+	title={$i18n.t('Delete function?')}
+	on:confirm={() => {
+		deleteHandler(selectedFunction);
+	}}
+>
+	<div class=" text-sm text-gray-500">
+		{$i18n.t('This will delete')} <span class="  font-semibold">{selectedFunction.name}</span>.
+	</div>
+</DeleteConfirmDialog>
+
+<ManifestModal bind:show={showManifestModal} manifest={selectedFunction?.meta?.manifest ?? {}} />
+<ValvesModal
+	bind:show={showValvesModal}
+	type="function"
+	id={selectedFunction?.id ?? null}
+	on:save={async () => {
+		await tick();
+		models.set(await getModels(localStorage.token));
+	}}
+/>
+
+<ConfirmDialog
+	bind:show={showConfirm}
+	on:confirm={() => {
+		const reader = new FileReader();
+		reader.onload = async (event) => {
+			const _functions = JSON.parse(event.target.result);
+			console.log(_functions);
+
+			for (const func of _functions) {
+				const res = await createNewFunction(localStorage.token, func).catch((error) => {
+					toast.error(error);
+					return null;
+				});
+			}
+
+			toast.success($i18n.t('Functions imported successfully'));
+			functions.set(await getFunctions(localStorage.token));
+			models.set(await getModels(localStorage.token));
+		};
+
+		reader.readAsText(importFiles[0]);
+	}}
+>
+	<div class="text-sm text-gray-500">
+		<div class=" bg-yellow-500/20 text-yellow-700 dark:text-yellow-200 rounded-lg px-4 py-3">
+			<div>Please carefully review the following warnings:</div>
+
+			<ul class=" mt-1 list-disc pl-4 text-xs">
+				<li>{$i18n.t('Functions allow arbitrary code execution.')}</li>
+				<li>{$i18n.t('Do not install functions from sources you do not fully trust.')}</li>
+			</ul>
+		</div>
+
+		<div class="my-3">
+			{$i18n.t(
+				'I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.'
+			)}
+		</div>
+	</div>
+</ConfirmDialog>
diff --git a/src/lib/components/workspace/Functions/FunctionEditor.svelte b/src/lib/components/workspace/Functions/FunctionEditor.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ec65bae5ddb2b913ad5015fbda23a316e63bf5e0
--- /dev/null
+++ b/src/lib/components/workspace/Functions/FunctionEditor.svelte
@@ -0,0 +1,422 @@
+<script>
+	import { getContext, createEventDispatcher, onMount, tick } from 'svelte';
+	import { goto } from '$app/navigation';
+
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	import CodeEditor from '$lib/components/common/CodeEditor.svelte';
+	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import Badge from '$lib/components/common/Badge.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
+
+	let formElement = null;
+	let loading = false;
+	let showConfirm = false;
+
+	export let edit = false;
+	export let clone = false;
+
+	export let id = '';
+	export let name = '';
+	export let meta = {
+		description: ''
+	};
+	export let content = '';
+	let _content = '';
+
+	$: if (content) {
+		updateContent();
+	}
+
+	const updateContent = () => {
+		_content = content;
+	};
+
+	$: if (name && !edit && !clone) {
+		id = name.replace(/\s+/g, '_').toLowerCase();
+	}
+
+	let codeEditor;
+	let boilerplate = `"""
+title: Example Filter
+author: open-webui
+author_url: https://github.com/open-webui
+funding_url: https://github.com/open-webui
+version: 0.1
+"""
+
+from pydantic import BaseModel, Field
+from typing import Optional
+
+
+class Filter:
+    class Valves(BaseModel):
+        priority: int = Field(
+            default=0, description="Priority level for the filter operations."
+        )
+        max_turns: int = Field(
+            default=8, description="Maximum allowable conversation turns for a user."
+        )
+        pass
+
+    class UserValves(BaseModel):
+        max_turns: int = Field(
+            default=4, description="Maximum allowable conversation turns for a user."
+        )
+        pass
+
+    def __init__(self):
+        # Indicates custom file handling logic. This flag helps disengage default routines in favor of custom
+        # implementations, informing the WebUI to defer file-related operations to designated methods within this class.
+        # Alternatively, you can remove the files directly from the body in from the inlet hook
+        # self.file_handler = True
+
+        # Initialize 'valves' with specific configurations. Using 'Valves' instance helps encapsulate settings,
+        # which ensures settings are managed cohesively and not confused with operational flags like 'file_handler'.
+        self.valves = self.Valves()
+        pass
+
+    def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
+        # Modify the request body or validate it before processing by the chat completion API.
+        # This function is the pre-processor for the API where various checks on the input can be performed.
+        # It can also modify the request before sending it to the API.
+        print(f"inlet:{__name__}")
+        print(f"inlet:body:{body}")
+        print(f"inlet:user:{__user__}")
+
+        if __user__.get("role", "admin") in ["user", "admin"]:
+            messages = body.get("messages", [])
+
+            max_turns = min(__user__["valves"].max_turns, self.valves.max_turns)
+            if len(messages) > max_turns:
+                raise Exception(
+                    f"Conversation turn limit exceeded. Max turns: {max_turns}"
+                )
+
+        return body
+
+    def outlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
+        # Modify or analyze the response body after processing by the API.
+        # This function is the post-processor for the API, which can be used to modify the response
+        # or perform additional checks and analytics.
+        print(f"outlet:{__name__}")
+        print(f"outlet:body:{body}")
+        print(f"outlet:user:{__user__}")
+
+        return body
+`;
+
+	const _boilerplate = `from pydantic import BaseModel
+from typing import Optional, Union, Generator, Iterator
+from open_webui.utils.misc import get_last_user_message
+
+import os
+import requests
+
+
+# Filter Class: This class is designed to serve as a pre-processor and post-processor
+# for request and response modifications. It checks and transforms requests and responses
+# to ensure they meet specific criteria before further processing or returning to the user.
+class Filter:
+    class Valves(BaseModel):
+        max_turns: int = 4
+        pass
+
+    def __init__(self):
+        # Indicates custom file handling logic. This flag helps disengage default routines in favor of custom
+        # implementations, informing the WebUI to defer file-related operations to designated methods within this class.
+        # Alternatively, you can remove the files directly from the body in from the inlet hook
+        self.file_handler = True
+
+        # Initialize 'valves' with specific configurations. Using 'Valves' instance helps encapsulate settings,
+        # which ensures settings are managed cohesively and not confused with operational flags like 'file_handler'.
+        self.valves = self.Valves(**{"max_turns": 2})
+        pass
+
+    def inlet(self, body: dict, user: Optional[dict] = None) -> dict:
+        # Modify the request body or validate it before processing by the chat completion API.
+        # This function is the pre-processor for the API where various checks on the input can be performed.
+        # It can also modify the request before sending it to the API.
+        print(f"inlet:{__name__}")
+        print(f"inlet:body:{body}")
+        print(f"inlet:user:{user}")
+
+        if user.get("role", "admin") in ["user", "admin"]:
+            messages = body.get("messages", [])
+            if len(messages) > self.valves.max_turns:
+                raise Exception(
+                    f"Conversation turn limit exceeded. Max turns: {self.valves.max_turns}"
+                )
+
+        return body
+
+    def outlet(self, body: dict, user: Optional[dict] = None) -> dict:
+        # Modify or analyze the response body after processing by the API.
+        # This function is the post-processor for the API, which can be used to modify the response
+        # or perform additional checks and analytics.
+        print(f"outlet:{__name__}")
+        print(f"outlet:body:{body}")
+        print(f"outlet:user:{user}")
+
+        messages = [
+            {
+                **message,
+                "content": f"{message['content']} - @@Modified from Filter Outlet",
+            }
+            for message in body.get("messages", [])
+        ]
+
+        return {"messages": messages}
+
+
+
+# Pipe Class: This class functions as a customizable pipeline.
+# It can be adapted to work with any external or internal models,
+# making it versatile for various use cases outside of just OpenAI models.
+class Pipe:
+    class Valves(BaseModel):
+        OPENAI_API_BASE_URL: str = "https://api.openai.com/v1"
+        OPENAI_API_KEY: str = "your-key"
+        pass
+
+    def __init__(self):
+        self.type = "manifold"
+        self.valves = self.Valves()
+        self.pipes = self.get_openai_models()
+        pass
+
+    def get_openai_models(self):
+        if self.valves.OPENAI_API_KEY:
+            try:
+                headers = {}
+                headers["Authorization"] = f"Bearer {self.valves.OPENAI_API_KEY}"
+                headers["Content-Type"] = "application/json"
+
+                r = requests.get(
+                    f"{self.valves.OPENAI_API_BASE_URL}/models", headers=headers
+                )
+
+                models = r.json()
+                return [
+                    {
+                        "id": model["id"],
+                        "name": model["name"] if "name" in model else model["id"],
+                    }
+                    for model in models["data"]
+                    if "gpt" in model["id"]
+                ]
+
+            except Exception as e:
+
+                print(f"Error: {e}")
+                return [
+                    {
+                        "id": "error",
+                        "name": "Could not fetch models from OpenAI, please update the API Key in the valves.",
+                    },
+                ]
+        else:
+            return []
+
+    def pipe(self, body: dict) -> Union[str, Generator, Iterator]:
+        # This is where you can add your custom pipelines like RAG.
+        print(f"pipe:{__name__}")
+
+        if "user" in body:
+            print(body["user"])
+            del body["user"]
+
+        headers = {}
+        headers["Authorization"] = f"Bearer {self.valves.OPENAI_API_KEY}"
+        headers["Content-Type"] = "application/json"
+
+        model_id = body["model"][body["model"].find(".") + 1 :]
+        payload = {**body, "model": model_id}
+        print(payload)
+
+        try:
+            r = requests.post(
+                url=f"{self.valves.OPENAI_API_BASE_URL}/chat/completions",
+                json=payload,
+                headers=headers,
+                stream=True,
+            )
+
+            r.raise_for_status()
+
+            if body["stream"]:
+                return r.iter_lines()
+            else:
+                return r.json()
+        except Exception as e:
+            return f"Error: {e}"
+`;
+
+	const saveHandler = async () => {
+		loading = true;
+		dispatch('save', {
+			id,
+			name,
+			meta,
+			content
+		});
+	};
+
+	const submitHandler = async () => {
+		if (codeEditor) {
+			content = _content;
+			await tick();
+
+			const res = await codeEditor.formatPythonCodeHandler();
+			await tick();
+
+			content = _content;
+			await tick();
+
+			if (res) {
+				console.log('Code formatted successfully');
+
+				saveHandler();
+			}
+		}
+	};
+</script>
+
+<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
+	<div class="mx-auto w-full md:px-0 h-full">
+		<form
+			bind:this={formElement}
+			class=" flex flex-col max-h-[100dvh] h-full"
+			on:submit|preventDefault={() => {
+				if (edit) {
+					submitHandler();
+				} else {
+					showConfirm = true;
+				}
+			}}
+		>
+			<div class="flex flex-col flex-1 overflow-auto h-0 rounded-lg">
+				<div class="w-full mb-2 flex flex-col gap-0.5">
+					<div class="flex w-full items-center">
+						<div class=" flex-shrink-0 mr-2">
+							<Tooltip content={$i18n.t('Back')}>
+								<button
+									class="w-full text-left text-sm py-1.5 px-1 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
+									on:click={() => {
+										goto('/workspace/functions');
+									}}
+									type="button"
+								>
+									<ChevronLeft strokeWidth="2.5" />
+								</button>
+							</Tooltip>
+						</div>
+
+						<div class="flex-1">
+							<input
+								class="w-full text-2xl font-medium bg-transparent outline-none font-primary"
+								type="text"
+								placeholder={$i18n.t('Function Name (e.g. My Filter)')}
+								bind:value={name}
+								required
+							/>
+						</div>
+
+						<div>
+							<Badge type="muted" content={$i18n.t('Function')} />
+						</div>
+					</div>
+
+					<div class=" flex gap-2 px-1">
+						{#if edit}
+							<div class="text-sm text-gray-500 flex-shrink-0">
+								{id}
+							</div>
+						{:else}
+							<input
+								class="w-full text-sm disabled:text-gray-500 bg-transparent outline-none"
+								type="text"
+								placeholder={$i18n.t('Function ID (e.g. my_filter)')}
+								bind:value={id}
+								required
+								disabled={edit}
+							/>
+						{/if}
+
+						<input
+							class="w-full text-sm bg-transparent outline-none"
+							type="text"
+							placeholder={$i18n.t(
+								'Function Description (e.g. A filter to remove profanity from text)'
+							)}
+							bind:value={meta.description}
+							required
+						/>
+					</div>
+				</div>
+
+				<div class="mb-2 flex-1 overflow-auto h-0 rounded-lg">
+					<CodeEditor
+						bind:this={codeEditor}
+						value={content}
+						lang="python"
+						{boilerplate}
+						on:change={(e) => {
+							_content = e.detail.value;
+						}}
+						on:save={async () => {
+							if (formElement) {
+								formElement.requestSubmit();
+							}
+						}}
+					/>
+				</div>
+
+				<div class="pb-3 flex justify-between">
+					<div class="flex-1 pr-3">
+						<div class="text-xs text-gray-500 line-clamp-2">
+							<span class=" font-semibold dark:text-gray-200">{$i18n.t('Warning:')}</span>
+							{$i18n.t('Functions allow arbitrary code execution')} <br />—
+							<span class=" font-medium dark:text-gray-400"
+								>{$i18n.t(`don't install random functions from sources you don't trust.`)}</span
+							>
+						</div>
+					</div>
+
+					<button
+						class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+						type="submit"
+					>
+						{$i18n.t('Save')}
+					</button>
+				</div>
+			</div>
+		</form>
+	</div>
+</div>
+
+<ConfirmDialog
+	bind:show={showConfirm}
+	on:confirm={() => {
+		submitHandler();
+	}}
+>
+	<div class="text-sm text-gray-500">
+		<div class=" bg-yellow-500/20 text-yellow-700 dark:text-yellow-200 rounded-lg px-4 py-3">
+			<div>{$i18n.t('Please carefully review the following warnings:')}</div>
+
+			<ul class=" mt-1 list-disc pl-4 text-xs">
+				<li>{$i18n.t('Functions allow arbitrary code execution.')}</li>
+				<li>{$i18n.t('Do not install functions from sources you do not fully trust.')}</li>
+			</ul>
+		</div>
+
+		<div class="my-3">
+			{$i18n.t(
+				'I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.'
+			)}
+		</div>
+	</div>
+</ConfirmDialog>
diff --git a/src/lib/components/workspace/Functions/FunctionMenu.svelte b/src/lib/components/workspace/Functions/FunctionMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..1a67a1fe9adc42558a98da03f98c8a4950d5e388
--- /dev/null
+++ b/src/lib/components/workspace/Functions/FunctionMenu.svelte
@@ -0,0 +1,138 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext } from 'svelte';
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Share from '$lib/components/icons/Share.svelte';
+	import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
+	import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let func;
+
+	export let editHandler: Function;
+	export let shareHandler: Function;
+	export let cloneHandler: Function;
+	export let exportHandler: Function;
+	export let deleteHandler: Function;
+	export let toggleGlobalHandler: Function;
+
+	export let onClose: Function;
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+>
+	<Tooltip content={$i18n.t('More')}>
+		<slot />
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[180px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			sideOffset={-2}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			{#if ['filter', 'action'].includes(func.type)}
+				<div
+					class="flex gap-2 justify-between items-center px-3 py-2 text-sm font-medium cursor-pointerrounded-md"
+				>
+					<div class="flex gap-2 items-center">
+						<GlobeAlt />
+
+						<div class="flex items-center">{$i18n.t('Global')}</div>
+					</div>
+
+					<div>
+						<Switch on:change={toggleGlobalHandler} bind:state={func.is_global} />
+					</div>
+				</div>
+
+				<hr class="border-gray-100 dark:border-gray-800 my-1" />
+			{/if}
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800  rounded-md"
+				on:click={() => {
+					editHandler();
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					fill="none"
+					viewBox="0 0 24 24"
+					stroke-width="1.5"
+					stroke="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						stroke-linecap="round"
+						stroke-linejoin="round"
+						d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
+					/>
+				</svg>
+
+				<div class="flex items-center">{$i18n.t('Edit')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800  rounded-md"
+				on:click={() => {
+					shareHandler();
+				}}
+			>
+				<Share />
+				<div class="flex items-center">{$i18n.t('Share')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					cloneHandler();
+				}}
+			>
+				<DocumentDuplicate />
+
+				<div class="flex items-center">{$i18n.t('Clone')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					exportHandler();
+				}}
+			>
+				<ArrowDownTray />
+
+				<div class="flex items-center">{$i18n.t('Export')}</div>
+			</DropdownMenu.Item>
+
+			<hr class="border-gray-100 dark:border-gray-800 my-1" />
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					deleteHandler();
+				}}
+			>
+				<GarbageBin strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Delete')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/workspace/Knowledge.svelte b/src/lib/components/workspace/Knowledge.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..0e3844905f397275d857eaaa0412be4d22cce3d2
--- /dev/null
+++ b/src/lib/components/workspace/Knowledge.svelte
@@ -0,0 +1,189 @@
+<script lang="ts">
+	import Fuse from 'fuse.js';
+
+	import dayjs from 'dayjs';
+	import relativeTime from 'dayjs/plugin/relativeTime';
+	dayjs.extend(relativeTime);
+
+	import { toast } from 'svelte-sonner';
+	import { onMount, getContext } from 'svelte';
+	const i18n = getContext('i18n');
+
+	import { WEBUI_NAME, knowledge } from '$lib/stores';
+
+	import { getKnowledgeItems, deleteKnowledgeById } from '$lib/apis/knowledge';
+
+	import { blobToFile, transformFileName } from '$lib/utils';
+
+	import { goto } from '$app/navigation';
+	import Tooltip from '../common/Tooltip.svelte';
+	import GarbageBin from '../icons/GarbageBin.svelte';
+	import Pencil from '../icons/Pencil.svelte';
+	import DeleteConfirmDialog from '../common/ConfirmDialog.svelte';
+	import ItemMenu from './Knowledge/ItemMenu.svelte';
+	import Badge from '../common/Badge.svelte';
+
+	let query = '';
+	let selectedItem = null;
+	let showDeleteConfirm = false;
+
+	let fuse = null;
+
+	let filteredItems = [];
+	$: if (fuse) {
+		filteredItems = query
+			? fuse.search(query).map((e) => {
+					return e.item;
+				})
+			: $knowledge;
+	}
+
+	const deleteHandler = async (item) => {
+		const res = await deleteKnowledgeById(localStorage.token, item.id).catch((e) => {
+			toast.error(e);
+		});
+
+		if (res) {
+			knowledge.set(await getKnowledgeItems(localStorage.token));
+			toast.success($i18n.t('Knowledge deleted successfully.'));
+		}
+	};
+
+	onMount(async () => {
+		knowledge.set(await getKnowledgeItems(localStorage.token));
+
+		knowledge.subscribe((value) => {
+			fuse = new Fuse(value, {
+				keys: ['name', 'description']
+			});
+		});
+	});
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Knowledge')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<DeleteConfirmDialog
+	bind:show={showDeleteConfirm}
+	on:confirm={() => {
+		deleteHandler(selectedItem);
+	}}
+/>
+
+<div class=" flex w-full space-x-2 mb-2.5">
+	<div class="flex flex-1">
+		<div class=" self-center ml-1 mr-3">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<input
+			class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+			bind:value={query}
+			placeholder={$i18n.t('Search Knowledge')}
+		/>
+	</div>
+
+	<div>
+		<button
+			class=" px-2 py-2 rounded-xl border border-gray-50 dark:border-gray-800 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
+			aria-label={$i18n.t('Create Knowledge')}
+			on:click={() => {
+				goto('/workspace/knowledge/create');
+			}}
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</button>
+	</div>
+</div>
+
+<div class="mb-3.5">
+	<div class="flex justify-between items-center">
+		<div class="flex md:self-center text-base font-medium px-0.5">
+			{$i18n.t('Knowledge')}
+			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
+			<span class="text-base font-medium text-gray-500 dark:text-gray-300"
+				>{filteredItems.length}</span
+			>
+		</div>
+	</div>
+</div>
+
+<div class="my-3 mb-5 grid md:grid-cols-2 lg:grid-cols-3 gap-2">
+	{#each filteredItems as item}
+		<button
+			class=" flex space-x-4 cursor-pointer text-left w-full px-4 py-3 border border-gray-50 dark:border-gray-850 dark:hover:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-xl"
+			on:click={() => {
+				if (item?.meta?.document) {
+					toast.error(
+						$i18n.t(
+							'Only collections can be edited, create a new knowledge base to edit/add documents.'
+						)
+					);
+				} else {
+					goto(`/workspace/knowledge/${item.id}`);
+				}
+			}}
+		>
+			<div class=" w-full">
+				<div class="flex items-center justify-between -mt-1">
+					<div class=" font-semibold line-clamp-1 h-fit">{item.name}</div>
+
+					<div class=" flex self-center">
+						<ItemMenu
+							on:delete={() => {
+								selectedItem = item;
+								showDeleteConfirm = true;
+							}}
+						/>
+					</div>
+				</div>
+
+				<div class=" self-center flex-1">
+					<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
+						{item.description}
+					</div>
+
+					<div class="mt-5 flex justify-between">
+						<div>
+							{#if item?.meta?.document}
+								<Badge type="muted" content={$i18n.t('Document')} />
+							{:else}
+								<Badge type="success" content={$i18n.t('Collection')} />
+							{/if}
+						</div>
+						<div class=" text-xs text-gray-500 line-clamp-1">
+							{$i18n.t('Updated')}
+							{dayjs(item.updated_at * 1000).fromNow()}
+						</div>
+					</div>
+				</div>
+			</div>
+		</button>
+	{/each}
+</div>
+
+<div class=" text-gray-500 text-xs mt-1 mb-2">
+	ⓘ {$i18n.t("Use '#' in the prompt input to load and include your knowledge.")}
+</div>
diff --git a/src/lib/components/workspace/Knowledge/Collection.svelte b/src/lib/components/workspace/Knowledge/Collection.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..641e5f64379bb2f4002aaaa3034b250993d8c4d9
--- /dev/null
+++ b/src/lib/components/workspace/Knowledge/Collection.svelte
@@ -0,0 +1,851 @@
+<script lang="ts">
+	import Fuse from 'fuse.js';
+	import { toast } from 'svelte-sonner';
+	import { v4 as uuidv4 } from 'uuid';
+	import { PaneGroup, Pane, PaneResizer } from 'paneforge';
+
+	import { onMount, getContext, onDestroy, tick } from 'svelte';
+	const i18n = getContext('i18n');
+
+	import { goto } from '$app/navigation';
+	import { page } from '$app/stores';
+	import { mobile, showSidebar, knowledge as _knowledge } from '$lib/stores';
+
+	import { updateFileDataContentById, uploadFile } from '$lib/apis/files';
+	import {
+		addFileToKnowledgeById,
+		getKnowledgeById,
+		getKnowledgeItems,
+		removeFileFromKnowledgeById,
+		resetKnowledgeById,
+		updateFileFromKnowledgeById,
+		updateKnowledgeById
+	} from '$lib/apis/knowledge';
+
+	import { transcribeAudio } from '$lib/apis/audio';
+	import { blobToFile } from '$lib/utils';
+	import { processFile } from '$lib/apis/retrieval';
+
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import Files from './Collection/Files.svelte';
+	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
+
+	import AddContentMenu from './Collection/AddContentMenu.svelte';
+	import AddTextContentModal from './Collection/AddTextContentModal.svelte';
+
+	import SyncConfirmDialog from '../../common/ConfirmDialog.svelte';
+	import RichTextInput from '$lib/components/common/RichTextInput.svelte';
+	import EllipsisVertical from '$lib/components/icons/EllipsisVertical.svelte';
+	import Drawer from '$lib/components/common/Drawer.svelte';
+	import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
+	import MenuLines from '$lib/components/icons/MenuLines.svelte';
+
+	let largeScreen = true;
+
+	let pane;
+	let showSidepanel = true;
+	let minSize = 0;
+
+	type Knowledge = {
+		id: string;
+		name: string;
+		description: string;
+		data: {
+			file_ids: string[];
+		};
+		files: any[];
+	};
+
+	let id = null;
+	let knowledge: Knowledge | null = null;
+	let query = '';
+
+	let showAddTextContentModal = false;
+	let showSyncConfirmModal = false;
+
+	let inputFiles = null;
+
+	let filteredItems = [];
+	$: if (knowledge) {
+		fuse = new Fuse(knowledge.files, {
+			keys: ['meta.name', 'meta.description']
+		});
+	}
+
+	$: if (fuse) {
+		filteredItems = query
+			? fuse.search(query).map((e) => {
+					return e.item;
+				})
+			: (knowledge?.files ?? []);
+	}
+
+	let selectedFile = null;
+	let selectedFileId = null;
+
+	$: if (selectedFileId) {
+		const file = (knowledge?.files ?? []).find((file) => file.id === selectedFileId);
+		if (file) {
+			file.data = file.data ?? { content: '' };
+			selectedFile = file;
+		} else {
+			selectedFile = null;
+		}
+	} else {
+		selectedFile = null;
+	}
+
+	let fuse = null;
+	let debounceTimeout = null;
+	let mediaQuery;
+	let dragged = false;
+
+	const createFileFromText = (name, content) => {
+		const blob = new Blob([content], { type: 'text/plain' });
+		const file = blobToFile(blob, `${name}.txt`);
+
+		console.log(file);
+		return file;
+	};
+
+	const uploadFileHandler = async (file) => {
+		console.log(file);
+
+		const tempItemId = uuidv4();
+		const fileItem = {
+			type: 'file',
+			file: '',
+			id: null,
+			url: '',
+			name: file.name,
+			size: file.size,
+			status: 'uploading',
+			error: '',
+			itemId: tempItemId
+		};
+
+		if (fileItem.size == 0) {
+			toast.error($i18n.t('You cannot upload an empty file.'));
+			return null;
+		}
+
+		knowledge.files = [...(knowledge.files ?? []), fileItem];
+
+		// Check if the file is an audio file and transcribe/convert it to text file
+		if (['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/x-m4a'].includes(file['type'])) {
+			const res = await transcribeAudio(localStorage.token, file).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+
+			if (res) {
+				console.log(res);
+				const blob = new Blob([res.text], { type: 'text/plain' });
+				file = blobToFile(blob, `${file.name}.txt`);
+			}
+		}
+
+		try {
+			const uploadedFile = await uploadFile(localStorage.token, file).catch((e) => {
+				toast.error(e);
+				return null;
+			});
+
+			if (uploadedFile) {
+				console.log(uploadedFile);
+				knowledge.files = knowledge.files.map((item) => {
+					if (item.itemId === tempItemId) {
+						item.id = uploadedFile.id;
+					}
+
+					// Remove temporary item id
+					delete item.itemId;
+					return item;
+				});
+				await addFileHandler(uploadedFile.id);
+			} else {
+				toast.error($i18n.t('Failed to upload file.'));
+			}
+		} catch (e) {
+			toast.error(e);
+		}
+	};
+
+	const uploadDirectoryHandler = async () => {
+		// Check if File System Access API is supported
+		const isFileSystemAccessSupported = 'showDirectoryPicker' in window;
+
+		try {
+			if (isFileSystemAccessSupported) {
+				// Modern browsers (Chrome, Edge) implementation
+				await handleModernBrowserUpload();
+			} else {
+				// Firefox fallback
+				await handleFirefoxUpload();
+			}
+		} catch (error) {
+			handleUploadError(error);
+		}
+	};
+
+	// Helper function to check if a path contains hidden folders
+	const hasHiddenFolder = (path) => {
+		return path.split('/').some((part) => part.startsWith('.'));
+	};
+
+	// Modern browsers implementation using File System Access API
+	const handleModernBrowserUpload = async () => {
+		const dirHandle = await window.showDirectoryPicker();
+		let totalFiles = 0;
+		let uploadedFiles = 0;
+
+		// Function to update the UI with the progress
+		const updateProgress = () => {
+			const percentage = (uploadedFiles / totalFiles) * 100;
+			toast.info(`Upload Progress: ${uploadedFiles}/${totalFiles} (${percentage.toFixed(2)}%)`);
+		};
+
+		// Recursive function to count all files excluding hidden ones
+		async function countFiles(dirHandle) {
+			for await (const entry of dirHandle.values()) {
+				// Skip hidden files and directories
+				if (entry.name.startsWith('.')) continue;
+
+				if (entry.kind === 'file') {
+					totalFiles++;
+				} else if (entry.kind === 'directory') {
+					// Only process non-hidden directories
+					if (!entry.name.startsWith('.')) {
+						await countFiles(entry);
+					}
+				}
+			}
+		}
+
+		// Recursive function to process directories excluding hidden files and folders
+		async function processDirectory(dirHandle, path = '') {
+			for await (const entry of dirHandle.values()) {
+				// Skip hidden files and directories
+				if (entry.name.startsWith('.')) continue;
+
+				const entryPath = path ? `${path}/${entry.name}` : entry.name;
+
+				// Skip if the path contains any hidden folders
+				if (hasHiddenFolder(entryPath)) continue;
+
+				if (entry.kind === 'file') {
+					const file = await entry.getFile();
+					const fileWithPath = new File([file], entryPath, { type: file.type });
+
+					await uploadFileHandler(fileWithPath);
+					uploadedFiles++;
+					updateProgress();
+				} else if (entry.kind === 'directory') {
+					// Only process non-hidden directories
+					if (!entry.name.startsWith('.')) {
+						await processDirectory(entry, entryPath);
+					}
+				}
+			}
+		}
+
+		await countFiles(dirHandle);
+		updateProgress();
+
+		if (totalFiles > 0) {
+			await processDirectory(dirHandle);
+		} else {
+			console.log('No files to upload.');
+		}
+	};
+
+	// Firefox fallback implementation using traditional file input
+	const handleFirefoxUpload = async () => {
+		return new Promise((resolve, reject) => {
+			// Create hidden file input
+			const input = document.createElement('input');
+			input.type = 'file';
+			input.webkitdirectory = true;
+			input.directory = true;
+			input.multiple = true;
+			input.style.display = 'none';
+
+			// Add input to DOM temporarily
+			document.body.appendChild(input);
+
+			input.onchange = async () => {
+				try {
+					const files = Array.from(input.files)
+						// Filter out files from hidden folders
+						.filter((file) => !hasHiddenFolder(file.webkitRelativePath));
+
+					let totalFiles = files.length;
+					let uploadedFiles = 0;
+
+					// Function to update the UI with the progress
+					const updateProgress = () => {
+						const percentage = (uploadedFiles / totalFiles) * 100;
+						toast.info(
+							`Upload Progress: ${uploadedFiles}/${totalFiles} (${percentage.toFixed(2)}%)`
+						);
+					};
+
+					updateProgress();
+
+					// Process all files
+					for (const file of files) {
+						// Skip hidden files (additional check)
+						if (!file.name.startsWith('.')) {
+							const relativePath = file.webkitRelativePath || file.name;
+							const fileWithPath = new File([file], relativePath, { type: file.type });
+
+							await uploadFileHandler(fileWithPath);
+							uploadedFiles++;
+							updateProgress();
+						}
+					}
+
+					// Clean up
+					document.body.removeChild(input);
+					resolve();
+				} catch (error) {
+					reject(error);
+				}
+			};
+
+			input.onerror = (error) => {
+				document.body.removeChild(input);
+				reject(error);
+			};
+
+			// Trigger file picker
+			input.click();
+		});
+	};
+
+	// Error handler
+	const handleUploadError = (error) => {
+		if (error.name === 'AbortError') {
+			toast.info('Directory selection was cancelled');
+		} else {
+			toast.error('Error accessing directory');
+			console.error('Directory access error:', error);
+		}
+	};
+
+	// Helper function to maintain file paths within zip
+	const syncDirectoryHandler = async () => {
+		if ((knowledge?.files ?? []).length > 0) {
+			const res = await resetKnowledgeById(localStorage.token, id).catch((e) => {
+				toast.error(e);
+			});
+
+			if (res) {
+				knowledge = res;
+				toast.success($i18n.t('Knowledge reset successfully.'));
+
+				// Upload directory
+				uploadDirectoryHandler();
+			}
+		} else {
+			uploadDirectoryHandler();
+		}
+	};
+
+	const addFileHandler = async (fileId) => {
+		const updatedKnowledge = await addFileToKnowledgeById(localStorage.token, id, fileId).catch(
+			(e) => {
+				toast.error(e);
+				return null;
+			}
+		);
+
+		if (updatedKnowledge) {
+			knowledge = updatedKnowledge;
+			toast.success($i18n.t('File added successfully.'));
+		} else {
+			toast.error($i18n.t('Failed to add file.'));
+			knowledge.files = knowledge.files.filter((file) => file.id !== fileId);
+		}
+	};
+
+	const deleteFileHandler = async (fileId) => {
+		const updatedKnowledge = await removeFileFromKnowledgeById(
+			localStorage.token,
+			id,
+			fileId
+		).catch((e) => {
+			toast.error(e);
+		});
+
+		if (updatedKnowledge) {
+			knowledge = updatedKnowledge;
+			toast.success($i18n.t('File removed successfully.'));
+		}
+	};
+
+	const updateFileContentHandler = async () => {
+		const fileId = selectedFile.id;
+		const content = selectedFile.data.content;
+
+		const res = updateFileDataContentById(localStorage.token, fileId, content).catch((e) => {
+			toast.error(e);
+		});
+
+		const updatedKnowledge = await updateFileFromKnowledgeById(
+			localStorage.token,
+			id,
+			fileId
+		).catch((e) => {
+			toast.error(e);
+		});
+
+		if (res && updatedKnowledge) {
+			knowledge = updatedKnowledge;
+			toast.success($i18n.t('File content updated successfully.'));
+		}
+	};
+
+	const changeDebounceHandler = () => {
+		console.log('debounce');
+		if (debounceTimeout) {
+			clearTimeout(debounceTimeout);
+		}
+
+		debounceTimeout = setTimeout(async () => {
+			if (knowledge.name.trim() === '' || knowledge.description.trim() === '') {
+				toast.error($i18n.t('Please fill in all fields.'));
+				return;
+			}
+
+			const res = await updateKnowledgeById(localStorage.token, id, {
+				name: knowledge.name,
+				description: knowledge.description
+			}).catch((e) => {
+				toast.error(e);
+			});
+
+			if (res) {
+				toast.success($i18n.t('Knowledge updated successfully'));
+				_knowledge.set(await getKnowledgeItems(localStorage.token));
+			}
+		}, 1000);
+	};
+
+	const handleMediaQuery = async (e) => {
+		if (e.matches) {
+			largeScreen = true;
+		} else {
+			largeScreen = false;
+		}
+	};
+
+	const onDragOver = (e) => {
+		e.preventDefault();
+		dragged = true;
+	};
+
+	const onDragLeave = () => {
+		dragged = false;
+	};
+
+	const onDrop = async (e) => {
+		e.preventDefault();
+		dragged = false;
+
+		if (e.dataTransfer?.files) {
+			const inputFiles = e.dataTransfer?.files;
+
+			if (inputFiles && inputFiles.length > 0) {
+				for (const file of inputFiles) {
+					await uploadFileHandler(file);
+				}
+			} else {
+				toast.error($i18n.t(`File not found.`));
+			}
+		}
+	};
+
+	onMount(async () => {
+		// listen to resize 1024px
+		mediaQuery = window.matchMedia('(min-width: 1024px)');
+
+		mediaQuery.addEventListener('change', handleMediaQuery);
+		handleMediaQuery(mediaQuery);
+
+		// Select the container element you want to observe
+		const container = document.getElementById('collection-container');
+
+		// initialize the minSize based on the container width
+		minSize = !largeScreen ? 100 : Math.floor((300 / container.clientWidth) * 100);
+
+		// Create a new ResizeObserver instance
+		const resizeObserver = new ResizeObserver((entries) => {
+			for (let entry of entries) {
+				const width = entry.contentRect.width;
+				// calculate the percentage of 300
+				const percentage = (300 / width) * 100;
+				// set the minSize to the percentage, must be an integer
+				minSize = !largeScreen ? 100 : Math.floor(percentage);
+
+				if (showSidepanel) {
+					if (pane && pane.isExpanded() && pane.getSize() < minSize) {
+						pane.resize(minSize);
+					}
+				}
+			}
+		});
+
+		// Start observing the container's size changes
+		resizeObserver.observe(container);
+
+		if (pane) {
+			pane.expand();
+		}
+
+		id = $page.params.id;
+
+		const res = await getKnowledgeById(localStorage.token, id).catch((e) => {
+			toast.error(e);
+			return null;
+		});
+
+		if (res) {
+			knowledge = res;
+		} else {
+			goto('/workspace/knowledge');
+		}
+
+		const dropZone = document.querySelector('body');
+		dropZone?.addEventListener('dragover', onDragOver);
+		dropZone?.addEventListener('drop', onDrop);
+		dropZone?.addEventListener('dragleave', onDragLeave);
+	});
+
+	onDestroy(() => {
+		mediaQuery?.removeEventListener('change', handleMediaQuery);
+		const dropZone = document.querySelector('body');
+		dropZone?.removeEventListener('dragover', onDragOver);
+		dropZone?.removeEventListener('drop', onDrop);
+		dropZone?.removeEventListener('dragleave', onDragLeave);
+	});
+</script>
+
+{#if dragged}
+	<div
+		class="fixed {$showSidebar
+			? 'left-0 md:left-[260px] md:w-[calc(100%-260px)]'
+			: 'left-0'}  w-full h-full flex z-50 touch-none pointer-events-none"
+		id="dropzone"
+		role="region"
+		aria-label="Drag and Drop Container"
+	>
+		<div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
+			<div class="m-auto pt-64 flex flex-col justify-center">
+				<div class="max-w-md">
+					<AddFilesPlaceholder>
+						<div class=" mt-2 text-center text-sm dark:text-gray-200 w-full">
+							Drop any files here to add to my documents
+						</div>
+					</AddFilesPlaceholder>
+				</div>
+			</div>
+		</div>
+	</div>
+{/if}
+
+<SyncConfirmDialog
+	bind:show={showSyncConfirmModal}
+	message={$i18n.t(
+		'This will reset the knowledge base and sync all files. Do you wish to continue?'
+	)}
+	on:confirm={() => {
+		syncDirectoryHandler();
+	}}
+/>
+
+<AddTextContentModal
+	bind:show={showAddTextContentModal}
+	on:submit={(e) => {
+		const file = createFileFromText(e.detail.name, e.detail.content);
+		uploadFileHandler(file);
+	}}
+/>
+
+<input
+	id="files-input"
+	bind:files={inputFiles}
+	type="file"
+	multiple
+	hidden
+	on:change={async () => {
+		if (inputFiles && inputFiles.length > 0) {
+			for (const file of inputFiles) {
+				await uploadFileHandler(file);
+			}
+
+			inputFiles = null;
+			const fileInputElement = document.getElementById('files-input');
+
+			if (fileInputElement) {
+				fileInputElement.value = '';
+			}
+		} else {
+			toast.error($i18n.t(`File not found.`));
+		}
+	}}
+/>
+
+<div class="flex flex-col w-full h-full max-h-[100dvh]" id="collection-container">
+	{#if id && knowledge}
+		<div class="flex flex-row flex-1 h-full max-h-full pb-2.5">
+			<PaneGroup direction="horizontal">
+				<Pane
+					bind:pane
+					defaultSize={minSize}
+					collapsible={true}
+					maxSize={50}
+					{minSize}
+					class="h-full"
+					onExpand={() => {
+						showSidepanel = true;
+					}}
+					onCollapse={() => {
+						showSidepanel = false;
+					}}
+				>
+					<div
+						class="{largeScreen ? 'flex-shrink-0' : 'flex-1'}
+						flex
+						py-2
+						rounded-2xl
+						border
+						border-gray-50
+						h-full
+						dark:border-gray-850"
+					>
+						<div class=" flex flex-col w-full space-x-2 rounded-lg h-full">
+							<div class="w-full h-full flex flex-col">
+								<div class=" px-3">
+									<div class="flex py-1">
+										<div class=" self-center ml-1 mr-3">
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												viewBox="0 0 20 20"
+												fill="currentColor"
+												class="w-4 h-4"
+											>
+												<path
+													fill-rule="evenodd"
+													d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+													clip-rule="evenodd"
+												/>
+											</svg>
+										</div>
+										<input
+											class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+											bind:value={query}
+											placeholder={$i18n.t('Search Collection')}
+											on:focus={() => {
+												selectedFileId = null;
+											}}
+										/>
+
+										<div>
+											<AddContentMenu
+												on:upload={(e) => {
+													if (e.detail.type === 'directory') {
+														uploadDirectoryHandler();
+													} else if (e.detail.type === 'text') {
+														showAddTextContentModal = true;
+													} else {
+														document.getElementById('files-input').click();
+													}
+												}}
+												on:sync={(e) => {
+													showSyncConfirmModal = true;
+												}}
+											/>
+										</div>
+									</div>
+								</div>
+
+								{#if filteredItems.length > 0}
+									<div class=" flex overflow-y-auto h-full w-full scrollbar-hidden text-xs">
+										<Files
+											files={filteredItems}
+											{selectedFileId}
+											on:click={(e) => {
+												selectedFileId = selectedFileId === e.detail ? null : e.detail;
+											}}
+											on:delete={(e) => {
+												console.log(e.detail);
+
+												selectedFileId = null;
+												deleteFileHandler(e.detail);
+											}}
+										/>
+									</div>
+								{:else}
+									<div class="m-auto text-gray-500 text-xs">{$i18n.t('No content found')}</div>
+								{/if}
+							</div>
+						</div>
+					</div>
+				</Pane>
+
+				{#if largeScreen}
+					<PaneResizer class="relative flex w-2 items-center justify-center bg-background group">
+						<div class="z-10 flex h-7 w-5 items-center justify-center rounded-sm">
+							<EllipsisVertical className="size-4 invisible group-hover:visible" />
+						</div>
+					</PaneResizer>
+					<Pane>
+						<div class="flex-1 flex justify-start h-full max-h-full">
+							{#if selectedFile}
+								<div class=" flex flex-col w-full h-full max-h-full ml-2.5">
+									<div class="flex-shrink-0 mb-2 flex items-center">
+										{#if !showSidepanel}
+											<div class="-translate-x-2">
+												<button
+													class="w-full text-left text-sm p-1.5 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
+													on:click={() => {
+														pane.expand();
+													}}
+												>
+													<ChevronLeft strokeWidth="2.5" />
+												</button>
+											</div>
+										{/if}
+
+										<div class=" flex-1 text-2xl font-medium">
+											<a
+												class="hover:text-gray-500 hover:dark:text-gray-100 hover:underline flex-grow line-clamp-1"
+												href={selectedFile.id ? `/api/v1/files/${selectedFile.id}/content` : '#'}
+												target="_blank"
+											>
+												{selectedFile?.meta?.name}
+											</a>
+										</div>
+
+										<div>
+											<button
+												class="self-center w-fit text-sm py-1 px-2.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-lg"
+												on:click={() => {
+													updateFileContentHandler();
+												}}
+											>
+												{$i18n.t('Save')}
+											</button>
+										</div>
+									</div>
+
+									<div
+										class=" flex-1 w-full h-full max-h-full text-sm bg-transparent outline-none overflow-y-auto scrollbar-hidden"
+									>
+										{#key selectedFile.id}
+											<RichTextInput
+												className="input-prose-sm"
+												bind:value={selectedFile.data.content}
+												placeholder={$i18n.t('Add content here')}
+											/>
+										{/key}
+									</div>
+								</div>
+							{:else}
+								<div class="m-auto pb-32">
+									<div>
+										<div class=" flex w-full mt-1 mb-3.5">
+											<div class="flex-1">
+												<div class="flex items-center justify-between w-full px-0.5 mb-1">
+													<div class="w-full">
+														<input
+															type="text"
+															class="text-center w-full font-medium text-3xl font-primary bg-transparent outline-none"
+															bind:value={knowledge.name}
+															on:input={() => {
+																changeDebounceHandler();
+															}}
+														/>
+													</div>
+												</div>
+
+												<div class="flex w-full px-1">
+													<input
+														type="text"
+														class="text-center w-full text-gray-500 bg-transparent outline-none"
+														bind:value={knowledge.description}
+														on:input={() => {
+															changeDebounceHandler();
+														}}
+													/>
+												</div>
+											</div>
+										</div>
+									</div>
+
+									<div class=" mt-2 text-center text-sm text-gray-200 dark:text-gray-700 w-full">
+										{$i18n.t('Select a file to view or drag and drop a file to upload')}
+									</div>
+								</div>
+							{/if}
+						</div>
+					</Pane>
+				{:else if !largeScreen && selectedFileId !== null}
+					<Drawer
+						className="h-full"
+						show={selectedFileId !== null}
+						on:close={() => {
+							selectedFileId = null;
+						}}
+					>
+						<div class="flex flex-col justify-start h-full max-h-full p-2">
+							<div class=" flex flex-col w-full h-full max-h-full">
+								<div class="flex-shrink-0 mt-1 mb-2 flex items-center">
+									<div class="mr-2">
+										<button
+											class="w-full text-left text-sm p-1.5 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
+											on:click={() => {
+												selectedFileId = null;
+											}}
+										>
+											<ChevronLeft strokeWidth="2.5" />
+										</button>
+									</div>
+									<div class=" flex-1 text-xl line-clamp-1">
+										{selectedFile?.meta?.name}
+									</div>
+
+									<div>
+										<button
+											class="self-center w-fit text-sm py-1 px-2.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-lg"
+											on:click={() => {
+												updateFileContentHandler();
+											}}
+										>
+											{$i18n.t('Save')}
+										</button>
+									</div>
+								</div>
+
+								<div
+									class=" flex-1 w-full h-full max-h-full py-2.5 px-3.5 rounded-lg text-sm bg-transparent overflow-y-auto scrollbar-hidden"
+								>
+									{#key selectedFile.id}
+										<RichTextInput
+											className="input-prose-sm"
+											bind:value={selectedFile.data.content}
+											placeholder={$i18n.t('Add content here')}
+										/>
+									{/key}
+								</div>
+							</div>
+						</div>
+					</Drawer>
+				{/if}
+			</PaneGroup>
+		</div>
+	{:else}
+		<Spinner />
+	{/if}
+</div>
diff --git a/src/lib/components/workspace/Knowledge/Collection/AddContentMenu.svelte b/src/lib/components/workspace/Knowledge/Collection/AddContentMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..88326ed89886086efde148b045c329ab7a60706c
--- /dev/null
+++ b/src/lib/components/workspace/Knowledge/Collection/AddContentMenu.svelte
@@ -0,0 +1,107 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext, createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import ArrowUpCircle from '$lib/components/icons/ArrowUpCircle.svelte';
+	import BarsArrowUp from '$lib/components/icons/BarsArrowUp.svelte';
+	import FolderOpen from '$lib/components/icons/FolderOpen.svelte';
+	import ArrowPath from '$lib/components/icons/ArrowPath.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let onClose: Function = () => {};
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+	align="end"
+>
+	<Tooltip content={$i18n.t('Add Content')}>
+		<button
+			class=" p-1.5 rounded-xl hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition font-medium text-sm flex items-center space-x-1"
+			on:click={(e) => {
+				e.stopPropagation();
+				show = true;
+			}}
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</button>
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-44 rounded-xl p-1 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			sideOffset={4}
+			side="bottom"
+			align="end"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					dispatch('upload', { type: 'files' });
+				}}
+			>
+				<ArrowUpCircle strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Upload files')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					dispatch('upload', { type: 'directory' });
+				}}
+			>
+				<FolderOpen strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Upload directory')}</div>
+			</DropdownMenu.Item>
+
+			<Tooltip
+				content={$i18n.t(
+					'This option will delete all existing files in the collection and replace them with newly uploaded files.'
+				)}
+				className="w-full"
+			>
+				<DropdownMenu.Item
+					class="flex  gap-2  items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					on:click={() => {
+						dispatch('sync', { type: 'directory' });
+					}}
+				>
+					<ArrowPath strokeWidth="2" />
+					<div class="flex items-center">{$i18n.t('Sync directory')}</div>
+				</DropdownMenu.Item>
+			</Tooltip>
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					dispatch('upload', { type: 'text' });
+				}}
+			>
+				<BarsArrowUp strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Add text content')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/workspace/Knowledge/Collection/AddTextContentModal.svelte b/src/lib/components/workspace/Knowledge/Collection/AddTextContentModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..40553b4c963d13fad4f512352738d1c96ac500a1
--- /dev/null
+++ b/src/lib/components/workspace/Knowledge/Collection/AddTextContentModal.svelte
@@ -0,0 +1,165 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import dayjs from 'dayjs';
+
+	import { onMount, getContext, createEventDispatcher } from 'svelte';
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import Modal from '$lib/components/common/Modal.svelte';
+	import RichTextInput from '$lib/components/common/RichTextInput.svelte';
+	import XMark from '$lib/components/icons/XMark.svelte';
+	import Mic from '$lib/components/icons/Mic.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import VoiceRecording from '$lib/components/chat/MessageInput/VoiceRecording.svelte';
+	export let show = false;
+
+	let name = 'Untitled';
+	let content = '';
+
+	let voiceInput = false;
+</script>
+
+<Modal size="full" className="h-full bg-white dark:bg-gray-900" bind:show>
+	<div class="absolute top-0 right-0 p-5">
+		<button
+			class="self-center dark:text-white"
+			type="button"
+			on:click={() => {
+				show = false;
+			}}
+		>
+			<XMark className="size-3.5" />
+		</button>
+	</div>
+	<div class="flex flex-col md:flex-row w-full h-full md:space-x-4 dark:text-gray-200">
+		<form
+			class="flex flex-col w-full h-full"
+			on:submit|preventDefault={() => {
+				if (name.trim() === '' || content.trim() === '') {
+					toast.error($i18n.t('Please fill in all fields.'));
+					name = name.trim();
+					content = content.trim();
+					return;
+				}
+
+				dispatch('submit', {
+					name,
+					content
+				});
+				show = false;
+				name = '';
+				content = '';
+			}}
+		>
+			<div class=" flex-1 w-full h-full flex justify-center overflow-auto px-5 py-4">
+				<div class=" max-w-3xl py-2 md:py-10 w-full flex flex-col gap-2">
+					<div class="flex-shrink-0 w-full flex justify-between items-center">
+						<div class="w-full">
+							<input
+								class="w-full text-3xl font-semibold bg-transparent outline-none"
+								type="text"
+								bind:value={name}
+								placeholder={$i18n.t('Title')}
+								required
+							/>
+						</div>
+					</div>
+
+					<div class=" flex-1 w-full h-full">
+						<RichTextInput bind:value={content} placeholder={$i18n.t('Write something...')} />
+					</div>
+				</div>
+			</div>
+
+			<div
+				class="flex flex-row items-center justify-end text-sm font-medium flex-shrink-0 mt-1 p-4 gap-1.5"
+			>
+				<div class="">
+					{#if voiceInput}
+						<div class=" max-w-full w-64">
+							<VoiceRecording
+								bind:recording={voiceInput}
+								className="p-1"
+								on:cancel={() => {
+									voiceInput = false;
+								}}
+								on:confirm={(e) => {
+									const { text, filename } = e.detail;
+									content = `${content}${text} `;
+
+									voiceInput = false;
+								}}
+							/>
+						</div>
+					{:else}
+						<Tooltip content={$i18n.t('Voice Input')}>
+							<button
+								class=" p-2 bg-gray-50 text-gray-700 dark:bg-gray-700 dark:text-white transition rounded-full"
+								type="button"
+								on:click={async () => {
+									try {
+										let stream = await navigator.mediaDevices
+											.getUserMedia({ audio: true })
+											.catch(function (err) {
+												toast.error(
+													$i18n.t(`Permission denied when accessing microphone: {{error}}`, {
+														error: err
+													})
+												);
+												return null;
+											});
+
+										if (stream) {
+											voiceInput = true;
+											const tracks = stream.getTracks();
+											tracks.forEach((track) => track.stop());
+										}
+										stream = null;
+									} catch {
+										toast.error($i18n.t('Permission denied when accessing microphone'));
+									}
+								}}
+							>
+								<Mic className="size-5" />
+							</button>
+						</Tooltip>
+					{/if}
+				</div>
+
+				<div class=" flex-shrink-0">
+					<Tooltip content={$i18n.t('Save')}>
+						<button
+							class=" px-3.5 py-2 bg-black text-white dark:bg-white dark:text-black transition rounded-full"
+							type="submit"
+						>
+							{$i18n.t('Save')}
+						</button>
+					</Tooltip>
+				</div>
+			</div>
+		</form>
+	</div>
+</Modal>
+
+<style>
+	input::-webkit-outer-spin-button,
+	input::-webkit-inner-spin-button {
+		/* display: none; <- Crashes Chrome on hover */
+		-webkit-appearance: none;
+		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
+	}
+
+	.tabs::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.tabs {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+
+	input[type='number'] {
+		-moz-appearance: textfield; /* Firefox */
+	}
+</style>
diff --git a/src/lib/components/workspace/Knowledge/Collection/Files.svelte b/src/lib/components/workspace/Knowledge/Collection/Files.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..288d398d19219fb501e9f98112169205e5e41a3c
--- /dev/null
+++ b/src/lib/components/workspace/Knowledge/Collection/Files.svelte
@@ -0,0 +1,42 @@
+<script lang="ts">
+	import { createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	import FileItem from '$lib/components/common/FileItem.svelte';
+
+	export let selectedFileId = null;
+	export let files = [];
+</script>
+
+<div class=" max-h-full flex flex-col w-full">
+	{#each files as file}
+		<div class="mt-1 px-2">
+			<FileItem
+				className="w-full"
+				colorClassName="{selectedFileId === file.id
+					? ' bg-gray-50 dark:bg-gray-850'
+					: 'bg-transparent'} hover:bg-gray-50 dark:hover:bg-gray-850 transition"
+				{file}
+				name={file?.name ?? file?.meta?.name}
+				type="file"
+				size={file?.size ?? file?.meta?.size ?? ''}
+				loading={file.status === 'uploading'}
+				dismissible
+				on:click={() => {
+					if (file.status === 'uploading') {
+						return;
+					}
+
+					dispatch('click', file.id);
+				}}
+				on:dismiss={() => {
+					if (file.status === 'uploading') {
+						return;
+					}
+
+					dispatch('delete', file.id);
+				}}
+			/>
+		</div>
+	{/each}
+</div>
diff --git a/src/lib/components/workspace/Knowledge/CreateCollection.svelte b/src/lib/components/workspace/Knowledge/CreateCollection.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e078f4be39b4b4973618ff8f90e05a5ad297cd0d
--- /dev/null
+++ b/src/lib/components/workspace/Knowledge/CreateCollection.svelte
@@ -0,0 +1,146 @@
+<script>
+	import { goto } from '$app/navigation';
+	import { getContext } from 'svelte';
+	const i18n = getContext('i18n');
+
+	import { createNewKnowledge, getKnowledgeItems } from '$lib/apis/knowledge';
+	import { toast } from 'svelte-sonner';
+	import { knowledge } from '$lib/stores';
+
+	let loading = false;
+
+	let name = '';
+	let description = '';
+
+	const submitHandler = async () => {
+		loading = true;
+
+		if (name.trim() === '' || description.trim() === '') {
+			toast.error($i18n.t('Please fill in all fields.'));
+			name = '';
+			description = '';
+			loading = false;
+			return;
+		}
+
+		const res = await createNewKnowledge(localStorage.token, name, description).catch((e) => {
+			toast.error(e);
+		});
+
+		if (res) {
+			toast.success($i18n.t('Knowledge created successfully.'));
+			knowledge.set(await getKnowledgeItems(localStorage.token));
+			goto(`/workspace/knowledge/${res.id}`);
+		}
+
+		loading = false;
+	};
+</script>
+
+<div class="w-full max-h-full">
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			goto('/workspace/knowledge');
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+
+	<form
+		class="flex flex-col max-w-lg mx-auto mt-10 mb-10"
+		on:submit|preventDefault={() => {
+			submitHandler();
+		}}
+	>
+		<div class=" w-full flex flex-col justify-center">
+			<div class=" text-2xl font-medium font-primary mb-2.5">Create a knowledge base</div>
+
+			<div class="w-full flex flex-col gap-2.5">
+				<div class="w-full">
+					<div class=" text-sm mb-2">What are you working on?</div>
+
+					<div class="w-full mt-1">
+						<input
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							type="text"
+							bind:value={name}
+							placeholder={`Name your knowledge base`}
+							required
+						/>
+					</div>
+				</div>
+
+				<div>
+					<div class="text-sm mb-2">What are you trying to achieve?</div>
+
+					<div class=" w-full mt-1">
+						<textarea
+							class="w-full resize-none rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							rows="4"
+							bind:value={description}
+							placeholder={`Describe your knowledge base and objectives`}
+							required
+						/>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<div class="flex justify-end mt-2">
+			<div>
+				<button
+					class=" text-sm px-4 py-2 transition rounded-lg {loading
+						? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+						: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800'} flex"
+					type="submit"
+					disabled={loading}
+				>
+					<div class=" self-center font-medium">{$i18n.t('Create Knowledge')}</div>
+
+					{#if loading}
+						<div class="ml-1.5 self-center">
+							<svg
+								class=" w-4 h-4"
+								viewBox="0 0 24 24"
+								fill="currentColor"
+								xmlns="http://www.w3.org/2000/svg"
+								><style>
+									.spinner_ajPY {
+										transform-origin: center;
+										animation: spinner_AtaB 0.75s infinite linear;
+									}
+									@keyframes spinner_AtaB {
+										100% {
+											transform: rotate(360deg);
+										}
+									}
+								</style><path
+									d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+									opacity=".25"
+								/><path
+									d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+									class="spinner_ajPY"
+								/></svg
+							>
+						</div>
+					{/if}
+				</button>
+			</div>
+		</div>
+	</form>
+</div>
diff --git a/src/lib/components/workspace/Knowledge/ItemMenu.svelte b/src/lib/components/workspace/Knowledge/ItemMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..b4d448b1580b9ace44e22ddb71b6dddcfccf066e
--- /dev/null
+++ b/src/lib/components/workspace/Knowledge/ItemMenu.svelte
@@ -0,0 +1,69 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext, createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
+	import Pencil from '$lib/components/icons/Pencil.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Tags from '$lib/components/chat/Tags.svelte';
+	import Share from '$lib/components/icons/Share.svelte';
+	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
+	import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
+	import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
+	import ArrowUpCircle from '$lib/components/icons/ArrowUpCircle.svelte';
+	import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let onClose: Function = () => {};
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+	align="end"
+>
+	<Tooltip content={$i18n.t('More')}>
+		<slot
+			><button
+				class="self-center w-fit text-sm p-1.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+				type="button"
+				on:click={(e) => {
+					e.stopPropagation();
+					show = true;
+				}}
+			>
+				<EllipsisHorizontal className="size-5" />
+			</button>
+		</slot>
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[160px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			sideOffset={-2}
+			side="bottom"
+			align="end"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					dispatch('delete');
+				}}
+			>
+				<GarbageBin strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Delete')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/workspace/Models.svelte b/src/lib/components/workspace/Models.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..bcb43e16632c40d7bad4af5b1da67a171aaad357
--- /dev/null
+++ b/src/lib/components/workspace/Models.svelte
@@ -0,0 +1,717 @@
+<script lang="ts">
+	import { marked } from 'marked';
+
+	import { toast } from 'svelte-sonner';
+	import Sortable from 'sortablejs';
+
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext, tick } from 'svelte';
+
+	import { WEBUI_NAME, config, mobile, models, settings, user } from '$lib/stores';
+	import { addNewModel, deleteModelById, getModelInfos, updateModelById } from '$lib/apis/models';
+
+	import { deleteModel } from '$lib/apis/ollama';
+	import { goto } from '$app/navigation';
+
+	import { getModels } from '$lib/apis';
+
+	import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
+	import ModelMenu from './Models/ModelMenu.svelte';
+	import ModelDeleteConfirmDialog from '../common/ConfirmDialog.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import GarbageBin from '../icons/GarbageBin.svelte';
+
+	const i18n = getContext('i18n');
+
+	let shiftKey = false;
+
+	let showModelDeleteConfirm = false;
+
+	let localModelfiles = [];
+
+	let importFiles;
+	let modelsImportInputElement: HTMLInputElement;
+
+	let _models = [];
+
+	let filteredModels = [];
+	let selectedModel = null;
+
+	$: if (_models) {
+		filteredModels = _models
+			.filter((m) => m?.owned_by !== 'arena')
+			.filter(
+				(m) => searchValue === '' || m.name.toLowerCase().includes(searchValue.toLowerCase())
+			);
+	}
+
+	let sortable = null;
+	let searchValue = '';
+
+	const deleteModelHandler = async (model) => {
+		console.log(model.info);
+		if (!model?.info) {
+			toast.error(
+				$i18n.t('{{ owner }}: You cannot delete a base model', {
+					owner: model.owned_by.toUpperCase()
+				})
+			);
+			return null;
+		}
+
+		const res = await deleteModelById(localStorage.token, model.id);
+
+		if (res) {
+			toast.success($i18n.t(`Deleted {{name}}`, { name: model.id }));
+		}
+
+		await models.set(await getModels(localStorage.token));
+		_models = $models;
+	};
+
+	const cloneModelHandler = async (model) => {
+		if ((model?.info?.base_model_id ?? null) === null) {
+			toast.error($i18n.t('You cannot clone a base model'));
+			return;
+		} else {
+			sessionStorage.model = JSON.stringify({
+				...model,
+				id: `${model.id}-clone`,
+				name: `${model.name} (Clone)`
+			});
+			goto('/workspace/models/create');
+		}
+	};
+
+	const shareModelHandler = async (model) => {
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
+
+		const url = 'https://openwebui.com';
+
+		const tab = await window.open(`${url}/models/create`, '_blank');
+
+		// Define the event handler function
+		const messageHandler = (event) => {
+			if (event.origin !== url) return;
+			if (event.data === 'loaded') {
+				tab.postMessage(JSON.stringify(model), '*');
+
+				// Remove the event listener after handling the message
+				window.removeEventListener('message', messageHandler);
+			}
+		};
+
+		window.addEventListener('message', messageHandler, false);
+	};
+
+	const moveToTopHandler = async (model) => {
+		// find models with position 0 and set them to 1
+		const topModels = _models.filter((m) => m.info?.meta?.position === 0);
+		for (const m of topModels) {
+			let info = m.info;
+			if (!info) {
+				info = {
+					id: m.id,
+					name: m.name,
+					meta: {
+						position: 1
+					},
+					params: {}
+				};
+			}
+
+			info.meta = {
+				...info.meta,
+				position: 1
+			};
+
+			await updateModelById(localStorage.token, info.id, info);
+		}
+
+		let info = model.info;
+
+		if (!info) {
+			info = {
+				id: model.id,
+				name: model.name,
+				meta: {
+					position: 0
+				},
+				params: {}
+			};
+		}
+
+		info.meta = {
+			...info.meta,
+			position: 0
+		};
+
+		const res = await updateModelById(localStorage.token, info.id, info);
+
+		if (res) {
+			toast.success($i18n.t(`Model {{name}} is now at the top`, { name: info.id }));
+		}
+
+		await models.set(await getModels(localStorage.token));
+		_models = $models;
+	};
+
+	const hideModelHandler = async (model) => {
+		let info = model.info;
+
+		if (!info) {
+			info = {
+				id: model.id,
+				name: model.name,
+				meta: {
+					suggestion_prompts: null
+				},
+				params: {}
+			};
+		}
+
+		info.meta = {
+			...info.meta,
+			hidden: !(info?.meta?.hidden ?? false)
+		};
+
+		console.log(info);
+
+		const res = await updateModelById(localStorage.token, info.id, info);
+
+		if (res) {
+			toast.success(
+				$i18n.t(`Model {{name}} is now {{status}}`, {
+					name: info.id,
+					status: info.meta.hidden ? 'hidden' : 'visible'
+				})
+			);
+		}
+
+		await models.set(await getModels(localStorage.token));
+		_models = $models;
+	};
+
+	const downloadModels = async (models) => {
+		let blob = new Blob([JSON.stringify(models)], {
+			type: 'application/json'
+		});
+		saveAs(blob, `models-export-${Date.now()}.json`);
+	};
+
+	const exportModelHandler = async (model) => {
+		let blob = new Blob([JSON.stringify([model])], {
+			type: 'application/json'
+		});
+		saveAs(blob, `${model.id}-${Date.now()}.json`);
+	};
+
+	const positionChangeHandler = async () => {
+		// Get the new order of the models
+		const modelIds = Array.from(document.getElementById('model-list').children).map((child) =>
+			child.id.replace('model-item-', '')
+		);
+
+		// Update the position of the models
+		for (const [index, id] of modelIds.entries()) {
+			const model = $models.find((m) => m.id === id);
+			if (model) {
+				let info = model.info;
+
+				if (!info) {
+					info = {
+						id: model.id,
+						name: model.name,
+						meta: {
+							position: index
+						},
+						params: {}
+					};
+				}
+
+				info.meta = {
+					...info.meta,
+					position: index
+				};
+				await updateModelById(localStorage.token, info.id, info);
+			}
+		}
+
+		await tick();
+		await models.set(await getModels(localStorage.token));
+	};
+
+	onMount(async () => {
+		// Legacy code to sync localModelfiles with models
+		_models = $models;
+		localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
+
+		if (localModelfiles) {
+			console.log(localModelfiles);
+		}
+
+		if (!$mobile) {
+			// SortableJS
+			sortable = new Sortable(document.getElementById('model-list'), {
+				animation: 150,
+				onUpdate: async (event) => {
+					console.log(event);
+					positionChangeHandler();
+				}
+			});
+		}
+
+		const onKeyDown = (event) => {
+			if (event.key === 'Shift') {
+				shiftKey = true;
+			}
+		};
+
+		const onKeyUp = (event) => {
+			if (event.key === 'Shift') {
+				shiftKey = false;
+			}
+		};
+
+		const onBlur = () => {
+			shiftKey = false;
+		};
+
+		window.addEventListener('keydown', onKeyDown);
+		window.addEventListener('keyup', onKeyUp);
+		window.addEventListener('blur', onBlur);
+
+		return () => {
+			window.removeEventListener('keydown', onKeyDown);
+			window.removeEventListener('keyup', onKeyUp);
+			window.removeEventListener('blur', onBlur);
+		};
+	});
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Models')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<ModelDeleteConfirmDialog
+	bind:show={showModelDeleteConfirm}
+	on:confirm={() => {
+		deleteModelHandler(selectedModel);
+	}}
+/>
+
+<div class=" flex w-full space-x-2 mb-2.5">
+	<div class="flex flex-1">
+		<div class=" self-center ml-1 mr-3">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<input
+			class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+			bind:value={searchValue}
+			placeholder={$i18n.t('Search Models')}
+		/>
+	</div>
+
+	<div>
+		<a
+			class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition font-medium text-sm flex items-center space-x-1"
+			href="/workspace/models/create"
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</a>
+	</div>
+</div>
+
+<div class="mb-3.5">
+	<div class="flex justify-between items-center">
+		<div class="flex md:self-center text-base font-medium px-0.5">
+			{$i18n.t('Models')}
+			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
+			<span class="text-base font-medium text-gray-500 dark:text-gray-300"
+				>{filteredModels.length}</span
+			>
+		</div>
+	</div>
+</div>
+
+<a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-1" href="/workspace/models/create">
+	<div class=" self-center w-8 flex-shrink-0">
+		<div
+			class="w-full h-8 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
+		>
+			<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6">
+				<path
+					fill-rule="evenodd"
+					d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+	</div>
+
+	<div class=" self-center">
+		<div class=" font-semibold line-clamp-1">{$i18n.t('Create a model')}</div>
+		<div class=" text-sm line-clamp-1 text-gray-500">
+			{$i18n.t('Customize models for a specific purpose')}
+		</div>
+	</div>
+</a>
+
+<div class=" my-2 mb-5" id="model-list">
+	{#each filteredModels as model}
+		<div
+			class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+			id="model-item-{model.id}"
+		>
+			<a
+				class=" flex flex-1 space-x-3.5 cursor-pointer w-full"
+				href={`/?models=${encodeURIComponent(model.id)}`}
+			>
+				<div class=" self-start w-8 pt-0.5">
+					<div
+						class=" rounded-full object-cover {(model?.info?.meta?.hidden ?? false)
+							? 'brightness-90 dark:brightness-50'
+							: ''} "
+					>
+						<img
+							src={model?.info?.meta?.profile_image_url ?? '/static/favicon.png'}
+							alt="modelfile profile"
+							class=" rounded-full w-full h-auto object-cover"
+						/>
+					</div>
+				</div>
+
+				<div
+					class=" flex-1 self-center {(model?.info?.meta?.hidden ?? false) ? 'text-gray-500' : ''}"
+				>
+					<Tooltip
+						content={marked.parse(
+							model?.ollama?.digest
+								? `${model?.ollama?.digest} *(${model?.ollama?.modified_at})*`
+								: ''
+						)}
+						className=" w-fit"
+						placement="top-start"
+					>
+						<div class="  font-semibold line-clamp-1">{model.name}</div>
+					</Tooltip>
+					<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1 text-gray-500">
+						{!!model?.info?.meta?.description
+							? model?.info?.meta?.description
+							: model?.ollama?.digest
+								? `${model.id} (${model?.ollama?.digest})`
+								: model.id}
+					</div>
+				</div>
+			</a>
+			<div class="flex flex-row gap-0.5 self-center">
+				{#if shiftKey}
+					<Tooltip
+						content={(model?.info?.meta?.hidden ?? false)
+							? $i18n.t('Show Model')
+							: $i18n.t('Hide Model')}
+					>
+						<button
+							class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+							type="button"
+							on:click={() => {
+								hideModelHandler(model);
+							}}
+						>
+							{#if model?.info?.meta?.hidden ?? false}
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									fill="none"
+									viewBox="0 0 24 24"
+									stroke-width="1.5"
+									stroke="currentColor"
+									class="size-4"
+								>
+									<path
+										stroke-linecap="round"
+										stroke-linejoin="round"
+										d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"
+									/>
+								</svg>
+							{:else}
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									fill="none"
+									viewBox="0 0 24 24"
+									stroke-width="1.5"
+									stroke="currentColor"
+									class="size-4"
+								>
+									<path
+										stroke-linecap="round"
+										stroke-linejoin="round"
+										d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
+									/>
+									<path
+										stroke-linecap="round"
+										stroke-linejoin="round"
+										d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
+									/>
+								</svg>
+							{/if}
+						</button>
+					</Tooltip>
+
+					<Tooltip content={$i18n.t('Delete')}>
+						<button
+							class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+							type="button"
+							on:click={() => {
+								deleteModelHandler(model);
+							}}
+						>
+							<GarbageBin />
+						</button>
+					</Tooltip>
+				{:else}
+					<a
+						class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+						type="button"
+						href={`/workspace/models/edit?id=${encodeURIComponent(model.id)}`}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="1.5"
+							stroke="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
+							/>
+						</svg>
+					</a>
+
+					<ModelMenu
+						{model}
+						shareHandler={() => {
+							shareModelHandler(model);
+						}}
+						cloneHandler={() => {
+							cloneModelHandler(model);
+						}}
+						exportHandler={() => {
+							exportModelHandler(model);
+						}}
+						moveToTopHandler={() => {
+							moveToTopHandler(model);
+						}}
+						hideHandler={() => {
+							hideModelHandler(model);
+						}}
+						deleteHandler={() => {
+							selectedModel = model;
+							showModelDeleteConfirm = true;
+						}}
+						onClose={() => {}}
+					>
+						<button
+							class="self-center w-fit text-sm p-1.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+							type="button"
+						>
+							<EllipsisHorizontal className="size-5" />
+						</button>
+					</ModelMenu>
+				{/if}
+			</div>
+		</div>
+	{/each}
+</div>
+
+<div class=" flex justify-end w-full mb-3">
+	<div class="flex space-x-1">
+		<input
+			id="models-import-input"
+			bind:this={modelsImportInputElement}
+			bind:files={importFiles}
+			type="file"
+			accept=".json"
+			hidden
+			on:change={() => {
+				console.log(importFiles);
+
+				let reader = new FileReader();
+				reader.onload = async (event) => {
+					let savedModels = JSON.parse(event.target.result);
+					console.log(savedModels);
+
+					for (const model of savedModels) {
+						if (model?.info ?? false) {
+							if ($models.find((m) => m.id === model.id)) {
+								await updateModelById(localStorage.token, model.id, model.info).catch((error) => {
+									return null;
+								});
+							} else {
+								await addNewModel(localStorage.token, model.info).catch((error) => {
+									return null;
+								});
+							}
+						}
+					}
+
+					await models.set(await getModels(localStorage.token));
+					_models = $models;
+				};
+
+				reader.readAsText(importFiles[0]);
+			}}
+		/>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={() => {
+				modelsImportInputElement.click();
+			}}
+		>
+			<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Import Models')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-3.5 h-3.5"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={async () => {
+				downloadModels($models);
+			}}
+		>
+			<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Models')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-3.5 h-3.5"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+	</div>
+
+	{#if localModelfiles.length > 0}
+		<div class="flex">
+			<div class=" self-center text-sm font-medium mr-4">
+				{localModelfiles.length} Local Modelfiles Detected
+			</div>
+
+			<div class="flex space-x-1">
+				<button
+					class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
+					on:click={async () => {
+						downloadModels(localModelfiles);
+
+						localStorage.removeItem('modelfiles');
+						localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
+					}}
+				>
+					<div class=" self-center">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="1.5"
+							stroke="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
+							/>
+						</svg>
+					</div>
+				</button>
+			</div>
+		</div>
+	{/if}
+</div>
+
+{#if $config?.features.enable_community_sharing}
+	<div class=" my-16">
+		<div class=" text-lg font-semibold mb-3 line-clamp-1">
+			{$i18n.t('Made by OpenWebUI Community')}
+		</div>
+
+		<a
+			class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2"
+			href="https://openwebui.com/#open-webui-community"
+			target="_blank"
+		>
+			<div class=" self-center w-10 flex-shrink-0">
+				<div
+					class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 24 24"
+						fill="currentColor"
+						class="w-6"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+			</div>
+
+			<div class=" self-center">
+				<div class=" font-semibold line-clamp-1">{$i18n.t('Discover a model')}</div>
+				<div class=" text-sm line-clamp-1">
+					{$i18n.t('Discover, download, and explore model presets')}
+				</div>
+			</div>
+		</a>
+	</div>
+{/if}
diff --git a/src/lib/components/workspace/Models/ActionsSelector.svelte b/src/lib/components/workspace/Models/ActionsSelector.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..8335455edab74179dcec9e736fbe2f88808be5ff
--- /dev/null
+++ b/src/lib/components/workspace/Models/ActionsSelector.svelte
@@ -0,0 +1,59 @@
+<script lang="ts">
+	import { getContext, onMount } from 'svelte';
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let actions = [];
+	export let selectedActionIds = [];
+
+	let _actions = {};
+
+	onMount(() => {
+		_actions = actions.reduce((acc, action) => {
+			acc[action.id] = {
+				...action,
+				selected: selectedActionIds.includes(action.id)
+			};
+
+			return acc;
+		}, {});
+	});
+</script>
+
+<div>
+	<div class="flex w-full justify-between mb-1">
+		<div class=" self-center text-sm font-semibold">{$i18n.t('Actions')}</div>
+	</div>
+
+	<div class=" text-xs dark:text-gray-500">
+		{$i18n.t('To select actions here, add them to the "Functions" workspace first.')}
+	</div>
+
+	<div class="flex flex-col">
+		{#if actions.length > 0}
+			<div class=" flex items-center mt-2 flex-wrap">
+				{#each Object.keys(_actions) as action, actionIdx}
+					<div class=" flex items-center gap-2 mr-3">
+						<div class="self-center flex items-center">
+							<Checkbox
+								state={_actions[action].selected ? 'checked' : 'unchecked'}
+								on:change={(e) => {
+									_actions[action].selected = e.detail === 'checked';
+									selectedActionIds = Object.keys(_actions).filter((t) => _actions[t].selected);
+								}}
+							/>
+						</div>
+
+						<div class=" py-0.5 text-sm w-full capitalize font-medium">
+							<Tooltip content={_actions[action].meta.description}>
+								{_actions[action].name}
+							</Tooltip>
+						</div>
+					</div>
+				{/each}
+			</div>
+		{/if}
+	</div>
+</div>
diff --git a/src/lib/components/workspace/Models/Capabilities.svelte b/src/lib/components/workspace/Models/Capabilities.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2a07f401d28c9f801cbb108ea0d14670ed8c0d59
--- /dev/null
+++ b/src/lib/components/workspace/Models/Capabilities.svelte
@@ -0,0 +1,44 @@
+<script lang="ts">
+	import { getContext } from 'svelte';
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import { marked } from 'marked';
+
+	const i18n = getContext('i18n');
+
+	const helpText = {
+		vision: $i18n.t('Model accepts image inputs'),
+		usage: $i18n.t(
+			'Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.'
+		)
+	};
+
+	export let capabilities: {
+		vision?: boolean;
+		usage?: boolean;
+	} = {};
+</script>
+
+<div>
+	<div class="flex w-full justify-between mb-1">
+		<div class=" self-center text-sm font-semibold">{$i18n.t('Capabilities')}</div>
+	</div>
+	<div class="flex flex-col">
+		{#each Object.keys(capabilities) as capability}
+			<div class=" flex items-center gap-2">
+				<Checkbox
+					state={capabilities[capability] ? 'checked' : 'unchecked'}
+					on:change={(e) => {
+						capabilities[capability] = e.detail === 'checked';
+					}}
+				/>
+
+				<div class=" py-0.5 text-sm capitalize">
+					<Tooltip content={marked.parse(helpText[capability])}>
+						{$i18n.t(capability)}
+					</Tooltip>
+				</div>
+			</div>
+		{/each}
+	</div>
+</div>
diff --git a/src/lib/components/workspace/Models/FiltersSelector.svelte b/src/lib/components/workspace/Models/FiltersSelector.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..92f64c2cf084a67b37deae73186b572e695861f9
--- /dev/null
+++ b/src/lib/components/workspace/Models/FiltersSelector.svelte
@@ -0,0 +1,60 @@
+<script lang="ts">
+	import { getContext, onMount } from 'svelte';
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let filters = [];
+	export let selectedFilterIds = [];
+
+	let _filters = {};
+
+	onMount(() => {
+		_filters = filters.reduce((acc, filter) => {
+			acc[filter.id] = {
+				...filter,
+				selected: selectedFilterIds.includes(filter.id)
+			};
+
+			return acc;
+		}, {});
+	});
+</script>
+
+<div>
+	<div class="flex w-full justify-between mb-1">
+		<div class=" self-center text-sm font-semibold">{$i18n.t('Filters')}</div>
+	</div>
+
+	<div class=" text-xs dark:text-gray-500">
+		{$i18n.t('To select filters here, add them to the "Functions" workspace first.')}
+	</div>
+
+	<!-- TODO: Filer order matters -->
+	<div class="flex flex-col">
+		{#if filters.length > 0}
+			<div class=" flex items-center mt-2 flex-wrap">
+				{#each Object.keys(_filters) as filter, filterIdx}
+					<div class=" flex items-center gap-2 mr-3">
+						<div class="self-center flex items-center">
+							<Checkbox
+								state={_filters[filter].selected ? 'checked' : 'unchecked'}
+								on:change={(e) => {
+									_filters[filter].selected = e.detail === 'checked';
+									selectedFilterIds = Object.keys(_filters).filter((t) => _filters[t].selected);
+								}}
+							/>
+						</div>
+
+						<div class=" py-0.5 text-sm w-full capitalize font-medium">
+							<Tooltip content={_filters[filter].meta.description}>
+								{_filters[filter].name}
+							</Tooltip>
+						</div>
+					</div>
+				{/each}
+			</div>
+		{/if}
+	</div>
+</div>
diff --git a/src/lib/components/workspace/Models/Knowledge.svelte b/src/lib/components/workspace/Models/Knowledge.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d2d57546bb6d8c4b8585dac317926be41782d1b9
--- /dev/null
+++ b/src/lib/components/workspace/Models/Knowledge.svelte
@@ -0,0 +1,63 @@
+<script lang="ts">
+	import { getContext } from 'svelte';
+	import Selector from './Knowledge/Selector.svelte';
+	import FileItem from '$lib/components/common/FileItem.svelte';
+
+	export let selectedKnowledge = [];
+	export let collections = [];
+
+	const i18n = getContext('i18n');
+</script>
+
+<div>
+	<div class="flex w-full justify-between mb-1">
+		<div class=" self-center text-sm font-semibold">{$i18n.t('Knowledge')}</div>
+	</div>
+
+	<div class=" text-xs dark:text-gray-500">
+		{$i18n.t('To attach knowledge base here, add them to the "Knowledge" workspace first.')}
+	</div>
+
+	<div class="flex flex-col">
+		{#if selectedKnowledge?.length > 0}
+			<div class=" flex flex-wrap items-center gap-2 mt-2">
+				{#each selectedKnowledge as file, fileIdx}
+					<FileItem
+						{file}
+						name={file.name}
+						type={file?.legacy
+							? `Legacy${file.type ? ` ${file.type}` : ''}`
+							: (file?.type ?? 'Collection')}
+						dismissible
+						on:dismiss={(e) => {
+							selectedKnowledge = selectedKnowledge.filter((_, idx) => idx !== fileIdx);
+						}}
+					/>
+				{/each}
+			</div>
+		{/if}
+
+		<div class="flex flex-wrap text-sm font-medium gap-1.5 mt-2">
+			<Selector
+				on:select={(e) => {
+					const item = e.detail;
+
+					if (!selectedKnowledge.find((k) => k.id === item.id)) {
+						selectedKnowledge = [
+							...selectedKnowledge,
+							{
+								...item
+							}
+						];
+					}
+				}}
+			>
+				<button
+					class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
+					type="button">{$i18n.t('Select Knowledge')}</button
+				>
+			</Selector>
+		</div>
+		<!-- {knowledge} -->
+	</div>
+</div>
diff --git a/src/lib/components/workspace/Models/Knowledge/Selector.svelte b/src/lib/components/workspace/Models/Knowledge/Selector.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6843c17a08ed00f3d7a39d8db239360f6afbaa4f
--- /dev/null
+++ b/src/lib/components/workspace/Models/Knowledge/Selector.svelte
@@ -0,0 +1,167 @@
+<script lang="ts">
+	import Fuse from 'fuse.js';
+
+	import { DropdownMenu } from 'bits-ui';
+	import { onMount, getContext, createEventDispatcher } from 'svelte';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { knowledge } from '$lib/stores';
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	export let onClose: Function = () => {};
+
+	let query = '';
+
+	let items = [];
+	let filteredItems = [];
+
+	let fuse = null;
+	$: if (fuse) {
+		filteredItems = query
+			? fuse.search(query).map((e) => {
+					return e.item;
+				})
+			: items;
+	}
+
+	onMount(() => {
+		let legacy_documents = $knowledge.filter((item) => item?.meta?.document);
+		let legacy_collections =
+			legacy_documents.length > 0
+				? [
+						{
+							name: 'All Documents',
+							legacy: true,
+							type: 'collection',
+							description: 'Deprecated (legacy collection), please create a new knowledge base.',
+
+							title: $i18n.t('All Documents'),
+							collection_names: legacy_documents.map((item) => item.id)
+						},
+
+						...legacy_documents
+							.reduce((a, item) => {
+								return [...new Set([...a, ...(item?.meta?.tags ?? []).map((tag) => tag.name)])];
+							}, [])
+							.map((tag) => ({
+								name: tag,
+								legacy: true,
+								type: 'collection',
+								description: 'Deprecated (legacy collection), please create a new knowledge base.',
+
+								collection_names: legacy_documents
+									.filter((item) => (item?.meta?.tags ?? []).map((tag) => tag.name).includes(tag))
+									.map((item) => item.id)
+							}))
+					]
+				: [];
+
+		items = [...$knowledge, ...legacy_collections].map((item) => {
+			return {
+				...item,
+				...(item?.legacy || item?.meta?.legacy || item?.meta?.document ? { legacy: true } : {}),
+				type: item?.meta?.document ? 'document' : 'collection'
+			};
+		});
+
+		fuse = new Fuse(items, {
+			keys: ['name', 'description']
+		});
+	});
+</script>
+
+<Dropdown
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+			query = '';
+		}
+	}}
+>
+	<slot />
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-80 rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
+			sideOffset={8}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			<div class=" flex w-full space-x-2 py-0.5 px-2">
+				<div class="flex flex-1">
+					<div class=" self-center ml-1 mr-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 20 20"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<input
+						class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+						bind:value={query}
+						placeholder={$i18n.t('Search Knowledge')}
+					/>
+				</div>
+			</div>
+
+			<hr class=" border-gray-50 dark:border-gray-700 my-1.5" />
+
+			<div class="max-h-48 overflow-y-scroll">
+				{#if filteredItems.length === 0}
+					<div class="text-center text-sm text-gray-500 dark:text-gray-400">
+						{$i18n.t('No knowledge found')}
+					</div>
+				{:else}
+					{#each filteredItems as item}
+						<DropdownMenu.Item
+							class="flex gap-2.5 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+							on:click={() => {
+								dispatch('select', item);
+							}}
+						>
+							<div class="flex items-center">
+								<div class="flex flex-col">
+									<div class=" w-fit mb-0.5">
+										{#if item.legacy}
+											<div
+												class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1"
+											>
+												Legacy
+											</div>
+										{:else if item?.meta?.document}
+											<div
+												class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1"
+											>
+												Document
+											</div>
+										{:else}
+											<div
+												class="bg-green-500/20 text-green-700 dark:text-green-200 rounded uppercase text-xs font-bold px-1"
+											>
+												Collection
+											</div>
+										{/if}
+									</div>
+
+									<div class="line-clamp-1 font-medium pr-0.5">
+										{item.name}
+									</div>
+								</div>
+							</div>
+						</DropdownMenu.Item>
+					{/each}
+				{/if}
+			</div>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/workspace/Models/ModelMenu.svelte b/src/lib/components/workspace/Models/ModelMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..49cefec1a75e4f5c37d44d494c41543365d15d76
--- /dev/null
+++ b/src/lib/components/workspace/Models/ModelMenu.svelte
@@ -0,0 +1,161 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext } from 'svelte';
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
+	import Pencil from '$lib/components/icons/Pencil.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Tags from '$lib/components/chat/Tags.svelte';
+	import Share from '$lib/components/icons/Share.svelte';
+	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
+	import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
+	import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
+	import ArrowUpCircle from '$lib/components/icons/ArrowUpCircle.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let model;
+
+	export let shareHandler: Function;
+	export let cloneHandler: Function;
+	export let exportHandler: Function;
+
+	export let moveToTopHandler: Function;
+	export let hideHandler: Function;
+	export let deleteHandler: Function;
+	export let onClose: Function;
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+>
+	<Tooltip content={$i18n.t('More')}>
+		<slot />
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[160px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			sideOffset={-2}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800  rounded-md"
+				on:click={() => {
+					shareHandler();
+				}}
+			>
+				<Share />
+				<div class="flex items-center">{$i18n.t('Share')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					cloneHandler();
+				}}
+			>
+				<DocumentDuplicate />
+
+				<div class="flex items-center">{$i18n.t('Clone')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					exportHandler();
+				}}
+			>
+				<ArrowDownTray />
+
+				<div class="flex items-center">{$i18n.t('Export')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					moveToTopHandler();
+				}}
+			>
+				<ArrowUpCircle />
+
+				<div class="flex items-center">{$i18n.t('Move to Top')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					hideHandler();
+				}}
+			>
+				{#if model?.info?.meta?.hidden ?? false}
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="size-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"
+						/>
+					</svg>
+				{:else}
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="size-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
+						/>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
+						/>
+					</svg>
+				{/if}
+
+				<div class="flex items-center">
+					{#if model?.info?.meta?.hidden ?? false}
+						{$i18n.t('Show Model')}
+					{:else}
+						{$i18n.t('Hide Model')}
+					{/if}
+				</div>
+			</DropdownMenu.Item>
+
+			<hr class="border-gray-100 dark:border-gray-800 my-1" />
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					deleteHandler();
+				}}
+			>
+				<GarbageBin strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Delete')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/workspace/Models/ToolsSelector.svelte b/src/lib/components/workspace/Models/ToolsSelector.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..5f1b3b482128882f2c076b85f044ed94cfa31798
--- /dev/null
+++ b/src/lib/components/workspace/Models/ToolsSelector.svelte
@@ -0,0 +1,57 @@
+<script lang="ts">
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+	import { getContext, onMount } from 'svelte';
+
+	export let tools = [];
+
+	let _tools = {};
+
+	export let selectedToolIds = [];
+
+	const i18n = getContext('i18n');
+
+	onMount(() => {
+		_tools = tools.reduce((acc, tool) => {
+			acc[tool.id] = {
+				...tool,
+				selected: selectedToolIds.includes(tool.id)
+			};
+
+			return acc;
+		}, {});
+	});
+</script>
+
+<div>
+	<div class="flex w-full justify-between mb-1">
+		<div class=" self-center text-sm font-semibold">{$i18n.t('Tools')}</div>
+	</div>
+
+	<div class=" text-xs dark:text-gray-500">
+		{$i18n.t('To select toolkits here, add them to the "Tools" workspace first.')}
+	</div>
+
+	<div class="flex flex-col">
+		{#if tools.length > 0}
+			<div class=" flex items-center mt-2 flex-wrap">
+				{#each Object.keys(_tools) as tool, toolIdx}
+					<div class=" flex items-center gap-2 mr-3">
+						<div class="self-center flex items-center">
+							<Checkbox
+								state={_tools[tool].selected ? 'checked' : 'unchecked'}
+								on:change={(e) => {
+									_tools[tool].selected = e.detail === 'checked';
+									selectedToolIds = Object.keys(_tools).filter((t) => _tools[t].selected);
+								}}
+							/>
+						</div>
+
+						<div class=" py-0.5 text-sm w-full capitalize font-medium">
+							{_tools[tool].name}
+						</div>
+					</div>
+				{/each}
+			</div>
+		{/if}
+	</div>
+</div>
diff --git a/src/lib/components/workspace/Prompts.svelte b/src/lib/components/workspace/Prompts.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6ccfa91a66a3872aff18d50f0954a26dba6c1eeb
--- /dev/null
+++ b/src/lib/components/workspace/Prompts.svelte
@@ -0,0 +1,337 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext } from 'svelte';
+	import { WEBUI_NAME, config, prompts } from '$lib/stores';
+	import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
+	import { error } from '@sveltejs/kit';
+	import { goto } from '$app/navigation';
+	import PromptMenu from './Prompts/PromptMenu.svelte';
+	import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
+	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+
+	const i18n = getContext('i18n');
+
+	let importFiles = '';
+	let query = '';
+	let promptsImportInputElement: HTMLInputElement;
+
+	let showDeleteConfirm = false;
+	let deletePrompt = null;
+
+	let filteredItems = [];
+	$: filteredItems = $prompts.filter((p) => query === '' || p.command.includes(query));
+
+	const shareHandler = async (prompt) => {
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
+
+		const url = 'https://openwebui.com';
+
+		const tab = await window.open(`${url}/prompts/create`, '_blank');
+		window.addEventListener(
+			'message',
+			(event) => {
+				if (event.origin !== url) return;
+				if (event.data === 'loaded') {
+					tab.postMessage(JSON.stringify(prompt), '*');
+				}
+			},
+			false
+		);
+	};
+
+	const cloneHandler = async (prompt) => {
+		sessionStorage.prompt = JSON.stringify(prompt);
+		goto('/workspace/prompts/create');
+	};
+
+	const exportHandler = async (prompt) => {
+		let blob = new Blob([JSON.stringify([prompt])], {
+			type: 'application/json'
+		});
+		saveAs(blob, `prompt-export-${Date.now()}.json`);
+	};
+
+	const deleteHandler = async (prompt) => {
+		const command = prompt.command;
+		await deletePromptByCommand(localStorage.token, command);
+		await prompts.set(await getPrompts(localStorage.token));
+	};
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Prompts')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<DeleteConfirmDialog
+	bind:show={showDeleteConfirm}
+	title={$i18n.t('Delete prompt?')}
+	on:confirm={() => {
+		deleteHandler(deletePrompt);
+	}}
+>
+	<div class=" text-sm text-gray-500">
+		{$i18n.t('This will delete')} <span class="  font-semibold">{deletePrompt.command}</span>.
+	</div>
+</DeleteConfirmDialog>
+
+<div class=" flex w-full space-x-2 mb-2.5">
+	<div class="flex flex-1">
+		<div class=" self-center ml-1 mr-3">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<input
+			class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+			bind:value={query}
+			placeholder={$i18n.t('Search Prompts')}
+		/>
+	</div>
+
+	<div>
+		<a
+			class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
+			href="/workspace/prompts/create"
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</a>
+	</div>
+</div>
+
+<div class="mb-3.5">
+	<div class="flex justify-between items-center">
+		<div class="flex md:self-center text-base font-medium px-0.5">
+			{$i18n.t('Prompts')}
+			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
+			<span class="text-base font-medium text-gray-500 dark:text-gray-300"
+				>{filteredItems.length}</span
+			>
+		</div>
+	</div>
+</div>
+
+<div class="my-3 mb-5">
+	{#each filteredItems as prompt}
+		<div
+			class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+		>
+			<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
+				<a href={`/workspace/prompts/edit?command=${encodeURIComponent(prompt.command)}`}>
+					<div class=" flex-1 self-center pl-1.5">
+						<div class=" font-semibold line-clamp-1">{prompt.command}</div>
+						<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
+							{prompt.title}
+						</div>
+					</div>
+				</a>
+			</div>
+			<div class="flex flex-row gap-0.5 self-center">
+				<a
+					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					href={`/workspace/prompts/edit?command=${encodeURIComponent(prompt.command)}`}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
+						/>
+					</svg>
+				</a>
+
+				<PromptMenu
+					shareHandler={() => {
+						shareHandler(prompt);
+					}}
+					cloneHandler={() => {
+						cloneHandler(prompt);
+					}}
+					exportHandler={() => {
+						exportHandler(prompt);
+					}}
+					deleteHandler={async () => {
+						deletePrompt = prompt;
+						showDeleteConfirm = true;
+					}}
+					onClose={() => {}}
+				>
+					<button
+						class="self-center w-fit text-sm p-1.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+						type="button"
+					>
+						<EllipsisHorizontal className="size-5" />
+					</button>
+				</PromptMenu>
+			</div>
+		</div>
+	{/each}
+</div>
+
+<div class=" flex justify-end w-full mb-3">
+	<div class="flex space-x-2">
+		<input
+			id="prompts-import-input"
+			bind:this={promptsImportInputElement}
+			bind:files={importFiles}
+			type="file"
+			accept=".json"
+			hidden
+			on:change={() => {
+				console.log(importFiles);
+
+				const reader = new FileReader();
+				reader.onload = async (event) => {
+					const savedPrompts = JSON.parse(event.target.result);
+					console.log(savedPrompts);
+
+					for (const prompt of savedPrompts) {
+						await createNewPrompt(
+							localStorage.token,
+							prompt.command.charAt(0) === '/' ? prompt.command.slice(1) : prompt.command,
+							prompt.title,
+							prompt.content
+						).catch((error) => {
+							toast.error(error);
+							return null;
+						});
+					}
+
+					await prompts.set(await getPrompts(localStorage.token));
+				};
+
+				reader.readAsText(importFiles[0]);
+			}}
+		/>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={() => {
+				promptsImportInputElement.click();
+			}}
+		>
+			<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Import Prompts')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={async () => {
+				// promptsImportInputElement.click();
+				let blob = new Blob([JSON.stringify($prompts)], {
+					type: 'application/json'
+				});
+				saveAs(blob, `prompts-export-${Date.now()}.json`);
+			}}
+		>
+			<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Prompts')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+
+		<!-- <button
+						on:click={() => {
+							loadDefaultPrompts();
+						}}
+					>
+						dd
+					</button> -->
+	</div>
+</div>
+
+{#if $config?.features.enable_community_sharing}
+	<div class=" my-16">
+		<div class=" text-lg font-semibold mb-3 line-clamp-1">
+			{$i18n.t('Made by OpenWebUI Community')}
+		</div>
+
+		<a
+			class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2"
+			href="https://openwebui.com/#open-webui-community"
+			target="_blank"
+		>
+			<div class=" self-center w-10 flex-shrink-0">
+				<div
+					class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 24 24"
+						fill="currentColor"
+						class="w-6"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+			</div>
+
+			<div class=" self-center">
+				<div class=" font-semibold line-clamp-1">{$i18n.t('Discover a prompt')}</div>
+				<div class=" text-sm line-clamp-1">
+					{$i18n.t('Discover, download, and explore custom prompts')}
+				</div>
+			</div>
+		</a>
+	</div>
+{/if}
diff --git a/src/lib/components/workspace/Prompts/PromptMenu.svelte b/src/lib/components/workspace/Prompts/PromptMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..9a4177b1edd979597a4c2af4391562eac91f75ac
--- /dev/null
+++ b/src/lib/components/workspace/Prompts/PromptMenu.svelte
@@ -0,0 +1,92 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext } from 'svelte';
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
+	import Pencil from '$lib/components/icons/Pencil.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Tags from '$lib/components/chat/Tags.svelte';
+	import Share from '$lib/components/icons/Share.svelte';
+	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
+	import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
+	import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let shareHandler: Function;
+	export let cloneHandler: Function;
+	export let exportHandler: Function;
+	export let deleteHandler: Function;
+	export let onClose: Function;
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+>
+	<Tooltip content={$i18n.t('More')}>
+		<slot />
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[160px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			sideOffset={-2}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800  rounded-md"
+				on:click={() => {
+					shareHandler();
+				}}
+			>
+				<Share />
+				<div class="flex items-center">{$i18n.t('Share')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					cloneHandler();
+				}}
+			>
+				<DocumentDuplicate />
+
+				<div class="flex items-center">{$i18n.t('Clone')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					exportHandler();
+				}}
+			>
+				<ArrowDownTray />
+
+				<div class="flex items-center">{$i18n.t('Export')}</div>
+			</DropdownMenu.Item>
+
+			<hr class="border-gray-100 dark:border-gray-800 my-1" />
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					deleteHandler();
+				}}
+			>
+				<GarbageBin strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Delete')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/workspace/Tools.svelte b/src/lib/components/workspace/Tools.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..50e57276e6e238c22fdbee2a1d4183094430982e
--- /dev/null
+++ b/src/lib/components/workspace/Tools.svelte
@@ -0,0 +1,520 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext } from 'svelte';
+	import { WEBUI_NAME, config, prompts, tools } from '$lib/stores';
+	import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
+
+	import { goto } from '$app/navigation';
+	import {
+		createNewTool,
+		deleteToolById,
+		exportTools,
+		getToolById,
+		getTools
+	} from '$lib/apis/tools';
+	import ArrowDownTray from '../icons/ArrowDownTray.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import ConfirmDialog from '../common/ConfirmDialog.svelte';
+	import ToolMenu from './Tools/ToolMenu.svelte';
+	import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
+	import ValvesModal from './common/ValvesModal.svelte';
+	import ManifestModal from './common/ManifestModal.svelte';
+	import Heart from '../icons/Heart.svelte';
+	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import GarbageBin from '../icons/GarbageBin.svelte';
+
+	const i18n = getContext('i18n');
+
+	let shiftKey = false;
+
+	let toolsImportInputElement: HTMLInputElement;
+	let importFiles;
+
+	let showConfirm = false;
+	let query = '';
+
+	let showManifestModal = false;
+	let showValvesModal = false;
+	let selectedTool = null;
+
+	let showDeleteConfirm = false;
+
+	let filteredItems = [];
+	$: filteredItems = $tools.filter(
+		(t) =>
+			query === '' ||
+			t.name.toLowerCase().includes(query.toLowerCase()) ||
+			t.id.toLowerCase().includes(query.toLowerCase())
+	);
+
+	const shareHandler = async (tool) => {
+		const item = await getToolById(localStorage.token, tool.id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
+
+		const url = 'https://openwebui.com';
+
+		const tab = await window.open(`${url}/tools/create`, '_blank');
+
+		// Define the event handler function
+		const messageHandler = (event) => {
+			if (event.origin !== url) return;
+			if (event.data === 'loaded') {
+				tab.postMessage(JSON.stringify(item), '*');
+
+				// Remove the event listener after handling the message
+				window.removeEventListener('message', messageHandler);
+			}
+		};
+
+		window.addEventListener('message', messageHandler, false);
+		console.log(item);
+	};
+
+	const cloneHandler = async (tool) => {
+		const _tool = await getToolById(localStorage.token, tool.id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (_tool) {
+			sessionStorage.tool = JSON.stringify({
+				..._tool,
+				id: `${_tool.id}_clone`,
+				name: `${_tool.name} (Clone)`
+			});
+			goto('/workspace/tools/create');
+		}
+	};
+
+	const exportHandler = async (tool) => {
+		const _tool = await getToolById(localStorage.token, tool.id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (_tool) {
+			let blob = new Blob([JSON.stringify([_tool])], {
+				type: 'application/json'
+			});
+			saveAs(blob, `tool-${_tool.id}-export-${Date.now()}.json`);
+		}
+	};
+
+	const deleteHandler = async (tool) => {
+		const res = await deleteToolById(localStorage.token, tool.id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Tool deleted successfully'));
+			tools.set(await getTools(localStorage.token));
+		}
+	};
+
+	onMount(() => {
+		const onKeyDown = (event) => {
+			if (event.key === 'Shift') {
+				shiftKey = true;
+			}
+		};
+
+		const onKeyUp = (event) => {
+			if (event.key === 'Shift') {
+				shiftKey = false;
+			}
+		};
+
+		const onBlur = () => {
+			shiftKey = false;
+		};
+
+		window.addEventListener('keydown', onKeyDown);
+		window.addEventListener('keyup', onKeyUp);
+		window.addEventListener('blur', onBlur);
+
+		return () => {
+			window.removeEventListener('keydown', onKeyDown);
+			window.removeEventListener('keyup', onKeyUp);
+			window.removeEventListener('blur', onBlur);
+		};
+	});
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Tools')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<div class=" flex w-full space-x-2 mb-2.5">
+	<div class="flex flex-1">
+		<div class=" self-center ml-1 mr-3">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<input
+			class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+			bind:value={query}
+			placeholder={$i18n.t('Search Tools')}
+		/>
+	</div>
+
+	<div>
+		<a
+			class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
+			href="/workspace/tools/create"
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</a>
+	</div>
+</div>
+
+<div class="mb-3.5">
+	<div class="flex justify-between items-center">
+		<div class="flex md:self-center text-base font-medium px-0.5">
+			{$i18n.t('Tools')}
+			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
+			<span class="text-base font-medium text-gray-500 dark:text-gray-300"
+				>{filteredItems.length}</span
+			>
+		</div>
+	</div>
+</div>
+
+<div class="my-3 mb-5">
+	{#each filteredItems as tool}
+		<div
+			class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+		>
+			<a
+				class=" flex flex-1 space-x-3.5 cursor-pointer w-full"
+				href={`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`}
+			>
+				<div class="flex items-center text-left">
+					<div class=" flex-1 self-center pl-1">
+						<div class=" font-semibold flex items-center gap-1.5">
+							<div
+								class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+							>
+								TOOL
+							</div>
+
+							{#if tool?.meta?.manifest?.version}
+								<div
+									class="text-xs font-bold px-1 rounded line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+								>
+									v{tool?.meta?.manifest?.version ?? ''}
+								</div>
+							{/if}
+
+							<div class="line-clamp-1">
+								{tool.name}
+							</div>
+						</div>
+
+						<div class="flex gap-1.5 px-1">
+							<div class=" text-gray-500 text-xs font-medium flex-shrink-0">{tool.id}</div>
+
+							<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
+								{tool.meta.description}
+							</div>
+						</div>
+					</div>
+				</div>
+			</a>
+			<div class="flex flex-row gap-0.5 self-center">
+				{#if shiftKey}
+					<Tooltip content={$i18n.t('Delete')}>
+						<button
+							class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+							type="button"
+							on:click={() => {
+								deleteHandler(tool);
+							}}
+						>
+							<GarbageBin />
+						</button>
+					</Tooltip>
+				{:else}
+					{#if tool?.meta?.manifest?.funding_url ?? false}
+						<Tooltip content="Support">
+							<button
+								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+								type="button"
+								on:click={() => {
+									selectedTool = tool;
+									showManifestModal = true;
+								}}
+							>
+								<Heart />
+							</button>
+						</Tooltip>
+					{/if}
+
+					<Tooltip content={$i18n.t('Valves')}>
+						<button
+							class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+							type="button"
+							on:click={() => {
+								selectedTool = tool;
+								showValvesModal = true;
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								fill="none"
+								viewBox="0 0 24 24"
+								stroke-width="1.5"
+								stroke="currentColor"
+								class="size-4"
+							>
+								<path
+									stroke-linecap="round"
+									stroke-linejoin="round"
+									d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
+								/>
+								<path
+									stroke-linecap="round"
+									stroke-linejoin="round"
+									d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
+								/>
+							</svg>
+						</button>
+					</Tooltip>
+
+					<ToolMenu
+						editHandler={() => {
+							goto(`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`);
+						}}
+						shareHandler={() => {
+							shareHandler(tool);
+						}}
+						cloneHandler={() => {
+							cloneHandler(tool);
+						}}
+						exportHandler={() => {
+							exportHandler(tool);
+						}}
+						deleteHandler={async () => {
+							selectedTool = tool;
+							showDeleteConfirm = true;
+						}}
+						onClose={() => {}}
+					>
+						<button
+							class="self-center w-fit text-sm p-1.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+							type="button"
+						>
+							<EllipsisHorizontal className="size-5" />
+						</button>
+					</ToolMenu>
+				{/if}
+			</div>
+		</div>
+	{/each}
+</div>
+
+<div class=" text-gray-500 text-xs mt-1 mb-2">
+	ⓘ {$i18n.t(
+		'Admins have access to all tools at all times; users need tools assigned per model in the workspace.'
+	)}
+</div>
+
+<div class=" flex justify-end w-full mb-2">
+	<div class="flex space-x-2">
+		<input
+			id="documents-import-input"
+			bind:this={toolsImportInputElement}
+			bind:files={importFiles}
+			type="file"
+			accept=".json"
+			hidden
+			on:change={() => {
+				console.log(importFiles);
+				showConfirm = true;
+			}}
+		/>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={() => {
+				toolsImportInputElement.click();
+			}}
+		>
+			<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Import Tools')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={async () => {
+				const _tools = await exportTools(localStorage.token).catch((error) => {
+					toast.error(error);
+					return null;
+				});
+
+				if (_tools) {
+					let blob = new Blob([JSON.stringify(_tools)], {
+						type: 'application/json'
+					});
+					saveAs(blob, `tools-export-${Date.now()}.json`);
+				}
+			}}
+		>
+			<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Tools')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+	</div>
+</div>
+
+{#if $config?.features.enable_community_sharing}
+	<div class=" my-16">
+		<div class=" text-lg font-semibold mb-3 line-clamp-1">
+			{$i18n.t('Made by OpenWebUI Community')}
+		</div>
+
+		<a
+			class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2"
+			href="https://openwebui.com/#open-webui-community"
+			target="_blank"
+		>
+			<div class=" self-center w-10 flex-shrink-0">
+				<div
+					class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 24 24"
+						fill="currentColor"
+						class="w-6"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</div>
+			</div>
+
+			<div class=" self-center">
+				<div class=" font-semibold line-clamp-1">{$i18n.t('Discover a tool')}</div>
+				<div class=" text-sm line-clamp-1">
+					{$i18n.t('Discover, download, and explore custom tools')}
+				</div>
+			</div>
+		</a>
+	</div>
+{/if}
+
+<DeleteConfirmDialog
+	bind:show={showDeleteConfirm}
+	title={$i18n.t('Delete tool?')}
+	on:confirm={() => {
+		deleteHandler(selectedTool);
+	}}
+>
+	<div class=" text-sm text-gray-500">
+		{$i18n.t('This will delete')} <span class="  font-semibold">{selectedTool.name}</span>.
+	</div>
+</DeleteConfirmDialog>
+
+<ValvesModal bind:show={showValvesModal} type="tool" id={selectedTool?.id ?? null} />
+<ManifestModal bind:show={showManifestModal} manifest={selectedTool?.meta?.manifest ?? {}} />
+
+<ConfirmDialog
+	bind:show={showConfirm}
+	on:confirm={() => {
+		const reader = new FileReader();
+		reader.onload = async (event) => {
+			const _tools = JSON.parse(event.target.result);
+			console.log(_tools);
+
+			for (const tool of _tools) {
+				const res = await createNewTool(localStorage.token, tool).catch((error) => {
+					toast.error(error);
+					return null;
+				});
+			}
+
+			toast.success($i18n.t('Tool imported successfully'));
+			tools.set(await getTools(localStorage.token));
+		};
+
+		reader.readAsText(importFiles[0]);
+	}}
+>
+	<div class="text-sm text-gray-500">
+		<div class=" bg-yellow-500/20 text-yellow-700 dark:text-yellow-200 rounded-lg px-4 py-3">
+			<div>{$i18n.t('Please carefully review the following warnings:')}</div>
+
+			<ul class=" mt-1 list-disc pl-4 text-xs">
+				<li>
+					{$i18n.t('Tools have a function calling system that allows arbitrary code execution')}.
+				</li>
+				<li>{$i18n.t('Do not install tools from sources you do not fully trust.')}</li>
+			</ul>
+		</div>
+
+		<div class="my-3">
+			{$i18n.t(
+				'I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.'
+			)}
+		</div>
+	</div>
+</ConfirmDialog>
diff --git a/src/lib/components/workspace/Tools/ToolMenu.svelte b/src/lib/components/workspace/Tools/ToolMenu.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..93a26b118df7a77cd11636ee0f048c604dfc662d
--- /dev/null
+++ b/src/lib/components/workspace/Tools/ToolMenu.svelte
@@ -0,0 +1,117 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext } from 'svelte';
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
+	import Pencil from '$lib/components/icons/Pencil.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Tags from '$lib/components/chat/Tags.svelte';
+	import Share from '$lib/components/icons/Share.svelte';
+	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
+	import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
+	import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let editHandler: Function;
+	export let shareHandler: Function;
+	export let cloneHandler: Function;
+	export let exportHandler: Function;
+	export let deleteHandler: Function;
+	export let onClose: Function;
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+>
+	<Tooltip content={$i18n.t('More')}>
+		<slot />
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[160px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			sideOffset={-2}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800  rounded-md"
+				on:click={() => {
+					editHandler();
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					fill="none"
+					viewBox="0 0 24 24"
+					stroke-width="1.5"
+					stroke="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						stroke-linecap="round"
+						stroke-linejoin="round"
+						d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
+					/>
+				</svg>
+
+				<div class="flex items-center">{$i18n.t('Edit')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800  rounded-md"
+				on:click={() => {
+					shareHandler();
+				}}
+			>
+				<Share />
+				<div class="flex items-center">{$i18n.t('Share')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					cloneHandler();
+				}}
+			>
+				<DocumentDuplicate />
+
+				<div class="flex items-center">{$i18n.t('Clone')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					exportHandler();
+				}}
+			>
+				<ArrowDownTray />
+
+				<div class="flex items-center">{$i18n.t('Export')}</div>
+			</DropdownMenu.Item>
+
+			<hr class="border-gray-100 dark:border-gray-800 my-1" />
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					deleteHandler();
+				}}
+			>
+				<GarbageBin strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Delete')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>
diff --git a/src/lib/components/workspace/Tools/ToolkitEditor.svelte b/src/lib/components/workspace/Tools/ToolkitEditor.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..362501595f4336c647de9df8540e67a8dd88a6ab
--- /dev/null
+++ b/src/lib/components/workspace/Tools/ToolkitEditor.svelte
@@ -0,0 +1,312 @@
+<script>
+	import { getContext, createEventDispatcher, onMount, tick } from 'svelte';
+
+	const i18n = getContext('i18n');
+
+	import CodeEditor from '$lib/components/common/CodeEditor.svelte';
+	import { goto } from '$app/navigation';
+	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import Badge from '$lib/components/common/Badge.svelte';
+	import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+
+	const dispatch = createEventDispatcher();
+
+	let formElement = null;
+	let loading = false;
+	let showConfirm = false;
+
+	export let edit = false;
+	export let clone = false;
+
+	export let id = '';
+	export let name = '';
+	export let meta = {
+		description: ''
+	};
+	export let content = '';
+	let _content = '';
+
+	$: if (content) {
+		updateContent();
+	}
+
+	const updateContent = () => {
+		_content = content;
+	};
+
+	$: if (name && !edit && !clone) {
+		id = name.replace(/\s+/g, '_').toLowerCase();
+	}
+
+	let codeEditor;
+	let boilerplate = `import os
+import requests
+from datetime import datetime
+
+
+class Tools:
+    def __init__(self):
+        pass
+
+    # Add your custom tools using pure Python code here, make sure to add type hints
+    # Use Sphinx-style docstrings to document your tools, they will be used for generating tools specifications
+    # Please refer to function_calling_filter_pipeline.py file from pipelines project for an example
+
+    def get_user_name_and_email_and_id(self, __user__: dict = {}) -> str:
+        """
+        Get the user name, Email and ID from the user object.
+        """
+
+        # Do not include :param for __user__ in the docstring as it should not be shown in the tool's specification
+        # The session user object will be passed as a parameter when the function is called
+
+        print(__user__)
+        result = ""
+
+        if "name" in __user__:
+            result += f"User: {__user__['name']}"
+        if "id" in __user__:
+            result += f" (ID: {__user__['id']})"
+        if "email" in __user__:
+            result += f" (Email: {__user__['email']})"
+
+        if result == "":
+            result = "User: Unknown"
+
+        return result
+
+    def get_current_time(self) -> str:
+        """
+        Get the current time in a more human-readable format.
+        :return: The current time.
+        """
+
+        now = datetime.now()
+        current_time = now.strftime("%I:%M:%S %p")  # Using 12-hour format with AM/PM
+        current_date = now.strftime(
+            "%A, %B %d, %Y"
+        )  # Full weekday, month name, day, and year
+
+        return f"Current Date and Time = {current_date}, {current_time}"
+
+    def calculator(self, equation: str) -> str:
+        """
+        Calculate the result of an equation.
+        :param equation: The equation to calculate.
+        """
+
+        # Avoid using eval in production code
+        # https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
+        try:
+            result = eval(equation)
+            return f"{equation} = {result}"
+        except Exception as e:
+            print(e)
+            return "Invalid equation"
+
+    def get_current_weather(self, city: str) -> str:
+        """
+        Get the current weather for a given city.
+        :param city: The name of the city to get the weather for.
+        :return: The current weather information or an error message.
+        """
+        api_key = os.getenv("OPENWEATHER_API_KEY")
+        if not api_key:
+            return (
+                "API key is not set in the environment variable 'OPENWEATHER_API_KEY'."
+            )
+
+        base_url = "http://api.openweathermap.org/data/2.5/weather"
+        params = {
+            "q": city,
+            "appid": api_key,
+            "units": "metric",  # Optional: Use 'imperial' for Fahrenheit
+        }
+
+        try:
+            response = requests.get(base_url, params=params)
+            response.raise_for_status()  # Raise HTTPError for bad responses (4xx and 5xx)
+            data = response.json()
+
+            if data.get("cod") != 200:
+                return f"Error fetching weather data: {data.get('message')}"
+
+            weather_description = data["weather"][0]["description"]
+            temperature = data["main"]["temp"]
+            humidity = data["main"]["humidity"]
+            wind_speed = data["wind"]["speed"]
+
+            return f"Weather in {city}: {temperature}°C"
+        except requests.RequestException as e:
+            return f"Error fetching weather data: {str(e)}"
+`;
+
+	const saveHandler = async () => {
+		loading = true;
+		dispatch('save', {
+			id,
+			name,
+			meta,
+			content
+		});
+	};
+
+	const submitHandler = async () => {
+		if (codeEditor) {
+			content = _content;
+			await tick();
+
+			const res = await codeEditor.formatPythonCodeHandler();
+			await tick();
+
+			content = _content;
+			await tick();
+
+			if (res) {
+				console.log('Code formatted successfully');
+
+				saveHandler();
+			}
+		}
+	};
+</script>
+
+<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
+	<div class="mx-auto w-full md:px-0 h-full">
+		<form
+			bind:this={formElement}
+			class=" flex flex-col max-h-[100dvh] h-full"
+			on:submit|preventDefault={() => {
+				if (edit) {
+					submitHandler();
+				} else {
+					showConfirm = true;
+				}
+			}}
+		>
+			<div class="flex flex-col flex-1 overflow-auto h-0">
+				<div class="w-full mb-2 flex flex-col gap-0.5">
+					<div class="flex w-full items-center">
+						<div class=" flex-shrink-0 mr-2">
+							<Tooltip content={$i18n.t('Back')}>
+								<button
+									class="w-full text-left text-sm py-1.5 px-1 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
+									on:click={() => {
+										goto('/workspace/tools');
+									}}
+									type="button"
+								>
+									<ChevronLeft strokeWidth="2.5" />
+								</button>
+							</Tooltip>
+						</div>
+
+						<div class="flex-1">
+							<input
+								class="w-full text-2xl font-medium bg-transparent outline-none"
+								type="text"
+								placeholder={$i18n.t('Toolkit Name (e.g. My ToolKit)')}
+								bind:value={name}
+								required
+							/>
+						</div>
+
+						<div>
+							<Badge type="muted" content={$i18n.t('Tool')} />
+						</div>
+					</div>
+
+					<div class=" flex gap-2 px-1">
+						{#if edit}
+							<div class="text-sm text-gray-500 flex-shrink-0">
+								{id}
+							</div>
+						{:else}
+							<input
+								class="w-full text-sm disabled:text-gray-500 bg-transparent outline-none"
+								type="text"
+								placeholder={$i18n.t('Toolkit ID (e.g. my_toolkit)')}
+								bind:value={id}
+								required
+								disabled={edit}
+							/>
+						{/if}
+
+						<input
+							class="w-full text-sm bg-transparent outline-none"
+							type="text"
+							placeholder={$i18n.t(
+								'Toolkit Description (e.g. A toolkit for performing various operations)'
+							)}
+							bind:value={meta.description}
+							required
+						/>
+					</div>
+				</div>
+
+				<div class="mb-2 flex-1 overflow-auto h-0 rounded-lg">
+					<CodeEditor
+						bind:this={codeEditor}
+						value={content}
+						{boilerplate}
+						lang="python"
+						on:change={(e) => {
+							_content = e.detail.value;
+						}}
+						on:save={() => {
+							if (formElement) {
+								formElement.requestSubmit();
+							}
+						}}
+					/>
+				</div>
+
+				<div class="pb-3 flex justify-between">
+					<div class="flex-1 pr-3">
+						<div class="text-xs text-gray-500 line-clamp-2">
+							<span class=" font-semibold dark:text-gray-200">{$i18n.t('Warning:')}</span>
+							{$i18n.t('Tools are a function calling system with arbitrary code execution')} <br />—
+							<span class=" font-medium dark:text-gray-400"
+								>{$i18n.t(`don't install random tools from sources you don't trust.`)}</span
+							>
+						</div>
+					</div>
+
+					<button
+						class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+						type="submit"
+					>
+						{$i18n.t('Save')}
+					</button>
+				</div>
+			</div>
+		</form>
+	</div>
+</div>
+
+<ConfirmDialog
+	bind:show={showConfirm}
+	on:confirm={() => {
+		submitHandler();
+	}}
+>
+	<div class="text-sm text-gray-500">
+		<div class=" bg-yellow-500/20 text-yellow-700 dark:text-yellow-200 rounded-lg px-4 py-3">
+			<div>{$i18n.t('Please carefully review the following warnings:')}</div>
+
+			<ul class=" mt-1 list-disc pl-4 text-xs">
+				<li>
+					{$i18n.t('Tools have a function calling system that allows arbitrary code execution.')}
+				</li>
+				<li>{$i18n.t('Do not install tools from sources you do not fully trust.')}</li>
+			</ul>
+		</div>
+
+		<div class="my-3">
+			{$i18n.t(
+				'I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.'
+			)}
+		</div>
+	</div>
+</ConfirmDialog>
diff --git a/src/lib/components/workspace/common/ManifestModal.svelte b/src/lib/components/workspace/common/ManifestModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..0e646e4097f15dfe0a2051120533b44fbe2ccc08
--- /dev/null
+++ b/src/lib/components/workspace/common/ManifestModal.svelte
@@ -0,0 +1,104 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { createEventDispatcher } from 'svelte';
+	import { onMount, getContext } from 'svelte';
+
+	import Modal from '../../common/Modal.svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	export let show = false;
+	export let manifest = {};
+</script>
+
+<Modal size="sm" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
+			<div class=" text-lg font-medium self-center">{$i18n.t('Show your support!')}</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full px-5 pb-4 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				<form
+					class="flex flex-col w-full"
+					on:submit|preventDefault={() => {
+						show = false;
+					}}
+				>
+					<div class="px-1 text-sm">
+						<div class="my-2">
+							{$i18n.t(
+								'The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.'
+							)}
+						</div>
+
+						<div class="my-2">
+							{$i18n.t(
+								'Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.'
+							)}
+						</div>
+
+						<hr class="dark:border-gray-800 my-3" />
+						<div class="my-2">
+							{$i18n.t('Support this plugin:')}
+							<a
+								href={manifest.funding_url}
+								target="_blank"
+								class="underline text-blue-400 hover:text-blue-300">{manifest.funding_url}</a
+							>
+						</div>
+					</div>
+
+					<div class="flex justify-end pt-3 text-sm font-medium">
+						<button
+							class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg flex flex-row space-x-1 items-center"
+							type="submit"
+						>
+							{$i18n.t('Done')}
+						</button>
+					</div>
+				</form>
+			</div>
+		</div>
+	</div>
+</Modal>
+
+<style>
+	input::-webkit-outer-spin-button,
+	input::-webkit-inner-spin-button {
+		/* display: none; <- Crashes Chrome on hover */
+		-webkit-appearance: none;
+		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
+	}
+
+	.tabs::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.tabs {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+
+	input[type='number'] {
+		-moz-appearance: textfield; /* Firefox */
+	}
+</style>
diff --git a/src/lib/components/workspace/common/ValvesModal.svelte b/src/lib/components/workspace/common/ValvesModal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..1f23c510ec0305dfc2ba168ba9d30cc64cf94040
--- /dev/null
+++ b/src/lib/components/workspace/common/ValvesModal.svelte
@@ -0,0 +1,202 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { createEventDispatcher } from 'svelte';
+	import { onMount, getContext } from 'svelte';
+	import { addUser } from '$lib/apis/auths';
+
+	import Modal from '../../common/Modal.svelte';
+	import {
+		getFunctionValvesById,
+		getFunctionValvesSpecById,
+		updateFunctionValvesById
+	} from '$lib/apis/functions';
+	import { getToolValvesById, getToolValvesSpecById, updateToolValvesById } from '$lib/apis/tools';
+	import Spinner from '../../common/Spinner.svelte';
+	import Switch from '$lib/components/common/Switch.svelte';
+	import Valves from '$lib/components/common/Valves.svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	export let show = false;
+
+	export let type = 'tool';
+	export let id = null;
+
+	let saving = false;
+	let loading = false;
+
+	let valvesSpec = null;
+	let valves = {};
+
+	const submitHandler = async () => {
+		saving = true;
+
+		if (valvesSpec) {
+			// Convert string to array
+			for (const property in valvesSpec.properties) {
+				if (valvesSpec.properties[property]?.type === 'array') {
+					valves[property] = (valves[property] ?? '').split(',').map((v) => v.trim());
+				}
+			}
+
+			let res = null;
+
+			if (type === 'tool') {
+				res = await updateToolValvesById(localStorage.token, id, valves).catch((error) => {
+					toast.error(error);
+				});
+			} else if (type === 'function') {
+				res = await updateFunctionValvesById(localStorage.token, id, valves).catch((error) => {
+					toast.error(error);
+				});
+			}
+
+			if (res) {
+				toast.success('Valves updated successfully');
+				dispatch('save');
+			}
+		}
+
+		saving = false;
+	};
+
+	const initHandler = async () => {
+		loading = true;
+		valves = {};
+		valvesSpec = null;
+
+		if (type === 'tool') {
+			valves = await getToolValvesById(localStorage.token, id);
+			valvesSpec = await getToolValvesSpecById(localStorage.token, id);
+		} else if (type === 'function') {
+			valves = await getFunctionValvesById(localStorage.token, id);
+			valvesSpec = await getFunctionValvesSpecById(localStorage.token, id);
+		}
+
+		if (!valves) {
+			valves = {};
+		}
+
+		if (valvesSpec) {
+			// Convert array to string
+			for (const property in valvesSpec.properties) {
+				if (valvesSpec.properties[property]?.type === 'array') {
+					valves[property] = (valves[property] ?? []).join(',');
+				}
+			}
+		}
+
+		loading = false;
+	};
+
+	$: if (show) {
+		initHandler();
+	}
+</script>
+
+<Modal size="sm" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
+			<div class=" text-lg font-medium self-center">{$i18n.t('Valves')}</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+
+		<div class="flex flex-col md:flex-row w-full px-5 pb-4 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				<form
+					class="flex flex-col w-full"
+					on:submit|preventDefault={() => {
+						submitHandler();
+					}}
+				>
+					<div class="px-1">
+						{#if !loading}
+							<Valves {valvesSpec} bind:valves />
+						{:else}
+							<Spinner className="size-5" />
+						{/if}
+					</div>
+
+					<div class="flex justify-end pt-3 text-sm font-medium">
+						<button
+							class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg flex flex-row space-x-1 items-center {saving
+								? ' cursor-not-allowed'
+								: ''}"
+							type="submit"
+							disabled={saving}
+						>
+							{$i18n.t('Save')}
+
+							{#if saving}
+								<div class="ml-2 self-center">
+									<svg
+										class=" w-4 h-4"
+										viewBox="0 0 24 24"
+										fill="currentColor"
+										xmlns="http://www.w3.org/2000/svg"
+										><style>
+											.spinner_ajPY {
+												transform-origin: center;
+												animation: spinner_AtaB 0.75s infinite linear;
+											}
+											@keyframes spinner_AtaB {
+												100% {
+													transform: rotate(360deg);
+												}
+											}
+										</style><path
+											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+											opacity=".25"
+										/><path
+											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+											class="spinner_ajPY"
+										/></svg
+									>
+								</div>
+							{/if}
+						</button>
+					</div>
+				</form>
+			</div>
+		</div>
+	</div>
+</Modal>
+
+<style>
+	input::-webkit-outer-spin-button,
+	input::-webkit-inner-spin-button {
+		/* display: none; <- Crashes Chrome on hover */
+		-webkit-appearance: none;
+		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
+	}
+
+	.tabs::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.tabs {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+
+	input[type='number'] {
+		-moz-appearance: textfield; /* Firefox */
+	}
+</style>
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fc756fce3ea536b9ce7a12505df534cc74cf4ad5
--- /dev/null
+++ b/src/lib/constants.ts
@@ -0,0 +1,101 @@
+import { browser, dev } from '$app/environment';
+// import { version } from '../../package.json';
+
+export const APP_NAME = 'Open WebUI';
+
+export const WEBUI_HOSTNAME = browser ? (dev ? `${location.hostname}:8080` : ``) : '';
+export const WEBUI_BASE_URL = browser ? (dev ? `http://${WEBUI_HOSTNAME}` : ``) : ``;
+export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`;
+
+export const OLLAMA_API_BASE_URL = `${WEBUI_BASE_URL}/ollama`;
+export const OPENAI_API_BASE_URL = `${WEBUI_BASE_URL}/openai`;
+export const AUDIO_API_BASE_URL = `${WEBUI_BASE_URL}/audio/api/v1`;
+export const IMAGES_API_BASE_URL = `${WEBUI_BASE_URL}/images/api/v1`;
+export const RETRIEVAL_API_BASE_URL = `${WEBUI_BASE_URL}/retrieval/api/v1`;
+
+export const WEBUI_VERSION = APP_VERSION;
+export const WEBUI_BUILD_HASH = APP_BUILD_HASH;
+export const REQUIRED_OLLAMA_VERSION = '0.1.16';
+
+export const SUPPORTED_FILE_TYPE = [
+	'application/epub+zip',
+	'application/pdf',
+	'text/plain',
+	'text/csv',
+	'text/xml',
+	'text/html',
+	'text/x-python',
+	'text/css',
+	'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+	'application/octet-stream',
+	'application/x-javascript',
+	'text/markdown',
+	'audio/mpeg',
+	'audio/wav',
+	'audio/ogg',
+	'audio/x-m4a'
+];
+
+export const SUPPORTED_FILE_EXTENSIONS = [
+	'md',
+	'rst',
+	'go',
+	'py',
+	'java',
+	'sh',
+	'bat',
+	'ps1',
+	'cmd',
+	'js',
+	'ts',
+	'css',
+	'cpp',
+	'hpp',
+	'h',
+	'c',
+	'cs',
+	'htm',
+	'html',
+	'sql',
+	'log',
+	'ini',
+	'pl',
+	'pm',
+	'r',
+	'dart',
+	'dockerfile',
+	'env',
+	'php',
+	'hs',
+	'hsc',
+	'lua',
+	'nginxconf',
+	'conf',
+	'm',
+	'mm',
+	'plsql',
+	'perl',
+	'rb',
+	'rs',
+	'db2',
+	'scala',
+	'bash',
+	'swift',
+	'vue',
+	'svelte',
+	'doc',
+	'docx',
+	'pdf',
+	'csv',
+	'txt',
+	'xls',
+	'xlsx',
+	'pptx',
+	'ppt',
+	'msg'
+];
+
+// Source: https://kit.svelte.dev/docs/modules#$env-static-public
+// This feature, akin to $env/static/private, exclusively incorporates environment variables
+// that are prefixed with config.kit.env.publicPrefix (usually set to PUBLIC_).
+// Consequently, these variables can be securely exposed to client-side code.
diff --git a/src/lib/i18n/index.ts b/src/lib/i18n/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..172c42f91bdc3fca84c460676147167448cfbe44
--- /dev/null
+++ b/src/lib/i18n/index.ts
@@ -0,0 +1,79 @@
+import i18next from 'i18next';
+import resourcesToBackend from 'i18next-resources-to-backend';
+import LanguageDetector from 'i18next-browser-languagedetector';
+import type { i18n as i18nType } from 'i18next';
+import { writable } from 'svelte/store';
+
+const createI18nStore = (i18n: i18nType) => {
+	const i18nWritable = writable(i18n);
+
+	i18n.on('initialized', () => {
+		i18nWritable.set(i18n);
+	});
+	i18n.on('loaded', () => {
+		i18nWritable.set(i18n);
+	});
+	i18n.on('added', () => i18nWritable.set(i18n));
+	i18n.on('languageChanged', () => {
+		i18nWritable.set(i18n);
+	});
+	return i18nWritable;
+};
+
+const createIsLoadingStore = (i18n: i18nType) => {
+	const isLoading = writable(false);
+
+	// if loaded resources are empty || {}, set loading to true
+	i18n.on('loaded', (resources) => {
+		// console.log('loaded:', resources);
+		Object.keys(resources).length !== 0 && isLoading.set(false);
+	});
+
+	// if resources failed loading, set loading to true
+	i18n.on('failedLoading', () => {
+		isLoading.set(true);
+	});
+
+	return isLoading;
+};
+
+export const initI18n = (defaultLocale: string | undefined) => {
+	let detectionOrder = defaultLocale
+		? ['querystring', 'localStorage']
+		: ['querystring', 'localStorage', 'navigator'];
+	let fallbackDefaultLocale = defaultLocale ? [defaultLocale] : ['en-US'];
+
+	const loadResource = (language: string, namespace: string) =>
+		import(`./locales/${language}/${namespace}.json`);
+
+	i18next
+		.use(resourcesToBackend(loadResource))
+		.use(LanguageDetector)
+		.init({
+			debug: false,
+			detection: {
+				order: detectionOrder,
+				caches: ['localStorage'],
+				lookupQuerystring: 'lang',
+				lookupLocalStorage: 'locale'
+			},
+			fallbackLng: {
+				default: fallbackDefaultLocale
+			},
+			ns: 'translation',
+			returnEmptyString: false,
+			interpolation: {
+				escapeValue: false // not needed for svelte as it escapes by default
+			}
+		});
+};
+
+const i18n = createI18nStore(i18next);
+const isLoadingStore = createIsLoadingStore(i18next);
+
+export const getLanguages = async () => {
+	const languages = (await import(`./locales/languages.json`)).default;
+	return languages;
+};
+export default i18n;
+export const isLoading = isLoadingStore;
diff --git a/src/lib/i18n/locales/ar-BH/translation.json b/src/lib/i18n/locales/ar-BH/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..5e2bd3e6edacf9b0289b26641ed2ef921214c579
--- /dev/null
+++ b/src/lib/i18n/locales/ar-BH/translation.json
@@ -0,0 +1,855 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' أو '-1' لا توجد انتهاء",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "( `sh webui.sh --api`مثال)",
+	"(latest)": "(الأخير)",
+	"{{ models }}": "{{ نماذج }}",
+	"{{ owner }}: You cannot delete a base model": "{{ المالك }}: لا يمكنك حذف نموذج أساسي",
+	"{{user}}'s Chats": "دردشات {{user}}",
+	"{{webUIName}} Backend Required": "{{webUIName}} مطلوب",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "يتم استخدام نموذج المهمة عند تنفيذ مهام مثل إنشاء عناوين للدردشات واستعلامات بحث الويب",
+	"a user": "مستخدم",
+	"About": "عن",
+	"Account": "الحساب",
+	"Account Activation Pending": "",
+	"Accurate information": "معلومات دقيقة",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "أضف",
+	"Add a model id": "إضافة معرف نموذج",
+	"Add a short description about what this model does": "أضف وصفا موجزا حول ما يفعله هذا النموذج",
+	"Add a short title for this prompt": "أضف عنوانًا قصيرًا لبداء المحادثة",
+	"Add a tag": "أضافة تاق",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "أضافة مطالبة مخصصه",
+	"Add Files": "إضافة ملفات",
+	"Add Memory": "إضافة ذكرايات",
+	"Add Model": "اضافة موديل",
+	"Add Tag": "",
+	"Add Tags": "اضافة تاق",
+	"Add text content": "",
+	"Add User": "اضافة مستخدم",
+	"Adjusting these settings will apply changes universally to all users.": "سيؤدي ضبط هذه الإعدادات إلى تطبيق التغييرات بشكل عام على كافة المستخدمين",
+	"admin": "المشرف",
+	"Admin": "",
+	"Admin Panel": "لوحة التحكم",
+	"Admin Settings": "اعدادات المشرف",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "التعليمات المتقدمة",
+	"Advanced Params": "المعلمات المتقدمة",
+	"All chats": "",
+	"All Documents": "جميع الملفات",
+	"Allow Chat Deletion": "يستطيع حذف المحادثات",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "الأحرف الأبجدية الرقمية والواصلات",
+	"Already have an account?": "هل تملك حساب ؟",
+	"an assistant": "مساعد",
+	"and": "و",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "و أنشئ رابط مشترك جديد.",
+	"API Base URL": "API الرابط الرئيسي",
+	"API Key": "API مفتاح",
+	"API Key created.": "API تم أنشاء المفتاح",
+	"API keys": "مفاتيح واجهة برمجة التطبيقات",
+	"April": "أبريل",
+	"Archive": "الأرشيف",
+	"Archive All Chats": "أرشفة جميع الدردشات",
+	"Archived Chats": "الأرشيف المحادثات",
+	"are allowed - Activate this command by typing": "مسموح - قم بتنشيط هذا الأمر عن طريق الكتابة",
+	"Are you sure?": "هل أنت متأكد ؟",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "أرفق ملف",
+	"Attention to detail": "انتبه للتفاصيل",
+	"Audio": "صوتي",
+	"August": "أغسطس",
+	"Auto-playback response": "استجابة التشغيل التلقائي",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 الرابط الرئيسي",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 الرابط مطلوب",
+	"Available list": "",
+	"available!": "متاح",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "خلف",
+	"Bad Response": "استجابة خطاء",
+	"Banners": "لافتات",
+	"Base Model (From)": "النموذج الأساسي (من)",
+	"Batch Size (num_batch)": "",
+	"before": "قبل",
+	"Being lazy": "كون كسول",
+	"Brave Search API Key": "مفتاح واجهة برمجة تطبيقات البحث الشجاع",
+	"Bypass SSL verification for Websites": "تجاوز التحقق من SSL للموقع",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "اللغاء",
+	"Capabilities": "قدرات",
+	"Change Password": "تغير الباسورد",
+	"Character": "",
+	"Chat": "المحادثة",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "UI الدردشة",
+	"Chat Controls": "",
+	"Chat direction": "اتجاه المحادثة",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "المحادثات",
+	"Check Again": "تحقق مرة اخرى",
+	"Check for updates": "تحقق من التحديثات",
+	"Checking for updates...": "البحث عن تحديثات",
+	"Choose a model before saving...": "أختار موديل قبل الحفظ",
+	"Chunk Overlap": "Chunk تداخل",
+	"Chunk Params": "Chunk المتغيرات",
+	"Chunk Size": "Chunk حجم",
+	"Citation": "اقتباس",
+	"Clear memory": "",
+	"Click here for help.": "أضغط هنا للمساعدة",
+	"Click here to": "أضغط هنا الانتقال",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "أضغط هنا للاختيار",
+	"Click here to select a csv file.": "أضغط هنا للاختيار ملف csv",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "أضغط هنا",
+	"Click on the user role button to change a user's role.": "أضغط على أسم الصلاحيات لتغيرها للمستخدم",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "استنساخ",
+	"Close": "أغلق",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "مجموعة",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI الرابط الافتراضي",
+	"ComfyUI Base URL is required.": "ComfyUI الرابط مطلوب",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "الأوامر",
+	"Completions": "",
+	"Concurrent Requests": "الطلبات المتزامنة",
+	"Confirm": "",
+	"Confirm Password": "تأكيد كلمة المرور",
+	"Confirm your action": "",
+	"Connections": "اتصالات",
+	"Contact Admin for WebUI Access": "",
+	"Content": "الاتصال",
+	"Content Extraction": "",
+	"Context Length": "طول السياق",
+	"Continue Response": "متابعة الرد",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "تم نسخ عنوان URL للدردشة المشتركة إلى الحافظة",
+	"Copied to clipboard": "",
+	"Copy": "نسخ",
+	"Copy last code block": "انسخ كتلة التعليمات البرمجية الأخيرة",
+	"Copy last response": "انسخ الرد الأخير",
+	"Copy Link": "أنسخ الرابط",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "تم النسخ إلى الحافظة بنجاح",
+	"Create a model": "إنشاء نموذج",
+	"Create Account": "إنشاء حساب",
+	"Create Knowledge": "",
+	"Create new key": "عمل مفتاح جديد",
+	"Create new secret key": "عمل سر جديد",
+	"Created at": "أنشئت في",
+	"Created At": "أنشئت من",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "الموديل المختار",
+	"Current Password": "كلمة السر الحالية",
+	"Custom": "مخصص",
+	"Customize models for a specific purpose": "تخصيص النماذج لغرض معين",
+	"Dark": "مظلم",
+	"Dashboard": "",
+	"Database": "قاعدة البيانات",
+	"December": "ديسمبر",
+	"Default": "الإفتراضي",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "(SentenceTransformers) الإفتراضي",
+	"Default Model": "النموذج الافتراضي",
+	"Default model updated": "الإفتراضي تحديث الموديل",
+	"Default Prompt Suggestions": "الإفتراضي Prompt الاقتراحات",
+	"Default User Role": "الإفتراضي صلاحيات المستخدم",
+	"Delete": "حذف",
+	"Delete a model": "حذف الموديل",
+	"Delete All Chats": "حذف جميع الدردشات",
+	"Delete chat": "حذف المحادثه",
+	"Delete Chat": "حذف المحادثه.",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "أحذف هذا الرابط",
+	"Delete tool?": "",
+	"Delete User": "حذف المستخدم",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} حذف",
+	"Deleted {{name}}": "حذف {{name}}",
+	"Description": "وصف",
+	"Didn't fully follow instructions": "لم أتبع التعليمات بشكل كامل",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "اكتشف نموذجا",
+	"Discover a prompt": "اكتشاف موجه",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "اكتشاف وتنزيل واستكشاف المطالبات المخصصة",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "اكتشاف وتنزيل واستكشاف الإعدادات المسبقة للنموذج",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "اعرض اسم المستخدم بدلاً منك في الدردشة",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "المستند",
+	"Documentation": "",
+	"Documents": "مستندات",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "لا يجري أي اتصالات خارجية، وتظل بياناتك آمنة على الخادم المستضاف محليًا.",
+	"Don't have an account?": "ليس لديك حساب؟",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "لا أحب النمط",
+	"Done": "",
+	"Download": "تحميل",
+	"Download canceled": "تم اللغاء التحميل",
+	"Download Database": "تحميل قاعدة البيانات",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "أسقط أية ملفات هنا لإضافتها إلى المحادثة",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "e.g. '30s','10m'. الوحدات الزمنية الصالحة هي 's', 'm', 'h'.",
+	"Edit": "تعديل",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "تعديل المستخدم",
+	"ElevenLabs": "",
+	"Email": "البريد",
+	"Embedding Batch Size": "",
+	"Embedding Model": "نموذج التضمين",
+	"Embedding Model Engine": "تضمين محرك النموذج",
+	"Embedding model set to \"{{embedding_model}}\"": "تم تعيين نموذج التضمين على \"{{embedding_model}}\"",
+	"Enable Community Sharing": "تمكين مشاركة المجتمع",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "تفعيل عمليات التسجيل الجديدة",
+	"Enable Web Search": "تمكين بحث الويب",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "تأكد من أن ملف CSV الخاص بك يتضمن 4 أعمدة بهذا الترتيب: Name, Email, Password, Role.",
+	"Enter {{role}} message here": "أدخل رسالة {{role}} هنا",
+	"Enter a detail about yourself for your LLMs to recall": "ادخل معلومات عنك تريد أن يتذكرها الموديل",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "أدخل مفتاح واجهة برمجة تطبيقات البحث الشجاع",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "أدخل الChunk Overlap",
+	"Enter Chunk Size": "أدخل Chunk الحجم",
+	"Enter description": "",
+	"Enter Github Raw URL": "أدخل عنوان URL ل Github Raw",
+	"Enter Google PSE API Key": "أدخل مفتاح واجهة برمجة تطبيقات PSE من Google",
+	"Enter Google PSE Engine Id": "أدخل معرف محرك PSE من Google",
+	"Enter Image Size (e.g. 512x512)": "(e.g. 512x512) أدخل حجم الصورة ",
+	"Enter language codes": "أدخل كود اللغة",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "(e.g. {{modelTag}}) أدخل الموديل تاق",
+	"Enter Number of Steps (e.g. 50)": "(e.g. 50) أدخل عدد الخطوات",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "أدخل النتيجة",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "أدخل عنوان URL لاستعلام Searxng",
+	"Enter Serper API Key": "أدخل مفتاح واجهة برمجة تطبيقات Serper",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "أدخل مفتاح واجهة برمجة تطبيقات Serpstack",
+	"Enter stop sequence": "أدخل تسلسل التوقف",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "أدخل Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "الرابط (e.g. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "URL (e.g. http://localhost:11434)",
+	"Enter Your Email": "أدخل البريد الاكتروني",
+	"Enter Your Full Name": "أدخل الاسم كامل",
+	"Enter your message": "",
+	"Enter Your Password": "ادخل كلمة المرور",
+	"Enter Your Role": "أدخل الصلاحيات",
+	"Error": "خطأ",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "تجريبي",
+	"Export": "تصدير",
+	"Export All Chats (All Users)": "تصدير جميع الدردشات (جميع المستخدمين)",
+	"Export chat (.json)": "",
+	"Export Chats": "تصدير جميع الدردشات",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "نماذج التصدير",
+	"Export Prompts": "مطالبات التصدير",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
+	"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "فبراير",
+	"Feedback History": "",
+	"Feel free to add specific details": "لا تتردد في إضافة تفاصيل محددة",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "وضع الملف",
+	"File not found.": "لم يتم العثور على الملف.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "تم اكتشاف انتحال بصمة الإصبع: غير قادر على استخدام الأحرف الأولى كصورة رمزية. الافتراضي لصورة الملف الشخصي الافتراضية.",
+	"Fluidly stream large external response chunks": "دفق قطع الاستجابة الخارجية الكبيرة بسلاسة",
+	"Focus chat input": "التركيز على إدخال الدردشة",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "اتبعت التعليمات على أكمل وجه",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "عقوبة التردد",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "عام",
+	"General Settings": "الاعدادات العامة",
+	"Generate Image": "",
+	"Generating search query": "إنشاء استعلام بحث",
+	"Generation Info": "معلومات الجيل",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "استجابة جيدة",
+	"Google PSE API Key": "مفتاح واجهة برمجة تطبيقات PSE من Google",
+	"Google PSE Engine Id": "معرف محرك PSE من Google",
+	"h:mm a": "الساعة:الدقائق صباحا/مساء",
+	"Haptic Feedback": "",
+	"has no conversations.": "ليس لديه محادثات.",
+	"Hello, {{name}}": " {{name}} مرحبا",
+	"Help": "مساعدة",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "أخفاء",
+	"Hide Model": "",
+	"How can I help you today?": "كيف استطيع مساعدتك اليوم؟",
+	"Hybrid Search": "البحث الهجين",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "توليد الصور (تجريبي)",
+	"Image Generation Engine": "محرك توليد الصور",
+	"Image Settings": "إعدادات الصورة",
+	"Images": "الصور",
+	"Import Chats": "استيراد الدردشات",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "استيراد النماذج",
+	"Import Prompts": "مطالبات الاستيراد",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "قم بتضمين علامة `-api` عند تشغيل Stable-diffusion-webui",
+	"Info": "معلومات",
+	"Input commands": "إدخال الأوامر",
+	"Install from Github URL": "التثبيت من عنوان URL لجيثب",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "واجهه المستخدم",
+	"Invalid file format.": "",
+	"Invalid Tag": "تاق غير صالحة",
+	"January": "يناير",
+	"join our Discord for help.": "انضم إلى Discord للحصول على المساعدة.",
+	"JSON": "JSON",
+	"JSON Preview": "معاينة JSON",
+	"July": "يوليو",
+	"June": "يونيو",
+	"JWT Expiration": "JWT تجريبي",
+	"JWT Token": "JWT Token",
+	"Keep Alive": "Keep Alive",
+	"Keyboard shortcuts": "اختصارات لوحة المفاتيح",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "اللغة",
+	"large language models, locally.": "",
+	"Last Active": "آخر نشاط",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "فاتح",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "يمكن أن تصدر بعض الأخطاء. لذلك يجب التحقق من المعلومات المهمة",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "من جهة اليسار إلى اليمين",
+	"Made by OpenWebUI Community": "OpenWebUI تم إنشاؤه بواسطة مجتمع ",
+	"Make sure to enclose them with": "تأكد من إرفاقها",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "إدارة النماذج",
+	"Manage Ollama Models": "Ollama إدارة موديلات ",
+	"Manage Pipelines": "إدارة خطوط الأنابيب",
+	"March": "مارس",
+	"Max Tokens (num_predict)": "ماكس توكنز (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "يمكن تنزيل 3 نماذج كحد أقصى في وقت واحد. الرجاء معاودة المحاولة في وقت لاحق.",
+	"May": "مايو",
+	"Memories accessible by LLMs will be shown here.": "سيتم عرض الذكريات التي يمكن الوصول إليها بواسطة LLMs هنا.",
+	"Memory": "الذاكرة",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "لن تتم مشاركة الرسائل التي ترسلها بعد إنشاء الرابط الخاص بك. سيتمكن المستخدمون الذين لديهم عنوان URL من عرض الدردشة المشتركة",
+	"Min P": "",
+	"Minimum Score": "الحد الأدنى من النقاط",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "تم تحميل النموذج '{{modelName}}' بنجاح",
+	"Model '{{modelTag}}' is already in queue for downloading.": "النموذج '{{modelTag}}' موجود بالفعل في قائمة الانتظار للتحميل",
+	"Model {{modelId}} not found": "لم يتم العثور على النموذج {{modelId}}.",
+	"Model {{modelName}} is not vision capable": "نموذج {{modelName}} غير قادر على الرؤية",
+	"Model {{name}} is now {{status}}": "نموذج {{name}} هو الآن {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "تم اكتشاف مسار نظام الملفات النموذجي. الاسم المختصر للنموذج مطلوب للتحديث، ولا يمكن الاستمرار.",
+	"Model ID": "رقم الموديل",
+	"Model Name": "",
+	"Model not selected": "لم تختار موديل",
+	"Model Params": "معلمات النموذج",
+	"Model updated successfully": "",
+	"Model Whitelisting": "القائمة البيضاء للموديل",
+	"Model(s) Whitelisted": "القائمة البيضاء الموديل",
+	"Modelfile Content": "محتوى الملف النموذجي",
+	"Models": "الموديلات",
+	"more": "",
+	"More": "المزيد",
+	"Move to Top": "",
+	"Name": "الأسم",
+	"Name your model": "قم بتسمية النموذج الخاص بك",
+	"New Chat": "دردشة جديدة",
+	"New folder": "",
+	"New Password": "كلمة المرور الجديدة",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "لا توجد نتايج",
+	"No search query generated": "لم يتم إنشاء استعلام بحث",
+	"No source available": "لا يوجد مصدر متاح",
+	"No valves to update": "",
+	"None": "اي",
+	"Not factually correct": "ليس صحيحا من حيث الواقع",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "ملاحظة: إذا قمت بتعيين الحد الأدنى من النقاط، فلن يؤدي البحث إلا إلى إرجاع المستندات التي لها نقاط أكبر من أو تساوي الحد الأدنى من النقاط.",
+	"Notes": "",
+	"Notifications": "إشعارات",
+	"November": "نوفمبر",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (أولاما)",
+	"OAuth ID": "",
+	"October": "اكتوبر",
+	"Off": "أغلاق",
+	"Okay, Let's Go!": "حسنا دعنا نذهب!",
+	"OLED Dark": "OLED داكن",
+	"Ollama": "Ollama",
+	"Ollama API": "أولاما API",
+	"Ollama API disabled": "أولاما API معطلة",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Ollama الاصدار",
+	"On": "تشغيل",
+	"Only": "فقط",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "يُسمح فقط بالأحرف الأبجدية الرقمية والواصلات في سلسلة الأمر.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "خطاء! يبدو أن عنوان URL غير صالح. يرجى التحقق مرة أخرى والمحاولة مرة أخرى.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "خطاء! أنت تستخدم طريقة غير مدعومة (الواجهة الأمامية فقط). يرجى تقديم واجهة WebUI من الواجهة الخلفية.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "فتح محادثة جديده",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API إعدادات",
+	"OpenAI API Key is required.": "OpenAI API.مطلوب مفتاح ",
+	"OpenAI URL/Key required.": "URL/مفتاح OpenAI.مطلوب عنوان ",
+	"or": "أو",
+	"Other": "آخر",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "الباسورد",
+	"PDF document (.pdf)": "PDF ملف (.pdf)",
+	"PDF Extract Images (OCR)": "PDF أستخرج الصور (OCR)",
+	"pending": "قيد الانتظار",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "{{error}} تم رفض الإذن عند الوصول إلى الميكروفون ",
+	"Personalization": "التخصيص",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "خطوط الانابيب",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "صمامات خطوط الأنابيب",
+	"Plain text (.txt)": "نص عادي (.txt)",
+	"Playground": "مكان التجربة",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "موقف ايجابي",
+	"Previous 30 days": "أخر 30 يوم",
+	"Previous 7 days": "أخر 7 أيام",
+	"Profile Image": "صورة الملف الشخصي",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "موجه (على سبيل المثال: أخبرني بحقيقة ممتعة عن الإمبراطورية الرومانية)",
+	"Prompt Content": "محتوى عاجل",
+	"Prompt suggestions": "اقتراحات سريعة",
+	"Prompts": "مطالبات",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com \"{{searchValue}}\" أسحب من ",
+	"Pull a model from Ollama.com": "Ollama.com سحب الموديل من ",
+	"Query Params": "Query Params",
+	"RAG Template": "RAG تنمبلت",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "أقراء لي",
+	"Record voice": "سجل صوت",
+	"Redirecting you to OpenWebUI Community": "OpenWebUI إعادة توجيهك إلى مجتمع ",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "رفض عندما لا ينبغي أن يكون",
+	"Regenerate": "تجديد",
+	"Release Notes": "ملاحظات الإصدار",
+	"Relevance": "",
+	"Remove": "إزالة",
+	"Remove Model": "حذف الموديل",
+	"Rename": "إعادة تسمية",
+	"Repeat Last N": "N كرر آخر",
+	"Request Mode": "وضع الطلب",
+	"Reranking Model": "إعادة تقييم النموذج",
+	"Reranking model disabled": "تم تعطيل نموذج إعادة الترتيب",
+	"Reranking model set to \"{{reranking_model}}\"": "تم ضبط نموذج إعادة الترتيب على \"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "النسخ التلقائي للاستجابة إلى الحافظة",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "منصب",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "من اليمين إلى اليسار",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "حفظ",
+	"Save & Create": "حفظ وإنشاء",
+	"Save & Update": "حفظ وتحديث",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "لم يعد حفظ سجلات الدردشة مباشرة في مساحة تخزين متصفحك مدعومًا. يرجى تخصيص بعض الوقت لتنزيل وحذف سجلات الدردشة الخاصة بك عن طريق النقر على الزر أدناه. لا تقلق، يمكنك بسهولة إعادة استيراد سجلات الدردشة الخاصة بك إلى الواجهة الخلفية من خلاله",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "البحث",
+	"Search a model": "البحث عن موديل",
+	"Search Chats": "البحث في الدردشات",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "نماذج البحث",
+	"Search Prompts": "أبحث حث",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "عدد نتائج البحث",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_zero": "تم البحث في {{count}} sites_zero",
+	"Searched {{count}} sites_one": "تم البحث في {{count}} sites_one",
+	"Searched {{count}} sites_two": "تم البحث في {{count}} sites_two",
+	"Searched {{count}} sites_few": "تم البحث في {{count}} sites_few",
+	"Searched {{count}} sites_many": "تم البحث في {{count}} sites_many",
+	"Searched {{count}} sites_other": "تم البحث في {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "عنوان URL لاستعلام Searxng",
+	"See readme.md for instructions": "readme.md للحصول على التعليمات",
+	"See what's new": "ما الجديد",
+	"Seed": "Seed",
+	"Select a base model": "حدد نموذجا أساسيا",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "أختار الموديل",
+	"Select a pipeline": "حدد مسارا",
+	"Select a pipeline url": "حدد عنوان URL لخط الأنابيب",
+	"Select a tool": "",
+	"Select an Ollama instance": "أختار سيرفر ",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": " أختار موديل",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "النموذج (النماذج) المحددة لا تدعم مدخلات الصور",
+	"Semantic distance to query": "",
+	"Send": "تم",
+	"Send a Message": "يُرجى إدخال طلبك هنا",
+	"Send message": "يُرجى إدخال طلبك هنا.",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "سبتمبر",
+	"Serper API Key": "مفتاح واجهة برمجة تطبيقات سيربر",
+	"Serply API Key": "",
+	"Serpstack API Key": "مفتاح واجهة برمجة تطبيقات Serpstack",
+	"Server connection verified": "تم التحقق من اتصال الخادم",
+	"Set as default": "الافتراضي",
+	"Set CFG Scale": "",
+	"Set Default Model": "تفعيد الموديل الافتراضي",
+	"Set embedding model (e.g. {{model}})": "ضبط نموذج المتجهات (على سبيل المثال: {{model}})",
+	"Set Image Size": "حجم الصورة",
+	"Set reranking model (e.g. {{model}})": "ضبط نموذج إعادة الترتيب (على سبيل المثال: {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "ضبط الخطوات",
+	"Set Task Model": "تعيين نموذج المهمة",
+	"Set Voice": "ضبط الصوت",
+	"Set whisper model": "",
+	"Settings": "الاعدادات",
+	"Settings saved successfully!": "تم حفظ الاعدادات بنجاح",
+	"Share": "كشاركة",
+	"Share Chat": "مشاركة الدردشة",
+	"Share to OpenWebUI Community": "OpenWebUI شارك في مجتمع",
+	"short-summary": "ملخص قصير",
+	"Show": "عرض",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "إظهار الاختصارات",
+	"Show your support!": "",
+	"Showcased creativity": "أظهر الإبداع",
+	"Sign in": "تسجيل الدخول",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "تسجيل الخروج",
+	"Sign up": "تسجيل",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "المصدر",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "{{error}} خطأ في التعرف على الكلام",
+	"Speech-to-Text Engine": "محرك تحويل الكلام إلى نص",
+	"Stop": "",
+	"Stop Sequence": "وقف التسلسل",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "STT اعدادات",
+	"Subtitle (e.g. about the Roman Empire)": "(e.g. about the Roman Empire) الترجمة",
+	"Success": "نجاح",
+	"Successfully updated.": "تم التحديث بنجاح",
+	"Suggested": "مقترحات",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "النظام",
+	"System Instructions": "",
+	"System Prompt": "محادثة النظام",
+	"Tags": "الوسوم",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "أخبرنا المزيد:",
+	"Temperature": "درجة حرارة",
+	"Template": "نموذج",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "محرك تحويل النص إلى كلام",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "شكرا لملاحظاتك!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "يجب أن تكون النتيجة قيمة تتراوح بين 0.0 (0%) و1.0 (100%).",
+	"Theme": "الثيم",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "وهذا يضمن حفظ محادثاتك القيمة بشكل آمن في قاعدة بياناتك الخلفية. شكرًا لك!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "شرح شامل",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "ملاحضة: قم بتحديث عدة فتحات متغيرة على التوالي عن طريق الضغط على مفتاح tab في مدخلات الدردشة بعد كل استبدال.",
+	"Title": "العنوان",
+	"Title (e.g. Tell me a fun fact)": "(e.g. Tell me a fun fact) العناون",
+	"Title Auto-Generation": "توليد تلقائي للعنوان",
+	"Title cannot be an empty string.": "العنوان مطلوب",
+	"Title Generation Prompt": "موجه إنشاء العنوان",
+	"To access the available model names for downloading,": "للوصول إلى أسماء الموديلات المتاحة للتنزيل،",
+	"To access the GGUF models available for downloading,": "للوصول إلى الموديلات GGUF المتاحة للتنزيل،",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "الى كتابة المحادثه",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "اليوم",
+	"Toggle settings": "فتح وأغلاق الاعدادات",
+	"Toggle sidebar": "فتح وأغلاق الشريط الجانبي",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "هل تواجه مشكلة في الوصول",
+	"TTS Model": "",
+	"TTS Settings": "TTS اعدادات",
+	"TTS Voice": "",
+	"Type": "نوع",
+	"Type Hugging Face Resolve (Download) URL": "اكتب عنوان URL لحل مشكلة الوجه (تنزيل).",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "{{provider}}خطاء أوه! حدثت مشكلة في الاتصال بـ ",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "تحديث ونسخ الرابط",
+	"Update for the latest features and improvements.": "",
+	"Update password": "تحديث كلمة المرور",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "GGUF رفع موديل نوع",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "تحميل الملفات",
+	"Upload Pipeline": "",
+	"Upload Progress": "جاري التحميل",
+	"URL Mode": "رابط الموديل",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Gravatar أستخدم",
+	"Use Initials": "Initials أستخدم",
+	"use_mlock (Ollama)": "use_mlock (أولاما)",
+	"use_mmap (Ollama)": "use_mmap (أولاما)",
+	"user": "مستخدم",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "صلاحيات المستخدم",
+	"Users": "المستخدمين",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "يستخدم",
+	"Valid time units:": "وحدات زمنية صالحة:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "المتغير",
+	"variable to have them replaced with clipboard content.": "متغير لاستبدالها بمحتوى الحافظة.",
+	"Version": "إصدار",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "تحذير",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "تحذير: إذا قمت بتحديث أو تغيير نموذج التضمين الخاص بك، فستحتاج إلى إعادة استيراد كافة المستندات.",
+	"Web": "Web",
+	"Web API": "",
+	"Web Loader Settings": "Web تحميل اعدادات",
+	"Web Search": "بحث الويب",
+	"Web Search Engine": "محرك بحث الويب",
+	"Webhook URL": "Webhook الرابط",
+	"WebUI Settings": "WebUI اعدادات",
+	"WebUI will make requests to": "سوف يقوم WebUI بتقديم طلبات ل",
+	"What’s New in": "ما هو الجديد",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "مساحة العمل",
+	"Write a prompt suggestion (e.g. Who are you?)": "اكتب اقتراحًا سريعًا (على سبيل المثال، من أنت؟)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "اكتب ملخصًا في 50 كلمة يلخص [الموضوع أو الكلمة الرئيسية]",
+	"Write something...": "",
+	"Yesterday": "أمس",
+	"You": "انت",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "لا يمكنك استنساخ نموذج أساسي",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "لا تملك محادثات محفوظه",
+	"You have shared this chat": "تم مشاركة هذه المحادثة",
+	"You're a helpful assistant.": "مساعدك المفيد هنا",
+	"You're now logged in.": "لقد قمت الآن بتسجيل الدخول.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube تحميل اعدادات"
+}
diff --git a/src/lib/i18n/locales/bg-BG/translation.json b/src/lib/i18n/locales/bg-BG/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..7ea8627717c3d6159aa336e94b9ba91486e7265b
--- /dev/null
+++ b/src/lib/i18n/locales/bg-BG/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' или '-1' за неограничен срок.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(например `sh webui.sh --api`)",
+	"(latest)": "(последна)",
+	"{{ models }}": "{{ модели }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Не можете да изтриете базов модел",
+	"{{user}}'s Chats": "{{user}}'s чатове",
+	"{{webUIName}} Backend Required": "{{webUIName}} Изисква се Бекенд",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Моделът на задачите се използва при изпълнение на задачи като генериране на заглавия за чатове и заявки за търсене в мрежата",
+	"a user": "потребител",
+	"About": "Относно",
+	"Account": "Акаунт",
+	"Account Activation Pending": "",
+	"Accurate information": "Точни информация",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "Добавяне",
+	"Add a model id": "Добавяне на ИД на модел",
+	"Add a short description about what this model does": "Добавете кратко описание за това какво прави този модел",
+	"Add a short title for this prompt": "Добавяне на кратко заглавие за този промпт",
+	"Add a tag": "Добавяне на таг",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Добавяне на собствен промпт",
+	"Add Files": "Добавяне на Файлове",
+	"Add Memory": "Добавяне на Памет",
+	"Add Model": "Добавяне на Модел",
+	"Add Tag": "",
+	"Add Tags": "добавяне на тагове",
+	"Add text content": "",
+	"Add User": "Добавяне на потребител",
+	"Adjusting these settings will apply changes universally to all users.": "При промяна на тези настройки промените се прилагат за всички потребители.",
+	"admin": "админ",
+	"Admin": "",
+	"Admin Panel": "Панел на Администратор",
+	"Admin Settings": "Настройки на Администратор",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "Разширени Параметри",
+	"Advanced Params": "Разширени параметри",
+	"All chats": "",
+	"All Documents": "Всички Документи",
+	"Allow Chat Deletion": "Позволи Изтриване на Чат",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "алфанумерични знаци и тире",
+	"Already have an account?": "Вече имате акаунт? ",
+	"an assistant": "асистент",
+	"and": "и",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "и създай нов общ линк.",
+	"API Base URL": "API Базов URL",
+	"API Key": "API Ключ",
+	"API Key created.": "API Ключ създаден.",
+	"API keys": "API Ключове",
+	"April": "Април",
+	"Archive": "Архивирани Чатове",
+	"Archive All Chats": "Архив Всички чатове",
+	"Archived Chats": "Архивирани Чатове",
+	"are allowed - Activate this command by typing": "са разрешени - Активирайте тази команда чрез въвеждане",
+	"Are you sure?": "Сигурни ли сте?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Прикачване на файл",
+	"Attention to detail": "Внимание към детайлите",
+	"Audio": "Аудио",
+	"August": "Август",
+	"Auto-playback response": "Аувтоматично възпроизвеждане на Отговора",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Базов URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Базов URL е задължителен.",
+	"Available list": "",
+	"available!": "наличен!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Назад",
+	"Bad Response": "Невалиден отговор от API",
+	"Banners": "Банери",
+	"Base Model (From)": "Базов модел (от)",
+	"Batch Size (num_batch)": "",
+	"before": "преди",
+	"Being lazy": "Да бъдеш мързелив",
+	"Brave Search API Key": "Смел ключ за API за търсене",
+	"Bypass SSL verification for Websites": "Изключване на SSL проверката за сайтове",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "Отказ",
+	"Capabilities": "Възможности",
+	"Change Password": "Промяна на Парола",
+	"Character": "",
+	"Chat": "Чат",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "UI за чат бублон",
+	"Chat Controls": "",
+	"Chat direction": "Направление на чата",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Чатове",
+	"Check Again": "Проверете Още Веднъж",
+	"Check for updates": "Проверка за актуализации",
+	"Checking for updates...": "Проверка за актуализации...",
+	"Choose a model before saving...": "Изберете модел преди запазване...",
+	"Chunk Overlap": "Chunk Overlap",
+	"Chunk Params": "Chunk Params",
+	"Chunk Size": "Chunk Size",
+	"Citation": "Цитат",
+	"Clear memory": "",
+	"Click here for help.": "Натиснете тук за помощ.",
+	"Click here to": "Натиснете тук за",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Натиснете тук, за да изберете",
+	"Click here to select a csv file.": "Натиснете тук, за да изберете csv файл.",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "натиснете тук.",
+	"Click on the user role button to change a user's role.": "Натиснете върху бутона за промяна на ролята на потребителя.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "Клонинг",
+	"Close": "Затвори",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Колекция",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Base URL",
+	"ComfyUI Base URL is required.": "ComfyUI Base URL е задължително.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Команда",
+	"Completions": "",
+	"Concurrent Requests": "Едновременни искания",
+	"Confirm": "",
+	"Confirm Password": "Потвърди Парола",
+	"Confirm your action": "",
+	"Connections": "Връзки",
+	"Contact Admin for WebUI Access": "",
+	"Content": "Съдържание",
+	"Content Extraction": "",
+	"Context Length": "Дължина на Контекста",
+	"Continue Response": "Продължи отговора",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "Копирана е връзката за чат!",
+	"Copied to clipboard": "",
+	"Copy": "Копирай",
+	"Copy last code block": "Копиране на последен код блок",
+	"Copy last response": "Копиране на последен отговор",
+	"Copy Link": "Копиране на връзка",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Копирането в клипборда беше успешно!",
+	"Create a model": "Създаване на модел",
+	"Create Account": "Създаване на Акаунт",
+	"Create Knowledge": "",
+	"Create new key": "Създаване на нов ключ",
+	"Create new secret key": "Създаване на нов секретен ключ",
+	"Created at": "Създадено на",
+	"Created At": "Създадено на",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "Текущ модел",
+	"Current Password": "Текуща Парола",
+	"Custom": "Персонализиран",
+	"Customize models for a specific purpose": "Персонализиране на модели за конкретна цел",
+	"Dark": "Тъмен",
+	"Dashboard": "",
+	"Database": "База данни",
+	"December": "Декември",
+	"Default": "По подразбиране",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "По подразбиране (SentenceTransformers)",
+	"Default Model": "Модел по подразбиране",
+	"Default model updated": "Моделът по подразбиране е обновен",
+	"Default Prompt Suggestions": "Промпт Предложения по подразбиране",
+	"Default User Role": "Роля на потребителя по подразбиране",
+	"Delete": "Изтриване",
+	"Delete a model": "Изтриване на модел",
+	"Delete All Chats": "Изтриване на всички чатове",
+	"Delete chat": "Изтриване на чат",
+	"Delete Chat": "Изтриване на Чат",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "Изтриване на този линк",
+	"Delete tool?": "",
+	"Delete User": "Изтриване на потребител",
+	"Deleted {{deleteModelTag}}": "Изтрито {{deleteModelTag}}",
+	"Deleted {{name}}": "Изтрито {{име}}",
+	"Description": "Описание",
+	"Didn't fully follow instructions": "Не следва инструкциите",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "Открийте модел",
+	"Discover a prompt": "Откриване на промпт",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Откриване, сваляне и преглед на персонализирани промптове",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Откриване, сваляне и преглед на пресетове на модели",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "Показване на потребителското име вместо Вие в чата",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Документ",
+	"Documentation": "",
+	"Documents": "Документи",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "няма външни връзки, и вашите данни остават сигурни на локално назначен сървър.",
+	"Don't have an account?": "Нямате акаунт?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "Не харесваш стила?",
+	"Done": "",
+	"Download": "Изтегляне отменено",
+	"Download canceled": "Изтегляне отменено",
+	"Download Database": "Сваляне на база данни",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Пускане на файлове тук, за да ги добавите в чата",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "напр. '30с','10м'. Валидни единици са 'с', 'м', 'ч'.",
+	"Edit": "Редактиране",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "Редактиране на потребител",
+	"ElevenLabs": "",
+	"Email": "Имейл",
+	"Embedding Batch Size": "",
+	"Embedding Model": "Модел за вграждане",
+	"Embedding Model Engine": "Модел за вграждане",
+	"Embedding model set to \"{{embedding_model}}\"": "Модел за вграждане е настроен на \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Разрешаване на споделяне в общност",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Вклюване на Нови Потребители",
+	"Enable Web Search": "Разрешаване на търсене в уеб",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Уверете се, че вашият CSV файл включва 4 колони в следния ред: Име, Имейл, Парола, Роля.",
+	"Enter {{role}} message here": "Въведете съобщение за {{role}} тук",
+	"Enter a detail about yourself for your LLMs to recall": "Въведете подробности за себе си, за да се herinnerат вашите LLMs",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Въведете Brave Search API ключ",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Въведете Chunk Overlap",
+	"Enter Chunk Size": "Въведете Chunk Size",
+	"Enter description": "",
+	"Enter Github Raw URL": "Въведете URL адреса на Github Raw",
+	"Enter Google PSE API Key": "Въведете Google PSE API ключ",
+	"Enter Google PSE Engine Id": "Въведете идентификатор на двигателя на Google PSE",
+	"Enter Image Size (e.g. 512x512)": "Въведете размер на изображението (напр. 512x512)",
+	"Enter language codes": "Въведете кодове на езика",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Въведете таг на модел (напр. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Въведете брой стъпки (напр. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Въведете оценка",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Въведете URL адреса на заявката на Searxng",
+	"Enter Serper API Key": "Въведете Serper API ключ",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "Въведете Serpstack API ключ",
+	"Enter stop sequence": "Въведете стоп последователност",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Въведете Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Въведете URL (напр. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Въведете URL (напр. http://localhost:11434)",
+	"Enter Your Email": "Въведете имейл",
+	"Enter Your Full Name": "Въведете вашето пълно име",
+	"Enter your message": "",
+	"Enter Your Password": "Въведете вашата парола",
+	"Enter Your Role": "Въведете вашата роля",
+	"Error": "Грешка",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Експериментално",
+	"Export": "Износ",
+	"Export All Chats (All Users)": "Експортване на всички чатове (За всички потребители)",
+	"Export chat (.json)": "",
+	"Export Chats": "Експортване на чатове",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "Експортиране на модели",
+	"Export Prompts": "Експортване на промптове",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Неуспешно създаване на API ключ.",
+	"Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "Февруари",
+	"Feedback History": "",
+	"Feel free to add specific details": "Feel free to add specific details",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Файл Мод",
+	"File not found.": "Файл не е намерен.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Потвърждаване на отпечатък: Не може да се използва инициализационна буква като аватар. Потребителят се връща към стандартна аватарка.",
+	"Fluidly stream large external response chunks": "Плавно предаване на големи части от външен отговор",
+	"Focus chat input": "Фокусиране на чат вход",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Следвайте инструкциите перфектно",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Наказание за честота",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Основни",
+	"General Settings": "Основни Настройки",
+	"Generate Image": "",
+	"Generating search query": "Генериране на заявка за търсене",
+	"Generation Info": "Информация за Генерация",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "Добра отговор",
+	"Google PSE API Key": "Google PSE API ключ",
+	"Google PSE Engine Id": "Идентификатор на двигателя на Google PSE",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "няма разговори.",
+	"Hello, {{name}}": "Здравей, {{name}}",
+	"Help": "Помощ",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Скрий",
+	"Hide Model": "",
+	"How can I help you today?": "Как мога да ви помогна днес?",
+	"Hybrid Search": "Hybrid Search",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Генерация на изображения (Експериментално)",
+	"Image Generation Engine": "Двигател за генериране на изображения",
+	"Image Settings": "Настройки на изображения",
+	"Images": "Изображения",
+	"Import Chats": "Импортване на чатове",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "Импортиране на модели",
+	"Import Prompts": "Импортване на промптове",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Включете флага `--api`, когато стартирате stable-diffusion-webui",
+	"Info": "Информация",
+	"Input commands": "Въведете команди",
+	"Install from Github URL": "Инсталиране от URL адреса на Github",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "Интерфейс",
+	"Invalid file format.": "",
+	"Invalid Tag": "Невалиден тег",
+	"January": "Януари",
+	"join our Discord for help.": "свържете се с нашия Discord за помощ.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON Преглед",
+	"July": "Июл",
+	"June": "Июн",
+	"JWT Expiration": "JWT Expiration",
+	"JWT Token": "JWT Token",
+	"Keep Alive": "Keep Alive",
+	"Keyboard shortcuts": "Клавиши за бърз достъп",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Език",
+	"large language models, locally.": "",
+	"Last Active": "Последни активни",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Светъл",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "LLMs могат да правят грешки. Проверете важните данни.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Направено от OpenWebUI общността",
+	"Make sure to enclose them with": "Уверете се, че са заключени с",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "Управление на Моделите",
+	"Manage Ollama Models": "Управление на Ollama Моделите",
+	"Manage Pipelines": "Управление на тръбопроводи",
+	"March": "Март",
+	"Max Tokens (num_predict)": "Макс токени (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 модели могат да бъдат сваляни едновременно. Моля, опитайте отново по-късно.",
+	"May": "Май",
+	"Memories accessible by LLMs will be shown here.": "Мемории достъпни от LLMs ще бъдат показани тук.",
+	"Memory": "Мемория",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Съобщенията, които изпращате след създаването на връзката, няма да бъдат споделяни. Потребителите с URL адреса ще могат да видят споделения чат.",
+	"Min P": "",
+	"Minimum Score": "Минимална оценка",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Моделът '{{modelName}}' беше успешно свален.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Моделът '{{modelTag}}' е вече в очакване за сваляне.",
+	"Model {{modelId}} not found": "Моделът {{modelId}} не е намерен",
+	"Model {{modelName}} is not vision capable": "Моделът {{modelName}} не може да се вижда",
+	"Model {{name}} is now {{status}}": "Моделът {{name}} сега е {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Открит е път до файловата система на модела. За актуализацията се изисква съкратено име на модела, не може да продължи.",
+	"Model ID": "ИД на модел",
+	"Model Name": "",
+	"Model not selected": "Не е избран модел",
+	"Model Params": "Модел Params",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Модел Whitelisting",
+	"Model(s) Whitelisted": "Модели Whitelisted",
+	"Modelfile Content": "Съдържание на модфайл",
+	"Models": "Модели",
+	"more": "",
+	"More": "Повече",
+	"Move to Top": "",
+	"Name": "Име",
+	"Name your model": "Дайте име на вашия модел",
+	"New Chat": "Нов чат",
+	"New folder": "",
+	"New Password": "Нова парола",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Няма намерени резултати",
+	"No search query generated": "Не е генерирана заявка за търсене",
+	"No source available": "Няма наличен източник",
+	"No valves to update": "",
+	"None": "Никой",
+	"Not factually correct": "Не е фактологически правилно",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Забележка: Ако зададете минимален резултат, търсенето ще върне само документи с резултат, по-голям или равен на минималния резултат.",
+	"Notes": "",
+	"Notifications": "Десктоп Известия",
+	"November": "Ноември",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "",
+	"October": "Октомври",
+	"Off": "Изкл.",
+	"Okay, Let's Go!": "ОК, Нека започваме!",
+	"OLED Dark": "OLED тъмно",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API деактивиран",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Ollama Версия",
+	"On": "Вкл.",
+	"Only": "Само",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Само алфанумерични знаци и тире са разрешени в командния низ.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Изглежда URL адресът е невалиден. Моля, проверете отново и опитайте пак.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Използвате неподдържан метод (само фронтенд). Моля, сервирайте WebUI от бекенда.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Отвори нов чат",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API Config",
+	"OpenAI API Key is required.": "OpenAI API ключ е задължителен.",
+	"OpenAI URL/Key required.": "OpenAI URL/Key е задължителен.",
+	"or": "или",
+	"Other": "Other",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Парола",
+	"PDF document (.pdf)": "PDF документ (.pdf)",
+	"PDF Extract Images (OCR)": "PDF Extract Images (OCR)",
+	"pending": "в очакване",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "Permission denied when accessing microphone: {{error}}",
+	"Personalization": "Персонализация",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "Тръбопроводи",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "Тръбопроводи Вентили",
+	"Plain text (.txt)": "Plain text (.txt)",
+	"Playground": "Плейграунд",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Позитивна ативност",
+	"Previous 30 days": "Предыдущите 30 дни",
+	"Previous 7 days": "Предыдущите 7 дни",
+	"Profile Image": "Профилна снимка",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Промпт (напр. Обмисли ме забавна факт за Римската империя)",
+	"Prompt Content": "Съдържание на промпта",
+	"Prompt suggestions": "Промпт предложения",
+	"Prompts": "Промптове",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Извади \"{{searchValue}}\" от Ollama.com",
+	"Pull a model from Ollama.com": "Издърпайте модел от Ollama.com",
+	"Query Params": "Query Параметри",
+	"RAG Template": "RAG Шаблон",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Прочети на Голос",
+	"Record voice": "Записване на глас",
+	"Redirecting you to OpenWebUI Community": "Пренасочване към OpenWebUI общността",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "Отказано, когато не трябва да бъде",
+	"Regenerate": "Регенериране",
+	"Release Notes": "Бележки по изданието",
+	"Relevance": "",
+	"Remove": "Изтриване",
+	"Remove Model": "Изтриване на модела",
+	"Rename": "Преименуване",
+	"Repeat Last N": "Repeat Last N",
+	"Request Mode": "Request Mode",
+	"Reranking Model": "Reranking Model",
+	"Reranking model disabled": "Reranking model disabled",
+	"Reranking model set to \"{{reranking_model}}\"": "Reranking model set to \"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Аувтоматично копиране на отговор в клипборда",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Роля",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "Запис",
+	"Save & Create": "Запис & Създаване",
+	"Save & Update": "Запис & Актуализиране",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Запазването на чат логове директно в хранилището на вашия браузър вече не се поддържа. Моля, отделете малко време, за да изтеглите и изтриете чат логовете си, като щракнете върху бутона по-долу. Не се притеснявайте, можете лесно да импортирате отново чат логовете си в бекенда чрез",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Търси",
+	"Search a model": "Търси модел",
+	"Search Chats": "Търсене на чатове",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "Търсене на модели",
+	"Search Prompts": "Търси Промптове",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "Брой резултати от търсенето",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Търси се в {{count}} sites_one",
+	"Searched {{count}} sites_other": "Търси се в {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "URL адрес на заявка на Searxng",
+	"See readme.md for instructions": "Виж readme.md за инструкции",
+	"See what's new": "Виж какво е новото",
+	"Seed": "Seed",
+	"Select a base model": "Изберете базов модел",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "Изберете модел",
+	"Select a pipeline": "Изберете тръбопровод",
+	"Select a pipeline url": "Избор на URL адрес на канал",
+	"Select a tool": "",
+	"Select an Ollama instance": "Изберете Ollama инстанция",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Изберете модел",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "Избраният(те) модел(и) не поддържа въвеждане на изображения",
+	"Semantic distance to query": "",
+	"Send": "Изпрати",
+	"Send a Message": "Изпращане на Съобщение",
+	"Send message": "Изпращане на съобщение",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "Септември",
+	"Serper API Key": "Serper API ключ",
+	"Serply API Key": "",
+	"Serpstack API Key": "Serpstack API ключ",
+	"Server connection verified": "Server connection verified",
+	"Set as default": "Задай по подразбиране",
+	"Set CFG Scale": "",
+	"Set Default Model": "Задай Модел По Подразбиране",
+	"Set embedding model (e.g. {{model}})": "Задай embedding model (e.g. {{model}})",
+	"Set Image Size": "Задай Размер на Изображението",
+	"Set reranking model (e.g. {{model}})": "Задай reranking model (e.g. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Задай Стъпки",
+	"Set Task Model": "Задаване на модел на задача",
+	"Set Voice": "Задай Глас",
+	"Set whisper model": "",
+	"Settings": "Настройки",
+	"Settings saved successfully!": "Настройките са запазени успешно!",
+	"Share": "Подели",
+	"Share Chat": "Подели Чат",
+	"Share to OpenWebUI Community": "Споделите с OpenWebUI Общността",
+	"short-summary": "short-summary",
+	"Show": "Покажи",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "Покажи",
+	"Show your support!": "",
+	"Showcased creativity": "Показана креативност",
+	"Sign in": "Вписване",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Изход",
+	"Sign up": "Регистрация",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Източник",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Speech recognition error: {{error}}",
+	"Speech-to-Text Engine": "Speech-to-Text Engine",
+	"Stop": "",
+	"Stop Sequence": "Stop Sequence",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "STT Настройки",
+	"Subtitle (e.g. about the Roman Empire)": "Подтитул (напр. за Римска империя)",
+	"Success": "Успех",
+	"Successfully updated.": "Успешно обновено.",
+	"Suggested": "Препоръчано",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "Система",
+	"System Instructions": "",
+	"System Prompt": "Системен Промпт",
+	"Tags": "Тагове",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "Повече информация:",
+	"Temperature": "Температура",
+	"Template": "Шаблон",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Text-to-Speech Engine",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Благодарим ви за вашия отзив!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "The score should be a value between 0.0 (0%) and 1.0 (100%).",
+	"Theme": "Тема",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Това гарантира, че ценните ви разговори се запазват сигурно във вашата бекенд база данни. Благодарим ви!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Това е подробно описание.",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Съвет: Актуализирайте няколко слота за променливи последователно, като натискате клавиша Tab в чат входа след всяка подмяна.",
+	"Title": "Заглавие",
+	"Title (e.g. Tell me a fun fact)": "Заглавие (напр. Моля, кажете ми нещо забавно)",
+	"Title Auto-Generation": "Автоматично Генериране на Заглавие",
+	"Title cannot be an empty string.": "Заглавието не може да бъде празно.",
+	"Title Generation Prompt": "Промпт за Генериране на Заглавие",
+	"To access the available model names for downloading,": "За да получите достъп до наличните имена на модели за изтегляне,",
+	"To access the GGUF models available for downloading,": "За да получите достъп до GGUF моделите, налични за изтегляне,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "към чат входа.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "днес",
+	"Toggle settings": "Toggle settings",
+	"Toggle sidebar": "Toggle sidebar",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Проблеми с достъпът до Ollama?",
+	"TTS Model": "",
+	"TTS Settings": "TTS Настройки",
+	"TTS Voice": "",
+	"Type": "Вид",
+	"Type Hugging Face Resolve (Download) URL": "Въведете Hugging Face Resolve (Download) URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "О, не! Възникна проблем при свързването с {{provider}}.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "Обнови и копирай връзка",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Обновяване на парола",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Качване на GGUF модел",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Качване на файлове",
+	"Upload Pipeline": "",
+	"Upload Progress": "Прогрес на качването",
+	"URL Mode": "URL Mode",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Използвайте Gravatar",
+	"Use Initials": "Използвайте Инициали",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "потребител",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "Права на потребителя",
+	"Users": "Потребители",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Използване",
+	"Valid time units:": "Валидни единици за време:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "променлива",
+	"variable to have them replaced with clipboard content.": "променливи да се заменят съдържанието от клипборд.",
+	"Version": "Версия",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "Предупреждение",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Предупреждение: Ако актуализирате или промените вашия модел за вграждане, трябва да повторите импортирането на всички документи.",
+	"Web": "Уеб",
+	"Web API": "",
+	"Web Loader Settings": "Настройки за зареждане на уеб",
+	"Web Search": "Търсене в уеб",
+	"Web Search Engine": "Уеб търсачка",
+	"Webhook URL": "Уебхук URL",
+	"WebUI Settings": "WebUI Настройки",
+	"WebUI will make requests to": "WebUI ще направи заявки към",
+	"What’s New in": "Какво е новото в",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "Работно пространство",
+	"Write a prompt suggestion (e.g. Who are you?)": "Напиши предложение за промпт (напр. Кой сте вие?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Напиши описание в 50 знака, което описва [тема или ключова дума].",
+	"Write something...": "",
+	"Yesterday": "вчера",
+	"You": "вие",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "Не можете да клонирате базов модел",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Нямате архивирани разговори.",
+	"You have shared this chat": "Вие сте споделели този чат",
+	"You're a helpful assistant.": "Вие сте полезен асистент.",
+	"You're now logged in.": "Сега, вие влязохте в системата.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube Loader Settings"
+}
diff --git a/src/lib/i18n/locales/bn-BD/translation.json b/src/lib/i18n/locales/bn-BD/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..86f3a9ab9a65d715952eb62dbc45d3435d4ea9fe
--- /dev/null
+++ b/src/lib/i18n/locales/bn-BD/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' অথবা অনির্দিষ্টকাল মেয়াদের জন্য '-1' ",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(যেমন `sh webui.sh --api`)",
+	"(latest)": "(সর্বশেষ)",
+	"{{ models }}": "{{ মডেল}}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner}}: আপনি একটি বেস মডেল মুছতে পারবেন না",
+	"{{user}}'s Chats": "{{user}}র চ্যাটস",
+	"{{webUIName}} Backend Required": "{{webUIName}} ব্যাকএন্ড আবশ্যক",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "চ্যাট এবং ওয়েব অনুসন্ধান প্রশ্নের জন্য শিরোনাম তৈরি করার মতো কাজগুলি সম্পাদন করার সময় একটি টাস্ক মডেল ব্যবহার করা হয়",
+	"a user": "একজন ব্যাবহারকারী",
+	"About": "সম্পর্কে",
+	"Account": "একাউন্ট",
+	"Account Activation Pending": "",
+	"Accurate information": "সঠিক তথ্য",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "যোগ করুন",
+	"Add a model id": "একটি মডেল ID যোগ করুন",
+	"Add a short description about what this model does": "এই মডেলটি কী করে সে সম্পর্কে একটি সংক্ষিপ্ত বিবরণ যুক্ত করুন",
+	"Add a short title for this prompt": "এই প্রম্পটের জন্য একটি সংক্ষিপ্ত টাইটেল যোগ করুন",
+	"Add a tag": "একটি ট্যাগ যোগ করুন",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "একটি কাস্টম প্রম্পট যোগ করুন",
+	"Add Files": "ফাইল যোগ করুন",
+	"Add Memory": "মেমোরি যোগ করুন",
+	"Add Model": "মডেল যোগ করুন",
+	"Add Tag": "",
+	"Add Tags": "ট্যাগ যোগ করুন",
+	"Add text content": "",
+	"Add User": "ইউজার যোগ করুন",
+	"Adjusting these settings will apply changes universally to all users.": "এই সেটিংগুলো পরিবর্তন করলে তা সব ইউজারের উপরেই প্রয়োগ করা হবে",
+	"admin": "এডমিন",
+	"Admin": "",
+	"Admin Panel": "এডমিন প্যানেল",
+	"Admin Settings": "এডমিন সেটিংস",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "এডভান্সড প্যারামিটার্স",
+	"Advanced Params": "অ্যাডভান্সড প্যারাম",
+	"All chats": "",
+	"All Documents": "সব ডকুমেন্ট",
+	"Allow Chat Deletion": "চ্যাট ডিলিট করতে দিন",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "ইংরেজি অক্ষর, সংখ্যা এবং হাইফেন",
+	"Already have an account?": "আগে থেকেই একাউন্ট আছে?",
+	"an assistant": "একটা এসিস্ট্যান্ট",
+	"and": "এবং",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "এবং একটি নতুন শেয়ারে লিংক তৈরি করুন.",
+	"API Base URL": "এপিআই বেজ ইউআরএল",
+	"API Key": "এপিআই কোড",
+	"API Key created.": "একটি এপিআই কোড তৈরি করা হয়েছে.",
+	"API keys": "এপিআই কোডস",
+	"April": "আপ্রিল",
+	"Archive": "আর্কাইভ",
+	"Archive All Chats": "আর্কাইভ করুন সকল চ্যাট",
+	"Archived Chats": "চ্যাট ইতিহাস সংরক্ষণাগার",
+	"are allowed - Activate this command by typing": "অনুমোদিত - কমান্ডটি চালু করার জন্য লিখুন",
+	"Are you sure?": "আপনি নিশ্চিত?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "ফাইল যুক্ত করুন",
+	"Attention to detail": "বিস্তারিত বিশেষতা",
+	"Audio": "অডিও",
+	"August": "আগস্ট",
+	"Auto-playback response": "রেসপন্স অটো-প্লেব্যাক",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 বেজ ইউআরএল",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 বেজ ইউআরএল আবশ্যক",
+	"Available list": "",
+	"available!": "উপলব্ধ!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "পেছনে",
+	"Bad Response": "খারাপ প্রতিক্রিয়া",
+	"Banners": "ব্যানার",
+	"Base Model (From)": "বেস মডেল (থেকে)",
+	"Batch Size (num_batch)": "",
+	"before": "পূর্ববর্তী",
+	"Being lazy": "অলস হওয়া",
+	"Brave Search API Key": "সাহসী অনুসন্ধান API কী",
+	"Bypass SSL verification for Websites": "ওয়েবসাইটের জন্য SSL যাচাই বাতিল করুন",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "বাতিল",
+	"Capabilities": "সক্ষমতা",
+	"Change Password": "পাসওয়ার্ড পরিবর্তন করুন",
+	"Character": "",
+	"Chat": "চ্যাট",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "চ্যাট বাবল UI",
+	"Chat Controls": "",
+	"Chat direction": "চ্যাট দিকনির্দেশ",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "চ্যাটসমূহ",
+	"Check Again": "আবার চেক করুন",
+	"Check for updates": "নতুন আপডেট আছে কিনা চেক করুন",
+	"Checking for updates...": "নতুন আপডেট আছে কিনা চেক করা হচ্ছে...",
+	"Choose a model before saving...": "সেভ করার আগে একটি মডেল নির্বাচন করুন",
+	"Chunk Overlap": "চাঙ্ক ওভারল্যাপ",
+	"Chunk Params": "চাঙ্ক প্যারামিটার্স",
+	"Chunk Size": "চাঙ্ক সাইজ",
+	"Citation": "উদ্ধৃতি",
+	"Clear memory": "",
+	"Click here for help.": "সাহায্যের জন্য এখানে ক্লিক করুন",
+	"Click here to": "এখানে ক্লিক করুন",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "নির্বাচন করার জন্য এখানে ক্লিক করুন",
+	"Click here to select a csv file.": "একটি csv ফাইল নির্বাচন করার জন্য এখানে ক্লিক করুন",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "এখানে ক্লিক করুন",
+	"Click on the user role button to change a user's role.": "ইউজারের পদবি পরিবর্তন করার জন্য ইউজারের পদবি বাটনে ক্লিক করুন",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "ক্লোন",
+	"Close": "বন্ধ",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "সংগ্রহ",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Base URL",
+	"ComfyUI Base URL is required.": "ComfyUI Base URL আবশ্যক।",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "কমান্ড",
+	"Completions": "",
+	"Concurrent Requests": "সমকালীন অনুরোধ",
+	"Confirm": "",
+	"Confirm Password": "পাসওয়ার্ড নিশ্চিত করুন",
+	"Confirm your action": "",
+	"Connections": "কানেকশনগুলো",
+	"Contact Admin for WebUI Access": "",
+	"Content": "বিষয়বস্তু",
+	"Content Extraction": "",
+	"Context Length": "কনটেক্সটের দৈর্ঘ্য",
+	"Continue Response": "যাচাই করুন",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "শেয়ারকৃত কথা-ব্যবহারের URL ক্লিপবোর্ডে কপি করা হয়েছে!",
+	"Copied to clipboard": "",
+	"Copy": "অনুলিপি",
+	"Copy last code block": "সর্বশেষ কোড ব্লক কপি করুন",
+	"Copy last response": "সর্বশেষ রেসপন্স কপি করুন",
+	"Copy Link": "লিংক কপি করুন",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "ক্লিপবোর্ডে কপি করা সফল হয়েছে",
+	"Create a model": "একটি মডেল তৈরি করুন",
+	"Create Account": "একাউন্ট তৈরি করুন",
+	"Create Knowledge": "",
+	"Create new key": "একটি নতুন কী তৈরি করুন",
+	"Create new secret key": "একটি নতুন সিক্রেট কী তৈরি করুন",
+	"Created at": "নির্মানকাল",
+	"Created At": "নির্মানকাল",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "বর্তমান মডেল",
+	"Current Password": "বর্তমান পাসওয়ার্ড",
+	"Custom": "কাস্টম",
+	"Customize models for a specific purpose": "একটি নির্দিষ্ট উদ্দেশ্যে মডেল কাস্টমাইজ করুন",
+	"Dark": "ডার্ক",
+	"Dashboard": "",
+	"Database": "ডেটাবেজ",
+	"December": "ডেসেম্বর",
+	"Default": "ডিফল্ট",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "ডিফল্ট (SentenceTransformers)",
+	"Default Model": "ডিফল্ট মডেল",
+	"Default model updated": "ডিফল্ট মডেল আপডেট হয়েছে",
+	"Default Prompt Suggestions": "ডিফল্ট প্রম্পট সাজেশন",
+	"Default User Role": "ইউজারের ডিফল্ট পদবি",
+	"Delete": "মুছে ফেলুন",
+	"Delete a model": "একটি মডেল মুছে ফেলুন",
+	"Delete All Chats": "সব চ্যাট মুছে ফেলুন",
+	"Delete chat": "চ্যাট মুছে ফেলুন",
+	"Delete Chat": "চ্যাট মুছে ফেলুন",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "এই লিংক মুছে ফেলুন",
+	"Delete tool?": "",
+	"Delete User": "ইউজার মুছে ফেলুন",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} মুছে ফেলা হয়েছে",
+	"Deleted {{name}}": "{{name}} মোছা হয়েছে",
+	"Description": "বিবরণ",
+	"Didn't fully follow instructions": "ইনস্ট্রাকশন সম্পূর্ণ অনুসরণ করা হয়নি",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "একটি মডেল আবিষ্কার করুন",
+	"Discover a prompt": "একটি প্রম্পট খুঁজে বের করুন",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "কাস্টম প্রম্পটগুলো আবিস্কার, ডাউনলোড এবং এক্সপ্লোর করুন",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "মডেল প্রিসেটগুলো আবিস্কার, ডাউনলোড এবং এক্সপ্লোর করুন",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "চ্যাটে 'আপনি'-র পরবর্তে ইউজারনেম দেখান",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "ডকুমেন্ট",
+	"Documentation": "",
+	"Documents": "ডকুমেন্টসমূহ",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "কোন এক্সটার্নাল কানেকশন তৈরি করে না, এবং আপনার ডেটা আর লোকালি হোস্টেড সার্ভারেই নিরাপদে থাকে।",
+	"Don't have an account?": "একাউন্ট নেই?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "স্টাইল পছন্দ করেন না",
+	"Done": "",
+	"Download": "ডাউনলোড",
+	"Download canceled": "ডাউনলোড বাতিল করা হয়েছে",
+	"Download Database": "ডেটাবেজ ডাউনলোড করুন",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "আলোচনায় যুক্ত করার জন্য যে কোন ফাইল এখানে ড্রপ করুন",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "যেমন '30s','10m'. সময়ের অনুমোদিত অনুমোদিত এককগুলি হচ্ছে 's', 'm', 'h'.",
+	"Edit": "এডিট করুন",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "ইউজার এডিট করুন",
+	"ElevenLabs": "",
+	"Email": "ইমেইল",
+	"Embedding Batch Size": "",
+	"Embedding Model": "ইমেজ ইমেবডিং মডেল",
+	"Embedding Model Engine": "ইমেজ ইমেবডিং মডেল ইঞ্জিন",
+	"Embedding model set to \"{{embedding_model}}\"": "ইমেজ ইমেবডিং মডেল সেট করা হয়েছে - \"{{embedding_model}}\"",
+	"Enable Community Sharing": "সম্প্রদায় শেয়ারকরণ সক্ষম করুন",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "নতুন সাইনআপ চালু করুন",
+	"Enable Web Search": "ওয়েব অনুসন্ধান সক্ষম করুন",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "আপনার সিএসভি ফাইলটিতে এই ক্রমে 4 টি কলাম অন্তর্ভুক্ত রয়েছে তা নিশ্চিত করুন: নাম, ইমেল, পাসওয়ার্ড, ভূমিকা।.",
+	"Enter {{role}} message here": "{{role}} মেসেজ এখানে লিখুন",
+	"Enter a detail about yourself for your LLMs to recall": "আপনার এলএলএমগুলি স্মরণ করার জন্য নিজের সম্পর্কে একটি বিশদ লিখুন",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "সাহসী অনুসন্ধান API কী লিখুন",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "চাঙ্ক ওভারল্যাপ লিখুন",
+	"Enter Chunk Size": "চাংক সাইজ লিখুন",
+	"Enter description": "",
+	"Enter Github Raw URL": "গিটহাব কাঁচা URL লিখুন",
+	"Enter Google PSE API Key": "গুগল পিএসই এপিআই কী লিখুন",
+	"Enter Google PSE Engine Id": "গুগল পিএসই ইঞ্জিন আইডি লিখুন",
+	"Enter Image Size (e.g. 512x512)": "ছবির মাপ লিখুন (যেমন 512x512)",
+	"Enter language codes": "ল্যাঙ্গুয়েজ কোড লিখুন",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "মডেল ট্যাগ লিখুন (e.g. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "ধাপের সংখ্যা দিন (যেমন: 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "স্কোর দিন",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Searxng ক্যোয়ারী URL লিখুন",
+	"Enter Serper API Key": "Serper API কী লিখুন",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "Serpstack API কী লিখুন",
+	"Enter stop sequence": "স্টপ সিকোয়েন্স লিখুন",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Top K লিখুন",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "ইউআরএল দিন (যেমন http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "ইউআরএল দিন (যেমন http://localhost:11434)",
+	"Enter Your Email": "আপনার ইমেইল লিখুন",
+	"Enter Your Full Name": "আপনার পূর্ণ নাম লিখুন",
+	"Enter your message": "",
+	"Enter Your Password": "আপনার পাসওয়ার্ড লিখুন",
+	"Enter Your Role": "আপনার রোল লিখুন",
+	"Error": "ত্রুটি",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "পরিক্ষামূলক",
+	"Export": "রপ্তানি",
+	"Export All Chats (All Users)": "সব চ্যাট এক্সপোর্ট করুন (সব ইউজারের)",
+	"Export chat (.json)": "",
+	"Export Chats": "চ্যাটগুলো এক্সপোর্ট করুন",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "রপ্তানি মডেল",
+	"Export Prompts": "প্রম্পটগুলো একপোর্ট করুন",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "API Key তৈরি করা যায়নি।",
+	"Failed to read clipboard contents": "ক্লিপবোর্ডের বিষয়বস্তু পড়া সম্ভব হয়নি",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "ফেব্রুয়ারি",
+	"Feedback History": "",
+	"Feel free to add specific details": "নির্দিষ্ট বিবরণ যোগ করতে বিনা দ্বিধায়",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "ফাইল মোড",
+	"File not found.": "ফাইল পাওয়া যায়নি",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "ফিঙ্গারপ্রিন্ট স্পুফিং ধরা পড়েছে: অ্যাভাটার হিসেবে নামের আদ্যক্ষর ব্যবহার করা যাচ্ছে না। ডিফল্ট প্রোফাইল পিকচারে ফিরিয়ে নেয়া হচ্ছে।",
+	"Fluidly stream large external response chunks": "বড় এক্সটার্নাল রেসপন্স চাঙ্কগুলো মসৃণভাবে প্রবাহিত করুন",
+	"Focus chat input": "চ্যাট ইনপুট ফোকাস করুন",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "নির্দেশাবলী নিখুঁতভাবে অনুসরণ করা হয়েছে",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "ফ্রিকোয়েন্সি পেনাল্টি",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "সাধারণ",
+	"General Settings": "সাধারণ সেটিংসমূহ",
+	"Generate Image": "",
+	"Generating search query": "অনুসন্ধান ক্যোয়ারী তৈরি করা হচ্ছে",
+	"Generation Info": "জেনারেশন ইনফো",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "ভালো সাড়া",
+	"Google PSE API Key": "গুগল পিএসই এপিআই কী",
+	"Google PSE Engine Id": "গুগল পিএসই ইঞ্জিন আইডি",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "কোন কনভার্সেশন আছে না।",
+	"Hello, {{name}}": "হ্যালো, {{name}}",
+	"Help": "সহায়তা",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "লুকান",
+	"Hide Model": "",
+	"How can I help you today?": "আপনাকে আজ কিভাবে সাহায্য করতে পারি?",
+	"Hybrid Search": "হাইব্রিড অনুসন্ধান",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "ইমেজ জেনারেশন (পরিক্ষামূলক)",
+	"Image Generation Engine": "ইমেজ জেনারেশন ইঞ্জিন",
+	"Image Settings": "ছবির সেটিংসমূহ",
+	"Images": "ছবিসমূহ",
+	"Import Chats": "চ্যাটগুলি ইমপোর্ট করুন",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "মডেল আমদানি করুন",
+	"Import Prompts": "প্রম্পটগুলো ইমপোর্ট করুন",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui চালু করার সময় `--api` ফ্ল্যাগ সংযুক্ত করুন",
+	"Info": "তথ্য",
+	"Input commands": "ইনপুট কমান্ডস",
+	"Install from Github URL": "Github URL থেকে ইনস্টল করুন",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "ইন্টারফেস",
+	"Invalid file format.": "",
+	"Invalid Tag": "অবৈধ ট্যাগ",
+	"January": "জানুয়ারী",
+	"join our Discord for help.": "সাহায্যের জন্য আমাদের Discord-এ যুক্ত হোন",
+	"JSON": "JSON",
+	"JSON Preview": "JSON প্রিভিউ",
+	"July": "জুলাই",
+	"June": "জুন",
+	"JWT Expiration": "JWT-র মেয়াদ",
+	"JWT Token": "JWT টোকেন",
+	"Keep Alive": "সচল রাখুন",
+	"Keyboard shortcuts": "কিবোর্ড শর্টকাটসমূহ",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "ভাষা",
+	"large language models, locally.": "",
+	"Last Active": "সর্বশেষ সক্রিয়",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "লাইট",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "LLM ভুল করতে পারে। গুরুত্বপূর্ণ তথ্য যাচাই করে নিন।",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "OpenWebUI কমিউনিটিকর্তৃক নির্মিত",
+	"Make sure to enclose them with": "এটা দিয়ে বন্ধনী দিতে ভুলবেন না",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "মডেলসমূহ ব্যবস্থাপনা করুন",
+	"Manage Ollama Models": "Ollama মডেলসূহ ব্যবস্থাপনা করুন",
+	"Manage Pipelines": "পাইপলাইন পরিচালনা করুন",
+	"March": "মার্চ",
+	"Max Tokens (num_predict)": "সর্বোচ্চ টোকেন (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "একসঙ্গে সর্বোচ্চ তিনটি মডেল ডাউনলোড করা যায়। দয়া করে পরে আবার চেষ্টা করুন।",
+	"May": "মে",
+	"Memories accessible by LLMs will be shown here.": "LLMs দ্বারা অ্যাক্সেসযোগ্য মেমোরিগুলি এখানে দেখানো হবে।",
+	"Memory": "মেমোরি",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "আপনার লিঙ্ক তৈরি করার পরে আপনার পাঠানো বার্তাগুলি শেয়ার করা হবে না। ইউআরএল ব্যবহারকারীরা শেয়ার করা চ্যাট দেখতে পারবেন।",
+	"Min P": "",
+	"Minimum Score": "Minimum Score",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' মডেল সফলভাবে ডাউনলোড হয়েছে।",
+	"Model '{{modelTag}}' is already in queue for downloading.": "{{modelTag}} ডাউনলোডের জন্য আগে থেকেই অপেক্ষমান আছে।",
+	"Model {{modelId}} not found": "{{modelId}} মডেল পাওয়া যায়নি",
+	"Model {{modelName}} is not vision capable": "মডেল {{modelName}} দৃষ্টি সক্ষম নয়",
+	"Model {{name}} is now {{status}}": "মডেল {{name}} এখন {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "মডেল ফাইলসিস্টেম পাথ পাওয়া গেছে। আপডেটের জন্য মডেলের শর্টনেম আবশ্যক, এগিয়ে যাওয়া যাচ্ছে না।",
+	"Model ID": "মডেল ID",
+	"Model Name": "",
+	"Model not selected": "মডেল নির্বাচন করা হয়নি",
+	"Model Params": "মডেল প্যারাম",
+	"Model updated successfully": "",
+	"Model Whitelisting": "মডেল হোয়াইটলিস্টিং",
+	"Model(s) Whitelisted": "হোয়াইটলিস্টেড মডেল(সমূহ)",
+	"Modelfile Content": "মডেলফাইল কনটেন্ট",
+	"Models": "মডেলসমূহ",
+	"more": "",
+	"More": "আরো",
+	"Move to Top": "",
+	"Name": "নাম",
+	"Name your model": "আপনার মডেলের নাম দিন",
+	"New Chat": "নতুন চ্যাট",
+	"New folder": "",
+	"New Password": "নতুন পাসওয়ার্ড",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "কোন ফলাফল পাওয়া যায়নি",
+	"No search query generated": "কোনও অনুসন্ধান ক্যোয়ারী উত্পন্ন হয়নি",
+	"No source available": "কোন উৎস পাওয়া যায়নি",
+	"No valves to update": "",
+	"None": "কোনোটিই নয়",
+	"Not factually correct": "তথ্যগত দিক থেকে সঠিক নয়",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "দ্রষ্টব্য: আপনি যদি ন্যূনতম স্কোর সেট করেন তবে অনুসন্ধানটি কেবলমাত্র ন্যূনতম স্কোরের চেয়ে বেশি বা সমান স্কোর সহ নথিগুলি ফেরত দেবে।",
+	"Notes": "",
+	"Notifications": "নোটিফিকেশনসমূহ",
+	"November": "নভেম্বর",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (ওলামা)",
+	"OAuth ID": "",
+	"October": "অক্টোবর",
+	"Off": "বন্ধ",
+	"Okay, Let's Go!": "ঠিক আছে, চলুন যাই!",
+	"OLED Dark": "OLED ডার্ক",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API নিষ্ক্রিয় করা হয়েছে",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Ollama ভার্সন",
+	"On": "চালু",
+	"Only": "শুধুমাত্র",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "কমান্ড স্ট্রিং-এ শুধুমাত্র ইংরেজি অক্ষর, সংখ্যা এবং হাইফেন ব্যবহার করা যাবে।",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "ওহ, মনে হচ্ছে ইউআরএলটা ইনভ্যালিড। দয়া করে আর চেক করে চেষ্টা করুন।",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "আপনি একটা আনসাপোর্টেড পদ্ধতি (শুধু ফ্রন্টএন্ড) ব্যবহার করছেন। দয়া করে WebUI ব্যাকএন্ড থেকে চালনা করুন।",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "নতুন চ্যাট খুলুন",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI এপিআই",
+	"OpenAI API Config": "OpenAI এপিআই কনফিগ",
+	"OpenAI API Key is required.": "OpenAI API কোড আবশ্যক",
+	"OpenAI URL/Key required.": "OpenAI URL/Key আবশ্যক",
+	"or": "অথবা",
+	"Other": "অন্যান্য",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "পাসওয়ার্ড",
+	"PDF document (.pdf)": "PDF ডকুমেন্ট (.pdf)",
+	"PDF Extract Images (OCR)": "পিডিএফ এর ছবি থেকে লেখা বের করুন (OCR)",
+	"pending": "অপেক্ষমান",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "মাইক্রোফোন ব্যবহারের অনুমতি পাওয়া যায়নি: {{error}}",
+	"Personalization": "ডিজিটাল বাংলা",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "পাইপলাইন",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "পাইপলাইন ভালভ",
+	"Plain text (.txt)": "প্লায়েন টেক্সট (.txt)",
+	"Playground": "খেলাঘর",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "পজিটিভ আক্রমণ",
+	"Previous 30 days": "পূর্ব ৩০ দিন",
+	"Previous 7 days": "পূর্ব ৭ দিন",
+	"Profile Image": "প্রোফাইল ইমেজ",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "প্রম্প্ট (উদাহরণস্বরূপ, আমি রোমান ইমপার্টের সম্পর্কে একটি উপস্থিতি জানতে বল)",
+	"Prompt Content": "প্রম্পট কন্টেন্ট",
+	"Prompt suggestions": "প্রম্পট সাজেশনসমূহ",
+	"Prompts": "প্রম্পটসমূহ",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com থেকে \"{{searchValue}}\" টানুন",
+	"Pull a model from Ollama.com": "Ollama.com থেকে একটি টেনে আনুন আনুন",
+	"Query Params": "Query প্যারামিটারসমূহ",
+	"RAG Template": "RAG টেম্পলেট",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "পড়াশোনা করুন",
+	"Record voice": "ভয়েস রেকর্ড করুন",
+	"Redirecting you to OpenWebUI Community": "আপনাকে OpenWebUI কমিউনিটিতে পাঠানো হচ্ছে",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "যদি উপযুক্ত নয়, তবে রেজিগেনেট করা হচ্ছে",
+	"Regenerate": "রেজিগেনেট করুন",
+	"Release Notes": "রিলিজ নোটসমূহ",
+	"Relevance": "",
+	"Remove": "রিমুভ করুন",
+	"Remove Model": "মডেল রিমুভ করুন",
+	"Rename": "রেনেম",
+	"Repeat Last N": "রিপিট Last N",
+	"Request Mode": "রিকোয়েস্ট মোড",
+	"Reranking Model": "রির্যাক্টিং মডেল",
+	"Reranking model disabled": "রির্যাক্টিং মডেল নিষ্ক্রিয় করা",
+	"Reranking model set to \"{{reranking_model}}\"": "রির ্যাঙ্কিং মডেল \"{{reranking_model}}\" -এ সেট করা আছে",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "রেসপন্সগুলো স্বয়ংক্রিভাবে ক্লিপবোর্ডে কপি হবে",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "পদবি",
+	"Rosé Pine": "রোজ পাইন",
+	"Rosé Pine Dawn": "ভোরের রোজ পাইন",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "সংরক্ষণ",
+	"Save & Create": "সংরক্ষণ এবং তৈরি করুন",
+	"Save & Update": "সংরক্ষণ এবং আপডেট করুন",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "মাধ্যমে",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "অনুসন্ধান",
+	"Search a model": "মডেল অনুসন্ধান করুন",
+	"Search Chats": "চ্যাট অনুসন্ধান করুন",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "অনুসন্ধান মডেল",
+	"Search Prompts": "প্রম্পটসমূহ অনুসন্ধান করুন",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "অনুসন্ধানের ফলাফল গণনা",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "{{কাউন্ট}} অনুসন্ধান করা হয়েছে sites_one",
+	"Searched {{count}} sites_other": "{{কাউন্ট}} অনুসন্ধান করা হয়েছে sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng ক্যোয়ারী URL",
+	"See readme.md for instructions": "নির্দেশিকার জন্য readme.md দেখুন",
+	"See what's new": "নতুন কী আছে দেখুন",
+	"Seed": "সীড",
+	"Select a base model": "একটি বেস মডেল নির্বাচন করুন",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "একটি মডেল নির্বাচন করুন",
+	"Select a pipeline": "একটি পাইপলাইন নির্বাচন করুন",
+	"Select a pipeline url": "একটি পাইপলাইন URL নির্বাচন করুন",
+	"Select a tool": "",
+	"Select an Ollama instance": "একটি Ollama ইন্সট্যান্স নির্বাচন করুন",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "মডেল নির্বাচন করুন",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "নির্বাচিত মডেল(গুলি) চিত্র ইনপুট সমর্থন করে না",
+	"Semantic distance to query": "",
+	"Send": "পাঠান",
+	"Send a Message": "একটি মেসেজ পাঠান",
+	"Send message": "মেসেজ পাঠান",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "সেপ্টেম্বর",
+	"Serper API Key": "Serper API Key",
+	"Serply API Key": "",
+	"Serpstack API Key": "Serpstack API Key",
+	"Server connection verified": "সার্ভার কানেকশন যাচাই করা হয়েছে",
+	"Set as default": "ডিফল্ট হিসেবে নির্ধারণ করুন",
+	"Set CFG Scale": "",
+	"Set Default Model": "ডিফল্ট মডেল নির্ধারণ করুন",
+	"Set embedding model (e.g. {{model}})": "ইমেম্বিং মডেল নির্ধারণ করুন (উদাহরণ {{model}})",
+	"Set Image Size": "ছবির সাইজ নির্ধারণ করুন",
+	"Set reranking model (e.g. {{model}})": "রি-র্যাংকিং মডেল নির্ধারণ করুন (উদাহরণ {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "পরবর্তী ধাপসমূহ",
+	"Set Task Model": "টাস্ক মডেল সেট করুন",
+	"Set Voice": "কন্ঠস্বর নির্ধারণ করুন",
+	"Set whisper model": "",
+	"Settings": "সেটিংসমূহ",
+	"Settings saved successfully!": "সেটিংগুলো সফলভাবে সংরক্ষিত হয়েছে",
+	"Share": "শেয়ার করুন",
+	"Share Chat": "চ্যাট শেয়ার করুন",
+	"Share to OpenWebUI Community": "OpenWebUI কমিউনিটিতে শেয়ার করুন",
+	"short-summary": "সংক্ষিপ্ত বিবরণ",
+	"Show": "দেখান",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "শর্টকাটগুলো দেখান",
+	"Show your support!": "",
+	"Showcased creativity": "সৃজনশীলতা প্রদর্শন",
+	"Sign in": "সাইন ইন",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "সাইন আউট",
+	"Sign up": "সাইন আপ",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "উৎস",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "স্পিচ রিকগনিশনে সমস্যা: {{error}}",
+	"Speech-to-Text Engine": "স্পিচ-টু-টেক্সট ইঞ্জিন",
+	"Stop": "",
+	"Stop Sequence": "সিকোয়েন্স থামান",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "STT সেটিংস",
+	"Subtitle (e.g. about the Roman Empire)": "সাবটাইটল (রোমান ইম্পার্টের সম্পর্কে)",
+	"Success": "সফল",
+	"Successfully updated.": "সফলভাবে আপডেট হয়েছে",
+	"Suggested": "প্রস্তাবিত",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "সিস্টেম",
+	"System Instructions": "",
+	"System Prompt": "সিস্টেম প্রম্পট",
+	"Tags": "ট্যাগসমূহ",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "আরও বলুন:",
+	"Temperature": "তাপমাত্রা",
+	"Template": "টেম্পলেট",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "টেক্সট-টু-স্পিচ ইঞ্জিন",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "আপনার মতামত ধন্যবাদ!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "স্কোর একটি 0.0 (0%) এবং 1.0 (100%) এর মধ্যে একটি মান হওয়া উচিত।",
+	"Theme": "থিম",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "এটা নিশ্চিত করে যে, আপনার গুরুত্বপূর্ণ আলোচনা নিরাপদে আপনার ব্যাকএন্ড ডেটাবেজে সংরক্ষিত আছে। ধন্যবাদ!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "পুঙ্খানুপুঙ্খ ব্যাখ্যা",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "পরামর্শ: একাধিক ভেরিয়েবল স্লট একের পর এক রিপ্লেস করার জন্য চ্যাট ইনপুটে কিবোর্ডের Tab বাটন ব্যবহার করুন।",
+	"Title": "শিরোনাম",
+	"Title (e.g. Tell me a fun fact)": "শিরোনাম (একটি উপস্থিতি বিবরণ জানান)",
+	"Title Auto-Generation": "স্বয়ংক্রিয় শিরোনামগঠন",
+	"Title cannot be an empty string.": "শিরোনাম অবশ্যই একটি পাশাপাশি শব্দ হতে হবে।",
+	"Title Generation Prompt": "শিরোনামগঠন প্রম্পট",
+	"To access the available model names for downloading,": "ডাউনলোডের জন্য এভেইলএবল মডেলের নামগুলো এক্সেস করতে,",
+	"To access the GGUF models available for downloading,": "ডাউলোডের জন্য এভেইলএবল GGUF মডেলগুলো এক্সেস করতে,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "চ্যাট ইনপুটে",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "আজ",
+	"Toggle settings": "সেটিংস টোগল",
+	"Toggle sidebar": "সাইডবার টোগল",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Ollama এক্সেস করতে সমস্যা হচ্ছে?",
+	"TTS Model": "",
+	"TTS Settings": "TTS সেটিংসমূহ",
+	"TTS Voice": "",
+	"Type": "টাইপ",
+	"Type Hugging Face Resolve (Download) URL": "Hugging Face থেকে ডাউনলোড করার ইউআরএল টাইপ করুন",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "ওহ-হো! {{provider}} এর সাথে কানেকশনে সমস্যা হয়েছে।",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "আপডেট এবং লিংক কপি করুন",
+	"Update for the latest features and improvements.": "",
+	"Update password": "পাসওয়ার্ড আপডেট করুন",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "একটি GGUF মডেল আপলোড করুন",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "ফাইল আপলোড করুন",
+	"Upload Pipeline": "",
+	"Upload Progress": "আপলোড হচ্ছে",
+	"URL Mode": "ইউআরএল মোড",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Gravatar ব্যবহার করুন",
+	"Use Initials": "নামের আদ্যক্ষর ব্যবহার করুন",
+	"use_mlock (Ollama)": "use_mlock (ওলামা)",
+	"use_mmap (Ollama)": "use_mmap (ওলামা)",
+	"user": "ব্যবহারকারী",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "ইউজার পারমিশনসমূহ",
+	"Users": "ব্যাবহারকারীগণ",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "ইউটিলাইজ",
+	"Valid time units:": "সময়ের গ্রহণযোগ্য এককসমূহ:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "ভেরিয়েবল",
+	"variable to have them replaced with clipboard content.": "ক্লিপবোর্ডের কন্টেন্ট দিয়ে যেই ভেরিয়েবল রিপ্লেস করা যাবে।",
+	"Version": "ভার্সন",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "সতর্কীকরণ",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "সতর্কীকরণ: আপনি যদি আপনার এম্বেডিং মডেল আপডেট বা পরিবর্তন করেন, তাহলে আপনাকে সমস্ত নথি পুনরায় আমদানি করতে হবে।.",
+	"Web": "ওয়েব",
+	"Web API": "",
+	"Web Loader Settings": "ওয়েব লোডার সেটিংস",
+	"Web Search": "ওয়েব অনুসন্ধান",
+	"Web Search Engine": "ওয়েব সার্চ ইঞ্জিন",
+	"Webhook URL": "ওয়েবহুক URL",
+	"WebUI Settings": "WebUI সেটিংসমূহ",
+	"WebUI will make requests to": "WebUI যেখানে রিকোয়েস্ট পাঠাবে",
+	"What’s New in": "এতে নতুন কী",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "ওয়ার্কস্পেস",
+	"Write a prompt suggestion (e.g. Who are you?)": "একটি প্রম্পট সাজেশন লিখুন (যেমন Who are you?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "৫০ শব্দের মধ্যে [topic or keyword] এর একটি সারসংক্ষেপ লিখুন।",
+	"Write something...": "",
+	"Yesterday": "আগামী",
+	"You": "আপনি",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "আপনি একটি বেস মডেল ক্লোন করতে পারবেন না",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "আপনার কোনও আর্কাইভ করা কথোপকথন নেই।",
+	"You have shared this chat": "আপনি এই চ্যাটটি শেয়ার করেছেন",
+	"You're a helpful assistant.": "আপনি একজন উপকারী এসিস্ট্যান্ট",
+	"You're now logged in.": "আপনি এখন লগইন করা অবস্থায় আছেন",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "YouTube",
+	"Youtube Loader Settings": "YouTube লোডার সেটিংস"
+}
diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d829b0d586e8547b4d7a6e72d20ec41c531c693
--- /dev/null
+++ b/src/lib/i18n/locales/ca-ES/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' o '-1' perquè no caduqui mai.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(p. ex. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(p. ex. `sh webui.sh --api`)",
+	"(latest)": "(últim)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: No es pot eliminar un model base",
+	"{{user}}'s Chats": "Els xats de {{user}}",
+	"{{webUIName}} Backend Required": "El Backend de {{webUIName}} és necessari",
+	"*Prompt node ID(s) are required for image generation": "*Els identificadors de nodes d'indicacions són necessaris per a la generació d'imatges",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "Hi ha una nova versió disponible (v{{LATEST_VERSION}}).",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un model de tasca s'utilitza quan es realitzen tasques com ara generar títols per a xats i consultes de cerca per a la web",
+	"a user": "un usuari",
+	"About": "Sobre",
+	"Account": "Compte",
+	"Account Activation Pending": "Activació del compte pendent",
+	"Accurate information": "Informació precisa",
+	"Actions": "Accions",
+	"Active Users": "Usuaris actius",
+	"Add": "Afegir",
+	"Add a model id": "Afegeix un identificador de model",
+	"Add a short description about what this model does": "Afegeix una breu descripció sobre què fa aquest model",
+	"Add a short title for this prompt": "Afegeix un títol curt per a aquesta indicació",
+	"Add a tag": "Afegir una etiqueta",
+	"Add Arena Model": "",
+	"Add Content": "Afegir contingut",
+	"Add content here": "Afegir contingut aquí",
+	"Add custom prompt": "Afegir una indicació personalitzada",
+	"Add Files": "Afegir arxius",
+	"Add Memory": "Afegir memòria",
+	"Add Model": "Afegir un model",
+	"Add Tag": "Afegir etiqueta",
+	"Add Tags": "Afegir etiquetes",
+	"Add text content": "Afegir contingut de text",
+	"Add User": "Afegir un usuari",
+	"Adjusting these settings will apply changes universally to all users.": "Si ajustes aquesta preferència, els canvis s'aplicaran de manera universal a tots els usuaris.",
+	"admin": "administrador",
+	"Admin": "Administrador",
+	"Admin Panel": "Panell d'administració",
+	"Admin Settings": "Preferències d'administració",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Els administradors tenen accés a totes les eines en tot moment; els usuaris necessiten eines assignades per model a l'espai de treball.",
+	"Advanced Parameters": "Paràmetres avançats",
+	"Advanced Params": "Paràmetres avançats",
+	"All chats": "Tots els xats",
+	"All Documents": "Tots els documents",
+	"Allow Chat Deletion": "Permetre la supressió del xat",
+	"Allow Chat Editing": "Permetre l'edició del xat",
+	"Allow non-local voices": "Permetre veus no locals",
+	"Allow Temporary Chat": "Permetre el xat temporal",
+	"Allow User Location": "Permetre la ubicació de l'usuari",
+	"Allow Voice Interruption in Call": "Permetre la interrupció de la veu en una trucada",
+	"alphanumeric characters and hyphens": "caràcters alfanumèrics i guions",
+	"Already have an account?": "Ja tens un compte?",
+	"an assistant": "un assistent",
+	"and": "i",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "i crear un nou enllaç compartit.",
+	"API Base URL": "URL Base de l'API",
+	"API Key": "clau API",
+	"API Key created.": "clau API creada.",
+	"API keys": "Claus de l'API",
+	"April": "Abril",
+	"Archive": "Arxiu",
+	"Archive All Chats": "Arxiva tots els xats",
+	"Archived Chats": "Xats arxivats",
+	"are allowed - Activate this command by typing": "estan permesos - Activa aquesta comanda escrivint",
+	"Are you sure?": "Estàs segur?",
+	"Arena Models": "",
+	"Artifacts": "Artefactes",
+	"Ask a question": "Fer una pregunta",
+	"Assistant": "",
+	"Attach file": "Adjuntar arxiu",
+	"Attention to detail": "Atenció al detall",
+	"Audio": "Àudio",
+	"August": "Agost",
+	"Auto-playback response": "Reproduir la resposta automàticament",
+	"Automatic1111": "Automatic1111",
+	"AUTOMATIC1111 Api Auth String": "Cadena d'autenticació de l'API d'AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL": "URL Base d'AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "Es requereix l'URL Base d'AUTOMATIC1111.",
+	"Available list": "Llista de disponibles",
+	"available!": "disponible!",
+	"Azure AI Speech": "Azure AI Speech",
+	"Azure Region": "Regió d'Azure",
+	"Back": "Enrere",
+	"Bad Response": "Resposta errònia",
+	"Banners": "Banners",
+	"Base Model (From)": "Model base (des de)",
+	"Batch Size (num_batch)": "Mida del lot (num_batch)",
+	"before": "abans",
+	"Being lazy": "Essent mandrós",
+	"Brave Search API Key": "Clau API de Brave Search",
+	"Bypass SSL verification for Websites": "Desactivar la verificació SSL per a l'accés a Internet",
+	"Call": "Trucada",
+	"Call feature is not supported when using Web STT engine": "La funció de trucada no s'admet quan s'utilitza el motor Web STT",
+	"Camera": "Càmera",
+	"Cancel": "Cancel·lar",
+	"Capabilities": "Capacitats",
+	"Change Password": "Canviar la contrasenya",
+	"Character": "Personatge",
+	"Chat": "Xat",
+	"Chat Background Image": "Imatge de fons del xat",
+	"Chat Bubble UI": "Chat Bubble UI",
+	"Chat Controls": "Controls de xat",
+	"Chat direction": "Direcció del xat",
+	"Chat Overview": "Vista general del xat",
+	"Chat Tags Auto-Generation": "Generació automàtica d'etiquetes del xat",
+	"Chats": "Xats",
+	"Check Again": "Comprovar-ho de nou",
+	"Check for updates": "Comprovar si hi ha actualitzacions",
+	"Checking for updates...": "Comprovant actualitzacions...",
+	"Choose a model before saving...": "Triar un model abans de desar...",
+	"Chunk Overlap": "Solapament de blocs",
+	"Chunk Params": "Paràmetres dels blocs",
+	"Chunk Size": "Mida del bloc",
+	"Citation": "Cita",
+	"Clear memory": "Esborrar la memòria",
+	"Click here for help.": "Clica aquí per obtenir ajuda.",
+	"Click here to": "Clic aquí per",
+	"Click here to download user import template file.": "Fes clic aquí per descarregar l'arxiu de plantilla d'importació d'usuaris",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Clica aquí per seleccionar",
+	"Click here to select a csv file.": "Clica aquí per seleccionar un fitxer csv.",
+	"Click here to select a py file.": "Clica aquí per seleccionar un fitxer py.",
+	"Click here to upload a workflow.json file.": "Clica aquí per pujar un arxiu workflow.json",
+	"click here.": "clica aquí.",
+	"Click on the user role button to change a user's role.": "Clica sobre el botó de rol d'usuari per canviar el rol d'un usuari.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Permís d'escriptura al porta-retalls denegat. Comprova els ajustos de navegador per donar l'accés necessari.",
+	"Clone": "Clonar",
+	"Close": "Tancar",
+	"Code execution": "Execució de codi",
+	"Code formatted successfully": "Codi formatat correctament",
+	"Collection": "Col·lecció",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL base de ComfyUI",
+	"ComfyUI Base URL is required.": "L'URL base de ComfyUI és obligatòria.",
+	"ComfyUI Workflow": "Flux de treball de ComfyUI",
+	"ComfyUI Workflow Nodes": "Nodes del flux de treball de ComfyUI",
+	"Command": "Comanda",
+	"Completions": "",
+	"Concurrent Requests": "Peticions simultànies",
+	"Confirm": "Confirmar",
+	"Confirm Password": "Confirmar la contrasenya",
+	"Confirm your action": "Confirma la teva acció",
+	"Connections": "Connexions",
+	"Contact Admin for WebUI Access": "Posat en contacte amb l'administrador per accedir a WebUI",
+	"Content": "Contingut",
+	"Content Extraction": "Extracció de contingut",
+	"Context Length": "Mida del context",
+	"Continue Response": "Continuar la resposta",
+	"Continue with {{provider}}": "Continuar amb {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "Controlar com es divideix el text del missatge per a les sol·licituds TTS. 'Puntuació' divideix en frases, 'paràgrafs' divideix en paràgrafs i 'cap' manté el missatge com una cadena única.",
+	"Controls": "Controls",
+	"Copied": "Copiat",
+	"Copied shared chat URL to clipboard!": "S'ha copiat l'URL compartida al porta-retalls!",
+	"Copied to clipboard": "Copiat al porta-retalls",
+	"Copy": "Copiar",
+	"Copy last code block": "Copiar l'últim bloc de codi",
+	"Copy last response": "Copiar l'última resposta",
+	"Copy Link": "Copiar l'enllaç",
+	"Copy to clipboard": "Copiar al porta-retalls",
+	"Copying to clipboard was successful!": "La còpia al porta-retalls s'ha realitzat correctament",
+	"Create a model": "Crear un model",
+	"Create Account": "Crear un compte",
+	"Create Knowledge": "Crear Coneixement",
+	"Create new key": "Crear una nova clau",
+	"Create new secret key": "Crear una nova clau secreta",
+	"Created at": "Creat el",
+	"Created At": "Creat el",
+	"Created by": "Creat per",
+	"CSV Import": "Importar CSV",
+	"Current Model": "Model actual",
+	"Current Password": "Contrasenya actual",
+	"Custom": "Personalitzat",
+	"Customize models for a specific purpose": "Personalitzar models per a un propòsit específic",
+	"Dark": "Fosc",
+	"Dashboard": "Tauler",
+	"Database": "Base de dades",
+	"December": "Desembre",
+	"Default": "Per defecte",
+	"Default (Open AI)": "Per defecte (Open AI)",
+	"Default (SentenceTransformers)": "Per defecte (SentenceTransformers)",
+	"Default Model": "Model per defecte",
+	"Default model updated": "Model per defecte actualitzat",
+	"Default Prompt Suggestions": "Suggeriments d'indicació per defecte",
+	"Default User Role": "Rol d'usuari per defecte",
+	"Delete": "Eliminar",
+	"Delete a model": "Eliminar un model",
+	"Delete All Chats": "Eliminar tots els xats",
+	"Delete chat": "Eliminar xat",
+	"Delete Chat": "Eliminar xat",
+	"Delete chat?": "Eliminar el xat?",
+	"Delete folder?": "Eliminar la carpeta?",
+	"Delete function?": "Eliminar funció?",
+	"Delete prompt?": "Eliminar indicació?",
+	"delete this link": "Eliminar aquest enllaç",
+	"Delete tool?": "Eliminar eina?",
+	"Delete User": "Eliminar usuari",
+	"Deleted {{deleteModelTag}}": "S'ha eliminat {{deleteModelTag}}",
+	"Deleted {{name}}": "S'ha eliminat {{name}}",
+	"Description": "Descripció",
+	"Didn't fully follow instructions": "No s'han seguit les instruccions completament",
+	"Disabled": "Deshabilitat",
+	"Discover a function": "Descobrir una funció",
+	"Discover a model": "Descobrir un model",
+	"Discover a prompt": "Descobrir una indicació",
+	"Discover a tool": "Descobrir una eina",
+	"Discover, download, and explore custom functions": "Descobrir, descarregar i explorar funcions personalitzades",
+	"Discover, download, and explore custom prompts": "Descobrir, descarregar i explorar indicacions personalitzades",
+	"Discover, download, and explore custom tools": "Descobrir, descarregar i explorar eines personalitzades",
+	"Discover, download, and explore model presets": "Descobrir, descarregar i explorar models preconfigurats",
+	"Dismissible": "Descartable",
+	"Display Emoji in Call": "Mostrar emojis a la trucada",
+	"Display the username instead of You in the Chat": "Mostrar el nom d'usuari en lloc de 'Tu' al xat",
+	"Do not install functions from sources you do not fully trust.": "No instal·lis funcions de fonts en què no confiïs plenament.",
+	"Do not install tools from sources you do not fully trust.": "No instal·lis eines de fonts en què no confiïs plenament.",
+	"Document": "Document",
+	"Documentation": "Documentació",
+	"Documents": "Documents",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "no realitza connexions externes, i les teves dades romanen segures al teu servidor allotjat localment.",
+	"Don't have an account?": "No tens un compte?",
+	"don't install random functions from sources you don't trust.": "no instal·lis funcions aleatòries de fonts en què no confiïs.",
+	"don't install random tools from sources you don't trust.": "no instal·lis eines aleatòries de fonts en què no confiïs.",
+	"Don't like the style": "No t'agrada l'estil?",
+	"Done": "Fet",
+	"Download": "Descarregar",
+	"Download canceled": "Descàrrega cancel·lada",
+	"Download Database": "Descarregar la base de dades",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Deixa qualsevol arxiu aquí per afegir-lo a la conversa",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "p. ex. '30s','10m'. Les unitats de temps vàlides són 's', 'm', 'h'.",
+	"Edit": "Editar",
+	"Edit Arena Model": "",
+	"Edit Memory": "Editar la memòria",
+	"Edit User": "Editar l'usuari",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "Correu electrònic",
+	"Embedding Batch Size": "Mida del lot d'incrustació",
+	"Embedding Model": "Model d'incrustació",
+	"Embedding Model Engine": "Motor de model d'incrustació",
+	"Embedding model set to \"{{embedding_model}}\"": "Model d'incrustació configurat a \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Activar l'ús compartit amb la comunitat",
+	"Enable Message Rating": "Permetre la qualificació de missatges",
+	"Enable New Sign Ups": "Permetre nous registres",
+	"Enable Web Search": "Activar la cerca web",
+	"Enable Web Search Query Generation": "Activa la generació de consultes de cerca web",
+	"Enabled": "Habilitat",
+	"Engine": "Motor",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Assegura't que els teus fitxers CSV inclouen 4 columnes en aquest ordre: Nom, Correu electrònic, Contrasenya, Rol.",
+	"Enter {{role}} message here": "Introdueix aquí el missatge de {{role}}",
+	"Enter a detail about yourself for your LLMs to recall": "Introdueix un detall sobre tu què els teus models de llenguatge puguin recordar",
+	"Enter api auth string (e.g. username:password)": "Entra la cadena d'autenticació api (p. ex. nom d'usuari:contrasenya)",
+	"Enter Brave Search API Key": "Introdueix la clau API de Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "Entra l'escala CFG (p.ex. 7.0)",
+	"Enter Chunk Overlap": "Introdueix la mida de solapament de blocs",
+	"Enter Chunk Size": "Introdueix la mida del bloc",
+	"Enter description": "",
+	"Enter Github Raw URL": "Introdueix l'URL en brut de Github",
+	"Enter Google PSE API Key": "Introdueix la clau API de Google PSE",
+	"Enter Google PSE Engine Id": "Introdueix l'identificador del motor PSE de Google",
+	"Enter Image Size (e.g. 512x512)": "Introdueix la mida de la imatge (p. ex. 512x512)",
+	"Enter language codes": "Introdueix els codis de llenguatge",
+	"Enter Model ID": "Introdueix l'identificador del model",
+	"Enter model tag (e.g. {{modelTag}})": "Introdueix l'etiqueta del model (p. ex. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Introdueix el nombre de passos (p. ex. 50)",
+	"Enter Sampler (e.g. Euler a)": "Introdueix el mostrejador (p.ex. Euler a)",
+	"Enter Scheduler (e.g. Karras)": "Entra el programador (p.ex. Karras)",
+	"Enter Score": "Introdueix la puntuació",
+	"Enter SearchApi API Key": "Introdueix la clau API SearchApi",
+	"Enter SearchApi Engine": "Introdueix el motor SearchApi",
+	"Enter Searxng Query URL": "Introdueix l'URL de consulta de Searxng",
+	"Enter Serper API Key": "Introdueix la clau API Serper",
+	"Enter Serply API Key": "Introdueix la clau API Serply",
+	"Enter Serpstack API Key": "Introdueix la clau API Serpstack",
+	"Enter stop sequence": "Introdueix la seqüència de parada",
+	"Enter system prompt": "Introdueix la indicació de sistema",
+	"Enter Tavily API Key": "Introdueix la clau API de Tavily",
+	"Enter Tika Server URL": "Introdueix l'URL del servidor Tika",
+	"Enter Top K": "Introdueix Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Introdueix l'URL (p. ex. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Introdueix l'URL (p. ex. http://localhost:11434)",
+	"Enter Your Email": "Introdueix el teu correu electrònic",
+	"Enter Your Full Name": "Introdueix el teu nom complet",
+	"Enter your message": "Introdueix el teu missatge",
+	"Enter Your Password": "Introdueix la teva contrasenya",
+	"Enter Your Role": "Introdueix el teu rol",
+	"Error": "Error",
+	"ERROR": "ERROR",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Experimental",
+	"Export": "Exportar",
+	"Export All Chats (All Users)": "Exportar tots els xats (Tots els usuaris)",
+	"Export chat (.json)": "Exportar el xat (.json)",
+	"Export Chats": "Exportar els xats",
+	"Export Config to JSON File": "Exportar la configuració a un arxiu JSON",
+	"Export Functions": "Exportar funcions",
+	"Export LiteLLM config.yaml": "Exportar la configuració LiteLLM config.yaml",
+	"Export Models": "Exportar els models",
+	"Export Prompts": "Exportar les indicacions",
+	"Export Tools": "Exportar les eines",
+	"External Models": "Models externs",
+	"Failed to add file.": "No s'ha pogut afegir l'arxiu.",
+	"Failed to create API Key.": "No s'ha pogut crear la clau API.",
+	"Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls",
+	"Failed to update settings": "No s'han pogut actualitzar les preferències",
+	"Failed to upload file.": "No s'ha pogut pujar l'arxiu.",
+	"February": "Febrer",
+	"Feedback History": "",
+	"Feel free to add specific details": "Sent-te lliure d'afegir detalls específics",
+	"File": "Arxiu",
+	"File added successfully.": "L'arxiu s'ha afegit correctament.",
+	"File content updated successfully.": "El contingut de l'arxiu s'ha actualitzat correctament.",
+	"File Mode": "Mode d'arxiu",
+	"File not found.": "No s'ha trobat l'arxiu.",
+	"File removed successfully.": "Arxiu eliminat correctament.",
+	"File size should not exceed {{maxSize}} MB.": "La mida del fitxer no ha de superar els {{maxSize}} MB.",
+	"Files": "Arxius",
+	"Filter is now globally disabled": "El filtre ha estat desactivat globalment",
+	"Filter is now globally enabled": "El filtre ha estat activat globalment",
+	"Filters": "Filtres",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "S'ha detectat la suplantació d'identitat de l'empremta digital: no es poden utilitzar les inicials com a avatar. S'estableix la imatge de perfil predeterminada.",
+	"Fluidly stream large external response chunks": "Transmetre amb fluïdesa grans trossos de resposta externa",
+	"Focus chat input": "Estableix el focus a l'entrada del xat",
+	"Folder deleted successfully": "Carpeta eliminada correctament",
+	"Folder name cannot be empty": "El nom de la carpeta no pot ser buit",
+	"Folder name cannot be empty.": "El nom de la carpeta no pot ser buit.",
+	"Folder name updated successfully": "Nom de la carpeta actualitzat correctament",
+	"Followed instructions perfectly": "S'han seguit les instruccions perfectament",
+	"Form": "Formulari",
+	"Format your variables using brackets like this:": "Formata les teves variables utilitzant claudàtors així:",
+	"Frequency Penalty": "Penalització per freqüència",
+	"Function": "Funció",
+	"Function created successfully": "La funció s'ha creat correctament",
+	"Function deleted successfully": "La funció s'ha eliminat correctament",
+	"Function Description (e.g. A filter to remove profanity from text)": "Descripció de la funció (per exemple, un filtre per eliminar paraules malsonants del text)",
+	"Function ID (e.g. my_filter)": "ID de la funció (per exemple, el_meu_filtre)",
+	"Function is now globally disabled": "La funció ha estat desactivada globalment",
+	"Function is now globally enabled": "La funció ha estat activada globalment",
+	"Function Name (e.g. My Filter)": "Nom de la funció (per exemple, El Meu Filtre)",
+	"Function updated successfully": "La funció s'ha actualitzat correctament",
+	"Functions": "Funcions",
+	"Functions allow arbitrary code execution": "Les funcions permeten l'execució de codi arbitrari",
+	"Functions allow arbitrary code execution.": "Les funcions permeten l'execució de codi arbitrari.",
+	"Functions imported successfully": "Les funcions s'han importat correctament",
+	"General": "General",
+	"General Settings": "Preferències generals",
+	"Generate Image": "Generar imatge",
+	"Generating search query": "Generant consulta",
+	"Generation Info": "Informació sobre la generació",
+	"Get up and running with": "Posa't en marxa amb",
+	"Global": "Global",
+	"Good Response": "Bona resposta",
+	"Google PSE API Key": "Clau API PSE de Google",
+	"Google PSE Engine Id": "Identificador del motor PSE de Google",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "Retorn hàptic",
+	"has no conversations.": "no té converses.",
+	"Hello, {{name}}": "Hola, {{name}}",
+	"Help": "Ajuda",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Amaga",
+	"Hide Model": "Amagar el model",
+	"How can I help you today?": "Com et puc ajudar avui?",
+	"Hybrid Search": "Cerca híbrida",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Afirmo que he llegit i entenc les implicacions de la meva acció. Soc conscient dels riscos associats a l'execució de codi arbitrari i he verificat la fiabilitat de la font.",
+	"ID": "",
+	"Image Generation (Experimental)": "Generació d'imatges (Experimental)",
+	"Image Generation Engine": "Motor de generació d'imatges",
+	"Image Settings": "Preferències d'imatges",
+	"Images": "Imatges",
+	"Import Chats": "Importar xats",
+	"Import Config from JSON File": "Importar la configuració des d'un arxiu JSON",
+	"Import Functions": "Importar funcions",
+	"Import Models": "Importar models",
+	"Import Prompts": "Importar indicacions",
+	"Import Tools": "Importar eines",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Inclou `--api-auth` quan executis stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Inclou `--api` quan executis stable-diffusion-webui",
+	"Info": "Informació",
+	"Input commands": "Entra comandes",
+	"Install from Github URL": "Instal·lar des de l'URL de Github",
+	"Instant Auto-Send After Voice Transcription": "Enviament automàtic després de la transcripció de veu",
+	"Interface": "Interfície",
+	"Invalid file format.": "Format d'arxiu no vàlid.",
+	"Invalid Tag": "Etiqueta no vàlida",
+	"January": "Gener",
+	"join our Discord for help.": "uneix-te al nostre Discord per obtenir ajuda.",
+	"JSON": "JSON",
+	"JSON Preview": "Vista prèvia del document JSON",
+	"July": "Juliol",
+	"June": "Juny",
+	"JWT Expiration": "Caducitat del JWT",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Manté actiu",
+	"Keyboard shortcuts": "Dreceres de teclat",
+	"Knowledge": "Coneixement",
+	"Knowledge created successfully.": "Coneixement creat correctament.",
+	"Knowledge deleted successfully.": "Coneixement eliminat correctament.",
+	"Knowledge reset successfully.": "Coneixement restablert correctament.",
+	"Knowledge updated successfully": "Coneixement actualitzat correctament.",
+	"Landing Page Mode": "Mode de la pàgina d'entrada",
+	"Language": "Idioma",
+	"large language models, locally.": "models de llenguatge extensos, localment",
+	"Last Active": "Activitat recent",
+	"Last Modified": "Modificació",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "Deixar-ho buit per il·limitat",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "Deixa-ho en blanc per utilitzar la indicació predeterminada o introdueix una indicació personalitzada",
+	"Light": "Clar",
+	"Listening...": "Escoltant...",
+	"LLMs can make mistakes. Verify important information.": "Els models de llenguatge poden cometre errors. Verifica la informació important.",
+	"Local Models": "Models locals",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Creat per la Comunitat OpenWebUI",
+	"Make sure to enclose them with": "Assegura't d'envoltar-los amb",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "Assegura't d'exportar un fitxer workflow.json com a format API des de ComfyUI.",
+	"Manage": "Gestionar",
+	"Manage Arena Models": "",
+	"Manage Models": "Gestionar els models",
+	"Manage Ollama Models": "Gestionar els models Ollama",
+	"Manage Pipelines": "Gestionar les Pipelines",
+	"March": "Març",
+	"Max Tokens (num_predict)": "Nombre màxim de Tokens (num_predict)",
+	"Max Upload Count": "Nombre màxim de càrregues",
+	"Max Upload Size": "Mida màxima de càrrega",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es poden descarregar un màxim de 3 models simultàniament. Si us plau, prova-ho més tard.",
+	"May": "Maig",
+	"Memories accessible by LLMs will be shown here.": "Les memòries accessibles pels models de llenguatge es mostraran aquí.",
+	"Memory": "Memòria",
+	"Memory added successfully": "Memòria afegida correctament",
+	"Memory cleared successfully": "Memòria eliminada correctament",
+	"Memory deleted successfully": "Memòria eliminada correctament",
+	"Memory updated successfully": "Memòria actualitzada correctament",
+	"Merge Responses": "Fusionar les respostes",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Els missatges enviats després de crear el teu enllaç no es compartiran. Els usuaris amb l'URL podran veure el xat compartit.",
+	"Min P": "Min P",
+	"Minimum Score": "Puntuació mínima",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Eta de Mirostat",
+	"Mirostat Tau": "Tau de Mirostat",
+	"MMMM DD, YYYY": "DD de MMMM, YYYY",
+	"MMMM DD, YYYY HH:mm": "DD de MMMM, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "DD de MMMM, YYYY HH:mm:ss, A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "El model '{{modelName}}' s'ha descarregat correctament.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "El model '{{modelTag}}' ja està en cua per ser descarregat.",
+	"Model {{modelId}} not found": "No s'ha trobat el model {{modelId}}",
+	"Model {{modelName}} is not vision capable": "El model {{modelName}} no és capaç de visió",
+	"Model {{name}} is now {{status}}": "El model {{name}} ara és {{status}}",
+	"Model {{name}} is now at the top": "El model {{name}} està ara a dalt de tot",
+	"Model accepts image inputs": "El model accepta entrades d'imatge",
+	"Model created successfully!": "Model creat correctament",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "S'ha detectat el camí del sistema de fitxers del model. És necessari un nom curt del model per actualitzar, no es pot continuar.",
+	"Model ID": "Identificador del model",
+	"Model Name": "",
+	"Model not selected": "Model no seleccionat",
+	"Model Params": "Paràmetres del model",
+	"Model updated successfully": "Model actualitzat correctament",
+	"Model Whitelisting": "Llista blanca de models",
+	"Model(s) Whitelisted": "Model(s) a la llista blanca",
+	"Modelfile Content": "Contingut del Modelfile",
+	"Models": "Models",
+	"more": "més",
+	"More": "Més",
+	"Move to Top": "Moure a dalt de tot",
+	"Name": "Nom",
+	"Name your model": "Posa un nom al teu model",
+	"New Chat": "Nou xat",
+	"New folder": "Nova carpeta",
+	"New Password": "Nova contrasenya",
+	"No content found": "No s'ha trobat contingut",
+	"No content to speak": "No hi ha contingut per parlar",
+	"No distance available": "No hi ha distància disponible",
+	"No feedbacks found": "",
+	"No file selected": "No s'ha escollit cap fitxer",
+	"No files found.": "No s'han trobat arxius.",
+	"No HTML, CSS, or JavaScript content found.": "No s'ha trobat contingut HTML, CSS o JavaScript.",
+	"No knowledge found": "No s'ha trobat Coneixement",
+	"No models found": "",
+	"No results found": "No s'han trobat resultats",
+	"No search query generated": "No s'ha generat cap consulta",
+	"No source available": "Sense font disponible",
+	"No valves to update": "No hi ha cap Valve per actualitzar",
+	"None": "Cap",
+	"Not factually correct": "No és clarament correcte",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Si s'estableix una puntuació mínima, la cerca només retornarà documents amb una puntuació major o igual a la puntuació mínima.",
+	"Notes": "",
+	"Notifications": "Notificacions",
+	"November": "Novembre",
+	"num_gpu (Ollama)": "num_gpu (Ollama)",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "ID OAuth",
+	"October": "Octubre",
+	"Off": "Desactivat",
+	"Okay, Let's Go!": "D'acord, som-hi!",
+	"OLED Dark": "OLED Fosc",
+	"Ollama": "Ollama",
+	"Ollama API": "API d'Ollama",
+	"Ollama API disabled": "API d'Ollama desactivada",
+	"Ollama API is disabled": "L'API d'Ollama està desactivada",
+	"Ollama Version": "Versió d'Ollama",
+	"On": "Activat",
+	"Only": "Només",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Només es permeten caràcters alfanumèrics i guions en la comanda.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Només es poden editar col·leccions, crea una nova base de coneixement per editar/afegir documents.",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ui! Sembla que l'URL no és vàlida. Si us plau, revisa-la i torna-ho a provar.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ui! Estàs utilitzant un mètode no suportat (només frontend). Si us plau, serveix la WebUI des del backend.",
+	"Open file": "Obrir arxiu",
+	"Open in full screen": "Obrir en pantalla complerta",
+	"Open new chat": "Obre un xat nou",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "La versió d'Open WebUI (v{{OPEN_WEBUI_VERSION}}) és inferior a la versió requerida (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API d'OpenAI",
+	"OpenAI API Config": "Configuració de l'API d'OpenAI",
+	"OpenAI API Key is required.": "Es requereix la clau API d'OpenAI.",
+	"OpenAI URL/Key required.": "URL/Clau d'OpenAI requerides.",
+	"or": "o",
+	"Other": "Altres",
+	"OUTPUT": "SORTIDA",
+	"Output format": "Format de sortida",
+	"Overview": "Vista general",
+	"page": "pàgina",
+	"Password": "Contrasenya",
+	"PDF document (.pdf)": "Document PDF (.pdf)",
+	"PDF Extract Images (OCR)": "Extreu imatges del PDF (OCR)",
+	"pending": "pendent",
+	"Permission denied when accessing media devices": "Permís denegat en accedir a dispositius multimèdia",
+	"Permission denied when accessing microphone": "Permís denegat en accedir al micròfon",
+	"Permission denied when accessing microphone: {{error}}": "Permís denegat en accedir al micròfon: {{error}}",
+	"Personalization": "Personalització",
+	"Pin": "Fixar",
+	"Pinned": "Fixat",
+	"Pipeline deleted successfully": "Pipeline eliminada correctament",
+	"Pipeline downloaded successfully": "Pipeline descarregada correctament",
+	"Pipelines": "Pipelines",
+	"Pipelines Not Detected": "No s'ha detectat Pipelines",
+	"Pipelines Valves": "Vàlvules de les Pipelines",
+	"Plain text (.txt)": "Text pla (.txt)",
+	"Playground": "Zona de jocs",
+	"Please carefully review the following warnings:": "Si us plau, revisa els següents avisos amb cura:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "Emplena tots els camps, si us plau.",
+	"Please select a reason": "Si us plau, selecciona una raó",
+	"Positive attitude": "Actitud positiva",
+	"Previous 30 days": "30 dies anteriors",
+	"Previous 7 days": "7 dies anteriors",
+	"Profile Image": "Imatge de perfil",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Indicació (p.ex. Digues-me quelcom divertit sobre l'Imperi Romà)",
+	"Prompt Content": "Contingut de la indicació",
+	"Prompt suggestions": "Suggeriments d'indicacions",
+	"Prompts": "Indicacions",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Obtenir \"{{searchValue}}\" de Ollama.com",
+	"Pull a model from Ollama.com": "Obtenir un model d'Ollama.com",
+	"Query Params": "Paràmetres de consulta",
+	"RAG Template": "Plantilla RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Llegir en veu alta",
+	"Record voice": "Enregistrar la veu",
+	"Redirecting you to OpenWebUI Community": "Redirigint-te a la comunitat OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Fes referència a tu mateix com a \"Usuari\" (p. ex., \"L'usuari està aprenent espanyol\")",
+	"References from": "Referències de",
+	"Refused when it shouldn't have": "Refusat quan no hauria d'haver estat",
+	"Regenerate": "Regenerar",
+	"Release Notes": "Notes de la versió",
+	"Relevance": "Rellevància",
+	"Remove": "Eliminar",
+	"Remove Model": "Eliminar el model",
+	"Rename": "Canviar el nom",
+	"Repeat Last N": "Repeteix els darrers N",
+	"Request Mode": "Mode de sol·licitud",
+	"Reranking Model": "Model de reavaluació",
+	"Reranking model disabled": "Model de reavaluació desactivat",
+	"Reranking model set to \"{{reranking_model}}\"": "Model de reavaluació establert a \"{{reranking_model}}\"",
+	"Reset": "Restableix",
+	"Reset Upload Directory": "Restableix el directori de pujades",
+	"Reset Vector Storage/Knowledge": "Restableix el Repositori de vectors/Coneixement",
+	"Response AutoCopy to Clipboard": "Copiar la resposta automàticament al porta-retalls",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Les notifications de resposta no es poden activar perquè els permisos del lloc web han estat rebutjats. Comprova les preferències del navegador per donar l'accés necessari.",
+	"Response splitting": "Divisió de la resposta",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rol",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Albada Rosé Pine",
+	"RTL": "RTL",
+	"Run": "Executar",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Executa Llama 2, Code Llama, i altres models. Personalitza i crea els teus propis models.",
+	"Running": "S'està executant",
+	"Save": "Desar",
+	"Save & Create": "Desar i crear",
+	"Save & Update": "Desar i actualitzar",
+	"Save As Copy": "Desar com a còpia",
+	"Save Tag": "Desar l'etiqueta",
+	"Saved": "Desat",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Desar els registres de xat directament a l'emmagatzematge del teu navegador ja no està suportat. Si us plau, descarregr i elimina els registres de xat fent clic al botó de sota. No et preocupis, pots tornar a importar fàcilment els teus registres de xat al backend a través de",
+	"Scroll to bottom when switching between branches": "Desplaçar a la part inferior quan es canviï de branca",
+	"Search": "Cercar",
+	"Search a model": "Cercar un model",
+	"Search Chats": "Cercar xats",
+	"Search Collection": "Cercar col·leccions",
+	"search for tags": "cercar etiquetes",
+	"Search Functions": "Cercar funcions",
+	"Search Knowledge": "Cercar coneixement",
+	"Search Models": "Cercar models",
+	"Search Prompts": "Cercar indicacions",
+	"Search Query Generation Prompt": "Indicació de cerca de generació de consultes",
+	"Search Result Count": "Recompte de resultats de cerca",
+	"Search Tools": "Cercar eines",
+	"SearchApi API Key": "Clau API de SearchApi",
+	"SearchApi Engine": "Motor de SearchApi",
+	"Searched {{count}} sites_one": "S'ha cercat {{count}} una pàgina",
+	"Searched {{count}} sites_many": "S'han cercat {{count}} pàgines",
+	"Searched {{count}} sites_other": "S'han cercat {{count}} pàgines",
+	"Searching \"{{searchQuery}}\"": "Cercant \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "Cercant \"{{searchQuery}}\" al coneixement",
+	"Searxng Query URL": "URL de consulta de Searxng",
+	"See readme.md for instructions": "Consulta l'arxiu readme.md per obtenir instruccions",
+	"See what's new": "Veure què hi ha de nou",
+	"Seed": "Llavor",
+	"Select a base model": "Seleccionar un model base",
+	"Select a engine": "Seleccionar un motor",
+	"Select a file to view or drag and drop a file to upload": "Seleccionar un arxiu o arrossegar un arxiu a pujar",
+	"Select a function": "Seleccionar una funció",
+	"Select a model": "Seleccionar un model",
+	"Select a pipeline": "Seleccionar una Pipeline",
+	"Select a pipeline url": "Seleccionar l'URL d'una Pipeline",
+	"Select a tool": "Seleccionar una eina",
+	"Select an Ollama instance": "Seleccionar una instància d'Ollama",
+	"Select Engine": "Seleccionar el motor",
+	"Select Knowledge": "Seleccionar coneixement",
+	"Select model": "Seleccionar un model",
+	"Select only one model to call": "Seleccionar només un model per trucar",
+	"Selected model(s) do not support image inputs": "El(s) model(s) seleccionats no admeten l'entrada d'imatges",
+	"Semantic distance to query": "Distància semàntica a la pregunta",
+	"Send": "Enviar",
+	"Send a Message": "Enviar un missatge",
+	"Send message": "Enviar missatge",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "Envia `stream_options: { include_usage: true }` a la sol·licitud.\nEls proveïdors compatibles retornaran la informació d'ús del token a la resposta quan s'estableixi.",
+	"September": "Setembre",
+	"Serper API Key": "Clau API de Serper",
+	"Serply API Key": "Clau API de Serply",
+	"Serpstack API Key": "Clau API de Serpstack",
+	"Server connection verified": "Connexió al servidor verificada",
+	"Set as default": "Establir com a predeterminat",
+	"Set CFG Scale": "Establir l'escala CFG",
+	"Set Default Model": "Establir el model predeterminat",
+	"Set embedding model (e.g. {{model}})": "Establir el model d'incrustació (p.ex. {{model}})",
+	"Set Image Size": "Establir la mida de la image",
+	"Set reranking model (e.g. {{model}})": "Establir el model de reavaluació (p.ex. {{model}})",
+	"Set Sampler": "Establir el mostrejador",
+	"Set Scheduler": "Establir el programador",
+	"Set Steps": "Establir el nombre de passos",
+	"Set Task Model": "Establir el model de tasca",
+	"Set Voice": "Establir la veu",
+	"Set whisper model": "",
+	"Settings": "Preferències",
+	"Settings saved successfully!": "Les preferències s'han desat correctament",
+	"Share": "Compartir",
+	"Share Chat": "Compartir el xat",
+	"Share to OpenWebUI Community": "Compartir amb la comunitat OpenWebUI",
+	"short-summary": "resum breu",
+	"Show": "Mostrar",
+	"Show Admin Details in Account Pending Overlay": "Mostrar els detalls de l'administrador a la superposició del compte pendent",
+	"Show Model": "Mostrar el model",
+	"Show shortcuts": "Mostrar dreceres",
+	"Show your support!": "Mostra el teu suport!",
+	"Showcased creativity": "Creativitat mostrada",
+	"Sign in": "Iniciar sessió",
+	"Sign in to {{WEBUI_NAME}}": "Iniciar sessió a {{WEBUI_NAME}}",
+	"Sign Out": "Tancar sessió",
+	"Sign up": "Registrar-se",
+	"Sign up to {{WEBUI_NAME}}": "Registrar-se a {{WEBUI_NAME}}",
+	"Signing in to {{WEBUI_NAME}}": "Iniciant sessió a {{WEBUI_NAME}}",
+	"Source": "Font",
+	"Speech Playback Speed": "Velocitat de la parla",
+	"Speech recognition error: {{error}}": "Error de reconeixement de veu: {{error}}",
+	"Speech-to-Text Engine": "Motor de veu a text",
+	"Stop": "Atura",
+	"Stop Sequence": "Atura la seqüència",
+	"Stream Chat Response": "Fer streaming de la resposta del xat",
+	"STT Model": "Model SST",
+	"STT Settings": "Preferències de STT",
+	"Subtitle (e.g. about the Roman Empire)": "Subtítol (per exemple, sobre l'Imperi Romà)",
+	"Success": "Èxit",
+	"Successfully updated.": "Actualitzat correctament.",
+	"Suggested": "Suggerit",
+	"Support": "Dona suport",
+	"Support this plugin:": "Dona suport a aquest complement:",
+	"Sync directory": "Sincronitzar directori",
+	"System": "Sistema",
+	"System Instructions": "",
+	"System Prompt": "Indicació del Sistema",
+	"Tags": "Etiquetes",
+	"Tags Generation Prompt": "Indicació per a la generació d'etiquetes",
+	"Tap to interrupt": "Prem per interrompre",
+	"Tavily API Key": "Clau API de Tavily",
+	"Tell us more:": "Dona'ns més informació:",
+	"Temperature": "Temperatura",
+	"Template": "Plantilla",
+	"Temporary Chat": "Xat temporal",
+	"Text Splitter": "Separador de text",
+	"Text-to-Speech Engine": "Motor de text a veu",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Gràcies pel teu comentari!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Els desenvolupadors d'aquest complement són voluntaris apassionats de la comunitat. Si trobeu útil aquest complement, considereu contribuir al seu desenvolupament.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "La mida màxima del fitxer en MB. Si la mida del fitxer supera aquest límit, el fitxer no es carregarà.",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "El nombre màxim de fitxers que es poden utilitzar alhora al xat. Si el nombre de fitxers supera aquest límit, els fitxers no es penjaran.",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "El valor de puntuació hauria de ser entre 0.0 (0%) i 1.0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "Pensant...",
+	"This action cannot be undone. Do you wish to continue?": "Aquesta acció no es pot desfer. Vols continuar?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Això assegura que les teves converses valuoses queden desades de manera segura a la teva base de dades. Gràcies!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Aquesta és una funció experimental, és possible que no funcioni com s'espera i està subjecta a canvis en qualsevol moment.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Aquesta opció eliminarà tots els fitxers existents de la col·lecció i els substituirà per fitxers recentment penjats.",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Això eliminarà",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "Això eliminarà <strong>{{NAME}}</strong> i <strong>tots els continguts</strong>.",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "Això restablirà la base de coneixement i sincronitzarà tots els fitxers. Vols continuar?",
+	"Thorough explanation": "Explicació en detall",
+	"Tika": "Tika",
+	"Tika Server URL required.": "La URL del servidor Tika és obligatòria.",
+	"Tiktoken": "Tiktoken",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Consell: Actualitza les diverses variables consecutivament prement la tecla de tabulació en l'entrada del xat després de cada reemplaçament.",
+	"Title": "Títol",
+	"Title (e.g. Tell me a fun fact)": "Títol (p. ex. Digues-me quelcom divertit)",
+	"Title Auto-Generation": "Generació automàtica de títol",
+	"Title cannot be an empty string.": "El títol no pot ser una cadena buida.",
+	"Title Generation Prompt": "Indicació de generació de títol",
+	"To access the available model names for downloading,": "Per accedir als noms dels models disponibles per descarregar,",
+	"To access the GGUF models available for downloading,": "Per accedir als models GGUF disponibles per descarregar,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Per accedir a la WebUI, poseu-vos en contacte amb l'administrador. Els administradors poden gestionar els estats dels usuaris des del tauler d'administració.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "Per adjuntar la base de coneixement aquí, afegiu-la primer a l'espai de treball \"Coneixement\".",
+	"to chat input.": "a l'entrada del xat.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Per seleccionar accions aquí, afegeix-les primer a l'espai de treball \"Funcions\".",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Per seleccionar filtres aquí, afegeix-los primer a l'espai de treball \"Funcions\".",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Per seleccionar kits d'eines aquí, afegeix-los primer a l'espai de treball \"Eines\".",
+	"Toast notifications for new updates": "",
+	"Today": "Avui",
+	"Toggle settings": "Alterna preferències",
+	"Toggle sidebar": "Alterna la barra lateral",
+	"Token": "Token",
+	"Tokens To Keep On Context Refresh (num_keep)": "Tokens a mantenir en l'actualització del context (num_keep)",
+	"Too verbose": "",
+	"Tool": "Eina",
+	"Tool created successfully": "Eina creada correctament",
+	"Tool deleted successfully": "Eina eliminada correctament",
+	"Tool imported successfully": "Eina importada correctament",
+	"Tool updated successfully": "Eina actualitzada correctament",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Descripció del kit d'eines (p. ex. Un kit d'eines per fer diverses operacions)",
+	"Toolkit ID (e.g. my_toolkit)": "ID del kit d'eines (p. ex. el_meu_kit)",
+	"Toolkit Name (e.g. My ToolKit)": "Nom del kit d'eines (p. ex. El meu kit)",
+	"Tools": "Eines",
+	"Tools are a function calling system with arbitrary code execution": "Les eines són un sistema de crida a funcions amb execució de codi arbitrari",
+	"Tools have a function calling system that allows arbitrary code execution": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari",
+	"Tools have a function calling system that allows arbitrary code execution.": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problemes en accedir a Ollama?",
+	"TTS Model": "Model TTS",
+	"TTS Settings": "Preferències de TTS",
+	"TTS Voice": "Veu TTS",
+	"Type": "Tipus",
+	"Type Hugging Face Resolve (Download) URL": "Escriu l'URL de Resolució (Descàrrega) de Hugging Face",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Oh! Hi ha hagut un problema connectant a {{provider}}.",
+	"UI": "UI",
+	"Unpin": "Alliberar",
+	"Untagged": "Sense etiquetes",
+	"Update": "Actualitzar",
+	"Update and Copy Link": "Actualitzar i copiar l'enllaç",
+	"Update for the latest features and improvements.": "Actualitza per a les darreres característiques i millores.",
+	"Update password": "Actualitzar la contrasenya",
+	"Updated": "Actualitzat",
+	"Updated at": "Actualitzat el",
+	"Updated At": "",
+	"Upload": "Pujar",
+	"Upload a GGUF model": "Pujar un model GGUF",
+	"Upload directory": "Pujar directori",
+	"Upload files": "Pujar fitxers",
+	"Upload Files": "Pujar fitxers",
+	"Upload Pipeline": "Pujar una Pipeline",
+	"Upload Progress": "Progrés de càrrega",
+	"URL Mode": "Mode URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "Utilitza '#' a l'entrada de la indicació per carregar i incloure els teus coneixements.",
+	"Use Gravatar": "Utilitzar Gravatar",
+	"Use Initials": "Utilitzar inicials",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "usuari",
+	"User": "",
+	"User location successfully retrieved.": "Ubicació de l'usuari obtinguda correctament",
+	"User Permissions": "Permisos d'usuari",
+	"Users": "Usuaris",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Utilitzar",
+	"Valid time units:": "Unitats de temps vàlides:",
+	"Valves": "Valves",
+	"Valves updated": "Valves actualitzat",
+	"Valves updated successfully": "Valves actualitat correctament",
+	"variable": "variable",
+	"variable to have them replaced with clipboard content.": "variable per tenir-les reemplaçades amb el contingut del porta-retalls.",
+	"Version": "Versió",
+	"Version {{selectedVersion}} of {{totalVersions}}": "Versió {{selectedVersion}} de {{totalVersions}}",
+	"Voice": "Veu",
+	"Voice Input": "Entrada de veu",
+	"Warning": "Avís",
+	"Warning:": "Avís:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Avís: Si s'actualitza o es canvia el model d'incrustació, s'hauran de tornar a importar tots els documents.",
+	"Web": "Web",
+	"Web API": "Web API",
+	"Web Loader Settings": "Preferències del carregador web",
+	"Web Search": "Cerca la web",
+	"Web Search Engine": "Motor de cerca de la web",
+	"Webhook URL": "URL del webhook",
+	"WebUI Settings": "Preferències de WebUI",
+	"WebUI will make requests to": "WebUI farà peticions a",
+	"What’s New in": "Què hi ha de nou a",
+	"Whisper (Local)": "Whisper (local)",
+	"Widescreen Mode": "Mode de pantalla ampla",
+	"Won": "",
+	"Workspace": "Espai de treball",
+	"Write a prompt suggestion (e.g. Who are you?)": "Escriu una suggerència d'indicació (p. ex. Qui ets?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Escriu un resum en 50 paraules que resumeixi [tema o paraula clau].",
+	"Write something...": "Escriu quelcom...",
+	"Yesterday": "Ahir",
+	"You": "Tu",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Només pots xatejar amb un màxim de {{maxCount}} fitxers alhora.",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Pots personalitzar les teves interaccions amb els models de llenguatge afegint memòries mitjançant el botó 'Gestiona' que hi ha a continuació, fent-les més útils i adaptades a tu.",
+	"You cannot clone a base model": "No es pot clonar un model base",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "No tens converses arxivades.",
+	"You have shared this chat": "Has compartit aquest xat",
+	"You're a helpful assistant.": "Ets un assistent útil.",
+	"You're now logged in.": "Ara estàs connectat.",
+	"Your account status is currently pending activation.": "El compte està actualment pendent d'activació",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Tota la teva contribució anirà directament al desenvolupador del complement; Open WebUI no se'n queda cap percentatge. Tanmateix, la plataforma de finançament escollida pot tenir les seves pròpies comissions.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Preferències del carregador de Youtube"
+}
diff --git a/src/lib/i18n/locales/ceb-PH/translation.json b/src/lib/i18n/locales/ceb-PH/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..e828c6238ea60bbc0c7932059f607a1020038faf
--- /dev/null
+++ b/src/lib/i18n/locales/ceb-PH/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' o '-1' para walay expiration.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(pananglitan `sh webui.sh --api`)",
+	"(latest)": "",
+	"{{ models }}": "",
+	"{{ owner }}: You cannot delete a base model": "",
+	"{{user}}'s Chats": "",
+	"{{webUIName}} Backend Required": "Backend {{webUIName}} gikinahanglan",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
+	"a user": "usa ka user",
+	"About": "Mahitungod sa",
+	"Account": "Account",
+	"Account Activation Pending": "",
+	"Accurate information": "",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "",
+	"Add a model id": "",
+	"Add a short description about what this model does": "",
+	"Add a short title for this prompt": "Pagdugang og usa ka mubo nga titulo alang niini nga prompt",
+	"Add a tag": "Pagdugang og tag",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Pagdugang og custom prompt",
+	"Add Files": "Idugang ang mga file",
+	"Add Memory": "",
+	"Add Model": "",
+	"Add Tag": "",
+	"Add Tags": "idugang ang mga tag",
+	"Add text content": "",
+	"Add User": "",
+	"Adjusting these settings will apply changes universally to all users.": "Ang pag-adjust niini nga mga setting magamit ang mga pagbag-o sa tanan nga tiggamit.",
+	"admin": "Administrator",
+	"Admin": "",
+	"Admin Panel": "Admin Panel",
+	"Admin Settings": "Mga setting sa administratibo",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "advanced settings",
+	"Advanced Params": "",
+	"All chats": "",
+	"All Documents": "",
+	"Allow Chat Deletion": "Tugoti nga mapapas ang mga chat",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "alphanumeric nga mga karakter ug hyphen",
+	"Already have an account?": "Naa na kay account ?",
+	"an assistant": "usa ka katabang",
+	"and": "Ug",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "",
+	"API Base URL": "API Base URL",
+	"API Key": "yawe sa API",
+	"API Key created.": "",
+	"API keys": "",
+	"April": "",
+	"Archive": "",
+	"Archive All Chats": "",
+	"Archived Chats": "pagrekord sa chat",
+	"are allowed - Activate this command by typing": "gitugotan - I-enable kini nga sugo pinaagi sa pag-type",
+	"Are you sure?": "Sigurado ka ?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Ilakip ang usa ka file",
+	"Attention to detail": "Pagtagad sa mga detalye",
+	"Audio": "Audio",
+	"August": "",
+	"Auto-playback response": "Autoplay nga tubag",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "Base URL AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "Ang AUTOMATIC1111 base URL gikinahanglan.",
+	"Available list": "",
+	"available!": "magamit!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Balik",
+	"Bad Response": "",
+	"Banners": "",
+	"Base Model (From)": "",
+	"Batch Size (num_batch)": "",
+	"before": "",
+	"Being lazy": "",
+	"Brave Search API Key": "",
+	"Bypass SSL verification for Websites": "",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "Pagkanselar",
+	"Capabilities": "",
+	"Change Password": "Usba ang password",
+	"Character": "",
+	"Chat": "Panaghisgot",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "",
+	"Chat Controls": "",
+	"Chat direction": "",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Mga panaghisgot",
+	"Check Again": "Susiha pag-usab",
+	"Check for updates": "Susiha ang mga update",
+	"Checking for updates...": "Pagsusi alang sa mga update...",
+	"Choose a model before saving...": "Pagpili og template sa dili pa i-save...",
+	"Chunk Overlap": "Block overlap",
+	"Chunk Params": "Mga Setting sa Block",
+	"Chunk Size": "Gidak-on sa block",
+	"Citation": "Mga kinutlo",
+	"Clear memory": "",
+	"Click here for help.": "I-klik dinhi alang sa tabang.",
+	"Click here to": "",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "I-klik dinhi aron makapili",
+	"Click here to select a csv file.": "",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "I-klik dinhi.",
+	"Click on the user role button to change a user's role.": "I-klik ang User Role button aron usbon ang role sa user.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "",
+	"Close": "Suod nga",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Koleksyon",
+	"ComfyUI": "",
+	"ComfyUI Base URL": "",
+	"ComfyUI Base URL is required.": "",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Pag-order",
+	"Completions": "",
+	"Concurrent Requests": "",
+	"Confirm": "",
+	"Confirm Password": "Kumpirma ang password",
+	"Confirm your action": "",
+	"Connections": "Mga koneksyon",
+	"Contact Admin for WebUI Access": "",
+	"Content": "Kontento",
+	"Content Extraction": "",
+	"Context Length": "Ang gitas-on sa konteksto",
+	"Continue Response": "",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "",
+	"Copied to clipboard": "",
+	"Copy": "",
+	"Copy last code block": "Kopyaha ang katapusang bloke sa code",
+	"Copy last response": "Kopyaha ang kataposang tubag",
+	"Copy Link": "",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Ang pagkopya sa clipboard malampuson!",
+	"Create a model": "",
+	"Create Account": "Paghimo og account",
+	"Create Knowledge": "",
+	"Create new key": "",
+	"Create new secret key": "",
+	"Created at": "Gihimo ang",
+	"Created At": "",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "Kasamtangang modelo",
+	"Current Password": "Kasamtangang Password",
+	"Custom": "Custom",
+	"Customize models for a specific purpose": "",
+	"Dark": "Ngitngit",
+	"Dashboard": "",
+	"Database": "Database",
+	"December": "",
+	"Default": "Pinaagi sa default",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "",
+	"Default Model": "",
+	"Default model updated": "Gi-update nga default template",
+	"Default Prompt Suggestions": "Default nga prompt nga mga sugyot",
+	"Default User Role": "Default nga Papel sa Gumagamit",
+	"Delete": "",
+	"Delete a model": "Pagtangtang sa usa ka template",
+	"Delete All Chats": "",
+	"Delete chat": "Pagtangtang sa panaghisgot",
+	"Delete Chat": "",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "",
+	"Delete tool?": "",
+	"Delete User": "",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} gipapas",
+	"Deleted {{name}}": "",
+	"Description": "Deskripsyon",
+	"Didn't fully follow instructions": "",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "",
+	"Discover a prompt": "Pagkaplag usa ka prompt",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Pagdiskubre, pag-download ug pagsuhid sa mga naandan nga pag-aghat",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Pagdiskobre, pag-download, ug pagsuhid sa mga preset sa template",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "Ipakita ang username imbes nga 'Ikaw' sa Panaghisgutan",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Dokumento",
+	"Documentation": "",
+	"Documents": "Mga dokumento",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "wala maghimo ug eksternal nga koneksyon, ug ang imong data nagpabiling luwas sa imong lokal nga host server.",
+	"Don't have an account?": "Wala kay account ?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "",
+	"Done": "",
+	"Download": "",
+	"Download canceled": "",
+	"Download Database": "I-download ang database",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Ihulog ang bisan unsang file dinhi aron idugang kini sa panag-istoryahanay",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "p. ",
+	"Edit": "",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "I-edit ang tiggamit",
+	"ElevenLabs": "",
+	"Email": "E-mail",
+	"Embedding Batch Size": "",
+	"Embedding Model": "",
+	"Embedding Model Engine": "",
+	"Embedding model set to \"{{embedding_model}}\"": "",
+	"Enable Community Sharing": "",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "I-enable ang bag-ong mga rehistro",
+	"Enable Web Search": "",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
+	"Enter {{role}} message here": "Pagsulod sa mensahe {{role}} dinhi",
+	"Enter a detail about yourself for your LLMs to recall": "",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Pagsulod sa block overlap",
+	"Enter Chunk Size": "Isulod ang block size",
+	"Enter description": "",
+	"Enter Github Raw URL": "",
+	"Enter Google PSE API Key": "",
+	"Enter Google PSE Engine Id": "",
+	"Enter Image Size (e.g. 512x512)": "Pagsulod sa gidak-on sa hulagway (pananglitan 512x512)",
+	"Enter language codes": "",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Pagsulod sa template tag (e.g. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Pagsulod sa gidaghanon sa mga lakang (e.g. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "",
+	"Enter Serper API Key": "",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "",
+	"Enter stop sequence": "Pagsulod sa katapusan nga han-ay",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Pagsulod sa Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Pagsulod sa URL (e.g. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "",
+	"Enter Your Email": "Pagsulod sa imong e-mail address",
+	"Enter Your Full Name": "Ibutang ang imong tibuok nga ngalan",
+	"Enter your message": "",
+	"Enter Your Password": "Ibutang ang imong password",
+	"Enter Your Role": "",
+	"Error": "",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Eksperimento",
+	"Export": "",
+	"Export All Chats (All Users)": "I-export ang tanan nga mga chat (Tanan nga tiggamit)",
+	"Export chat (.json)": "",
+	"Export Chats": "I-export ang mga chat",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "",
+	"Export Prompts": "Export prompts",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "",
+	"Failed to read clipboard contents": "Napakyas sa pagbasa sa sulod sa clipboard",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "",
+	"Feedback History": "",
+	"Feel free to add specific details": "",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "File mode",
+	"File not found.": "Wala makit-an ang file.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
+	"Fluidly stream large external response chunks": "Hapsay nga paghatud sa daghang mga tipik sa eksternal nga mga tubag",
+	"Focus chat input": "Pag-focus sa entry sa diskusyon",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Heneral",
+	"General Settings": "kinatibuk-ang mga setting",
+	"Generate Image": "",
+	"Generating search query": "",
+	"Generation Info": "",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "",
+	"Google PSE API Key": "",
+	"Google PSE Engine Id": "",
+	"h:mm a": "",
+	"Haptic Feedback": "",
+	"has no conversations.": "",
+	"Hello, {{name}}": "Maayong buntag, {{name}}",
+	"Help": "",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Tagoa",
+	"Hide Model": "",
+	"How can I help you today?": "Unsaon nako pagtabang kanimo karon?",
+	"Hybrid Search": "",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Pagmugna og hulagway (Eksperimento)",
+	"Image Generation Engine": "Makina sa paghimo og imahe",
+	"Image Settings": "Mga Setting sa Imahen",
+	"Images": "Mga hulagway",
+	"Import Chats": "Import nga mga chat",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "",
+	"Import Prompts": "Import prompt",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Iapil ang `--api` nga bandila kung nagdagan nga stable-diffusion-webui",
+	"Info": "",
+	"Input commands": "Pagsulod sa input commands",
+	"Install from Github URL": "",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "Interface",
+	"Invalid file format.": "",
+	"Invalid Tag": "",
+	"January": "",
+	"join our Discord for help.": "Apil sa among Discord alang sa tabang.",
+	"JSON": "JSON",
+	"JSON Preview": "",
+	"July": "",
+	"June": "",
+	"JWT Expiration": "Pag-expire sa JWT",
+	"JWT Token": "JWT token",
+	"Keep Alive": "Padayon nga aktibo",
+	"Keyboard shortcuts": "Mga shortcut sa keyboard",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Pinulongan",
+	"large language models, locally.": "",
+	"Last Active": "",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Kahayag",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "Ang mga LLM mahimong masayop. ",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "",
+	"Made by OpenWebUI Community": "Gihimo sa komunidad sa OpenWebUI",
+	"Make sure to enclose them with": "Siguruha nga palibutan sila",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "Pagdumala sa mga templates",
+	"Manage Ollama Models": "Pagdumala sa mga modelo sa Ollama",
+	"Manage Pipelines": "",
+	"March": "",
+	"Max Tokens (num_predict)": "",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Ang labing taas nga 3 nga mga disenyo mahimong ma-download nga dungan. ",
+	"May": "",
+	"Memories accessible by LLMs will be shown here.": "",
+	"Memory": "",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "",
+	"Min P": "",
+	"Minimum Score": "",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Ang modelo'{{modelName}}' malampuson nga na-download.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Ang modelo'{{modelTag}}' naa na sa pila para ma-download.",
+	"Model {{modelId}} not found": "Modelo {{modelId}} wala makit-an",
+	"Model {{modelName}} is not vision capable": "",
+	"Model {{name}} is now {{status}}": "",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
+	"Model ID": "",
+	"Model Name": "",
+	"Model not selected": "Wala gipili ang modelo",
+	"Model Params": "",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Whitelist sa modelo",
+	"Model(s) Whitelisted": "Gi-whitelist nga (mga) modelo",
+	"Modelfile Content": "Mga sulod sa template file",
+	"Models": "Mga modelo",
+	"more": "",
+	"More": "",
+	"Move to Top": "",
+	"Name": "Ngalan",
+	"Name your model": "",
+	"New Chat": "Bag-ong diskusyon",
+	"New folder": "",
+	"New Password": "Bag-ong Password",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "",
+	"No search query generated": "",
+	"No source available": "Walay tinubdan nga anaa",
+	"No valves to update": "",
+	"None": "",
+	"Not factually correct": "",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
+	"Notes": "",
+	"Notifications": "Mga pahibalo sa desktop",
+	"November": "",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "",
+	"OAuth ID": "",
+	"October": "",
+	"Off": "Napuo",
+	"Okay, Let's Go!": "Okay, lakaw na!",
+	"OLED Dark": "",
+	"Ollama": "",
+	"Ollama API": "",
+	"Ollama API disabled": "",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Ollama nga bersyon",
+	"On": "Gipaandar",
+	"Only": "Lamang",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Ang alphanumeric nga mga karakter ug hyphen lang ang gitugotan sa command string.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! ",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! ",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Ablihi ang bag-ong diskusyon",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "",
+	"OpenAI API Key is required.": "Ang yawe sa OpenAI API gikinahanglan.",
+	"OpenAI URL/Key required.": "",
+	"or": "O",
+	"Other": "",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Password",
+	"PDF document (.pdf)": "",
+	"PDF Extract Images (OCR)": "PDF Image Extraction (OCR)",
+	"pending": "gipugngan",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "Gidili ang pagtugot sa dihang nag-access sa mikropono: {{error}}",
+	"Personalization": "",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "",
+	"Plain text (.txt)": "",
+	"Playground": "Dulaanan",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "",
+	"Previous 30 days": "",
+	"Previous 7 days": "",
+	"Profile Image": "",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
+	"Prompt Content": "Ang sulod sa prompt",
+	"Prompt suggestions": "Maabtik nga mga Sugyot",
+	"Prompts": "Mga aghat",
+	"Pull \"{{searchValue}}\" from Ollama.com": "",
+	"Pull a model from Ollama.com": "Pagkuha ug template gikan sa Ollama.com",
+	"Query Params": "Mga parameter sa pangutana",
+	"RAG Template": "RAG nga modelo",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "",
+	"Record voice": "Irekord ang tingog",
+	"Redirecting you to OpenWebUI Community": "Gi-redirect ka sa komunidad sa OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "",
+	"Regenerate": "",
+	"Release Notes": "Release Notes",
+	"Relevance": "",
+	"Remove": "",
+	"Remove Model": "",
+	"Rename": "",
+	"Repeat Last N": "Balika ang katapusang N",
+	"Request Mode": "Query mode",
+	"Reranking Model": "",
+	"Reranking model disabled": "",
+	"Reranking model set to \"{{reranking_model}}\"": "",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Awtomatikong kopya sa tubag sa clipboard",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Papel",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Aube Pine Rosé",
+	"RTL": "",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "Tipigi",
+	"Save & Create": "I-save ug Paghimo",
+	"Save & Update": "I-save ug I-update",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Ang pag-save sa mga chat log direkta sa imong browser storage dili na suportado. ",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Pagpanukiduki",
+	"Search a model": "",
+	"Search Chats": "",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "",
+	"Search Prompts": "Pangitaa ang mga prompt",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "",
+	"Searched {{count}} sites_other": "",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "",
+	"See readme.md for instructions": "Tan-awa ang readme.md alang sa mga panudlo",
+	"See what's new": "Tan-awa unsay bag-o",
+	"Seed": "Binhi",
+	"Select a base model": "",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "Pagpili og modelo",
+	"Select a pipeline": "",
+	"Select a pipeline url": "",
+	"Select a tool": "",
+	"Select an Ollama instance": "Pagpili usa ka pananglitan sa Ollama",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Pagpili og modelo",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "",
+	"Semantic distance to query": "",
+	"Send": "",
+	"Send a Message": "Magpadala ug mensahe",
+	"Send message": "Magpadala ug mensahe",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "",
+	"Serper API Key": "",
+	"Serply API Key": "",
+	"Serpstack API Key": "",
+	"Server connection verified": "Gipamatud-an nga koneksyon sa server",
+	"Set as default": "Define pinaagi sa default",
+	"Set CFG Scale": "",
+	"Set Default Model": "Ibutang ang default template",
+	"Set embedding model (e.g. {{model}})": "",
+	"Set Image Size": "Ibutang ang gidak-on sa hulagway",
+	"Set reranking model (e.g. {{model}})": "",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Ipasabot ang mga lakang",
+	"Set Task Model": "",
+	"Set Voice": "Ibutang ang tingog",
+	"Set whisper model": "",
+	"Settings": "Mga setting",
+	"Settings saved successfully!": "Malampuson nga na-save ang mga setting!",
+	"Share": "",
+	"Share Chat": "",
+	"Share to OpenWebUI Community": "Ipakigbahin sa komunidad sa OpenWebUI",
+	"short-summary": "mubo nga summary",
+	"Show": "Pagpakita",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "Ipakita ang mga shortcut",
+	"Show your support!": "",
+	"Showcased creativity": "",
+	"Sign in": "Para maka log in",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Pag-sign out",
+	"Sign up": "Pagrehistro",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Tinubdan",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Sayop sa pag-ila sa tingog: {{error}}",
+	"Speech-to-Text Engine": "Engine sa pag-ila sa tingog",
+	"Stop": "",
+	"Stop Sequence": "Pagkasunod-sunod sa pagsira",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "Mga setting sa STT",
+	"Subtitle (e.g. about the Roman Empire)": "",
+	"Success": "Kalampusan",
+	"Successfully updated.": "Malampuson nga na-update.",
+	"Suggested": "",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "Sistema",
+	"System Instructions": "",
+	"System Prompt": "Madasig nga Sistema",
+	"Tags": "Mga tag",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "",
+	"Temperature": "Temperatura",
+	"Template": "Modelo",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Text-to-speech nga makina",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
+	"Theme": "Tema",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Kini nagsiguro nga ang imong bililhon nga mga panag-istoryahanay luwas nga natipig sa imong backend database. ",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Sugyot: Pag-update sa daghang variable nga lokasyon nga sunud-sunod pinaagi sa pagpindot sa tab key sa chat entry pagkahuman sa matag puli.",
+	"Title": "Titulo",
+	"Title (e.g. Tell me a fun fact)": "",
+	"Title Auto-Generation": "Awtomatikong paghimo sa titulo",
+	"Title cannot be an empty string.": "",
+	"Title Generation Prompt": "Madasig nga henerasyon sa titulo",
+	"To access the available model names for downloading,": "Aron ma-access ang mga ngalan sa modelo nga ma-download,",
+	"To access the GGUF models available for downloading,": "Aron ma-access ang mga modelo sa GGUF nga magamit alang sa pag-download,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "sa entrada sa iring.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "",
+	"Toggle settings": "I-toggle ang mga setting",
+	"Toggle sidebar": "I-toggle ang sidebar",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Ibabaw nga P",
+	"Trouble accessing Ollama?": "Adunay mga problema sa pag-access sa Ollama?",
+	"TTS Model": "",
+	"TTS Settings": "Mga Setting sa TTS",
+	"TTS Voice": "",
+	"Type": "",
+	"Type Hugging Face Resolve (Download) URL": "Pagsulod sa resolusyon (pag-download) URL Hugging Face",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh!  {{provider}}.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "",
+	"Update for the latest features and improvements.": "",
+	"Update password": "I-update ang password",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Pag-upload ug modelo sa GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "",
+	"Upload Pipeline": "",
+	"Upload Progress": "Pag-uswag sa Pag-upload",
+	"URL Mode": "URL mode",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Paggamit sa Gravatar",
+	"Use Initials": "",
+	"use_mlock (Ollama)": "",
+	"use_mmap (Ollama)": "",
+	"user": "tiggamit",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "Mga permiso sa tiggamit",
+	"Users": "Mga tiggamit",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Sa paggamit",
+	"Valid time units:": "Balido nga mga yunit sa oras:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "variable",
+	"variable to have them replaced with clipboard content.": "variable aron pulihan kini sa mga sulud sa clipboard.",
+	"Version": "Bersyon",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
+	"Web": "Web",
+	"Web API": "",
+	"Web Loader Settings": "",
+	"Web Search": "",
+	"Web Search Engine": "",
+	"Webhook URL": "",
+	"WebUI Settings": "Mga Setting sa WebUI",
+	"WebUI will make requests to": "Ang WebUI maghimo mga hangyo sa",
+	"What’s New in": "Unsay bag-o sa",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "",
+	"Write a prompt suggestion (e.g. Who are you?)": "Pagsulat og gisugyot nga prompt (eg. Kinsa ka?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Pagsulat og 50 ka pulong nga summary nga nagsumaryo [topic o keyword].",
+	"Write something...": "",
+	"Yesterday": "",
+	"You": "",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "",
+	"You have shared this chat": "",
+	"You're a helpful assistant.": "Usa ka ka mapuslanon nga katabang",
+	"You're now logged in.": "Konektado ka na karon.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "",
+	"Youtube Loader Settings": ""
+}
diff --git a/src/lib/i18n/locales/da-DK/translation.json b/src/lib/i18n/locales/da-DK/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..0c44b7a015418a0091ab7b50efe1e7355c178fa4
--- /dev/null
+++ b/src/lib/i18n/locales/da-DK/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' eller '-1' for ingen udløb",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(f.eks. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(f.eks. `sh webui.sh --api`)",
+	"(latest)": "(seneste)",
+	"{{ models }}": "{{ modeller }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Du kan ikke slette en base model",
+	"{{user}}'s Chats": "{{user}}s chats",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend kræves",
+	"*Prompt node ID(s) are required for image generation": "*Prompt node ID(s) er påkrævet for at kunne generere billeder",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "En ny version (v{{LATEST_VERSION}}) er nu tilgængelig.",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "En 'task model' bliver brugt til at opgaver såsom at generere overskrifter til chats eller internetsøgninger",
+	"a user": "en bruger",
+	"About": "Information",
+	"Account": "Profil",
+	"Account Activation Pending": "Aktivering af profil afventer",
+	"Accurate information": "Profilinformation",
+	"Actions": "Handlinger",
+	"Active Users": "Aktive brugere",
+	"Add": "Tilføj",
+	"Add a model id": "Tilføj et model-id",
+	"Add a short description about what this model does": "En kort beskrivelse af hvad denne model gør",
+	"Add a short title for this prompt": "Tilføj en kort titel for denne prompt",
+	"Add a tag": "Tilføj et tag",
+	"Add Arena Model": "",
+	"Add Content": "Tilføj indhold",
+	"Add content here": "Tilføj indhold her",
+	"Add custom prompt": "Tilføj en special-prompt",
+	"Add Files": "Tilføj filer",
+	"Add Memory": "Tilføj hukommelse",
+	"Add Model": "Tilføj model",
+	"Add Tag": "Tilføj tag",
+	"Add Tags": "Tilføj tags",
+	"Add text content": "Tilføj tekst",
+	"Add User": "Tilføj bruger",
+	"Adjusting these settings will apply changes universally to all users.": "Ændringer af disse indstillinger har konsekvenser for alle brugere.",
+	"admin": "administrator",
+	"Admin": "Administrator",
+	"Admin Panel": "Administrationspanel",
+	"Admin Settings": "Administrationsindstillinger",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Administratorer har adgang til alle værktøjer altid; brugere skal tilføjes værktøjer pr. model i hvert workspace.",
+	"Advanced Parameters": "Advancerede indstillinger",
+	"Advanced Params": "Advancerede indstillinger",
+	"All chats": "",
+	"All Documents": "Alle dokumenter",
+	"Allow Chat Deletion": "Tillad sletning af chats",
+	"Allow Chat Editing": "Tillad redigering af chats",
+	"Allow non-local voices": "Tillad ikke-lokale stemmer",
+	"Allow Temporary Chat": "Tillad midlertidig chat",
+	"Allow User Location": "Tillad bruger-lokation",
+	"Allow Voice Interruption in Call": "Tillad afbrydelser i stemme i opkald",
+	"alphanumeric characters and hyphens": "alphanumeriske bogstaver og bindestreger",
+	"Already have an account?": "Har du allerede en profil?",
+	"an assistant": "en assistent",
+	"and": "og",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "og lav et nyt link til deling",
+	"API Base URL": "API Base URL",
+	"API Key": "API nøgle",
+	"API Key created.": "API nøgle lavet",
+	"API keys": "API nøgler",
+	"April": "april",
+	"Archive": "Arkiv",
+	"Archive All Chats": "Arkiver alle chats",
+	"Archived Chats": "Arkiverede chats",
+	"are allowed - Activate this command by typing": "er tilladt - Aktiver denne kommando ved at skrive",
+	"Are you sure?": "Er du sikker?",
+	"Arena Models": "",
+	"Artifacts": "Artifakter",
+	"Ask a question": "Stil et spørgsmål",
+	"Assistant": "",
+	"Attach file": "Vedhæft fil",
+	"Attention to detail": "Detajleorientering",
+	"Audio": "Lyd",
+	"August": "august",
+	"Auto-playback response": "Automatisk afspil svar",
+	"Automatic1111": "Automatic1111",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Api Auth String",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Base URL er påkrævet.",
+	"Available list": "Tilgængelige lister",
+	"available!": "tilgængelig!",
+	"Azure AI Speech": "Azure AI Speech",
+	"Azure Region": "Azure Region",
+	"Back": "Tilbage",
+	"Bad Response": "Problem i response",
+	"Banners": "Bannere",
+	"Base Model (From)": "Base Model (Fra)",
+	"Batch Size (num_batch)": "Batch størrelse (num_batch)",
+	"before": "før",
+	"Being lazy": "At være doven",
+	"Brave Search API Key": "Brave Search API nøgle",
+	"Bypass SSL verification for Websites": "Forbigå SSL verifikation på websider",
+	"Call": "Opkald",
+	"Call feature is not supported when using Web STT engine": "Opkaldsfunktion er ikke understøttet for Web STT engine",
+	"Camera": "Kamera",
+	"Cancel": "Afbryd",
+	"Capabilities": "Funktioner",
+	"Change Password": "Skift password",
+	"Character": "",
+	"Chat": "Chat",
+	"Chat Background Image": "Chat baggrundsbillede",
+	"Chat Bubble UI": "Chat Bubble UI",
+	"Chat Controls": "Chat indstillinger",
+	"Chat direction": "Chat retning",
+	"Chat Overview": "Chat overblik",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Chats",
+	"Check Again": "Tjek igen",
+	"Check for updates": "Søg efter opdateringer",
+	"Checking for updates...": "Søger efter opdateringer",
+	"Choose a model before saving...": "Vælg en model før du gemmer",
+	"Chunk Overlap": "Chunk overlap",
+	"Chunk Params": "Chunk parametre",
+	"Chunk Size": "Chunk størrelse",
+	"Citation": "Citat",
+	"Clear memory": "Slet hukommelse",
+	"Click here for help.": "Klik her for hjælp",
+	"Click here to": "Klik her for at",
+	"Click here to download user import template file.": "Klik her for at downloade bruger import template fil.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Klik her for at vælge",
+	"Click here to select a csv file.": "Klik her for at vælge en csv fil",
+	"Click here to select a py file.": "Klik her for at vælge en py fil",
+	"Click here to upload a workflow.json file.": "Klik her for at uploade en workflow.json fil",
+	"click here.": "klik her.",
+	"Click on the user role button to change a user's role.": "Klik på bruger ikonet for at ændre brugerens rolle.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Skriveadgang til udklipsholderen ikke tilladt. Tjek venligst indstillingerne i din browser for at give adgang.",
+	"Clone": "Klon",
+	"Close": "Luk",
+	"Code execution": "",
+	"Code formatted successfully": "Kode formateret korrekt",
+	"Collection": "Samling",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Base URL",
+	"ComfyUI Base URL is required.": "ComfyUI Base URL er påkrævet.",
+	"ComfyUI Workflow": "ComfyUI Workflow",
+	"ComfyUI Workflow Nodes": "ComfyUI Workflow Nodes",
+	"Command": "Kommando",
+	"Completions": "",
+	"Concurrent Requests": "Concurrent requests",
+	"Confirm": "Bekræft",
+	"Confirm Password": "Bekræft password",
+	"Confirm your action": "Bekræft din handling",
+	"Connections": "Forbindelser",
+	"Contact Admin for WebUI Access": "Kontakt din administrator for adgang til WebUI",
+	"Content": "Indhold",
+	"Content Extraction": "Udtræk af indhold",
+	"Context Length": "Kontekst længde",
+	"Continue Response": "Fortsæt svar",
+	"Continue with {{provider}}": "Fortsæt med {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "Kontroller hvordan beskedens tekst bliver splittet til TTS requests. 'Punctuation' (tegnsætning) splitter i sætninger, 'paragraphs' splitter i paragraffer, og 'none' beholder beskeden som en samlet streng.",
+	"Controls": "Indstillinger",
+	"Copied": "Kopieret",
+	"Copied shared chat URL to clipboard!": "Link til deling kopieret til udklipsholder",
+	"Copied to clipboard": "Kopieret til udklipsholder",
+	"Copy": "Kopier",
+	"Copy last code block": "Kopier seneste kode",
+	"Copy last response": "Kopier senester svar",
+	"Copy Link": "Kopier link",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Kopieret til udklipsholder!",
+	"Create a model": "Lav en model",
+	"Create Account": "Opret profil",
+	"Create Knowledge": "Opret Viden",
+	"Create new key": "Opret en ny nøgle",
+	"Create new secret key": "Opret en ny hemmelig nøgle",
+	"Created at": "Oprettet",
+	"Created At": "Oprettet",
+	"Created by": "Oprettet af",
+	"CSV Import": "Importer CSV",
+	"Current Model": "Nuværende model",
+	"Current Password": "Nuværende password",
+	"Custom": "Custom",
+	"Customize models for a specific purpose": "Lav en model til et specifikt formål",
+	"Dark": "Mørk",
+	"Dashboard": "Dashboard",
+	"Database": "Database",
+	"December": "december",
+	"Default": "Standard",
+	"Default (Open AI)": "Standard (Open AI)",
+	"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
+	"Default Model": "Standard model",
+	"Default model updated": "Standard model opdateret",
+	"Default Prompt Suggestions": "Standardforslag til prompt",
+	"Default User Role": "Brugers rolle som standard",
+	"Delete": "Slet",
+	"Delete a model": "Slet en model",
+	"Delete All Chats": "Slet alle chats",
+	"Delete chat": "Slet chat",
+	"Delete Chat": "Slet chat",
+	"Delete chat?": "Slet chat?",
+	"Delete folder?": "",
+	"Delete function?": "Slet funktion?",
+	"Delete prompt?": "Slet prompt?",
+	"delete this link": "slet dette link",
+	"Delete tool?": "Slet værktøj?",
+	"Delete User": "Slet bruger",
+	"Deleted {{deleteModelTag}}": "Slettede {{deleteModelTag}}",
+	"Deleted {{name}}": "Slettede {{name}}",
+	"Description": "Beskrivelse",
+	"Didn't fully follow instructions": "Fulgte ikke instruktioner",
+	"Disabled": "Inaktiv",
+	"Discover a function": "Find en funktion",
+	"Discover a model": "Find en model",
+	"Discover a prompt": "Find en prompt",
+	"Discover a tool": "Find et værktøj",
+	"Discover, download, and explore custom functions": "Find, download og udforsk unikke funktioner",
+	"Discover, download, and explore custom prompts": "Find, download og udforsk unikke prompts",
+	"Discover, download, and explore custom tools": "Find, download og udforsk unikke værktøjer",
+	"Discover, download, and explore model presets": "Find, download og udforsk modelindstillinger",
+	"Dismissible": "Kan afvises",
+	"Display Emoji in Call": "Vis emoji i chat",
+	"Display the username instead of You in the Chat": "Vis brugernavn i stedet for Dig i chatten",
+	"Do not install functions from sources you do not fully trust.": "Lad være med at installere funktioner fra kilder, som du ikke stoler på.",
+	"Do not install tools from sources you do not fully trust.": "Lad være med at installere værktøjer fra kilder, som du ikke stoler på.",
+	"Document": "Dokument",
+	"Documentation": "Dokumentation",
+	"Documents": "Dokumenter",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "laver ikke eksterne kald, og din data bliver sikkert på din egen lokalt hostede server.",
+	"Don't have an account?": "Har du ikke en profil?",
+	"don't install random functions from sources you don't trust.": "lad være med at installere tilfældige funktioner fra kilder, som du ikke stoler på.",
+	"don't install random tools from sources you don't trust.": "lad være med at installere tilfældige værktøjer fra kilder, som du ikke stoler på.",
+	"Don't like the style": "Kan du ikke lide stilen",
+	"Done": "Færdig",
+	"Download": "Download",
+	"Download canceled": "Download afbrudt",
+	"Download Database": "Download database",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Upload filer her for at tilføje til samtalen",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "f.eks. '30s', '10m'. Tilladte værdier er 's', 'm', 'h'.",
+	"Edit": "Rediger",
+	"Edit Arena Model": "",
+	"Edit Memory": "Rediger hukommelse",
+	"Edit User": "Rediger bruger",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "Email",
+	"Embedding Batch Size": "Embedding Batch størrelse",
+	"Embedding Model": "Embedding Model",
+	"Embedding Model Engine": "Embedding Model engine",
+	"Embedding model set to \"{{embedding_model}}\"": "Embedding model sat til \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Aktiver deling til Community",
+	"Enable Message Rating": "Aktiver rating af besked",
+	"Enable New Sign Ups": "Aktiver nye signups",
+	"Enable Web Search": "Aktiver websøgning",
+	"Enable Web Search Query Generation": "Aktiver query generation med websøgning",
+	"Enabled": "Aktiveret",
+	"Engine": "engine",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Sørg for at din CSV-fil indeholder 4 kolonner in denne rækkefølge: Name, Email, Password, Role.",
+	"Enter {{role}} message here": "Indtast {{role}} besked her",
+	"Enter a detail about yourself for your LLMs to recall": "Indtast en detalje om dig selv, som dine LLMs kan huske",
+	"Enter api auth string (e.g. username:password)": "Indtast api-godkendelsesstreng (f.eks. brugernavn:adgangskode)",
+	"Enter Brave Search API Key": "Indtast Brave Search API-nøgle",
+	"Enter CFG Scale (e.g. 7.0)": "Indtast CFG-skala (f.eks. 7.0)",
+	"Enter Chunk Overlap": "Indtast overlapning af tekststykker",
+	"Enter Chunk Size": "Indtast størrelse af tekststykker",
+	"Enter description": "",
+	"Enter Github Raw URL": "Indtast Github Raw URL",
+	"Enter Google PSE API Key": "Indtast Google PSE API-nøgle",
+	"Enter Google PSE Engine Id": "Indtast Google PSE Engine ID",
+	"Enter Image Size (e.g. 512x512)": "Indtast billedstørrelse (f.eks. 512x512)",
+	"Enter language codes": "Indtast sprogkoder",
+	"Enter Model ID": "Indtast model-ID",
+	"Enter model tag (e.g. {{modelTag}})": "Indtast modelmærke (f.eks. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Indtast antal trin (f.eks. 50)",
+	"Enter Sampler (e.g. Euler a)": "Indtast sampler (f.eks. Euler a)",
+	"Enter Scheduler (e.g. Karras)": "Indtast scheduler (f.eks. Karras)",
+	"Enter Score": "Indtast score",
+	"Enter SearchApi API Key": "Indtast SearchApi API-nøgle",
+	"Enter SearchApi Engine": "Indtast SearchApi-engine",
+	"Enter Searxng Query URL": "Indtast Searxng-forespørgsels-URL",
+	"Enter Serper API Key": "Indtast Serper API-nøgle",
+	"Enter Serply API Key": "Indtast Serply API-nøgle",
+	"Enter Serpstack API Key": "Indtast Serpstack API-nøgle",
+	"Enter stop sequence": "Indtast stopsekvens",
+	"Enter system prompt": "Indtast systemprompt",
+	"Enter Tavily API Key": "Indtast Tavily API-nøgle",
+	"Enter Tika Server URL": "Indtast Tika Server URL",
+	"Enter Top K": "Indtast Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Indtast URL (f.eks. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Indtast URL (f.eks. http://localhost:11434)",
+	"Enter Your Email": "Indtast din e-mail",
+	"Enter Your Full Name": "Indtast dit fulde navn",
+	"Enter your message": "Indtast din besked",
+	"Enter Your Password": "Indtast din adgangskode",
+	"Enter Your Role": "Indtast din rolle",
+	"Error": "Fejl",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Eksperimentel",
+	"Export": "Eksportér",
+	"Export All Chats (All Users)": "Eksportér alle chats (alle brugere)",
+	"Export chat (.json)": "Eksportér chat (.json)",
+	"Export Chats": "Eksportér chats",
+	"Export Config to JSON File": "Eksportér konfiguration til JSON-fil",
+	"Export Functions": "Eksportér funktioner",
+	"Export LiteLLM config.yaml": "Eksportér LiteLLM config.yaml",
+	"Export Models": "Eksportér modeller",
+	"Export Prompts": "Eksportér prompts",
+	"Export Tools": "Eksportér værktøjer",
+	"External Models": "Eksterne modeller",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Kunne ikke oprette API-nøgle.",
+	"Failed to read clipboard contents": "Kunne ikke læse indholdet af udklipsholderen",
+	"Failed to update settings": "Kunne ikke opdatere indstillinger",
+	"Failed to upload file.": "Kunne ikke uploade fil.",
+	"February": "Februar",
+	"Feedback History": "",
+	"Feel free to add specific details": "Du er velkommen til at tilføje specifikke detaljer",
+	"File": "Fil",
+	"File added successfully.": "Fil tilføjet.",
+	"File content updated successfully.": "Filens indhold er opdateret.",
+	"File Mode": "Filtilstand",
+	"File not found.": "Filen blev ikke fundet.",
+	"File removed successfully.": "Fil fjernet.",
+	"File size should not exceed {{maxSize}} MB.": "Filstørrelsen må ikke overstige {{maxSize}} MB.",
+	"Files": "Filer",
+	"Filter is now globally disabled": "Filter er nu globalt deaktiveret",
+	"Filter is now globally enabled": "Filter er nu globalt aktiveret",
+	"Filters": "Filtre",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingeraftryksspoofing registreret: Kan ikke bruge initialer som avatar. Bruger standard profilbillede.",
+	"Fluidly stream large external response chunks": "Stream store eksterne svar chunks flydende",
+	"Focus chat input": "Fokuser på chatinput",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Fulgte instruktionerne perfekt",
+	"Form": "Formular",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Hyppighedsstraf",
+	"Function": "",
+	"Function created successfully": "Funktion oprettet.",
+	"Function deleted successfully": "Funktion slettet.",
+	"Function Description (e.g. A filter to remove profanity from text)": "Funktionsbeskrivelse (f.eks. et filter til at fjerne bandeord fra tekst)",
+	"Function ID (e.g. my_filter)": "Funktions-ID (f.eks. mit_filter)",
+	"Function is now globally disabled": "Funktionen er nu globalt deaktiveret",
+	"Function is now globally enabled": "Funktionen er nu globalt aktiveret",
+	"Function Name (e.g. My Filter)": "Funktionsnavn (f.eks. Mit filter)",
+	"Function updated successfully": "Funktion opdateret.",
+	"Functions": "Funktioner",
+	"Functions allow arbitrary code execution": "Funktioner tillader kørsel af vilkårlig kode",
+	"Functions allow arbitrary code execution.": "Funktioner tillader kørsel af vilkårlig kode.",
+	"Functions imported successfully": "Funktioner importeret.",
+	"General": "Generelt",
+	"General Settings": "Generelle indstillinger",
+	"Generate Image": "Generer billede",
+	"Generating search query": "Genererer søgeforespørgsel",
+	"Generation Info": "Genereringsinfo",
+	"Get up and running with": "Kom i gang med",
+	"Global": "Global",
+	"Good Response": "Godt svar",
+	"Google PSE API Key": "Google PSE API-nøgle",
+	"Google PSE Engine Id": "Google PSE Engine-ID",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "Haptisk feedback",
+	"has no conversations.": "har ingen samtaler.",
+	"Hello, {{name}}": "Hej {{name}}",
+	"Help": "Hjælp",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Skjul",
+	"Hide Model": "Skjul model",
+	"How can I help you today?": "Hvordan kan jeg hjælpe dig i dag?",
+	"Hybrid Search": "Hybrid søgning",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Jeg anerkender, at jeg har læst og forstået konsekvenserne af min handling. Jeg er opmærksom på de risici, der er forbundet med at udføre vilkårlig kode, og jeg har verificeret kildens troværdighed.",
+	"ID": "",
+	"Image Generation (Experimental)": "Billedgenerering (eksperimentel)",
+	"Image Generation Engine": "Billedgenereringsengine",
+	"Image Settings": "Billedindstillinger",
+	"Images": "Billeder",
+	"Import Chats": "Importer chats",
+	"Import Config from JSON File": "Importer konfiguration fra JSON-fil",
+	"Import Functions": "Importer funktioner",
+	"Import Models": "Importer modeller",
+	"Import Prompts": "Importer prompts",
+	"Import Tools": "Importer værktøjer",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Inkluder `--api-auth` flag, når du kører stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Inkluder `--api` flag, når du kører stable-diffusion-webui",
+	"Info": "Info",
+	"Input commands": "Inputkommandoer",
+	"Install from Github URL": "Installer fra Github URL",
+	"Instant Auto-Send After Voice Transcription": "Øjeblikkelig automatisk afsendelse efter stemmetransskription",
+	"Interface": "Grænseflade",
+	"Invalid file format.": "",
+	"Invalid Tag": "Ugyldigt tag",
+	"January": "Januar",
+	"join our Discord for help.": "tilslut dig vores Discord for at få hjælp.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON-forhåndsvisning",
+	"July": "Juli",
+	"June": "Juni",
+	"JWT Expiration": "JWT-udløb",
+	"JWT Token": "JWT-token",
+	"Keep Alive": "Hold i live",
+	"Keyboard shortcuts": "Tastaturgenveje",
+	"Knowledge": "Viden",
+	"Knowledge created successfully.": "Viden oprettet.",
+	"Knowledge deleted successfully.": "Viden slettet.",
+	"Knowledge reset successfully.": "Viden nulstillet.",
+	"Knowledge updated successfully": "Viden opdateret.",
+	"Landing Page Mode": "Landing Page-tilstand",
+	"Language": "Sprog",
+	"large language models, locally.": "store sprogmodeller, lokalt.",
+	"Last Active": "Sidst aktiv",
+	"Last Modified": "Sidst ændret",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "Lad stå tomt for ubegrænset",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "Lad stå tomt for at bruge standardprompten, eller indtast en brugerdefineret prompt",
+	"Light": "Lys",
+	"Listening...": "Lytter...",
+	"LLMs can make mistakes. Verify important information.": "LLM'er kan lave fejl. Bekræft vigtige oplysninger.",
+	"Local Models": "Lokale modeller",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Lavet af OpenWebUI Community",
+	"Make sure to enclose them with": "Sørg for at omslutte dem med",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "Sørg for at eksportere en workflow.json-fil som API-format fra ComfyUI.",
+	"Manage": "Administrer",
+	"Manage Arena Models": "",
+	"Manage Models": "Administrer modeller",
+	"Manage Ollama Models": "Administrer Ollama-modeller",
+	"Manage Pipelines": "Administrer pipelines",
+	"March": "Marts",
+	"Max Tokens (num_predict)": "Maks. tokens (num_predict)",
+	"Max Upload Count": "Maks. uploadantal",
+	"Max Upload Size": "Maks. uploadstørrelse",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Højst 3 modeller kan downloades samtidigt. Prøv igen senere.",
+	"May": "Maj",
+	"Memories accessible by LLMs will be shown here.": "Minder, der er tilgængelige for LLM'er, vises her.",
+	"Memory": "Hukommelse",
+	"Memory added successfully": "Hukommelse tilføjet.",
+	"Memory cleared successfully": "Hukommelse ryddet.",
+	"Memory deleted successfully": "Hukommelse slettet.",
+	"Memory updated successfully": "Hukommelse opdateret.",
+	"Merge Responses": "Flet svar",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Beskeder, du sender efter at have oprettet dit link, deles ikke. Brugere med URL'en vil kunne se den delte chat.",
+	"Min P": "Min P",
+	"Minimum Score": "Minimumscore",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' er blevet downloadet.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' er allerede i kø til download.",
+	"Model {{modelId}} not found": "Model {{modelId}} ikke fundet",
+	"Model {{modelName}} is not vision capable": "Model {{modelName}} understøtter ikke billeder",
+	"Model {{name}} is now {{status}}": "Model {{name}} er nu {{status}}",
+	"Model {{name}} is now at the top": "Model {{name}} er nu øverst",
+	"Model accepts image inputs": "Model accepterer billedinput",
+	"Model created successfully!": "Model oprettet!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model filsystemsti registreret. Modelkortnavn er påkrævet til opdatering, kan ikke fortsætte.",
+	"Model ID": "Model-ID",
+	"Model Name": "",
+	"Model not selected": "Model ikke valgt",
+	"Model Params": "Modelparametre",
+	"Model updated successfully": "Model opdateret.",
+	"Model Whitelisting": "Modelhvidliste",
+	"Model(s) Whitelisted": "Model(ler) hvidlistet",
+	"Modelfile Content": "Modelfilindhold",
+	"Models": "Modeller",
+	"more": "",
+	"More": "Mere",
+	"Move to Top": "Flyt til toppen",
+	"Name": "Navn",
+	"Name your model": "Navngiv din model",
+	"New Chat": "Ny chat",
+	"New folder": "",
+	"New Password": "Ny adgangskode",
+	"No content found": "",
+	"No content to speak": "Intet indhold at tale",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Ingen fil valgt",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "Intet HTML-, CSS- eller JavaScript-indhold fundet.",
+	"No knowledge found": "Ingen viden fundet",
+	"No models found": "",
+	"No results found": "Ingen resultater fundet",
+	"No search query generated": "Ingen søgeforespørgsel genereret",
+	"No source available": "Ingen kilde tilgængelig",
+	"No valves to update": "Ingen ventiler at opdatere",
+	"None": "Ingen",
+	"Not factually correct": "Ikke faktuelt korrekt",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Bemærk: Hvis du angiver en minimumscore, returnerer søgningen kun dokumenter med en score, der er større end eller lig med minimumscoren.",
+	"Notes": "",
+	"Notifications": "Notifikationer",
+	"November": "November",
+	"num_gpu (Ollama)": "num_gpu (Ollama)",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth-ID",
+	"October": "Oktober",
+	"Off": "Fra",
+	"Okay, Let's Go!": "Okay, lad os gå!",
+	"OLED Dark": "OLED Mørk",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API deaktiveret",
+	"Ollama API is disabled": "Ollama API er deaktiveret",
+	"Ollama Version": "Ollama-version",
+	"On": "Til",
+	"Only": "Kun",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Kun alfanumeriske tegn og bindestreger er tilladt i kommandostrengen.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Kun samlinger kan redigeres, opret en ny vidensbase for at redigere/tilføje dokumenter.",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ups! URL'en ser ud til at være ugyldig. Tjek den igen, og prøv igen.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Du bruger en metode, der ikke understøttes (kun frontend). Kør WebUI fra backend.",
+	"Open file": "Åbn fil",
+	"Open in full screen": "Åbn i fuld skærm",
+	"Open new chat": "Åbn ny chat",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI-version (v{{OPEN_WEBUI_VERSION}}) er lavere end den krævede version (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API-konfiguration",
+	"OpenAI API Key is required.": "OpenAI API-nøgle er påkrævet.",
+	"OpenAI URL/Key required.": "OpenAI URL/nøgle påkrævet.",
+	"or": "eller",
+	"Other": "Andet",
+	"OUTPUT": "",
+	"Output format": "Outputformat",
+	"Overview": "Oversigt",
+	"page": "side",
+	"Password": "Adgangskode",
+	"PDF document (.pdf)": "PDF-dokument (.pdf)",
+	"PDF Extract Images (OCR)": "Udtræk billeder fra PDF (OCR)",
+	"pending": "afventer",
+	"Permission denied when accessing media devices": "Tilladelse nægtet ved adgang til medieenheder",
+	"Permission denied when accessing microphone": "Tilladelse nægtet ved adgang til mikrofon",
+	"Permission denied when accessing microphone: {{error}}": "Tilladelse nægtet ved adgang til mikrofon: {{error}}",
+	"Personalization": "Personalisering",
+	"Pin": "Fastgør",
+	"Pinned": "Fastgjort",
+	"Pipeline deleted successfully": "Pipeline slettet.",
+	"Pipeline downloaded successfully": "Pipeline downloadet.",
+	"Pipelines": "Pipelines",
+	"Pipelines Not Detected": "Pipelines ikke registreret",
+	"Pipelines Valves": "Pipelines-ventiler",
+	"Plain text (.txt)": "Almindelig tekst (.txt)",
+	"Playground": "Legeplads",
+	"Please carefully review the following warnings:": "Gennemgå omhyggeligt følgende advarsler:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "Udfyld alle felter.",
+	"Please select a reason": "Vælg en årsag",
+	"Positive attitude": "Positiv holdning",
+	"Previous 30 days": "Seneste 30 dage",
+	"Previous 7 days": "Seneste 7 dage",
+	"Profile Image": "Profilbillede",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (f.eks. Fortæl mig en sjov kendsgerning om Romerriget)",
+	"Prompt Content": "Promptindhold",
+	"Prompt suggestions": "Promptforslag",
+	"Prompts": "Prompts",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Hent \"{{searchValue}}\" fra Ollama.com",
+	"Pull a model from Ollama.com": "Hent en model fra Ollama.com",
+	"Query Params": "Forespørgselsparametre",
+	"RAG Template": "RAG-skabelon",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Læs højt",
+	"Record voice": "Optag stemme",
+	"Redirecting you to OpenWebUI Community": "Omdirigerer dig til OpenWebUI Community",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Referer til dig selv som \"Bruger\" (f.eks. \"Bruger lærer spansk\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Afvist, når den ikke burde have været det",
+	"Regenerate": "Regenerer",
+	"Release Notes": "Udgivelsesnoter",
+	"Relevance": "",
+	"Remove": "Fjern",
+	"Remove Model": "Fjern model",
+	"Rename": "Omdøb",
+	"Repeat Last N": "Gentag sidste N",
+	"Request Mode": "Forespørgselstilstand",
+	"Reranking Model": "Omarrangeringsmodel",
+	"Reranking model disabled": "Omarrangeringsmodel deaktiveret",
+	"Reranking model set to \"{{reranking_model}}\"": "Omarrangeringsmodel sat til \"{{reranking_model}}\"",
+	"Reset": "Nulstil",
+	"Reset Upload Directory": "Nulstil uploadmappe",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Automatisk kopiering af svar til udklipsholder",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Svarnotifikationer kan ikke aktiveres, da webstedets tilladelser er blevet nægtet. Besøg dine browserindstillinger for at give den nødvendige adgang.",
+	"Response splitting": "Svaropdeling",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rolle",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "Kør",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Kør Llama 2, Code Llama og andre modeller. Tilpas og opret dine egne.",
+	"Running": "Kører",
+	"Save": "Gem",
+	"Save & Create": "Gem og opret",
+	"Save & Update": "Gem og opdater",
+	"Save As Copy": "Gem som kopi",
+	"Save Tag": "Gem tag",
+	"Saved": "Gemt",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Lagring af chatlogs direkte i din browsers lager understøttes ikke længere. Download og slet dine chatlogs ved at klikke på knappen nedenfor. Du kan nemt importere dine chatlogs til backend igennem",
+	"Scroll to bottom when switching between branches": "Rul til bunden, når du skifter mellem grene",
+	"Search": "Søg",
+	"Search a model": "Søg efter en model",
+	"Search Chats": "Søg i chats",
+	"Search Collection": "Søg i samling",
+	"search for tags": "",
+	"Search Functions": "Søg i funktioner",
+	"Search Knowledge": "Søg i viden",
+	"Search Models": "Søg i modeller",
+	"Search Prompts": "Søg i prompts",
+	"Search Query Generation Prompt": "Prompt til generering af søgeforespørgsel",
+	"Search Result Count": "Antal søgeresultater",
+	"Search Tools": "Søg i værktøjer",
+	"SearchApi API Key": "SearchApi API-nøgle",
+	"SearchApi Engine": "SearchApi-engine",
+	"Searched {{count}} sites_one": "Søgte {{count}} websted",
+	"Searched {{count}} sites_other": "Søgte {{count}} websteder",
+	"Searching \"{{searchQuery}}\"": "Søger efter \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "Søger i viden efter \"{{searchQuery}}\"",
+	"Searxng Query URL": "Searxng forespørgsels-URL",
+	"See readme.md for instructions": "Se readme.md for instruktioner",
+	"See what's new": "Se, hvad der er nyt",
+	"Seed": "Seed",
+	"Select a base model": "Vælg en basemodel",
+	"Select a engine": "Vælg en engine",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Vælg en funktion",
+	"Select a model": "Vælg en model",
+	"Select a pipeline": "Vælg en pipeline",
+	"Select a pipeline url": "Vælg en pipeline-URL",
+	"Select a tool": "Vælg et værktøj",
+	"Select an Ollama instance": "Vælg en Ollama-instans",
+	"Select Engine": "Vælg engine",
+	"Select Knowledge": "Vælg viden",
+	"Select model": "Vælg model",
+	"Select only one model to call": "Vælg kun én model at kalde",
+	"Selected model(s) do not support image inputs": "Valgte model(ler) understøtter ikke billedinput",
+	"Semantic distance to query": "",
+	"Send": "Send",
+	"Send a Message": "Send en besked",
+	"Send message": "Send besked",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "Sender `stream_options: { include_usage: true }` i forespørgslen.\nUnderstøttede udbydere vil returnere tokenforbrugsinformation i svaret, når det er indstillet.",
+	"September": "September",
+	"Serper API Key": "Serper API-nøgle",
+	"Serply API Key": "Serply API-nøgle",
+	"Serpstack API Key": "Serpstack API-nøgle",
+	"Server connection verified": "Serverforbindelse bekræftet",
+	"Set as default": "Indstil som standard",
+	"Set CFG Scale": "Indstil CFG-skala",
+	"Set Default Model": "Indstil standardmodel",
+	"Set embedding model (e.g. {{model}})": "Indstil indlejringsmodel (f.eks. {{model}})",
+	"Set Image Size": "Indstil billedstørrelse",
+	"Set reranking model (e.g. {{model}})": "Indstil omarrangeringsmodel (f.eks. {{model}})",
+	"Set Sampler": "Indstil sampler",
+	"Set Scheduler": "Indstil scheduler",
+	"Set Steps": "Indstil trin",
+	"Set Task Model": "Indstil opgavemodel",
+	"Set Voice": "Indstil stemme",
+	"Set whisper model": "",
+	"Settings": "Indstillinger",
+	"Settings saved successfully!": "Indstillinger gemt!",
+	"Share": "Del",
+	"Share Chat": "Del chat",
+	"Share to OpenWebUI Community": "Del til OpenWebUI Community",
+	"short-summary": "kort opsummering",
+	"Show": "Vis",
+	"Show Admin Details in Account Pending Overlay": "Vis administratordetaljer i overlay for ventende konto",
+	"Show Model": "Vis model",
+	"Show shortcuts": "Vis genveje",
+	"Show your support!": "Vis din støtte!",
+	"Showcased creativity": "Udstillet kreativitet",
+	"Sign in": "Log ind",
+	"Sign in to {{WEBUI_NAME}}": "Log ind på {{WEBUI_NAME}}",
+	"Sign Out": "Log ud",
+	"Sign up": "Tilmeld dig",
+	"Sign up to {{WEBUI_NAME}}": "Tilmeld dig {{WEBUI_NAME}}",
+	"Signing in to {{WEBUI_NAME}}": "Logger ind på {{WEBUI_NAME}}",
+	"Source": "Kilde",
+	"Speech Playback Speed": "Talehastighed",
+	"Speech recognition error: {{error}}": "Talegenkendelsesfejl: {{error}}",
+	"Speech-to-Text Engine": "Tale-til-tekst-engine",
+	"Stop": "",
+	"Stop Sequence": "Stopsekvens",
+	"Stream Chat Response": "Stream chatsvar",
+	"STT Model": "STT-model",
+	"STT Settings": "STT-indstillinger",
+	"Subtitle (e.g. about the Roman Empire)": "Undertitel (f.eks. om Romerriget)",
+	"Success": "Succes",
+	"Successfully updated.": "Opdateret.",
+	"Suggested": "Foreslået",
+	"Support": "Support",
+	"Support this plugin:": "Støt dette plugin:",
+	"Sync directory": "Synkroniser mappe",
+	"System": "System",
+	"System Instructions": "",
+	"System Prompt": "Systemprompt",
+	"Tags": "Tags",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Tryk for at afbryde",
+	"Tavily API Key": "Tavily API-nøgle",
+	"Tell us more:": "Fortæl os mere:",
+	"Temperature": "Temperatur",
+	"Template": "Skabelon",
+	"Temporary Chat": "Midlertidig chat",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Tekst-til-tale-engine",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Tak for din feedback!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Udviklerne bag dette plugin er passionerede frivillige fra fællesskabet. Hvis du finder dette plugin nyttigt, kan du overveje at bidrage til dets udvikling.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "Den maksimale filstørrelse i MB. Hvis filstørrelsen overstiger denne grænse, uploades filen ikke.",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "Det maksimale antal filer, der kan bruges på én gang i chatten. Hvis antallet af filer overstiger denne grænse, uploades filerne ikke.",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Scoren skal være en værdi mellem 0,0 (0%) og 1,0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "Tænker...",
+	"This action cannot be undone. Do you wish to continue?": "Denne handling kan ikke fortrydes. Vil du fortsætte?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dette sikrer, at dine værdifulde samtaler gemmes sikkert i din backend-database. Tak!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dette er en eksperimentel funktion, den fungerer muligvis ikke som forventet og kan ændres når som helst.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Denne indstilling sletter alle eksisterende filer i samlingen og erstatter dem med nyligt uploadede filer.",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Dette vil slette",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "Dette vil nulstille vidensbasen og synkronisere alle filer. Vil du fortsætte?",
+	"Thorough explanation": "Grundig forklaring",
+	"Tika": "Tika",
+	"Tika Server URL required.": "Tika-server-URL påkrævet.",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tip: Opdater flere variabelpladser fortløbende ved at trykke på tabulatortasten i chatinput efter hver udskiftning.",
+	"Title": "Titel",
+	"Title (e.g. Tell me a fun fact)": "Titel (f.eks. Fortæl mig en sjov kendsgerning)",
+	"Title Auto-Generation": "Automatisk titelgenerering",
+	"Title cannot be an empty string.": "Titel kan ikke være en tom streng.",
+	"Title Generation Prompt": "Prompt til titelgenerering",
+	"To access the available model names for downloading,": "For at få adgang til de tilgængelige modelnavne til download,",
+	"To access the GGUF models available for downloading,": "For at få adgang til de GGUF-modeller, der er tilgængelige til download,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "For at få adgang til WebUI skal du kontakte administratoren. Administratorer kan administrere brugerstatus fra administrationspanelet.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "For at vedhæfte vidensbase her skal du først tilføje dem til \"Viden\"-arbejdsområdet.",
+	"to chat input.": "til chatinput.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "For at vælge handlinger her skal du først tilføje dem til \"Funktioner\"-arbejdsområdet.",
+	"To select filters here, add them to the \"Functions\" workspace first.": "For at vælge filtre her skal du først tilføje dem til \"Funktioner\"-arbejdsområdet.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "For at vælge værktøjssæt her skal du først tilføje dem til \"Værktøjer\"-arbejdsområdet.",
+	"Toast notifications for new updates": "",
+	"Today": "I dag",
+	"Toggle settings": "Skift indstillinger",
+	"Toggle sidebar": "Skift sidebjælke",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Tokens, der skal beholdes ved kontekstopdatering (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Værktøj oprettet.",
+	"Tool deleted successfully": "Værktøj slettet.",
+	"Tool imported successfully": "Værktøj importeret.",
+	"Tool updated successfully": "Værktøj opdateret.",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Værktøjssætbeskrivelse (f.eks. et værktøjssæt til at udføre forskellige handlinger)",
+	"Toolkit ID (e.g. my_toolkit)": "Værktøjssæt-ID (f.eks. mit_værktøjssæt)",
+	"Toolkit Name (e.g. My ToolKit)": "Værktøjssætnavn (f.eks. Mit værktøjssæt)",
+	"Tools": "Værktøjer",
+	"Tools are a function calling system with arbitrary code execution": "Værktøjer er et funktionkaldssystem med vilkårlig kodeudførelse",
+	"Tools have a function calling system that allows arbitrary code execution": "Værktøjer har et funktionkaldssystem, der tillader vilkårlig kodeudførelse",
+	"Tools have a function calling system that allows arbitrary code execution.": "Værktøjer har et funktionkaldssystem, der tillader vilkårlig kodeudførelse.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problemer med at få adgang til Ollama?",
+	"TTS Model": "TTS-model",
+	"TTS Settings": "TTS-indstillinger",
+	"TTS Voice": "TTS-stemme",
+	"Type": "Type",
+	"Type Hugging Face Resolve (Download) URL": "Indtast Hugging Face Resolve (Download) URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Ups! Der opstod et problem med at oprette forbindelse til {{provider}}.",
+	"UI": "UI",
+	"Unpin": "Frigør",
+	"Untagged": "",
+	"Update": "Opdater",
+	"Update and Copy Link": "Opdater og kopier link",
+	"Update for the latest features and improvements.": "Opdater for at få de nyeste funktioner og forbedringer.",
+	"Update password": "Opdater adgangskode",
+	"Updated": "",
+	"Updated at": "Opdateret kl.",
+	"Updated At": "",
+	"Upload": "Upload",
+	"Upload a GGUF model": "Upload en GGUF-model",
+	"Upload directory": "Uploadmappe",
+	"Upload files": "Upload filer",
+	"Upload Files": "Upload filer",
+	"Upload Pipeline": "Upload pipeline",
+	"Upload Progress": "Uploadfremdrift",
+	"URL Mode": "URL-tilstand",
+	"Use '#' in the prompt input to load and include your knowledge.": "Brug '#' i promptinput for at indlæse og inkludere din viden.",
+	"Use Gravatar": "Brug Gravatar",
+	"Use Initials": "Brug initialer",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "bruger",
+	"User": "",
+	"User location successfully retrieved.": "Brugerplacering hentet.",
+	"User Permissions": "Brugertilladelser",
+	"Users": "Brugere",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Anvend",
+	"Valid time units:": "Gyldige tidsenheder:",
+	"Valves": "Ventiler",
+	"Valves updated": "Ventiler opdateret",
+	"Valves updated successfully": "Ventiler opdateret.",
+	"variable": "variabel",
+	"variable to have them replaced with clipboard content.": "variabel for at få dem erstattet med indholdet af udklipsholderen.",
+	"Version": "Version",
+	"Version {{selectedVersion}} of {{totalVersions}}": "Version {{selectedVersion}} af {{totalVersions}}",
+	"Voice": "Stemme",
+	"Voice Input": "",
+	"Warning": "Advarsel",
+	"Warning:": "Advarsel:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Advarsel: Hvis du opdaterer eller ændrer din indlejringsmodel, skal du importere alle dokumenter igen.",
+	"Web": "Web",
+	"Web API": "Web API",
+	"Web Loader Settings": "Web Loader-indstillinger",
+	"Web Search": "Websøgning",
+	"Web Search Engine": "Websøgemaskine",
+	"Webhook URL": "Webhook-URL",
+	"WebUI Settings": "WebUI-indstillinger",
+	"WebUI will make requests to": "WebUI vil sende forespørgsler til",
+	"What’s New in": "Nyheder i",
+	"Whisper (Local)": "Whisper (lokal)",
+	"Widescreen Mode": "Widescreen-tilstand",
+	"Won": "",
+	"Workspace": "Arbejdsområde",
+	"Write a prompt suggestion (e.g. Who are you?)": "Skriv et promptforslag (f.eks. Hvem er du?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Skriv en opsummering på 50 ord, der opsummerer [emne eller søgeord].",
+	"Write something...": "",
+	"Yesterday": "I går",
+	"You": "Du",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Du kan kun chatte med maksimalt {{maxCount}} fil(er) ad gangen.",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Du kan personliggøre dine interaktioner med LLM'er ved at tilføje minder via knappen 'Administrer' nedenfor, hvilket gør dem mere nyttige og skræddersyet til dig.",
+	"You cannot clone a base model": "Du kan ikke klone en basemodel",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Du har ingen arkiverede samtaler.",
+	"You have shared this chat": "Du har delt denne chat",
+	"You're a helpful assistant.": "Du er en hjælpsom assistent.",
+	"You're now logged in.": "Du er nu logget ind.",
+	"Your account status is currently pending activation.": "Din kontostatus afventer i øjeblikket aktivering.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Hele dit bidrag går direkte til plugin-udvikleren; Open WebUI tager ikke nogen procentdel. Den valgte finansieringsplatform kan dog have sine egne gebyrer.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube Loader-indstillinger"
+}
diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..b877f94b0ea1d6cfab90ec9b0186b5a92bd4390f
--- /dev/null
+++ b/src/lib/i18n/locales/de-DE/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' oder '-1' für keine Ablaufzeit.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(z. B. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(z. B. `sh webui.sh --api`)",
+	"(latest)": "(neueste)",
+	"{{ models }}": "{{ Modelle }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Sie können ein Basismodell nicht löschen",
+	"{{user}}'s Chats": "{{user}}s Unterhaltungen",
+	"{{webUIName}} Backend Required": "{{webUIName}}-Backend erforderlich",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Aufgabenmodelle können Unterhaltungstitel oder Websuchanfragen generieren.",
+	"a user": "ein Benutzer",
+	"About": "Über",
+	"Account": "Konto",
+	"Account Activation Pending": "Kontoaktivierung ausstehend",
+	"Accurate information": "Präzise Information(en)",
+	"Actions": "",
+	"Active Users": "Aktive Benutzer",
+	"Add": "Hinzufügen",
+	"Add a model id": "Modell-ID hinzufügen",
+	"Add a short description about what this model does": "Fügen Sie eine kurze Beschreibung über dieses Modell hinzu",
+	"Add a short title for this prompt": "Fügen Sie einen kurzen Titel für diesen Prompt hinzu",
+	"Add a tag": "Tag hinzufügen",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Benutzerdefinierten Prompt hinzufügen",
+	"Add Files": "Dateien hinzufügen",
+	"Add Memory": "Erinnerung hinzufügen",
+	"Add Model": "Modell hinzufügen",
+	"Add Tag": "Tag hinzufügen",
+	"Add Tags": "Tags hinzufügen",
+	"Add text content": "",
+	"Add User": "Benutzer hinzufügen",
+	"Adjusting these settings will apply changes universally to all users.": "Das Anpassen dieser Einstellungen wird Änderungen universell auf alle Benutzer anwenden.",
+	"admin": "Administrator",
+	"Admin": "Administrator",
+	"Admin Panel": "Administrationsbereich",
+	"Admin Settings": "Administrationsbereich",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Administratoren haben jederzeit Zugriff auf alle Werkzeuge. Benutzer können im Arbeitsbereich zugewiesen.",
+	"Advanced Parameters": "Erweiterte Parameter",
+	"Advanced Params": "Erweiterte Parameter",
+	"All chats": "",
+	"All Documents": "Alle Dokumente",
+	"Allow Chat Deletion": "Unterhaltungen löschen erlauben",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Nicht-lokale Stimmen erlauben",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "Standort freigeben",
+	"Allow Voice Interruption in Call": "Unterbrechung durch Stimme im Anruf zulassen",
+	"alphanumeric characters and hyphens": "alphanumerische Zeichen und Bindestriche",
+	"Already have an account?": "Haben Sie bereits einen Account?",
+	"an assistant": "ein Assistent",
+	"and": "und",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "und erstellen Sie einen neuen freigegebenen Link.",
+	"API Base URL": "API-Basis-URL",
+	"API Key": "API-Schlüssel",
+	"API Key created.": "API-Schlüssel erstellt.",
+	"API keys": "API-Schlüssel",
+	"April": "April",
+	"Archive": "Archivieren",
+	"Archive All Chats": "Alle Unterhaltungen archivieren",
+	"Archived Chats": "Archivierte Unterhaltungen",
+	"are allowed - Activate this command by typing": "sind erlaubt — Aktivieren Sie diesen Befehl durch Eingabe von",
+	"Are you sure?": "Sind Sie sicher?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Datei anhängen",
+	"Attention to detail": "Aufmerksamkeit für Details",
+	"Audio": "Audio",
+	"August": "August",
+	"Auto-playback response": "Antwort automatisch abspielen",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111-API-Authentifizierungszeichenfolge",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111-Basis-URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111-Basis-URL ist erforderlich.",
+	"Available list": "",
+	"available!": "Verfügbar!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Zurück",
+	"Bad Response": "Schlechte Antwort",
+	"Banners": "Banner",
+	"Base Model (From)": "Basismodell (From)",
+	"Batch Size (num_batch)": "Stapelgröße (num_batch)",
+	"before": "bereits geteilt",
+	"Being lazy": "Faulheit",
+	"Brave Search API Key": "Brave Search API-Schlüssel",
+	"Bypass SSL verification for Websites": "SSL-Überprüfung für Webseiten umgehen",
+	"Call": "Anrufen",
+	"Call feature is not supported when using Web STT engine": "Die Anruffunktion wird nicht unterstützt, wenn die Web-STT-Engine verwendet wird.",
+	"Camera": "Kamera",
+	"Cancel": "Abbrechen",
+	"Capabilities": "Fähigkeiten",
+	"Change Password": "Passwort ändern",
+	"Character": "",
+	"Chat": "Gespräch",
+	"Chat Background Image": "Hintergrundbild des Unterhaltungsfensters",
+	"Chat Bubble UI": "Chat Bubble UI",
+	"Chat Controls": "",
+	"Chat direction": "Textrichtung",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Unterhaltungen",
+	"Check Again": "Erneut überprüfen",
+	"Check for updates": "Nach Updates suchen",
+	"Checking for updates...": "Sucht nach Updates...",
+	"Choose a model before saving...": "Wählen Sie ein Modell, bevor Sie speichern...",
+	"Chunk Overlap": "Blocküberlappung",
+	"Chunk Params": "Blockparameter",
+	"Chunk Size": "Blockgröße",
+	"Citation": "Zitate",
+	"Clear memory": "Alle Erinnerungen entfernen",
+	"Click here for help.": "Klicken Sie hier für Hilfe.",
+	"Click here to": "Klicke Sie hier, um",
+	"Click here to download user import template file.": "Klicken Sie hier, um die Vorlage für den Benutzerimport herunterzuladen.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Klicke Sie zum Auswählen hier",
+	"Click here to select a csv file.": "Klicken Sie zum Auswählen einer CSV-Datei hier.",
+	"Click here to select a py file.": "Klicken Sie zum Auswählen einer py-Datei hier.",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "hier klicken.",
+	"Click on the user role button to change a user's role.": "Klicken Sie auf die Benutzerrolle, um sie zu ändern.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Schreibberechtigung für die Zwischenablage verweigert. Bitte überprüfen Sie Ihre Browsereinstellungen, um den erforderlichen Zugriff zu erlauben.",
+	"Clone": "Klonen",
+	"Close": "Schließen",
+	"Code execution": "",
+	"Code formatted successfully": "Code erfolgreich formatiert",
+	"Collection": "Kollektion",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI-Basis-URL",
+	"ComfyUI Base URL is required.": "ComfyUI-Basis-URL wird benötigt.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Befehl",
+	"Completions": "",
+	"Concurrent Requests": "Anzahl gleichzeitiger Anfragen",
+	"Confirm": "Bestätigen",
+	"Confirm Password": "Passwort bestätigen",
+	"Confirm your action": "Bestätigen Sie Ihre Aktion.",
+	"Connections": "Verbindungen",
+	"Contact Admin for WebUI Access": "Kontaktieren Sie den Administrator für den Zugriff auf die Weboberfläche",
+	"Content": "Info",
+	"Content Extraction": "Inhaltsextraktion",
+	"Context Length": "Kontextlänge",
+	"Continue Response": "Antwort fortsetzen",
+	"Continue with {{provider}}": "Mit {{provider}} fortfahren",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "Freigabelink in die Zwischenablage kopiert!",
+	"Copied to clipboard": "",
+	"Copy": "Kopieren",
+	"Copy last code block": "Letzten Codeblock kopieren",
+	"Copy last response": "Letzte Antwort kopieren",
+	"Copy Link": "Link kopieren",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Das Kopieren in die Zwischenablage war erfolgreich!",
+	"Create a model": "Ein Modell erstellen",
+	"Create Account": "Konto erstellen",
+	"Create Knowledge": "",
+	"Create new key": "Neuen Schlüssel erstellen",
+	"Create new secret key": "Neuen API-Schlüssel erstellen",
+	"Created at": "Erstellt am",
+	"Created At": "Erstellt am",
+	"Created by": "Erstellt von",
+	"CSV Import": "CSV-Import",
+	"Current Model": "Aktuelles Modell",
+	"Current Password": "Aktuelles Passwort",
+	"Custom": "Benutzerdefiniert",
+	"Customize models for a specific purpose": "Modelle für einen bestimmten Zweck anpassen",
+	"Dark": "Dunkel",
+	"Dashboard": "Übersicht",
+	"Database": "Datenbank",
+	"December": "Dezember",
+	"Default": "Standard",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
+	"Default Model": "Standardmodell",
+	"Default model updated": "Standardmodell aktualisiert",
+	"Default Prompt Suggestions": "Prompt-Vorschläge",
+	"Default User Role": "Standardbenutzerrolle",
+	"Delete": "Löschen",
+	"Delete a model": "Ein Modell löschen",
+	"Delete All Chats": "Alle Unterhaltungen löschen",
+	"Delete chat": "Unterhaltung löschen",
+	"Delete Chat": "Unterhaltung löschen",
+	"Delete chat?": "Unterhaltung löschen?",
+	"Delete folder?": "",
+	"Delete function?": "Funktion löschen?",
+	"Delete prompt?": "Prompt löschen?",
+	"delete this link": "diesen Link löschen",
+	"Delete tool?": "Werkzeug löschen?",
+	"Delete User": "Benutzer löschen",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} gelöscht",
+	"Deleted {{name}}": "{{name}} gelöscht",
+	"Description": "Beschreibung",
+	"Didn't fully follow instructions": "Nicht genau den Answeisungen gefolgt",
+	"Disabled": "",
+	"Discover a function": "Entdecken Sie weitere Funktionen",
+	"Discover a model": "Entdecken Sie weitere Modelle",
+	"Discover a prompt": "Entdecken Sie weitere Prompts",
+	"Discover a tool": "Entdecken Sie weitere Werkzeuge",
+	"Discover, download, and explore custom functions": "Entdecken und beziehen Sie benutzerdefinierte Funktionen",
+	"Discover, download, and explore custom prompts": "Entdecken und beziehen Sie benutzerdefinierte Prompts",
+	"Discover, download, and explore custom tools": "Entdecken und beziehen Sie benutzerdefinierte Werkzeuge",
+	"Discover, download, and explore model presets": "Entdecken und beziehen Sie benutzerdefinierte Modellvorlagen",
+	"Dismissible": "ausblendbar",
+	"Display Emoji in Call": "Emojis im Anruf anzeigen",
+	"Display the username instead of You in the Chat": "Soll \"Sie\" durch Ihren Benutzernamen ersetzt werden?",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Dokument",
+	"Documentation": "Dokumentation",
+	"Documents": "Dokumente",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "stellt keine externen Verbindungen her, und Ihre Daten bleiben sicher auf Ihrem lokal gehosteten Server.",
+	"Don't have an account?": "Haben Sie noch kein Benutzerkonto?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "schlechter Schreibstil",
+	"Done": "Erledigt",
+	"Download": "Exportieren",
+	"Download canceled": "Exportierung abgebrochen",
+	"Download Database": "Datenbank exportieren",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Ziehen Sie beliebige Dateien hierher, um sie der Unterhaltung hinzuzufügen",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "z. B. '30s','10m'. Gültige Zeiteinheiten sind 's', 'm', 'h'.",
+	"Edit": "Bearbeiten",
+	"Edit Arena Model": "",
+	"Edit Memory": "Erinnerungen bearbeiten",
+	"Edit User": "Benutzer bearbeiten",
+	"ElevenLabs": "",
+	"Email": "E-Mail",
+	"Embedding Batch Size": "Embedding-Stapelgröße",
+	"Embedding Model": "Embedding-Modell",
+	"Embedding Model Engine": "Embedding-Modell-Engine",
+	"Embedding model set to \"{{embedding_model}}\"": "Embedding-Modell auf \"{{embedding_model}}\" gesetzt",
+	"Enable Community Sharing": "Community-Freigabe aktivieren",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Registrierung erlauben",
+	"Enable Web Search": "Websuche aktivieren",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "Engine",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Stellen Sie sicher, dass Ihre CSV-Datei 4 Spalten in dieser Reihenfolge enthält: Name, E-Mail, Passwort, Rolle.",
+	"Enter {{role}} message here": "Geben Sie die {{role}}-Nachricht hier ein",
+	"Enter a detail about yourself for your LLMs to recall": "Geben Sie ein Detail über sich selbst ein, das Ihre Sprachmodelle (LLMs) sich merken sollen",
+	"Enter api auth string (e.g. username:password)": "Geben Sie die API-Authentifizierungszeichenfolge ein (z. B. Benutzername:Passwort)",
+	"Enter Brave Search API Key": "Geben Sie den Brave Search API-Schlüssel ein",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Geben Sie die Blocküberlappung ein",
+	"Enter Chunk Size": "Geben Sie die Blockgröße ein",
+	"Enter description": "",
+	"Enter Github Raw URL": "Geben Sie die Github Raw-URL ein",
+	"Enter Google PSE API Key": "Geben Sie den Google PSE-API-Schlüssel ein",
+	"Enter Google PSE Engine Id": "Geben Sie die Google PSE-Engine-ID ein",
+	"Enter Image Size (e.g. 512x512)": "Geben Sie die Bildgröße ein (z. B. 512x512)",
+	"Enter language codes": "Geben Sie die Sprachcodes ein",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Gebn Sie den Model-Tag ein",
+	"Enter Number of Steps (e.g. 50)": "Geben Sie die Anzahl an Schritten ein (z. B. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Punktzahl eingeben",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Geben Sie die Searxng-Abfrage-URL ein",
+	"Enter Serper API Key": "Geben Sie den Serper-API-Schlüssel ein",
+	"Enter Serply API Key": "Geben Sie den",
+	"Enter Serpstack API Key": "Geben Sie den Serpstack-API-Schlüssel ein",
+	"Enter stop sequence": "Stop-Sequenz eingeben",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "Geben Sie den Tavily-API-Schlüssel ein",
+	"Enter Tika Server URL": "Geben Sie die Tika-Server-URL ein",
+	"Enter Top K": "Geben Sie Top K ein",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Geben Sie die URL ein (z. B. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Geben Sie die URL ein (z. B. http://localhost:11434)",
+	"Enter Your Email": "Geben Sie Ihre E-Mail-Adresse ein",
+	"Enter Your Full Name": "Geben Sie Ihren vollständigen Namen ein",
+	"Enter your message": "",
+	"Enter Your Password": "Geben Sie Ihr Passwort ein",
+	"Enter Your Role": "Geben Sie Ihre Rolle ein",
+	"Error": "Fehler",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Experimentell",
+	"Export": "Exportieren",
+	"Export All Chats (All Users)": "Alle Unterhaltungen exportieren (alle Benutzer)",
+	"Export chat (.json)": "Unterhaltung exportieren (.json)",
+	"Export Chats": "Unterhaltungen exportieren",
+	"Export Config to JSON File": "",
+	"Export Functions": "Funktionen exportieren",
+	"Export LiteLLM config.yaml": "LiteLLM-Konfiguration exportieren (config.yaml)",
+	"Export Models": "Modelle exportieren",
+	"Export Prompts": "Prompts exportieren",
+	"Export Tools": "Werkzeuge exportieren",
+	"External Models": "Externe Modelle",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Fehler beim Erstellen des API-Schlüssels.",
+	"Failed to read clipboard contents": "Fehler beim Abruf der Zwischenablage",
+	"Failed to update settings": "Fehler beim Aktualisieren der Einstellungen",
+	"Failed to upload file.": "",
+	"February": "Februar",
+	"Feedback History": "",
+	"Feel free to add specific details": "Fühlen Sie sich frei, spezifische Details hinzuzufügen",
+	"File": "Datei",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Datei-Modus",
+	"File not found.": "Datei nicht gefunden.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "Filter ist jetzt global deaktiviert",
+	"Filter is now globally enabled": "Filter ist jetzt global aktiviert",
+	"Filters": "Filter",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingerabdruck-Spoofing erkannt: Initialen können nicht als Avatar verwendet werden. Standard-Avatar wird verwendet.",
+	"Fluidly stream large external response chunks": "Nahtlose Übertragung großer externer Antwortabschnitte",
+	"Focus chat input": "Chat-Eingabe fokussieren",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Anweisungen perfekt befolgt",
+	"Form": "Formular",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Frequenzstrafe",
+	"Function": "",
+	"Function created successfully": "Funktion erfolgreich erstellt",
+	"Function deleted successfully": "Funktion erfolgreich gelöscht",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "Funktion erfolgreich aktualisiert",
+	"Functions": "Funktionen",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "Funktionen erfolgreich importiert",
+	"General": "Allgemein",
+	"General Settings": "Allgemeine Einstellungen",
+	"Generate Image": "Bild erzeugen",
+	"Generating search query": "Suchanfrage wird erstellt",
+	"Generation Info": "Generierungsinformationen",
+	"Get up and running with": "",
+	"Global": "Global",
+	"Good Response": "Gute Antwort",
+	"Google PSE API Key": "Google PSE-API-Schlüssel",
+	"Google PSE Engine Id": "Google PSE-Engine-ID",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "hat keine Unterhaltungen.",
+	"Hello, {{name}}": "Hallo, {{name}}",
+	"Help": "Hilfe",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Verbergen",
+	"Hide Model": "Modell ausblenden",
+	"How can I help you today?": "Wie kann ich Ihnen heute helfen?",
+	"Hybrid Search": "Hybride Suche",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Bildgenerierung (experimentell)",
+	"Image Generation Engine": "Bildgenerierungs-Engine",
+	"Image Settings": "Bildeinstellungen",
+	"Images": "Bilder",
+	"Import Chats": "Unterhaltungen importieren",
+	"Import Config from JSON File": "",
+	"Import Functions": "Funktionen importieren",
+	"Import Models": "Modelle importieren",
+	"Import Prompts": "Prompts importieren",
+	"Import Tools": "Werkzeuge importieren",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Fügen Sie beim Ausführen von stable-diffusion-webui die Option `--api-auth` hinzu",
+	"Include `--api` flag when running stable-diffusion-webui": "Fügen Sie beim Ausführen von stable-diffusion-webui die Option `--api` hinzu",
+	"Info": "Info",
+	"Input commands": "Eingabebefehle",
+	"Install from Github URL": "Installiere von der Github-URL",
+	"Instant Auto-Send After Voice Transcription": "Spracherkennung direkt absenden",
+	"Interface": "Benutzeroberfläche",
+	"Invalid file format.": "",
+	"Invalid Tag": "Ungültiger Tag",
+	"January": "Januar",
+	"join our Discord for help.": "Treten Sie unserem Discord bei, um Hilfe zu erhalten.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON-Vorschau",
+	"July": "Juli",
+	"June": "Juni",
+	"JWT Expiration": "JWT-Ablauf",
+	"JWT Token": "JWT-Token",
+	"Keep Alive": "Verbindung aufrechterhalten",
+	"Keyboard shortcuts": "Tastenkombinationen",
+	"Knowledge": "Wissen",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Sprache",
+	"large language models, locally.": "",
+	"Last Active": "Zuletzt aktiv",
+	"Last Modified": "Zuletzt bearbeitet",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Hell",
+	"Listening...": "Höre zu...",
+	"LLMs can make mistakes. Verify important information.": "LLMs können Fehler machen. Überprüfe wichtige Informationen.",
+	"Local Models": "Lokale Modelle",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Von der OpenWebUI-Community",
+	"Make sure to enclose them with": "Umschließe Variablen mit",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Verwalten",
+	"Manage Arena Models": "",
+	"Manage Models": "Modelle verwalten",
+	"Manage Ollama Models": "Ollama-Modelle verwalten",
+	"Manage Pipelines": "Pipelines verwalten",
+	"March": "März",
+	"Max Tokens (num_predict)": "Maximale Tokenanzahl (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es können maximal 3 Modelle gleichzeitig heruntergeladen werden. Bitte versuchen Sie es später erneut.",
+	"May": "Mai",
+	"Memories accessible by LLMs will be shown here.": "Erinnerungen, die für Modelle zugänglich sind, werden hier angezeigt.",
+	"Memory": "Erinnerungen",
+	"Memory added successfully": "Erinnerung erfolgreich hinzugefügt",
+	"Memory cleared successfully": "Erinnerung erfolgreich gelöscht",
+	"Memory deleted successfully": "Erinnerung erfolgreich gelöscht",
+	"Memory updated successfully": "Erinnerung erfolgreich aktualisiert",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Nachrichten, die Sie nach der Erstellung Ihres Links senden, werden nicht geteilt. Nutzer mit der URL können die freigegebene Unterhaltung einsehen.",
+	"Min P": "",
+	"Minimum Score": "Mindestpunktzahl",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "DD MMMM YYYY",
+	"MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY HH:mm A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Modell '{{modelName}}' wurde erfolgreich heruntergeladen.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange zum Herunterladen.",
+	"Model {{modelId}} not found": "Modell {{modelId}} nicht gefunden",
+	"Model {{modelName}} is not vision capable": "Das Modell {{modelName}} ist nicht für die Bildverarbeitung geeignet",
+	"Model {{name}} is now {{status}}": "Modell {{name}} ist jetzt {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "Modell erfolgreich erstellt!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modell-Dateisystempfad erkannt. Modellkurzname ist für das Update erforderlich, Fortsetzung nicht möglich.",
+	"Model ID": "Modell-ID",
+	"Model Name": "",
+	"Model not selected": "Modell nicht ausgewählt",
+	"Model Params": "Modell-Params",
+	"Model updated successfully": "Modell erfolgreich aktualisiert",
+	"Model Whitelisting": "Modell-Whitelisting",
+	"Model(s) Whitelisted": "Modell(e) auf der Whitelist",
+	"Modelfile Content": "Modelfile-Inhalt",
+	"Models": "Modelle",
+	"more": "mehr",
+	"More": "Mehr",
+	"Move to Top": "",
+	"Name": "Name",
+	"Name your model": "Benennen Sie Ihr Modell",
+	"New Chat": "Neue Unterhaltung",
+	"New folder": "",
+	"New Password": "Neues Passwort",
+	"No content found": "",
+	"No content to speak": "Kein Inhalt zum Vorlesen",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Keine Datei ausgewählt",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Keine Ergebnisse gefunden",
+	"No search query generated": "Keine Suchanfrage generiert",
+	"No source available": "Keine Quelle verfügbar",
+	"No valves to update": "Keine Valves zum Aktualisieren",
+	"None": "Nichts",
+	"Not factually correct": "Nicht sachlich korrekt",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Hinweis: Wenn Sie eine Mindestpunktzahl festlegen, werden in der Suche nur Dokumente mit einer Punktzahl größer oder gleich der Mindestpunktzahl zurückgegeben.",
+	"Notes": "",
+	"Notifications": "Benachrichtigungen",
+	"November": "November",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth-ID",
+	"October": "Oktober",
+	"Off": "Aus",
+	"Okay, Let's Go!": "Okay, los geht's!",
+	"OLED Dark": "OLED-Dunkel",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama-API",
+	"Ollama API disabled": "Ollama-API deaktiviert",
+	"Ollama API is disabled": "Ollama-API  ist deaktiviert.",
+	"Ollama Version": "Ollama-Version",
+	"On": "Ein",
+	"Only": "Nur",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "In der Befehlszeichenfolge sind nur alphanumerische Zeichen und Bindestriche erlaubt.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppla! Es scheint, dass die URL ungültig ist. Bitte überprüfen Sie diese und versuchen Sie es erneut.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppla! Sie verwenden eine nicht unterstützte Methode (nur Frontend). Bitte stellen Sie die WebUI vom Backend bereit.",
+	"Open file": "Datei öffnen",
+	"Open in full screen": "",
+	"Open new chat": "Neuen Chat öffnen",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Die installierte Open-WebUI-Version (v{{OPEN_WEBUI_VERSION}}) ist niedriger als die erforderliche Version (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI-API",
+	"OpenAI API Config": "OpenAI-API-Konfiguration",
+	"OpenAI API Key is required.": "OpenAI-API-Schlüssel erforderlich.",
+	"OpenAI URL/Key required.": "OpenAI-URL/Schlüssel erforderlich.",
+	"or": "oder",
+	"Other": "Andere",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "Seite",
+	"Password": "Passwort",
+	"PDF document (.pdf)": "PDF-Dokument (.pdf)",
+	"PDF Extract Images (OCR)": "Text von Bildern aus PDFs extrahieren (OCR)",
+	"pending": "ausstehend",
+	"Permission denied when accessing media devices": "Zugriff auf Mediengeräte verweigert",
+	"Permission denied when accessing microphone": "Zugriff auf das Mikrofon verweigert",
+	"Permission denied when accessing microphone: {{error}}": "Zugriff auf das Mikrofon verweigert: {{error}}",
+	"Personalization": "Personalisierung",
+	"Pin": "Anheften",
+	"Pinned": "Angeheftet",
+	"Pipeline deleted successfully": "Pipeline erfolgreich gelöscht",
+	"Pipeline downloaded successfully": "Pipeline erfolgreich heruntergeladen",
+	"Pipelines": "Pipelines",
+	"Pipelines Not Detected": "Pipelines nicht erkannt",
+	"Pipelines Valves": "Pipeline Valves",
+	"Plain text (.txt)": "Nur Text (.txt)",
+	"Playground": "Testumgebung",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Positive Einstellung",
+	"Previous 30 days": "Vorherige 30 Tage",
+	"Previous 7 days": "Vorherige 7 Tage",
+	"Profile Image": "Profilbild",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (z. B. \"Erzähle mir eine interessante Tatsache über das Römische Reich\")",
+	"Prompt Content": "Prompt-Inhalt",
+	"Prompt suggestions": "Prompt-Vorschläge",
+	"Prompts": "Prompts",
+	"Pull \"{{searchValue}}\" from Ollama.com": "\"{{searchValue}}\" von Ollama.com beziehen",
+	"Pull a model from Ollama.com": "Modell von Ollama.com beziehen",
+	"Query Params": "Abfrageparameter",
+	"RAG Template": "RAG-Vorlage",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Vorlesen",
+	"Record voice": "Stimme aufnehmen",
+	"Redirecting you to OpenWebUI Community": "Sie werden zur OpenWebUI-Community weitergeleitet",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "Referenzen aus",
+	"Refused when it shouldn't have": "Abgelehnt, obwohl es nicht hätte abgelehnt werden sollen",
+	"Regenerate": "Neu generieren",
+	"Release Notes": "Veröffentlichungshinweise",
+	"Relevance": "",
+	"Remove": "Entfernen",
+	"Remove Model": "Modell entfernen",
+	"Rename": "Umbenennen",
+	"Repeat Last N": "Wiederhole die letzten N",
+	"Request Mode": "Anforderungsmodus",
+	"Reranking Model": "Reranking-Modell",
+	"Reranking model disabled": "Reranking-Modell deaktiviert",
+	"Reranking model set to \"{{reranking_model}}\"": "Reranking-Modell \"{{reranking_model}}\" fesgelegt",
+	"Reset": "Zurücksetzen",
+	"Reset Upload Directory": "Upload-Verzeichnis zurücksetzen",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Antwort automatisch in die Zwischenablage kopieren",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Benachrichtigungen können nicht aktiviert werden, da die Website-Berechtigungen abgelehnt wurden. Bitte besuchen Sie Ihre Browser-Einstellungen, um den erforderlichen Zugriff zu gewähren.",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rolle",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "Läuft",
+	"Save": "Speichern",
+	"Save & Create": "Erstellen",
+	"Save & Update": "Aktualisieren",
+	"Save As Copy": "",
+	"Save Tag": "Tag speichern",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Das direkte Speichern von Unterhaltungen im Browser-Speicher wird nicht mehr unterstützt. Bitte nehmen Sie einen Moment Zeit, um Ihre Unterhaltungen zu exportieren und zu löschen, indem Sie auf die Schaltfläche unten klicken. Keine Sorge, Sie können Ihre Unterhaltungen problemlos über das Backend wieder importieren.",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Suchen",
+	"Search a model": "Modell suchen",
+	"Search Chats": "Unterhaltungen durchsuchen...",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Funktionen durchsuchen...",
+	"Search Knowledge": "",
+	"Search Models": "Modelle durchsuchen...",
+	"Search Prompts": "Prompts durchsuchen...",
+	"Search Query Generation Prompt": "Suchanfragengenerierungsvorlage",
+	"Search Result Count": "Anzahl der Suchergebnisse",
+	"Search Tools": "Werkzeuge durchsuchen...",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "{{count}} Seite durchsucht",
+	"Searched {{count}} sites_other": "{{count}} Seiten durchsucht",
+	"Searching \"{{searchQuery}}\"": "Suche nach \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng-Abfrage-URL",
+	"See readme.md for instructions": "Anleitung in readme.md anzeigen",
+	"See what's new": "Entdecken Sie die Neuigkeiten",
+	"Seed": "Seed",
+	"Select a base model": "Wählen Sie ein Basismodell",
+	"Select a engine": "Wählen Sie eine Engine",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Wählen Sie eine Funktion",
+	"Select a model": "Wählen Sie ein Modell",
+	"Select a pipeline": "Wählen Sie eine Pipeline",
+	"Select a pipeline url": "Wählen Sie eine Pipeline-URL",
+	"Select a tool": "Wählen Sie ein Werkzeug",
+	"Select an Ollama instance": "Wählen Sie eine Ollama-Instanz",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Modell auswählen",
+	"Select only one model to call": "Wählen Sie nur ein Modell zum Anrufen aus",
+	"Selected model(s) do not support image inputs": "Ihre ausgewählten Modelle unterstützen keine Bildeingaben",
+	"Semantic distance to query": "",
+	"Send": "Senden",
+	"Send a Message": "Eine Nachricht senden",
+	"Send message": "Nachricht senden",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "September",
+	"Serper API Key": "Serper-API-Schlüssel",
+	"Serply API Key": "Serply-API-Schlüssel",
+	"Serpstack API Key": "Serpstack-API-Schlüssel",
+	"Server connection verified": "Serververbindung überprüft",
+	"Set as default": "Als Standard festlegen",
+	"Set CFG Scale": "",
+	"Set Default Model": "Standardmodell festlegen",
+	"Set embedding model (e.g. {{model}})": "Einbettungsmodell festlegen (z. B. {{model}})",
+	"Set Image Size": "Bildgröße festlegen",
+	"Set reranking model (e.g. {{model}})": "Rerankingmodell festlegen (z. B. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Schrittgröße festlegen",
+	"Set Task Model": "Aufgabenmodell festlegen",
+	"Set Voice": "Stimme festlegen",
+	"Set whisper model": "",
+	"Settings": "Einstellungen",
+	"Settings saved successfully!": "Einstellungen erfolgreich gespeichert!",
+	"Share": "Teilen",
+	"Share Chat": "Unterhaltung teilen",
+	"Share to OpenWebUI Community": "Mit OpenWebUI Community teilen",
+	"short-summary": "kurze-zusammenfassung",
+	"Show": "Anzeigen",
+	"Show Admin Details in Account Pending Overlay": "Admin-Details im Account-Pending-Overlay anzeigen",
+	"Show Model": "Modell anzeigen",
+	"Show shortcuts": "Verknüpfungen anzeigen",
+	"Show your support!": "Zeigen Sie Ihre Unterstützung!",
+	"Showcased creativity": "Kreativität gezeigt",
+	"Sign in": "Anmelden",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Abmelden",
+	"Sign up": "Registrieren",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Quelle",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Spracherkennungsfehler: {{error}}",
+	"Speech-to-Text Engine": "Sprache-zu-Text-Engine",
+	"Stop": "",
+	"Stop Sequence": "Stop-Sequenz",
+	"Stream Chat Response": "",
+	"STT Model": "STT-Modell",
+	"STT Settings": "STT-Einstellungen",
+	"Subtitle (e.g. about the Roman Empire)": "Untertitel (z. B. über das Römische Reich)",
+	"Success": "Erfolg",
+	"Successfully updated.": "Erfolgreich aktualisiert.",
+	"Suggested": "Vorgeschlagen",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "System",
+	"System Instructions": "",
+	"System Prompt": "System-Prompt",
+	"Tags": "Tags",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Zum Unterbrechen tippen",
+	"Tavily API Key": "Tavily-API-Schlüssel",
+	"Tell us more:": "Erzähl uns mehr",
+	"Temperature": "Temperatur",
+	"Template": "Vorlage",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Text-zu-Sprache-Engine",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Danke für Ihr Feedback!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Die Punktzahl sollte ein Wert zwischen 0,0 (0 %) und 1,0 (100 %) sein.",
+	"Theme": "Design",
+	"Thinking...": "Denke nach...",
+	"This action cannot be undone. Do you wish to continue?": "Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie fortfahren?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dies stellt sicher, dass Ihre wertvollen Unterhaltungen sicher in Ihrer Backend-Datenbank gespeichert werden. Vielen Dank!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dies ist eine experimentelle Funktion, sie funktioniert möglicherweise nicht wie erwartet und kann jederzeit geändert werden.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Dies löscht",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Ausführliche Erklärung",
+	"Tika": "Tika",
+	"Tika Server URL required.": "Tika-Server-URL erforderlich.",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tipp: Aktualisieren Sie mehrere Variablenfelder nacheinander, indem Sie nach jedem Ersetzen die Tabulatortaste im Eingabefeld der Unterhaltung drücken.",
+	"Title": "Titel",
+	"Title (e.g. Tell me a fun fact)": "Titel (z. B. Erzähl mir einen lustigen Fakt)",
+	"Title Auto-Generation": "Unterhaltungstitel automatisch generieren",
+	"Title cannot be an empty string.": "Titel darf nicht leer sein.",
+	"Title Generation Prompt": "Prompt für Titelgenerierung",
+	"To access the available model names for downloading,": "Um auf die verfügbaren Modellnamen zuzugreifen,",
+	"To access the GGUF models available for downloading,": "Um auf die verfügbaren GGUF-Modelle zuzugreifen,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Um auf das WebUI zugreifen zu können, wenden Sie sich bitte an einen Administrator. Administratoren können den Benutzerstatus über das Admin-Panel verwalten.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "zum Eingabefeld der Unterhaltung.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Um Filter auszuwählen, fügen Sie diese zunächst dem Arbeitsbereich „Funktionen“ hinzu.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Um Toolkits auszuwählen, fügen Sie sie zunächst dem Arbeitsbereich „Werkzeuge“ hinzu.",
+	"Toast notifications for new updates": "",
+	"Today": "Heute",
+	"Toggle settings": "Einstellungen umschalten",
+	"Toggle sidebar": "Seitenleiste umschalten",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Beizubehaltende Tokens bei Kontextaktualisierung (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Werkzeug erfolgreich erstellt",
+	"Tool deleted successfully": "Werkzeug erfolgreich gelöscht",
+	"Tool imported successfully": "Werkzeug erfolgreich importiert",
+	"Tool updated successfully": "Werkzeug erfolgreich aktualisiert",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "Werkzeuge",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Probleme beim Zugriff auf Ollama?",
+	"TTS Model": "TTS-Modell",
+	"TTS Settings": "TTS-Einstellungen",
+	"TTS Voice": "TTS-Stimme",
+	"Type": "Art",
+	"Type Hugging Face Resolve (Download) URL": "Geben Sie die Hugging Face Resolve-URL ein",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Ups! Es gab ein Problem bei der Verbindung mit {{provider}}.",
+	"UI": "Oberfläche",
+	"Unpin": "Lösen",
+	"Untagged": "",
+	"Update": "Aktualisieren",
+	"Update and Copy Link": "Aktualisieren und Link kopieren",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Passwort aktualisieren",
+	"Updated": "",
+	"Updated at": "Aktualisiert am",
+	"Updated At": "",
+	"Upload": "Hochladen",
+	"Upload a GGUF model": "GGUF-Model hochladen",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Datei(en) hochladen",
+	"Upload Pipeline": "Pipeline hochladen",
+	"Upload Progress": "Hochladefortschritt",
+	"URL Mode": "URL-Modus",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Gravatar verwenden",
+	"Use Initials": "Initialen verwenden",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "Benutzer",
+	"User": "",
+	"User location successfully retrieved.": "Benutzerstandort erfolgreich ermittelt.",
+	"User Permissions": "Benutzerberechtigungen",
+	"Users": "Benutzer",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Verwende",
+	"Valid time units:": "Gültige Zeiteinheiten:",
+	"Valves": "Valves",
+	"Valves updated": "Valves aktualisiert",
+	"Valves updated successfully": "Valves erfolgreich aktualisiert",
+	"variable": "Variable",
+	"variable to have them replaced with clipboard content.": "Variable, um den Inhalt der Zwischenablage beim Nutzen des Prompts zu ersetzen.",
+	"Version": "Version",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Stimme",
+	"Voice Input": "",
+	"Warning": "Warnung",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Warnung: Wenn Sie das Einbettungsmodell aktualisieren oder ändern, müssen Sie alle Dokumente erneut importieren.",
+	"Web": "Web",
+	"Web API": "Web-API",
+	"Web Loader Settings": "Web Loader Einstellungen",
+	"Web Search": "Websuche",
+	"Web Search Engine": "Suchmaschine",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "WebUI-Einstellungen",
+	"WebUI will make requests to": "WebUI sendet Anfragen an:",
+	"What’s New in": "Neuigkeiten von",
+	"Whisper (Local)": "Whisper (lokal)",
+	"Widescreen Mode": "Breitbildmodus",
+	"Won": "",
+	"Workspace": "Arbeitsbereich",
+	"Write a prompt suggestion (e.g. Who are you?)": "Schreiben Sie einen Promptvorschlag (z. B. Wer sind Sie?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.",
+	"Write something...": "",
+	"Yesterday": "Gestern",
+	"You": "Sie",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Personalisieren Sie Interaktionen mit LLMs, indem Sie über die Schaltfläche \"Verwalten\" Erinnerungen hinzufügen.",
+	"You cannot clone a base model": "Sie können Basismodelle nicht klonen",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.",
+	"You have shared this chat": "Sie haben diese Unterhaltung geteilt",
+	"You're a helpful assistant.": "Du bist ein hilfreicher Assistent.",
+	"You're now logged in.": "Sie sind jetzt eingeloggt.",
+	"Your account status is currently pending activation.": "Ihr Kontostatus ist derzeit ausstehend und wartet auf Aktivierung.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "YouTube",
+	"Youtube Loader Settings": "YouTube-Ladeeinstellungen"
+}
diff --git a/src/lib/i18n/locales/dg-DG/translation.json b/src/lib/i18n/locales/dg-DG/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..8b707bb0409efd60f61e1f8931a7faa90ec96141
--- /dev/null
+++ b/src/lib/i18n/locales/dg-DG/translation.json
@@ -0,0 +1,853 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' or '-1' for no expire. Much permanent, very wow.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(such e.g. `sh webui.sh --api`)",
+	"(latest)": "(much latest)",
+	"{{ models }}": "",
+	"{{ owner }}: You cannot delete a base model": "",
+	"{{user}}'s Chats": "",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend Much Required",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
+	"a user": "such user",
+	"About": "Much About",
+	"Account": "Account",
+	"Account Activation Pending": "",
+	"Accurate information": "",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "",
+	"Add a model id": "",
+	"Add a short description about what this model does": "",
+	"Add a short title for this prompt": "Add short title for this prompt",
+	"Add a tag": "Add such tag",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "",
+	"Add Files": "Add Files",
+	"Add Memory": "",
+	"Add Model": "",
+	"Add Tag": "",
+	"Add Tags": "",
+	"Add text content": "",
+	"Add User": "",
+	"Adjusting these settings will apply changes universally to all users.": "Adjusting these settings will apply changes to all users. Such universal, very wow.",
+	"admin": "admin",
+	"Admin": "",
+	"Admin Panel": "Admin Panel",
+	"Admin Settings": "Admin Settings",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "Advanced Parameters",
+	"Advanced Params": "",
+	"All chats": "",
+	"All Documents": "",
+	"Allow Chat Deletion": "Allow Delete Chats",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "so alpha, many hyphen",
+	"Already have an account?": "Such account exists?",
+	"an assistant": "such assistant",
+	"and": "and",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "",
+	"API Base URL": "API Base URL",
+	"API Key": "API Key",
+	"API Key created.": "",
+	"API keys": "",
+	"April": "",
+	"Archive": "",
+	"Archive All Chats": "",
+	"Archived Chats": "",
+	"are allowed - Activate this command by typing": "are allowed. Activate typing",
+	"Are you sure?": "Such certainty?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Attach file",
+	"Attention to detail": "",
+	"Audio": "Audio",
+	"August": "",
+	"Auto-playback response": "Auto-playback response",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Base URL is required.",
+	"Available list": "",
+	"available!": "available! So excite!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Back",
+	"Bad Response": "",
+	"Banners": "",
+	"Base Model (From)": "",
+	"Batch Size (num_batch)": "",
+	"before": "",
+	"Being lazy": "",
+	"Brave Search API Key": "",
+	"Bypass SSL verification for Websites": "",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "Cancel",
+	"Capabilities": "",
+	"Change Password": "Change Password",
+	"Character": "",
+	"Chat": "Chat",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "",
+	"Chat Controls": "",
+	"Chat direction": "",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Chats",
+	"Check Again": "Check Again",
+	"Check for updates": "Check for updates",
+	"Checking for updates...": "Checking for updates... Such anticipation...",
+	"Choose a model before saving...": "Choose model before saving... Wow choose first.",
+	"Chunk Overlap": "Chunk Overlap",
+	"Chunk Params": "Chunk Params",
+	"Chunk Size": "Chunk Size",
+	"Citation": "",
+	"Clear memory": "",
+	"Click here for help.": "Click for help. Much assist.",
+	"Click here to": "",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Click to select",
+	"Click here to select a csv file.": "",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "click here. Such click.",
+	"Click on the user role button to change a user's role.": "Click user role button to change role.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "",
+	"Close": "Close",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Collection",
+	"ComfyUI": "",
+	"ComfyUI Base URL": "",
+	"ComfyUI Base URL is required.": "",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Command",
+	"Completions": "",
+	"Concurrent Requests": "",
+	"Confirm": "",
+	"Confirm Password": "Confirm Password",
+	"Confirm your action": "",
+	"Connections": "Connections",
+	"Contact Admin for WebUI Access": "",
+	"Content": "Content",
+	"Content Extraction": "",
+	"Context Length": "Context Length",
+	"Continue Response": "",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "",
+	"Copied to clipboard": "",
+	"Copy": "",
+	"Copy last code block": "Copy last code block",
+	"Copy last response": "Copy last response",
+	"Copy Link": "",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Copying to clipboard was success! Very success!",
+	"Create a model": "",
+	"Create Account": "Create Account",
+	"Create Knowledge": "",
+	"Create new key": "",
+	"Create new secret key": "",
+	"Created at": "Created at",
+	"Created At": "",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "Current Model",
+	"Current Password": "Current Password",
+	"Custom": "Custom",
+	"Customize models for a specific purpose": "",
+	"Dark": "Dark",
+	"Dashboard": "",
+	"Database": "Database",
+	"December": "",
+	"Default": "Default",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "",
+	"Default Model": "",
+	"Default model updated": "Default model much updated",
+	"Default Prompt Suggestions": "Default Prompt Suggestions",
+	"Default User Role": "Default User Role",
+	"Delete": "",
+	"Delete a model": "Delete a model",
+	"Delete All Chats": "",
+	"Delete chat": "Delete chat",
+	"Delete Chat": "",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "",
+	"Delete tool?": "",
+	"Delete User": "",
+	"Deleted {{deleteModelTag}}": "Deleted {{deleteModelTag}}",
+	"Deleted {{name}}": "",
+	"Description": "Description",
+	"Didn't fully follow instructions": "",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "",
+	"Discover a prompt": "Discover a prompt",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Discover, download, and explore custom prompts",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Discover, download, and explore model presets",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "Display username instead of You in Chat",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Document",
+	"Documentation": "",
+	"Documents": "Documents",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "does not connect external, data stays safe locally.",
+	"Don't have an account?": "No account? Much sad.",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "",
+	"Done": "",
+	"Download": "",
+	"Download canceled": "",
+	"Download Database": "Download Database",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Drop files here to add to conversation",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "e.g. '30s','10m'. Much time units are 's', 'm', 'h'.",
+	"Edit": "",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "Edit Wowser",
+	"ElevenLabs": "",
+	"Email": "Email",
+	"Embedding Batch Size": "",
+	"Embedding Model": "",
+	"Embedding Model Engine": "",
+	"Embedding model set to \"{{embedding_model}}\"": "",
+	"Enable Community Sharing": "",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Enable New Bark Ups",
+	"Enable Web Search": "",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
+	"Enter {{role}} message here": "Enter {{role}} bork here",
+	"Enter a detail about yourself for your LLMs to recall": "",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Enter Overlap of Chunks",
+	"Enter Chunk Size": "Enter Size of Chunk",
+	"Enter description": "",
+	"Enter Github Raw URL": "",
+	"Enter Google PSE API Key": "",
+	"Enter Google PSE Engine Id": "",
+	"Enter Image Size (e.g. 512x512)": "Enter Size of Wow (e.g. 512x512)",
+	"Enter language codes": "",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Enter model doge tag (e.g. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Enter Number of Steps (e.g. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "",
+	"Enter Serper API Key": "",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "",
+	"Enter stop sequence": "Enter stop bark",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Enter Top Wow",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Enter URL (e.g. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "",
+	"Enter Your Email": "Enter Your Dogemail",
+	"Enter Your Full Name": "Enter Your Full Wow",
+	"Enter your message": "",
+	"Enter Your Password": "Enter Your Barkword",
+	"Enter Your Role": "",
+	"Error": "",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Much Experiment",
+	"Export": "",
+	"Export All Chats (All Users)": "Export All Chats (All Doggos)",
+	"Export chat (.json)": "",
+	"Export Chats": "Export Barks",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "",
+	"Export Prompts": "Export Promptos",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "",
+	"Failed to read clipboard contents": "Failed to read clipboard borks",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "",
+	"Feedback History": "",
+	"Feel free to add specific details": "",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Bark Mode",
+	"File not found.": "Bark not found.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingerprint dogeing: Unable to use initials as avatar. Defaulting to default doge image.",
+	"Fluidly stream large external response chunks": "Fluidly wow big chunks",
+	"Focus chat input": "Focus chat bork",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Woweral",
+	"General Settings": "General Doge Settings",
+	"Generate Image": "",
+	"Generating search query": "",
+	"Generation Info": "",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "",
+	"Google PSE API Key": "",
+	"Google PSE Engine Id": "",
+	"h:mm a": "",
+	"Haptic Feedback": "",
+	"has no conversations.": "",
+	"Hello, {{name}}": "Much helo, {{name}}",
+	"Help": "",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Hide",
+	"Hide Model": "",
+	"How can I help you today?": "How can I halp u today?",
+	"Hybrid Search": "",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Image Wow (Much Experiment)",
+	"Image Generation Engine": "Image Engine",
+	"Image Settings": "Settings for Wowmage",
+	"Images": "Wowmages",
+	"Import Chats": "Import Barks",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "",
+	"Import Prompts": "Import Promptos",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Include `--api` flag when running stable-diffusion-webui",
+	"Info": "",
+	"Input commands": "Input commands",
+	"Install from Github URL": "",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "Interface",
+	"Invalid file format.": "",
+	"Invalid Tag": "",
+	"January": "",
+	"join our Discord for help.": "join our Discord for help.",
+	"JSON": "JSON",
+	"JSON Preview": "",
+	"July": "",
+	"June": "",
+	"JWT Expiration": "JWT Expire",
+	"JWT Token": "JWT Borken",
+	"Keep Alive": "Keep Wow",
+	"Keyboard shortcuts": "Keyboard Barkcuts",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Doge Speak",
+	"large language models, locally.": "",
+	"Last Active": "",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Light",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "LLMs can make borks. Verify important info.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "",
+	"Made by OpenWebUI Community": "Made by OpenWebUI Community",
+	"Make sure to enclose them with": "Make sure to enclose them with",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "Manage Wowdels",
+	"Manage Ollama Models": "Manage Ollama Wowdels",
+	"Manage Pipelines": "",
+	"March": "",
+	"Max Tokens (num_predict)": "",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maximum of 3 models can be downloaded simultaneously. Please try again later.",
+	"May": "",
+	"Memories accessible by LLMs will be shown here.": "",
+	"Memory": "",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "",
+	"Min P": "",
+	"Minimum Score": "",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' has been successfully downloaded.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' is already in queue for downloading.",
+	"Model {{modelId}} not found": "Model {{modelId}} not found",
+	"Model {{modelName}} is not vision capable": "",
+	"Model {{name}} is now {{status}}": "",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model filesystem bark detected. Model shortname is required for update, cannot continue.",
+	"Model ID": "",
+	"Model Name": "",
+	"Model not selected": "Model not selected",
+	"Model Params": "",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Wowdel Whitelisting",
+	"Model(s) Whitelisted": "Wowdel(s) Whitelisted",
+	"Modelfile Content": "Modelfile Content",
+	"Models": "Wowdels",
+	"more": "",
+	"More": "",
+	"Move to Top": "",
+	"Name": "Name",
+	"Name your model": "",
+	"New Chat": "New Bark",
+	"New folder": "",
+	"New Password": "New Barkword",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "",
+	"No search query generated": "",
+	"No source available": "No source available",
+	"No valves to update": "",
+	"None": "",
+	"Not factually correct": "",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
+	"Notes": "",
+	"Notifications": "Notifications",
+	"November": "",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "",
+	"OAuth ID": "",
+	"October": "",
+	"Off": "Off",
+	"Okay, Let's Go!": "Okay, Let's Go!",
+	"OLED Dark": "OLED Dark",
+	"Ollama": "",
+	"Ollama API": "",
+	"Ollama API disabled": "",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Ollama Version",
+	"On": "On",
+	"Only": "Only",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Only wow characters and hyphens are allowed in the bork string.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Looks like the URL is invalid. Please double-check and try again.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Open new bark",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "",
+	"OpenAI API Key is required.": "OpenAI Bark Key is required.",
+	"OpenAI URL/Key required.": "",
+	"or": "or",
+	"Other": "",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Barkword",
+	"PDF document (.pdf)": "",
+	"PDF Extract Images (OCR)": "PDF Extract Wowmages (OCR)",
+	"pending": "pending",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "Permission denied when accessing microphone: {{error}}",
+	"Personalization": "Personalization",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "",
+	"Plain text (.txt)": "Plain text (.txt)",
+	"Playground": "Playground",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "",
+	"Previous 30 days": "",
+	"Previous 7 days": "",
+	"Profile Image": "",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
+	"Prompt Content": "Prompt Content",
+	"Prompt suggestions": "Prompt wowgestions",
+	"Prompts": "Promptos",
+	"Pull \"{{searchValue}}\" from Ollama.com": "",
+	"Pull a model from Ollama.com": "Pull a wowdel from Ollama.com",
+	"Query Params": "Query Bark",
+	"RAG Template": "RAG Template",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "",
+	"Record voice": "Record Bark",
+	"Redirecting you to OpenWebUI Community": "Redirecting you to OpenWebUI Community",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "",
+	"Regenerate": "",
+	"Release Notes": "Release Borks",
+	"Relevance": "",
+	"Remove": "",
+	"Remove Model": "",
+	"Rename": "",
+	"Repeat Last N": "Repeat Last N",
+	"Request Mode": "Request Bark",
+	"Reranking Model": "",
+	"Reranking model disabled": "",
+	"Reranking model set to \"{{reranking_model}}\"": "",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Copy Bark Auto Bark",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Role",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "Save much wow",
+	"Save & Create": "Save & Create much create",
+	"Save & Update": "Save & Update much update",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Saving chat logs in browser storage not support anymore. Pls download and delete your chat logs by click button below. Much easy re-import to backend through",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Search very search",
+	"Search a model": "",
+	"Search Chats": "",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "",
+	"Search Prompts": "Search Prompts much wow",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "",
+	"Searched {{count}} sites_few": "",
+	"Searched {{count}} sites_many": "",
+	"Searched {{count}} sites_other": "",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "",
+	"See readme.md for instructions": "See readme.md for instructions wow",
+	"See what's new": "See what's new so amaze",
+	"Seed": "Seed very plant",
+	"Select a base model": "",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "Select a model much choice",
+	"Select a pipeline": "",
+	"Select a pipeline url": "",
+	"Select a tool": "",
+	"Select an Ollama instance": "Select an Ollama instance very choose",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Select model much choice",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "",
+	"Semantic distance to query": "",
+	"Send": "",
+	"Send a Message": "Send a Message much message",
+	"Send message": "Send message very send",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "",
+	"Serper API Key": "",
+	"Serply API Key": "",
+	"Serpstack API Key": "",
+	"Server connection verified": "Server connection verified much secure",
+	"Set as default": "Set as default very default",
+	"Set CFG Scale": "",
+	"Set Default Model": "Set Default Model much model",
+	"Set embedding model (e.g. {{model}})": "",
+	"Set Image Size": "Set Image Size very size",
+	"Set reranking model (e.g. {{model}})": "",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Set Steps so many steps",
+	"Set Task Model": "",
+	"Set Voice": "Set Voice so speak",
+	"Set whisper model": "",
+	"Settings": "Settings much settings",
+	"Settings saved successfully!": "Settings saved successfully! Very success!",
+	"Share": "",
+	"Share Chat": "",
+	"Share to OpenWebUI Community": "Share to OpenWebUI Community much community",
+	"short-summary": "short-summary so short",
+	"Show": "Show much show",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "Show shortcuts much shortcut",
+	"Show your support!": "",
+	"Showcased creativity": "",
+	"Sign in": "Sign in very sign",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Sign Out much logout",
+	"Sign up": "Sign up much join",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Source",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Speech recognition error: {{error}} so error",
+	"Speech-to-Text Engine": "Speech-to-Text Engine much speak",
+	"Stop": "",
+	"Stop Sequence": "Stop Sequence much stop",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "STT Settings very settings",
+	"Subtitle (e.g. about the Roman Empire)": "",
+	"Success": "Success very success",
+	"Successfully updated.": "Successfully updated. Very updated.",
+	"Suggested": "",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "System very system",
+	"System Instructions": "",
+	"System Prompt": "System Prompt much prompt",
+	"Tags": "Tags very tags",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "",
+	"Temperature": "Temperature very temp",
+	"Template": "Template much template",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Text-to-Speech Engine much speak",
+	"Tfs Z": "Tfs Z much Z",
+	"Thanks for your feedback!": "",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
+	"Theme": "Theme much theme",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "This ensures that your valuable conversations are securely saved to your backend database. Thank you! Much secure!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement. Much tip!",
+	"Title": "Title very title",
+	"Title (e.g. Tell me a fun fact)": "",
+	"Title Auto-Generation": "Title Auto-Generation much auto-gen",
+	"Title cannot be an empty string.": "",
+	"Title Generation Prompt": "Title Generation Prompt very prompt",
+	"To access the available model names for downloading,": "To access the available model names for downloading, much access",
+	"To access the GGUF models available for downloading,": "To access the GGUF models available for downloading, much access",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "to chat input. Very chat.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "",
+	"Toggle settings": "Toggle settings much toggle",
+	"Toggle sidebar": "Toggle sidebar much toggle",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K very top",
+	"Top P": "Top P very top",
+	"Trouble accessing Ollama?": "Trouble accessing Ollama? Much trouble?",
+	"TTS Model": "",
+	"TTS Settings": "TTS Settings much settings",
+	"TTS Voice": "",
+	"Type": "",
+	"Type Hugging Face Resolve (Download) URL": "Type Hugging Face Resolve (Download) URL much download",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! There was an issue connecting to {{provider}}. Much uh-oh!",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Update password much change",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Upload a GGUF model very upload",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "",
+	"Upload Pipeline": "",
+	"Upload Progress": "Upload Progress much progress",
+	"URL Mode": "URL Mode much mode",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Use Gravatar much avatar",
+	"Use Initials": "Use Initials much initial",
+	"use_mlock (Ollama)": "",
+	"use_mmap (Ollama)": "",
+	"user": "user much user",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "User Permissions much permissions",
+	"Users": "Users much users",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Utilize very use",
+	"Valid time units:": "Valid time units: much time",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "variable very variable",
+	"variable to have them replaced with clipboard content.": "variable to have them replaced with clipboard content. Very replace.",
+	"Version": "Version much version",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
+	"Web": "Web very web",
+	"Web API": "",
+	"Web Loader Settings": "",
+	"Web Search": "",
+	"Web Search Engine": "",
+	"Webhook URL": "",
+	"WebUI Settings": "WebUI Settings much settings",
+	"WebUI will make requests to": "WebUI will make requests to much request",
+	"What’s New in": "What’s New in much new",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "",
+	"Write a prompt suggestion (e.g. Who are you?)": "Write a prompt suggestion (e.g. Who are you?) much suggest",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Write a summary in 50 words that summarizes [topic or keyword]. Much summarize.",
+	"Write something...": "",
+	"Yesterday": "",
+	"You": "",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "",
+	"You have shared this chat": "",
+	"You're a helpful assistant.": "You're a helpful assistant. Much helpful.",
+	"You're now logged in.": "You're now logged in. Much logged.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "",
+	"Youtube Loader Settings": ""
+}
diff --git a/src/lib/i18n/locales/en-GB/translation.json b/src/lib/i18n/locales/en-GB/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..702b3e9aa46b4ac8a99542d9edf6a76c930e5758
--- /dev/null
+++ b/src/lib/i18n/locales/en-GB/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "",
+	"(latest)": "",
+	"{{ models }}": "",
+	"{{ owner }}: You cannot delete a base model": "",
+	"{{user}}'s Chats": "",
+	"{{webUIName}} Backend Required": "",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
+	"a user": "",
+	"About": "",
+	"Account": "",
+	"Account Activation Pending": "",
+	"Accurate information": "",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "",
+	"Add a model id": "",
+	"Add a short description about what this model does": "",
+	"Add a short title for this prompt": "",
+	"Add a tag": "",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "",
+	"Add Files": "",
+	"Add Memory": "",
+	"Add Model": "",
+	"Add Tag": "",
+	"Add Tags": "",
+	"Add text content": "",
+	"Add User": "",
+	"Adjusting these settings will apply changes universally to all users.": "",
+	"admin": "",
+	"Admin": "",
+	"Admin Panel": "",
+	"Admin Settings": "",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "",
+	"Advanced Params": "",
+	"All chats": "",
+	"All Documents": "",
+	"Allow Chat Deletion": "",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "",
+	"Already have an account?": "",
+	"an assistant": "",
+	"and": "",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "",
+	"API Base URL": "",
+	"API Key": "",
+	"API Key created.": "",
+	"API keys": "",
+	"April": "",
+	"Archive": "",
+	"Archive All Chats": "",
+	"Archived Chats": "",
+	"are allowed - Activate this command by typing": "",
+	"Are you sure?": "",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "",
+	"Attention to detail": "",
+	"Audio": "",
+	"August": "",
+	"Auto-playback response": "",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "",
+	"AUTOMATIC1111 Base URL is required.": "",
+	"Available list": "",
+	"available!": "",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "",
+	"Bad Response": "",
+	"Banners": "",
+	"Base Model (From)": "",
+	"Batch Size (num_batch)": "",
+	"before": "",
+	"Being lazy": "",
+	"Brave Search API Key": "",
+	"Bypass SSL verification for Websites": "",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "",
+	"Capabilities": "",
+	"Change Password": "",
+	"Character": "",
+	"Chat": "",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "",
+	"Chat Controls": "",
+	"Chat direction": "",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "",
+	"Check Again": "",
+	"Check for updates": "",
+	"Checking for updates...": "",
+	"Choose a model before saving...": "",
+	"Chunk Overlap": "",
+	"Chunk Params": "",
+	"Chunk Size": "",
+	"Citation": "",
+	"Clear memory": "",
+	"Click here for help.": "",
+	"Click here to": "",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "",
+	"Click here to select a csv file.": "",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "",
+	"Click on the user role button to change a user's role.": "",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "",
+	"Close": "",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "",
+	"ComfyUI": "",
+	"ComfyUI Base URL": "",
+	"ComfyUI Base URL is required.": "",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "",
+	"Completions": "",
+	"Concurrent Requests": "",
+	"Confirm": "",
+	"Confirm Password": "",
+	"Confirm your action": "",
+	"Connections": "",
+	"Contact Admin for WebUI Access": "",
+	"Content": "",
+	"Content Extraction": "",
+	"Context Length": "",
+	"Continue Response": "",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "",
+	"Copied to clipboard": "",
+	"Copy": "",
+	"Copy last code block": "",
+	"Copy last response": "",
+	"Copy Link": "",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "",
+	"Create a model": "",
+	"Create Account": "",
+	"Create Knowledge": "",
+	"Create new key": "",
+	"Create new secret key": "",
+	"Created at": "",
+	"Created At": "",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "",
+	"Current Password": "",
+	"Custom": "",
+	"Customize models for a specific purpose": "",
+	"Dark": "",
+	"Dashboard": "",
+	"Database": "",
+	"December": "",
+	"Default": "",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "",
+	"Default Model": "",
+	"Default model updated": "",
+	"Default Prompt Suggestions": "",
+	"Default User Role": "",
+	"Delete": "",
+	"Delete a model": "",
+	"Delete All Chats": "",
+	"Delete chat": "",
+	"Delete Chat": "",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "",
+	"Delete tool?": "",
+	"Delete User": "",
+	"Deleted {{deleteModelTag}}": "",
+	"Deleted {{name}}": "",
+	"Description": "",
+	"Didn't fully follow instructions": "",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "",
+	"Discover a prompt": "",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "",
+	"Documentation": "",
+	"Documents": "",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "",
+	"Don't have an account?": "",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "",
+	"Done": "",
+	"Download": "",
+	"Download canceled": "",
+	"Download Database": "",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "",
+	"Edit": "",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "",
+	"ElevenLabs": "",
+	"Email": "",
+	"Embedding Batch Size": "",
+	"Embedding Model": "",
+	"Embedding Model Engine": "",
+	"Embedding model set to \"{{embedding_model}}\"": "",
+	"Enable Community Sharing": "",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "",
+	"Enable Web Search": "",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
+	"Enter {{role}} message here": "",
+	"Enter a detail about yourself for your LLMs to recall": "",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "",
+	"Enter Chunk Size": "",
+	"Enter description": "",
+	"Enter Github Raw URL": "",
+	"Enter Google PSE API Key": "",
+	"Enter Google PSE Engine Id": "",
+	"Enter Image Size (e.g. 512x512)": "",
+	"Enter language codes": "",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "",
+	"Enter Number of Steps (e.g. 50)": "",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "",
+	"Enter Serper API Key": "",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "",
+	"Enter stop sequence": "",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "",
+	"Enter URL (e.g. http://localhost:11434)": "",
+	"Enter Your Email": "",
+	"Enter Your Full Name": "",
+	"Enter your message": "",
+	"Enter Your Password": "",
+	"Enter Your Role": "",
+	"Error": "",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "",
+	"Export": "",
+	"Export All Chats (All Users)": "",
+	"Export chat (.json)": "",
+	"Export Chats": "",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "",
+	"Export Prompts": "",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "",
+	"Failed to read clipboard contents": "",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "",
+	"Feedback History": "",
+	"Feel free to add specific details": "",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "",
+	"File not found.": "",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
+	"Fluidly stream large external response chunks": "",
+	"Focus chat input": "",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "",
+	"General Settings": "",
+	"Generate Image": "",
+	"Generating search query": "",
+	"Generation Info": "",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "",
+	"Google PSE API Key": "",
+	"Google PSE Engine Id": "",
+	"h:mm a": "",
+	"Haptic Feedback": "",
+	"has no conversations.": "",
+	"Hello, {{name}}": "",
+	"Help": "",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "",
+	"Hide Model": "",
+	"How can I help you today?": "",
+	"Hybrid Search": "",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "",
+	"Image Generation Engine": "",
+	"Image Settings": "",
+	"Images": "",
+	"Import Chats": "",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "",
+	"Import Prompts": "",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "",
+	"Info": "",
+	"Input commands": "",
+	"Install from Github URL": "",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "",
+	"Invalid file format.": "",
+	"Invalid Tag": "",
+	"January": "",
+	"join our Discord for help.": "",
+	"JSON": "",
+	"JSON Preview": "",
+	"July": "",
+	"June": "",
+	"JWT Expiration": "",
+	"JWT Token": "",
+	"Keep Alive": "",
+	"Keyboard shortcuts": "",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "",
+	"large language models, locally.": "",
+	"Last Active": "",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "",
+	"Made by OpenWebUI Community": "",
+	"Make sure to enclose them with": "",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "",
+	"Manage Ollama Models": "",
+	"Manage Pipelines": "",
+	"March": "",
+	"Max Tokens (num_predict)": "",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "",
+	"May": "",
+	"Memories accessible by LLMs will be shown here.": "",
+	"Memory": "",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "",
+	"Min P": "",
+	"Minimum Score": "",
+	"Mirostat": "",
+	"Mirostat Eta": "",
+	"Mirostat Tau": "",
+	"MMMM DD, YYYY": "",
+	"MMMM DD, YYYY HH:mm": "",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "",
+	"Model '{{modelTag}}' is already in queue for downloading.": "",
+	"Model {{modelId}} not found": "",
+	"Model {{modelName}} is not vision capable": "",
+	"Model {{name}} is now {{status}}": "",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
+	"Model ID": "",
+	"Model Name": "",
+	"Model not selected": "",
+	"Model Params": "",
+	"Model updated successfully": "",
+	"Model Whitelisting": "",
+	"Model(s) Whitelisted": "",
+	"Modelfile Content": "",
+	"Models": "",
+	"more": "",
+	"More": "",
+	"Move to Top": "",
+	"Name": "",
+	"Name your model": "",
+	"New Chat": "",
+	"New folder": "",
+	"New Password": "",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "",
+	"No search query generated": "",
+	"No source available": "",
+	"No valves to update": "",
+	"None": "",
+	"Not factually correct": "",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
+	"Notes": "",
+	"Notifications": "",
+	"November": "",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "",
+	"OAuth ID": "",
+	"October": "",
+	"Off": "",
+	"Okay, Let's Go!": "",
+	"OLED Dark": "",
+	"Ollama": "",
+	"Ollama API": "",
+	"Ollama API disabled": "",
+	"Ollama API is disabled": "",
+	"Ollama Version": "",
+	"On": "",
+	"Only": "",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "",
+	"OpenAI API": "",
+	"OpenAI API Config": "",
+	"OpenAI API Key is required.": "",
+	"OpenAI URL/Key required.": "",
+	"or": "",
+	"Other": "",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "",
+	"PDF document (.pdf)": "",
+	"PDF Extract Images (OCR)": "",
+	"pending": "",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "",
+	"Personalization": "",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "",
+	"Plain text (.txt)": "",
+	"Playground": "",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "",
+	"Previous 30 days": "",
+	"Previous 7 days": "",
+	"Profile Image": "",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
+	"Prompt Content": "",
+	"Prompt suggestions": "",
+	"Prompts": "",
+	"Pull \"{{searchValue}}\" from Ollama.com": "",
+	"Pull a model from Ollama.com": "",
+	"Query Params": "",
+	"RAG Template": "",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "",
+	"Record voice": "",
+	"Redirecting you to OpenWebUI Community": "",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "",
+	"Regenerate": "",
+	"Release Notes": "",
+	"Relevance": "",
+	"Remove": "",
+	"Remove Model": "",
+	"Rename": "",
+	"Repeat Last N": "",
+	"Request Mode": "",
+	"Reranking Model": "",
+	"Reranking model disabled": "",
+	"Reranking model set to \"{{reranking_model}}\"": "",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "",
+	"Rosé Pine": "",
+	"Rosé Pine Dawn": "",
+	"RTL": "",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "",
+	"Save & Create": "",
+	"Save & Update": "",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "",
+	"Search a model": "",
+	"Search Chats": "",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "",
+	"Search Prompts": "",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "",
+	"Searched {{count}} sites_other": "",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "",
+	"See readme.md for instructions": "",
+	"See what's new": "",
+	"Seed": "",
+	"Select a base model": "",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "",
+	"Select a pipeline": "",
+	"Select a pipeline url": "",
+	"Select a tool": "",
+	"Select an Ollama instance": "",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "",
+	"Semantic distance to query": "",
+	"Send": "",
+	"Send a Message": "",
+	"Send message": "",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "",
+	"Serper API Key": "",
+	"Serply API Key": "",
+	"Serpstack API Key": "",
+	"Server connection verified": "",
+	"Set as default": "",
+	"Set CFG Scale": "",
+	"Set Default Model": "",
+	"Set embedding model (e.g. {{model}})": "",
+	"Set Image Size": "",
+	"Set reranking model (e.g. {{model}})": "",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "",
+	"Set Task Model": "",
+	"Set Voice": "",
+	"Set whisper model": "",
+	"Settings": "",
+	"Settings saved successfully!": "",
+	"Share": "",
+	"Share Chat": "",
+	"Share to OpenWebUI Community": "",
+	"short-summary": "",
+	"Show": "",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "",
+	"Show your support!": "",
+	"Showcased creativity": "",
+	"Sign in": "",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "",
+	"Sign up": "",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "",
+	"Speech-to-Text Engine": "",
+	"Stop": "",
+	"Stop Sequence": "",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "",
+	"Subtitle (e.g. about the Roman Empire)": "",
+	"Success": "",
+	"Successfully updated.": "",
+	"Suggested": "",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "",
+	"System Instructions": "",
+	"System Prompt": "",
+	"Tags": "",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "",
+	"Temperature": "",
+	"Template": "",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "",
+	"Tfs Z": "",
+	"Thanks for your feedback!": "",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
+	"Theme": "",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "",
+	"Title": "",
+	"Title (e.g. Tell me a fun fact)": "",
+	"Title Auto-Generation": "",
+	"Title cannot be an empty string.": "",
+	"Title Generation Prompt": "",
+	"To access the available model names for downloading,": "",
+	"To access the GGUF models available for downloading,": "",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "",
+	"Toggle settings": "",
+	"Toggle sidebar": "",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "",
+	"Top P": "",
+	"Trouble accessing Ollama?": "",
+	"TTS Model": "",
+	"TTS Settings": "",
+	"TTS Voice": "",
+	"Type": "",
+	"Type Hugging Face Resolve (Download) URL": "",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "",
+	"Update for the latest features and improvements.": "",
+	"Update password": "",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "",
+	"Upload Pipeline": "",
+	"Upload Progress": "",
+	"URL Mode": "",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "",
+	"Use Initials": "",
+	"use_mlock (Ollama)": "",
+	"use_mmap (Ollama)": "",
+	"user": "",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "",
+	"Users": "",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "",
+	"Valid time units:": "",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "",
+	"variable to have them replaced with clipboard content.": "",
+	"Version": "",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
+	"Web": "",
+	"Web API": "",
+	"Web Loader Settings": "",
+	"Web Search": "",
+	"Web Search Engine": "",
+	"Webhook URL": "",
+	"WebUI Settings": "",
+	"WebUI will make requests to": "",
+	"What’s New in": "",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "",
+	"Write a prompt suggestion (e.g. Who are you?)": "",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "",
+	"Write something...": "",
+	"Yesterday": "",
+	"You": "",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "",
+	"You have shared this chat": "",
+	"You're a helpful assistant.": "",
+	"You're now logged in.": "",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "",
+	"Youtube Loader Settings": ""
+}
diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..702b3e9aa46b4ac8a99542d9edf6a76c930e5758
--- /dev/null
+++ b/src/lib/i18n/locales/en-US/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "",
+	"(latest)": "",
+	"{{ models }}": "",
+	"{{ owner }}: You cannot delete a base model": "",
+	"{{user}}'s Chats": "",
+	"{{webUIName}} Backend Required": "",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
+	"a user": "",
+	"About": "",
+	"Account": "",
+	"Account Activation Pending": "",
+	"Accurate information": "",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "",
+	"Add a model id": "",
+	"Add a short description about what this model does": "",
+	"Add a short title for this prompt": "",
+	"Add a tag": "",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "",
+	"Add Files": "",
+	"Add Memory": "",
+	"Add Model": "",
+	"Add Tag": "",
+	"Add Tags": "",
+	"Add text content": "",
+	"Add User": "",
+	"Adjusting these settings will apply changes universally to all users.": "",
+	"admin": "",
+	"Admin": "",
+	"Admin Panel": "",
+	"Admin Settings": "",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "",
+	"Advanced Params": "",
+	"All chats": "",
+	"All Documents": "",
+	"Allow Chat Deletion": "",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "",
+	"Already have an account?": "",
+	"an assistant": "",
+	"and": "",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "",
+	"API Base URL": "",
+	"API Key": "",
+	"API Key created.": "",
+	"API keys": "",
+	"April": "",
+	"Archive": "",
+	"Archive All Chats": "",
+	"Archived Chats": "",
+	"are allowed - Activate this command by typing": "",
+	"Are you sure?": "",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "",
+	"Attention to detail": "",
+	"Audio": "",
+	"August": "",
+	"Auto-playback response": "",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "",
+	"AUTOMATIC1111 Base URL is required.": "",
+	"Available list": "",
+	"available!": "",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "",
+	"Bad Response": "",
+	"Banners": "",
+	"Base Model (From)": "",
+	"Batch Size (num_batch)": "",
+	"before": "",
+	"Being lazy": "",
+	"Brave Search API Key": "",
+	"Bypass SSL verification for Websites": "",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "",
+	"Capabilities": "",
+	"Change Password": "",
+	"Character": "",
+	"Chat": "",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "",
+	"Chat Controls": "",
+	"Chat direction": "",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "",
+	"Check Again": "",
+	"Check for updates": "",
+	"Checking for updates...": "",
+	"Choose a model before saving...": "",
+	"Chunk Overlap": "",
+	"Chunk Params": "",
+	"Chunk Size": "",
+	"Citation": "",
+	"Clear memory": "",
+	"Click here for help.": "",
+	"Click here to": "",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "",
+	"Click here to select a csv file.": "",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "",
+	"Click on the user role button to change a user's role.": "",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "",
+	"Close": "",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "",
+	"ComfyUI": "",
+	"ComfyUI Base URL": "",
+	"ComfyUI Base URL is required.": "",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "",
+	"Completions": "",
+	"Concurrent Requests": "",
+	"Confirm": "",
+	"Confirm Password": "",
+	"Confirm your action": "",
+	"Connections": "",
+	"Contact Admin for WebUI Access": "",
+	"Content": "",
+	"Content Extraction": "",
+	"Context Length": "",
+	"Continue Response": "",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "",
+	"Copied to clipboard": "",
+	"Copy": "",
+	"Copy last code block": "",
+	"Copy last response": "",
+	"Copy Link": "",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "",
+	"Create a model": "",
+	"Create Account": "",
+	"Create Knowledge": "",
+	"Create new key": "",
+	"Create new secret key": "",
+	"Created at": "",
+	"Created At": "",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "",
+	"Current Password": "",
+	"Custom": "",
+	"Customize models for a specific purpose": "",
+	"Dark": "",
+	"Dashboard": "",
+	"Database": "",
+	"December": "",
+	"Default": "",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "",
+	"Default Model": "",
+	"Default model updated": "",
+	"Default Prompt Suggestions": "",
+	"Default User Role": "",
+	"Delete": "",
+	"Delete a model": "",
+	"Delete All Chats": "",
+	"Delete chat": "",
+	"Delete Chat": "",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "",
+	"Delete tool?": "",
+	"Delete User": "",
+	"Deleted {{deleteModelTag}}": "",
+	"Deleted {{name}}": "",
+	"Description": "",
+	"Didn't fully follow instructions": "",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "",
+	"Discover a prompt": "",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "",
+	"Documentation": "",
+	"Documents": "",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "",
+	"Don't have an account?": "",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "",
+	"Done": "",
+	"Download": "",
+	"Download canceled": "",
+	"Download Database": "",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "",
+	"Edit": "",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "",
+	"ElevenLabs": "",
+	"Email": "",
+	"Embedding Batch Size": "",
+	"Embedding Model": "",
+	"Embedding Model Engine": "",
+	"Embedding model set to \"{{embedding_model}}\"": "",
+	"Enable Community Sharing": "",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "",
+	"Enable Web Search": "",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
+	"Enter {{role}} message here": "",
+	"Enter a detail about yourself for your LLMs to recall": "",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "",
+	"Enter Chunk Size": "",
+	"Enter description": "",
+	"Enter Github Raw URL": "",
+	"Enter Google PSE API Key": "",
+	"Enter Google PSE Engine Id": "",
+	"Enter Image Size (e.g. 512x512)": "",
+	"Enter language codes": "",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "",
+	"Enter Number of Steps (e.g. 50)": "",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "",
+	"Enter Serper API Key": "",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "",
+	"Enter stop sequence": "",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "",
+	"Enter URL (e.g. http://localhost:11434)": "",
+	"Enter Your Email": "",
+	"Enter Your Full Name": "",
+	"Enter your message": "",
+	"Enter Your Password": "",
+	"Enter Your Role": "",
+	"Error": "",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "",
+	"Export": "",
+	"Export All Chats (All Users)": "",
+	"Export chat (.json)": "",
+	"Export Chats": "",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "",
+	"Export Prompts": "",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "",
+	"Failed to read clipboard contents": "",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "",
+	"Feedback History": "",
+	"Feel free to add specific details": "",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "",
+	"File not found.": "",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
+	"Fluidly stream large external response chunks": "",
+	"Focus chat input": "",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "",
+	"General Settings": "",
+	"Generate Image": "",
+	"Generating search query": "",
+	"Generation Info": "",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "",
+	"Google PSE API Key": "",
+	"Google PSE Engine Id": "",
+	"h:mm a": "",
+	"Haptic Feedback": "",
+	"has no conversations.": "",
+	"Hello, {{name}}": "",
+	"Help": "",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "",
+	"Hide Model": "",
+	"How can I help you today?": "",
+	"Hybrid Search": "",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "",
+	"Image Generation Engine": "",
+	"Image Settings": "",
+	"Images": "",
+	"Import Chats": "",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "",
+	"Import Prompts": "",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "",
+	"Info": "",
+	"Input commands": "",
+	"Install from Github URL": "",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "",
+	"Invalid file format.": "",
+	"Invalid Tag": "",
+	"January": "",
+	"join our Discord for help.": "",
+	"JSON": "",
+	"JSON Preview": "",
+	"July": "",
+	"June": "",
+	"JWT Expiration": "",
+	"JWT Token": "",
+	"Keep Alive": "",
+	"Keyboard shortcuts": "",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "",
+	"large language models, locally.": "",
+	"Last Active": "",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "",
+	"Made by OpenWebUI Community": "",
+	"Make sure to enclose them with": "",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "",
+	"Manage Ollama Models": "",
+	"Manage Pipelines": "",
+	"March": "",
+	"Max Tokens (num_predict)": "",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "",
+	"May": "",
+	"Memories accessible by LLMs will be shown here.": "",
+	"Memory": "",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "",
+	"Min P": "",
+	"Minimum Score": "",
+	"Mirostat": "",
+	"Mirostat Eta": "",
+	"Mirostat Tau": "",
+	"MMMM DD, YYYY": "",
+	"MMMM DD, YYYY HH:mm": "",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "",
+	"Model '{{modelTag}}' is already in queue for downloading.": "",
+	"Model {{modelId}} not found": "",
+	"Model {{modelName}} is not vision capable": "",
+	"Model {{name}} is now {{status}}": "",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
+	"Model ID": "",
+	"Model Name": "",
+	"Model not selected": "",
+	"Model Params": "",
+	"Model updated successfully": "",
+	"Model Whitelisting": "",
+	"Model(s) Whitelisted": "",
+	"Modelfile Content": "",
+	"Models": "",
+	"more": "",
+	"More": "",
+	"Move to Top": "",
+	"Name": "",
+	"Name your model": "",
+	"New Chat": "",
+	"New folder": "",
+	"New Password": "",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "",
+	"No search query generated": "",
+	"No source available": "",
+	"No valves to update": "",
+	"None": "",
+	"Not factually correct": "",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
+	"Notes": "",
+	"Notifications": "",
+	"November": "",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "",
+	"OAuth ID": "",
+	"October": "",
+	"Off": "",
+	"Okay, Let's Go!": "",
+	"OLED Dark": "",
+	"Ollama": "",
+	"Ollama API": "",
+	"Ollama API disabled": "",
+	"Ollama API is disabled": "",
+	"Ollama Version": "",
+	"On": "",
+	"Only": "",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "",
+	"OpenAI API": "",
+	"OpenAI API Config": "",
+	"OpenAI API Key is required.": "",
+	"OpenAI URL/Key required.": "",
+	"or": "",
+	"Other": "",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "",
+	"PDF document (.pdf)": "",
+	"PDF Extract Images (OCR)": "",
+	"pending": "",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "",
+	"Personalization": "",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "",
+	"Plain text (.txt)": "",
+	"Playground": "",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "",
+	"Previous 30 days": "",
+	"Previous 7 days": "",
+	"Profile Image": "",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
+	"Prompt Content": "",
+	"Prompt suggestions": "",
+	"Prompts": "",
+	"Pull \"{{searchValue}}\" from Ollama.com": "",
+	"Pull a model from Ollama.com": "",
+	"Query Params": "",
+	"RAG Template": "",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "",
+	"Record voice": "",
+	"Redirecting you to OpenWebUI Community": "",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "",
+	"Regenerate": "",
+	"Release Notes": "",
+	"Relevance": "",
+	"Remove": "",
+	"Remove Model": "",
+	"Rename": "",
+	"Repeat Last N": "",
+	"Request Mode": "",
+	"Reranking Model": "",
+	"Reranking model disabled": "",
+	"Reranking model set to \"{{reranking_model}}\"": "",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "",
+	"Rosé Pine": "",
+	"Rosé Pine Dawn": "",
+	"RTL": "",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "",
+	"Save & Create": "",
+	"Save & Update": "",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "",
+	"Search a model": "",
+	"Search Chats": "",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "",
+	"Search Prompts": "",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "",
+	"Searched {{count}} sites_other": "",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "",
+	"See readme.md for instructions": "",
+	"See what's new": "",
+	"Seed": "",
+	"Select a base model": "",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "",
+	"Select a pipeline": "",
+	"Select a pipeline url": "",
+	"Select a tool": "",
+	"Select an Ollama instance": "",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "",
+	"Semantic distance to query": "",
+	"Send": "",
+	"Send a Message": "",
+	"Send message": "",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "",
+	"Serper API Key": "",
+	"Serply API Key": "",
+	"Serpstack API Key": "",
+	"Server connection verified": "",
+	"Set as default": "",
+	"Set CFG Scale": "",
+	"Set Default Model": "",
+	"Set embedding model (e.g. {{model}})": "",
+	"Set Image Size": "",
+	"Set reranking model (e.g. {{model}})": "",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "",
+	"Set Task Model": "",
+	"Set Voice": "",
+	"Set whisper model": "",
+	"Settings": "",
+	"Settings saved successfully!": "",
+	"Share": "",
+	"Share Chat": "",
+	"Share to OpenWebUI Community": "",
+	"short-summary": "",
+	"Show": "",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "",
+	"Show your support!": "",
+	"Showcased creativity": "",
+	"Sign in": "",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "",
+	"Sign up": "",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "",
+	"Speech-to-Text Engine": "",
+	"Stop": "",
+	"Stop Sequence": "",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "",
+	"Subtitle (e.g. about the Roman Empire)": "",
+	"Success": "",
+	"Successfully updated.": "",
+	"Suggested": "",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "",
+	"System Instructions": "",
+	"System Prompt": "",
+	"Tags": "",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "",
+	"Temperature": "",
+	"Template": "",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "",
+	"Tfs Z": "",
+	"Thanks for your feedback!": "",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
+	"Theme": "",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "",
+	"Title": "",
+	"Title (e.g. Tell me a fun fact)": "",
+	"Title Auto-Generation": "",
+	"Title cannot be an empty string.": "",
+	"Title Generation Prompt": "",
+	"To access the available model names for downloading,": "",
+	"To access the GGUF models available for downloading,": "",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "",
+	"Toggle settings": "",
+	"Toggle sidebar": "",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "",
+	"Top P": "",
+	"Trouble accessing Ollama?": "",
+	"TTS Model": "",
+	"TTS Settings": "",
+	"TTS Voice": "",
+	"Type": "",
+	"Type Hugging Face Resolve (Download) URL": "",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "",
+	"Update for the latest features and improvements.": "",
+	"Update password": "",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "",
+	"Upload Pipeline": "",
+	"Upload Progress": "",
+	"URL Mode": "",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "",
+	"Use Initials": "",
+	"use_mlock (Ollama)": "",
+	"use_mmap (Ollama)": "",
+	"user": "",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "",
+	"Users": "",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "",
+	"Valid time units:": "",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "",
+	"variable to have them replaced with clipboard content.": "",
+	"Version": "",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
+	"Web": "",
+	"Web API": "",
+	"Web Loader Settings": "",
+	"Web Search": "",
+	"Web Search Engine": "",
+	"Webhook URL": "",
+	"WebUI Settings": "",
+	"WebUI will make requests to": "",
+	"What’s New in": "",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "",
+	"Write a prompt suggestion (e.g. Who are you?)": "",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "",
+	"Write something...": "",
+	"Yesterday": "",
+	"You": "",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "",
+	"You have shared this chat": "",
+	"You're a helpful assistant.": "",
+	"You're now logged in.": "",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "",
+	"Youtube Loader Settings": ""
+}
diff --git a/src/lib/i18n/locales/es-ES/translation.json b/src/lib/i18n/locales/es-ES/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..5cc8d00b7eed5c38078386caa9ce89537c948f67
--- /dev/null
+++ b/src/lib/i18n/locales/es-ES/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' o '-1' para evitar expiración.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(p.ej. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(p.ej. `sh webui.sh --api`)",
+	"(latest)": "(latest)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: No se puede eliminar un modelo base",
+	"{{user}}'s Chats": "Chats de {{user}}",
+	"{{webUIName}} Backend Required": "{{webUIName}} Servidor Requerido",
+	"*Prompt node ID(s) are required for image generation": "Los ID de nodo son requeridos para la generación de imágenes",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un modelo de tareas se utiliza cuando se realizan tareas como la generación de títulos para chats y consultas de búsqueda web",
+	"a user": "un usuario",
+	"About": "Sobre nosotros",
+	"Account": "Cuenta",
+	"Account Activation Pending": "Activación de cuenta pendiente",
+	"Accurate information": "Información precisa",
+	"Actions": "Acciones",
+	"Active Users": "Usuarios activos",
+	"Add": "Agregar",
+	"Add a model id": "Adición de un identificador de modelo",
+	"Add a short description about what this model does": "Agregue una breve descripción sobre lo que hace este modelo",
+	"Add a short title for this prompt": "Agregue un título corto para este Prompt",
+	"Add a tag": "Agregar una etiqueta",
+	"Add Arena Model": "",
+	"Add Content": "Agregar Contenido",
+	"Add content here": "Agrege contenido aquí",
+	"Add custom prompt": "Agregar un prompt personalizado",
+	"Add Files": "Agregar Archivos",
+	"Add Memory": "Agregar Memoria",
+	"Add Model": "Agregar Modelo",
+	"Add Tag": "Agregar etiqueta",
+	"Add Tags": "agregar etiquetas",
+	"Add text content": "Añade contenido de texto",
+	"Add User": "Agregar Usuario",
+	"Adjusting these settings will apply changes universally to all users.": "Ajustar estas opciones aplicará los cambios universalmente a todos los usuarios.",
+	"admin": "admin",
+	"Admin": "Admin",
+	"Admin Panel": "Panel de Administración",
+	"Admin Settings": "Configuración de Administrador",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Admins tienen acceso a todas las herramientas en todo momento; los usuarios necesitan herramientas asignadas por modelo en el espacio de trabajo.",
+	"Advanced Parameters": "Parámetros Avanzados",
+	"Advanced Params": "Parámetros avanzados",
+	"All chats": "",
+	"All Documents": "Todos los Documentos",
+	"Allow Chat Deletion": "Permitir Borrar Chats",
+	"Allow Chat Editing": "Permitir Editar Chat",
+	"Allow non-local voices": "Permitir voces no locales",
+	"Allow Temporary Chat": "Permitir Chat Temporal",
+	"Allow User Location": "Permitir Ubicación del Usuario",
+	"Allow Voice Interruption in Call": "Permitir interrupción de voz en llamada",
+	"alphanumeric characters and hyphens": "caracteres alfanuméricos y guiones",
+	"Already have an account?": "¿Ya tienes una cuenta?",
+	"an assistant": "un asistente",
+	"and": "y",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "y crear un nuevo enlace compartido.",
+	"API Base URL": "Dirección URL de la API",
+	"API Key": "Clave de la API ",
+	"API Key created.": "Clave de la API creada.",
+	"API keys": "Claves de la API",
+	"April": "Abril",
+	"Archive": "Archivar",
+	"Archive All Chats": "Archivar todos los chats",
+	"Archived Chats": "Chats archivados",
+	"are allowed - Activate this command by typing": "están permitidos - Active este comando escribiendo",
+	"Are you sure?": "¿Está seguro?",
+	"Arena Models": "",
+	"Artifacts": "Artefactos",
+	"Ask a question": "Haz una pregunta",
+	"Assistant": "",
+	"Attach file": "Adjuntar archivo",
+	"Attention to detail": "Detalle preciso",
+	"Audio": "Audio",
+	"August": "Agosto",
+	"Auto-playback response": "Respuesta de reproducción automática",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "Cadena de autenticación de API",
+	"AUTOMATIC1111 Base URL": "Dirección URL de AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "La dirección URL de AUTOMATIC1111 es requerida.",
+	"Available list": "Lista disponible",
+	"available!": "¡disponible!",
+	"Azure AI Speech": "",
+	"Azure Region": "Región de Azure",
+	"Back": "Volver",
+	"Bad Response": "Respuesta incorrecta",
+	"Banners": "Banners",
+	"Base Model (From)": "Modelo base (desde)",
+	"Batch Size (num_batch)": "Tamaño del Batch (num_batch)",
+	"before": "antes",
+	"Being lazy": "Ser perezoso",
+	"Brave Search API Key": "Clave de API de Brave Search",
+	"Bypass SSL verification for Websites": "Desactivar la verificación SSL para sitios web",
+	"Call": "Llamada",
+	"Call feature is not supported when using Web STT engine": "La funcionalidad de llamada no puede usarse junto con el motor de STT Web",
+	"Camera": "Cámara",
+	"Cancel": "Cancelar",
+	"Capabilities": "Capacidades",
+	"Change Password": "Cambia la Contraseña",
+	"Character": "",
+	"Chat": "Chat",
+	"Chat Background Image": "Imágen de fondo del Chat",
+	"Chat Bubble UI": "Burbuja de chat UI",
+	"Chat Controls": "Controles de chat",
+	"Chat direction": "Dirección del Chat",
+	"Chat Overview": "Vista general del chat",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Chats",
+	"Check Again": "Verifica de nuevo",
+	"Check for updates": "Verificar actualizaciones",
+	"Checking for updates...": "Verificando actualizaciones...",
+	"Choose a model before saving...": "Escoge un modelo antes de guardar los cambios...",
+	"Chunk Overlap": "Superposición de fragmentos",
+	"Chunk Params": "Parámetros de fragmentos",
+	"Chunk Size": "Tamaño de fragmentos",
+	"Citation": "Cita",
+	"Clear memory": "Liberar memoria",
+	"Click here for help.": "Presiona aquí para obtener ayuda.",
+	"Click here to": "Presiona aquí para",
+	"Click here to download user import template file.": "Presiona aquí para descargar el archivo de plantilla de importación de usuario.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Presiona aquí para seleccionar",
+	"Click here to select a csv file.": "Presiona aquí para seleccionar un archivo csv.",
+	"Click here to select a py file.": "Presiona aquí para seleccionar un archivo py.",
+	"Click here to upload a workflow.json file.": "Presiona aquí para subir un archivo workflow.json.",
+	"click here.": "Presiona aquí.",
+	"Click on the user role button to change a user's role.": "Presiona en el botón de roles del usuario para cambiar su rol.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Permisos de escritura del portapapeles denegados. Por favor, comprueba las configuraciones de tu navegador para otorgar el acceso necesario.",
+	"Clone": "Clonar",
+	"Close": "Cerrar",
+	"Code execution": "",
+	"Code formatted successfully": "Se ha formateado correctamente el código.",
+	"Collection": "Colección",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Base URL",
+	"ComfyUI Base URL is required.": "ComfyUI Base URL es requerido.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "Nodos para ComfyUI Workflow",
+	"Command": "Comando",
+	"Completions": "",
+	"Concurrent Requests": "Solicitudes simultáneas",
+	"Confirm": "Confirmar",
+	"Confirm Password": "Confirmar Contraseña",
+	"Confirm your action": "Confirma tu acción",
+	"Connections": "Conexiones",
+	"Contact Admin for WebUI Access": "Contacta el administrador para obtener acceso al WebUI",
+	"Content": "Contenido",
+	"Content Extraction": "Extracción de contenido",
+	"Context Length": "Longitud del contexto",
+	"Continue Response": "Continuar Respuesta",
+	"Continue with {{provider}}": "Continuar con {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "Controles",
+	"Copied": "Copiado",
+	"Copied shared chat URL to clipboard!": "¡URL de chat compartido copiado al portapapeles!",
+	"Copied to clipboard": "Copiado al portapapeles",
+	"Copy": "Copiar",
+	"Copy last code block": "Copia el último bloque de código",
+	"Copy last response": "Copia la última respuesta",
+	"Copy Link": "Copiar enlace",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "¡La copia al portapapeles se ha realizado correctamente!",
+	"Create a model": "Crear un modelo",
+	"Create Account": "Crear una cuenta",
+	"Create Knowledge": "Crear Conocimiento",
+	"Create new key": "Crear una nueva clave",
+	"Create new secret key": "Crear una nueva clave secreta",
+	"Created at": "Creado en",
+	"Created At": "Creado en",
+	"Created by": "Creado por",
+	"CSV Import": "Importa un CSV",
+	"Current Model": "Modelo Actual",
+	"Current Password": "Contraseña Actual",
+	"Custom": "Personalizado",
+	"Customize models for a specific purpose": "Personalizar modelos para un propósito específico",
+	"Dark": "Oscuro",
+	"Dashboard": "Panel de Control",
+	"Database": "Base de datos",
+	"December": "Diciembre",
+	"Default": "Por defecto",
+	"Default (Open AI)": "Predeterminado (Open AI)",
+	"Default (SentenceTransformers)": "Predeterminado (SentenceTransformers)",
+	"Default Model": "Modelo predeterminado",
+	"Default model updated": "El modelo por defecto ha sido actualizado",
+	"Default Prompt Suggestions": "Sugerencias de mensajes por defecto",
+	"Default User Role": "Rol por defecto para usuarios",
+	"Delete": "Borrar",
+	"Delete a model": "Borra un modelo",
+	"Delete All Chats": "Eliminar todos los chats",
+	"Delete chat": "Borrar chat",
+	"Delete Chat": "Borrar Chat",
+	"Delete chat?": "Borrar el chat?",
+	"Delete folder?": "",
+	"Delete function?": "Borrar la función?",
+	"Delete prompt?": "Borrar el prompt?",
+	"delete this link": "Borrar este enlace",
+	"Delete tool?": "Borrar la herramienta",
+	"Delete User": "Borrar Usuario",
+	"Deleted {{deleteModelTag}}": "Se borró {{deleteModelTag}}",
+	"Deleted {{name}}": "Eliminado {{nombre}}",
+	"Description": "Descripción",
+	"Didn't fully follow instructions": "No siguió las instrucciones",
+	"Disabled": "Desactivado",
+	"Discover a function": "Descubre una función",
+	"Discover a model": "Descubrir un modelo",
+	"Discover a prompt": "Descubre un Prompt",
+	"Discover a tool": "Descubre una herramienta",
+	"Discover, download, and explore custom functions": "Descubre, descarga y explora funciones personalizadas",
+	"Discover, download, and explore custom prompts": "Descubre, descarga, y explora Prompts personalizados",
+	"Discover, download, and explore custom tools": "Descubre, descarga y explora herramientas personalizadas",
+	"Discover, download, and explore model presets": "Descubre, descarga y explora ajustes preestablecidos de modelos",
+	"Dismissible": "Desestimable",
+	"Display Emoji in Call": "Muestra Emoji en llamada",
+	"Display the username instead of You in the Chat": "Mostrar el nombre de usuario en lugar de Usted en el chat",
+	"Do not install functions from sources you do not fully trust.": "No instale funciones desde fuentes que no confíe totalmente.",
+	"Do not install tools from sources you do not fully trust.": "No instale herramientas desde fuentes que no confíe totalmente.",
+	"Document": "Documento",
+	"Documentation": "Documentación",
+	"Documents": "Documentos",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "no realiza ninguna conexión externa y sus datos permanecen seguros en su servidor alojado localmente.",
+	"Don't have an account?": "¿No tienes una cuenta?",
+	"don't install random functions from sources you don't trust.": "no instale funciones aleatorias desde fuentes que no confíe.",
+	"don't install random tools from sources you don't trust.": "no instale herramientas aleatorias desde fuentes que no confíe.",
+	"Don't like the style": "No te gusta el estilo?",
+	"Done": "Hecho",
+	"Download": "Descargar",
+	"Download canceled": "Descarga cancelada",
+	"Download Database": "Descarga la Base de Datos",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Suelta cualquier archivo aquí para agregarlo a la conversación",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "p.ej. '30s','10m'. Unidades válidas de tiempo son 's', 'm', 'h'.",
+	"Edit": "Editar",
+	"Edit Arena Model": "",
+	"Edit Memory": "Editar Memoria",
+	"Edit User": "Editar Usuario",
+	"ElevenLabs": "",
+	"Email": "Email",
+	"Embedding Batch Size": "Tamaño de Embedding",
+	"Embedding Model": "Modelo de Embedding",
+	"Embedding Model Engine": "Motor de Modelo de Embedding",
+	"Embedding model set to \"{{embedding_model}}\"": "Modelo de Embedding configurado a \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Habilitar el uso compartido de la comunidad",
+	"Enable Message Rating": "Habilitar la calificación de los mensajes",
+	"Enable New Sign Ups": "Habilitar Nuevos Registros",
+	"Enable Web Search": "Habilitar la búsqueda web",
+	"Enable Web Search Query Generation": "Habilitar generación de consultas web",
+	"Enabled": "Activado",
+	"Engine": "Motor",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Asegúrese de que su archivo CSV incluya 4 columnas en este orden: Nombre, Correo Electrónico, Contraseña, Rol.",
+	"Enter {{role}} message here": "Ingrese el mensaje {{role}} aquí",
+	"Enter a detail about yourself for your LLMs to recall": "Ingrese un detalle sobre usted para que sus LLMs recuerden",
+	"Enter api auth string (e.g. username:password)": "Ingrese la cadena de autorización de api (p.ej., nombre:contraseña)",
+	"Enter Brave Search API Key": "Ingresa la clave de API de Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "Ingresa la escala de CFG (p.ej., 7.0)",
+	"Enter Chunk Overlap": "Ingresar superposición de fragmentos",
+	"Enter Chunk Size": "Ingrese el tamaño del fragmento",
+	"Enter description": "",
+	"Enter Github Raw URL": "Ingresa la URL sin procesar de Github",
+	"Enter Google PSE API Key": "Ingrese la clave API de Google PSE",
+	"Enter Google PSE Engine Id": "Introduzca el ID del motor PSE de Google",
+	"Enter Image Size (e.g. 512x512)": "Ingrese el tamaño de la imagen (p.ej. 512x512)",
+	"Enter language codes": "Ingrese códigos de idioma",
+	"Enter Model ID": "Ingresa el ID del modelo",
+	"Enter model tag (e.g. {{modelTag}})": "Ingrese la etiqueta del modelo (p.ej. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Ingrese el número de pasos (p.ej., 50)",
+	"Enter Sampler (e.g. Euler a)": "Ingrese el sampler (p.ej., Euler a)",
+	"Enter Scheduler (e.g. Karras)": "Ingrese el planificador (p.ej., Karras)",
+	"Enter Score": "Ingrese la puntuación",
+	"Enter SearchApi API Key": "Ingrese la Clave API de SearchApi",
+	"Enter SearchApi Engine": "Ingrese el motor de SearchApi",
+	"Enter Searxng Query URL": "Introduzca la URL de consulta de Searxng",
+	"Enter Serper API Key": "Ingrese la clave API de Serper",
+	"Enter Serply API Key": "Ingrese la clave API de Serply",
+	"Enter Serpstack API Key": "Ingrese la clave API de Serpstack",
+	"Enter stop sequence": "Ingrese la secuencia de parada",
+	"Enter system prompt": "Ingrese el prompt del sistema",
+	"Enter Tavily API Key": "Ingrese la clave API de Tavily",
+	"Enter Tika Server URL": "Ingrese la URL del servidor Tika",
+	"Enter Top K": "Ingrese el Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Ingrese la URL (p.ej., http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Ingrese la URL (p.ej., http://localhost:11434)",
+	"Enter Your Email": "Ingrese su correo electrónico",
+	"Enter Your Full Name": "Ingrese su nombre completo",
+	"Enter your message": "Ingrese su mensaje",
+	"Enter Your Password": "Ingrese su contraseña",
+	"Enter Your Role": "Ingrese su rol",
+	"Error": "Error",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Experimental",
+	"Export": "Exportar",
+	"Export All Chats (All Users)": "Exportar todos los chats (Todos los usuarios)",
+	"Export chat (.json)": "Exportar chat (.json)",
+	"Export Chats": "Exportar Chats",
+	"Export Config to JSON File": "",
+	"Export Functions": "Exportar Funciones",
+	"Export LiteLLM config.yaml": "Exportar LiteLLM config.yaml",
+	"Export Models": "Exportar Modelos",
+	"Export Prompts": "Exportar Prompts",
+	"Export Tools": "Exportar Herramientas",
+	"External Models": "Modelos Externos",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "No se pudo crear la clave API.",
+	"Failed to read clipboard contents": "No se pudo leer el contenido del portapapeles",
+	"Failed to update settings": "Falla al actualizar los ajustes",
+	"Failed to upload file.": "Falla al subir el archivo.",
+	"February": "Febrero",
+	"Feedback History": "",
+	"Feel free to add specific details": "Libre de agregar detalles específicos",
+	"File": "Archivo",
+	"File added successfully.": "Archivo agregado correctamente.",
+	"File content updated successfully.": "Contenido del archivo actualizado correctamente.",
+	"File Mode": "Modo de archivo",
+	"File not found.": "Archivo no encontrado.",
+	"File removed successfully.": "Archivo eliminado correctamente.",
+	"File size should not exceed {{maxSize}} MB.": "Tamaño del archivo no debe exceder {{maxSize}} MB.",
+	"Files": "Archivos",
+	"Filter is now globally disabled": "El filtro ahora está desactivado globalmente",
+	"Filter is now globally enabled": "El filtro ahora está habilitado globalmente",
+	"Filters": "Filtros",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Se detectó suplantación de huellas: No se pueden usar las iniciales como avatar. Por defecto se utiliza la imagen de perfil predeterminada.",
+	"Fluidly stream large external response chunks": "Transmita con fluidez grandes fragmentos de respuesta externa",
+	"Focus chat input": "Enfoca la entrada del chat",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Siguió las instrucciones perfectamente",
+	"Form": "De",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Penalización de frecuencia",
+	"Function": "",
+	"Function created successfully": "Función creada exitosamente",
+	"Function deleted successfully": "Función borrada exitosamente",
+	"Function Description (e.g. A filter to remove profanity from text)": "Descripción de la función (por ejemplo, un filtro para eliminar lo profano del texto)",
+	"Function ID (e.g. my_filter)": "ID de la función (por ejemplo, mi_filtro)",
+	"Function is now globally disabled": "La función ahora está desactivada globalmente",
+	"Function is now globally enabled": "La función está habilitada globalmente",
+	"Function Name (e.g. My Filter)": "Nombre de la función (por ejemplo, Mi filtro)",
+	"Function updated successfully": "Función actualizada exitosamente",
+	"Functions": "Funciones",
+	"Functions allow arbitrary code execution": "Funciones habilitan la ejecución de código arbitrario",
+	"Functions allow arbitrary code execution.": "Funciones habilitan la ejecución de código arbitrario.",
+	"Functions imported successfully": "Funciones importadas exitosamente",
+	"General": "General",
+	"General Settings": "Opciones Generales",
+	"Generate Image": "Generar imagen",
+	"Generating search query": "Generación de consultas de búsqueda",
+	"Generation Info": "Información de Generación",
+	"Get up and running with": "Levanta y empieza con",
+	"Global": "Global",
+	"Good Response": "Buena Respuesta",
+	"Google PSE API Key": "Clave API de Google PSE",
+	"Google PSE Engine Id": "ID del motor PSE de Google",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "Retroalimentación háptica",
+	"has no conversations.": "no tiene conversaciones.",
+	"Hello, {{name}}": "Hola, {{name}}",
+	"Help": "Ayuda",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Esconder",
+	"Hide Model": "Esconder Modelo",
+	"How can I help you today?": "¿Cómo puedo ayudarte hoy?",
+	"Hybrid Search": "Búsqueda Híbrida",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Aseguro que he leído y entiendo las implicaciones de mi acción. Estoy consciente de los riesgos asociados con la ejecución de código arbitrario y he verificado la confianza de la fuente.",
+	"ID": "",
+	"Image Generation (Experimental)": "Generación de imágenes (experimental)",
+	"Image Generation Engine": "Motor de generación de imágenes",
+	"Image Settings": "Ajustes de la Imágen",
+	"Images": "Imágenes",
+	"Import Chats": "Importar chats",
+	"Import Config from JSON File": "",
+	"Import Functions": "Importar Funciones",
+	"Import Models": "Importar modelos",
+	"Import Prompts": "Importar Prompts",
+	"Import Tools": "Importar Herramientas",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Incluir el indicador `--api-auth` al ejecutar stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Incluir el indicador `--api` al ejecutar stable-diffusion-webui",
+	"Info": "Información",
+	"Input commands": "Ingresar comandos",
+	"Install from Github URL": "Instalar desde la URL de Github",
+	"Instant Auto-Send After Voice Transcription": "Auto-Enviar Después de la Transcripción de Voz",
+	"Interface": "Interfaz",
+	"Invalid file format.": "",
+	"Invalid Tag": "Etiqueta Inválida",
+	"January": "Enero",
+	"join our Discord for help.": "Únase a nuestro Discord para obtener ayuda.",
+	"JSON": "JSON",
+	"JSON Preview": "Vista previa de JSON",
+	"July": "Julio",
+	"June": "Junio",
+	"JWT Expiration": "Expiración del JWT",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Mantener Vivo",
+	"Keyboard shortcuts": "Atajos de teclado",
+	"Knowledge": "Conocimiento",
+	"Knowledge created successfully.": "Conocimiento creado exitosamente.",
+	"Knowledge deleted successfully.": "Conocimiento eliminado exitosamente.",
+	"Knowledge reset successfully.": "Conocimiento restablecido exitosamente.",
+	"Knowledge updated successfully": "Conocimiento actualizado exitosamente.",
+	"Landing Page Mode": "Modo de Página de Inicio",
+	"Language": "Lenguaje",
+	"large language models, locally.": "modelos de lenguaje grande, localmente",
+	"Last Active": "Última Actividad",
+	"Last Modified": "Modificado por última vez",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "Deje vacío para ilimitado",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "Deje vacío para usar el propmt predeterminado, o ingrese un propmt personalizado",
+	"Light": "Claro",
+	"Listening...": "Escuchando...",
+	"LLMs can make mistakes. Verify important information.": "Los LLM pueden cometer errores. Verifica la información importante.",
+	"Local Models": "Modelos locales",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Hecho por la comunidad de OpenWebUI",
+	"Make sure to enclose them with": "Asegúrese de adjuntarlos con",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "Asegúrese de exportar un archivo workflow.json en formato API desde ComfyUI.",
+	"Manage": "Gestionar",
+	"Manage Arena Models": "",
+	"Manage Models": "Administrar Modelos",
+	"Manage Ollama Models": "Administrar Modelos Ollama",
+	"Manage Pipelines": "Administrar Pipelines",
+	"March": "Marzo",
+	"Max Tokens (num_predict)": "Máximo de fichas (num_predict)",
+	"Max Upload Count": "Cantidad máxima de cargas",
+	"Max Upload Size": "Tamaño máximo de Cargas",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Se pueden descargar un máximo de 3 modelos simultáneamente. Por favor, inténtelo de nuevo más tarde.",
+	"May": "Mayo",
+	"Memories accessible by LLMs will be shown here.": "Las memorias accesibles por los LLMs se mostrarán aquí.",
+	"Memory": "Memoria",
+	"Memory added successfully": "Memoria añadida correctamente",
+	"Memory cleared successfully": "Memoria liberada correctamente",
+	"Memory deleted successfully": "Memoria borrada correctamente",
+	"Memory updated successfully": "Memoria actualizada correctamente",
+	"Merge Responses": "Fusionar Respuestas",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Los mensajes que envíe después de crear su enlace no se compartirán. Los usuarios con el enlace podrán ver el chat compartido.",
+	"Min P": "",
+	"Minimum Score": "Puntuación mínima",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "El modelo '{{modelName}}' se ha descargado correctamente.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "El modelo '{{modelTag}}' ya está en cola para descargar.",
+	"Model {{modelId}} not found": "El modelo {{modelId}} no fue encontrado",
+	"Model {{modelName}} is not vision capable": "El modelo {{modelName}} no es capaz de ver",
+	"Model {{name}} is now {{status}}": "El modelo {{name}} ahora es {{status}}",
+	"Model {{name}} is now at the top": "El modelo {{name}} está ahora en el tope",
+	"Model accepts image inputs": "El modelo acepta entradas de imagenes",
+	"Model created successfully!": "Modelo creado correctamente!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Se detectó la ruta del sistema de archivos del modelo. Se requiere el nombre corto del modelo para la actualización, no se puede continuar.",
+	"Model ID": "ID del modelo",
+	"Model Name": "",
+	"Model not selected": "Modelo no seleccionado",
+	"Model Params": "Parámetros del modelo",
+	"Model updated successfully": "Modelo actualizado correctamente",
+	"Model Whitelisting": "Listado de Modelos habilitados",
+	"Model(s) Whitelisted": "Modelo(s) habilitados",
+	"Modelfile Content": "Contenido del Modelfile",
+	"Models": "Modelos",
+	"more": "",
+	"More": "Más",
+	"Move to Top": "Mueve al tope",
+	"Name": "Nombre",
+	"Name your model": "Asigne un nombre a su modelo",
+	"New Chat": "Nuevo Chat",
+	"New folder": "",
+	"New Password": "Nueva Contraseña",
+	"No content found": "",
+	"No content to speak": "No hay contenido para hablar",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Ningún archivo fué seleccionado",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "No se encontró contenido HTML, CSS, o JavaScript.",
+	"No knowledge found": "No se encontró ningún conocimiento",
+	"No models found": "",
+	"No results found": "No se han encontrado resultados",
+	"No search query generated": "No se ha generado ninguna consulta de búsqueda",
+	"No source available": "No hay fuente disponible",
+	"No valves to update": "No valves para actualizar",
+	"None": "Ninguno",
+	"Not factually correct": "No es correcto en todos los aspectos",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Si estableces una puntuación mínima, la búsqueda sólo devolverá documentos con una puntuación mayor o igual a la puntuación mínima.",
+	"Notes": "",
+	"Notifications": "Notificaciones",
+	"November": "Noviembre",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth ID",
+	"October": "Octubre",
+	"Off": "Desactivado",
+	"Okay, Let's Go!": "Bien, ¡Vamos!",
+	"OLED Dark": "OLED oscuro",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "API de Ollama deshabilitada",
+	"Ollama API is disabled": "API de Ollama desactivada",
+	"Ollama Version": "Versión de Ollama",
+	"On": "Activado",
+	"Only": "Solamente",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Sólo se permiten caracteres alfanuméricos y guiones en la cadena de comando.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Solo se pueden editar las colecciones, crear una nueva base de conocimientos para editar / añadir documentos",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "¡Ups! Parece que la URL no es válida. Vuelva a verificar e inténtelo nuevamente.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "¡Ups! Estás utilizando un método no compatible (solo frontend). Por favor ejecute la WebUI desde el backend.",
+	"Open file": "Abrir archivo",
+	"Open in full screen": "Abrir en pantalla completa",
+	"Open new chat": "Abrir nuevo chat",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "La versión de Open WebUI (v{{OPEN_WEBUI_VERSION}}) es inferior a la versión requerida (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API Config",
+	"OpenAI API Key is required.": "La Clave de la API de OpenAI es requerida.",
+	"OpenAI URL/Key required.": "URL/Clave de OpenAI es requerida.",
+	"or": "o",
+	"Other": "Otro",
+	"OUTPUT": "",
+	"Output format": "Formato de salida",
+	"Overview": "Vista general",
+	"page": "página",
+	"Password": "Contraseña",
+	"PDF document (.pdf)": "PDF document (.pdf)",
+	"PDF Extract Images (OCR)": "Extraer imágenes de PDF (OCR)",
+	"pending": "pendiente",
+	"Permission denied when accessing media devices": "Permiso denegado al acceder a los dispositivos",
+	"Permission denied when accessing microphone": "Permiso denegado al acceder a la micrófono",
+	"Permission denied when accessing microphone: {{error}}": "Permiso denegado al acceder al micrófono: {{error}}",
+	"Personalization": "Personalización",
+	"Pin": "Fijar",
+	"Pinned": "Fijado",
+	"Pipeline deleted successfully": "Pipeline borrada exitosamente",
+	"Pipeline downloaded successfully": "Pipeline descargada exitosamente",
+	"Pipelines": "Pipelines",
+	"Pipelines Not Detected": "Pipeline No Detectada",
+	"Pipelines Valves": "Tuberías Válvulas",
+	"Plain text (.txt)": "Texto plano (.txt)",
+	"Playground": "Patio de juegos",
+	"Please carefully review the following warnings:": "Por favor revise con cuidado los siguientes avisos:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "Por favor llene todos los campos.",
+	"Please select a reason": "Por favor seleccione una razón",
+	"Positive attitude": "Actitud positiva",
+	"Previous 30 days": "Últimos 30 días",
+	"Previous 7 days": "Últimos 7 días",
+	"Profile Image": "Imagen de perfil",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (por ejemplo, cuéntame una cosa divertida sobre el Imperio Romano)",
+	"Prompt Content": "Contenido del Prompt",
+	"Prompt suggestions": "Sugerencias de Prompts",
+	"Prompts": "Prompts",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Extraer \"{{searchValue}}\" de Ollama.com",
+	"Pull a model from Ollama.com": "Obtener un modelo de Ollama.com",
+	"Query Params": "Parámetros de consulta",
+	"RAG Template": "Plantilla de RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Leer al oído",
+	"Record voice": "Grabar voz",
+	"Redirecting you to OpenWebUI Community": "Redireccionándote a la comunidad OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Referirse a usted mismo como \"Usuario\" (por ejemplo, \"El usuario está aprendiendo Español\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Rechazado cuando no debería",
+	"Regenerate": "Regenerar",
+	"Release Notes": "Notas de la versión",
+	"Relevance": "",
+	"Remove": "Eliminar",
+	"Remove Model": "Eliminar modelo",
+	"Rename": "Renombrar",
+	"Repeat Last N": "Repetir las últimas N",
+	"Request Mode": "Modo de petición",
+	"Reranking Model": "Modelo de reranking",
+	"Reranking model disabled": "Modelo de reranking deshabilitado",
+	"Reranking model set to \"{{reranking_model}}\"": "Modelo de reranking establecido en \"{{reranking_model}}\"",
+	"Reset": "Reiniciar",
+	"Reset Upload Directory": "Reiniciar Directorio de carga",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Copiar respuesta automáticamente al portapapeles",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Las notificaciones de respuesta no pueden activarse debido a que los permisos del sitio web han sido denegados. Por favor, visite las configuraciones de su navegador para otorgar el acceso necesario.",
+	"Response splitting": "División de respuestas",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rol",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "Ejecutar",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Correr Llama 2, Code Llama y otros modelos. Personalice y cree sus propios modelos.",
+	"Running": "Ejecutando",
+	"Save": "Guardar",
+	"Save & Create": "Guardar y Crear",
+	"Save & Update": "Guardar y Actualizar",
+	"Save As Copy": "Guardar como copia",
+	"Save Tag": "Guardar etiqueta",
+	"Saved": "Guardado",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Ya no se admite guardar registros de chat directamente en el almacenamiento de su navegador. Tómese un momento para descargar y eliminar sus registros de chat haciendo clic en el botón a continuación. No te preocupes, puedes volver a importar fácilmente tus registros de chat al backend a través de",
+	"Scroll to bottom when switching between branches": "Moverse a la parte inferior cuando se cambia entre ramas",
+	"Search": "Buscar",
+	"Search a model": "Buscar un modelo",
+	"Search Chats": "Chats de búsqueda",
+	"Search Collection": "Buscar Colección",
+	"search for tags": "",
+	"Search Functions": "Buscar Funciones",
+	"Search Knowledge": "Buscar Conocimiento",
+	"Search Models": "Buscar Modelos",
+	"Search Prompts": "Buscar Prompts",
+	"Search Query Generation Prompt": "Búsqueda de consulta de generación de prompts",
+	"Search Result Count": "Recuento de resultados de búsqueda",
+	"Search Tools": "Búsqueda de herramientas",
+	"SearchApi API Key": "Clave API de SearchApi",
+	"SearchApi Engine": "Motor de SearchApi",
+	"Searched {{count}} sites_one": "Buscado {{count}} sites_one",
+	"Searched {{count}} sites_many": "Buscado {{count}} sites_many",
+	"Searched {{count}} sites_other": "Buscó {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "Buscando \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "Buscando Conocimiento para \"{{searchQuery}}\"",
+	"Searxng Query URL": "Searxng URL de consulta",
+	"See readme.md for instructions": "Vea el readme.md para instrucciones",
+	"See what's new": "Ver las novedades",
+	"Seed": "Seed",
+	"Select a base model": "Seleccionar un modelo base",
+	"Select a engine": "Busca un motor",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Busca una función",
+	"Select a model": "Selecciona un modelo",
+	"Select a pipeline": "Selección de una Pipeline",
+	"Select a pipeline url": "Selección de una dirección URL de Pipeline",
+	"Select a tool": "Busca una herramienta",
+	"Select an Ollama instance": "Seleccione una instancia de Ollama",
+	"Select Engine": "Selecciona Motor",
+	"Select Knowledge": "Selecciona Conocimiento",
+	"Select model": "Selecciona un modelo",
+	"Select only one model to call": "Selecciona sólo un modelo para llamar",
+	"Selected model(s) do not support image inputs": "Los modelos seleccionados no admiten entradas de imagen",
+	"Semantic distance to query": "",
+	"Send": "Enviar",
+	"Send a Message": "Enviar un Mensaje",
+	"Send message": "Enviar Mensaje",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "Envia `stream_options: { include_usage: true }` en la solicitud.\nLos proveedores admitidos devolverán información de uso del token en la respuesta cuando se establezca.",
+	"September": "Septiembre",
+	"Serper API Key": "Clave API de Serper",
+	"Serply API Key": "Clave API de Serply",
+	"Serpstack API Key": "Clave API de Serpstack",
+	"Server connection verified": "Conexión del servidor verificada",
+	"Set as default": "Establecer por defecto",
+	"Set CFG Scale": "Establecer la escala CFG",
+	"Set Default Model": "Establecer modelo predeterminado",
+	"Set embedding model (e.g. {{model}})": "Establecer modelo de embedding (ej. {{model}})",
+	"Set Image Size": "Establecer tamaño de imagen",
+	"Set reranking model (e.g. {{model}})": "Establecer modelo de reranking (ej. {{model}})",
+	"Set Sampler": "Establecer Sampler",
+	"Set Scheduler": "Establecer Programador",
+	"Set Steps": "Establecer Pasos",
+	"Set Task Model": "Establecer modelo de tarea",
+	"Set Voice": "Establecer la voz",
+	"Set whisper model": "",
+	"Settings": "Configuración",
+	"Settings saved successfully!": "¡Configuración guardada con éxito!",
+	"Share": "Compartir",
+	"Share Chat": "Compartir Chat",
+	"Share to OpenWebUI Community": "Compartir con la comunidad OpenWebUI",
+	"short-summary": "resumen-corto",
+	"Show": "Mostrar",
+	"Show Admin Details in Account Pending Overlay": "Mostrar detalles de administración en la capa de espera de la cuenta",
+	"Show Model": "Mostrar Modelo",
+	"Show shortcuts": "Mostrar atajos",
+	"Show your support!": "¡Muestra tu apoyo!",
+	"Showcased creativity": "Creatividad mostrada",
+	"Sign in": "Iniciar sesión",
+	"Sign in to {{WEBUI_NAME}}": "Iniciar sesión en {{WEBUI_NAME}}",
+	"Sign Out": "Cerrar sesión",
+	"Sign up": "Crear una cuenta",
+	"Sign up to {{WEBUI_NAME}}": "Crear una cuenta en {{WEBUI_NAME}}",
+	"Signing in to {{WEBUI_NAME}}": "Iniciando sesión en {{WEBUI_NAME}}",
+	"Source": "Fuente",
+	"Speech Playback Speed": "Velocidad de reproducción de voz",
+	"Speech recognition error: {{error}}": "Error de reconocimiento de voz: {{error}}",
+	"Speech-to-Text Engine": "Motor de voz a texto",
+	"Stop": "",
+	"Stop Sequence": "Detener secuencia",
+	"Stream Chat Response": "",
+	"STT Model": "Modelo STT",
+	"STT Settings": "Configuraciones de STT",
+	"Subtitle (e.g. about the Roman Empire)": "Subtítulo (por ejemplo, sobre el Imperio Romano)",
+	"Success": "Éxito",
+	"Successfully updated.": "Actualizado exitosamente.",
+	"Suggested": "Sugerido",
+	"Support": "Soporte",
+	"Support this plugin:": "Brinda soporte a este plugin:",
+	"Sync directory": "Sincroniza directorio",
+	"System": "Sistema",
+	"System Instructions": "",
+	"System Prompt": "Prompt del sistema",
+	"Tags": "Etiquetas",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Toca para interrumpir",
+	"Tavily API Key": "Clave API de Tavily",
+	"Tell us more:": "Dinos más:",
+	"Temperature": "Temperatura",
+	"Template": "Plantilla",
+	"Temporary Chat": "Chat temporal",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Motor de texto a voz",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "¡Gracias por tu retroalimentación!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Los desarrolladores de este plugin son apasionados voluntarios de la comunidad. Si encuentras este plugin útil, por favor considere contribuir a su desarrollo.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "El tamaño máximo del archivo en MB. Si el tamaño del archivo supera este límite, el archivo no se subirá.",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "El número máximo de archivos que se pueden utilizar a la vez en chat. Si este límite es superado, los archivos no se subirán.",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "El puntaje debe ser un valor entre 0.0 (0%) y 1.0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "Pensando...",
+	"This action cannot be undone. Do you wish to continue?": "Esta acción no se puede deshacer. ¿Desea continuar?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Esto garantiza que sus valiosas conversaciones se guarden de forma segura en su base de datos en el backend. ¡Gracias!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Esta es una característica experimental que puede no funcionar como se esperaba y está sujeto a cambios en cualquier momento.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": " Esta opción eliminará todos los archivos existentes en la colección y los reemplazará con nuevos archivos subidos.",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Esto eliminará",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "Esto reseteará la base de conocimientos y sincronizará todos los archivos. ¿Desea continuar?",
+	"Thorough explanation": "Explicación exhaustiva",
+	"Tika": "Tika",
+	"Tika Server URL required.": "URL del servidor de Tika",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Consejo: Actualice múltiples variables consecutivamente presionando la tecla tab en la entrada del chat después de cada reemplazo.",
+	"Title": "Título",
+	"Title (e.g. Tell me a fun fact)": "Título (por ejemplo, cuéntame una curiosidad)",
+	"Title Auto-Generation": "Generación automática de títulos",
+	"Title cannot be an empty string.": "El título no puede ser una cadena vacía.",
+	"Title Generation Prompt": "Prompt de generación de título",
+	"To access the available model names for downloading,": "Para acceder a los nombres de modelos disponibles para descargar,",
+	"To access the GGUF models available for downloading,": "Para acceder a los modelos GGUF disponibles para descargar,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Para acceder al interfaz de usuario web, por favor contacte al administrador. Los administradores pueden administrar los estados de los usuarios desde el panel de administración.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "Para adjuntar la base de conocimientos aquí, agreguelas al área de trabajo \"Conocimiento\" primero.",
+	"to chat input.": "a la entrada del chat.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Para seleccionar acciones aquí, agreguelas al área de trabajo \"Funciones\" primero.",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Para seleccionar filtros aquí, agreguelos al área de trabajo \"Funciones\" primero.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Para seleccionar herramientas aquí, agreguelas al área de trabajo \"Herramientas\" primero.",
+	"Toast notifications for new updates": "",
+	"Today": "Hoy",
+	"Toggle settings": "Alternar configuración",
+	"Toggle sidebar": "Alternar barra lateral",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Tokens a mantener en el contexto de actualización (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Herramienta creada con éxito",
+	"Tool deleted successfully": "Herramienta eliminada con éxito",
+	"Tool imported successfully": "Herramienta importada con éxito",
+	"Tool updated successfully": "Herramienta actualizada con éxito",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Descripción del Kit de herramientas (por ejemplo, una herramienta para realizar diversas operaciones)",
+	"Toolkit ID (e.g. my_toolkit)": "ID del Kit de Herramientas (por ejemplo, mi_herramienta)",
+	"Toolkit Name (e.g. My ToolKit)": "Nombre del Kit de Herramientas (por ejemplo, mi kit de herramientas)",
+	"Tools": "Herramientas",
+	"Tools are a function calling system with arbitrary code execution": "Las herramientas son un sistema de llamada de funciones con código arbitrario",
+	"Tools have a function calling system that allows arbitrary code execution": "Las herramientas tienen un sistema de llamadas de funciones que permite la ejecución de código arbitrario",
+	"Tools have a function calling system that allows arbitrary code execution.": "Las herramientas tienen un sistema de llamada de funciones que permite la ejecución de código arbitrario.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "¿Problemas para acceder a Ollama?",
+	"TTS Model": "Modelo TTS",
+	"TTS Settings": "Configuración de TTS",
+	"TTS Voice": "Voz del TTS",
+	"Type": "Tipo",
+	"Type Hugging Face Resolve (Download) URL": "Escriba la URL (Descarga) de Hugging Face Resolve",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "¡Uh oh! Hubo un problema al conectarse a {{provider}}.",
+	"UI": "UI",
+	"Unpin": "Desanclar",
+	"Untagged": "",
+	"Update": "Actualizar",
+	"Update and Copy Link": "Actualizar y copiar enlace",
+	"Update for the latest features and improvements.": "Actualize para las últimas características e mejoras.",
+	"Update password": "Actualizar contraseña",
+	"Updated": "",
+	"Updated at": "Actualizado en",
+	"Updated At": "",
+	"Upload": "Subir",
+	"Upload a GGUF model": "Subir un modelo GGUF",
+	"Upload directory": "Directorio de carga",
+	"Upload files": "Subir archivos",
+	"Upload Files": "Subir archivos",
+	"Upload Pipeline": "Subir Pipeline",
+	"Upload Progress": "Progreso de carga",
+	"URL Mode": "Modo de URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "Utilize '#' en el prompt para cargar y incluir su conocimiento.",
+	"Use Gravatar": "Usar Gravatar",
+	"Use Initials": "Usar Iniciales",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "usuario",
+	"User": "",
+	"User location successfully retrieved.": "Localización del usuario recuperada con éxito.",
+	"User Permissions": "Permisos de usuario",
+	"Users": "Usuarios",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Utilizar",
+	"Valid time units:": "Unidades válidas de tiempo:",
+	"Valves": "Valves",
+	"Valves updated": "Valves actualizados",
+	"Valves updated successfully": "Valves actualizados con éxito",
+	"variable": "variable",
+	"variable to have them replaced with clipboard content.": "variable para reemplazarlos con el contenido del portapapeles.",
+	"Version": "Versión",
+	"Version {{selectedVersion}} of {{totalVersions}}": "Versión {{selectedVersion}} de {{totalVersions}}",
+	"Voice": "Voz",
+	"Voice Input": "",
+	"Warning": "Advertencia",
+	"Warning:": "Advertencia:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Advertencia: Si actualiza o cambia su modelo de inserción, necesitará volver a importar todos los documentos.",
+	"Web": "Web",
+	"Web API": "API Web",
+	"Web Loader Settings": "Web Loader Settings",
+	"Web Search": "Búsqueda en la Web",
+	"Web Search Engine": "Motor de búsqueda web",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "Configuración del WebUI",
+	"WebUI will make requests to": "WebUI realizará solicitudes a",
+	"What’s New in": "Novedades en",
+	"Whisper (Local)": "Whisper (Local)",
+	"Widescreen Mode": "Modo de pantalla ancha",
+	"Won": "",
+	"Workspace": "Espacio de trabajo",
+	"Write a prompt suggestion (e.g. Who are you?)": "Escribe una sugerencia para un prompt (por ejemplo, ¿quién eres?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Escribe un resumen en 50 palabras que resuma [tema o palabra clave].",
+	"Write something...": "",
+	"Yesterday": "Ayer",
+	"You": "Usted",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Puede personalizar sus interacciones con LLMs añadiendo memorias a través del botón 'Gestionar' debajo, haciendo que sean más útiles y personalizados para usted.",
+	"You cannot clone a base model": "No se puede clonar un modelo base",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "No tiene conversaciones archivadas.",
+	"You have shared this chat": "Usted ha compartido esta conversación",
+	"You're a helpful assistant.": "Usted es un asistente útil.",
+	"You're now logged in.": "Usted ahora está conectado.",
+	"Your account status is currently pending activation.": "El estado de su cuenta actualmente se encuentra pendiente de activación.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Su contribución completa irá directamente a el desarrollador del plugin; Open WebUI no toma ningun porcentaje. Sin embargo, la plataforma de financiación elegida podría tener sus propias tarifas.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Configuración del cargador de Youtube"
+}
diff --git a/src/lib/i18n/locales/fa-IR/translation.json b/src/lib/i18n/locales/fa-IR/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..92ceb9286f8e5c33926f7db17de5673904120a3b
--- /dev/null
+++ b/src/lib/i18n/locales/fa-IR/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' یا '-1' برای غیر فعال کردن انقضا.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(e.g. `sh webui.sh --api`)",
+	"(latest)": "(آخرین)",
+	"{{ models }}": "{{ مدل }}",
+	"{{ owner }}: You cannot delete a base model": "{{ مالک }}: شما نمیتوانید یک مدل پایه را حذف کنید",
+	"{{user}}'s Chats": "{{user}} چت ها",
+	"{{webUIName}} Backend Required": "بکند {{webUIName}} نیاز است.",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "یک مدل وظیفه هنگام انجام وظایف مانند تولید عناوین برای چت ها و نمایش های جستجوی وب استفاده می شود.",
+	"a user": "یک کاربر",
+	"About": "درباره",
+	"Account": "حساب کاربری",
+	"Account Activation Pending": "",
+	"Accurate information": "اطلاعات دقیق",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "اضافه کردن",
+	"Add a model id": "افزودن شناسه مدل",
+	"Add a short description about what this model does": "اضافه کردن توضیحات کوتاه در مورد انچه که این مدل انجام می دهد",
+	"Add a short title for this prompt": "یک عنوان کوتاه برای این درخواست اضافه کنید",
+	"Add a tag": "اضافه کردن یک تگ",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "اضافه کردن یک درخواست سفارشی",
+	"Add Files": "اضافه کردن فایل\u200cها",
+	"Add Memory": "اضافه کردن یادگیری",
+	"Add Model": "اضافه کردن مدل",
+	"Add Tag": "",
+	"Add Tags": "اضافه کردن تگ\u200cها",
+	"Add text content": "",
+	"Add User": "اضافه کردن کاربر",
+	"Adjusting these settings will apply changes universally to all users.": "با تنظیم این تنظیمات، تغییرات به طور کلی برای همه کاربران اعمال می شود.",
+	"admin": "مدیر",
+	"Admin": "",
+	"Admin Panel": "پنل مدیریت",
+	"Admin Settings": "تنظیمات مدیریت",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "پارامترهای پیشرفته",
+	"Advanced Params": "پارام های پیشرفته",
+	"All chats": "",
+	"All Documents": "تمام سند ها",
+	"Allow Chat Deletion": "اجازه حذف گپ",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "حروف الفبایی و خط فاصله",
+	"Already have an account?": "از قبل حساب کاربری دارید؟",
+	"an assistant": "یک دستیار",
+	"and": "و",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "و یک لینک به اشتراک گذاری جدید ایجاد کنید.",
+	"API Base URL": "API Base URL",
+	"API Key": "API Key",
+	"API Key created.": "API Key created.",
+	"API keys": "API keys",
+	"April": "ژوئن",
+	"Archive": "آرشیو",
+	"Archive All Chats": "بایگانی همه گفتگوها",
+	"Archived Chats": "آرشیو تاریخچه چت",
+	"are allowed - Activate this command by typing": "مجاز هستند - این دستور را با تایپ کردن این فعال کنید:",
+	"Are you sure?": "آیا مطمئن هستید؟",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "پیوست فایل",
+	"Attention to detail": "دقیق",
+	"Audio": "صدا",
+	"August": "آگوست",
+	"Auto-playback response": "پخش خودکار پاسخ ",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "پایه URL AUTOMATIC1111 ",
+	"AUTOMATIC1111 Base URL is required.": "به URL پایه AUTOMATIC1111 مورد نیاز است.",
+	"Available list": "",
+	"available!": "در دسترس!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "بازگشت",
+	"Bad Response": "پاسخ خوب نیست",
+	"Banners": "بنر",
+	"Base Model (From)": "مدل پایه (از)",
+	"Batch Size (num_batch)": "",
+	"before": "قبل",
+	"Being lazy": "حالت سازنده",
+	"Brave Search API Key": "کلید API جستجوی شجاع",
+	"Bypass SSL verification for Websites": "عبور از تأیید SSL برای وب سایت ها",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "لغو",
+	"Capabilities": "قابلیت",
+	"Change Password": "تغییر رمز عبور",
+	"Character": "",
+	"Chat": "گپ",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "UI\u200cی\u200c گفتگو\u200c",
+	"Chat Controls": "",
+	"Chat direction": "جهت\u200cگفتگو",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "گپ\u200cها",
+	"Check Again": "چک مجدد",
+	"Check for updates": "بررسی به\u200cروزرسانی",
+	"Checking for updates...": "در حال بررسی برای به\u200cروزرسانی..",
+	"Choose a model before saving...": "قبل از ذخیره یک مدل را انتخاب کنید...",
+	"Chunk Overlap": "همپوشانی تکه",
+	"Chunk Params": "پارامترهای تکه",
+	"Chunk Size": "اندازه تکه",
+	"Citation": "استناد",
+	"Clear memory": "",
+	"Click here for help.": "برای کمک اینجا را کلیک کنید.",
+	"Click here to": "برای کمک اینجا را کلیک کنید.",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "برای انتخاب اینجا کلیک کنید",
+	"Click here to select a csv file.": "برای انتخاب یک فایل csv اینجا را کلیک کنید.",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "اینجا کلیک کنید.",
+	"Click on the user role button to change a user's role.": "برای تغییر نقش کاربر، روی دکمه نقش کاربر کلیک کنید.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "کلون",
+	"Close": "بسته",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "مجموعه",
+	"ComfyUI": "کومیوآی",
+	"ComfyUI Base URL": "URL پایه کومیوآی",
+	"ComfyUI Base URL is required.": "URL پایه کومیوآی الزامی است.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "دستور",
+	"Completions": "",
+	"Concurrent Requests": "درخواست های همزمان",
+	"Confirm": "",
+	"Confirm Password": "تایید رمز عبور",
+	"Confirm your action": "",
+	"Connections": "ارتباطات",
+	"Contact Admin for WebUI Access": "",
+	"Content": "محتوا",
+	"Content Extraction": "",
+	"Context Length": "طول زمینه",
+	"Continue Response": "ادامه پاسخ",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "URL چت به کلیپ بورد کپی شد!",
+	"Copied to clipboard": "",
+	"Copy": "کپی",
+	"Copy last code block": "کپی آخرین بلوک کد",
+	"Copy last response": "کپی آخرین پاسخ",
+	"Copy Link": "کپی لینک",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "کپی کردن در کلیپ بورد با موفقیت انجام شد!",
+	"Create a model": "ایجاد یک مدل",
+	"Create Account": "ساخت حساب کاربری",
+	"Create Knowledge": "",
+	"Create new key": "ساخت کلید جدید",
+	"Create new secret key": "ساخت کلید gehez جدید",
+	"Created at": "ایجاد شده در",
+	"Created At": "ایجاد شده در",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "مدل فعلی",
+	"Current Password": "رمز عبور فعلی",
+	"Custom": "دلخواه",
+	"Customize models for a specific purpose": "سفارشی کردن مدل ها برای یک هدف خاص",
+	"Dark": "تیره",
+	"Dashboard": "",
+	"Database": "پایگاه داده",
+	"December": "دسامبر",
+	"Default": "پیشفرض",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "پیشفرض (SentenceTransformers)",
+	"Default Model": "مدل پیشفرض",
+	"Default model updated": "مدل پیشفرض به\u200cروزرسانی شد",
+	"Default Prompt Suggestions": "پیشنهادات پرامپت پیش فرض",
+	"Default User Role": "نقش کاربر پیش فرض",
+	"Delete": "حذف",
+	"Delete a model": "حذف یک مدل",
+	"Delete All Chats": "حذف همه گفتگوها",
+	"Delete chat": "حذف گپ",
+	"Delete Chat": "حذف گپ",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "حذف این لینک",
+	"Delete tool?": "",
+	"Delete User": "حذف کاربر",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} پاک شد",
+	"Deleted {{name}}": "حذف شده {{name}}",
+	"Description": "توضیحات",
+	"Didn't fully follow instructions": "نمی تواند دستورالعمل را کامل پیگیری کند",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "کشف یک مدل",
+	"Discover a prompt": "یک اعلان را کشف کنید",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "پرامپت\u200cهای سفارشی را کشف، دانلود و کاوش کنید",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "پیش تنظیمات مدل را کشف، دانلود و کاوش کنید",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "نمایش نام کاربری به جای «شما» در چت",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "سند",
+	"Documentation": "",
+	"Documents": "اسناد",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "هیچ اتصال خارجی ایجاد نمی کند و داده های شما به طور ایمن در سرور میزبان محلی شما باقی می ماند.",
+	"Don't have an account?": "حساب کاربری ندارید؟",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "نظری ندارید؟",
+	"Done": "",
+	"Download": "دانلود کن",
+	"Download canceled": "دانلود لغو شد",
+	"Download Database": "دانلود پایگاه داده",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "هر فایلی را اینجا رها کنید تا به مکالمه اضافه شود",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "به طور مثال '30s','10m'. واحد\u200cهای زمانی معتبر 's', 'm', 'h' هستند.",
+	"Edit": "ویرایش",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "ویرایش کاربر",
+	"ElevenLabs": "",
+	"Email": "ایمیل",
+	"Embedding Batch Size": "",
+	"Embedding Model": "مدل پیدائش",
+	"Embedding Model Engine": "محرک مدل پیدائش",
+	"Embedding model set to \"{{embedding_model}}\"": "مدل پیدائش را به \"{{embedding_model}}\" تنظیم کنید",
+	"Enable Community Sharing": "فعالسازی اشتراک انجمن",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "فعال کردن ثبت نام\u200cهای جدید",
+	"Enable Web Search": "فعالسازی جستجوی وب",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "اطمینان حاصل کنید که فایل CSV شما شامل چهار ستون در این ترتیب است: نام، ایمیل، رمز عبور، نقش.",
+	"Enter {{role}} message here": "پیام {{role}} را اینجا وارد کنید",
+	"Enter a detail about yourself for your LLMs to recall": "برای ذخیره سازی اطلاعات خود، یک توضیح کوتاه درباره خود را وارد کنید",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "کلید API جستجوی شجاع را وارد کنید",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "مقدار Chunk Overlap را وارد کنید",
+	"Enter Chunk Size": "مقدار Chunk Size را وارد کنید",
+	"Enter description": "",
+	"Enter Github Raw URL": "ادرس Github Raw را وارد کنید",
+	"Enter Google PSE API Key": "کلید API گوگل PSE را وارد کنید",
+	"Enter Google PSE Engine Id": "شناسه موتور PSE گوگل را وارد کنید",
+	"Enter Image Size (e.g. 512x512)": "اندازه تصویر را وارد کنید (مثال: 512x512)",
+	"Enter language codes": "کد زبان را وارد کنید",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "تگ مدل را وارد کنید (مثلا {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "تعداد گام ها را وارد کنید (مثال: 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "امتیاز را وارد کنید",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "نشانی وب پرسوجوی Searxng را وارد کنید",
+	"Enter Serper API Key": "کلید API Serper را وارد کنید",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "کلید API Serpstack را وارد کنید",
+	"Enter stop sequence": "توالی توقف را وارد کنید",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "مقدار Top K را وارد کنید",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "مقدار URL را وارد کنید (مثال http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "مقدار URL را وارد کنید (مثال http://localhost:11434)",
+	"Enter Your Email": "ایمیل خود را وارد کنید",
+	"Enter Your Full Name": "نام کامل خود را وارد کنید",
+	"Enter your message": "",
+	"Enter Your Password": "رمز عبور خود را وارد کنید",
+	"Enter Your Role": "نقش خود را وارد کنید",
+	"Error": "خطا",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "آزمایشی",
+	"Export": "صادرات",
+	"Export All Chats (All Users)": "اکسپورت از همه گپ\u200cها(همه کاربران)",
+	"Export chat (.json)": "",
+	"Export Chats": "اکسپورت از گپ\u200cها",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "مدل های صادرات",
+	"Export Prompts": "اکسپورت از پرامپت\u200cها",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "ایجاد کلید API با خطا مواجه شد.",
+	"Failed to read clipboard contents": "خواندن محتوای کلیپ بورد ناموفق بود",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "فوری",
+	"Feedback History": "",
+	"Feel free to add specific details": "اگر به دلخواه، معلومات خاصی اضافه کنید",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "حالت فایل",
+	"File not found.": "فایل یافت نشد.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "فانگ سرفیس شناسایی شد: نمی توان از نمایه شما به عنوان آواتار استفاده کرد. پیش فرض به عکس پروفایل پیش فرض برگشت داده شد.",
+	"Fluidly stream large external response chunks": "تکه های پاسخ خارجی بزرگ را به صورت سیال پخش کنید",
+	"Focus chat input": "فوکوس کردن ورودی گپ",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "دستورالعمل ها را کاملا دنبال کرد",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "مجازات فرکانس",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "عمومی",
+	"General Settings": "تنظیمات عمومی",
+	"Generate Image": "",
+	"Generating search query": "در حال تولید پرسوجوی جستجو",
+	"Generation Info": "اطلاعات تولید",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "پاسخ خوب",
+	"Google PSE API Key": "گوگل PSE API کلید",
+	"Google PSE Engine Id": "شناسه موتور PSE گوگل",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "ندارد.",
+	"Hello, {{name}}": "سلام، {{name}}",
+	"Help": "کمک",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "پنهان",
+	"Hide Model": "",
+	"How can I help you today?": "امروز چطور می توانم کمک تان کنم؟",
+	"Hybrid Search": "جستجوی همزمان",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "تولید تصویر (آزمایشی)",
+	"Image Generation Engine": "موتور تولید تصویر",
+	"Image Settings": "تنظیمات تصویر",
+	"Images": "تصاویر",
+	"Import Chats": "ایمپورت گپ\u200cها",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "واردات مدلها",
+	"Import Prompts": "ایمپورت پرامپت\u200cها",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "فلگ `--api` را هنکام اجرای stable-diffusion-webui استفاده کنید.",
+	"Info": "اطلاعات",
+	"Input commands": "ورودی دستورات",
+	"Install from Github URL": "نصب از ادرس Github",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "رابط",
+	"Invalid file format.": "",
+	"Invalid Tag": "تگ نامعتبر",
+	"January": "ژانویه",
+	"join our Discord for help.": "برای کمک به دیسکورد ما بپیوندید.",
+	"JSON": "JSON",
+	"JSON Preview": "پیش نمایش JSON",
+	"July": "ژوئن",
+	"June": "جولای",
+	"JWT Expiration": "JWT انقضای",
+	"JWT Token": "JWT توکن",
+	"Keep Alive": "Keep Alive",
+	"Keyboard shortcuts": "میانبرهای صفحه کلید",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "زبان",
+	"large language models, locally.": "",
+	"Last Active": "آخرین فعال",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "روشن",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "مدل\u200cهای زبانی بزرگ می\u200cتوانند اشتباه کنند. اطلاعات مهم را راستی\u200cآزمایی کنید.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "ساخته شده توسط OpenWebUI Community",
+	"Make sure to enclose them with": "مطمئن شوید که آنها را با این محصور کنید:",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "مدیریت مدل\u200cها",
+	"Manage Ollama Models": "مدیریت مدل\u200cهای اولاما",
+	"Manage Pipelines": "مدیریت خطوط لوله",
+	"March": "مارچ",
+	"Max Tokens (num_predict)": "توکنهای بیشینه (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "حداکثر 3 مدل را می توان به طور همزمان دانلود کرد. لطفاً بعداً دوباره امتحان کنید.",
+	"May": "ماهی",
+	"Memories accessible by LLMs will be shown here.": "حافظه های دسترسی به LLMs در اینجا نمایش داده می شوند.",
+	"Memory": "حافظه",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "پیام های شما بعد از ایجاد لینک شما به اشتراک نمی گردد. کاربران با لینک URL می توانند چت اشتراک را مشاهده کنند.",
+	"Min P": "",
+	"Minimum Score": "نماد کمینه",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "مدل '{{modelName}}' با موفقیت دانلود شد.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "مدل '{{modelTag}}' در حال حاضر در صف برای دانلود است.",
+	"Model {{modelId}} not found": "مدل {{modelId}} یافت نشد",
+	"Model {{modelName}} is not vision capable": "مدل {{modelName}} قادر به بینایی نیست",
+	"Model {{name}} is now {{status}}": "مدل {{name}} در حال حاضر {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "مسیر فایل سیستم مدل یافت شد. برای بروزرسانی نیاز است نام کوتاه مدل وجود داشته باشد.",
+	"Model ID": "شناسه مدل",
+	"Model Name": "",
+	"Model not selected": "مدل انتخاب نشده",
+	"Model Params": "مدل پارامز",
+	"Model updated successfully": "",
+	"Model Whitelisting": "لیست سفید مدل",
+	"Model(s) Whitelisted": "مدل در لیست سفید ثبت شد",
+	"Modelfile Content": "محتویات فایل مدل",
+	"Models": "مدل\u200cها",
+	"more": "",
+	"More": "بیشتر",
+	"Move to Top": "",
+	"Name": "نام",
+	"Name your model": "نام مدل خود را",
+	"New Chat": "گپ جدید",
+	"New folder": "",
+	"New Password": "رمز عبور جدید",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "نتیجه\u200cای یافت نشد",
+	"No search query generated": "پرسوجوی جستجویی ایجاد نشده است",
+	"No source available": "منبعی در دسترس نیست",
+	"No valves to update": "",
+	"None": "هیچ کدام",
+	"Not factually correct": "اشتباهی فکری نیست",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "توجه: اگر حداقل نمره را تعیین کنید، جستجو تنها اسنادی را با نمره بیشتر یا برابر با حداقل نمره باز می گرداند.",
+	"Notes": "",
+	"Notifications": "اعلان",
+	"November": "نوامبر",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (اولاما)",
+	"OAuth ID": "",
+	"October": "اکتبر",
+	"Off": "خاموش",
+	"Okay, Let's Go!": "باشه، بزن بریم!",
+	"OLED Dark": "OLED تیره",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "API Ollama غیرفعال شد",
+	"Ollama API is disabled": "",
+	"Ollama Version": "نسخه اولاما",
+	"On": "روشن",
+	"Only": "فقط",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "فقط کاراکترهای الفبایی و خط فاصله در رشته فرمان مجاز هستند.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "اوه! به نظر می رسد URL نامعتبر است. لطفاً دوباره بررسی کنید و دوباره امتحان کنید.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "اوه! شما از یک روش پشتیبانی نشده (فقط frontend) استفاده می کنید. لطفاً WebUI را از بکند اجرا کنید.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "باز کردن گپ جدید",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API Config",
+	"OpenAI API Key is required.": "مقدار کلید OpenAI API مورد نیاز است.",
+	"OpenAI URL/Key required.": "URL/Key OpenAI مورد نیاز است.",
+	"or": "روشن",
+	"Other": "دیگر",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "رمز عبور",
+	"PDF document (.pdf)": "PDF سند (.pdf)",
+	"PDF Extract Images (OCR)": "استخراج تصاویر از PDF (OCR)",
+	"pending": "در انتظار",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "هنگام دسترسی به میکروفون، اجازه داده نشد: {{error}}",
+	"Personalization": "شخصی سازی",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "خط لوله",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "شیرالات خطوط لوله",
+	"Plain text (.txt)": "متن ساده (.txt)",
+	"Playground": "زمین بازی",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "نظرات مثبت",
+	"Previous 30 days": "30 روز قبل",
+	"Previous 7 days": "7 روز قبل",
+	"Profile Image": "تصویر پروفایل",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "پیشنهاد (برای مثال: به من بگوید چیزی که برای من یک کاربرد داره درباره ایران)",
+	"Prompt Content": "محتویات پرامپت",
+	"Prompt suggestions": "پیشنهادات پرامپت",
+	"Prompts": "پرامپت\u200cها",
+	"Pull \"{{searchValue}}\" from Ollama.com": "بازگرداندن \"{{searchValue}}\" از Ollama.com",
+	"Pull a model from Ollama.com": "دریافت یک مدل از Ollama.com",
+	"Query Params": "پارامترهای پرس و جو",
+	"RAG Template": "RAG الگوی",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "خواندن به صورت صوتی",
+	"Record voice": "ضبط صدا",
+	"Redirecting you to OpenWebUI Community": "در حال هدایت به OpenWebUI Community",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "رد شده زمانی که باید نباشد",
+	"Regenerate": "ری\u200cسازی",
+	"Release Notes": "یادداشت\u200cهای انتشار",
+	"Relevance": "",
+	"Remove": "حذف",
+	"Remove Model": "حذف مدل",
+	"Rename": "تغییر نام",
+	"Repeat Last N": "Repeat Last N",
+	"Request Mode": "حالت درخواست",
+	"Reranking Model": "مدل ری\u200cشناسی مجدد غیرفعال است",
+	"Reranking model disabled": "مدل ری\u200cشناسی مجدد غیرفعال است",
+	"Reranking model set to \"{{reranking_model}}\"": "مدل ری\u200cشناسی مجدد به \"{{reranking_model}}\" تنظیم شده است",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "کپی خودکار پاسخ به کلیپ بورد",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "نقش",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "ذخیره",
+	"Save & Create": "ذخیره و ایجاد",
+	"Save & Update": "ذخیره و به\u200cروزرسانی",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "ذخیره گزارش\u200cهای چت مستقیماً در حافظه مرورگر شما دیگر پشتیبانی نمی\u200cشود. لطفاً با کلیک بر روی دکمه زیر، چند لحظه برای دانلود و حذف گزارش های چت خود وقت بگذارید. نگران نباشید، شما به راحتی می توانید گزارش های چت خود را از طریق بکند دوباره وارد کنید",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "جستجو",
+	"Search a model": "جستجوی مدل",
+	"Search Chats": "جستجو گپ ها",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "مدل های جستجو",
+	"Search Prompts": "جستجوی پرامپت\u200cها",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "تعداد نتایج جستجو",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "جستجو {{count}} sites_one",
+	"Searched {{count}} sites_other": "جستجو {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "نشانی وب جستجوی Searxng",
+	"See readme.md for instructions": "برای مشاهده دستورالعمل\u200cها به readme.md مراجعه کنید",
+	"See what's new": "ببینید موارد جدید چه بوده",
+	"Seed": "Seed",
+	"Select a base model": "انتخاب یک مدل پایه",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "انتخاب یک مدل",
+	"Select a pipeline": "انتخاب یک خط لوله",
+	"Select a pipeline url": "یک ادرس خط لوله را انتخاب کنید",
+	"Select a tool": "",
+	"Select an Ollama instance": "انتخاب یک نمونه از اولاما",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "انتخاب یک مدل",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "مدل) های (انتخاب شده ورودیهای تصویر را پشتیبانی نمیکند",
+	"Semantic distance to query": "",
+	"Send": "ارسال",
+	"Send a Message": "ارسال یک پیام",
+	"Send message": "ارسال پیام",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "سپتامبر",
+	"Serper API Key": "کلید API Serper",
+	"Serply API Key": "",
+	"Serpstack API Key": "کلید API Serpstack",
+	"Server connection verified": "اتصال سرور تأیید شد",
+	"Set as default": "تنظیم به عنوان پیشفرض",
+	"Set CFG Scale": "",
+	"Set Default Model": "تنظیم مدل پیش فرض",
+	"Set embedding model (e.g. {{model}})": "تنظیم مدل پیچشی (برای مثال {{model}})",
+	"Set Image Size": "تنظیم اندازه تصویر",
+	"Set reranking model (e.g. {{model}})": "تنظیم مدل ری\u200cراینگ (برای مثال {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "تنظیم گام\u200cها",
+	"Set Task Model": "تنظیم مدل تکلیف",
+	"Set Voice": "تنظیم صدا",
+	"Set whisper model": "",
+	"Settings": "تنظیمات",
+	"Settings saved successfully!": "تنظیمات با موفقیت ذخیره شد!",
+	"Share": "اشتراک\u200cگذاری",
+	"Share Chat": "اشتراک\u200cگذاری چت",
+	"Share to OpenWebUI Community": "اشتراک گذاری با OpenWebUI Community",
+	"short-summary": "خلاصه کوتاه",
+	"Show": "نمایش",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "نمایش میانبرها",
+	"Show your support!": "",
+	"Showcased creativity": "ایده\u200cآفرینی",
+	"Sign in": "ورود",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "خروج",
+	"Sign up": "ثبت نام",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "منبع",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "خطای تشخیص گفتار: {{error}}",
+	"Speech-to-Text Engine": "موتور گفتار به متن",
+	"Stop": "",
+	"Stop Sequence": "توالی توقف",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "STT تنظیمات",
+	"Subtitle (e.g. about the Roman Empire)": "زیرنویس (برای مثال: درباره رمانی)",
+	"Success": "موفقیت",
+	"Successfully updated.": "با موفقیت به روز شد",
+	"Suggested": "پیشنهادی",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "سیستم",
+	"System Instructions": "",
+	"System Prompt": "پرامپت سیستم",
+	"Tags": "تگ\u200cها",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "بیشتر بگویید:",
+	"Temperature": "دما",
+	"Template": "الگو",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "موتور تبدیل متن به گفتار",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "با تشکر از بازخورد شما!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "امتیاز باید یک مقدار بین 0.0 (0%) و 1.0 (100%) باشد.",
+	"Theme": "قالب",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "این تضمین می کند که مکالمات ارزشمند شما به طور ایمن در پایگاه داده بکند ذخیره می شود. تشکر!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "توضیح کامل",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "با فشردن کلید Tab در ورودی چت پس از هر بار تعویض، چندین متغیر را به صورت متوالی به روزرسانی کنید.",
+	"Title": "عنوان",
+	"Title (e.g. Tell me a fun fact)": "عنوان (برای مثال: به من بگوید چیزی که دوست دارید)",
+	"Title Auto-Generation": "تولید خودکار عنوان",
+	"Title cannot be an empty string.": "عنوان نمی تواند یک رشته خالی باشد.",
+	"Title Generation Prompt": "پرامپت تولید عنوان",
+	"To access the available model names for downloading,": "برای دسترسی به نام مدل های موجود برای دانلود،",
+	"To access the GGUF models available for downloading,": "برای دسترسی به مدل\u200cهای GGUF موجود برای دانلود،",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "در ورودی گپ.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "امروز",
+	"Toggle settings": "نمایش/عدم نمایش تنظیمات",
+	"Toggle sidebar": "نمایش/عدم نمایش نوار کناری",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "در دسترسی به اولاما مشکل دارید؟",
+	"TTS Model": "",
+	"TTS Settings": "تنظیمات TTS",
+	"TTS Voice": "",
+	"Type": "نوع",
+	"Type Hugging Face Resolve (Download) URL": "مقدار URL دانلود (Resolve) Hugging Face را وارد کنید",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "اوه اوه! مشکلی در اتصال به {{provider}} وجود داشت.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "به روزرسانی و کپی لینک",
+	"Update for the latest features and improvements.": "",
+	"Update password": "به روزرسانی رمزعبور",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "آپلود یک مدل GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "بارگذاری پروندهها",
+	"Upload Pipeline": "",
+	"Upload Progress": "پیشرفت آپلود",
+	"URL Mode": "حالت URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "استفاده از گراواتار",
+	"Use Initials": "استفاده از آبزوده",
+	"use_mlock (Ollama)": "use_mlock (اولاما)",
+	"use_mmap (Ollama)": "use_mmap (اولاما)",
+	"user": "کاربر",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "مجوزهای کاربر",
+	"Users": "کاربران",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "استفاده کنید",
+	"Valid time units:": "واحدهای زمانی معتبر:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "متغیر",
+	"variable to have them replaced with clipboard content.": "متغیر برای جایگزینی آنها با محتوای کلیپ بورد.",
+	"Version": "نسخه",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "هشدار",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "هشدار: اگر شما به روز کنید یا تغییر دهید مدل شما، باید تمام سند ها را مجددا وارد کنید.",
+	"Web": "وب",
+	"Web API": "",
+	"Web Loader Settings": "تنظیمات لودر وب",
+	"Web Search": "جستجوی وب",
+	"Web Search Engine": "موتور جستجوی وب",
+	"Webhook URL": "URL وبهوک",
+	"WebUI Settings": "تنظیمات WebUI",
+	"WebUI will make requests to": "WebUI درخواست\u200cها را ارسال خواهد کرد به",
+	"What’s New in": "موارد جدید در",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "محیط کار",
+	"Write a prompt suggestion (e.g. Who are you?)": "یک پیشنهاد پرامپت بنویسید (مثلاً شما کی هستید؟)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "خلاصه ای در 50 کلمه بنویسید که [موضوع یا کلمه کلیدی] را خلاصه کند.",
+	"Write something...": "",
+	"Yesterday": "دیروز",
+	"You": "شما",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "شما نمیتوانید یک مدل پایه را کلون کنید",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "شما هیچ گفتگوی ذخیره شده ندارید.",
+	"You have shared this chat": "شما این گفتگو را به اشتراک گذاشته اید",
+	"You're a helpful assistant.": "تو یک دستیار سودمند هستی.",
+	"You're now logged in.": "شما اکنون وارد شده\u200cاید.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "یوتیوب",
+	"Youtube Loader Settings": "تنظیمات لودر یوتیوب"
+}
diff --git a/src/lib/i18n/locales/fi-FI/translation.json b/src/lib/i18n/locales/fi-FI/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..0f122a7981350e87fd4443191e15e65be402e099
--- /dev/null
+++ b/src/lib/i18n/locales/fi-FI/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' tai '-1' jottei vanhene.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(esim. `sh webui.sh --api`)",
+	"(latest)": "(uusin)",
+	"{{ models }}": "{{ mallit }}",
+	"{{ owner }}: You cannot delete a base model": "{{ omistaja }}: Perusmallia ei voi poistaa",
+	"{{user}}'s Chats": "{{user}}:n keskustelut",
+	"{{webUIName}} Backend Required": "{{webUIName}} backend vaaditaan",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Tehtävämallia käytetään tehtävien suorittamiseen, kuten otsikoiden luomiseen keskusteluille ja verkkohakukyselyille",
+	"a user": "käyttäjä",
+	"About": "Tietoja",
+	"Account": "Tili",
+	"Account Activation Pending": "",
+	"Accurate information": "Tarkkaa tietoa",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "Lisää",
+	"Add a model id": "Mallitunnuksen lisääminen",
+	"Add a short description about what this model does": "Lisää lyhyt kuvaus siitä, mitä tämä malli tekee",
+	"Add a short title for this prompt": "Lisää lyhyt otsikko tälle kehotteelle",
+	"Add a tag": "Lisää tagi",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Lisää mukautettu kehote",
+	"Add Files": "Lisää tiedostoja",
+	"Add Memory": "Lisää muistia",
+	"Add Model": "Lisää malli",
+	"Add Tag": "",
+	"Add Tags": "Lisää tageja",
+	"Add text content": "",
+	"Add User": "Lisää käyttäjä",
+	"Adjusting these settings will apply changes universally to all users.": "Näiden asetusten säätäminen vaikuttaa kaikkiin käyttäjiin.",
+	"admin": "hallinta",
+	"Admin": "",
+	"Admin Panel": "Hallintapaneeli",
+	"Admin Settings": "Hallinta-asetukset",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "Edistyneet parametrit",
+	"Advanced Params": "Edistyneet parametrit",
+	"All chats": "",
+	"All Documents": "Kaikki asiakirjat",
+	"Allow Chat Deletion": "Salli keskustelujen poisto",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "kirjaimia, numeroita ja väliviivoja",
+	"Already have an account?": "Onko sinulla jo tili?",
+	"an assistant": "avustaja",
+	"and": "ja",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "ja luo uusi jaettu linkki.",
+	"API Base URL": "APIn perus-URL",
+	"API Key": "API-avain",
+	"API Key created.": "API-avain luotu.",
+	"API keys": "API-avaimet",
+	"April": "huhtikuu",
+	"Archive": "Arkisto",
+	"Archive All Chats": "Arkistoi kaikki keskustelut",
+	"Archived Chats": "Arkistoidut keskustelut",
+	"are allowed - Activate this command by typing": "ovat sallittuja - Aktivoi tämä komento kirjoittamalla",
+	"Are you sure?": "Oletko varma?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Liitä tiedosto",
+	"Attention to detail": "Huomio yksityiskohtiin",
+	"Audio": "Ääni",
+	"August": "elokuu",
+	"Auto-playback response": "Soita vastaus automaattisesti",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111-perus-URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111-perus-URL vaaditaan.",
+	"Available list": "",
+	"available!": "saatavilla!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Takaisin",
+	"Bad Response": "Epäkelpo vastaus",
+	"Banners": "Bannerit",
+	"Base Model (From)": "Perusmalli (alkaen)",
+	"Batch Size (num_batch)": "",
+	"before": "ennen",
+	"Being lazy": "Oli laiska",
+	"Brave Search API Key": "Brave Search API -avain",
+	"Bypass SSL verification for Websites": "Ohita SSL-varmennus verkkosivustoille",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "Peruuta",
+	"Capabilities": "Ominaisuuksia",
+	"Change Password": "Vaihda salasana",
+	"Character": "",
+	"Chat": "Keskustelu",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "Keskustelu-pallojen käyttöliittymä",
+	"Chat Controls": "",
+	"Chat direction": "Keskustelun suunta",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Keskustelut",
+	"Check Again": "Tarkista uudelleen",
+	"Check for updates": "Tarkista päivitykset",
+	"Checking for updates...": "Tarkistetaan päivityksiä...",
+	"Choose a model before saving...": "Valitse malli ennen tallentamista...",
+	"Chunk Overlap": "Osien päällekkäisyys",
+	"Chunk Params": "Osien parametrit",
+	"Chunk Size": "Osien koko",
+	"Citation": "Sitaatti",
+	"Clear memory": "",
+	"Click here for help.": "Klikkaa tästä saadaksesi apua.",
+	"Click here to": "Klikkaa tästä",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Klikkaa tästä valitaksesi",
+	"Click here to select a csv file.": "Klikkaa tästä valitaksesi CSV-tiedosto.",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "klikkaa tästä.",
+	"Click on the user role button to change a user's role.": "Klikkaa käyttäjän roolipainiketta vaihtaaksesi käyttäjän roolia.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "Klooni",
+	"Close": "Sulje",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Kokoelma",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI-perus-URL",
+	"ComfyUI Base URL is required.": "ComfyUI-perus-URL vaaditaan.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Komento",
+	"Completions": "",
+	"Concurrent Requests": "Samanaikaiset pyynnöt",
+	"Confirm": "",
+	"Confirm Password": "Vahvista salasana",
+	"Confirm your action": "",
+	"Connections": "Yhteydet",
+	"Contact Admin for WebUI Access": "",
+	"Content": "Sisältö",
+	"Content Extraction": "",
+	"Context Length": "Kontekstin pituus",
+	"Continue Response": "Jatka vastausta",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "Jaettu keskustelulinkki kopioitu leikepöydälle!",
+	"Copied to clipboard": "",
+	"Copy": "Kopioi",
+	"Copy last code block": "Kopioi viimeisin koodilohko",
+	"Copy last response": "Kopioi viimeisin vastaus",
+	"Copy Link": "Kopioi linkki",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Kopioiminen leikepöydälle onnistui!",
+	"Create a model": "Mallin luominen",
+	"Create Account": "Luo tili",
+	"Create Knowledge": "",
+	"Create new key": "Luo uusi avain",
+	"Create new secret key": "Luo uusi salainen avain",
+	"Created at": "Luotu",
+	"Created At": "Luotu",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "Nykyinen malli",
+	"Current Password": "Nykyinen salasana",
+	"Custom": "Mukautettu",
+	"Customize models for a specific purpose": "Mallien mukauttaminen tiettyyn tarkoitukseen",
+	"Dark": "Tumma",
+	"Dashboard": "",
+	"Database": "Tietokanta",
+	"December": "joulukuu",
+	"Default": "Oletus",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Oletus (SentenceTransformers)",
+	"Default Model": "Oletusmalli",
+	"Default model updated": "Oletusmalli päivitetty",
+	"Default Prompt Suggestions": "Oletuskehotteiden ehdotukset",
+	"Default User Role": "Oletuskäyttäjärooli",
+	"Delete": "Poista",
+	"Delete a model": "Poista malli",
+	"Delete All Chats": "Poista kaikki keskustelut",
+	"Delete chat": "Poista keskustelu",
+	"Delete Chat": "Poista keskustelu",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "poista tämä linkki",
+	"Delete tool?": "",
+	"Delete User": "Poista käyttäjä",
+	"Deleted {{deleteModelTag}}": "Poistettu {{deleteModelTag}}",
+	"Deleted {{name}}": "Poistettu {{nimi}}",
+	"Description": "Kuvaus",
+	"Didn't fully follow instructions": "Ei noudattanut ohjeita täysin",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "Tutustu malliin",
+	"Discover a prompt": "Löydä kehote",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Löydä ja lataa mukautettuja kehotteita",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Löydä ja lataa mallien esiasetuksia",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "Näytä käyttäjänimi keskustelussa",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Asiakirja",
+	"Documentation": "",
+	"Documents": "Asiakirjat",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "ei tee ulkoisia yhteyksiä, ja tietosi pysyvät turvallisesti paikallisesti isännöidyllä palvelimellasi.",
+	"Don't have an account?": "Eikö sinulla ole tiliä?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "En pidä tyylistä",
+	"Done": "",
+	"Download": "Lataa",
+	"Download canceled": "Lataus peruutettu",
+	"Download Database": "Lataa tietokanta",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Pudota tiedostoja tähän lisätäksesi ne keskusteluun",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "esim. '30s', '10m'. Kelpoiset aikayksiköt ovat 's', 'm', 'h'.",
+	"Edit": "Muokkaa",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "Muokkaa käyttäjää",
+	"ElevenLabs": "",
+	"Email": "Sähköposti",
+	"Embedding Batch Size": "",
+	"Embedding Model": "Upotusmalli",
+	"Embedding Model Engine": "Upotusmallin moottori",
+	"Embedding model set to \"{{embedding_model}}\"": "\"{{embedding_model}}\" valittu upotusmalliksi",
+	"Enable Community Sharing": "Ota yhteisön jakaminen käyttöön",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Salli uudet rekisteröitymiset",
+	"Enable Web Search": "Ota verkkohaku käyttöön",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Varmista, että CSV-tiedostossasi on 4 saraketta seuraavassa järjestyksessä: Nimi, Sähköposti, Salasana, Rooli.",
+	"Enter {{role}} message here": "Kirjoita {{role}} viesti tähän",
+	"Enter a detail about yourself for your LLMs to recall": "Kirjoita tieto itseestäsi LLM:ien muistamiseksi",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Anna Brave Search API -avain",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Syötä osien päällekkäisyys",
+	"Enter Chunk Size": "Syötä osien koko",
+	"Enter description": "",
+	"Enter Github Raw URL": "Kirjoita Github Raw URL-osoite",
+	"Enter Google PSE API Key": "Anna Google PSE API -avain",
+	"Enter Google PSE Engine Id": "Anna Google PSE -moottorin tunnus",
+	"Enter Image Size (e.g. 512x512)": "Syötä kuvan koko (esim. 512x512)",
+	"Enter language codes": "Syötä kielikoodit",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Syötä mallitagi (esim. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Syötä askelien määrä (esim. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Syötä pisteet",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Kirjoita Searxng-kyselyn URL-osoite",
+	"Enter Serper API Key": "Anna Serper API -avain",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "Anna Serpstack API -avain",
+	"Enter stop sequence": "Syötä lopetussekvenssi",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Syötä Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Syötä URL (esim. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Syötä URL (esim. http://localhost:11434)",
+	"Enter Your Email": "Syötä sähköpostiosoitteesi",
+	"Enter Your Full Name": "Syötä koko nimesi",
+	"Enter your message": "",
+	"Enter Your Password": "Syötä salasanasi",
+	"Enter Your Role": "Syötä roolisi",
+	"Error": "Virhe",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Kokeellinen",
+	"Export": "Vienti",
+	"Export All Chats (All Users)": "Vie kaikki keskustelut (kaikki käyttäjät)",
+	"Export chat (.json)": "",
+	"Export Chats": "Vie keskustelut",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "Vie malleja",
+	"Export Prompts": "Vie kehotteet",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "API-avaimen luonti epäonnistui.",
+	"Failed to read clipboard contents": "Leikepöydän sisällön lukeminen epäonnistui",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "helmikuu",
+	"Feedback History": "",
+	"Feel free to add specific details": "Voit lisätä tarkempia tietoja",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Tiedostotila",
+	"File not found.": "Tiedostoa ei löytynyt.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Sormenjäljen väärentäminen havaittu: Ei voi käyttää alkukirjaimia avatarina. Käytetään oletusprofiilikuvaa.",
+	"Fluidly stream large external response chunks": "Virtaa suuria ulkoisia vastausosia joustavasti",
+	"Focus chat input": "Fokusoi syöttökenttään",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Noudatti ohjeita täydellisesti",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Taajuussakko",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Yleinen",
+	"General Settings": "Yleisasetukset",
+	"Generate Image": "",
+	"Generating search query": "Hakukyselyn luominen",
+	"Generation Info": "Generointitiedot",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "Hyvä vastaus",
+	"Google PSE API Key": "Google PSE API -avain",
+	"Google PSE Engine Id": "Google PSE -moduulin tunnus",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "ei ole keskusteluja.",
+	"Hello, {{name}}": "Terve, {{name}}",
+	"Help": "Apua",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Piilota",
+	"Hide Model": "",
+	"How can I help you today?": "Kuinka voin auttaa tänään?",
+	"Hybrid Search": "Hybridihaku",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Kuvagenerointi (kokeellinen)",
+	"Image Generation Engine": "Kuvagenerointimoottori",
+	"Image Settings": "Kuva-asetukset",
+	"Images": "Kuvat",
+	"Import Chats": "Tuo keskustelut",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "Mallien tuominen",
+	"Import Prompts": "Tuo kehotteita",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Sisällytä `--api`-parametri suorittaessasi stable-diffusion-webui",
+	"Info": "Info",
+	"Input commands": "Syötä komennot",
+	"Install from Github URL": "Asenna Githubin URL-osoitteesta",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "Käyttöliittymä",
+	"Invalid file format.": "",
+	"Invalid Tag": "Virheellinen tagi",
+	"January": "tammikuu",
+	"join our Discord for help.": "liity Discordiimme saadaksesi apua.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON-esikatselu",
+	"July": "heinäkuu",
+	"June": "kesäkuu",
+	"JWT Expiration": "JWT:n vanheneminen",
+	"JWT Token": "JWT-token",
+	"Keep Alive": "Pysy aktiivisena",
+	"Keyboard shortcuts": "Pikanäppäimet",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Kieli",
+	"large language models, locally.": "",
+	"Last Active": "Viimeksi aktiivinen",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Vaalea",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "Kielimallit voivat tehdä virheitä. Varmista tärkeät tiedot.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Tehnyt OpenWebUI-yhteisö",
+	"Make sure to enclose them with": "Varmista, että suljet ne",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "Hallitse malleja",
+	"Manage Ollama Models": "Hallitse Ollama-malleja",
+	"Manage Pipelines": "Hallitse putkia",
+	"March": "maaliskuu",
+	"Max Tokens (num_predict)": "Tokenien enimmäismäärä (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Enintään 3 mallia voidaan ladata samanaikaisesti. Yritä myöhemmin uudelleen.",
+	"May": "toukokuu",
+	"Memories accessible by LLMs will be shown here.": "Muistitiedostot, joita LLM-ohjelmat käyttävät, näkyvät tässä.",
+	"Memory": "Muisti",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Linkin luomisen jälkeen lähettämiäsi viestejä ei jaeta. Käyttäjät, joilla on URL-osoite, voivat tarkastella jaettua keskustelua.",
+	"Min P": "",
+	"Minimum Score": "Vähimmäispisteet",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "DD MMMM YYYY",
+	"MMMM DD, YYYY HH:mm": "DD MMMM YYYY, HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Malli '{{modelName}}' ladattiin onnistuneesti.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Malli '{{modelTag}}' on jo jonossa ladattavaksi.",
+	"Model {{modelId}} not found": "Mallia {{modelId}} ei löytynyt",
+	"Model {{modelName}} is not vision capable": "Malli {{modelName}} ei kykene näkökykyyn",
+	"Model {{name}} is now {{status}}": "Malli {{name}} on nyt {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Mallin tiedostojärjestelmäpolku havaittu. Mallin lyhytnimi vaaditaan päivitykseen, ei voi jatkaa.",
+	"Model ID": "Mallin tunnus",
+	"Model Name": "",
+	"Model not selected": "Mallia ei valittu",
+	"Model Params": "Mallin parametrit",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Mallin sallimislista",
+	"Model(s) Whitelisted": "Malli(t) sallittu",
+	"Modelfile Content": "Mallitiedoston sisältö",
+	"Models": "Mallit",
+	"more": "",
+	"More": "Lisää",
+	"Move to Top": "",
+	"Name": "Nimi",
+	"Name your model": "Mallin nimeäminen",
+	"New Chat": "Uusi keskustelu",
+	"New folder": "",
+	"New Password": "Uusi salasana",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Ei tuloksia",
+	"No search query generated": "Hakukyselyä ei luotu",
+	"No source available": "Ei lähdettä saatavilla",
+	"No valves to update": "",
+	"None": "Ei lainkaan",
+	"Not factually correct": "Ei faktisesti oikein",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Huom: Jos asetat vähimmäispisteet, haku palauttaa vain asiakirjat, joiden pisteet ovat suurempia tai yhtä suuria kuin vähimmäispistemäärä.",
+	"Notes": "",
+	"Notifications": "Ilmoitukset",
+	"November": "marraskuu",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "",
+	"October": "lokakuu",
+	"Off": "Pois",
+	"Okay, Let's Go!": "Eikun menoksi!",
+	"OLED Dark": "OLED-tumma",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API poistettu käytöstä",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Ollama-versio",
+	"On": "Päällä",
+	"Only": "Vain",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Vain kirjaimet, numerot ja väliviivat ovat sallittuja komentosarjassa.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hups! Näyttää siltä, että URL on virheellinen. Tarkista se ja yritä uudelleen.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hupsista! Käytät ei-tuettua menetelmää. WebUI pitää palvella backendista.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Avaa uusi keskustelu",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API -asetukset",
+	"OpenAI API Key is required.": "OpenAI API -avain vaaditaan.",
+	"OpenAI URL/Key required.": "OpenAI URL/ -avain vaaditaan.",
+	"or": "tai",
+	"Other": "Muu",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Salasana",
+	"PDF document (.pdf)": "PDF-tiedosto (.pdf)",
+	"PDF Extract Images (OCR)": "PDF-tiedoston kuvien erottelu (OCR)",
+	"pending": "odottaa",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "Mikrofonin käyttöoikeus evätty: {{error}}",
+	"Personalization": "Henkilökohtaisuus",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "Putkistot",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "Putkistot Venttiilit",
+	"Plain text (.txt)": "Pelkkä teksti (.txt)",
+	"Playground": "Leikkipaikka",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Positiivinen asenne",
+	"Previous 30 days": "Edelliset 30 päivää",
+	"Previous 7 days": "Edelliset 7 päivää",
+	"Profile Image": "Profiilikuva",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Kehote (esim. Kerro hauska fakta Turusta)",
+	"Prompt Content": "Kehotteen sisältö",
+	"Prompt suggestions": "Kehotteen ehdotukset",
+	"Prompts": "Kehotteet",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Lataa \"{{searchValue}}\" Ollama.comista",
+	"Pull a model from Ollama.com": "Lataa malli Ollama.comista",
+	"Query Params": "Kyselyparametrit",
+	"RAG Template": "RAG-malline",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Lue ääneen",
+	"Record voice": "Nauhoita ääni",
+	"Redirecting you to OpenWebUI Community": "Ohjataan sinut OpenWebUI-yhteisöön",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "Kieltäytyi, vaikka ei olisi pitänyt",
+	"Regenerate": "Uudelleenluo",
+	"Release Notes": "Julkaisutiedot",
+	"Relevance": "",
+	"Remove": "Poista",
+	"Remove Model": "Poista malli",
+	"Rename": "Nimeä uudelleen",
+	"Repeat Last N": "Viimeinen N -toisto",
+	"Request Mode": "Pyyntötila",
+	"Reranking Model": "Uudelleenpisteytysmalli",
+	"Reranking model disabled": "Uudelleenpisteytysmalli poistettu käytöstä",
+	"Reranking model set to \"{{reranking_model}}\"": "\"{{reranking_model}}\" valittu uudelleenpisteytysmalliksi",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Vastauksen automaattikopiointi leikepöydälle",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rooli",
+	"Rosé Pine": "Rosee-mänty",
+	"Rosé Pine Dawn": "Aamuinen Rosee-mänty",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "Tallenna",
+	"Save & Create": "Tallenna ja luo",
+	"Save & Update": "Tallenna ja päivitä",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Keskustelulokien tallentaminen suoraan selaimen tallennustilaan ei ole enää tuettua. Lataa ja poista keskustelulokit napsauttamalla alla olevaa painiketta. Älä huoli, voit helposti tuoda keskustelulokit takaisin backendiin",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Haku",
+	"Search a model": "Hae mallia",
+	"Search Chats": "Etsi chatteja",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "Hae malleja",
+	"Search Prompts": "Hae kehotteita",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "Hakutulosten määrä",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Haettu {{count}} sites_one",
+	"Searched {{count}} sites_other": "Haku {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng-kyselyn URL-osoite",
+	"See readme.md for instructions": "Katso lisää ohjeita readme.md:stä",
+	"See what's new": "Katso, mitä uutta",
+	"Seed": "Siemen",
+	"Select a base model": "Valitse perusmalli",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "Valitse malli",
+	"Select a pipeline": "Valitse putki",
+	"Select a pipeline url": "Valitse putken URL-osoite",
+	"Select a tool": "",
+	"Select an Ollama instance": "Valitse Ollama-instanssi",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Valitse malli",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "Valitut mallit eivät tue kuvasyötteitä",
+	"Semantic distance to query": "",
+	"Send": "Lähetä",
+	"Send a Message": "Lähetä viesti",
+	"Send message": "Lähetä viesti",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "syyskuu",
+	"Serper API Key": "Serper API -avain",
+	"Serply API Key": "",
+	"Serpstack API Key": "Serpstack API -avain",
+	"Server connection verified": "Palvelinyhteys varmennettu",
+	"Set as default": "Aseta oletukseksi",
+	"Set CFG Scale": "",
+	"Set Default Model": "Aseta oletusmalli",
+	"Set embedding model (e.g. {{model}})": "Aseta upotusmalli (esim. {{model}})",
+	"Set Image Size": "Aseta kuvan koko",
+	"Set reranking model (e.g. {{model}})": "Aseta uudelleenpisteytysmalli (esim. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Aseta askelmäärä",
+	"Set Task Model": "Aseta tehtävämalli",
+	"Set Voice": "Aseta puheääni",
+	"Set whisper model": "",
+	"Settings": "Asetukset",
+	"Settings saved successfully!": "Asetukset tallennettu onnistuneesti!",
+	"Share": "Jaa",
+	"Share Chat": "Jaa keskustelu",
+	"Share to OpenWebUI Community": "Jaa OpenWebUI-yhteisöön",
+	"short-summary": "lyhyt-yhteenveto",
+	"Show": "Näytä",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "Näytä pikanäppäimet",
+	"Show your support!": "",
+	"Showcased creativity": "Näytti luovuutta",
+	"Sign in": "Kirjaudu sisään",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Kirjaudu ulos",
+	"Sign up": "Rekisteröidy",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Lähde",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Puheentunnistusvirhe: {{error}}",
+	"Speech-to-Text Engine": "Puheentunnistusmoottori",
+	"Stop": "",
+	"Stop Sequence": "Lopetussekvenssi",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "Puheentunnistusasetukset",
+	"Subtitle (e.g. about the Roman Empire)": "Alaotsikko (esim. Rooman valtakunnasta)",
+	"Success": "Onnistui",
+	"Successfully updated.": "Päivitetty onnistuneesti.",
+	"Suggested": "Suositeltu",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "Järjestelmä",
+	"System Instructions": "",
+	"System Prompt": "Järjestelmäkehote",
+	"Tags": "Tagit",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "Kerro lisää:",
+	"Temperature": "Lämpötila",
+	"Template": "Malline",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Puhemoottori",
+	"Tfs Z": "TFS Z",
+	"Thanks for your feedback!": "Kiitos palautteestasi!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Pisteytyksen tulee olla arvo välillä 0.0 (0%) ja 1.0 (100%).",
+	"Theme": "Teema",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Tämä varmistaa, että arvokkaat keskustelusi tallennetaan turvallisesti backend-tietokantaasi. Kiitos!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Perusteellinen selitys",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Vinkki: Päivitä useita muuttujapaikkoja peräkkäin painamalla tabulaattoria keskustelusyötteessä jokaisen korvauksen jälkeen.",
+	"Title": "Otsikko",
+	"Title (e.g. Tell me a fun fact)": "Otsikko (esim. Kerro hauska fakta)",
+	"Title Auto-Generation": "Otsikon automaattinen luonti",
+	"Title cannot be an empty string.": "Otsikko ei voi olla tyhjä.",
+	"Title Generation Prompt": "Otsikon luontikehote",
+	"To access the available model names for downloading,": "Päästäksesi käsiksi ladattavissa oleviin mallinimiin,",
+	"To access the GGUF models available for downloading,": "Päästäksesi käsiksi ladattavissa oleviin GGUF-malleihin,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "keskustelusyötteeseen.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "Tänään",
+	"Toggle settings": "Kytke asetukset",
+	"Toggle sidebar": "Kytke sivupalkki",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Ongelmia Ollama-yhteydessä?",
+	"TTS Model": "",
+	"TTS Settings": "Puheentuottamisasetukset",
+	"TTS Voice": "",
+	"Type": "Tyyppi",
+	"Type Hugging Face Resolve (Download) URL": "Kirjoita Hugging Face -resolve-osoite",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Voi ei! Yhteysongelma {{provider}}:n kanssa.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "Päivitä ja kopioi linkki",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Päivitä salasana",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Lataa GGUF-malli",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Lataa tiedostoja",
+	"Upload Pipeline": "",
+	"Upload Progress": "Latauksen eteneminen",
+	"URL Mode": "URL-tila",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Käytä Gravataria",
+	"Use Initials": "Käytä alkukirjaimia",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "käyttäjä",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "Käyttäjäoikeudet",
+	"Users": "Käyttäjät",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Käytä",
+	"Valid time units:": "Kelvolliset aikayksiköt:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "muuttuja",
+	"variable to have them replaced with clipboard content.": "muuttuja korvataan leikepöydän sisällöllä.",
+	"Version": "Versio",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "Varoitus",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Varoitus: Jos päivität tai vaihdat upotusmallia, sinun on tuotava kaikki asiakirjat uudelleen.",
+	"Web": "Web",
+	"Web API": "",
+	"Web Loader Settings": "Web Loader asetukset",
+	"Web Search": "Web-haku",
+	"Web Search Engine": "Web-hakukone",
+	"Webhook URL": "Webhook-URL",
+	"WebUI Settings": "WebUI-asetukset",
+	"WebUI will make requests to": "WebUI tekee pyyntöjä",
+	"What’s New in": "Mitä uutta",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "Työtilat",
+	"Write a prompt suggestion (e.g. Who are you?)": "Kirjoita ehdotettu kehote (esim. Kuka olet?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Kirjoita 50 sanan yhteenveto, joka tiivistää [aihe tai avainsana].",
+	"Write something...": "",
+	"Yesterday": "Eilen",
+	"You": "Sinä",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "Perusmallia ei voi kloonata",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Sinulla ei ole arkistoituja keskusteluja.",
+	"You have shared this chat": "Olet jakanut tämän keskustelun",
+	"You're a helpful assistant.": "Olet avulias apulainen.",
+	"You're now logged in.": "Olet nyt kirjautunut sisään.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube Loader-asetukset"
+}
diff --git a/src/lib/i18n/locales/fr-CA/translation.json b/src/lib/i18n/locales/fr-CA/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..aa312ce991e6aefc34ffa8f7396a6bed543112b0
--- /dev/null
+++ b/src/lib/i18n/locales/fr-CA/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": " 's', 'm', 'h', 'd', 'w' ou '-1' pour une durée illimitée.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(par ex. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(par exemple `sh webui.sh --api`)",
+	"(latest)": "(dernier)",
+	"{{ models }}": "{{ modèles }}",
+	"{{ owner }}: You cannot delete a base model": "{{ propriétaire }} : Vous ne pouvez pas supprimer un modèle de base.",
+	"{{user}}'s Chats": "Discussions de {{user}}",
+	"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un modèle de tâche est utilisé lors de l’exécution de tâches telles que la génération de titres pour les conversations et les requêtes de recherche sur le web.",
+	"a user": "un utilisateur",
+	"About": "À propos",
+	"Account": "Compte",
+	"Account Activation Pending": "Activation du compte en attente",
+	"Accurate information": "Information exacte",
+	"Actions": "",
+	"Active Users": "Utilisateurs actifs",
+	"Add": "Ajouter",
+	"Add a model id": "Ajouter un identifiant de modèle",
+	"Add a short description about what this model does": "Ajoutez une brève description de ce que fait ce modèle.",
+	"Add a short title for this prompt": "Ajoutez un bref titre pour cette prompt.",
+	"Add a tag": "Ajouter une balise",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Ajouter une prompt personnalisée",
+	"Add Files": "Ajouter des fichiers",
+	"Add Memory": "Ajouter de la mémoire",
+	"Add Model": "Ajouter un modèle",
+	"Add Tag": "",
+	"Add Tags": "Ajouter des balises",
+	"Add text content": "",
+	"Add User": "Ajouter un Utilisateur",
+	"Adjusting these settings will apply changes universally to all users.": "L'ajustement de ces paramètres appliquera universellement les changements à tous les utilisateurs.",
+	"admin": "administrateur",
+	"Admin": "Administrateur",
+	"Admin Panel": "Tableau de bord administrateur",
+	"Admin Settings": "Paramètres d'administration",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Les administrateurs ont accès à tous les outils en tout temps ; les utilisateurs ont besoin d'outils affectés par modèle dans l'espace de travail.",
+	"Advanced Parameters": "Paramètres avancés",
+	"Advanced Params": "Paramètres avancés",
+	"All chats": "",
+	"All Documents": "Tous les documents",
+	"Allow Chat Deletion": "Autoriser la suppression de l'historique de chat",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Autoriser les voix non locales",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "Autoriser l'emplacement de l'utilisateur",
+	"Allow Voice Interruption in Call": "Autoriser l'interruption vocale pendant un appel",
+	"alphanumeric characters and hyphens": "caractères alphanumériques et tirets",
+	"Already have an account?": "Avez-vous déjà un compte ?",
+	"an assistant": "un assistant",
+	"and": "et",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "et créer un nouveau lien partagé.",
+	"API Base URL": "URL de base de l'API",
+	"API Key": "Clé d'API",
+	"API Key created.": "Clé d'API générée.",
+	"API keys": "Clés d'API",
+	"April": "Avril",
+	"Archive": "Archivage",
+	"Archive All Chats": "Archiver toutes les conversations",
+	"Archived Chats": "Conversations archivées",
+	"are allowed - Activate this command by typing": "sont autorisés - Activer cette commande en tapant",
+	"Are you sure?": "Êtes-vous certain ?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Joindre un document",
+	"Attention to detail": "Attention aux détails",
+	"Audio": "Audio",
+	"August": "Août",
+	"Auto-playback response": "Réponse de lecture automatique",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Chaîne d'authentification de l'API",
+	"AUTOMATIC1111 Base URL": "URL de base AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "L'URL de base {AUTOMATIC1111} est requise.",
+	"Available list": "",
+	"available!": "disponible !",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Retour en arrière",
+	"Bad Response": "Mauvaise réponse",
+	"Banners": "Banniers",
+	"Base Model (From)": "Modèle de base (à partir de)",
+	"Batch Size (num_batch)": "Taille du lot (num_batch)",
+	"before": "avant",
+	"Being lazy": "Être fainéant",
+	"Brave Search API Key": "Clé API Brave Search",
+	"Bypass SSL verification for Websites": "Bypasser la vérification SSL pour les sites web",
+	"Call": "Appeler",
+	"Call feature is not supported when using Web STT engine": "La fonction d'appel n'est pas prise en charge lors de l'utilisation du moteur Web STT",
+	"Camera": "Appareil photo",
+	"Cancel": "Annuler",
+	"Capabilities": "Capacités",
+	"Change Password": "Changer le mot de passe",
+	"Character": "",
+	"Chat": "Chat",
+	"Chat Background Image": "Image d'arrière-plan de la fenêtre de chat",
+	"Chat Bubble UI": "Bulles de discussion",
+	"Chat Controls": "",
+	"Chat direction": "Direction du chat",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Conversations",
+	"Check Again": "Vérifiez à nouveau.",
+	"Check for updates": "Vérifier les mises à jour disponibles",
+	"Checking for updates...": "Recherche de mises à jour...",
+	"Choose a model before saving...": "Choisissez un modèle avant de sauvegarder...",
+	"Chunk Overlap": "Chevauchement de blocs",
+	"Chunk Params": "Paramètres d'encombrement",
+	"Chunk Size": "Taille de bloc",
+	"Citation": "Citation",
+	"Clear memory": "Libérer la mémoire",
+	"Click here for help.": "Cliquez ici pour obtenir de l'aide.",
+	"Click here to": "Cliquez ici pour",
+	"Click here to download user import template file.": "Cliquez ici pour télécharger le fichier modèle d'importation utilisateur.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Cliquez ici pour sélectionner",
+	"Click here to select a csv file.": "Cliquez ici pour sélectionner un fichier CSV.",
+	"Click here to select a py file.": "Cliquez ici pour sélectionner un fichier .py.",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "cliquez ici.",
+	"Click on the user role button to change a user's role.": "Cliquez sur le bouton de rôle d'utilisateur pour modifier le rôle d'un utilisateur.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "L'autorisation d'écriture du presse-papier a été refusée. Veuillez vérifier les paramètres de votre navigateur pour accorder l'accès nécessaire.",
+	"Clone": "Copie conforme",
+	"Close": "Fermer",
+	"Code execution": "",
+	"Code formatted successfully": "Le code a été formaté avec succès",
+	"Collection": "Collection",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL de base ComfyUI",
+	"ComfyUI Base URL is required.": "L'URL de base ComfyUI est requise.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Commande",
+	"Completions": "",
+	"Concurrent Requests": "Demandes concurrentes",
+	"Confirm": "Confirmer",
+	"Confirm Password": "Confirmer le mot de passe",
+	"Confirm your action": "Confirmez votre action",
+	"Connections": "Connexions",
+	"Contact Admin for WebUI Access": "Contacter l'administrateur pour l'accès à l'interface Web",
+	"Content": "Contenu",
+	"Content Extraction": "",
+	"Context Length": "Longueur du contexte",
+	"Continue Response": "Continuer la réponse",
+	"Continue with {{provider}}": "Continuer avec {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "Contrôle comment le texte des messages est divisé pour les demandes de TTS. 'Ponctuation' divise en phrases, 'paragraphes' divise en paragraphes et 'aucun' garde le message comme une seule chaîne.",
+	"Controls": "Contrôles",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "URL du chat copiée dans le presse-papiers\u00a0!",
+	"Copied to clipboard": "",
+	"Copy": "Copie",
+	"Copy last code block": "Copier le dernier bloc de code",
+	"Copy last response": "Copier la dernière réponse",
+	"Copy Link": "Copier le lien",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "La copie dans le presse-papiers a réussi !",
+	"Create a model": "Créer un modèle",
+	"Create Account": "Créer un compte",
+	"Create Knowledge": "",
+	"Create new key": "Créer une nouvelle clé principale",
+	"Create new secret key": "Créer une nouvelle clé secrète",
+	"Created at": "Créé à",
+	"Created At": "Créé le",
+	"Created by": "Créé par",
+	"CSV Import": "Import CSV",
+	"Current Model": "Modèle actuel amélioré",
+	"Current Password": "Mot de passe actuel",
+	"Custom": "Sur mesure",
+	"Customize models for a specific purpose": "Personnaliser les modèles pour une fonction spécifique",
+	"Dark": "Obscur",
+	"Dashboard": "Tableau de bord",
+	"Database": "Base de données",
+	"December": "Décembre",
+	"Default": "Par défaut",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Par défaut (Sentence Transformers)",
+	"Default Model": "Modèle standard",
+	"Default model updated": "Modèle par défaut mis à jour",
+	"Default Prompt Suggestions": "Suggestions de prompts par défaut",
+	"Default User Role": "Rôle utilisateur par défaut",
+	"Delete": "Supprimer",
+	"Delete a model": "Supprimer un modèle",
+	"Delete All Chats": "Supprimer toutes les conversations",
+	"Delete chat": "Supprimer la conversation",
+	"Delete Chat": "Supprimer la Conversation",
+	"Delete chat?": "Supprimer la conversation ?",
+	"Delete folder?": "",
+	"Delete function?": "Supprimer la fonction ?",
+	"Delete prompt?": "Supprimer la prompt ?",
+	"delete this link": "supprimer ce lien",
+	"Delete tool?": "Effacer l'outil ?",
+	"Delete User": "Supprimer le compte d'utilisateur",
+	"Deleted {{deleteModelTag}}": "Supprimé {{deleteModelTag}}",
+	"Deleted {{name}}": "Supprimé {{name}}",
+	"Description": "Description",
+	"Didn't fully follow instructions": "N'a pas entièrement respecté les instructions",
+	"Disabled": "",
+	"Discover a function": "Découvrez une fonction",
+	"Discover a model": "Découvrir un modèle",
+	"Discover a prompt": "Découvrir une suggestion",
+	"Discover a tool": "Découvrez un outil",
+	"Discover, download, and explore custom functions": "Découvrez, téléchargez et explorez des fonctions personnalisées",
+	"Discover, download, and explore custom prompts": "Découvrez, téléchargez et explorez des prompts personnalisés",
+	"Discover, download, and explore custom tools": "Découvrez, téléchargez et explorez des outils personnalisés",
+	"Discover, download, and explore model presets": "Découvrir, télécharger et explorer des préréglages de modèles",
+	"Dismissible": "Fermeture",
+	"Display Emoji in Call": "Afficher les emojis pendant l'appel",
+	"Display the username instead of You in the Chat": "Afficher le nom d'utilisateur à la place de \"Vous\" dans le Chat",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Document",
+	"Documentation": "Documentation",
+	"Documents": "Documents",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "ne fait aucune connexion externe et garde vos données en sécurité sur votre serveur local.",
+	"Don't have an account?": "Vous n'avez pas de compte ?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "N'apprécie pas le style",
+	"Done": "Terminé",
+	"Download": "Télécharger",
+	"Download canceled": "Téléchargement annulé",
+	"Download Database": "Télécharger la base de données",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Déposez des fichiers ici pour les ajouter à la conversation",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "par ex. '30s', '10 min'. Les unités de temps valides sont 's', 'm', 'h'.",
+	"Edit": "Modifier",
+	"Edit Arena Model": "",
+	"Edit Memory": "Modifier la mémoire",
+	"Edit User": "Modifier l'utilisateur",
+	"ElevenLabs": "",
+	"Email": "E-mail",
+	"Embedding Batch Size": "Taille du lot d'encodage",
+	"Embedding Model": "Modèle d'embedding",
+	"Embedding Model Engine": "Moteur de modèle d'encodage",
+	"Embedding model set to \"{{embedding_model}}\"": "Modèle d'encodage défini sur « {{embedding_model}} »",
+	"Enable Community Sharing": "Activer le partage communautaire",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Activer les nouvelles inscriptions",
+	"Enable Web Search": "Activer la recherche sur le Web",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "Moteur",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Vérifiez que votre fichier CSV comprenne les 4 colonnes dans cet ordre : Name, Email, Password, Role.",
+	"Enter {{role}} message here": "Entrez le message {{role}} ici",
+	"Enter a detail about yourself for your LLMs to recall": "Saisissez un détail sur vous-même que vos LLMs pourront se rappeler",
+	"Enter api auth string (e.g. username:password)": "Entrez la chaîne d'authentification de l'API (par ex. nom d'utilisateur:mot de passe)",
+	"Enter Brave Search API Key": "Entrez la clé API Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Entrez le chevauchement de chunk",
+	"Enter Chunk Size": "Entrez la taille de bloc",
+	"Enter description": "",
+	"Enter Github Raw URL": "Entrez l'URL brute de GitHub",
+	"Enter Google PSE API Key": "Entrez la clé API Google PSE",
+	"Enter Google PSE Engine Id": "Entrez l'identifiant du moteur Google PSE",
+	"Enter Image Size (e.g. 512x512)": "Entrez la taille de l'image (par ex. 512x512)",
+	"Enter language codes": "Entrez les codes de langue",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Entrez l'étiquette du modèle (par ex. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Entrez le nombre de pas (par ex. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Entrez votre score",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Entrez l'URL de la requête Searxng",
+	"Enter Serper API Key": "Entrez la clé API Serper",
+	"Enter Serply API Key": "Entrez la clé API Serply",
+	"Enter Serpstack API Key": "Entrez la clé API Serpstack",
+	"Enter stop sequence": "Entrez la séquence d'arrêt",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "Entrez la clé API Tavily",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Entrez les Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Entrez l'URL (par ex. {http://127.0.0.1:7860/})",
+	"Enter URL (e.g. http://localhost:11434)": "Entrez l'URL (par ex. http://localhost:11434)",
+	"Enter Your Email": "Entrez votre adresse e-mail",
+	"Enter Your Full Name": "Entrez votre nom complet",
+	"Enter your message": "",
+	"Enter Your Password": "Entrez votre mot de passe",
+	"Enter Your Role": "Entrez votre rôle",
+	"Error": "Erreur",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Expérimental",
+	"Export": "Exportation",
+	"Export All Chats (All Users)": "Exporter toutes les conversations (tous les utilisateurs)",
+	"Export chat (.json)": "Exporter la discussion (.json)",
+	"Export Chats": "Exporter les conversations",
+	"Export Config to JSON File": "",
+	"Export Functions": "Exportez les Fonctions",
+	"Export LiteLLM config.yaml": "Exportez le fichier LiteLLM config.yaml",
+	"Export Models": "Exporter les modèles",
+	"Export Prompts": "Exporter les Prompts",
+	"Export Tools": "Outils d'exportation",
+	"External Models": "Modèles externes",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Échec de la création de la clé API.",
+	"Failed to read clipboard contents": "Échec de la lecture du contenu du presse-papiers",
+	"Failed to update settings": "Échec de la mise à jour des paramètres",
+	"Failed to upload file.": "",
+	"February": "Février",
+	"Feedback History": "",
+	"Feel free to add specific details": "N'hésitez pas à ajouter des détails spécifiques",
+	"File": "Fichier",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Mode fichier",
+	"File not found.": "Fichier introuvable.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "Le filtre est maintenant désactivé globalement",
+	"Filter is now globally enabled": "Le filtre est désormais activé globalement",
+	"Filters": "Filtres",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Spoofing détecté : impossible d'utiliser les initiales comme avatar. Retour à l'image de profil par défaut.",
+	"Fluidly stream large external response chunks": "Diffuser de manière fluide de larges portions de réponses externes",
+	"Focus chat input": "Se concentrer sur le chat en entrée",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "A parfaitement suivi les instructions",
+	"Form": "Formulaire",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Pénalité de fréquence",
+	"Function": "",
+	"Function created successfully": "La fonction a été créée avec succès",
+	"Function deleted successfully": "Fonction supprimée avec succès",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "La fonction a été mise à jour avec succès",
+	"Functions": "Fonctions",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "Fonctions importées avec succès",
+	"General": "Général",
+	"General Settings": "Paramètres Généraux",
+	"Generate Image": "Générer une image",
+	"Generating search query": "Génération d'une requête de recherche",
+	"Generation Info": "Informations sur la génération",
+	"Get up and running with": "",
+	"Global": "Mondial",
+	"Good Response": "Bonne réponse",
+	"Google PSE API Key": "Clé API Google PSE",
+	"Google PSE Engine Id": "ID du moteur de recherche personnalisé de Google",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "n'a aucune conversation.",
+	"Hello, {{name}}": "Bonjour, {{name}}.",
+	"Help": "Aide",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Cacher",
+	"Hide Model": "Masquer le modèle",
+	"How can I help you today?": "Comment puis-je vous être utile aujourd'hui ?",
+	"Hybrid Search": "Recherche hybride",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Génération d'images (expérimental)",
+	"Image Generation Engine": "Moteur de génération d'images",
+	"Image Settings": "Paramètres de l'image",
+	"Images": "Images",
+	"Import Chats": "Importer les discussions",
+	"Import Config from JSON File": "",
+	"Import Functions": "Import de fonctions",
+	"Import Models": "Importer des modèles",
+	"Import Prompts": "Importer des Enseignes",
+	"Import Tools": "Outils d'importation",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Inclure le drapeau `--api-auth` lors de l'exécution de stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Inclure le drapeau `--api` lorsque vous exécutez stable-diffusion-webui",
+	"Info": "Info",
+	"Input commands": "Entrez les commandes",
+	"Install from Github URL": "Installer depuis l'URL GitHub",
+	"Instant Auto-Send After Voice Transcription": "Envoi automatique instantané après transcription vocale",
+	"Interface": "Interface utilisateur",
+	"Invalid file format.": "",
+	"Invalid Tag": "Étiquette non valide",
+	"January": "Janvier",
+	"join our Discord for help.": "Rejoignez notre Discord pour obtenir de l'aide.",
+	"JSON": "JSON",
+	"JSON Preview": "Aperçu JSON",
+	"July": "Juillet",
+	"June": "Juin",
+	"JWT Expiration": "Expiration du jeton JWT",
+	"JWT Token": "Jeton JWT",
+	"Keep Alive": "Rester connecté",
+	"Keyboard shortcuts": "Raccourcis clavier",
+	"Knowledge": "Connaissance",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Langue",
+	"large language models, locally.": "",
+	"Last Active": "Dernière activité",
+	"Last Modified": "Dernière modification",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Lumineux",
+	"Listening...": "En train d'écouter...",
+	"LLMs can make mistakes. Verify important information.": "Les LLM peuvent faire des erreurs. Vérifiez les informations importantes.",
+	"Local Models": "Modèles locaux",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Réalisé par la communauté OpenWebUI",
+	"Make sure to enclose them with": "Assurez-vous de les inclure dans",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Gérer",
+	"Manage Arena Models": "",
+	"Manage Models": "Gérer les Modèles",
+	"Manage Ollama Models": "Gérer les modèles Ollama",
+	"Manage Pipelines": "Gérer les pipelines",
+	"March": "Mars",
+	"Max Tokens (num_predict)": "Tokens maximaux (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Un maximum de 3 modèles peut être téléchargé en même temps. Veuillez réessayer ultérieurement.",
+	"May": "Mai",
+	"Memories accessible by LLMs will be shown here.": "Les mémoires accessibles par les LLMs seront affichées ici.",
+	"Memory": "Mémoire",
+	"Memory added successfully": "Mémoire ajoutée avec succès",
+	"Memory cleared successfully": "La mémoire a été effacée avec succès",
+	"Memory deleted successfully": "La mémoire a été supprimée avec succès",
+	"Memory updated successfully": "La mémoire a été mise à jour avec succès",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Les messages que vous envoyez après avoir créé votre lien ne seront pas partagés. Les utilisateurs disposant de l'URL pourront voir le chat partagé.",
+	"Min P": "",
+	"Minimum Score": "Score minimal",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "DD MMMM YYYY",
+	"MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY HH:mm:ss",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Le modèle '{{modelName}}' a été téléchargé avec succès.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Le modèle '{{modelTag}}' est déjà dans la file d'attente pour le téléchargement.",
+	"Model {{modelId}} not found": "Modèle {{modelId}} introuvable",
+	"Model {{modelName}} is not vision capable": "Le modèle {{modelName}} n'a pas de capacités visuelles",
+	"Model {{name}} is now {{status}}": "Le modèle {{name}} est désormais {{status}}.",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "Le modèle a été créé avec succès !",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Chemin du système de fichiers de modèle détecté. Le nom court du modèle est requis pour la mise à jour, l'opération ne peut pas être poursuivie.",
+	"Model ID": "ID du modèle",
+	"Model Name": "",
+	"Model not selected": "Modèle non sélectionné",
+	"Model Params": "Paramètres du modèle",
+	"Model updated successfully": "Le modèle a été mis à jour avec succès",
+	"Model Whitelisting": "Liste blanche de modèles",
+	"Model(s) Whitelisted": "Modèle(s) Autorisé(s)",
+	"Modelfile Content": "Contenu du Fichier de Modèle",
+	"Models": "Modèles",
+	"more": "",
+	"More": "Plus de",
+	"Move to Top": "",
+	"Name": "Nom",
+	"Name your model": "Nommez votre modèle",
+	"New Chat": "Nouvelle conversation",
+	"New folder": "",
+	"New Password": "Nouveau mot de passe",
+	"No content found": "",
+	"No content to speak": "Rien à signaler",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Aucun fichier sélectionné",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Aucun résultat trouvé",
+	"No search query generated": "Aucune requête de recherche générée",
+	"No source available": "Aucune source n'est disponible",
+	"No valves to update": "Aucune vanne à mettre à jour",
+	"None": "Aucun",
+	"Not factually correct": "Non factuellement correct",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Note : Si vous définissez un score minimum, seuls les documents ayant un score supérieur ou égal à ce score minimum seront retournés par la recherche.",
+	"Notes": "",
+	"Notifications": "Notifications",
+	"November": "Novembre",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "ID OAuth",
+	"October": "Octobre",
+	"Off": "Désactivé",
+	"Okay, Let's Go!": "D'accord, on y va !",
+	"OLED Dark": "Noir OLED",
+	"Ollama": "Ollama",
+	"Ollama API": "API Ollama",
+	"Ollama API disabled": "API Ollama désactivée",
+	"Ollama API is disabled": "L'API Ollama est désactivée",
+	"Ollama Version": "Version Ollama améliorée",
+	"On": "Activé",
+	"Only": "Seulement",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Seuls les caractères alphanumériques et les tirets sont autorisés dans la chaîne de commande.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oups ! Il semble que l'URL soit invalide. Veuillez vérifier à nouveau et réessayer.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oups\u00a0! Vous utilisez une méthode non prise en charge (frontend uniquement). Veuillez servir l'interface Web à partir du backend.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Ouvrir une nouvelle discussion",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API OpenAI",
+	"OpenAI API Config": "Configuration de l'API OpenAI",
+	"OpenAI API Key is required.": "Une clé API OpenAI est requise.",
+	"OpenAI URL/Key required.": "URL/Clé OpenAI requise.",
+	"or": "ou",
+	"Other": "Autre",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Mot de passe",
+	"PDF document (.pdf)": "Document au format PDF  (.pdf)",
+	"PDF Extract Images (OCR)": "Extraction d'images PDF (OCR)",
+	"pending": "en attente",
+	"Permission denied when accessing media devices": "Accès aux appareils multimédias refusé",
+	"Permission denied when accessing microphone": "Autorisation refusée lors de l'accès au micro",
+	"Permission denied when accessing microphone: {{error}}": "Permission refusée lors de l'accès au microphone : {{error}}",
+	"Personalization": "Personnalisation",
+	"Pin": "Épingler",
+	"Pinned": "Épinglé",
+	"Pipeline deleted successfully": "Le pipeline a été supprimé avec succès",
+	"Pipeline downloaded successfully": "Le pipeline a été téléchargé avec succès",
+	"Pipelines": "Pipelines",
+	"Pipelines Not Detected": "Aucun pipelines détecté",
+	"Pipelines Valves": "Vannes de Pipelines",
+	"Plain text (.txt)": "Texte simple (.txt)",
+	"Playground": "Aire de jeux",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Attitude positive",
+	"Previous 30 days": "30 derniers jours",
+	"Previous 7 days": "7 derniers jours",
+	"Profile Image": "Image de profil",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (par ex. Dites-moi un fait amusant à propos de l'Empire romain)",
+	"Prompt Content": "Contenu du prompt",
+	"Prompt suggestions": "Suggestions pour le prompt",
+	"Prompts": "Prompts",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Récupérer « {{searchValue}} » depuis Ollama.com",
+	"Pull a model from Ollama.com": "Télécharger un modèle depuis Ollama.com",
+	"Query Params": "Paramètres de requête",
+	"RAG Template": "Modèle RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Lire à haute voix",
+	"Record voice": "Enregistrer la voix",
+	"Redirecting you to OpenWebUI Community": "Redirection vers la communauté OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Désignez-vous comme « Utilisateur » (par ex. « L'utilisateur apprend l'espagnol »)",
+	"References from": "",
+	"Refused when it shouldn't have": "Refusé alors qu'il n'aurait pas dû l'être",
+	"Regenerate": "Regénérer",
+	"Release Notes": "Notes de publication",
+	"Relevance": "",
+	"Remove": "Retirer",
+	"Remove Model": "Retirer le modèle",
+	"Rename": "Renommer",
+	"Repeat Last N": "Répéter les N derniers",
+	"Request Mode": "Mode de Requête",
+	"Reranking Model": "Modèle de ré-ranking",
+	"Reranking model disabled": "Modèle de ré-ranking désactivé",
+	"Reranking model set to \"{{reranking_model}}\"": "Modèle de ré-ranking défini sur « {{reranking_model}} »",
+	"Reset": "Réinitialiser",
+	"Reset Upload Directory": "Répertoire de téléchargement réinitialisé",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Copie automatique de la réponse vers le presse-papiers",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Les notifications de réponse ne peuvent pas être activées car les autorisations du site web ont été refusées. Veuillez visiter les paramètres de votre navigateur pour accorder l'accès nécessaire.",
+	"Response splitting": "Fractionnement de la réponse",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rôle",
+	"Rosé Pine": "Pin rosé",
+	"Rosé Pine Dawn": "Aube de Pin Rosé",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "Courir",
+	"Save": "Enregistrer",
+	"Save & Create": "Enregistrer & Créer",
+	"Save & Update": "Enregistrer & Mettre à jour",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "La sauvegarde des journaux de discussion directement dans le stockage de votre navigateur n'est plus prise en charge. Veuillez prendre un instant pour télécharger et supprimer vos journaux de discussion en cliquant sur le bouton ci-dessous. Pas de soucis, vous pouvez facilement les réimporter depuis le backend via l'interface ci-dessous",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Recherche",
+	"Search a model": "Rechercher un modèle",
+	"Search Chats": "Rechercher des conversations",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Fonctions de recherche",
+	"Search Knowledge": "",
+	"Search Models": "Rechercher des modèles",
+	"Search Prompts": "Recherche de prompts",
+	"Search Query Generation Prompt": "Génération d'interrogation de recherche",
+	"Search Result Count": "Nombre de résultats de recherche",
+	"Search Tools": "Outils de recherche",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Recherché {{count}} site(s)_one",
+	"Searched {{count}} sites_many": "Recherché {{count}} sites_many",
+	"Searched {{count}} sites_other": "Recherché {{count}} sites_autres",
+	"Searching \"{{searchQuery}}\"": "Recherche de « {{searchQuery}} »",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "URL de recherche Searxng",
+	"See readme.md for instructions": "Voir le fichier readme.md pour les instructions",
+	"See what's new": "Découvrez les nouvelles fonctionnalités",
+	"Seed": "Graine",
+	"Select a base model": "Sélectionnez un modèle de base",
+	"Select a engine": "Sélectionnez un moteur",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Sélectionnez une fonction",
+	"Select a model": "Sélectionnez un modèle",
+	"Select a pipeline": "Sélectionnez un pipeline",
+	"Select a pipeline url": "Sélectionnez l'URL du pipeline",
+	"Select a tool": "Sélectionnez un outil",
+	"Select an Ollama instance": "Sélectionnez une instance Ollama",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Sélectionnez un modèle",
+	"Select only one model to call": "Sélectionnez seulement un modèle pour appeler",
+	"Selected model(s) do not support image inputs": "Les modèle(s) sélectionné(s) ne prennent pas en charge les entrées d'images",
+	"Semantic distance to query": "",
+	"Send": "Envoyer",
+	"Send a Message": "Envoyer un message",
+	"Send message": "Envoyer un message",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "Septembre",
+	"Serper API Key": "Clé API Serper",
+	"Serply API Key": "Clé API Serply",
+	"Serpstack API Key": "Clé API Serpstack",
+	"Server connection verified": "Connexion au serveur vérifiée",
+	"Set as default": "Définir comme valeur par défaut",
+	"Set CFG Scale": "",
+	"Set Default Model": "Définir le modèle par défaut",
+	"Set embedding model (e.g. {{model}})": "Définir le modèle d'encodage (par ex. {{model}})",
+	"Set Image Size": "Définir la taille de l'image",
+	"Set reranking model (e.g. {{model}})": "Définir le modèle de reclassement (par ex. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Définir les étapes",
+	"Set Task Model": "Définir le modèle de tâche",
+	"Set Voice": "Définir la voix",
+	"Set whisper model": "",
+	"Settings": "Paramètres",
+	"Settings saved successfully!": "Paramètres enregistrés avec succès !",
+	"Share": "Partager",
+	"Share Chat": "Partage de conversation",
+	"Share to OpenWebUI Community": "Partager avec la communauté OpenWebUI",
+	"short-summary": "résumé concis",
+	"Show": "Montrer",
+	"Show Admin Details in Account Pending Overlay": "Afficher les détails de l'administrateur dans la superposition en attente du compte",
+	"Show Model": "Montrer le modèle",
+	"Show shortcuts": "Afficher les raccourcis",
+	"Show your support!": "Montre ton soutien !",
+	"Showcased creativity": "Créativité mise en avant",
+	"Sign in": "S'identifier",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Déconnexion",
+	"Sign up": "Inscrivez-vous",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Source",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Erreur de reconnaissance vocale\u00a0: {{error}}",
+	"Speech-to-Text Engine": "Moteur de reconnaissance vocale",
+	"Stop": "",
+	"Stop Sequence": "Séquence d'arrêt",
+	"Stream Chat Response": "",
+	"STT Model": "Modèle de STT",
+	"STT Settings": "Paramètres de STT",
+	"Subtitle (e.g. about the Roman Empire)": "Sous-titres (par ex. sur l'Empire romain)",
+	"Success": "Réussite",
+	"Successfully updated.": "Mise à jour réussie.",
+	"Suggested": "Sugéré",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "Système",
+	"System Instructions": "",
+	"System Prompt": "Prompt du système",
+	"Tags": "Balises",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Appuyez pour interrompre",
+	"Tavily API Key": "Clé API Tavily",
+	"Tell us more:": "Dites-nous en plus à ce sujet : ",
+	"Temperature": "Température",
+	"Template": "Template",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Moteur de synthèse vocale",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Merci pour vos commentaires !",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Le score doit être une valeur comprise entre 0,0 (0\u00a0%) et 1,0 (100\u00a0%).",
+	"Theme": "Thème",
+	"Thinking...": "En train de réfléchir...",
+	"This action cannot be undone. Do you wish to continue?": "Cette action ne peut pas être annulée. Souhaitez-vous continuer ?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Cela garantit que vos conversations précieuses soient sauvegardées en toute sécurité dans votre base de données backend. Merci !",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Il s'agit d'une fonctionnalité expérimentale, elle peut ne pas fonctionner comme prévu et est sujette à modification à tout moment.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Cela supprimera",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Explication approfondie",
+	"Tika": "Tika",
+	"Tika Server URL required.": "URL du serveur Tika requise.",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Conseil\u00a0: mettez à jour plusieurs emplacements de variables consécutivement en appuyant sur la touche Tab dans l’entrée de chat après chaque remplacement.",
+	"Title": "Titre",
+	"Title (e.g. Tell me a fun fact)": "Titre (par ex. raconte-moi un fait amusant)",
+	"Title Auto-Generation": "Génération automatique de titres",
+	"Title cannot be an empty string.": "Le titre ne peut pas être une chaîne de caractères vide.",
+	"Title Generation Prompt": "Prompt de génération de titre",
+	"To access the available model names for downloading,": "Pour accéder aux noms des modèles disponibles en téléchargement,",
+	"To access the GGUF models available for downloading,": "Pour accéder aux modèles GGUF disponibles en téléchargement,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Pour accéder à l'interface Web, veuillez contacter l'administrateur. Les administrateurs peuvent gérer les statuts des utilisateurs depuis le panneau d'administration.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "à l'entrée de discussion.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Pour sélectionner des filtres ici, ajoutez-les d'abord à l'espace de travail « Fonctions ». ",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Pour sélectionner des toolkits ici, ajoutez-les d'abord à l'espace de travail « Outils ». ",
+	"Toast notifications for new updates": "",
+	"Today": "Aujourd'hui",
+	"Toggle settings": "Basculer les paramètres",
+	"Toggle sidebar": "Basculer la barre latérale",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Jeton à conserver pour l'actualisation du contexte (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "L'outil a été créé avec succès",
+	"Tool deleted successfully": "Outil supprimé avec succès",
+	"Tool imported successfully": "Outil importé avec succès",
+	"Tool updated successfully": "L'outil a été mis à jour avec succès",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "Outils",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Rencontrez-vous des difficultés pour accéder à Ollama ?",
+	"TTS Model": "Modèle de synthèse vocale",
+	"TTS Settings": "Paramètres de synthèse vocale",
+	"TTS Voice": "Voix TTS",
+	"Type": "Type",
+	"Type Hugging Face Resolve (Download) URL": "Entrez l'URL de Téléchargement Hugging Face Resolve",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Oh non ! Un problème est survenu lors de la connexion à {{provider}}.",
+	"UI": "Interface utilisateur",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "Mise à jour",
+	"Update and Copy Link": "Mettre à jour et copier le lien",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Mettre à jour le mot de passe",
+	"Updated": "",
+	"Updated at": "Mise à jour le",
+	"Updated At": "",
+	"Upload": "Télécharger",
+	"Upload a GGUF model": "Téléverser un modèle GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Télécharger des fichiers",
+	"Upload Pipeline": "Pipeline de téléchargement",
+	"Upload Progress": "Progression de l'envoi",
+	"URL Mode": "Mode d'URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Utilisez Gravatar",
+	"Use Initials": "Utiliser les initiales",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "utiliser mmap (Ollama)",
+	"user": "utilisateur",
+	"User": "",
+	"User location successfully retrieved.": "L'emplacement de l'utilisateur a été récupéré avec succès.",
+	"User Permissions": "Permissions utilisateur",
+	"Users": "Utilisateurs",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Utilisez",
+	"Valid time units:": "Unités de temps valides\u00a0:",
+	"Valves": "Vannes",
+	"Valves updated": "Vannes mises à jour",
+	"Valves updated successfully": "Les vannes ont été mises à jour avec succès",
+	"variable": "variable",
+	"variable to have them replaced with clipboard content.": "variable pour qu'elles soient remplacées par le contenu du presse-papiers.",
+	"Version": "Version améliorée",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Voix",
+	"Voice Input": "",
+	"Warning": "Avertissement !",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Avertissement : Si vous mettez à jour ou modifiez votre modèle d'encodage, vous devrez réimporter tous les documents.",
+	"Web": "Web",
+	"Web API": "API Web",
+	"Web Loader Settings": "Paramètres du chargeur web",
+	"Web Search": "Recherche Web",
+	"Web Search Engine": "Moteur de recherche Web",
+	"Webhook URL": "URL du webhook",
+	"WebUI Settings": "Paramètres de WebUI",
+	"WebUI will make requests to": "WebUI effectuera des requêtes vers",
+	"What’s New in": "Quoi de neuf",
+	"Whisper (Local)": "Whisper (local)",
+	"Widescreen Mode": "Mode Grand Écran",
+	"Won": "",
+	"Workspace": "Espace de travail",
+	"Write a prompt suggestion (e.g. Who are you?)": "Écrivez une suggestion de prompt (par exemple : Qui êtes-vous ?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Rédigez un résumé de 50 mots qui résume [sujet ou mot-clé].",
+	"Write something...": "",
+	"Yesterday": "Hier",
+	"You": "Vous",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Vous pouvez personnaliser vos interactions avec les LLM en ajoutant des souvenirs via le bouton 'Gérer' ci-dessous, ce qui les rendra plus utiles et adaptés à vos besoins.",
+	"You cannot clone a base model": "Vous ne pouvez pas cloner un modèle de base",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Vous n'avez aucune conversation archivée",
+	"You have shared this chat": "Vous avez partagé cette conversation.",
+	"You're a helpful assistant.": "Vous êtes un assistant serviable.",
+	"You're now logged in.": "Vous êtes désormais connecté.",
+	"Your account status is currently pending activation.": "Votre statut de compte est actuellement en attente d'activation.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "YouTube",
+	"Youtube Loader Settings": "Paramètres de l'outil de téléchargement YouTube"
+}
diff --git a/src/lib/i18n/locales/fr-FR/translation.json b/src/lib/i18n/locales/fr-FR/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..f665d18d56d9281f39781f1729e1cdae35332656
--- /dev/null
+++ b/src/lib/i18n/locales/fr-FR/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": " 's', 'm', 'h', 'd', 'w' ou '-1' pour une durée illimitée.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(par ex. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(par exemple `sh webui.sh --api`)",
+	"(latest)": "(dernière version)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }} : Vous ne pouvez pas supprimer un modèle de base.",
+	"{{user}}'s Chats": "Conversations de {{user}}",
+	"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
+	"*Prompt node ID(s) are required for image generation": "*Les ID de noeud du prompt sont nécessaires pour la génération d’images",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "Une nouvelle version (v{{LATEST_VERSION}}) est disponible.",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un modèle de tâche est utilisé lors de l’exécution de tâches telles que la génération de titres pour les conversations et les requêtes de recherche sur le web.",
+	"a user": "un utilisateur",
+	"About": "À propos",
+	"Account": "Compte",
+	"Account Activation Pending": "Activation du compte en attente",
+	"Accurate information": "Information exacte",
+	"Actions": "Actions",
+	"Active Users": "Utilisateurs actifs",
+	"Add": "Ajouter",
+	"Add a model id": "Ajouter un identifiant de modèle",
+	"Add a short description about what this model does": "Ajoutez une brève description de ce que fait ce modèle.",
+	"Add a short title for this prompt": "Ajoutez un bref titre pour ce prompt.",
+	"Add a tag": "Ajouter un tag",
+	"Add Arena Model": "Ajouter un modèle d'arène",
+	"Add Content": "Ajouter du contenu",
+	"Add content here": "Ajoutez du contenu ici",
+	"Add custom prompt": "Ajouter un prompt personnalisé",
+	"Add Files": "Ajouter des fichiers",
+	"Add Memory": "Ajouter de la mémoire",
+	"Add Model": "Ajouter un modèle",
+	"Add Tag": "Ajouter un tag",
+	"Add Tags": "Ajouter des tags",
+	"Add text content": "Ajouter du contenu textuel",
+	"Add User": "Ajouter un utilisateur",
+	"Adjusting these settings will apply changes universally to all users.": "L'ajustement de ces paramètres appliquera universellement les changements à tous les utilisateurs.",
+	"admin": "administrateur",
+	"Admin": "Administrateur",
+	"Admin Panel": "Panneau d'administration",
+	"Admin Settings": "Paramètres admin.",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Les administrateurs ont accès à tous les outils en permanence ; les utilisateurs doivent se voir attribuer des outils pour chaque modèle dans l’espace de travail.",
+	"Advanced Parameters": "Paramètres avancés",
+	"Advanced Params": "Paramètres avancés",
+	"All chats": "Toutes les conversations",
+	"All Documents": "Tous les documents",
+	"Allow Chat Deletion": "Autoriser la suppression de l'historique de chat",
+	"Allow Chat Editing": "Autoriser la modification de l'historique de chat",
+	"Allow non-local voices": "Autoriser les voix non locales",
+	"Allow Temporary Chat": "Autoriser le chat éphémère",
+	"Allow User Location": "Autoriser l'emplacement de l'utilisateur",
+	"Allow Voice Interruption in Call": "Autoriser l'interruption vocale pendant un appel",
+	"alphanumeric characters and hyphens": "caractères alphanumériques et tirets",
+	"Already have an account?": "Avez-vous déjà un compte ?",
+	"an assistant": "un assistant",
+	"and": "et",
+	"and {{COUNT}} more": "et {{COUNT}} autres",
+	"and create a new shared link.": "et créer un nouveau lien partagé.",
+	"API Base URL": "URL de base de l'API",
+	"API Key": "Clé d'API",
+	"API Key created.": "Clé d'API générée.",
+	"API keys": "Clés d'API",
+	"April": "Avril",
+	"Archive": "Archiver",
+	"Archive All Chats": "Archiver toutes les conversations",
+	"Archived Chats": "Conversations archivées",
+	"are allowed - Activate this command by typing": "sont autorisés - Activer cette commande en tapant",
+	"Are you sure?": "Êtes-vous certain ?",
+	"Arena Models": "Modèles d'arène",
+	"Artifacts": "Artéfacts",
+	"Ask a question": "Posez votre question",
+	"Assistant": "Assistant",
+	"Attach file": "Joindre un document",
+	"Attention to detail": "Attention aux détails",
+	"Audio": "Audio",
+	"August": "Août",
+	"Auto-playback response": "Lire automatiquement la réponse",
+	"Automatic1111": "Automatic1111",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Chaîne d'authentification de l'API",
+	"AUTOMATIC1111 Base URL": "URL de base AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "L'URL de base {AUTOMATIC1111} est requise.",
+	"Available list": "Liste disponible",
+	"available!": "disponible !",
+	"Azure AI Speech": "Azure AI Speech",
+	"Azure Region": "Région Azure",
+	"Back": "Retour en arrière",
+	"Bad Response": "Mauvaise réponse",
+	"Banners": "Bannières",
+	"Base Model (From)": "Modèle de base (à partir de)",
+	"Batch Size (num_batch)": "Batch Size (num_batch)",
+	"before": "avant",
+	"Being lazy": "Être fainéant",
+	"Brave Search API Key": "Clé API Brave Search",
+	"Bypass SSL verification for Websites": "Bypasser la vérification SSL pour les sites web",
+	"Call": "Appeler",
+	"Call feature is not supported when using Web STT engine": "La fonction d'appel n'est pas prise en charge lors de l'utilisation du moteur Web STT",
+	"Camera": "Appareil photo",
+	"Cancel": "Annuler",
+	"Capabilities": "Capacités",
+	"Change Password": "Changer le mot de passe",
+	"Character": "Caractère",
+	"Chat": "Chat",
+	"Chat Background Image": "Image d'arrière-plan de la fenêtre de chat",
+	"Chat Bubble UI": "Bulles de chat",
+	"Chat Controls": "Contrôles du chat",
+	"Chat direction": "Direction du chat",
+	"Chat Overview": "Aperçu du chat",
+	"Chat Tags Auto-Generation": "Génération automatique des tags",
+	"Chats": "Conversations",
+	"Check Again": "Vérifiez à nouveau.",
+	"Check for updates": "Vérifier les mises à jour disponibles",
+	"Checking for updates...": "Recherche de mises à jour...",
+	"Choose a model before saving...": "Choisissez un modèle avant de sauvegarder...",
+	"Chunk Overlap": "Chevauchement des chunks",
+	"Chunk Params": "Paramètres des chunks",
+	"Chunk Size": "Taille des chunks",
+	"Citation": "Citation",
+	"Clear memory": "Effacer la mémoire",
+	"Click here for help.": "Cliquez ici pour obtenir de l'aide.",
+	"Click here to": "Cliquez ici pour",
+	"Click here to download user import template file.": "Cliquez ici pour télécharger le fichier modèle d'importation des utilisateurs.",
+	"Click here to learn more about faster-whisper and see the available models.": "Cliquez ici pour en savoir plus sur faster-whisper et voir les modèles disponibles.",
+	"Click here to select": "Cliquez ici pour sélectionner",
+	"Click here to select a csv file.": "Cliquez ici pour sélectionner un fichier .csv.",
+	"Click here to select a py file.": "Cliquez ici pour sélectionner un fichier .py.",
+	"Click here to upload a workflow.json file.": "Cliquez ici pour télécharger un fichier workflow.json.",
+	"click here.": "cliquez ici.",
+	"Click on the user role button to change a user's role.": "Cliquez sur le bouton de rôle d'utilisateur pour modifier son rôle.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "L'autorisation d'écriture du presse-papier a été refusée. Veuillez vérifier les paramètres de votre navigateur pour accorder l'accès nécessaire.",
+	"Clone": "Cloner",
+	"Close": "Fermer",
+	"Code execution": "Exécution de code",
+	"Code formatted successfully": "Le code a été formaté avec succès",
+	"Collection": "Collection",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL de base ComfyUI",
+	"ComfyUI Base URL is required.": "L'URL de base ComfyUI est requise.",
+	"ComfyUI Workflow": "Flux de travaux de ComfyUI",
+	"ComfyUI Workflow Nodes": "Noeud du flux de travaux de ComfyUI",
+	"Command": "Commande",
+	"Completions": "Complétions",
+	"Concurrent Requests": "Demandes concurrentes",
+	"Confirm": "Confirmer",
+	"Confirm Password": "Confirmer le mot de passe",
+	"Confirm your action": "Confirmer votre action",
+	"Connections": "Connexions",
+	"Contact Admin for WebUI Access": "Contacter l'administrateur pour obtenir l'accès à WebUI",
+	"Content": "Contenu",
+	"Content Extraction": "Extraction du contenu",
+	"Context Length": "Longueur du contexte",
+	"Continue Response": "Continuer la réponse",
+	"Continue with {{provider}}": "Continuer avec {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "Contrôle la façon dont le texte des messages est divisé pour les demandes de Text-to-Speech. « ponctuation » divise en phrases, « paragraphes » divise en paragraphes et « aucun » garde le message en tant que chaîne de texte unique.",
+	"Controls": "Contrôles",
+	"Copied": "Copié",
+	"Copied shared chat URL to clipboard!": "URL du chat copié dans le presse-papiers !",
+	"Copied to clipboard": "Copié dans le presse-papiers",
+	"Copy": "Copier",
+	"Copy last code block": "Copier le dernier bloc de code",
+	"Copy last response": "Copier la dernière réponse",
+	"Copy Link": "Copier le lien",
+	"Copy to clipboard": "Copier dans le presse-papiers",
+	"Copying to clipboard was successful!": "La copie dans le presse-papiers a réussi !",
+	"Create a model": "Créer un modèle",
+	"Create Account": "Créer un compte",
+	"Create Knowledge": "Créer une connaissance",
+	"Create new key": "Créer une nouvelle clé",
+	"Create new secret key": "Créer une nouvelle clé secrète",
+	"Created at": "Créé le",
+	"Created At": "Créé le",
+	"Created by": "Créé par",
+	"CSV Import": "Import CSV",
+	"Current Model": "Modèle actuel",
+	"Current Password": "Mot de passe actuel",
+	"Custom": "Sur mesure",
+	"Customize models for a specific purpose": "Personnaliser les modèles pour un usage spécifique",
+	"Dark": "Sombre",
+	"Dashboard": "Tableau de bord",
+	"Database": "Base de données",
+	"December": "Décembre",
+	"Default": "Par défaut",
+	"Default (Open AI)": "Par défaut (OpenAI)",
+	"Default (SentenceTransformers)": "Par défaut (Sentence Transformers)",
+	"Default Model": "Modèle standard",
+	"Default model updated": "Modèle par défaut mis à jour",
+	"Default Prompt Suggestions": "Suggestions de prompts par défaut",
+	"Default User Role": "Rôle utilisateur par défaut",
+	"Delete": "Supprimer",
+	"Delete a model": "Supprimer un modèle",
+	"Delete All Chats": "Supprimer toutes les conversations",
+	"Delete chat": "Supprimer la conversation",
+	"Delete Chat": "Supprimer la Conversation",
+	"Delete chat?": "Supprimer la conversation ?",
+	"Delete folder?": "Supprimer le dossier ?",
+	"Delete function?": "Supprimer la fonction ?",
+	"Delete prompt?": "Supprimer le prompt ?",
+	"delete this link": "supprimer ce lien",
+	"Delete tool?": "Effacer l'outil ?",
+	"Delete User": "Supprimer le compte d'utilisateur",
+	"Deleted {{deleteModelTag}}": "Supprimé {{deleteModelTag}}",
+	"Deleted {{name}}": "Supprimé {{name}}",
+	"Description": "Description",
+	"Didn't fully follow instructions": "N'a pas entièrement respecté les instructions",
+	"Disabled": "Désactivé",
+	"Discover a function": "Trouvez une fonction",
+	"Discover a model": "Trouvez un modèle",
+	"Discover a prompt": "Trouvez un prompt",
+	"Discover a tool": "Trouvez un outil",
+	"Discover, download, and explore custom functions": "Découvrez, téléchargez et explorez des fonctions personnalisées",
+	"Discover, download, and explore custom prompts": "Découvrez, téléchargez et explorez des prompts personnalisés",
+	"Discover, download, and explore custom tools": "Découvrez, téléchargez et explorez des outils personnalisés",
+	"Discover, download, and explore model presets": "Découvrir, télécharger et explorer des préréglages de modèles",
+	"Dismissible": "Fermeture",
+	"Display Emoji in Call": "Afficher les emojis pendant l'appel",
+	"Display the username instead of You in the Chat": "Afficher le nom d'utilisateur à la place de \"Vous\" dans le chat",
+	"Do not install functions from sources you do not fully trust.": "N'installez pas de fonctions provenant de sources auxquelles vous ne faites pas entièrement confiance.",
+	"Do not install tools from sources you do not fully trust.": "N'installez pas d'outils provenant de sources auxquelles vous ne faites pas entièrement confiance.",
+	"Document": "Document",
+	"Documentation": "Documentation",
+	"Documents": "Documents",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "n'établit aucune connexion externe et garde vos données en sécurité sur votre serveur local.",
+	"Don't have an account?": "Vous n'avez pas de compte ?",
+	"don't install random functions from sources you don't trust.": "n'installez pas de fonctions aléatoires provenant de sources auxquelles vous ne faites pas confiance.",
+	"don't install random tools from sources you don't trust.": "n'installez pas d'outils aléatoires provenant de sources auxquelles vous ne faites pas confiance.",
+	"Don't like the style": "N'apprécie pas le style",
+	"Done": "Terminé",
+	"Download": "Télécharger",
+	"Download canceled": "Téléchargement annulé",
+	"Download Database": "Télécharger la base de données",
+	"Draw": "Match nul",
+	"Drop any files here to add to the conversation": "Déposez des fichiers ici pour les ajouter à la conversation",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "par ex. '30s', '10 min'. Les unités de temps valides sont 's', 'm', 'h'.",
+	"Edit": "Modifier",
+	"Edit Arena Model": "Modifier le modèle d'arène",
+	"Edit Memory": "Modifier la mémoire",
+	"Edit User": "Modifier l'utilisateur",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "E-mail",
+	"Embedding Batch Size": "Taille du lot d'embedding",
+	"Embedding Model": "Modèle d'embedding",
+	"Embedding Model Engine": "Moteur de modèle d'embedding",
+	"Embedding model set to \"{{embedding_model}}\"": "Modèle d'embedding défini sur « {{embedding_model}} »",
+	"Enable Community Sharing": "Activer le partage communautaire",
+	"Enable Message Rating": "Activer l'évaluation des messages",
+	"Enable New Sign Ups": "Activer les nouvelles inscriptions",
+	"Enable Web Search": "Activer la recherche Web",
+	"Enable Web Search Query Generation": "Activer la génération de requêtes de recherche Web",
+	"Enabled": "Activé",
+	"Engine": "Moteur",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Vérifiez que votre fichier CSV comprenne les 4 colonnes dans cet ordre : Name, Email, Password, Role.",
+	"Enter {{role}} message here": "Entrez le message {{role}} ici",
+	"Enter a detail about yourself for your LLMs to recall": "Saisissez un détail sur vous-même que vos LLMs pourront se rappeler",
+	"Enter api auth string (e.g. username:password)": "Entrez la chaîne d'authentification de l'API (par ex. nom d'utilisateur:mot de passe)",
+	"Enter Brave Search API Key": "Entrez la clé API Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "Entrez l'échelle CFG (par ex. 7.0)",
+	"Enter Chunk Overlap": "Entrez le chevauchement des chunks",
+	"Enter Chunk Size": "Entrez la taille des chunks",
+	"Enter description": "Entrez la description",
+	"Enter Github Raw URL": "Entrez l'URL brute de GitHub",
+	"Enter Google PSE API Key": "Entrez la clé API Google PSE",
+	"Enter Google PSE Engine Id": "Entrez l'identifiant du moteur Google PSE",
+	"Enter Image Size (e.g. 512x512)": "Entrez la taille de l'image (par ex. 512x512)",
+	"Enter language codes": "Entrez les codes de langue",
+	"Enter Model ID": "Entrez l'ID du modèle",
+	"Enter model tag (e.g. {{modelTag}})": "Entrez le tag du modèle (par ex. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Entrez le nombre d'étapes (par ex. 50)",
+	"Enter Sampler (e.g. Euler a)": "Entrez le sampler (par ex. Euler a)",
+	"Enter Scheduler (e.g. Karras)": "Entrez le planificateur (par ex. Karras)",
+	"Enter Score": "Entrez votre score",
+	"Enter SearchApi API Key": "Entrez la clé API SearchApi",
+	"Enter SearchApi Engine": "Entrez le moteur de recherche SearchApi",
+	"Enter Searxng Query URL": "Entrez l'URL de la requête Searxng",
+	"Enter Serper API Key": "Entrez la clé API Serper",
+	"Enter Serply API Key": "Entrez la clé API Serply",
+	"Enter Serpstack API Key": "Entrez la clé API Serpstack",
+	"Enter stop sequence": "Entrez la séquence d'arrêt",
+	"Enter system prompt": "Entrez le prompt système",
+	"Enter Tavily API Key": "Entrez la clé API Tavily",
+	"Enter Tika Server URL": "Entrez l'URL du serveur Tika",
+	"Enter Top K": "Entrez les Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Entrez l'URL (par ex. {http://127.0.0.1:7860/})",
+	"Enter URL (e.g. http://localhost:11434)": "Entrez l'URL (par ex. http://localhost:11434)",
+	"Enter Your Email": "Entrez votre adresse e-mail",
+	"Enter Your Full Name": "Entrez votre nom complet",
+	"Enter your message": "Entrez votre message",
+	"Enter Your Password": "Entrez votre mot de passe",
+	"Enter Your Role": "Entrez votre rôle",
+	"Error": "Erreur",
+	"ERROR": "ERREUR",
+	"Evaluations": "Évaluations",
+	"Exclude": "Exclure",
+	"Experimental": "Expérimental",
+	"Export": "Exportation",
+	"Export All Chats (All Users)": "Exporter toutes les conversations (de tous les utilisateurs)",
+	"Export chat (.json)": "Exporter la conversation (.json)",
+	"Export Chats": "Exporter les conversations",
+	"Export Config to JSON File": "Exporter la configuration vers un fichier JSON",
+	"Export Functions": "Exporter des fonctions",
+	"Export LiteLLM config.yaml": "Exportez le fichier LiteLLM config.yaml",
+	"Export Models": "Exporter des modèles",
+	"Export Prompts": "Exporter des prompts",
+	"Export Tools": "Exporter des outils",
+	"External Models": "Modèles externes",
+	"Failed to add file.": "Échec de l'ajout du fichier.",
+	"Failed to create API Key.": "Échec de la création de la clé API.",
+	"Failed to read clipboard contents": "Échec de la lecture du contenu du presse-papiers",
+	"Failed to update settings": "Échec de la mise à jour des paramètres",
+	"Failed to upload file.": "Échec du téléchargement du fichier.",
+	"February": "Février",
+	"Feedback History": "Historique des avis",
+	"Feel free to add specific details": "N'hésitez pas à ajouter des détails spécifiques",
+	"File": "Fichier",
+	"File added successfully.": "Fichier ajouté avec succès.",
+	"File content updated successfully.": "Contenu du fichier mis à jour avec succès.",
+	"File Mode": "Mode fichier",
+	"File not found.": "Fichier introuvable.",
+	"File removed successfully.": "Fichier supprimé avec succès.",
+	"File size should not exceed {{maxSize}} MB.": "La taille du fichier ne doit pas dépasser {{maxSize}} Mo.",
+	"Files": "Fichiers",
+	"Filter is now globally disabled": "Le filtre est maintenant désactivé globalement",
+	"Filter is now globally enabled": "Le filtre est désormais activé globalement",
+	"Filters": "Filtres",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Spoofing détecté : impossible d'utiliser les initiales comme avatar. Retour à l'image de profil par défaut.",
+	"Fluidly stream large external response chunks": "Streaming fluide de gros chunks de réponses externes",
+	"Focus chat input": "Mettre le focus sur le champ de chat",
+	"Folder deleted successfully": "Dossier supprimé avec succès",
+	"Folder name cannot be empty": "Le nom du dossier ne peut pas être vide",
+	"Folder name cannot be empty.": "Le nom du dossier ne peut pas être vide.",
+	"Folder name updated successfully": "Le nom du dossier a été mis à jour avec succès",
+	"Followed instructions perfectly": "A parfaitement suivi les instructions",
+	"Form": "Formulaire",
+	"Format your variables using brackets like this:": "Formatez vos variables en utilisant des parenthèses comme ceci :",
+	"Frequency Penalty": "Pénalité de fréquence",
+	"Function": "Fonction",
+	"Function created successfully": "La fonction a été créée avec succès",
+	"Function deleted successfully": "Fonction supprimée avec succès",
+	"Function Description (e.g. A filter to remove profanity from text)": "Description de la fonction (par ex. Un filtre pour supprimer les grossièretés d'un texte)",
+	"Function ID (e.g. my_filter)": "ID de la fonction (par ex. mon_filtre)",
+	"Function is now globally disabled": "La fonction est désormais désactivée globalement",
+	"Function is now globally enabled": "La fonction est désormais activée globalement",
+	"Function Name (e.g. My Filter)": "Nom de la fonction (par ex. Mon Filtre)",
+	"Function updated successfully": "La fonction a été mise à jour avec succès",
+	"Functions": "Fonctions",
+	"Functions allow arbitrary code execution": "Les fonctions permettent l'exécution de code arbitraire",
+	"Functions allow arbitrary code execution.": "Les fonctions permettent l'exécution de code arbitraire.",
+	"Functions imported successfully": "Fonctions importées avec succès",
+	"General": "Général",
+	"General Settings": "Paramètres généraux",
+	"Generate Image": "Générer une image",
+	"Generating search query": "Génération d'une requête de recherche",
+	"Generation Info": "Informations sur la génération",
+	"Get up and running with": "Démarrez avec",
+	"Global": "Mondial",
+	"Good Response": "Bonne réponse",
+	"Google PSE API Key": "Clé API Google PSE",
+	"Google PSE Engine Id": "ID du moteur de recherche PSE de Google",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "Retour haptique",
+	"has no conversations.": "n'a aucune conversation.",
+	"Hello, {{name}}": "Bonjour, {{name}}.",
+	"Help": "Aide",
+	"Help us create the best community leaderboard by sharing your feedback history!": "Aidez-nous à créer le meilleur classement communautaire en partageant votre historique des avis !",
+	"Hide": "Cacher",
+	"Hide Model": "Masquer le modèle",
+	"How can I help you today?": "Comment puis-je vous aider aujourd'hui ?",
+	"Hybrid Search": "Recherche hybride",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Je reconnais avoir lu et compris les implications de mes actions. Je suis conscient des risques associés à l'exécution d'un code arbitraire et j'ai vérifié la fiabilité de la source.",
+	"ID": "ID",
+	"Image Generation (Experimental)": "Génération d'images (expérimental)",
+	"Image Generation Engine": "Moteur de génération d'images",
+	"Image Settings": "Paramètres de génération d'images",
+	"Images": "Images",
+	"Import Chats": "Importer les conversations",
+	"Import Config from JSON File": "Importer la configuration depuis un fichier JSON",
+	"Import Functions": "Importer des fonctions",
+	"Import Models": "Importer des modèles",
+	"Import Prompts": "Importer des prompts",
+	"Import Tools": "Importer des outils",
+	"Include": "Inclure",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Inclure le drapeau `--api-auth` lors de l'exécution de stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Inclure le drapeau `--api` lorsque vous exécutez stable-diffusion-webui",
+	"Info": "Info",
+	"Input commands": "Commandes d'entrée",
+	"Install from Github URL": "Installer depuis une URL GitHub",
+	"Instant Auto-Send After Voice Transcription": "Envoi automatique après la transcription",
+	"Interface": "Interface utilisateur",
+	"Invalid file format.": "Format de fichier non valide.",
+	"Invalid Tag": "Tag non valide",
+	"January": "Janvier",
+	"join our Discord for help.": "Rejoignez notre Discord pour obtenir de l'aide.",
+	"JSON": "JSON",
+	"JSON Preview": "Aperçu JSON",
+	"July": "Juillet",
+	"June": "Juin",
+	"JWT Expiration": "Expiration du token JWT",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Temps de maintien connecté",
+	"Keyboard shortcuts": "Raccourcis clavier",
+	"Knowledge": "Connaissances",
+	"Knowledge created successfully.": "Connaissance créée avec succès.",
+	"Knowledge deleted successfully.": "Connaissance supprimée avec succès.",
+	"Knowledge reset successfully.": "Connaissance réinitialisée avec succès.",
+	"Knowledge updated successfully": "Connaissance mise à jour avec succès",
+	"Landing Page Mode": "Mode de la page d'accueil",
+	"Language": "Langue",
+	"large language models, locally.": "grand modèle de langage, localement.",
+	"Last Active": "Dernière activité",
+	"Last Modified": "Dernière modification",
+	"Leaderboard": "Classement",
+	"Leave empty for unlimited": "Laissez vide pour illimité",
+	"Leave empty to include all models or select specific models": "Laissez vide pour inclure tous les modèles ou sélectionnez des modèles spécifiques",
+	"Leave empty to use the default prompt, or enter a custom prompt": "Laissez vide pour utiliser le prompt par défaut, ou entrez un prompt personnalisé",
+	"Light": "Clair",
+	"Listening...": "Écoute en cours...",
+	"LLMs can make mistakes. Verify important information.": "Les LLM peuvent faire des erreurs. Vérifiez les informations importantes.",
+	"Local Models": "Modèles locaux",
+	"Lost": "Perdu",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Réalisé par la communauté OpenWebUI",
+	"Make sure to enclose them with": "Assurez-vous de les inclure dans",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "Veillez à exporter un fichier workflow.json au format API depuis ComfyUI.",
+	"Manage": "Gérer",
+	"Manage Arena Models": "Gérer les modèles d'arène",
+	"Manage Models": "Gérer les modèles",
+	"Manage Ollama Models": "Gérer les modèles Ollama",
+	"Manage Pipelines": "Gérer les pipelines",
+	"March": "Mars",
+	"Max Tokens (num_predict)": "Nb max de tokens (num_predict)",
+	"Max Upload Count": "Nombre maximal de téléversements",
+	"Max Upload Size": "Limite de taille de téléversement",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Un maximum de 3 modèles peut être téléchargé en même temps. Veuillez réessayer ultérieurement.",
+	"May": "Mai",
+	"Memories accessible by LLMs will be shown here.": "Les mémoires accessibles par les LLMs seront affichées ici.",
+	"Memory": "Mémoire",
+	"Memory added successfully": "Mémoire ajoutée avec succès",
+	"Memory cleared successfully": "La mémoire a été effacée avec succès",
+	"Memory deleted successfully": "La mémoire a été supprimée avec succès",
+	"Memory updated successfully": "La mémoire a été mise à jour avec succès",
+	"Merge Responses": "Fusionner les réponses",
+	"Message rating should be enabled to use this feature": "L'évaluation des messages doit être activée pour utiliser cette fonctionnalité",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Les messages que vous envoyez après avoir créé votre lien ne seront pas partagés. Les utilisateurs disposant de l'URL pourront voir la conversation partagée.",
+	"Min P": "P min",
+	"Minimum Score": "Score minimal",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "DD MMMM YYYY",
+	"MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY HH:mm:ss",
+	"Model": "Modèle",
+	"Model '{{modelName}}' has been successfully downloaded.": "Le modèle '{{modelName}}' a été téléchargé avec succès.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Le modèle '{{modelTag}}' est déjà dans la file d'attente pour le téléchargement.",
+	"Model {{modelId}} not found": "Modèle {{modelId}} introuvable",
+	"Model {{modelName}} is not vision capable": "Le modèle {{modelName}} n'a pas de capacités visuelles",
+	"Model {{name}} is now {{status}}": "Le modèle {{name}} est désormais {{status}}.",
+	"Model {{name}} is now at the top": "Le modèle {{name}} est désormais en haut",
+	"Model accepts image inputs": "Le modèle accepte les images en entrée",
+	"Model created successfully!": "Le modèle a été créé avec succès !",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Chemin du système de fichiers de modèle détecté. Le nom court du modèle est requis pour la mise à jour, l'opération ne peut pas être poursuivie.",
+	"Model ID": "ID du modèle",
+	"Model Name": "Nom du modèle",
+	"Model not selected": "Modèle non sélectionné",
+	"Model Params": "Paramètres du modèle",
+	"Model updated successfully": "Le modèle a été mis à jour avec succès",
+	"Model Whitelisting": "Liste blanche de modèles",
+	"Model(s) Whitelisted": "Modèle(s) Autorisé(s)",
+	"Modelfile Content": "Contenu du Fichier de Modèle",
+	"Models": "Modèles",
+	"more": "plus",
+	"More": "Plus",
+	"Move to Top": "Déplacer en haut",
+	"Name": "Nom d'utilisateur",
+	"Name your model": "Nommez votre modèle",
+	"New Chat": "Nouvelle conversation",
+	"New folder": "Nouveau dossier",
+	"New Password": "Nouveau mot de passe",
+	"No content found": "Aucun contenu trouvé",
+	"No content to speak": "Rien à signaler",
+	"No distance available": "Aucune distance disponible",
+	"No feedbacks found": "Aucun avis trouvé",
+	"No file selected": "Aucun fichier sélectionné",
+	"No files found.": "Aucun fichier trouvé.",
+	"No HTML, CSS, or JavaScript content found.": "Aucun contenu HTML, CSS ou JavaScript trouvé.",
+	"No knowledge found": "Aucune connaissance trouvée",
+	"No models found": "Aucun modèle trouvé",
+	"No results found": "Aucun résultat trouvé",
+	"No search query generated": "Aucune requête de recherche générée",
+	"No source available": "Aucune source n'est disponible",
+	"No valves to update": "Aucune vanne à mettre à jour",
+	"None": "Aucun",
+	"Not factually correct": "Non factuellement correct",
+	"Not helpful": "Pas utile",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Note : Si vous définissez un score minimum, seuls les documents ayant un score supérieur ou égal à ce score minimum seront retournés par la recherche.",
+	"Notes": "Notes",
+	"Notifications": "Notifications",
+	"November": "Novembre",
+	"num_gpu (Ollama)": "num_gpu (Ollama)",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "ID OAuth",
+	"October": "Octobre",
+	"Off": "Désactivé",
+	"Okay, Let's Go!": "D'accord, allons-y !",
+	"OLED Dark": "Noir OLED",
+	"Ollama": "Ollama",
+	"Ollama API": "API Ollama",
+	"Ollama API disabled": "API Ollama désactivée",
+	"Ollama API is disabled": "L'API Ollama est désactivée",
+	"Ollama Version": "Version Ollama",
+	"On": "Activé",
+	"Only": "Seulement",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Seuls les caractères alphanumériques et les tirets sont autorisés dans la chaîne de commande.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Seules les collections peuvent être modifiées, créez une nouvelle base de connaissance pour modifier/ajouter des documents.",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oups ! Il semble que l'URL soit invalide. Veuillez vérifier à nouveau et réessayer.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "Oups ! Des fichiers sont encore en cours de téléversement. Veuillez patienter jusqu'à la fin du téléversement.",
+	"Oops! There was an error in the previous response.": "Oups ! Il y a eu une erreur dans la réponse précédente.",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oups\u00a0! Vous utilisez une méthode non prise en charge (frontend uniquement). Veuillez servir l'interface Web à partir du backend.",
+	"Open file": "Ouvrir le fichier",
+	"Open in full screen": "Ouvrir en plein écran",
+	"Open new chat": "Ouvrir une nouvelle conversation",
+	"Open WebUI uses faster-whisper internally.": "Open WebUI utilise faster-whisper en interne.",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "La version Open WebUI (v{{OPEN_WEBUI_VERSION}}) est inférieure à la version requise (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API compatibles OpenAI",
+	"OpenAI API Config": "Configuration de l'API OpenAI",
+	"OpenAI API Key is required.": "Une clé API OpenAI est requise.",
+	"OpenAI URL/Key required.": "URL/Clé OpenAI requise.",
+	"or": "ou",
+	"Other": "Autre",
+	"OUTPUT": "SORTIE",
+	"Output format": "Format de sortie",
+	"Overview": "Aperçu",
+	"page": "page",
+	"Password": "Mot de passe",
+	"PDF document (.pdf)": "Document au format PDF (.pdf)",
+	"PDF Extract Images (OCR)": "Extraction d'images PDF (OCR)",
+	"pending": "en attente",
+	"Permission denied when accessing media devices": "Accès aux appareils multimédias refusé",
+	"Permission denied when accessing microphone": "Autorisation refusée lors de l'accès au micro",
+	"Permission denied when accessing microphone: {{error}}": "Permission refusée lors de l'accès au microphone : {{error}}",
+	"Personalization": "Personnalisation",
+	"Pin": "Épingler",
+	"Pinned": "Épinglé",
+	"Pipeline deleted successfully": "Le pipeline a été supprimé avec succès",
+	"Pipeline downloaded successfully": "Le pipeline a été téléchargé avec succès",
+	"Pipelines": "Pipelines",
+	"Pipelines Not Detected": "Aucun pipelines détecté",
+	"Pipelines Valves": "Vannes de pipelines",
+	"Plain text (.txt)": "Texte simple (.txt)",
+	"Playground": "Playground",
+	"Please carefully review the following warnings:": "Veuillez lire attentivement les avertissements suivants :",
+	"Please enter a prompt": "Veuillez saisir un prompt",
+	"Please fill in all fields.": "Veuillez remplir tous les champs.",
+	"Please select a reason": "Veuillez sélectionner une raison",
+	"Positive attitude": "Attitude positive",
+	"Previous 30 days": "30 derniers jours",
+	"Previous 7 days": "7 derniers jours",
+	"Profile Image": "Image de profil",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (par ex. Dites-moi un fait amusant à propos de l'Empire romain)",
+	"Prompt Content": "Contenu du prompt",
+	"Prompt suggestions": "Suggestions pour le prompt",
+	"Prompts": "Prompts",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Récupérer « {{searchValue}} » depuis Ollama.com",
+	"Pull a model from Ollama.com": "Télécharger un modèle depuis Ollama.com",
+	"Query Params": "Paramètres de requête",
+	"RAG Template": "Modèle RAG",
+	"Rating": "Note",
+	"Re-rank models by topic similarity": "Reclasser les modèles par similarité de sujet",
+	"Read Aloud": "Lire à haute voix",
+	"Record voice": "Enregistrer la voix",
+	"Redirecting you to OpenWebUI Community": "Redirection vers la communauté OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Désignez-vous comme « Utilisateur » (par ex. « L'utilisateur apprend l'espagnol »)",
+	"References from": "Références de",
+	"Refused when it shouldn't have": "Refusé alors qu'il n'aurait pas dû l'être",
+	"Regenerate": "Regénérer",
+	"Release Notes": "Notes de mise à jour",
+	"Relevance": "Pertinence",
+	"Remove": "Retirer",
+	"Remove Model": "Retirer le modèle",
+	"Rename": "Renommer",
+	"Repeat Last N": "Répéter les N derniers",
+	"Request Mode": "Mode de requête",
+	"Reranking Model": "Modèle de ré-ranking",
+	"Reranking model disabled": "Modèle de ré-ranking désactivé",
+	"Reranking model set to \"{{reranking_model}}\"": "Modèle de ré-ranking défini sur « {{reranking_model}} »",
+	"Reset": "Réinitialiser",
+	"Reset Upload Directory": "Réinitialiser le répertoire de téléchargement",
+	"Reset Vector Storage/Knowledge": "Réinitialiser le stockage vectoriel/connaissances",
+	"Response AutoCopy to Clipboard": "Copie automatique de la réponse vers le presse-papiers",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Les notifications de réponse ne peuvent pas être activées car les autorisations du site web ont été refusées. Veuillez vérifier les paramètres de votre navigateur pour accorder l'accès nécessaire.",
+	"Response splitting": "Fractionnement de la réponse",
+	"Result": "Résultat",
+	"Rich Text Input for Chat": "Saisie de texte enrichi pour le chat",
+	"RK": "Rang",
+	"Role": "Rôle",
+	"Rosé Pine": "Pin rosé",
+	"Rosé Pine Dawn": "Aube de Pin Rosé",
+	"RTL": "RTL",
+	"Run": "Exécuter",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Exécutez Llama 2, Code Llama et d'autres modèles. Personnalisez et créez votre propre modèle.",
+	"Running": "Exécution",
+	"Save": "Enregistrer",
+	"Save & Create": "Enregistrer & Créer",
+	"Save & Update": "Enregistrer & Mettre à jour",
+	"Save As Copy": "Enregistrer comme copie",
+	"Save Tag": "Enregistrer le tag",
+	"Saved": "Enregistré",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "La sauvegarde des journaux de conversation directement dans le stockage de votre navigateur n'est plus prise en charge. Veuillez prendre un instant pour télécharger et supprimer vos journaux de conversation en cliquant sur le bouton ci-dessous. Ne vous inquiétez pas, vous pouvez facilement réimporter vos journaux de conversation dans le backend via",
+	"Scroll to bottom when switching between branches": "Défiler vers le bas lors du passage d'une branche à l'autre",
+	"Search": "Recherche",
+	"Search a model": "Rechercher un modèle",
+	"Search Chats": "Rechercher des conversations",
+	"Search Collection": "Rechercher une collection",
+	"search for tags": "Rechercher des tags",
+	"Search Functions": "Rechercher des fonctions",
+	"Search Knowledge": "Rechercher des connaissances",
+	"Search Models": "Rechercher des modèles",
+	"Search Prompts": "Rechercher des prompts",
+	"Search Query Generation Prompt": "Rechercher des prompts de génération de requête",
+	"Search Result Count": "Nombre de résultats de recherche",
+	"Search Tools": "Rechercher des outils",
+	"SearchApi API Key": "Clé API SearchApi",
+	"SearchApi Engine": "Moteur de recherche SearchApi",
+	"Searched {{count}} sites_one": "Recherché {{count}} site(s)_one",
+	"Searched {{count}} sites_many": "Recherché {{count}} sites_many",
+	"Searched {{count}} sites_other": "Recherché {{count}} sites_autres",
+	"Searching \"{{searchQuery}}\"": "Recherche de « {{searchQuery}} »",
+	"Searching Knowledge for \"{{searchQuery}}\"": "Recherche des connaissances pour « {{searchQuery}} »",
+	"Searxng Query URL": "URL de recherche Searxng",
+	"See readme.md for instructions": "Voir le fichier readme.md pour les instructions",
+	"See what's new": "Découvrez les nouvelles fonctionnalités",
+	"Seed": "Seed",
+	"Select a base model": "Sélectionnez un modèle de base",
+	"Select a engine": "Sélectionnez un moteur",
+	"Select a file to view or drag and drop a file to upload": "Sélectionnez un fichier à afficher ou faites glisser et déposez un fichier à téléverser",
+	"Select a function": "Sélectionnez une fonction",
+	"Select a model": "Sélectionnez un modèle",
+	"Select a pipeline": "Sélectionnez un pipeline",
+	"Select a pipeline url": "Sélectionnez l'URL du pipeline",
+	"Select a tool": "Sélectionnez un outil",
+	"Select an Ollama instance": "Sélectionnez une instance Ollama",
+	"Select Engine": "Sélectionnez le moteur",
+	"Select Knowledge": "Sélectionnez une connaissance",
+	"Select model": "Sélectionner un modèle",
+	"Select only one model to call": "Sélectionnez seulement un modèle pour appeler",
+	"Selected model(s) do not support image inputs": "Les modèle(s) sélectionné(s) ne prennent pas en charge les entrées d'images",
+	"Semantic distance to query": "Distance sémantique à la requête",
+	"Send": "Envoyer",
+	"Send a Message": "Envoyer un message",
+	"Send message": "Envoyer un message",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "Envoie `stream_options: { include_usage: true }` dans la requête.\nLes fournisseurs pris en charge renverront des informations sur l'utilisation des tokens dans la réponse lorsque cette option est activée.",
+	"September": "Septembre",
+	"Serper API Key": "Clé API Serper",
+	"Serply API Key": "Clé API Serply",
+	"Serpstack API Key": "Clé API Serpstack",
+	"Server connection verified": "Connexion au serveur vérifiée",
+	"Set as default": "Définir comme valeur par défaut",
+	"Set CFG Scale": "Définir la CFG",
+	"Set Default Model": "Définir le modèle par défaut",
+	"Set embedding model (e.g. {{model}})": "Définir le modèle d'embedding (par ex. {{model}})",
+	"Set Image Size": "Définir la taille de l'image",
+	"Set reranking model (e.g. {{model}})": "Définir le modèle de ré-ranking (par ex. {{model}})",
+	"Set Sampler": "Définir le sampler",
+	"Set Scheduler": "Définir le planificateur",
+	"Set Steps": "Définir le nombre d'étapes",
+	"Set Task Model": "Définir le modèle de tâche",
+	"Set Voice": "Choisir la voix",
+	"Set whisper model": "Choisir le modèle Whisper",
+	"Settings": "Paramètres",
+	"Settings saved successfully!": "Paramètres enregistrés avec succès !",
+	"Share": "Partager",
+	"Share Chat": "Partage de conversation",
+	"Share to OpenWebUI Community": "Partager avec la communauté OpenWebUI",
+	"short-summary": "résumé concis",
+	"Show": "Afficher",
+	"Show Admin Details in Account Pending Overlay": "Afficher les coordonnées de l'administrateur aux comptes en attente",
+	"Show Model": "Afficher le modèle",
+	"Show shortcuts": "Afficher les raccourcis",
+	"Show your support!": "Montrez votre soutien !",
+	"Showcased creativity": "Créativité mise en avant",
+	"Sign in": "Connexion",
+	"Sign in to {{WEBUI_NAME}}": "Connectez-vous à {{WEBUI_NAME}}",
+	"Sign Out": "Déconnexion",
+	"Sign up": "Inscrivez-vous",
+	"Sign up to {{WEBUI_NAME}}": "Inscrivez-vous à {{WEBUI_NAME}}",
+	"Signing in to {{WEBUI_NAME}}": "Connexion à {{WEBUI_NAME}}",
+	"Source": "Source",
+	"Speech Playback Speed": "Vitesse de lecture de la parole",
+	"Speech recognition error: {{error}}": "Erreur de reconnaissance vocale\u00a0: {{error}}",
+	"Speech-to-Text Engine": "Moteur de reconnaissance vocale",
+	"Stop": "Stop",
+	"Stop Sequence": "Séquence d'arrêt",
+	"Stream Chat Response": "Streamer la réponse de la conversation",
+	"STT Model": "Modèle de Speech-to-Text",
+	"STT Settings": "Paramètres de Speech-to-Text",
+	"Subtitle (e.g. about the Roman Empire)": "Sous-titres (par ex. sur l'Empire romain)",
+	"Success": "Réussite",
+	"Successfully updated.": "Mise à jour réussie.",
+	"Suggested": "Suggéré",
+	"Support": "Supporter",
+	"Support this plugin:": "Supporter ce module",
+	"Sync directory": "Synchroniser le répertoire",
+	"System": "Système",
+	"System Instructions": "Instructions système",
+	"System Prompt": "Prompt système",
+	"Tags": "Tags",
+	"Tags Generation Prompt": "Prompt de génération de tags",
+	"Tap to interrupt": "Appuyez pour interrompre",
+	"Tavily API Key": "Clé API Tavily",
+	"Tell us more:": "Dites-nous en plus à ce sujet : ",
+	"Temperature": "Température",
+	"Template": "Template",
+	"Temporary Chat": "Chat éphémère",
+	"Text Splitter": "Text Splitter",
+	"Text-to-Speech Engine": "Moteur de Text-to-Speech",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Merci pour vos commentaires !",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Les développeurs de ce plugin sont des bénévoles passionnés issus de la communauté. Si vous trouvez ce plugin utile, merci de contribuer à son développement.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "Le classement d'évaluation est basé sur le système de notation Elo et est mis à jour en temps réel.",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "Le classement est actuellement en version bêta et nous pouvons ajuster les calculs de notation à mesure que nous peaufinons l'algorithme.",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "La taille maximale du fichier en Mo. Si la taille du fichier dépasse cette limite, le fichier ne sera pas téléchargé.",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "Le nombre maximal de fichiers pouvant être utilisés en même temps dans la conversation. Si le nombre de fichiers dépasse cette limite, les fichiers ne seront pas téléchargés.",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Le score doit être une valeur comprise entre 0,0 (0\u00a0%) et 1,0 (100\u00a0%).",
+	"Theme": "Thème",
+	"Thinking...": "En train de réfléchir...",
+	"This action cannot be undone. Do you wish to continue?": "Cette action ne peut pas être annulée. Souhaitez-vous continuer ?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Cela garantit que vos conversations précieuses soient sauvegardées en toute sécurité dans votre base de données backend. Merci !",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Il s'agit d'une fonctionnalité expérimentale, elle peut ne pas fonctionner comme prévu et est sujette à modification à tout moment.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Cette option supprimera tous les fichiers existants dans la collection et les remplacera par les fichiers nouvellement téléchargés.",
+	"This response was generated by \"{{model}}\"": "Cette réponse a été générée par \"{{model}}\"",
+	"This will delete": "Cela supprimera",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "Cela supprimera <strong>{{NAME}}</strong> et <strong>tout son contenu</strong>.",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "Cela réinitialisera la base de connaissances et synchronisera tous les fichiers. Souhaitez-vous continuer ?",
+	"Thorough explanation": "Explication approfondie",
+	"Tika": "Tika",
+	"Tika Server URL required.": "URL du serveur Tika requise.",
+	"Tiktoken": "Tiktoken",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Conseil\u00a0: mettez à jour plusieurs emplacements de variables consécutivement en appuyant sur la touche Tab dans l’entrée de chat après chaque remplacement.",
+	"Title": "Titre",
+	"Title (e.g. Tell me a fun fact)": "Titre (par ex. raconte-moi un fait amusant)",
+	"Title Auto-Generation": "Génération automatique des titres",
+	"Title cannot be an empty string.": "Le titre ne peut pas être une chaîne de caractères vide.",
+	"Title Generation Prompt": "Prompt de génération de titre",
+	"To access the available model names for downloading,": "Pour accéder aux noms des modèles disponibles,",
+	"To access the GGUF models available for downloading,": "Pour accéder aux modèles GGUF disponibles,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Pour accéder à l'interface Web, veuillez contacter l'administrateur. Les administrateurs peuvent gérer les statuts des utilisateurs depuis le panneau d'administration.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "Pour attacher une base de connaissances ici, ajoutez-les d'abord à l'espace de travail « Connaissances ».",
+	"to chat input.": "Vers la zone de chat.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "Pour protéger votre confidentialité, seules les notes, les identifiants de modèle, les tags et les métadonnées de vos commentaires sont partagés. Vos journaux de discussion restent privés et ne sont pas inclus.",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Pour sélectionner des actions ici, ajoutez-les d'abord à l'espace de travail « Fonctions ».",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Pour sélectionner des filtres ici, ajoutez-les d'abord à l'espace de travail « Fonctions ». ",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Pour sélectionner des outils ici, ajoutez-les d'abord à l'espace de travail « Outils ». ",
+	"Toast notifications for new updates": "Notifications toast pour les nouvelles mises à jour",
+	"Today": "Aujourd'hui",
+	"Toggle settings": "Afficher/masquer les paramètres",
+	"Toggle sidebar": "Afficher/masquer la barre latérale",
+	"Token": "Token",
+	"Tokens To Keep On Context Refresh (num_keep)": "Tokens à conserver lors du rafraîchissement du contexte (num_keep)",
+	"Too verbose": "Trop détaillé",
+	"Tool": "Outil",
+	"Tool created successfully": "L'outil a été créé avec succès",
+	"Tool deleted successfully": "Outil supprimé avec succès",
+	"Tool imported successfully": "Outil importé avec succès",
+	"Tool updated successfully": "L'outil a été mis à jour avec succès",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Description du toolkit (par ex. un toolkit permettant d'effectuer diverses opérations)",
+	"Toolkit ID (e.g. my_toolkit)": "ID du Toolkit (par ex. mon_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Nom du toolkit (par ex. Mon Toolkit)",
+	"Tools": "Outils",
+	"Tools are a function calling system with arbitrary code execution": "Les outils sont un système d'appel de fonction avec exécution de code arbitraire",
+	"Tools have a function calling system that allows arbitrary code execution": "Les outils ont un système d'appel de fonction qui permet l'exécution de code arbitraire",
+	"Tools have a function calling system that allows arbitrary code execution.": "Les outils ont un système d'appel de fonction qui permet l'exécution de code arbitraire.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problèmes d'accès à Ollama ?",
+	"TTS Model": "Modèle de Text-to-Speech",
+	"TTS Settings": "Paramètres de Text-to-Speech",
+	"TTS Voice": "Voix de Text-to-Speech",
+	"Type": "Type",
+	"Type Hugging Face Resolve (Download) URL": "Entrez l'URL de Téléchargement Hugging Face Resolve",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Oh non ! Un problème est survenu lors de la connexion à {{provider}}.",
+	"UI": "UI",
+	"Unpin": "Désépingler",
+	"Untagged": "Pas de tag",
+	"Update": "Mise à jour",
+	"Update and Copy Link": "Mettre à jour et copier le lien",
+	"Update for the latest features and improvements.": "Mettez à jour pour bénéficier des dernières fonctionnalités et améliorations.",
+	"Update password": "Mettre à jour le mot de passe",
+	"Updated": "Mis à jour",
+	"Updated at": "Mise à jour le",
+	"Updated At": "Mise à jour le",
+	"Upload": "Téléverser",
+	"Upload a GGUF model": "Téléverser un modèle GGUF",
+	"Upload directory": "Téléverser un dossier",
+	"Upload files": "Téléverser des fichiers",
+	"Upload Files": "Téléverser des fichiers",
+	"Upload Pipeline": "Pipeline de téléchargement",
+	"Upload Progress": "Progression de l'envoi",
+	"URL Mode": "Mode d'URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "Utilisez '#' dans la zone de saisie du prompt pour charger et inclure vos connaissances.",
+	"Use Gravatar": "Utiliser Gravatar",
+	"Use Initials": "Utiliser les initiales",
+	"use_mlock (Ollama)": "Utiliser mlock (Ollama)",
+	"use_mmap (Ollama)": "Utiliser mmap (Ollama)",
+	"user": "utilisateur",
+	"User": "Utilisateur",
+	"User location successfully retrieved.": "L'emplacement de l'utilisateur a été récupéré avec succès.",
+	"User Permissions": "Permissions utilisateur",
+	"Users": "Utilisateurs",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "Utilisation du modèle d'arène par défaut avec tous les modèles. Cliquez sur le bouton plus pour ajouter des modèles personnalisés.",
+	"Utilize": "Utilisez",
+	"Valid time units:": "Unités de temps valides\u00a0:",
+	"Valves": "Vannes",
+	"Valves updated": "Vannes mises à jour",
+	"Valves updated successfully": "Les vannes ont été mises à jour avec succès",
+	"variable": "variable",
+	"variable to have them replaced with clipboard content.": "variable pour qu'elles soient remplacées par le contenu du presse-papiers.",
+	"Version": "version:",
+	"Version {{selectedVersion}} of {{totalVersions}}": "Version {{selectedVersion}} de {{totalVersions}}",
+	"Voice": "Voix",
+	"Voice Input": "Saisie vocale",
+	"Warning": "Avertissement",
+	"Warning:": "Avertissement :",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Avertissement : Si vous mettez à jour ou modifiez votre modèle d'embedding, vous devrez réimporter tous les documents.",
+	"Web": "Web",
+	"Web API": "API Web",
+	"Web Loader Settings": "Paramètres du Web Loader",
+	"Web Search": "Recherche Web",
+	"Web Search Engine": "Moteur de recherche Web",
+	"Webhook URL": "URL du webhook",
+	"WebUI Settings": "Paramètres de WebUI",
+	"WebUI will make requests to": "WebUI effectuera des requêtes vers",
+	"What’s New in": "Quoi de neuf dans",
+	"Whisper (Local)": "Whisper (local)",
+	"Widescreen Mode": "Mode grand écran",
+	"Won": "Gagné",
+	"Workspace": "Espace de travail",
+	"Write a prompt suggestion (e.g. Who are you?)": "Écrivez une suggestion de prompt (par exemple : Qui êtes-vous ?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Rédigez un résumé de 50 mots qui résume [sujet ou mot-clé].",
+	"Write something...": "Écrivez quelque chose...",
+	"Yesterday": "Hier",
+	"You": "Vous",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Vous ne pouvez discuter qu'avec un maximum de {{maxCount}} fichier(s) à la fois.",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Vous pouvez personnaliser vos interactions avec les LLM en ajoutant des mémoires à l'aide du bouton « Gérer » ci-dessous, ce qui les rendra plus utiles et mieux adaptées à vos besoins.",
+	"You cannot clone a base model": "Vous ne pouvez pas cloner un modèle de base",
+	"You cannot upload an empty file.": "Vous ne pouvez pas envoyer un fichier vide.",
+	"You have no archived conversations.": "Vous n'avez aucune conversation archivée.",
+	"You have shared this chat": "Vous avez partagé cette conversation.",
+	"You're a helpful assistant.": "Vous êtes un assistant efficace.",
+	"You're now logged in.": "Vous êtes désormais connecté.",
+	"Your account status is currently pending activation.": "Votre statut de compte est actuellement en attente d'activation.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "L'intégralité de votre contribution ira directement au développeur du plugin ; Open WebUI ne prend aucun pourcentage. Cependant, la plateforme de financement choisie peut avoir ses propres frais.",
+	"Youtube": "YouTube",
+	"Youtube Loader Settings": "Paramètres de l'outil de téléchargement YouTube"
+}
diff --git a/src/lib/i18n/locales/he-IL/translation.json b/src/lib/i18n/locales/he-IL/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..83925f15e2cbb906e88c0fdf748f9a4e9b220d19
--- /dev/null
+++ b/src/lib/i18n/locales/he-IL/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' או '-1' ללא תפוגה.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(למשל `sh webui.sh --api`)",
+	"(latest)": "(האחרון)",
+	"{{ models }}": "{{ דגמים }}",
+	"{{ owner }}: You cannot delete a base model": "{{ בעלים }}: לא ניתן למחוק מודל בסיס",
+	"{{user}}'s Chats": "צ'אטים של {{user}}",
+	"{{webUIName}} Backend Required": "נדרש Backend של {{webUIName}}",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "מודל משימה משמש בעת ביצוע משימות כגון יצירת כותרות עבור צ'אטים ושאילתות חיפוש באינטרנט",
+	"a user": "משתמש",
+	"About": "אודות",
+	"Account": "חשבון",
+	"Account Activation Pending": "",
+	"Accurate information": "מידע מדויק",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "הוסף",
+	"Add a model id": "הוספת מזהה דגם",
+	"Add a short description about what this model does": "הוסף תיאור קצר אודות אופן הפעולה של מודל זה",
+	"Add a short title for this prompt": "הוסף כותרת קצרה לפקודה זו",
+	"Add a tag": "הוסף תג",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "הוסף פקודה מותאמת אישית",
+	"Add Files": "הוסף קבצים",
+	"Add Memory": "הוסף זיכרון",
+	"Add Model": "הוסף מודל",
+	"Add Tag": "",
+	"Add Tags": "הוסף תגים",
+	"Add text content": "",
+	"Add User": "הוסף משתמש",
+	"Adjusting these settings will apply changes universally to all users.": "התאמת הגדרות אלו תחול על כל המשתמשים.",
+	"admin": "מנהל",
+	"Admin": "",
+	"Admin Panel": "לוח בקרה למנהל",
+	"Admin Settings": "הגדרות מנהל",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "פרמטרים מתקדמים",
+	"Advanced Params": "פרמטרים מתקדמים",
+	"All chats": "",
+	"All Documents": "כל המסמכים",
+	"Allow Chat Deletion": "אפשר מחיקת צ'אט",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "תווים אלפאנומריים ומקפים",
+	"Already have an account?": "כבר יש לך חשבון?",
+	"an assistant": "עוזר",
+	"and": "וגם",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "וצור קישור משותף חדש.",
+	"API Base URL": "כתובת URL בסיסית ל-API",
+	"API Key": "מפתח API",
+	"API Key created.": "מפתח API נוצר.",
+	"API keys": "מפתחות API",
+	"April": "אפריל",
+	"Archive": "ארכיון",
+	"Archive All Chats": "אחסן בארכיון את כל הצ'אטים",
+	"Archived Chats": "צ'אטים מאורכבים",
+	"are allowed - Activate this command by typing": "מותרים - הפעל פקודה זו על ידי הקלדה",
+	"Are you sure?": "האם אתה בטוח?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "צרף קובץ",
+	"Attention to detail": "תשומת לב לפרטים",
+	"Audio": "אודיו",
+	"August": "אוגוסט",
+	"Auto-playback response": "תגובת השמעה אוטומטית",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "כתובת URL בסיסית של AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "נדרשת כתובת URL בסיסית של AUTOMATIC1111",
+	"Available list": "",
+	"available!": "זמין!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "חזור",
+	"Bad Response": "תגובה שגויה",
+	"Banners": "באנרים",
+	"Base Model (From)": "דגם בסיס (מ)",
+	"Batch Size (num_batch)": "",
+	"before": "לפני",
+	"Being lazy": "להיות עצלן",
+	"Brave Search API Key": "מפתח API של חיפוש אמיץ",
+	"Bypass SSL verification for Websites": "עקוף אימות SSL עבור אתרים",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "בטל",
+	"Capabilities": "יכולות",
+	"Change Password": "שנה סיסמה",
+	"Character": "",
+	"Chat": "צ'אט",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "UI של תיבת הדיבור",
+	"Chat Controls": "",
+	"Chat direction": "כיוון צ'אט",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "צ'אטים",
+	"Check Again": "בדוק שוב",
+	"Check for updates": "בדוק עדכונים",
+	"Checking for updates...": "בודק עדכונים...",
+	"Choose a model before saving...": "בחר מודל לפני השמירה...",
+	"Chunk Overlap": "חפיפת נתונים",
+	"Chunk Params": "פרמטרי נתונים",
+	"Chunk Size": "גודל נתונים",
+	"Citation": "ציטוט",
+	"Clear memory": "",
+	"Click here for help.": "לחץ כאן לעזרה.",
+	"Click here to": "לחץ כאן כדי",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "לחץ כאן לבחירה",
+	"Click here to select a csv file.": "לחץ כאן לבחירת קובץ csv.",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "לחץ כאן.",
+	"Click on the user role button to change a user's role.": "לחץ על כפתור תפקיד המשתמש כדי לשנות את תפקיד המשתמש.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "שיבוט",
+	"Close": "סגור",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "אוסף",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "כתובת URL בסיסית של ComfyUI",
+	"ComfyUI Base URL is required.": "נדרשת כתובת URL בסיסית של ComfyUI",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "פקודה",
+	"Completions": "",
+	"Concurrent Requests": "בקשות בו-זמניות",
+	"Confirm": "",
+	"Confirm Password": "אשר סיסמה",
+	"Confirm your action": "",
+	"Connections": "חיבורים",
+	"Contact Admin for WebUI Access": "",
+	"Content": "תוכן",
+	"Content Extraction": "",
+	"Context Length": "אורך הקשר",
+	"Continue Response": "המשך תגובה",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "העתקת כתובת URL של צ'אט משותף ללוח!",
+	"Copied to clipboard": "",
+	"Copy": "העתק",
+	"Copy last code block": "העתק את בלוק הקוד האחרון",
+	"Copy last response": "העתק את התגובה האחרונה",
+	"Copy Link": "העתק קישור",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "ההעתקה ללוח הייתה מוצלחת!",
+	"Create a model": "יצירת מודל",
+	"Create Account": "צור חשבון",
+	"Create Knowledge": "",
+	"Create new key": "צור מפתח חדש",
+	"Create new secret key": "צור מפתח סודי חדש",
+	"Created at": "נוצר ב",
+	"Created At": "נוצר ב",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "המודל הנוכחי",
+	"Current Password": "הסיסמה הנוכחית",
+	"Custom": "מותאם אישית",
+	"Customize models for a specific purpose": "התאמה אישית של מודלים למטרה ספציפית",
+	"Dark": "כהה",
+	"Dashboard": "",
+	"Database": "מסד נתונים",
+	"December": "דצמבר",
+	"Default": "ברירת מחדל",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "ברירת מחדל (SentenceTransformers)",
+	"Default Model": "מודל ברירת מחדל",
+	"Default model updated": "המודל המוגדר כברירת מחדל עודכן",
+	"Default Prompt Suggestions": "הצעות ברירת מחדל לפקודות",
+	"Default User Role": "תפקיד משתמש ברירת מחדל",
+	"Delete": "מחק",
+	"Delete a model": "מחק מודל",
+	"Delete All Chats": "מחק את כל הצ'אטים",
+	"Delete chat": "מחק צ'אט",
+	"Delete Chat": "מחק צ'אט",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "מחק את הקישור הזה",
+	"Delete tool?": "",
+	"Delete User": "מחק משתמש",
+	"Deleted {{deleteModelTag}}": "נמחק {{deleteModelTag}}",
+	"Deleted {{name}}": "נמחק {{name}}",
+	"Description": "תיאור",
+	"Didn't fully follow instructions": "לא עקב אחרי ההוראות באופן מלא",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "גלה מודל",
+	"Discover a prompt": "גלה פקודה",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "גלה, הורד, וחקור פקודות מותאמות אישית",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "גלה, הורד, וחקור הגדרות מודל מוגדרות מראש",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "הצג את שם המשתמש במקום 'אתה' בצ'אט",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "מסמך",
+	"Documentation": "",
+	"Documents": "מסמכים",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "לא מבצע חיבורים חיצוניים, והנתונים שלך נשמרים באופן מאובטח בשרת המקומי שלך.",
+	"Don't have an account?": "אין לך חשבון?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "לא אוהב את הסגנון",
+	"Done": "",
+	"Download": "הורד",
+	"Download canceled": "ההורדה בוטלה",
+	"Download Database": "הורד מסד נתונים",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "גרור כל קובץ לכאן כדי להוסיף לשיחה",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "למשל '30s', '10m'. יחידות זמן חוקיות הן 's', 'm', 'h'.",
+	"Edit": "ערוך",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "ערוך משתמש",
+	"ElevenLabs": "",
+	"Email": "דוא\"ל",
+	"Embedding Batch Size": "",
+	"Embedding Model": "מודל הטמעה",
+	"Embedding Model Engine": "מנוע מודל הטמעה",
+	"Embedding model set to \"{{embedding_model}}\"": "מודל ההטמעה הוגדר ל-\"{{embedding_model}}\"",
+	"Enable Community Sharing": "הפיכת שיתוף קהילה לזמין",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "אפשר הרשמות חדשות",
+	"Enable Web Search": "הפיכת חיפוש באינטרנט לזמין",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "ודא שקובץ ה-CSV שלך כולל 4 עמודות בסדר הבא: שם, דוא\"ל, סיסמה, תפקיד.",
+	"Enter {{role}} message here": "הזן הודעת {{role}} כאן",
+	"Enter a detail about yourself for your LLMs to recall": "הזן פרטים על עצמך כדי שLLMs יזכור",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "הזן מפתח API של חיפוש אמיץ",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "הזן חפיפת נתונים",
+	"Enter Chunk Size": "הזן גודל נתונים",
+	"Enter description": "",
+	"Enter Github Raw URL": "הזן כתובת URL של Github Raw",
+	"Enter Google PSE API Key": "הזן מפתח API של Google PSE",
+	"Enter Google PSE Engine Id": "הזן את מזהה מנוע PSE של Google",
+	"Enter Image Size (e.g. 512x512)": "הזן גודל תמונה (למשל 512x512)",
+	"Enter language codes": "הזן קודי שפה",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "הזן תג מודל (למשל {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "הזן מספר שלבים (למשל 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "הזן ציון",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "הזן כתובת URL של שאילתת Searxng",
+	"Enter Serper API Key": "הזן מפתח API של Serper",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "הזן מפתח API של Serpstack",
+	"Enter stop sequence": "הזן רצף עצירה",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "הזן Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "הזן כתובת URL (למשל http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "הזן כתובת URL (למשל http://localhost:11434)",
+	"Enter Your Email": "הזן את דוא\"ל שלך",
+	"Enter Your Full Name": "הזן את שמך המלא",
+	"Enter your message": "",
+	"Enter Your Password": "הזן את הסיסמה שלך",
+	"Enter Your Role": "הזן את התפקיד שלך",
+	"Error": "שגיאה",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "ניסיוני",
+	"Export": "ייצא",
+	"Export All Chats (All Users)": "ייצוא כל הצ'אטים (כל המשתמשים)",
+	"Export chat (.json)": "",
+	"Export Chats": "ייצוא צ'אטים",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "ייצוא מודלים",
+	"Export Prompts": "ייצוא פקודות",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "יצירת מפתח API נכשלה.",
+	"Failed to read clipboard contents": "קריאת תוכן הלוח נכשלה",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "פברואר",
+	"Feedback History": "",
+	"Feel free to add specific details": "נא להוסיף פרטים ספציפיים לפי רצון",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "מצב קובץ",
+	"File not found.": "הקובץ לא נמצא.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "התגלתה הזיית טביעת אצבע: לא ניתן להשתמש בראשי תיבות כאווטאר. משתמש בתמונת פרופיל ברירת מחדל.",
+	"Fluidly stream large external response chunks": "שידור נתונים חיצוניים בקצב רציף",
+	"Focus chat input": "מיקוד הקלט לצ'אט",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "עקב אחר ההוראות במושלמות",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "עונש תדירות",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "כללי",
+	"General Settings": "הגדרות כלליות",
+	"Generate Image": "",
+	"Generating search query": "יצירת שאילתת חיפוש",
+	"Generation Info": "מידע על היצירה",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "תגובה טובה",
+	"Google PSE API Key": "מפתח API של Google PSE",
+	"Google PSE Engine Id": "מזהה מנוע PSE של Google",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "אין שיחות.",
+	"Hello, {{name}}": "שלום, {{name}}",
+	"Help": "עזרה",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "הסתר",
+	"Hide Model": "",
+	"How can I help you today?": "כיצד אוכל לעזור לך היום?",
+	"Hybrid Search": "חיפוש היברידי",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "יצירת תמונות (ניסיוני)",
+	"Image Generation Engine": "מנוע יצירת תמונות",
+	"Image Settings": "הגדרות תמונה",
+	"Images": "תמונות",
+	"Import Chats": "יבוא צ'אטים",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "ייבוא דגמים",
+	"Import Prompts": "יבוא פקודות",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "כלול את הדגל `--api` בעת הרצת stable-diffusion-webui",
+	"Info": "מידע",
+	"Input commands": "פקודות קלט",
+	"Install from Github URL": "התקן מכתובת URL של Github",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "ממשק",
+	"Invalid file format.": "",
+	"Invalid Tag": "תג לא חוקי",
+	"January": "ינואר",
+	"join our Discord for help.": "הצטרף ל-Discord שלנו לעזרה.",
+	"JSON": "JSON",
+	"JSON Preview": "תצוגה מקדימה של JSON",
+	"July": "יולי",
+	"June": "יוני",
+	"JWT Expiration": "תפוגת JWT",
+	"JWT Token": "אסימון JWT",
+	"Keep Alive": "השאר פעיל",
+	"Keyboard shortcuts": "קיצורי מקלדת",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "שפה",
+	"large language models, locally.": "",
+	"Last Active": "פעיל לאחרונה",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "בהיר",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "מודלים בשפה טבעית יכולים לטעות. אמת מידע חשוב.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "נוצר על ידי קהילת OpenWebUI",
+	"Make sure to enclose them with": "ודא להקיף אותם עם",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "נהל מודלים",
+	"Manage Ollama Models": "נהל מודלים של Ollama",
+	"Manage Pipelines": "ניהול צינורות",
+	"March": "מרץ",
+	"Max Tokens (num_predict)": "מקסימום אסימונים (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "ניתן להוריד מקסימום 3 מודלים בו זמנית. אנא נסה שוב מאוחר יותר.",
+	"May": "מאי",
+	"Memories accessible by LLMs will be shown here.": "מזכירים נגישים על ידי LLMs יוצגו כאן.",
+	"Memory": "זיכרון",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "הודעות שתשלח לאחר יצירת הקישור לא ישותפו. משתמשים עם כתובת האתר יוכלו לצפות בצ'אט המשותף.",
+	"Min P": "",
+	"Minimum Score": "ציון מינימלי",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "DD בMMMM, YYYY",
+	"MMMM DD, YYYY HH:mm": "DD בMMMM, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "המודל '{{modelName}}' הורד בהצלחה.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "המודל '{{modelTag}}' כבר בתור להורדה.",
+	"Model {{modelId}} not found": "המודל {{modelId}} לא נמצא",
+	"Model {{modelName}} is not vision capable": "דגם {{modelName}} אינו בעל יכולת ראייה",
+	"Model {{name}} is now {{status}}": "דגם {{name}} הוא כעת {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "נתיב מערכת הקבצים של המודל זוהה. נדרש שם קצר של המודל לעדכון, לא ניתן להמשיך.",
+	"Model ID": "מזהה דגם",
+	"Model Name": "",
+	"Model not selected": "לא נבחר מודל",
+	"Model Params": "פרמס מודל",
+	"Model updated successfully": "",
+	"Model Whitelisting": "רישום לבן של מודלים",
+	"Model(s) Whitelisted": "מודלים שנכללו ברשימה הלבנה",
+	"Modelfile Content": "תוכן קובץ מודל",
+	"Models": "מודלים",
+	"more": "",
+	"More": "עוד",
+	"Move to Top": "",
+	"Name": "שם",
+	"Name your model": "תן שם לדגם שלך",
+	"New Chat": "צ'אט חדש",
+	"New folder": "",
+	"New Password": "סיסמה חדשה",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "לא נמצאו תוצאות",
+	"No search query generated": "לא נוצרה שאילתת חיפוש",
+	"No source available": "אין מקור זמין",
+	"No valves to update": "",
+	"None": "ללא",
+	"Not factually correct": "לא נכון מבחינה עובדתית",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "הערה: אם תקבע ציון מינימלי, החיפוש יחזיר רק מסמכים עם ציון שגבוה או שווה לציון המינימלי.",
+	"Notes": "",
+	"Notifications": "התראות",
+	"November": "נובמבר",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "",
+	"October": "אוקטובר",
+	"Off": "כבוי",
+	"Okay, Let's Go!": "בסדר, בואו נתחיל!",
+	"OLED Dark": "OLED כהה",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API מושבת",
+	"Ollama API is disabled": "",
+	"Ollama Version": "גרסת Ollama",
+	"On": "פועל",
+	"Only": "רק",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "רק תווים אלפאנומריים ומקפים מותרים במחרוזת הפקודה.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "אופס! נראה שהכתובת URL אינה תקינה. אנא בדוק שוב ונסה שנית.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "אופס! אתה משתמש בשיטה לא נתמכת (רק חזית). אנא שרת את ממשק המשתמש האינטרנטי מהשרת האחורי.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "פתח צ'אט חדש",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API של OpenAI",
+	"OpenAI API Config": "תצורת API של OpenAI",
+	"OpenAI API Key is required.": "נדרש מפתח API של OpenAI.",
+	"OpenAI URL/Key required.": "נדרשת כתובת URL/מפתח של OpenAI.",
+	"or": "או",
+	"Other": "אחר",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "סיסמה",
+	"PDF document (.pdf)": "מסמך PDF (.pdf)",
+	"PDF Extract Images (OCR)": "חילוץ תמונות מ-PDF (OCR)",
+	"pending": "ממתין",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "ההרשאה נדחתה בעת גישה למיקרופון: {{error}}",
+	"Personalization": "תאור",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "צינורות",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "צינורות שסתומים",
+	"Plain text (.txt)": "טקסט פשוט (.txt)",
+	"Playground": "אזור משחקים",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "גישה חיובית",
+	"Previous 30 days": "30 הימים הקודמים",
+	"Previous 7 days": "7 הימים הקודמים",
+	"Profile Image": "תמונת פרופיל",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "פקודה (למשל, ספר לי עובדה מעניינת על האימפריה הרומית)",
+	"Prompt Content": "תוכן הפקודה",
+	"Prompt suggestions": "הצעות לפקודות",
+	"Prompts": "פקודות",
+	"Pull \"{{searchValue}}\" from Ollama.com": "משוך \"{{searchValue}}\" מ-Ollama.com",
+	"Pull a model from Ollama.com": "משוך מודל מ-Ollama.com",
+	"Query Params": "פרמטרי שאילתה",
+	"RAG Template": "תבנית RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "קרא בקול",
+	"Record voice": "הקלט קול",
+	"Redirecting you to OpenWebUI Community": "מפנה אותך לקהילת OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "נדחה כאשר לא היה צריך",
+	"Regenerate": "הפק מחדש",
+	"Release Notes": "הערות שחרור",
+	"Relevance": "",
+	"Remove": "הסר",
+	"Remove Model": "הסר מודל",
+	"Rename": "שנה שם",
+	"Repeat Last N": "חזור על ה-N האחרונים",
+	"Request Mode": "מצב בקשה",
+	"Reranking Model": "מודל דירוג מחדש",
+	"Reranking model disabled": "מודל דירוג מחדש מושבת",
+	"Reranking model set to \"{{reranking_model}}\"": "מודל דירוג מחדש הוגדר ל-\"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "העתקה אוטומטית של תגובה ללוח",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "תפקיד",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "שמור",
+	"Save & Create": "שמור וצור",
+	"Save & Update": "שמור ועדכן",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "שמירת יומני צ'אט ישירות באחסון הדפדפן שלך אינה נתמכת יותר. אנא הקדש רגע להוריד ולמחוק את יומני הצ'אט שלך על ידי לחיצה על הכפתור למטה. אל דאגה, באפשרותך לייבא מחדש בקלות את יומני הצ'אט שלך לשרת האחורי דרך",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "חפש",
+	"Search a model": "חפש מודל",
+	"Search Chats": "חיפוש צ'אטים",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "חיפוש מודלים",
+	"Search Prompts": "חפש פקודות",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "ספירת תוצאות חיפוש",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "חיפש {{count}} sites_one",
+	"Searched {{count}} sites_two": "חיפש {{count}} sites_two",
+	"Searched {{count}} sites_other": "חיפש {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "כתובת URL של שאילתת Searxng",
+	"See readme.md for instructions": "ראה את readme.md להוראות",
+	"See what's new": "ראה מה חדש",
+	"Seed": "זרע",
+	"Select a base model": "בחירת מודל בסיס",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "בחר מודל",
+	"Select a pipeline": "בחר קו צינור",
+	"Select a pipeline url": "בחר כתובת URL של קו צינור",
+	"Select a tool": "",
+	"Select an Ollama instance": "בחר מופע של Ollama",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "בחר מודל",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "דגמים נבחרים אינם תומכים בקלט תמונה",
+	"Semantic distance to query": "",
+	"Send": "שלח",
+	"Send a Message": "שלח הודעה",
+	"Send message": "שלח הודעה",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "ספטמבר",
+	"Serper API Key": "מפתח Serper API",
+	"Serply API Key": "",
+	"Serpstack API Key": "מפתח API של Serpstack",
+	"Server connection verified": "החיבור לשרת אומת",
+	"Set as default": "הגדר כברירת מחדל",
+	"Set CFG Scale": "",
+	"Set Default Model": "הגדר מודל ברירת מחדל",
+	"Set embedding model (e.g. {{model}})": "הגדר מודל הטמעה (למשל {{model}})",
+	"Set Image Size": "הגדר גודל תמונה",
+	"Set reranking model (e.g. {{model}})": "הגדר מודל דירוג מחדש (למשל {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "הגדר שלבים",
+	"Set Task Model": "הגדרת מודל משימה",
+	"Set Voice": "הגדר קול",
+	"Set whisper model": "",
+	"Settings": "הגדרות",
+	"Settings saved successfully!": "ההגדרות נשמרו בהצלחה!",
+	"Share": "שתף",
+	"Share Chat": "שתף צ'אט",
+	"Share to OpenWebUI Community": "שתף לקהילת OpenWebUI",
+	"short-summary": "סיכום קצר",
+	"Show": "הצג",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "הצג קיצורי דרך",
+	"Show your support!": "",
+	"Showcased creativity": "הצגת יצירתיות",
+	"Sign in": "הירשם",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "התנתקות",
+	"Sign up": "הרשמה",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "מקור",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "שגיאת תחקור שמע: {{error}}",
+	"Speech-to-Text Engine": "מנוע תחקור שמע",
+	"Stop": "",
+	"Stop Sequence": "סידור עצירה",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "הגדרות חקירה של TTS",
+	"Subtitle (e.g. about the Roman Empire)": "תחקור (לדוגמה: על מעמד הרומי)",
+	"Success": "הצלחה",
+	"Successfully updated.": "עדכון הצלחה.",
+	"Suggested": "מומלץ",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "מערכת",
+	"System Instructions": "",
+	"System Prompt": "תגובת מערכת",
+	"Tags": "תגיות",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "תרשמו יותר:",
+	"Temperature": "טמפרטורה",
+	"Template": "תבנית",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "מנוע טקסט לדיבור",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "תודה על המשוב שלך!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "ציון צריך להיות ערך בין 0.0 (0%) ל-1.0 (100%)",
+	"Theme": "נושא",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "פעולה זו מבטיחה שהשיחות בעלות הערך שלך יישמרו באופן מאובטח במסד הנתונים העורפי שלך. תודה!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "תיאור מפורט",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "טיפ: עדכן חריצים משתנים מרובים ברציפות על-ידי לחיצה על מקש Tab בקלט הצ'אט לאחר כל החלפה.",
+	"Title": "שם",
+	"Title (e.g. Tell me a fun fact)": "שם (לדוגמה: תרגום)",
+	"Title Auto-Generation": "יצירת שם אוטומטית",
+	"Title cannot be an empty string.": "שם לא יכול להיות מחרוזת ריקה.",
+	"Title Generation Prompt": "שאלה ליצירת שם",
+	"To access the available model names for downloading,": "כדי לגשת לשמות הדגמים הזמינים להורדה,",
+	"To access the GGUF models available for downloading,": "כדי לגשת לדגמי GGUF הזמינים להורדה,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "לקלטת שיחה.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "היום",
+	"Toggle settings": "החלפת מצב של הגדרות",
+	"Toggle sidebar": "החלפת מצב של סרגל הצד",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "קשה לגשת לOllama?",
+	"TTS Model": "",
+	"TTS Settings": "הגדרות TTS",
+	"TTS Voice": "",
+	"Type": "סוג",
+	"Type Hugging Face Resolve (Download) URL": "הקלד כתובת URL של פתרון פנים מחבק (הורד)",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "או-הו! אירעה בעיה בהתחברות ל- {{provider}}.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "עדכן ושכפל קישור",
+	"Update for the latest features and improvements.": "",
+	"Update password": "עדכן סיסמה",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "העלה מודל GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "העלאת קבצים",
+	"Upload Pipeline": "",
+	"Upload Progress": "תקדמות העלאה",
+	"URL Mode": "מצב URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "שימוש ב Gravatar",
+	"Use Initials": "שימוש ב initials",
+	"use_mlock (Ollama)": "use_mlock (אולמה)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "משתמש",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "הרשאות משתמש",
+	"Users": "משתמשים",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "שימוש",
+	"Valid time units:": "יחידות זמן תקינות:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "משתנה",
+	"variable to have them replaced with clipboard content.": "משתנה להחליפו ב- clipboard תוכן.",
+	"Version": "גרסה",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "אזהרה",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "אזהרה: אם תעדכן או תשנה את מודל ההטבעה שלך, יהיה עליך לייבא מחדש את כל המסמכים.",
+	"Web": "רשת",
+	"Web API": "",
+	"Web Loader Settings": "הגדרות טעינת אתר",
+	"Web Search": "חיפוש באינטרנט",
+	"Web Search Engine": "מנוע חיפוש באינטרנט",
+	"Webhook URL": "URL Webhook",
+	"WebUI Settings": "הגדרות WebUI",
+	"WebUI will make requests to": "WebUI יבקש לבקש",
+	"What’s New in": "מה חדש ב",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "סביבה",
+	"Write a prompt suggestion (e.g. Who are you?)": "כתוב הצעה מהירה (למשל, מי אתה?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "כתוב סיכום ב-50 מילים שמסכם [נושא או מילת מפתח].",
+	"Write something...": "",
+	"Yesterday": "אתמול",
+	"You": "אתה",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "לא ניתן לשכפל מודל בסיס",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "אין לך שיחות בארכיון.",
+	"You have shared this chat": "שיתפת את השיחה הזו",
+	"You're a helpful assistant.": "אתה עוזר מועיל.",
+	"You're now logged in.": "כעת אתה מחובר.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "הגדרות Youtube Loader"
+}
diff --git a/src/lib/i18n/locales/hi-IN/translation.json b/src/lib/i18n/locales/hi-IN/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..e2a931b53d2d9228518ba8c03d495a2a9029d7e8
--- /dev/null
+++ b/src/lib/i18n/locales/hi-IN/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' or '-1' बिना किसी समाप्ति के",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(e.g. `sh webui.sh --api`)",
+	"(latest)": "(latest)",
+	"{{ models }}": "{{ मॉडल }}",
+	"{{ owner }}: You cannot delete a base model": "{{ मालिक }}: आप बेस मॉडल को हटा नहीं सकते",
+	"{{user}}'s Chats": "{{user}} की चैट",
+	"{{webUIName}} Backend Required": "{{webUIName}} बैकएंड आवश्यक",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "चैट और वेब खोज क्वेरी के लिए शीर्षक उत्पन्न करने जैसे कार्य करते समय कार्य मॉडल का उपयोग किया जाता है",
+	"a user": "एक उपयोगकर्ता",
+	"About": "हमारे बारे में",
+	"Account": "खाता",
+	"Account Activation Pending": "",
+	"Accurate information": "सटीक जानकारी",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "जोड़ें",
+	"Add a model id": "मॉडल आईडी जोड़ना",
+	"Add a short description about what this model does": "इस मॉडल के बारे में एक संक्षिप्त विवरण जोड़ें",
+	"Add a short title for this prompt": "इस संकेत के लिए एक संक्षिप्त शीर्षक जोड़ें",
+	"Add a tag": "एक टैग जोड़े",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "अनुकूल संकेत जोड़ें",
+	"Add Files": "फाइलें जोड़ें",
+	"Add Memory": "मेमोरी जोड़ें",
+	"Add Model": "मॉडल जोड़ें",
+	"Add Tag": "",
+	"Add Tags": "टैगों को जोड़ें",
+	"Add text content": "",
+	"Add User": "उपयोगकर्ता जोड़ें",
+	"Adjusting these settings will apply changes universally to all users.": "इन सेटिंग्स को समायोजित करने से परिवर्तन सभी उपयोगकर्ताओं पर सार्वभौमिक रूप से लागू होंगे।",
+	"admin": "व्यवस्थापक",
+	"Admin": "",
+	"Admin Panel": "व्यवस्थापक पैनल",
+	"Admin Settings": "व्यवस्थापक सेटिंग्स",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "उन्नत पैरामीटर",
+	"Advanced Params": "उन्नत परम",
+	"All chats": "",
+	"All Documents": "सभी डॉक्यूमेंट्स",
+	"Allow Chat Deletion": "चैट हटाने की अनुमति दें",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "अल्फ़ान्यूमेरिक वर्ण और हाइफ़न",
+	"Already have an account?": "क्या आपके पास पहले से एक खाता मौजूद है?",
+	"an assistant": "एक सहायक",
+	"and": "और",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "और एक नई साझा लिंक बनाएं.",
+	"API Base URL": "एपीआई बेस यूआरएल",
+	"API Key": "एपीआई कुंजी",
+	"API Key created.": "एपीआई कुंजी बनाई गई",
+	"API keys": "एपीआई कुंजियाँ",
+	"April": "अप्रैल",
+	"Archive": "पुरालेख",
+	"Archive All Chats": "सभी चैट संग्रहीत करें",
+	"Archived Chats": "संग्रहीत चैट",
+	"are allowed - Activate this command by typing": "अनुमति है - टाइप करके इस कमांड को सक्रिय करें",
+	"Are you sure?": "क्या आपको यकीन है?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "फ़ाइल atta",
+	"Attention to detail": "विस्तार पर ध्यान",
+	"Audio": "ऑडियो",
+	"August": "अगस्त",
+	"Auto-playback response": "ऑटो-प्लेबैक प्रतिक्रिया",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 बेस यूआरएल",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 का बेस यूआरएल आवश्यक है।",
+	"Available list": "",
+	"available!": "उपलब्ध!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "पीछे",
+	"Bad Response": "ख़राब प्रतिक्रिया",
+	"Banners": "बैनर",
+	"Base Model (From)": "बेस मॉडल (से)",
+	"Batch Size (num_batch)": "",
+	"before": "पहले",
+	"Being lazy": "आलसी होना",
+	"Brave Search API Key": "Brave सर्च एपीआई कुंजी",
+	"Bypass SSL verification for Websites": "वेबसाइटों के लिए SSL सुनिश्चिती को छोड़ें",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "रद्द करें",
+	"Capabilities": "क्षमताओं",
+	"Change Password": "पासवर्ड बदलें",
+	"Character": "",
+	"Chat": "चैट करें",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "चैट बॉली",
+	"Chat Controls": "",
+	"Chat direction": "चैट दिशा",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "सभी चैट",
+	"Check Again": "फिर से जाँचो",
+	"Check for updates": "अपडेट के लिए जाँच",
+	"Checking for updates...": "अपडेट के लिए जांच कर रहा है...",
+	"Choose a model before saving...": "सहेजने से पहले एक मॉडल चुनें...",
+	"Chunk Overlap": "चंक ओवरलैप",
+	"Chunk Params": "चंक पैरामीटर्स",
+	"Chunk Size": "चंक आकार",
+	"Citation": "उद्धरण",
+	"Clear memory": "",
+	"Click here for help.": "सहायता के लिए यहां क्लिक करें।",
+	"Click here to": "यहां क्लिक करें",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "चयन करने के लिए यहां क्लिक करें।",
+	"Click here to select a csv file.": "सीएसवी फ़ाइल का चयन करने के लिए यहां क्लिक करें।",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "यहाँ क्लिक करें।",
+	"Click on the user role button to change a user's role.": "उपयोगकर्ता की भूमिका बदलने के लिए उपयोगकर्ता भूमिका बटन पर क्लिक करें।",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "क्लोन",
+	"Close": "बंद करना",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "संग्रह",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI बेस यूआरएल",
+	"ComfyUI Base URL is required.": "ComfyUI का बेस यूआरएल आवश्यक है",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "कमांड",
+	"Completions": "",
+	"Concurrent Requests": "समवर्ती अनुरोध",
+	"Confirm": "",
+	"Confirm Password": "पासवर्ड की पुष्टि कीजिये",
+	"Confirm your action": "",
+	"Connections": "सम्बन्ध",
+	"Contact Admin for WebUI Access": "",
+	"Content": "सामग्री",
+	"Content Extraction": "",
+	"Context Length": "प्रसंग की लंबाई",
+	"Continue Response": "प्रतिक्रिया जारी रखें",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "साझा चैट URL को क्लिपबोर्ड पर कॉपी किया गया!",
+	"Copied to clipboard": "",
+	"Copy": "कॉपी",
+	"Copy last code block": "अंतिम कोड ब्लॉक कॉपी करें",
+	"Copy last response": "अंतिम प्रतिक्रिया कॉपी करें",
+	"Copy Link": "लिंक को कॉपी करें",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "क्लिपबोर्ड पर कॉपी बनाना सफल रहा!",
+	"Create a model": "एक मॉडल बनाएं",
+	"Create Account": "खाता बनाएं",
+	"Create Knowledge": "",
+	"Create new key": "नया क्रिप्टोग्राफिक क्षेत्र बनाएं",
+	"Create new secret key": "नया क्रिप्टोग्राफिक क्षेत्र बनाएं",
+	"Created at": "किस समय बनाया गया",
+	"Created At": "किस समय बनाया गया",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "वर्तमान मॉडल",
+	"Current Password": "वर्तमान पासवर्ड",
+	"Custom": "कस्टम संस्करण",
+	"Customize models for a specific purpose": "एक विशिष्ट उद्देश्य के लिए मॉडल अनुकूलित करें",
+	"Dark": "डार्क",
+	"Dashboard": "",
+	"Database": "डेटाबेस",
+	"December": "डिसेंबर",
+	"Default": "डिफ़ॉल्ट",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "डिफ़ॉल्ट (SentenceTransformers)",
+	"Default Model": "डिफ़ॉल्ट मॉडल",
+	"Default model updated": "डिफ़ॉल्ट मॉडल अपडेट किया गया",
+	"Default Prompt Suggestions": "डिफ़ॉल्ट प्रॉम्प्ट सुझाव",
+	"Default User Role": "डिफ़ॉल्ट उपयोगकर्ता भूमिका",
+	"Delete": "डिलीट",
+	"Delete a model": "एक मॉडल हटाएँ",
+	"Delete All Chats": "सभी चैट हटाएं",
+	"Delete chat": "चैट हटाएं",
+	"Delete Chat": "चैट हटाएं",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "इस लिंक को हटाएं",
+	"Delete tool?": "",
+	"Delete User": "उपभोक्ता मिटायें",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} हटा दिया गया",
+	"Deleted {{name}}": "{{name}} हटा दिया गया",
+	"Description": "विवरण",
+	"Didn't fully follow instructions": "निर्देशों का पूरी तरह से पालन नहीं किया",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "एक मॉडल की खोज करें",
+	"Discover a prompt": "प्रॉम्प्ट खोजें",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "कस्टम प्रॉम्प्ट को खोजें, डाउनलोड करें और एक्सप्लोर करें",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "मॉडल प्रीसेट खोजें, डाउनलोड करें और एक्सप्लोर करें",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "चैट में 'आप' के स्थान पर उपयोगकर्ता नाम प्रदर्शित करें",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "दस्तावेज़",
+	"Documentation": "",
+	"Documents": "दस्तावेज़",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "कोई बाहरी कनेक्शन नहीं बनाता है, और आपका डेटा आपके स्थानीय रूप से होस्ट किए गए सर्वर पर सुरक्षित रूप से रहता है।",
+	"Don't have an account?": "कोई खाता नहीं है?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "शैली पसंद नहीं है",
+	"Done": "",
+	"Download": "डाउनलोड",
+	"Download canceled": "डाउनलोड रद्द किया गया",
+	"Download Database": "डेटाबेस डाउनलोड करें",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "बातचीत में जोड़ने के लिए कोई भी फ़ाइल यहां छोड़ें",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "जैसे '30s', '10m', मान्य समय इकाइयाँ 's', 'm', 'h' हैं।",
+	"Edit": "संपादित करें",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "यूजर को संपादित करो",
+	"ElevenLabs": "",
+	"Email": "ईमेल",
+	"Embedding Batch Size": "",
+	"Embedding Model": "मॉडेल अनुकूलन",
+	"Embedding Model Engine": "एंबेडिंग मॉडल इंजन",
+	"Embedding model set to \"{{embedding_model}}\"": "एम्बेडिंग मॉडल को \"{{embedding_model}}\" पर सेट किया गया",
+	"Enable Community Sharing": "समुदाय साझाकरण सक्षम करें",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "नए साइन अप सक्रिय करें",
+	"Enable Web Search": "वेब खोज सक्षम करें",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "सुनिश्चित करें कि आपकी CSV फ़ाइल में इस क्रम में 4 कॉलम शामिल हैं: नाम, ईमेल, पासवर्ड, भूमिका।",
+	"Enter {{role}} message here": "यहां {{role}} संदेश दर्ज करें",
+	"Enter a detail about yourself for your LLMs to recall": "अपने एलएलएम को याद करने के लिए अपने बारे में एक विवरण दर्ज करें",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Brave सर्च एपीआई कुंजी डालें",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "चंक ओवरलैप दर्ज करें",
+	"Enter Chunk Size": "खंड आकार दर्ज करें",
+	"Enter description": "",
+	"Enter Github Raw URL": "Github Raw URL दर्ज करें",
+	"Enter Google PSE API Key": "Google PSE API कुंजी दर्ज करें",
+	"Enter Google PSE Engine Id": "Google PSE इंजन आईडी दर्ज करें",
+	"Enter Image Size (e.g. 512x512)": "छवि का आकार दर्ज करें (उदा. 512x512)",
+	"Enter language codes": "भाषा कोड दर्ज करें",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Model tag दर्ज करें (उदा. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "चरणों की संख्या दर्ज करें (उदा. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "स्कोर दर्ज करें",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Searxng क्वेरी URL दर्ज करें",
+	"Enter Serper API Key": "Serper API कुंजी दर्ज करें",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "सर्पस्टैक एपीआई कुंजी दर्ज करें",
+	"Enter stop sequence": "स्टॉप अनुक्रम दर्ज करें",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "शीर्ष K दर्ज करें",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "यूआरएल दर्ज करें (उदा. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "यूआरएल दर्ज करें (उदा. http://localhost:11434)",
+	"Enter Your Email": "अपना ईमेल दर्ज करें",
+	"Enter Your Full Name": "अपना पूरा नाम भरें",
+	"Enter your message": "",
+	"Enter Your Password": "अपना पासवर्ड भरें",
+	"Enter Your Role": "अपनी भूमिका दर्ज करें",
+	"Error": "चूक",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "प्रयोगात्मक",
+	"Export": "निर्यातित माल",
+	"Export All Chats (All Users)": "सभी चैट निर्यात करें (सभी उपयोगकर्ताओं की)",
+	"Export chat (.json)": "",
+	"Export Chats": "चैट निर्यात करें",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "निर्यात मॉडल",
+	"Export Prompts": "प्रॉम्प्ट निर्यात करें",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "एपीआई कुंजी बनाने में विफल.",
+	"Failed to read clipboard contents": "क्लिपबोर्ड सामग्री पढ़ने में विफल",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "फरवरी",
+	"Feedback History": "",
+	"Feel free to add specific details": "विशिष्ट विवरण जोड़ने के लिए स्वतंत्र महसूस करें",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "फ़ाइल मोड",
+	"File not found.": "फ़ाइल प्राप्त नहीं हुई।",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "फ़िंगरप्रिंट स्पूफ़िंग का पता चला: प्रारंभिक अक्षरों को अवतार के रूप में उपयोग करने में असमर्थ। प्रोफ़ाइल छवि को डिफ़ॉल्ट पर डिफ़ॉल्ट किया जा रहा है.",
+	"Fluidly stream large external response chunks": "बड़े बाह्य प्रतिक्रिया खंडों को तरल रूप से प्रवाहित करें",
+	"Focus chat input": "चैट इनपुट पर फ़ोकस करें",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "निर्देशों का पूर्णतः पालन किया",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "फ्रीक्वेंसी पेनल्टी",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "सामान्य",
+	"General Settings": "सामान्य सेटिंग्स",
+	"Generate Image": "",
+	"Generating search query": "खोज क्वेरी जनरेट करना",
+	"Generation Info": "जनरेशन की जानकारी",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "अच्छी प्रतिक्रिया",
+	"Google PSE API Key": "Google PSE API कुंजी",
+	"Google PSE Engine Id": "Google PSE इंजन आईडी",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "कोई बातचीत नहीं है",
+	"Hello, {{name}}": "नमस्ते, {{name}}",
+	"Help": "मदद",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "छुपाएं",
+	"Hide Model": "",
+	"How can I help you today?": "आज मैं आपकी कैसे मदद कर सकता हूँ?",
+	"Hybrid Search": "हाइब्रिड खोज",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "छवि निर्माण (प्रायोगिक)",
+	"Image Generation Engine": "छवि निर्माण इंजन",
+	"Image Settings": "छवि सेटिंग्स",
+	"Images": "इमेजिस",
+	"Import Chats": "चैट आयात करें",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "आयात मॉडल",
+	"Import Prompts": "प्रॉम्प्ट आयात करें",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui चलाते समय `--api` ध्वज शामिल करें",
+	"Info": "सूचना-विषयक",
+	"Input commands": "इनपुट क命",
+	"Install from Github URL": "Github URL से इंस्टॉल करें",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "इंटरफेस",
+	"Invalid file format.": "",
+	"Invalid Tag": "अवैध टैग",
+	"January": "जनवरी",
+	"join our Discord for help.": "मदद के लिए हमारे डिस्कोर्ड में शामिल हों।",
+	"JSON": "ज्ञान प्रकार",
+	"JSON Preview": "JSON पूर्वावलोकन",
+	"July": "जुलाई",
+	"June": "जुन",
+	"JWT Expiration": "JWT समाप्ति",
+	"JWT Token": "जट टोकन",
+	"Keep Alive": "क्रियाशील रहो",
+	"Keyboard shortcuts": "कीबोर्ड शॉर्टकट",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "भाषा",
+	"large language models, locally.": "",
+	"Last Active": "पिछली बार सक्रिय",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "सुन",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "एलएलएम गलतियाँ कर सकते हैं। महत्वपूर्ण जानकारी सत्यापित करें.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "OpenWebUI समुदाय द्वारा निर्मित",
+	"Make sure to enclose them with": "उन्हें संलग्न करना सुनिश्चित करें",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "मॉडल प्रबंधित करें",
+	"Manage Ollama Models": "Ollama मॉडल प्रबंधित करें",
+	"Manage Pipelines": "पाइपलाइनों का प्रबंधन करें",
+	"March": "मार्च",
+	"Max Tokens (num_predict)": "अधिकतम टोकन (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "अधिकतम 3 मॉडल एक साथ डाउनलोड किये जा सकते हैं। कृपया बाद में पुन: प्रयास करें।",
+	"May": "मेई",
+	"Memories accessible by LLMs will be shown here.": "एलएलएम द्वारा सुलभ यादें यहां दिखाई जाएंगी।",
+	"Memory": "मेमोरी",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "अपना लिंक बनाने के बाद आपके द्वारा भेजे गए संदेश साझा नहीं किए जाएंगे। यूआरएल वाले यूजर्स शेयर की गई चैट देख पाएंगे।",
+	"Min P": "",
+	"Minimum Score": "न्यूनतम स्कोर",
+	"Mirostat": "मिरोस्टा",
+	"Mirostat Eta": "मिरोस्टा ईटा",
+	"Mirostat Tau": "मिरोस्तात ताऊ",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "मॉडल '{{modelName}}' सफलतापूर्वक डाउनलोड हो गया है।",
+	"Model '{{modelTag}}' is already in queue for downloading.": "मॉडल '{{modelTag}}' पहले से ही डाउनलोड करने के लिए कतार में है।",
+	"Model {{modelId}} not found": "मॉडल {{modelId}} नहीं मिला",
+	"Model {{modelName}} is not vision capable": "मॉडल {{modelName}} दृष्टि सक्षम नहीं है",
+	"Model {{name}} is now {{status}}": "मॉडल {{name}} अब {{status}} है",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "मॉडल फ़ाइल सिस्टम पथ का पता चला. अद्यतन के लिए मॉडल संक्षिप्त नाम आवश्यक है, जारी नहीं रखा जा सकता।",
+	"Model ID": "मॉडल आईडी",
+	"Model Name": "",
+	"Model not selected": "मॉडल चयनित नहीं है",
+	"Model Params": "मॉडल Params",
+	"Model updated successfully": "",
+	"Model Whitelisting": "मॉडल श्वेतसूचीकरण करें",
+	"Model(s) Whitelisted": "मॉडल श्वेतसूची में है",
+	"Modelfile Content": "मॉडल फ़ाइल सामग्री",
+	"Models": "सभी मॉडल",
+	"more": "",
+	"More": "और..",
+	"Move to Top": "",
+	"Name": "नाम",
+	"Name your model": "अपने मॉडल को नाम दें",
+	"New Chat": "नई चैट",
+	"New folder": "",
+	"New Password": "नया पासवर्ड",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "कोई परिणाम नहीं मिला",
+	"No search query generated": "कोई खोज क्वेरी जनरेट नहीं हुई",
+	"No source available": "कोई स्रोत उपलब्ध नहीं है",
+	"No valves to update": "",
+	"None": "कोई नहीं",
+	"Not factually correct": "तथ्यात्मक रूप से सही नहीं है",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "ध्यान दें: यदि आप न्यूनतम स्कोर निर्धारित करते हैं, तो खोज केवल न्यूनतम स्कोर से अधिक या उसके बराबर स्कोर वाले दस्तावेज़ वापस लाएगी।",
+	"Notes": "",
+	"Notifications": "सूचनाएं",
+	"November": "नवंबर",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (ओलामा)",
+	"OAuth ID": "",
+	"October": "अक्टूबर",
+	"Off": "बंद",
+	"Okay, Let's Go!": "ठीक है, चलिए चलते हैं!",
+	"OLED Dark": "OLEDescuro",
+	"Ollama": "Ollama",
+	"Ollama API": "ओलामा एपीआई",
+	"Ollama API disabled": "ओलामा एपीआई अक्षम",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Ollama Version",
+	"On": "चालू",
+	"Only": "केवल",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "कमांड स्ट्रिंग में केवल अल्फ़ान्यूमेरिक वर्ण और हाइफ़न की अनुमति है।",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "उफ़! ऐसा लगता है कि यूआरएल अमान्य है. कृपया दोबारा जांचें और पुनः प्रयास करें।",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "उफ़! आप एक असमर्थित विधि (केवल फ्रंटएंड) का उपयोग कर रहे हैं। कृपया बैकएंड से WebUI सर्वे करें।",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "नई चैट खोलें",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API कॉन्फिग",
+	"OpenAI API Key is required.": "OpenAI API कुंजी आवश्यक है",
+	"OpenAI URL/Key required.": "OpenAI URL/Key आवश्यक है।",
+	"or": "या",
+	"Other": "अन्य",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "पासवर्ड",
+	"PDF document (.pdf)": "PDF दस्तावेज़ (.pdf)",
+	"PDF Extract Images (OCR)": "PDF छवियाँ निकालें (OCR)",
+	"pending": "लंबित",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "माइक्रोफ़ोन तक पहुँचने पर अनुमति अस्वीकृत: {{error}}",
+	"Personalization": "पेरसनलाइज़मेंट",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "पाइपलाइनों",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "पाइपलाइन वाल्व",
+	"Plain text (.txt)": "सादा पाठ (.txt)",
+	"Playground": "कार्यक्षेत्र",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "सकारात्मक रवैया",
+	"Previous 30 days": "पिछले 30 दिन",
+	"Previous 7 days": "पिछले 7 दिन",
+	"Profile Image": "प्रोफ़ाइल छवि",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "प्रॉम्प्ट (उदाहरण के लिए मुझे रोमन साम्राज्य के बारे में एक मजेदार तथ्य बताएं)",
+	"Prompt Content": "प्रॉम्प्ट सामग्री",
+	"Prompt suggestions": "प्रॉम्प्ट सुझाव",
+	"Prompts": "प्रॉम्प्ट",
+	"Pull \"{{searchValue}}\" from Ollama.com": "\"{{searchValue}}\" को Ollama.com से खींचें",
+	"Pull a model from Ollama.com": "Ollama.com से एक मॉडल खींचें",
+	"Query Params": "क्वेरी पैरामीटर",
+	"RAG Template": "RAG टेम्पलेट",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "जोर से पढ़ें",
+	"Record voice": "आवाज रिकॉर्ड करना",
+	"Redirecting you to OpenWebUI Community": "आपको OpenWebUI समुदाय पर पुनर्निर्देशित किया जा रहा है",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "जब ऐसा नहीं होना चाहिए था तो मना कर दिया",
+	"Regenerate": "पुनः जेनरेट",
+	"Release Notes": "रिलीज नोट्स",
+	"Relevance": "",
+	"Remove": "हटा दें",
+	"Remove Model": "मोडेल हटाएँ",
+	"Rename": "नाम बदलें",
+	"Repeat Last N": "अंतिम N दोहराएँ",
+	"Request Mode": "अनुरोध मोड",
+	"Reranking Model": "रीरैकिंग मोड",
+	"Reranking model disabled": "पुनर्रैंकिंग मॉडल अक्षम किया गया",
+	"Reranking model set to \"{{reranking_model}}\"": "रीरैंकिंग मॉडल को \"{{reranking_model}}\" पर \u200b\u200bसेट किया गया",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "क्लिपबोर्ड पर प्रतिक्रिया ऑटोकॉपी",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "भूमिका",
+	"Rosé Pine": "रोसे पिन",
+	"Rosé Pine Dawn": "रोसे पिन डेन",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "सहेजें",
+	"Save & Create": "सहेजें और बनाएं",
+	"Save & Update": "सहेजें और अपडेट करें",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "चैट लॉग को सीधे आपके ब्राउज़र के स्टोरेज में सहेजना अब समर्थित नहीं है। कृपया नीचे दिए गए बटन पर क्लिक करके डाउनलोड करने और अपने चैट लॉग को हटाने के लिए कुछ समय दें। चिंता न करें, आप आसानी से अपने चैट लॉग को बैकएंड पर पुनः आयात कर सकते हैं",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "खोजें",
+	"Search a model": "एक मॉडल खोजें",
+	"Search Chats": "चैट खोजें",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "मॉडल खोजें",
+	"Search Prompts": "प्रॉम्प्ट खोजें",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "खोज परिणामों की संख्या",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "{{count}} sites_one खोजा गया",
+	"Searched {{count}} sites_other": "{{count}} sites_other खोजा गया",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng क्वेरी URL",
+	"See readme.md for instructions": "निर्देशों के लिए readme.md देखें",
+	"See what's new": "देखें, क्या नया है",
+	"Seed": "सीड्\u200c",
+	"Select a base model": "एक आधार मॉडल का चयन करें",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "एक मॉडल चुनें",
+	"Select a pipeline": "एक पाइपलाइन का चयन करें",
+	"Select a pipeline url": "एक पाइपलाइन url चुनें",
+	"Select a tool": "",
+	"Select an Ollama instance": "एक Ollama Instance चुनें",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "मॉडल चुनें",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "चयनित मॉडल छवि इनपुट का समर्थन नहीं करते हैं",
+	"Semantic distance to query": "",
+	"Send": "भेज",
+	"Send a Message": "एक संदेश भेजो",
+	"Send message": "मेसेज भेजें",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "सितंबर",
+	"Serper API Key": "Serper API कुंजी",
+	"Serply API Key": "",
+	"Serpstack API Key": "सर्पस्टैक एपीआई कुंजी",
+	"Server connection verified": "सर्वर कनेक्शन सत्यापित",
+	"Set as default": "डिफाल्ट के रूप में सेट",
+	"Set CFG Scale": "",
+	"Set Default Model": "डिफ़ॉल्ट मॉडल सेट करें",
+	"Set embedding model (e.g. {{model}})": "ईम्बेडिंग मॉडल सेट करें (उदाहरण: {{model}})",
+	"Set Image Size": "छवि का आकार सेट करें",
+	"Set reranking model (e.g. {{model}})": "रीकरण मॉडल सेट करें (उदाहरण: {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "चरण निर्धारित करें",
+	"Set Task Model": "कार्य मॉडल सेट करें",
+	"Set Voice": "आवाज सेट करें",
+	"Set whisper model": "",
+	"Settings": "सेटिंग्स",
+	"Settings saved successfully!": "सेटिंग्स सफलतापूर्वक सहेजी गईं!",
+	"Share": "साझा करें",
+	"Share Chat": "चैट साझा करें",
+	"Share to OpenWebUI Community": "OpenWebUI समुदाय में साझा करें",
+	"short-summary": "संक्षिप्त सारांश",
+	"Show": "दिखाओ",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "शॉर्टकट दिखाएँ",
+	"Show your support!": "",
+	"Showcased creativity": "रचनात्मकता का प्रदर्शन किया",
+	"Sign in": "साइन इन",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "साइन आउट",
+	"Sign up": "साइन अप",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "स्रोत",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "वाक् पहचान त्रुटि: {{error}}",
+	"Speech-to-Text Engine": "वाक्-से-पाठ इंजन",
+	"Stop": "",
+	"Stop Sequence": "अनुक्रम रोकें",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "STT सेटिंग्स ",
+	"Subtitle (e.g. about the Roman Empire)": "उपशीर्षक (जैसे रोमन साम्राज्य के बारे में)",
+	"Success": "संपन्न",
+	"Successfully updated.": "सफलतापूर्वक उत्परिवर्तित।",
+	"Suggested": "सुझावी",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "सिस्टम",
+	"System Instructions": "",
+	"System Prompt": "सिस्टम प्रॉम्प्ट",
+	"Tags": "टैग",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "हमें और अधिक बताएँ:",
+	"Temperature": "टेंपेरेचर",
+	"Template": "टेम्पलेट",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "टेक्स्ट-टू-स्पीच इंजन",
+	"Tfs Z": "टफ्स Z",
+	"Thanks for your feedback!": "आपकी प्रतिक्रिया के लिए धन्यवाद!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "स्कोर का मान 0.0 (0%) और 1.0 (100%) के बीच होना चाहिए।",
+	"Theme": "थीम",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "यह सुनिश्चित करता है कि आपकी मूल्यवान बातचीत आपके बैकएंड डेटाबेस में सुरक्षित रूप से सहेजी गई है। धन्यवाद!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "विस्तृत व्याख्या",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "टिप: प्रत्येक प्रतिस्थापन के बाद चैट इनपुट में टैब कुंजी दबाकर लगातार कई वैरिएबल स्लॉट अपडेट करें।",
+	"Title": "शीर्षक",
+	"Title (e.g. Tell me a fun fact)": "शीर्षक (उदा. मुझे एक मज़ेदार तथ्य बताएं)",
+	"Title Auto-Generation": "शीर्षक ऑटो-जेनरेशन",
+	"Title cannot be an empty string.": "शीर्षक नहीं खाली पाठ हो सकता है.",
+	"Title Generation Prompt": "शीर्षक जनरेशन प्रॉम्प्ट",
+	"To access the available model names for downloading,": "डाउनलोड करने के लिए उपलब्ध मॉडल नामों तक पहुंचने के लिए,",
+	"To access the GGUF models available for downloading,": "डाउनलोडिंग के लिए उपलब्ध GGUF मॉडल तक पहुँचने के लिए,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "इनपुट चैट करने के लिए.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "आज",
+	"Toggle settings": "सेटिंग्स टॉगल करें",
+	"Toggle sidebar": "साइडबार टॉगल करें",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "शीर्ष  K",
+	"Top P": "शीर्ष  P",
+	"Trouble accessing Ollama?": "Ollama तक पहुँचने में परेशानी हो रही है?",
+	"TTS Model": "",
+	"TTS Settings": "TTS सेटिंग्स",
+	"TTS Voice": "",
+	"Type": "प्रकार",
+	"Type Hugging Face Resolve (Download) URL": "हगिंग फेस रिज़ॉल्व (डाउनलोड) यूआरएल टाइप करें",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "उह ओह! {{provider}} से कनेक्ट करने में एक समस्या थी।",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "अपडेट करें और लिंक कॉपी करें",
+	"Update for the latest features and improvements.": "",
+	"Update password": "पासवर्ड अपडेट करें",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "GGUF मॉडल अपलोड करें",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "फ़ाइलें अपलोड करें",
+	"Upload Pipeline": "",
+	"Upload Progress": "प्रगति अपलोड करें",
+	"URL Mode": "URL मोड",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Gravatar का प्रयोग करें",
+	"Use Initials": "प्रथमाक्षर का प्रयोग करें",
+	"use_mlock (Ollama)": "use_mlock (ओलामा)",
+	"use_mmap (Ollama)": "use_mmap (ओलामा)",
+	"user": "उपयोगकर्ता",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "उपयोगकर्ता अनुमतियाँ",
+	"Users": "उपयोगकर्ताओं",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "उपयोग करें",
+	"Valid time units:": "मान्य समय इकाइयाँ:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "वेरिएबल",
+	"variable to have them replaced with clipboard content.": "उन्हें क्लिपबोर्ड सामग्री से बदलने के लिए वेरिएबल।",
+	"Version": "संस्करण",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "चेतावनी",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "चेतावनी: यदि आप अपने एम्बेडिंग मॉडल को अपडेट या बदलते हैं, तो आपको सभी दस्तावेज़ों को फिर से आयात करने की आवश्यकता होगी।",
+	"Web": "वेब",
+	"Web API": "",
+	"Web Loader Settings": "वेब लोडर सेटिंग्स",
+	"Web Search": "वेब खोज",
+	"Web Search Engine": "वेब खोज इंजन",
+	"Webhook URL": "वेबहुक URL",
+	"WebUI Settings": "WebUI सेटिंग्स",
+	"WebUI will make requests to": "WebUI अनुरोध करेगा",
+	"What’s New in": "इसमें नया क्या है",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "वर्कस्पेस",
+	"Write a prompt suggestion (e.g. Who are you?)": "एक त्वरित सुझाव लिखें (जैसे कि आप कौन हैं?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "50 शब्दों में एक सारांश लिखें जो [विषय या कीवर्ड] का सारांश प्रस्तुत करता हो।",
+	"Write something...": "",
+	"Yesterday": "कल",
+	"You": "आप",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "आप बेस मॉडल का क्लोन नहीं बना सकते",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "आपको कोई अंकित चैट नहीं है।",
+	"You have shared this chat": "आपने इस चैट को शेयर किया है",
+	"You're a helpful assistant.": "आप एक सहायक सहायक हैं",
+	"You're now logged in.": "अब आप लॉग इन हो गए हैं",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "यूट्यूब लोडर सेटिंग्स"
+}
diff --git a/src/lib/i18n/locales/hr-HR/translation.json b/src/lib/i18n/locales/hr-HR/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..18050d8c32e1ef75e08d0faf713e509210c41e1d
--- /dev/null
+++ b/src/lib/i18n/locales/hr-HR/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' ili '-1' za bez isteka.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(npr. `sh webui.sh --api`)",
+	"(latest)": "(najnovije)",
+	"{{ models }}": "{{ modeli }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Ne možete obrisati osnovni model",
+	"{{user}}'s Chats": "Razgovori korisnika {{user}}",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend je potreban",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Model zadatka koristi se pri izvođenju zadataka kao što su generiranje naslova za razgovore i upite za pretraživanje weba",
+	"a user": "korisnik",
+	"About": "O aplikaciji",
+	"Account": "Račun",
+	"Account Activation Pending": "",
+	"Accurate information": "Točne informacije",
+	"Actions": "",
+	"Active Users": "Aktivni korisnici",
+	"Add": "Dodaj",
+	"Add a model id": "Dodavanje ID-a modela",
+	"Add a short description about what this model does": "Dodajte kratak opis funkcija ovog modela",
+	"Add a short title for this prompt": "Dodajte kratki naslov za ovaj prompt",
+	"Add a tag": "Dodaj oznaku",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Dodaj prilagođeni prompt",
+	"Add Files": "Dodaj datoteke",
+	"Add Memory": "Dodaj memoriju",
+	"Add Model": "Dodaj model",
+	"Add Tag": "",
+	"Add Tags": "Dodaj oznake",
+	"Add text content": "",
+	"Add User": "Dodaj korisnika",
+	"Adjusting these settings will apply changes universally to all users.": "Podešavanje će se primijeniti univerzalno na sve korisnike.",
+	"admin": "administrator",
+	"Admin": "Admin",
+	"Admin Panel": "Admin ploča",
+	"Admin Settings": "Admin postavke",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "Napredni parametri",
+	"Advanced Params": "Napredni parametri",
+	"All chats": "",
+	"All Documents": "Svi dokumenti",
+	"Allow Chat Deletion": "Dopusti brisanje razgovora",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Dopusti nelokalne glasove",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "alfanumerički znakovi i crtice",
+	"Already have an account?": "Već imate račun?",
+	"an assistant": "asistent",
+	"and": "i",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "i stvorite novu dijeljenu vezu.",
+	"API Base URL": "Osnovni URL API-ja",
+	"API Key": "API ključ",
+	"API Key created.": "API ključ je stvoren.",
+	"API keys": "API ključevi",
+	"April": "Travanj",
+	"Archive": "Arhiva",
+	"Archive All Chats": "Arhivirajte sve razgovore",
+	"Archived Chats": "Arhivirani razgovori",
+	"are allowed - Activate this command by typing": "su dopušteni - Aktivirajte ovu naredbu upisivanjem",
+	"Are you sure?": "Jeste li sigurni?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Priloži datoteku",
+	"Attention to detail": "Pažnja na detalje",
+	"Audio": "Audio",
+	"August": "Kolovoz",
+	"Auto-playback response": "Automatska reprodukcija odgovora",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 osnovni URL",
+	"AUTOMATIC1111 Base URL is required.": "Potreban je AUTOMATIC1111 osnovni URL.",
+	"Available list": "",
+	"available!": "dostupno!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Natrag",
+	"Bad Response": "Loš odgovor",
+	"Banners": "Baneri",
+	"Base Model (From)": "Osnovni model (Od)",
+	"Batch Size (num_batch)": "",
+	"before": "prije",
+	"Being lazy": "Biti lijen",
+	"Brave Search API Key": "Brave tražilica - API ključ",
+	"Bypass SSL verification for Websites": "Zaobiđi SSL provjeru za web stranice",
+	"Call": "Poziv",
+	"Call feature is not supported when using Web STT engine": "Značajka poziva nije podržana kada se koristi Web STT mehanizam",
+	"Camera": "Kamera",
+	"Cancel": "Otkaži",
+	"Capabilities": "Mogućnosti",
+	"Change Password": "Promijeni lozinku",
+	"Character": "",
+	"Chat": "Razgovor",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "Razgovor - Bubble UI",
+	"Chat Controls": "",
+	"Chat direction": "Razgovor - smijer",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Razgovori",
+	"Check Again": "Provjeri ponovo",
+	"Check for updates": "Provjeri za ažuriranja",
+	"Checking for updates...": "Provjeravam ažuriranja...",
+	"Choose a model before saving...": "Odaberite model prije spremanja...",
+	"Chunk Overlap": "Preklapanje dijelova",
+	"Chunk Params": "Parametri dijelova",
+	"Chunk Size": "Veličina dijela",
+	"Citation": "Citiranje",
+	"Clear memory": "Očisti memoriju",
+	"Click here for help.": "Kliknite ovdje za pomoć.",
+	"Click here to": "Kliknite ovdje za",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Kliknite ovdje za odabir",
+	"Click here to select a csv file.": "Kliknite ovdje da odaberete csv datoteku.",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "kliknite ovdje.",
+	"Click on the user role button to change a user's role.": "Kliknite na gumb uloge korisnika za promjenu uloge korisnika.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "Kloniraj",
+	"Close": "Zatvori",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Kolekcija",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI osnovni URL",
+	"ComfyUI Base URL is required.": "Potreban je ComfyUI osnovni URL.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Naredba",
+	"Completions": "",
+	"Concurrent Requests": "Istodobni zahtjevi",
+	"Confirm": "",
+	"Confirm Password": "Potvrdite lozinku",
+	"Confirm your action": "",
+	"Connections": "Povezivanja",
+	"Contact Admin for WebUI Access": "Kontaktirajte admina za WebUI pristup",
+	"Content": "Sadržaj",
+	"Content Extraction": "",
+	"Context Length": "Dužina konteksta",
+	"Continue Response": "Nastavi odgovor",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "URL dijeljenog razgovora kopiran u međuspremnik!",
+	"Copied to clipboard": "",
+	"Copy": "Kopiraj",
+	"Copy last code block": "Kopiraj zadnji blok koda",
+	"Copy last response": "Kopiraj zadnji odgovor",
+	"Copy Link": "Kopiraj vezu",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Kopiranje u međuspremnik je uspješno!",
+	"Create a model": "Izradite model",
+	"Create Account": "Stvori račun",
+	"Create Knowledge": "",
+	"Create new key": "Stvori novi ključ",
+	"Create new secret key": "Stvori novi tajni ključ",
+	"Created at": "Stvoreno",
+	"Created At": "Stvoreno",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "Trenutni model",
+	"Current Password": "Trenutna lozinka",
+	"Custom": "Prilagođeno",
+	"Customize models for a specific purpose": "Prilagodba modela za određenu svrhu",
+	"Dark": "Tamno",
+	"Dashboard": "Radna ploča",
+	"Database": "Baza podataka",
+	"December": "Prosinac",
+	"Default": "Zadano",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Zadano (SentenceTransformers)",
+	"Default Model": "Zadani model",
+	"Default model updated": "Zadani model ažuriran",
+	"Default Prompt Suggestions": "Zadani prijedlozi prompta",
+	"Default User Role": "Zadana korisnička uloga",
+	"Delete": "Izbriši",
+	"Delete a model": "Izbriši model",
+	"Delete All Chats": "Izbriši sve razgovore",
+	"Delete chat": "Izbriši razgovor",
+	"Delete Chat": "Izbriši razgovor",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "izbriši ovu vezu",
+	"Delete tool?": "",
+	"Delete User": "Izbriši korisnika",
+	"Deleted {{deleteModelTag}}": "Izbrisan {{deleteModelTag}}",
+	"Deleted {{name}}": "Izbrisano {{name}}",
+	"Description": "Opis",
+	"Didn't fully follow instructions": "Nije u potpunosti slijedio upute",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "Otkrijte model",
+	"Discover a prompt": "Otkrijte prompt",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Otkrijte, preuzmite i istražite prilagođene prompte",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Otkrijte, preuzmite i istražite unaprijed postavljene modele",
+	"Dismissible": "Odbaciti",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "Prikaži korisničko ime umjesto Vas u razgovoru",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Dokument",
+	"Documentation": "Dokumentacija",
+	"Documents": "Dokumenti",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "ne uspostavlja vanjske veze, a vaši podaci ostaju sigurno na vašem lokalno hostiranom poslužitelju.",
+	"Don't have an account?": "Nemate račun?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "Ne sviđa mi se stil",
+	"Done": "",
+	"Download": "Preuzimanje",
+	"Download canceled": "Preuzimanje otkazano",
+	"Download Database": "Preuzmi bazu podataka",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Spustite bilo koje datoteke ovdje za dodavanje u razgovor",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "npr. '30s','10m'. Važeće vremenske jedinice su 's', 'm', 'h'.",
+	"Edit": "Uredi",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "Uredi korisnika",
+	"ElevenLabs": "",
+	"Email": "Email",
+	"Embedding Batch Size": "Embedding - Veličina batch-a",
+	"Embedding Model": "Embedding model",
+	"Embedding Model Engine": "Embedding model pogon",
+	"Embedding model set to \"{{embedding_model}}\"": "Embedding model postavljen na \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Omogući zajedničko korištenje zajednice",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Omogući nove prijave",
+	"Enable Web Search": "Omogući pretraživanje weba",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Provjerite da vaša CSV datoteka uključuje 4 stupca u ovom redoslijedu: Name, Email, Password, Role.",
+	"Enter {{role}} message here": "Unesite {{role}} poruku ovdje",
+	"Enter a detail about yourself for your LLMs to recall": "Unesite pojedinosti o sebi da bi učitali memoriju u LLM",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Unesite Brave Search API ključ",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Unesite preklapanje dijelova",
+	"Enter Chunk Size": "Unesite veličinu dijela",
+	"Enter description": "",
+	"Enter Github Raw URL": "Unesite Github sirovi URL",
+	"Enter Google PSE API Key": "Unesite Google PSE API ključ",
+	"Enter Google PSE Engine Id": "Unesite ID Google PSE motora",
+	"Enter Image Size (e.g. 512x512)": "Unesite veličinu slike (npr. 512x512)",
+	"Enter language codes": "Unesite kodove jezika",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Unesite oznaku modela (npr. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Unesite broj koraka (npr. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Unesite ocjenu",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Unesite URL upita Searxng",
+	"Enter Serper API Key": "Unesite Serper API ključ",
+	"Enter Serply API Key": "Unesite Serply API ključ",
+	"Enter Serpstack API Key": "Unesite Serpstack API ključ",
+	"Enter stop sequence": "Unesite sekvencu zaustavljanja",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Unesite Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Unesite URL (npr. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Unesite URL (npr. http://localhost:11434)",
+	"Enter Your Email": "Unesite svoj email",
+	"Enter Your Full Name": "Unesite svoje puno ime",
+	"Enter your message": "",
+	"Enter Your Password": "Unesite svoju lozinku",
+	"Enter Your Role": "Unesite svoju ulogu",
+	"Error": "Greška",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Eksperimentalno",
+	"Export": "Izvoz",
+	"Export All Chats (All Users)": "Izvoz svih razgovora (svi korisnici)",
+	"Export chat (.json)": "Izvoz četa (.json)",
+	"Export Chats": "Izvoz razgovora",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "Izvoz modela",
+	"Export Prompts": "Izvoz prompta",
+	"Export Tools": "Izvoz alata",
+	"External Models": "Vanjski modeli",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Neuspješno stvaranje API ključa.",
+	"Failed to read clipboard contents": "Neuspješno čitanje sadržaja međuspremnika",
+	"Failed to update settings": "Greška kod ažuriranja postavki",
+	"Failed to upload file.": "",
+	"February": "Veljača",
+	"Feedback History": "",
+	"Feel free to add specific details": "Slobodno dodajte specifične detalje",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Način datoteke",
+	"File not found.": "Datoteka nije pronađena.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Otkriveno krivotvorenje otisaka prstiju: Nemoguće je koristiti inicijale kao avatar. Postavljanje na zadanu profilnu sliku.",
+	"Fluidly stream large external response chunks": "Glavno strujanje velikih vanjskih dijelova odgovora",
+	"Focus chat input": "Fokusiraj unos razgovora",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Savršeno slijedio upute",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Kazna za učestalost",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Općenito",
+	"General Settings": "Opće postavke",
+	"Generate Image": "Gneriraj sliku",
+	"Generating search query": "Generiranje upita za pretraživanje",
+	"Generation Info": "Informacije o generaciji",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "Dobar odgovor",
+	"Google PSE API Key": "Google PSE API ključ",
+	"Google PSE Engine Id": "ID Google PSE modula",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "nema razgovora.",
+	"Hello, {{name}}": "Bok, {{name}}",
+	"Help": "Pomoć",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Sakrij",
+	"Hide Model": "",
+	"How can I help you today?": "Kako vam mogu pomoći danas?",
+	"Hybrid Search": "Hibridna pretraga",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Generiranje slika (eksperimentalno)",
+	"Image Generation Engine": "Stroj za generiranje slika",
+	"Image Settings": "Postavke slike",
+	"Images": "Slike",
+	"Import Chats": "Uvoz razgovora",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "Uvoz modela",
+	"Import Prompts": "Uvoz prompta",
+	"Import Tools": "Uvoz alata",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Uključite zastavicu `--api` prilikom pokretanja stable-diffusion-webui",
+	"Info": "Informacije",
+	"Input commands": "Unos naredbi",
+	"Install from Github URL": "Instaliraj s Github URL-a",
+	"Instant Auto-Send After Voice Transcription": "Trenutačno automatsko slanje nakon glasovne transkripcije",
+	"Interface": "Sučelje",
+	"Invalid file format.": "",
+	"Invalid Tag": "Nevažeća oznaka",
+	"January": "Siječanj",
+	"join our Discord for help.": "pridružite se našem Discordu za pomoć.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON pretpregled",
+	"July": "Srpanj",
+	"June": "Lipanj",
+	"JWT Expiration": "Isticanje JWT-a",
+	"JWT Token": "JWT token",
+	"Keep Alive": "Održavanje živim",
+	"Keyboard shortcuts": "Tipkovnički prečaci",
+	"Knowledge": "Znanje",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Jezik",
+	"large language models, locally.": "",
+	"Last Active": "Zadnja aktivnost",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Svijetlo",
+	"Listening...": "Slušam...",
+	"LLMs can make mistakes. Verify important information.": "LLM-ovi mogu pogriješiti. Provjerite važne informacije.",
+	"Local Models": "Lokalni modeli",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Izradio OpenWebUI Community",
+	"Make sure to enclose them with": "Provjerite da ih zatvorite s",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Upravljaj",
+	"Manage Arena Models": "",
+	"Manage Models": "Upravljanje modelima",
+	"Manage Ollama Models": "Upravljanje Ollama modelima",
+	"Manage Pipelines": "Upravljanje cjevovodima",
+	"March": "Ožujak",
+	"Max Tokens (num_predict)": "Maksimalan broj tokena (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maksimalno 3 modela se mogu preuzeti istovremeno. Pokušajte ponovo kasnije.",
+	"May": "Svibanj",
+	"Memories accessible by LLMs will be shown here.": "Ovdje će biti prikazana memorija kojoj mogu pristupiti LLM-ovi.",
+	"Memory": "Memorija",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Poruke koje pošaljete nakon stvaranja veze neće se dijeliti. Korisnici s URL-om moći će vidjeti zajednički chat.",
+	"Min P": "",
+	"Minimum Score": "Minimalna ocjena",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' je uspješno preuzet.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' je već u redu za preuzimanje.",
+	"Model {{modelId}} not found": "Model {{modelId}} nije pronađen",
+	"Model {{modelName}} is not vision capable": "Model {{modelName}} ne čita vizualne impute",
+	"Model {{name}} is now {{status}}": "Model {{name}} sada je {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Otkriven put datotečnog sustava modela. Kratko ime modela je potrebno za ažuriranje, nije moguće nastaviti.",
+	"Model ID": "ID modela",
+	"Model Name": "",
+	"Model not selected": "Model nije odabran",
+	"Model Params": "Model parametri",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Model - Bijela lista",
+	"Model(s) Whitelisted": "Model(i) na bijeloj listi",
+	"Modelfile Content": "Sadržaj datoteke modela",
+	"Models": "Modeli",
+	"more": "",
+	"More": "Više",
+	"Move to Top": "",
+	"Name": "Ime",
+	"Name your model": "Dodijelite naziv modelu",
+	"New Chat": "Novi razgovor",
+	"New folder": "",
+	"New Password": "Nova lozinka",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Nema rezultata",
+	"No search query generated": "Nije generiran upit za pretraživanje",
+	"No source available": "Nema dostupnog izvora",
+	"No valves to update": "",
+	"None": "Ništa",
+	"Not factually correct": "Nije činjenično točno",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Napomena: Ako postavite minimalnu ocjenu, pretraga će vratiti samo dokumente s ocjenom većom ili jednakom minimalnoj ocjeni.",
+	"Notes": "",
+	"Notifications": "Obavijesti",
+	"November": "Studeni",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "",
+	"October": "Listopad",
+	"Off": "Isključeno",
+	"Okay, Let's Go!": "U redu, idemo!",
+	"OLED Dark": "OLED Tamno",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API je onemogućen",
+	"Ollama API is disabled": "Ollama API je onemogućen",
+	"Ollama Version": "Ollama verzija",
+	"On": "Uključeno",
+	"Only": "Samo",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Samo alfanumerički znakovi i crtice su dopušteni u naredbenom nizu.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ups! Izgleda da je URL nevažeći. Molimo provjerite ponovno i pokušajte ponovo.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Koristite nepodržanu metodu (samo frontend). Molimo poslužite WebUI s backend-a.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Otvorite novi razgovor",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API konfiguracija",
+	"OpenAI API Key is required.": "Potreban je OpenAI API ključ.",
+	"OpenAI URL/Key required.": "Potreban je OpenAI URL/ključ.",
+	"or": "ili",
+	"Other": "Ostalo",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Lozinka",
+	"PDF document (.pdf)": "PDF dokument (.pdf)",
+	"PDF Extract Images (OCR)": "PDF izdvajanje slika (OCR)",
+	"pending": "u tijeku",
+	"Permission denied when accessing media devices": "Dopuštenje je odbijeno prilikom pristupa medijskim uređajima",
+	"Permission denied when accessing microphone": "Dopuštenje je odbijeno prilikom pristupa mikrofonu",
+	"Permission denied when accessing microphone: {{error}}": "Pristup mikrofonu odbijen: {{error}}",
+	"Personalization": "Prilagodba",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "Cjevovodi",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "Ventili za cjevovode",
+	"Plain text (.txt)": "Običan tekst (.txt)",
+	"Playground": "Igralište",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Pozitivan stav",
+	"Previous 30 days": "Prethodnih 30 dana",
+	"Previous 7 days": "Prethodnih 7 dana",
+	"Profile Image": "Profilna slika",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (npr. Reci mi zanimljivost o Rimskom carstvu)",
+	"Prompt Content": "Sadržaj prompta",
+	"Prompt suggestions": "Prijedlozi prompta",
+	"Prompts": "Prompti",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Povucite \"{{searchValue}}\" s Ollama.com",
+	"Pull a model from Ollama.com": "Povucite model s Ollama.com",
+	"Query Params": "Parametri upita",
+	"RAG Template": "RAG predložak",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Čitaj naglas",
+	"Record voice": "Snimanje glasa",
+	"Redirecting you to OpenWebUI Community": "Preusmjeravanje na OpenWebUI zajednicu",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Nazivajte se \"Korisnik\" (npr. \"Korisnik uči španjolski\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Odbijen kada nije trebao biti",
+	"Regenerate": "Regeneriraj",
+	"Release Notes": "Bilješke o izdanju",
+	"Relevance": "",
+	"Remove": "Ukloni",
+	"Remove Model": "Ukloni model",
+	"Rename": "Preimenuj",
+	"Repeat Last N": "Ponovi zadnjih N",
+	"Request Mode": "Način zahtjeva",
+	"Reranking Model": "Model za ponovno rangiranje",
+	"Reranking model disabled": "Model za ponovno rangiranje onemogućen",
+	"Reranking model set to \"{{reranking_model}}\"": "Model za ponovno rangiranje postavljen na \"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "Poništi upload direktorij",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Automatsko kopiranje odgovora u međuspremnik",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Uloga",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "Pokrenuto",
+	"Save": "Spremi",
+	"Save & Create": "Spremi i stvori",
+	"Save & Update": "Spremi i ažuriraj",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Spremanje zapisnika razgovora izravno u pohranu vašeg preglednika više nije podržano. Molimo vas da odvojite trenutak za preuzimanje i brisanje zapisnika razgovora klikom na gumb ispod. Ne brinite, možete lako ponovno uvesti zapisnike razgovora u backend putem",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Pretraga",
+	"Search a model": "Pretraži model",
+	"Search Chats": "Pretraži razgovore",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "Pretražite modele",
+	"Search Prompts": "Pretraga prompta",
+	"Search Query Generation Prompt": "Upit za generiranje upita za pretraživanje",
+	"Search Result Count": "Broj rezultata pretraživanja",
+	"Search Tools": "Alati za pretraživanje",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Pretraženo {{count}} sites_one",
+	"Searched {{count}} sites_few": "Pretraženo {{count}} sites_few",
+	"Searched {{count}} sites_other": "Pretraženo {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng URL upita",
+	"See readme.md for instructions": "Pogledajte readme.md za upute",
+	"See what's new": "Pogledajte što je novo",
+	"Seed": "Sjeme",
+	"Select a base model": "Odabir osnovnog modela",
+	"Select a engine": "Odaberite pogon",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "Odaberite model",
+	"Select a pipeline": "Odabir kanala",
+	"Select a pipeline url": "Odabir URL-a kanala",
+	"Select a tool": "",
+	"Select an Ollama instance": "Odaberite Ollama instancu",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Odaberite model",
+	"Select only one model to call": "Odaberite samo jedan model za poziv",
+	"Selected model(s) do not support image inputs": "Odabrani modeli ne podržavaju unose slika",
+	"Semantic distance to query": "",
+	"Send": "Pošalji",
+	"Send a Message": "Pošaljite poruku",
+	"Send message": "Pošalji poruku",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "Rujan",
+	"Serper API Key": "Serper API ključ",
+	"Serply API Key": "Serply API ključ",
+	"Serpstack API Key": "Serpstack API API ključ",
+	"Server connection verified": "Veza s poslužiteljem potvrđena",
+	"Set as default": "Postavi kao zadano",
+	"Set CFG Scale": "",
+	"Set Default Model": "Postavi zadani model",
+	"Set embedding model (e.g. {{model}})": "Postavi model za embedding (npr. {{model}})",
+	"Set Image Size": "Postavi veličinu slike",
+	"Set reranking model (e.g. {{model}})": "Postavi model za ponovno rangiranje (npr. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Postavi korake",
+	"Set Task Model": "Postavite model zadatka",
+	"Set Voice": "Postavi glas",
+	"Set whisper model": "",
+	"Settings": "Postavke",
+	"Settings saved successfully!": "Postavke su uspješno spremljene!",
+	"Share": "Podijeli",
+	"Share Chat": "Podijeli razgovor",
+	"Share to OpenWebUI Community": "Podijeli u OpenWebUI zajednici",
+	"short-summary": "kratki sažetak",
+	"Show": "Pokaži",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "Pokaži prečace",
+	"Show your support!": "",
+	"Showcased creativity": "Prikazana kreativnost",
+	"Sign in": "Prijava",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Odjava",
+	"Sign up": "Registracija",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Izvor",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Pogreška prepoznavanja govora: {{error}}",
+	"Speech-to-Text Engine": "Stroj za prepoznavanje govora",
+	"Stop": "",
+	"Stop Sequence": "Zaustavi sekvencu",
+	"Stream Chat Response": "",
+	"STT Model": "STT model",
+	"STT Settings": "STT postavke",
+	"Subtitle (e.g. about the Roman Empire)": "Podnaslov (npr. o Rimskom carstvu)",
+	"Success": "Uspjeh",
+	"Successfully updated.": "Uspješno ažurirano.",
+	"Suggested": "Predloženo",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "Sustav",
+	"System Instructions": "",
+	"System Prompt": "Sistemski prompt",
+	"Tags": "Oznake",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "Recite nam više:",
+	"Temperature": "Temperatura",
+	"Template": "Predložak",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Stroj za pretvorbu teksta u govor",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Hvala na povratnim informacijama!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Ocjena treba biti vrijednost između 0,0 (0%) i 1,0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "Razmišljam",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ovo osigurava da su vaši vrijedni razgovori sigurno spremljeni u bazu podataka. Hvala vam!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Ovo je eksperimentalna značajka, možda neće funkcionirati prema očekivanjima i podložna je promjenama u bilo kojem trenutku.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Detaljno objašnjenje",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Savjet: Ažurirajte više mjesta za varijable uzastopno pritiskom na tipku tab u unosu razgovora nakon svake zamjene.",
+	"Title": "Naslov",
+	"Title (e.g. Tell me a fun fact)": "Naslov (npr. Reci mi zanimljivost)",
+	"Title Auto-Generation": "Automatsko generiranje naslova",
+	"Title cannot be an empty string.": "Naslov ne može biti prazni niz.",
+	"Title Generation Prompt": "Prompt za generiranje naslova",
+	"To access the available model names for downloading,": "Za pristup dostupnim nazivima modela za preuzimanje,",
+	"To access the GGUF models available for downloading,": "Za pristup GGUF modelima dostupnim za preuzimanje,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Za pristup WebUI-u obratite se administratoru. Administratori mogu upravljati statusima korisnika s Admin panela.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "u unos razgovora.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "Danas",
+	"Toggle settings": "Prebaci postavke",
+	"Toggle sidebar": "Prebaci bočnu traku",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "Alati",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problemi s pristupom Ollama?",
+	"TTS Model": "TTS model",
+	"TTS Settings": "TTS postavke",
+	"TTS Voice": "TTS glas",
+	"Type": "Tip",
+	"Type Hugging Face Resolve (Download) URL": "Upišite Hugging Face Resolve (Download) URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Pojavio se problem s povezivanjem na {{provider}}.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "Ažuriraj i kopiraj vezu",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Ažuriraj lozinku",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Učitaj GGUF model",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Prijenos datoteka",
+	"Upload Pipeline": "Prijenos kanala",
+	"Upload Progress": "Napredak učitavanja",
+	"URL Mode": "URL način",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Koristi Gravatar",
+	"Use Initials": "Koristi inicijale",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "korisnik",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "Korisnička dopuštenja",
+	"Users": "Korisnici",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Iskoristi",
+	"Valid time units:": "Važeće vremenske jedinice:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "varijabla",
+	"variable to have them replaced with clipboard content.": "varijabla za zamjenu sadržajem međuspremnika.",
+	"Version": "Verzija",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "Upozorenje",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Upozorenje: Ako ažurirate ili promijenite svoj model za umetanje, morat ćete ponovno uvesti sve dokumente.",
+	"Web": "Web",
+	"Web API": "Web API",
+	"Web Loader Settings": "Postavke web učitavanja",
+	"Web Search": "Internet pretraga",
+	"Web Search Engine": "Web tražilica",
+	"Webhook URL": "URL webkuke",
+	"WebUI Settings": "WebUI postavke",
+	"WebUI will make requests to": "WebUI će slati zahtjeve na",
+	"What’s New in": "Što je novo u",
+	"Whisper (Local)": "Whisper (lokalno)",
+	"Widescreen Mode": "Mod širokog zaslona",
+	"Won": "",
+	"Workspace": "Radna ploča",
+	"Write a prompt suggestion (e.g. Who are you?)": "Napišite prijedlog prompta (npr. Tko si ti?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Napišite sažetak u 50 riječi koji sažima [temu ili ključnu riječ].",
+	"Write something...": "",
+	"Yesterday": "Jučer",
+	"You": "Vi",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Možete personalizirati svoje interakcije s LLM-ima dodavanjem uspomena putem gumba 'Upravljanje' u nastavku, čineći ih korisnijima i prilagođenijima vama.",
+	"You cannot clone a base model": "Ne možete klonirati osnovni model",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Nemate arhiviranih razgovora.",
+	"You have shared this chat": "Podijelili ste ovaj razgovor",
+	"You're a helpful assistant.": "Vi ste korisni asistent.",
+	"You're now logged in.": "Sada ste prijavljeni.",
+	"Your account status is currently pending activation.": "Status vašeg računa trenutno čeka aktivaciju.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "YouTube",
+	"Youtube Loader Settings": "YouTube postavke učitavanja"
+}
diff --git a/src/lib/i18n/locales/hu-HU/translation.json b/src/lib/i18n/locales/hu-HU/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..fc13c18a5219f7c12746e387ed7cfc9e47aa8c62
--- /dev/null
+++ b/src/lib/i18n/locales/hu-HU/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' vagy '-1' ha nincs lejárat.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(pl. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(pl. `sh webui.sh --api`)",
+	"(latest)": "(legújabb)",
+	"{{ models }}": "{{ modellek }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Nem törölhetsz alap modellt",
+	"{{user}}'s Chats": "{{user}} beszélgetései",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend szükséges",
+	"*Prompt node ID(s) are required for image generation": "*Prompt node ID(k) szükségesek a képgeneráláshoz",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "Új verzió (v{{LATEST_VERSION}}) érhető el.",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "A feladat modell olyan feladatokhoz használatos, mint a beszélgetések címeinek generálása és webes keresési lekérdezések",
+	"a user": "egy felhasználó",
+	"About": "Névjegy",
+	"Account": "Fiók",
+	"Account Activation Pending": "Fiók aktiválása folyamatban",
+	"Accurate information": "Pontos információ",
+	"Actions": "Műveletek",
+	"Active Users": "Aktív felhasználók",
+	"Add": "Hozzáadás",
+	"Add a model id": "Modell ID hozzáadása",
+	"Add a short description about what this model does": "Adj hozzá egy rövid leírást arról, hogy mit csinál ez a modell",
+	"Add a short title for this prompt": "Adj hozzá egy rövid címet ehhez a prompthoz",
+	"Add a tag": "Címke hozzáadása",
+	"Add Arena Model": "Arena modell hozzáadása",
+	"Add Content": "Tartalom hozzáadása",
+	"Add content here": "Tartalom hozzáadása ide",
+	"Add custom prompt": "Egyéni prompt hozzáadása",
+	"Add Files": "Fájlok hozzáadása",
+	"Add Memory": "Memória hozzáadása",
+	"Add Model": "Modell hozzáadása",
+	"Add Tag": "Címke hozzáadása",
+	"Add Tags": "Címkék hozzáadása",
+	"Add text content": "Szöveges tartalom hozzáadása",
+	"Add User": "Felhasználó hozzáadása",
+	"Adjusting these settings will apply changes universally to all users.": "Ezen beállítások módosítása minden felhasználóra érvényes lesz.",
+	"admin": "admin",
+	"Admin": "Admin",
+	"Admin Panel": "Admin Panel",
+	"Admin Settings": "Admin beállítások",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Az adminok mindig hozzáférnek minden eszközhöz; a felhasználóknak modellenként kell eszközöket hozzárendelni a munkaterületen.",
+	"Advanced Parameters": "Haladó paraméterek",
+	"Advanced Params": "Haladó paraméterek",
+	"All chats": "Minden beszélgetés",
+	"All Documents": "Minden dokumentum",
+	"Allow Chat Deletion": "Beszélgetések törlésének engedélyezése",
+	"Allow Chat Editing": "Beszélgetések szerkesztésének engedélyezése",
+	"Allow non-local voices": "Nem helyi hangok engedélyezése",
+	"Allow Temporary Chat": "Ideiglenes beszélgetés engedélyezése",
+	"Allow User Location": "Felhasználói helyzet engedélyezése",
+	"Allow Voice Interruption in Call": "Hang megszakítás engedélyezése hívás közben",
+	"alphanumeric characters and hyphens": "alfanumerikus karakterek és kötőjelek",
+	"Already have an account?": "Már van fiókod?",
+	"an assistant": "egy asszisztens",
+	"and": "és",
+	"and {{COUNT}} more": "és még {{COUNT}} db",
+	"and create a new shared link.": "és hozz létre egy új megosztott linket.",
+	"API Base URL": "API alap URL",
+	"API Key": "API kulcs",
+	"API Key created.": "API kulcs létrehozva.",
+	"API keys": "API kulcsok",
+	"April": "Április",
+	"Archive": "Archiválás",
+	"Archive All Chats": "Minden beszélgetés archiválása",
+	"Archived Chats": "Archivált beszélgetések",
+	"are allowed - Activate this command by typing": "engedélyezettek - Aktiváld ezt a parancsot a következő beírásával",
+	"Are you sure?": "Biztos vagy benne?",
+	"Arena Models": "Arena modellek",
+	"Artifacts": "Műtermékek",
+	"Ask a question": "Kérdezz valamit",
+	"Assistant": "Asszisztens",
+	"Attach file": "Fájl csatolása",
+	"Attention to detail": "Részletekre való odafigyelés",
+	"Audio": "Hang",
+	"August": "Augusztus",
+	"Auto-playback response": "Automatikus válasz lejátszás",
+	"Automatic1111": "Automatic1111",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Api hitelesítési karakterlánc",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 alap URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 alap URL szükséges.",
+	"Available list": "Elérhető lista",
+	"available!": "elérhető!",
+	"Azure AI Speech": "Azure AI beszéd",
+	"Azure Region": "Azure régió",
+	"Back": "Vissza",
+	"Bad Response": "Rossz válasz",
+	"Banners": "Bannerek",
+	"Base Model (From)": "Alap modell (Forrás)",
+	"Batch Size (num_batch)": "Köteg méret (num_batch)",
+	"before": "előtt",
+	"Being lazy": "Lustaság",
+	"Brave Search API Key": "Brave Search API kulcs",
+	"Bypass SSL verification for Websites": "SSL ellenőrzés kihagyása weboldalakhoz",
+	"Call": "Hívás",
+	"Call feature is not supported when using Web STT engine": "A hívás funkció nem támogatott Web STT motor használatakor",
+	"Camera": "Kamera",
+	"Cancel": "Mégse",
+	"Capabilities": "Képességek",
+	"Change Password": "Jelszó módosítása",
+	"Character": "Karakter",
+	"Chat": "Beszélgetés",
+	"Chat Background Image": "Beszélgetés háttérkép",
+	"Chat Bubble UI": "Beszélgetés buborék felület",
+	"Chat Controls": "Beszélgetés vezérlők",
+	"Chat direction": "Beszélgetés iránya",
+	"Chat Overview": "Beszélgetés áttekintés",
+	"Chat Tags Auto-Generation": "Beszélgetés címkék automatikus generálása",
+	"Chats": "Beszélgetések",
+	"Check Again": "Ellenőrzés újra",
+	"Check for updates": "Frissítések keresése",
+	"Checking for updates...": "Frissítések keresése...",
+	"Choose a model before saving...": "Válassz modellt mentés előtt...",
+	"Chunk Overlap": "Darab átfedés",
+	"Chunk Params": "Darab paraméterek",
+	"Chunk Size": "Darab méret",
+	"Citation": "Idézet",
+	"Clear memory": "Memória törlése",
+	"Click here for help.": "Kattints ide segítségért.",
+	"Click here to": "Kattints ide",
+	"Click here to download user import template file.": "Kattints ide a felhasználó importálási sablon letöltéséhez.",
+	"Click here to learn more about faster-whisper and see the available models.": "Kattints ide, hogy többet tudj meg a faster-whisperről és lásd az elérhető modelleket.",
+	"Click here to select": "Kattints ide a kiválasztáshoz",
+	"Click here to select a csv file.": "Kattints ide egy CSV fájl kiválasztásához.",
+	"Click here to select a py file.": "Kattints ide egy py fájl kiválasztásához.",
+	"Click here to upload a workflow.json file.": "Kattints ide egy workflow.json fájl feltöltéséhez.",
+	"click here.": "kattints ide.",
+	"Click on the user role button to change a user's role.": "Kattints a felhasználói szerep gombra a felhasználó szerepének módosításához.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Vágólap írási engedély megtagadva. Kérjük, ellenőrizd a böngésző beállításait a szükséges hozzáférés megadásához.",
+	"Clone": "Klónozás",
+	"Close": "Bezárás",
+	"Code execution": "Kód végrehajtás",
+	"Code formatted successfully": "Kód sikeresen formázva",
+	"Collection": "Gyűjtemény",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI alap URL",
+	"ComfyUI Base URL is required.": "ComfyUI alap URL szükséges.",
+	"ComfyUI Workflow": "ComfyUI munkafolyamat",
+	"ComfyUI Workflow Nodes": "ComfyUI munkafolyamat csomópontok",
+	"Command": "Parancs",
+	"Completions": "Kiegészítések",
+	"Concurrent Requests": "Párhuzamos kérések",
+	"Confirm": "Megerősítés",
+	"Confirm Password": "Jelszó megerősítése",
+	"Confirm your action": "Erősítsd meg a műveletet",
+	"Connections": "Kapcsolatok",
+	"Contact Admin for WebUI Access": "Lépj kapcsolatba az adminnal a WebUI hozzáférésért",
+	"Content": "Tartalom",
+	"Content Extraction": "Tartalom kinyerés",
+	"Context Length": "Kontextus hossz",
+	"Continue Response": "Válasz folytatása",
+	"Continue with {{provider}}": "Folytatás {{provider}} szolgáltatóval",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "Szabályozd, hogyan legyen felosztva az üzenet szövege a TTS kérésekhez. A 'Központozás' mondatokra bontja, a 'Bekezdések' bekezdésekre bontja, a 'Nincs' pedig egyetlen szövegként kezeli az üzenetet.",
+	"Controls": "Vezérlők",
+	"Copied": "Másolva",
+	"Copied shared chat URL to clipboard!": "Megosztott beszélgetés URL másolva a vágólapra!",
+	"Copied to clipboard": "Vágólapra másolva",
+	"Copy": "Másolás",
+	"Copy last code block": "Utolsó kódblokk másolása",
+	"Copy last response": "Utolsó válasz másolása",
+	"Copy Link": "Link másolása",
+	"Copy to clipboard": "Másolás a vágólapra",
+	"Copying to clipboard was successful!": "Sikeres másolás a vágólapra!",
+	"Create a model": "Modell létrehozása",
+	"Create Account": "Fiók létrehozása",
+	"Create Knowledge": "Tudás létrehozása",
+	"Create new key": "Új kulcs létrehozása",
+	"Create new secret key": "Új titkos kulcs létrehozása",
+	"Created at": "Létrehozva",
+	"Created At": "Létrehozva",
+	"Created by": "Létrehozta",
+	"CSV Import": "CSV importálás",
+	"Current Model": "Jelenlegi modell",
+	"Current Password": "Jelenlegi jelszó",
+	"Custom": "Egyéni",
+	"Customize models for a specific purpose": "Modellek testreszabása specifikus célra",
+	"Dark": "Sötét",
+	"Dashboard": "Irányítópult",
+	"Database": "Adatbázis",
+	"December": "December",
+	"Default": "Alapértelmezett",
+	"Default (Open AI)": "Alapértelmezett (Open AI)",
+	"Default (SentenceTransformers)": "Alapértelmezett (SentenceTransformers)",
+	"Default Model": "Alapértelmezett modell",
+	"Default model updated": "Alapértelmezett modell frissítve",
+	"Default Prompt Suggestions": "Alapértelmezett prompt javaslatok",
+	"Default User Role": "Alapértelmezett felhasználói szerep",
+	"Delete": "Törlés",
+	"Delete a model": "Modell törlése",
+	"Delete All Chats": "Minden beszélgetés törlése",
+	"Delete chat": "Beszélgetés törlése",
+	"Delete Chat": "Beszélgetés törlése",
+	"Delete chat?": "Törli a beszélgetést?",
+	"Delete folder?": "Törli a mappát?",
+	"Delete function?": "Törli a funkciót?",
+	"Delete prompt?": "Törli a promptot?",
+	"delete this link": "link törlése",
+	"Delete tool?": "Törli az eszközt?",
+	"Delete User": "Felhasználó törlése",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} törölve",
+	"Deleted {{name}}": "{{name}} törölve",
+	"Description": "Leírás",
+	"Didn't fully follow instructions": "Nem követte teljesen az utasításokat",
+	"Disabled": "Letiltva",
+	"Discover a function": "Funkció felfedezése",
+	"Discover a model": "Modell felfedezése",
+	"Discover a prompt": "Prompt felfedezése",
+	"Discover a tool": "Eszköz felfedezése",
+	"Discover, download, and explore custom functions": "Fedezz fel, tölts le és fedezz fel egyéni funkciókat",
+	"Discover, download, and explore custom prompts": "Fedezz fel, tölts le és fedezz fel egyéni promptokat",
+	"Discover, download, and explore custom tools": "Fedezz fel, tölts le és fedezz fel egyéni eszközöket",
+	"Discover, download, and explore model presets": "Fedezz fel, tölts le és fedezz fel modell beállításokat",
+	"Dismissible": "Elutasítható",
+	"Display Emoji in Call": "Emoji megjelenítése hívásban",
+	"Display the username instead of You in the Chat": "Felhasználónév megjelenítése a 'Te' helyett a beszélgetésben",
+	"Do not install functions from sources you do not fully trust.": "Ne telepíts funkciókat olyan forrásokból, amelyekben nem bízol teljesen.",
+	"Do not install tools from sources you do not fully trust.": "Ne telepíts eszközöket olyan forrásokból, amelyekben nem bízol teljesen.",
+	"Document": "Dokumentum",
+	"Documentation": "Dokumentáció",
+	"Documents": "Dokumentumok",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "nem létesít külső kapcsolatokat, és az adataid biztonságban maradnak a helyileg hosztolt szervereden.",
+	"Don't have an account?": "Nincs még fiókod?",
+	"don't install random functions from sources you don't trust.": "ne telepíts véletlenszerű funkciókat olyan forrásokból, amelyekben nem bízol.",
+	"don't install random tools from sources you don't trust.": "ne telepíts véletlenszerű eszközöket olyan forrásokból, amelyekben nem bízol.",
+	"Don't like the style": "Nem tetszik a stílus",
+	"Done": "Kész",
+	"Download": "Letöltés",
+	"Download canceled": "Letöltés megszakítva",
+	"Download Database": "Adatbázis letöltése",
+	"Draw": "Rajzolás",
+	"Drop any files here to add to the conversation": "Húzz ide fájlokat a beszélgetéshez való hozzáadáshoz",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "pl. '30s','10m'. Érvényes időegységek: 's', 'm', 'h'.",
+	"Edit": "Szerkesztés",
+	"Edit Arena Model": "Arena modell szerkesztése",
+	"Edit Memory": "Memória szerkesztése",
+	"Edit User": "Felhasználó szerkesztése",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "Email",
+	"Embedding Batch Size": "Beágyazási köteg méret",
+	"Embedding Model": "Beágyazási modell",
+	"Embedding Model Engine": "Beágyazási modell motor",
+	"Embedding model set to \"{{embedding_model}}\"": "Beágyazási modell beállítva: \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Közösségi megosztás engedélyezése",
+	"Enable Message Rating": "Üzenet értékelés engedélyezése",
+	"Enable New Sign Ups": "Új regisztrációk engedélyezése",
+	"Enable Web Search": "Webes keresés engedélyezése",
+	"Enable Web Search Query Generation": "Webes keresési lekérdezés generálás engedélyezése",
+	"Enabled": "Engedélyezve",
+	"Engine": "Motor",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Győződj meg róla, hogy a CSV fájl tartalmazza ezt a 4 oszlopot ebben a sorrendben: Név, Email, Jelszó, Szerep.",
+	"Enter {{role}} message here": "Írd ide a {{role}} üzenetet",
+	"Enter a detail about yourself for your LLMs to recall": "Adj meg egy részletet magadról, amit az LLM-ek megjegyezhetnek",
+	"Enter api auth string (e.g. username:password)": "Add meg az API hitelesítési karakterláncot (pl. felhasználónév:jelszó)",
+	"Enter Brave Search API Key": "Add meg a Brave Search API kulcsot",
+	"Enter CFG Scale (e.g. 7.0)": "Add meg a CFG skálát (pl. 7.0)",
+	"Enter Chunk Overlap": "Add meg a darab átfedést",
+	"Enter Chunk Size": "Add meg a darab méretet",
+	"Enter description": "Add meg a leírást",
+	"Enter Github Raw URL": "Add meg a Github Raw URL-t",
+	"Enter Google PSE API Key": "Add meg a Google PSE API kulcsot",
+	"Enter Google PSE Engine Id": "Add meg a Google PSE motor azonosítót",
+	"Enter Image Size (e.g. 512x512)": "Add meg a kép méretet (pl. 512x512)",
+	"Enter language codes": "Add meg a nyelvi kódokat",
+	"Enter Model ID": "Add meg a modell azonosítót",
+	"Enter model tag (e.g. {{modelTag}})": "Add meg a modell címkét (pl. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Add meg a lépések számát (pl. 50)",
+	"Enter Sampler (e.g. Euler a)": "Add meg a mintavételezőt (pl. Euler a)",
+	"Enter Scheduler (e.g. Karras)": "Add meg az ütemezőt (pl. Karras)",
+	"Enter Score": "Add meg a pontszámot",
+	"Enter SearchApi API Key": "Add meg a SearchApi API kulcsot",
+	"Enter SearchApi Engine": "Add meg a SearchApi motort",
+	"Enter Searxng Query URL": "Add meg a Searxng lekérdezési URL-t",
+	"Enter Serper API Key": "Add meg a Serper API kulcsot",
+	"Enter Serply API Key": "Add meg a Serply API kulcsot",
+	"Enter Serpstack API Key": "Add meg a Serpstack API kulcsot",
+	"Enter stop sequence": "Add meg a leállítási szekvenciát",
+	"Enter system prompt": "Add meg a rendszer promptot",
+	"Enter Tavily API Key": "Add meg a Tavily API kulcsot",
+	"Enter Tika Server URL": "Add meg a Tika szerver URL-t",
+	"Enter Top K": "Add meg a Top K értéket",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Add meg az URL-t (pl. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Add meg az URL-t (pl. http://localhost:11434)",
+	"Enter Your Email": "Add meg az email címed",
+	"Enter Your Full Name": "Add meg a teljes neved",
+	"Enter your message": "Írd be az üzeneted",
+	"Enter Your Password": "Add meg a jelszavad",
+	"Enter Your Role": "Add meg a szereped",
+	"Error": "Hiba",
+	"ERROR": "HIBA",
+	"Evaluations": "Értékelések",
+	"Exclude": "Kizárás",
+	"Experimental": "Kísérleti",
+	"Export": "Exportálás",
+	"Export All Chats (All Users)": "Minden beszélgetés exportálása (minden felhasználó)",
+	"Export chat (.json)": "Beszélgetés exportálása (.json)",
+	"Export Chats": "Beszélgetések exportálása",
+	"Export Config to JSON File": "Konfiguráció exportálása JSON fájlba",
+	"Export Functions": "Funkciók exportálása",
+	"Export LiteLLM config.yaml": "LiteLLM config.yaml exportálása",
+	"Export Models": "Modellek exportálása",
+	"Export Prompts": "Promptok exportálása",
+	"Export Tools": "Eszközök exportálása",
+	"External Models": "Külső modellek",
+	"Failed to add file.": "Nem sikerült hozzáadni a fájlt.",
+	"Failed to create API Key.": "Nem sikerült létrehozni az API kulcsot.",
+	"Failed to read clipboard contents": "Nem sikerült olvasni a vágólap tartalmát",
+	"Failed to update settings": "Nem sikerült frissíteni a beállításokat",
+	"Failed to upload file.": "Nem sikerült feltölteni a fájlt.",
+	"February": "Február",
+	"Feedback History": "Visszajelzés előzmények",
+	"Feel free to add specific details": "Nyugodtan adj hozzá specifikus részleteket",
+	"File": "Fájl",
+	"File added successfully.": "Fájl sikeresen hozzáadva.",
+	"File content updated successfully.": "Fájl tartalom sikeresen frissítve.",
+	"File Mode": "Fájl mód",
+	"File not found.": "Fájl nem található.",
+	"File removed successfully.": "Fájl sikeresen eltávolítva.",
+	"File size should not exceed {{maxSize}} MB.": "A fájl mérete nem haladhatja meg a {{maxSize}} MB-ot.",
+	"Files": "Fájlok",
+	"Filter is now globally disabled": "A szűrő globálisan letiltva",
+	"Filter is now globally enabled": "A szűrő globálisan engedélyezve",
+	"Filters": "Szűrők",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Ujjlenyomat hamisítás észlelve: Nem lehet a kezdőbetűket avatárként használni. Alapértelmezett profilkép használata.",
+	"Fluidly stream large external response chunks": "Nagy külső válasz darabok folyamatos streamelése",
+	"Focus chat input": "Chat bevitel fókuszálása",
+	"Folder deleted successfully": "Mappa sikeresen törölve",
+	"Folder name cannot be empty": "A mappa neve nem lehet üres",
+	"Folder name cannot be empty.": "A mappa neve nem lehet üres.",
+	"Folder name updated successfully": "Mappa neve sikeresen frissítve",
+	"Followed instructions perfectly": "Tökéletesen követte az utasításokat",
+	"Form": "Űrlap",
+	"Format your variables using brackets like this:": "Formázd a változóidat zárójelekkel így:",
+	"Frequency Penalty": "Gyakorisági büntetés",
+	"Function": "Funkció",
+	"Function created successfully": "Funkció sikeresen létrehozva",
+	"Function deleted successfully": "Funkció sikeresen törölve",
+	"Function Description (e.g. A filter to remove profanity from text)": "Funkció leírása (pl. Egy szűrő a trágár szavak eltávolításához a szövegből)",
+	"Function ID (e.g. my_filter)": "Funkció azonosító (pl. my_filter)",
+	"Function is now globally disabled": "A funkció globálisan letiltva",
+	"Function is now globally enabled": "A funkció globálisan engedélyezve",
+	"Function Name (e.g. My Filter)": "Funkció neve (pl. Saját szűrő)",
+	"Function updated successfully": "Funkció sikeresen frissítve",
+	"Functions": "Funkciók",
+	"Functions allow arbitrary code execution": "A funkciók tetszőleges kód végrehajtását teszik lehetővé",
+	"Functions allow arbitrary code execution.": "A funkciók tetszőleges kód végrehajtását teszik lehetővé.",
+	"Functions imported successfully": "Funkciók sikeresen importálva",
+	"General": "Általános",
+	"General Settings": "Általános beállítások",
+	"Generate Image": "Kép generálása",
+	"Generating search query": "Keresési lekérdezés generálása",
+	"Generation Info": "Generálási információ",
+	"Get up and running with": "Kezdj el dolgozni a következővel:",
+	"Global": "Globális",
+	"Good Response": "Jó válasz",
+	"Google PSE API Key": "Google PSE API kulcs",
+	"Google PSE Engine Id": "Google PSE motor azonosító",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "Tapintási visszajelzés",
+	"has no conversations.": "nincsenek beszélgetései.",
+	"Hello, {{name}}": "Helló, {{name}}",
+	"Help": "Segítség",
+	"Help us create the best community leaderboard by sharing your feedback history!": "Segíts nekünk a legjobb közösségi ranglista létrehozásában a visszajelzési előzményeid megosztásával!",
+	"Hide": "Elrejtés",
+	"Hide Model": "Modell elrejtése",
+	"How can I help you today?": "Hogyan segíthetek ma?",
+	"Hybrid Search": "Hibrid keresés",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Elismerem, hogy elolvastam és megértem a cselekedetem következményeit. Tisztában vagyok a tetszőleges kód végrehajtásával járó kockázatokkal, és ellenőriztem a forrás megbízhatóságát.",
+	"ID": "Azonosító",
+	"Image Generation (Experimental)": "Képgenerálás (kísérleti)",
+	"Image Generation Engine": "Képgenerálási motor",
+	"Image Settings": "Kép beállítások",
+	"Images": "Képek",
+	"Import Chats": "Beszélgetések importálása",
+	"Import Config from JSON File": "Konfiguráció importálása JSON fájlból",
+	"Import Functions": "Funkciók importálása",
+	"Import Models": "Modellek importálása",
+	"Import Prompts": "Promptok importálása",
+	"Import Tools": "Eszközök importálása",
+	"Include": "Tartalmaz",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Add hozzá a `--api-auth` kapcsolót a stable-diffusion-webui futtatásakor",
+	"Include `--api` flag when running stable-diffusion-webui": "Add hozzá a `--api` kapcsolót a stable-diffusion-webui futtatásakor",
+	"Info": "Információ",
+	"Input commands": "Beviteli parancsok",
+	"Install from Github URL": "Telepítés Github URL-ről",
+	"Instant Auto-Send After Voice Transcription": "Azonnali automatikus küldés hangfelismerés után",
+	"Interface": "Felület",
+	"Invalid file format.": "Érvénytelen fájlformátum.",
+	"Invalid Tag": "Érvénytelen címke",
+	"January": "Január",
+	"join our Discord for help.": "Csatlakozz a Discord szerverünkhöz segítségért.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON előnézet",
+	"July": "Július",
+	"June": "Június",
+	"JWT Expiration": "JWT lejárat",
+	"JWT Token": "JWT token",
+	"Keep Alive": "Kapcsolat fenntartása",
+	"Keyboard shortcuts": "Billentyűparancsok",
+	"Knowledge": "Tudásbázis",
+	"Knowledge created successfully.": "Tudásbázis sikeresen létrehozva.",
+	"Knowledge deleted successfully.": "Tudásbázis sikeresen törölve.",
+	"Knowledge reset successfully.": "Tudásbázis sikeresen visszaállítva.",
+	"Knowledge updated successfully": "Tudásbázis sikeresen frissítve",
+	"Landing Page Mode": "Kezdőlap mód",
+	"Language": "Nyelv",
+	"large language models, locally.": "nagy nyelvi modellek, helyileg.",
+	"Last Active": "Utoljára aktív",
+	"Last Modified": "Utoljára módosítva",
+	"Leaderboard": "Ranglista",
+	"Leave empty for unlimited": "Hagyja üresen a korlátlan használathoz",
+	"Leave empty to include all models or select specific models": "Hagyja üresen az összes modell használatához, vagy válasszon ki konkrét modelleket",
+	"Leave empty to use the default prompt, or enter a custom prompt": "Hagyja üresen az alapértelmezett prompt használatához, vagy adjon meg egyéni promptot",
+	"Light": "Világos",
+	"Listening...": "Hallgatás...",
+	"LLMs can make mistakes. Verify important information.": "Az LLM-ek hibázhatnak. Ellenőrizze a fontos információkat.",
+	"Local Models": "Helyi modellek",
+	"Lost": "Elveszett",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Az OpenWebUI közösség által készítve",
+	"Make sure to enclose them with": "Győződjön meg róla, hogy körülveszi őket",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "Győződjön meg róla, hogy exportál egy workflow.json fájlt API formátumban a ComfyUI-ból.",
+	"Manage": "Kezelés",
+	"Manage Arena Models": "Arena modellek kezelése",
+	"Manage Models": "Modellek kezelése",
+	"Manage Ollama Models": "Ollama modellek kezelése",
+	"Manage Pipelines": "Folyamatok kezelése",
+	"March": "Március",
+	"Max Tokens (num_predict)": "Maximum tokenek (num_predict)",
+	"Max Upload Count": "Maximum feltöltések száma",
+	"Max Upload Size": "Maximum feltöltési méret",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maximum 3 modell tölthető le egyszerre. Kérjük, próbálja újra később.",
+	"May": "Május",
+	"Memories accessible by LLMs will be shown here.": "Az LLM-ek által elérhető emlékek itt jelennek meg.",
+	"Memory": "Memória",
+	"Memory added successfully": "Memória sikeresen hozzáadva",
+	"Memory cleared successfully": "Memória sikeresen törölve",
+	"Memory deleted successfully": "Memória sikeresen törölve",
+	"Memory updated successfully": "Memória sikeresen frissítve",
+	"Merge Responses": "Válaszok egyesítése",
+	"Message rating should be enabled to use this feature": "Az üzenetértékelésnek engedélyezve kell lennie ehhez a funkcióhoz",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "A link létrehozása után küldött üzenetei nem lesznek megosztva. A URL-lel rendelkező felhasználók megtekinthetik a megosztott beszélgetést.",
+	"Min P": "Min P",
+	"Minimum Score": "Minimum pontszám",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "YYYY. MMMM DD.",
+	"MMMM DD, YYYY HH:mm": "YYYY. MMMM DD. HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "YYYY. MMMM DD. hh:mm:ss A",
+	"Model": "Modell",
+	"Model '{{modelName}}' has been successfully downloaded.": "A '{{modelName}}' modell sikeresen letöltve.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "A '{{modelTag}}' modell már a letöltési sorban van.",
+	"Model {{modelId}} not found": "A {{modelId}} modell nem található",
+	"Model {{modelName}} is not vision capable": "A {{modelName}} modell nem képes képfeldolgozásra",
+	"Model {{name}} is now {{status}}": "A {{name}} modell most {{status}} állapotban van",
+	"Model {{name}} is now at the top": "A {{name}} modell most a lista tetején van",
+	"Model accepts image inputs": "A modell elfogad képbemenetet",
+	"Model created successfully!": "Modell sikeresen létrehozva!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modell fájlrendszer útvonal észlelve. A modell rövid neve szükséges a frissítéshez, nem folytatható.",
+	"Model ID": "Modell azonosító",
+	"Model Name": "Modell neve",
+	"Model not selected": "Nincs kiválasztva modell",
+	"Model Params": "Modell paraméterek",
+	"Model updated successfully": "Modell sikeresen frissítve",
+	"Model Whitelisting": "Modell fehérlista",
+	"Model(s) Whitelisted": "Fehérlistázott modell(ek)",
+	"Modelfile Content": "Modellfájl tartalom",
+	"Models": "Modellek",
+	"more": "több",
+	"More": "Több",
+	"Move to Top": "Mozgatás felülre",
+	"Name": "Név",
+	"Name your model": "Nevezze el a modelljét",
+	"New Chat": "Új beszélgetés",
+	"New folder": "Új mappa",
+	"New Password": "Új jelszó",
+	"No content found": "Nem található tartalom",
+	"No content to speak": "Nincs felolvasható tartalom",
+	"No distance available": "Nincs elérhető távolság",
+	"No feedbacks found": "Nem található visszajelzés",
+	"No file selected": "Nincs kiválasztva fájl",
+	"No files found.": "Nem található fájl.",
+	"No HTML, CSS, or JavaScript content found.": "Nem található HTML, CSS vagy JavaScript tartalom.",
+	"No knowledge found": "Nem található tudásbázis",
+	"No models found": "Nem található modell",
+	"No results found": "Nincs találat",
+	"No search query generated": "Nem generálódott keresési lekérdezés",
+	"No source available": "Nincs elérhető forrás",
+	"No valves to update": "Nincs frissítendő szelep",
+	"None": "Nincs",
+	"Not factually correct": "Tényszerűen nem helyes",
+	"Not helpful": "Nem segítőkész",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Megjegyzés: Ha minimum pontszámot állít be, a keresés csak olyan dokumentumokat ad vissza, amelyek pontszáma nagyobb vagy egyenlő a minimum pontszámmal.",
+	"Notes": "Jegyzetek",
+	"Notifications": "Értesítések",
+	"November": "November",
+	"num_gpu (Ollama)": "num_gpu (Ollama)",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth azonosító",
+	"October": "Október",
+	"Off": "Ki",
+	"Okay, Let's Go!": "Rendben, kezdjük!",
+	"OLED Dark": "OLED sötét",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API letiltva",
+	"Ollama API is disabled": "Az Ollama API le van tiltva",
+	"Ollama Version": "Ollama verzió",
+	"On": "Be",
+	"Only": "Csak",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Csak alfanumerikus karakterek és kötőjelek engedélyezettek a parancssorban.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Csak gyűjtemények szerkeszthetők, hozzon létre új tudásbázist dokumentumok szerkesztéséhez/hozzáadásához.",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppá! Úgy tűnik, az URL érvénytelen. Kérjük, ellenőrizze és próbálja újra.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "Hoppá! Még vannak feltöltés alatt álló fájlok. Kérjük, várja meg a feltöltés befejezését.",
+	"Oops! There was an error in the previous response.": "Hoppá! Hiba történt az előző válaszban.",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppá! Nem támogatott módszert használ (csak frontend). Kérjük, szolgálja ki a WebUI-t a backend-ről.",
+	"Open file": "Fájl megnyitása",
+	"Open in full screen": "Megnyitás teljes képernyőn",
+	"Open new chat": "Új beszélgetés megnyitása",
+	"Open WebUI uses faster-whisper internally.": "Az Open WebUI belsőleg a faster-whispert használja.",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Az Open WebUI verzió (v{{OPEN_WEBUI_VERSION}}) alacsonyabb, mint a szükséges verzió (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API konfiguráció",
+	"OpenAI API Key is required.": "OpenAI API kulcs szükséges.",
+	"OpenAI URL/Key required.": "OpenAI URL/kulcs szükséges.",
+	"or": "vagy",
+	"Other": "Egyéb",
+	"OUTPUT": "KIMENET",
+	"Output format": "Kimeneti formátum",
+	"Overview": "Áttekintés",
+	"page": "oldal",
+	"Password": "Jelszó",
+	"PDF document (.pdf)": "PDF dokumentum (.pdf)",
+	"PDF Extract Images (OCR)": "PDF képek kinyerése (OCR)",
+	"pending": "függőben",
+	"Permission denied when accessing media devices": "Hozzáférés megtagadva a médiaeszközökhöz",
+	"Permission denied when accessing microphone": "Hozzáférés megtagadva a mikrofonhoz",
+	"Permission denied when accessing microphone: {{error}}": "Hozzáférés megtagadva a mikrofonhoz: {{error}}",
+	"Personalization": "Személyre szabás",
+	"Pin": "Rögzítés",
+	"Pinned": "Rögzítve",
+	"Pipeline deleted successfully": "Folyamat sikeresen törölve",
+	"Pipeline downloaded successfully": "Folyamat sikeresen letöltve",
+	"Pipelines": "Folyamatok",
+	"Pipelines Not Detected": "Folyamatok nem észlelhetők",
+	"Pipelines Valves": "Folyamat szelepek",
+	"Plain text (.txt)": "Egyszerű szöveg (.txt)",
+	"Playground": "Játszótér",
+	"Please carefully review the following warnings:": "Kérjük, gondosan tekintse át a következő figyelmeztetéseket:",
+	"Please enter a prompt": "Kérjük, adjon meg egy promptot",
+	"Please fill in all fields.": "Kérjük, töltse ki az összes mezőt.",
+	"Please select a reason": "Kérjük, válasszon egy okot",
+	"Positive attitude": "Pozitív hozzáállás",
+	"Previous 30 days": "Előző 30 nap",
+	"Previous 7 days": "Előző 7 nap",
+	"Profile Image": "Profilkép",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (pl. Mondj egy érdekes tényt a Római Birodalomról)",
+	"Prompt Content": "Prompt tartalom",
+	"Prompt suggestions": "Prompt javaslatok",
+	"Prompts": "Promptok",
+	"Pull \"{{searchValue}}\" from Ollama.com": "\"{{searchValue}}\" letöltése az Ollama.com-ról",
+	"Pull a model from Ollama.com": "Modell letöltése az Ollama.com-ról",
+	"Query Params": "Lekérdezési paraméterek",
+	"RAG Template": "RAG sablon",
+	"Rating": "Értékelés",
+	"Re-rank models by topic similarity": "Modellek újrarangsorolása téma hasonlóság alapján",
+	"Read Aloud": "Felolvasás",
+	"Record voice": "Hang rögzítése",
+	"Redirecting you to OpenWebUI Community": "Átirányítás az OpenWebUI közösséghez",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Hivatkozzon magára \"Felhasználó\"-ként (pl. \"A Felhasználó spanyolul tanul\")",
+	"References from": "Hivatkozások innen",
+	"Refused when it shouldn't have": "Elutasítva, amikor nem kellett volna",
+	"Regenerate": "Újragenerálás",
+	"Release Notes": "Kiadási jegyzetek",
+	"Relevance": "Relevancia",
+	"Remove": "Eltávolítás",
+	"Remove Model": "Modell eltávolítása",
+	"Rename": "Átnevezés",
+	"Repeat Last N": "Utolsó N ismétlése",
+	"Request Mode": "Kérési mód",
+	"Reranking Model": "Újrarangsoroló modell",
+	"Reranking model disabled": "Újrarangsoroló modell letiltva",
+	"Reranking model set to \"{{reranking_model}}\"": "Újrarangsoroló modell beállítva erre: \"{{reranking_model}}\"",
+	"Reset": "Visszaállítás",
+	"Reset Upload Directory": "Feltöltési könyvtár visszaállítása",
+	"Reset Vector Storage/Knowledge": "Vektor tárhely/tudásbázis visszaállítása",
+	"Response AutoCopy to Clipboard": "Válasz automatikus másolása a vágólapra",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "A válasz értesítések nem aktiválhatók, mert a weboldal engedélyei meg lettek tagadva. Kérjük, látogasson el a böngésző beállításaihoz a szükséges hozzáférés megadásához.",
+	"Response splitting": "Válasz felosztás",
+	"Result": "Eredmény",
+	"Rich Text Input for Chat": "Formázott szövegbevitel a chathez",
+	"RK": "RK",
+	"Role": "Szerep",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "Futtatás",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Futtassa a Llama 2-t, Code Llama-t és más modelleket. Testreszabhatja és létrehozhatja sajátjait.",
+	"Running": "Fut",
+	"Save": "Mentés",
+	"Save & Create": "Mentés és létrehozás",
+	"Save & Update": "Mentés és frissítés",
+	"Save As Copy": "Mentés másolatként",
+	"Save Tag": "Címke mentése",
+	"Saved": "Mentve",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "A csevegési naplók közvetlen mentése a böngésző tárolójába már nem támogatott. Kérjük, szánjon egy percet a csevegési naplók letöltésére és törlésére az alábbi gomb megnyomásával. Ne aggódjon, könnyen újra importálhatja a csevegési naplókat a backend-be",
+	"Scroll to bottom when switching between branches": "Görgetés az aljára ágak közötti váltáskor",
+	"Search": "Keresés",
+	"Search a model": "Modell keresése",
+	"Search Chats": "Beszélgetések keresése",
+	"Search Collection": "Gyűjtemény keresése",
+	"search for tags": "címkék keresése",
+	"Search Functions": "Funkciók keresése",
+	"Search Knowledge": "Tudásbázis keresése",
+	"Search Models": "Modellek keresése",
+	"Search Prompts": "Promptok keresése",
+	"Search Query Generation Prompt": "Keresési lekérdezés generálási prompt",
+	"Search Result Count": "Keresési találatok száma",
+	"Search Tools": "Eszközök keresése",
+	"SearchApi API Key": "SearchApi API kulcs",
+	"SearchApi Engine": "SearchApi motor",
+	"Searched {{count}} sites_one": "{{count}} oldal keresve",
+	"Searched {{count}} sites_other": "{{count}} oldal keresve",
+	"Searching \"{{searchQuery}}\"": "Keresés: \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "Tudásbázis keresése: \"{{searchQuery}}\"",
+	"Searxng Query URL": "Searxng lekérdezési URL",
+	"See readme.md for instructions": "Lásd a readme.md fájlt az útmutatásért",
+	"See what's new": "Újdonságok megtekintése",
+	"Seed": "Seed",
+	"Select a base model": "Válasszon egy alapmodellt",
+	"Select a engine": "Válasszon egy motort",
+	"Select a file to view or drag and drop a file to upload": "Válasszon ki egy fájlt megtekintésre vagy húzzon ide egy fájlt feltöltéshez",
+	"Select a function": "Válasszon egy funkciót",
+	"Select a model": "Válasszon egy modellt",
+	"Select a pipeline": "Válasszon egy folyamatot",
+	"Select a pipeline url": "Válasszon egy folyamat URL-t",
+	"Select a tool": "Válasszon egy eszközt",
+	"Select an Ollama instance": "Válasszon egy Ollama példányt",
+	"Select Engine": "Motor kiválasztása",
+	"Select Knowledge": "Tudásbázis kiválasztása",
+	"Select model": "Modell kiválasztása",
+	"Select only one model to call": "Csak egy modellt válasszon ki hívásra",
+	"Selected model(s) do not support image inputs": "A kiválasztott modell(ek) nem támogatják a képbemenetet",
+	"Semantic distance to query": "Szemantikai távolság a lekérdezéshez",
+	"Send": "Küldés",
+	"Send a Message": "Üzenet küldése",
+	"Send message": "Üzenet küldése",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "A kérésben elküldi a `stream_options: { include_usage: true }` opciót.\nA támogatott szolgáltatók token használati információt küldenek vissza a válaszban, ha be van állítva.",
+	"September": "Szeptember",
+	"Serper API Key": "Serper API kulcs",
+	"Serply API Key": "Serply API kulcs",
+	"Serpstack API Key": "Serpstack API kulcs",
+	"Server connection verified": "Szerverkapcsolat ellenőrizve",
+	"Set as default": "Beállítás alapértelmezettként",
+	"Set CFG Scale": "CFG skála beállítása",
+	"Set Default Model": "Alapértelmezett modell beállítása",
+	"Set embedding model (e.g. {{model}})": "Beágyazási modell beállítása (pl. {{model}})",
+	"Set Image Size": "Képméret beállítása",
+	"Set reranking model (e.g. {{model}})": "Újrarangsoroló modell beállítása (pl. {{model}})",
+	"Set Sampler": "Mintavételező beállítása",
+	"Set Scheduler": "Ütemező beállítása",
+	"Set Steps": "Lépések beállítása",
+	"Set Task Model": "Feladat modell beállítása",
+	"Set Voice": "Hang beállítása",
+	"Set whisper model": "Whisper modell beállítása",
+	"Settings": "Beállítások",
+	"Settings saved successfully!": "Beállítások sikeresen mentve!",
+	"Share": "Megosztás",
+	"Share Chat": "Beszélgetés megosztása",
+	"Share to OpenWebUI Community": "Megosztás az OpenWebUI közösséggel",
+	"short-summary": "rövid-összefoglaló",
+	"Show": "Mutat",
+	"Show Admin Details in Account Pending Overlay": "Admin részletek megjelenítése a függő fiók átfedésben",
+	"Show Model": "Modell megjelenítése",
+	"Show shortcuts": "Gyorsbillentyűk megjelenítése",
+	"Show your support!": "Mutassa meg támogatását!",
+	"Showcased creativity": "Kreativitás bemutatva",
+	"Sign in": "Bejelentkezés",
+	"Sign in to {{WEBUI_NAME}}": "Bejelentkezés ide: {{WEBUI_NAME}}",
+	"Sign Out": "Kijelentkezés",
+	"Sign up": "Regisztráció",
+	"Sign up to {{WEBUI_NAME}}": "Regisztráció ide: {{WEBUI_NAME}}",
+	"Signing in to {{WEBUI_NAME}}": "Bejelentkezés ide: {{WEBUI_NAME}}",
+	"Source": "Forrás",
+	"Speech Playback Speed": "Beszéd lejátszási sebesség",
+	"Speech recognition error: {{error}}": "Beszédfelismerési hiba: {{error}}",
+	"Speech-to-Text Engine": "Beszéd-szöveg motor",
+	"Stop": "Leállítás",
+	"Stop Sequence": "Leállítási szekvencia",
+	"Stream Chat Response": "Chat válasz streamelése",
+	"STT Model": "STT modell",
+	"STT Settings": "STT beállítások",
+	"Subtitle (e.g. about the Roman Empire)": "Alcím (pl. a Római Birodalomról)",
+	"Success": "Siker",
+	"Successfully updated.": "Sikeresen frissítve.",
+	"Suggested": "Javasolt",
+	"Support": "Támogatás",
+	"Support this plugin:": "Támogassa ezt a bővítményt:",
+	"Sync directory": "Könyvtár szinkronizálása",
+	"System": "Rendszer",
+	"System Instructions": "Rendszer utasítások",
+	"System Prompt": "Rendszer prompt",
+	"Tags": "Címkék",
+	"Tags Generation Prompt": "Címke generálási prompt",
+	"Tap to interrupt": "Koppintson a megszakításhoz",
+	"Tavily API Key": "Tavily API kulcs",
+	"Tell us more:": "Mondjon többet:",
+	"Temperature": "Hőmérséklet",
+	"Template": "Sablon",
+	"Temporary Chat": "Ideiglenes chat",
+	"Text Splitter": "Szöveg felosztó",
+	"Text-to-Speech Engine": "Szöveg-beszéd motor",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Köszönjük a visszajelzést!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "A bővítmény fejlesztői lelkes önkéntesek a közösségből. Ha hasznosnak találja ezt a bővítményt, kérjük, fontolja meg a fejlesztéséhez való hozzájárulást.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "Az értékelési ranglista az Elo értékelési rendszeren alapul és valós időben frissül.",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "A ranglista jelenleg béta verzióban van, és az algoritmus finomítása során módosíthatjuk az értékelési számításokat.",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "A maximális fájlméret MB-ban. Ha a fájlméret meghaladja ezt a limitet, a fájl nem lesz feltöltve.",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "A chatben egyszerre használható fájlok maximális száma. Ha a fájlok száma meghaladja ezt a limitet, a fájlok nem lesznek feltöltve.",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "A pontszámnak 0,0 (0%) és 1,0 (100%) közötti értéknek kell lennie.",
+	"Theme": "Téma",
+	"Thinking...": "Gondolkodik...",
+	"This action cannot be undone. Do you wish to continue?": "Ez a művelet nem vonható vissza. Szeretné folytatni?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ez biztosítja, hogy értékes beszélgetései biztonságosan mentésre kerüljenek a backend adatbázisban. Köszönjük!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Ez egy kísérleti funkció, lehet, hogy nem a várt módon működik és bármikor változhat.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Ez az opció törli az összes meglévő fájlt a gyűjteményben és lecseréli őket az újonnan feltöltött fájlokkal.",
+	"This response was generated by \"{{model}}\"": "Ezt a választ a \"{{model}}\" generálta",
+	"This will delete": "Ez törölni fogja",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "Ez törölni fogja a <strong>{{NAME}}</strong>-t és <strong>minden tartalmát</strong>.",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "Ez visszaállítja a tudásbázist és szinkronizálja az összes fájlt. Szeretné folytatni?",
+	"Thorough explanation": "Alapos magyarázat",
+	"Tika": "Tika",
+	"Tika Server URL required.": "Tika szerver URL szükséges.",
+	"Tiktoken": "Tiktoken",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tipp: Frissítsen több változó helyet egymás után a tab billentyű megnyomásával a chat bevitelben minden helyettesítés után.",
+	"Title": "Cím",
+	"Title (e.g. Tell me a fun fact)": "Cím (pl. Mondj egy érdekes tényt)",
+	"Title Auto-Generation": "Cím automatikus generálása",
+	"Title cannot be an empty string.": "A cím nem lehet üres karakterlánc.",
+	"Title Generation Prompt": "Cím generálási prompt",
+	"To access the available model names for downloading,": "A letölthető modellek nevének eléréséhez,",
+	"To access the GGUF models available for downloading,": "A letölthető GGUF modellek eléréséhez,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "A WebUI eléréséhez kérjük, forduljon az adminisztrátorhoz. Az adminisztrátorok az Admin Panelen keresztül kezelhetik a felhasználói státuszokat.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "A tudásbázis csatolásához először adja hozzá őket a \"Knowledge\" munkaterülethez.",
+	"to chat input.": "a chat beviteli mezőhöz.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "Adatai védelme érdekében a visszajelzésből csak az értékelések, modell azonosítók, címkék és metaadatok kerülnek megosztásra - a chat előzményei privátak maradnak és nem kerülnek megosztásra.",
+	"To select actions here, add them to the \"Functions\" workspace first.": "A műveletek kiválasztásához először adja hozzá őket a \"Functions\" munkaterülethez.",
+	"To select filters here, add them to the \"Functions\" workspace first.": "A szűrők kiválasztásához először adja hozzá őket a \"Functions\" munkaterülethez.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Az eszközkészletek kiválasztásához először adja hozzá őket a \"Tools\" munkaterülethez.",
+	"Toast notifications for new updates": "Felugró értesítések az új frissítésekről",
+	"Today": "Ma",
+	"Toggle settings": "Beállítások be/ki",
+	"Toggle sidebar": "Oldalsáv be/ki",
+	"Token": "Token",
+	"Tokens To Keep On Context Refresh (num_keep)": "Megőrzendő tokenek kontextus frissítéskor (num_keep)",
+	"Too verbose": "Túl bőbeszédű",
+	"Tool": "Eszköz",
+	"Tool created successfully": "Eszköz sikeresen létrehozva",
+	"Tool deleted successfully": "Eszköz sikeresen törölve",
+	"Tool imported successfully": "Eszköz sikeresen importálva",
+	"Tool updated successfully": "Eszköz sikeresen frissítve",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Eszközkészlet leírása (pl. Eszközkészlet különböző műveletek végrehajtásához)",
+	"Toolkit ID (e.g. my_toolkit)": "Eszközkészlet azonosító (pl. my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Eszközkészlet neve (pl. Saját eszközkészlet)",
+	"Tools": "Eszközök",
+	"Tools are a function calling system with arbitrary code execution": "Az eszközök olyan függvényhívó rendszert alkotnak, amely tetszőleges kód végrehajtását teszi lehetővé",
+	"Tools have a function calling system that allows arbitrary code execution": "Az eszközök olyan függvényhívó rendszerrel rendelkeznek, amely lehetővé teszi tetszőleges kód végrehajtását",
+	"Tools have a function calling system that allows arbitrary code execution.": "Az eszközök olyan függvényhívó rendszerrel rendelkeznek, amely lehetővé teszi tetszőleges kód végrehajtását.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problémája van az Ollama elérésével?",
+	"TTS Model": "TTS modell",
+	"TTS Settings": "TTS beállítások",
+	"TTS Voice": "TTS hang",
+	"Type": "Típus",
+	"Type Hugging Face Resolve (Download) URL": "Adja meg a Hugging Face Resolve (Letöltési) URL-t",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Hoppá! Probléma merült fel a {{provider}} kapcsolódás során.",
+	"UI": "Felhasználói felület",
+	"Unpin": "Rögzítés feloldása",
+	"Untagged": "Címkézetlen",
+	"Update": "Frissítés",
+	"Update and Copy Link": "Frissítés és link másolása",
+	"Update for the latest features and improvements.": "Frissítsen a legújabb funkciókért és fejlesztésekért.",
+	"Update password": "Jelszó frissítése",
+	"Updated": "Frissítve",
+	"Updated at": "Frissítve ekkor",
+	"Updated At": "Frissítve ekkor",
+	"Upload": "Feltöltés",
+	"Upload a GGUF model": "GGUF modell feltöltése",
+	"Upload directory": "Könyvtár feltöltése",
+	"Upload files": "Fájlok feltöltése",
+	"Upload Files": "Fájlok feltöltése",
+	"Upload Pipeline": "Pipeline feltöltése",
+	"Upload Progress": "Feltöltési folyamat",
+	"URL Mode": "URL mód",
+	"Use '#' in the prompt input to load and include your knowledge.": "Használja a '#' karaktert a prompt bevitelénél a tudásbázis betöltéséhez és felhasználásához.",
+	"Use Gravatar": "Gravatar használata",
+	"Use Initials": "Monogram használata",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "felhasználó",
+	"User": "Felhasználó",
+	"User location successfully retrieved.": "Felhasználó helye sikeresen lekérve.",
+	"User Permissions": "Felhasználói jogosultságok",
+	"Users": "Felhasználók",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "Az alapértelmezett aréna modell használata az összes modellel. Kattintson a plusz gombra egyéni modellek hozzáadásához.",
+	"Utilize": "Használat",
+	"Valid time units:": "Érvényes időegységek:",
+	"Valves": "Szelepek",
+	"Valves updated": "Szelepek frissítve",
+	"Valves updated successfully": "Szelepek sikeresen frissítve",
+	"variable": "változó",
+	"variable to have them replaced with clipboard content.": "változó, hogy a vágólap tartalmával helyettesítse őket.",
+	"Version": "Verzió",
+	"Version {{selectedVersion}} of {{totalVersions}}": "{{selectedVersion}}. verzió a {{totalVersions}}-ból",
+	"Voice": "Hang",
+	"Voice Input": "Hangbevitel",
+	"Warning": "Figyelmeztetés",
+	"Warning:": "Figyelmeztetés:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Figyelmeztetés: Ha frissíti vagy megváltoztatja a beágyazási modellt, minden dokumentumot újra kell importálnia.",
+	"Web": "Web",
+	"Web API": "Web API",
+	"Web Loader Settings": "Web betöltő beállítások",
+	"Web Search": "Webes keresés",
+	"Web Search Engine": "Webes keresőmotor",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "WebUI beállítások",
+	"WebUI will make requests to": "A WebUI kéréseket fog küldeni ide:",
+	"What’s New in": "",
+	"Whisper (Local)": "Whisper (helyi)",
+	"Widescreen Mode": "Szélesvásznú mód",
+	"Won": "Nyert",
+	"Workspace": "Munkaterület",
+	"Write a prompt suggestion (e.g. Who are you?)": "Írjon egy prompt javaslatot (pl. Ki vagy te?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Írjon egy 50 szavas összefoglalót a [téma vagy kulcsszó]-ról.",
+	"Write something...": "Írjon valamit...",
+	"Yesterday": "Tegnap",
+	"You": "Ön",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Egyszerre maximum {{maxCount}} fájllal tud csevegni.",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Az LLM-ekkel való interakcióit személyre szabhatja emlékek hozzáadásával a lenti 'Kezelés' gomb segítségével, így azok még hasznosabbak és személyre szabottabbak lesznek.",
+	"You cannot clone a base model": "Nem lehet klónozni az alapmodellt",
+	"You cannot upload an empty file.": "Nem tölthet fel üres fájlt.",
+	"You have no archived conversations.": "Nincsenek archivált beszélgetései.",
+	"You have shared this chat": "Megosztotta ezt a beszélgetést",
+	"You're a helpful assistant.": "Ön egy segítőkész asszisztens.",
+	"You're now logged in.": "Sikeresen bejelentkezett.",
+	"Your account status is currently pending activation.": "Fiókja jelenleg aktiválásra vár.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "A teljes hozzájárulása közvetlenül a bővítmény fejlesztőjéhez kerül; az Open WebUI nem vesz le százalékot. Azonban a választott támogatási platformnak lehetnek saját díjai.",
+	"Youtube": "YouTube",
+	"Youtube Loader Settings": "YouTube betöltő beállítások"
+}
diff --git a/src/lib/i18n/locales/id-ID/translation.json b/src/lib/i18n/locales/id-ID/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..f295b0a1563a94f55a5b34ca61a9a1e5d1e93db0
--- /dev/null
+++ b/src/lib/i18n/locales/id-ID/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' atau '-1' untuk tidak ada kedaluwarsa.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(contoh: `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(contoh: `sh webui.sh --api`)",
+	"(latest)": "(terbaru)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Anda tidak dapat menghapus model dasar",
+	"{{user}}'s Chats": "Obrolan {{user}}",
+	"{{webUIName}} Backend Required": "{{webUIName}} Diperlukan Backend",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Model tugas digunakan saat melakukan tugas seperti membuat judul untuk obrolan dan kueri penelusuran web",
+	"a user": "seorang pengguna",
+	"About": "Tentang",
+	"Account": "Akun",
+	"Account Activation Pending": "Aktivasi Akun Tertunda",
+	"Accurate information": "Informasi yang akurat",
+	"Actions": "",
+	"Active Users": "Pengguna Aktif",
+	"Add": "Tambah",
+	"Add a model id": "Tambahkan id model",
+	"Add a short description about what this model does": "Tambahkan deskripsi singkat tentang apa yang dilakukan model ini",
+	"Add a short title for this prompt": "Tambahkan judul singkat untuk prompt ini",
+	"Add a tag": "Menambahkan tag",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Tambahkan prompt khusus",
+	"Add Files": "Menambahkan File",
+	"Add Memory": "Menambahkan Memori",
+	"Add Model": "Tambahkan Model",
+	"Add Tag": "",
+	"Add Tags": "Tambahkan Tag",
+	"Add text content": "",
+	"Add User": "Tambah Pengguna",
+	"Adjusting these settings will apply changes universally to all users.": "Menyesuaikan pengaturan ini akan menerapkan perubahan secara universal ke semua pengguna.",
+	"admin": "admin",
+	"Admin": "Admin",
+	"Admin Panel": "Panel Admin",
+	"Admin Settings": "Pengaturan Admin",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Admin memiliki akses ke semua alat setiap saat; pengguna memerlukan alat yang ditetapkan per model di ruang kerja.",
+	"Advanced Parameters": "Parameter Lanjutan",
+	"Advanced Params": "Parameter Lanjutan",
+	"All chats": "",
+	"All Documents": "Semua Dokumen",
+	"Allow Chat Deletion": "Izinkan Penghapusan Obrolan",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Izinkan suara non-lokal",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "Izinkan Lokasi Pengguna",
+	"Allow Voice Interruption in Call": "Izinkan Gangguan Suara dalam Panggilan",
+	"alphanumeric characters and hyphens": "karakter alfanumerik dan tanda hubung",
+	"Already have an account?": "Sudah memiliki akun?",
+	"an assistant": "asisten",
+	"and": "dan",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "dan membuat tautan bersama baru.",
+	"API Base URL": "URL Dasar API",
+	"API Key": "Kunci API",
+	"API Key created.": "Kunci API dibuat.",
+	"API keys": "Kunci API",
+	"April": "April",
+	"Archive": "Arsipkan",
+	"Archive All Chats": "Arsipkan Semua Obrolan",
+	"Archived Chats": "Obrolan yang Diarsipkan",
+	"are allowed - Activate this command by typing": "diizinkan - Aktifkan perintah ini dengan mengetik",
+	"Are you sure?": "Apakah Anda yakin?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Lampirkan file",
+	"Attention to detail": "Perhatian terhadap detail",
+	"Audio": "Audio",
+	"August": "Agustus",
+	"Auto-playback response": "Respons pemutaran otomatis",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Api Auth String",
+	"AUTOMATIC1111 Base URL": "URL Dasar AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 URL Dasar diperlukan.",
+	"Available list": "",
+	"available!": "tersedia!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Kembali",
+	"Bad Response": "Respons Buruk",
+	"Banners": "Spanduk",
+	"Base Model (From)": "Model Dasar (Dari)",
+	"Batch Size (num_batch)": "Ukuran Batch (num_batch)",
+	"before": "sebelum",
+	"Being lazy": "Menjadi malas",
+	"Brave Search API Key": "Kunci API Pencarian Berani",
+	"Bypass SSL verification for Websites": "Lewati verifikasi SSL untuk Situs Web",
+	"Call": "Panggilan",
+	"Call feature is not supported when using Web STT engine": "Fitur panggilan tidak didukung saat menggunakan mesin Web STT",
+	"Camera": "Kamera",
+	"Cancel": "Batal",
+	"Capabilities": "Kemampuan",
+	"Change Password": "Ubah Kata Sandi",
+	"Character": "",
+	"Chat": "Obrolan",
+	"Chat Background Image": "Gambar Latar Belakang Obrolan",
+	"Chat Bubble UI": "UI Gelembung Obrolan",
+	"Chat Controls": "",
+	"Chat direction": "Arah obrolan",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Obrolan",
+	"Check Again": "Periksa Lagi",
+	"Check for updates": "Memeriksa pembaruan",
+	"Checking for updates...": "Memeriksa pembaruan...",
+	"Choose a model before saving...": "Pilih model sebelum menyimpan...",
+	"Chunk Overlap": "Tumpang Tindih Potongan",
+	"Chunk Params": "Parameter Potongan",
+	"Chunk Size": "Ukuran Potongan",
+	"Citation": "Kutipan",
+	"Clear memory": "Menghapus memori",
+	"Click here for help.": "Klik di sini untuk bantuan.",
+	"Click here to": "Klik di sini untuk",
+	"Click here to download user import template file.": "Klik di sini untuk mengunduh file templat impor pengguna.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Klik di sini untuk memilih",
+	"Click here to select a csv file.": "Klik di sini untuk memilih file csv.",
+	"Click here to select a py file.": "Klik di sini untuk memilih file py.",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "Klik di sini.",
+	"Click on the user role button to change a user's role.": "Klik tombol peran pengguna untuk mengubah peran pengguna.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Izin menulis papan klip ditolak. Periksa pengaturan peramban Anda untuk memberikan akses yang diperlukan.",
+	"Clone": "Kloning",
+	"Close": "Tutup",
+	"Code execution": "",
+	"Code formatted successfully": "Kode berhasil diformat",
+	"Collection": "Koleksi",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL Dasar ComfyUI",
+	"ComfyUI Base URL is required.": "URL Dasar ComfyUI diperlukan.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Perintah",
+	"Completions": "",
+	"Concurrent Requests": "Permintaan Bersamaan",
+	"Confirm": "Konfirmasi",
+	"Confirm Password": "Konfirmasi Kata Sandi",
+	"Confirm your action": "Konfirmasi tindakan Anda",
+	"Connections": "Koneksi",
+	"Contact Admin for WebUI Access": "Hubungi Admin untuk Akses WebUI",
+	"Content": "Konten",
+	"Content Extraction": "",
+	"Context Length": "Panjang Konteks",
+	"Continue Response": "Lanjutkan Tanggapan",
+	"Continue with {{provider}}": "Lanjutkan dengan {{penyedia}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "Menyalin URL obrolan bersama ke papan klip!",
+	"Copied to clipboard": "",
+	"Copy": "Menyalin",
+	"Copy last code block": "Salin blok kode terakhir",
+	"Copy last response": "Salin tanggapan terakhir",
+	"Copy Link": "Salin Tautan",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Penyalinan ke papan klip berhasil!",
+	"Create a model": "Buat model",
+	"Create Account": "Buat Akun",
+	"Create Knowledge": "",
+	"Create new key": "Buat kunci baru",
+	"Create new secret key": "Buat kunci rahasia baru",
+	"Created at": "Dibuat di",
+	"Created At": "Dibuat di",
+	"Created by": "Dibuat oleh",
+	"CSV Import": "Impor CSV",
+	"Current Model": "Model Saat Ini",
+	"Current Password": "Kata Sandi Saat Ini",
+	"Custom": "Kustom",
+	"Customize models for a specific purpose": "Menyesuaikan model untuk tujuan tertentu",
+	"Dark": "Gelap",
+	"Dashboard": "Dasbor",
+	"Database": "Basis data",
+	"December": "Desember",
+	"Default": "Default",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Default (Pengubah Kalimat)",
+	"Default Model": "Model Default",
+	"Default model updated": "Model default diperbarui",
+	"Default Prompt Suggestions": "Saran Permintaan Default",
+	"Default User Role": "Peran Pengguna Default",
+	"Delete": "Menghapus",
+	"Delete a model": "Menghapus model",
+	"Delete All Chats": "Menghapus Semua Obrolan",
+	"Delete chat": "Menghapus obrolan",
+	"Delete Chat": "Menghapus Obrolan",
+	"Delete chat?": "Menghapus obrolan?",
+	"Delete folder?": "",
+	"Delete function?": "Fungsi hapus?",
+	"Delete prompt?": "Perintah hapus?",
+	"delete this link": "hapus tautan ini",
+	"Delete tool?": "Hapus alat?",
+	"Delete User": "Menghapus Pengguna",
+	"Deleted {{deleteModelTag}}": "Menghapus {{deleteModelTag}}",
+	"Deleted {{name}}": "Menghapus {{name}}",
+	"Description": "Deskripsi",
+	"Didn't fully follow instructions": "Tidak sepenuhnya mengikuti instruksi",
+	"Disabled": "",
+	"Discover a function": "Menemukan sebuah fungsi",
+	"Discover a model": "Menemukan sebuah model",
+	"Discover a prompt": "Temukan petunjuk",
+	"Discover a tool": "Menemukan alat",
+	"Discover, download, and explore custom functions": "Menemukan, mengunduh, dan menjelajahi fungsi khusus",
+	"Discover, download, and explore custom prompts": "Temukan, unduh, dan jelajahi prompt khusus",
+	"Discover, download, and explore custom tools": "Menemukan, mengunduh, dan menjelajahi alat khusus",
+	"Discover, download, and explore model presets": "Menemukan, mengunduh, dan menjelajahi preset model",
+	"Dismissible": "Tidak dapat digunakan",
+	"Display Emoji in Call": "Menampilkan Emoji dalam Panggilan",
+	"Display the username instead of You in the Chat": "Menampilkan nama pengguna, bukan Anda di Obrolan",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Dokumen",
+	"Documentation": "Dokumentasi",
+	"Documents": "Dokumen",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "tidak membuat koneksi eksternal apa pun, dan data Anda tetap aman di server yang dihosting secara lokal.",
+	"Don't have an account?": "Tidak memiliki akun?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "Tidak suka gayanya",
+	"Done": "Selesai",
+	"Download": "Unduh",
+	"Download canceled": "Unduh dibatalkan",
+	"Download Database": "Unduh Basis Data",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Letakkan file apa pun di sini untuk ditambahkan ke percakapan",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "misalnya '30-an', '10m'. Satuan waktu yang valid adalah 's', 'm', 'h'.",
+	"Edit": "Edit",
+	"Edit Arena Model": "",
+	"Edit Memory": "Edit Memori",
+	"Edit User": "Edit Pengguna",
+	"ElevenLabs": "",
+	"Email": "Email",
+	"Embedding Batch Size": "Menyematkan Ukuran Batch",
+	"Embedding Model": "Model Penyematan",
+	"Embedding Model Engine": "Mesin Model Penyematan",
+	"Embedding model set to \"{{embedding_model}}\"": "Model penyematan diatur ke \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Aktifkan Berbagi Komunitas",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Aktifkan Pendaftaran Baru",
+	"Enable Web Search": "Aktifkan Pencarian Web",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Pastikan file CSV Anda menyertakan 4 kolom dengan urutan sebagai berikut: Nama, Email, Kata Sandi, Peran.",
+	"Enter {{role}} message here": "Masukkan pesan {{role}} di sini",
+	"Enter a detail about yourself for your LLMs to recall": "Masukkan detail tentang diri Anda untuk diingat oleh LLM Anda",
+	"Enter api auth string (e.g. username:password)": "Masukkan string pengesahan API (misalnya nama pengguna: kata sandi)",
+	"Enter Brave Search API Key": "Masukkan Kunci API Pencarian Berani",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Masukkan Tumpang Tindih Chunk",
+	"Enter Chunk Size": "Masukkan Ukuran Potongan",
+	"Enter description": "",
+	"Enter Github Raw URL": "Masukkan URL Mentah Github",
+	"Enter Google PSE API Key": "Masukkan Kunci API Google PSE",
+	"Enter Google PSE Engine Id": "Masukkan Id Mesin Google PSE",
+	"Enter Image Size (e.g. 512x512)": "Masukkan Ukuran Gambar (mis. 512x512)",
+	"Enter language codes": "Masukkan kode bahasa",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Masukkan tag model (misalnya {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Masukkan Jumlah Langkah (mis. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Masukkan Skor",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Masukkan URL Kueri Searxng",
+	"Enter Serper API Key": "Masukkan Kunci API Serper",
+	"Enter Serply API Key": "Masukkan Kunci API Serply",
+	"Enter Serpstack API Key": "Masukkan Kunci API Serpstack",
+	"Enter stop sequence": "Masukkan urutan berhenti",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "Masukkan Kunci API Tavily",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Masukkan Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Masukkan URL (mis. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Masukkan URL (mis. http://localhost:11434)",
+	"Enter Your Email": "Masukkan Email Anda",
+	"Enter Your Full Name": "Masukkan Nama Lengkap Anda",
+	"Enter your message": "",
+	"Enter Your Password": "Masukkan Kata Sandi Anda",
+	"Enter Your Role": "Masukkan Peran Anda",
+	"Error": "Kesalahan",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Percobaan",
+	"Export": "Ekspor",
+	"Export All Chats (All Users)": "Ekspor Semua Obrolan (Semua Pengguna)",
+	"Export chat (.json)": "Ekspor obrolan (.json)",
+	"Export Chats": "Ekspor Obrolan",
+	"Export Config to JSON File": "",
+	"Export Functions": "Fungsi Ekspor",
+	"Export LiteLLM config.yaml": "Ekspor LiteLLM config.yaml",
+	"Export Models": "Model Ekspor",
+	"Export Prompts": "Perintah Ekspor",
+	"Export Tools": "Alat Ekspor",
+	"External Models": "Model Eksternal",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Gagal membuat API Key.",
+	"Failed to read clipboard contents": "Gagal membaca konten papan klip",
+	"Failed to update settings": "Gagal memperbarui pengaturan",
+	"Failed to upload file.": "",
+	"February": "Februari",
+	"Feedback History": "",
+	"Feel free to add specific details": "Jangan ragu untuk menambahkan detail spesifik",
+	"File": "Berkas",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Mode File",
+	"File not found.": "File tidak ditemukan.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "Filter sekarang dinonaktifkan secara global",
+	"Filter is now globally enabled": "Filter sekarang diaktifkan secara global",
+	"Filters": "Filter",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Pemalsuan sidik jari terdeteksi: Tidak dapat menggunakan inisial sebagai avatar. Default ke gambar profil default.",
+	"Fluidly stream large external response chunks": "Mengalirkan potongan respons eksternal yang besar dengan lancar",
+	"Focus chat input": "Memfokuskan input obrolan",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Mengikuti instruksi dengan sempurna",
+	"Form": "Formulir",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Penalti Frekuensi",
+	"Function": "",
+	"Function created successfully": "Fungsi berhasil dibuat",
+	"Function deleted successfully": "Fungsi berhasil dihapus",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "Fungsi berhasil diperbarui",
+	"Functions": "Fungsi",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "Fungsi berhasil diimpor",
+	"General": "Umum",
+	"General Settings": "Pengaturan Umum",
+	"Generate Image": "Menghasilkan Gambar",
+	"Generating search query": "Membuat kueri penelusuran",
+	"Generation Info": "Info Pembuatan",
+	"Get up and running with": "",
+	"Global": "Global",
+	"Good Response": "Respons yang Baik",
+	"Google PSE API Key": "Kunci API Google PSE",
+	"Google PSE Engine Id": "Id Mesin Google PSE",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "tidak memiliki percakapan.",
+	"Hello, {{name}}": "Halo, {{name}}",
+	"Help": "Bantuan",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Sembunyikan",
+	"Hide Model": "Sembunyikan Model",
+	"How can I help you today?": "Ada yang bisa saya bantu hari ini?",
+	"Hybrid Search": "Pencarian Hibrida",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Pembuatan Gambar (Eksperimental)",
+	"Image Generation Engine": "Mesin Pembuat Gambar",
+	"Image Settings": "Pengaturan Gambar",
+	"Images": "Gambar",
+	"Import Chats": "Impor Obrolan",
+	"Import Config from JSON File": "",
+	"Import Functions": "Fungsi Impor",
+	"Import Models": "Model Impor",
+	"Import Prompts": "Petunjuk Impor",
+	"Import Tools": "Alat Impor",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Sertakan bendera `--api-auth` saat menjalankan stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Sertakan bendera `--api` saat menjalankan stable-diffusion-webui",
+	"Info": "Info",
+	"Input commands": "Perintah masukan",
+	"Install from Github URL": "Instal dari URL Github",
+	"Instant Auto-Send After Voice Transcription": "Kirim Otomatis Instan Setelah Transkripsi Suara",
+	"Interface": "Antarmuka",
+	"Invalid file format.": "",
+	"Invalid Tag": "Tag tidak valid",
+	"January": "Januari",
+	"join our Discord for help.": "bergabunglah dengan Discord kami untuk mendapatkan bantuan.",
+	"JSON": "JSON",
+	"JSON Preview": "Pratinjau JSON",
+	"July": "Juli",
+	"June": "Juni",
+	"JWT Expiration": "Kedaluwarsa JWT",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Tetap Hidup",
+	"Keyboard shortcuts": "Pintasan keyboard",
+	"Knowledge": "Pengetahuan",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Bahasa",
+	"large language models, locally.": "",
+	"Last Active": "Terakhir Aktif",
+	"Last Modified": "Terakhir Dimodifikasi",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Cahaya",
+	"Listening...": "Mendengarkan",
+	"LLMs can make mistakes. Verify important information.": "LLM dapat membuat kesalahan. Verifikasi informasi penting.",
+	"Local Models": "Model Lokal",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Dibuat oleh Komunitas OpenWebUI",
+	"Make sure to enclose them with": "Pastikan untuk melampirkannya dengan",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Mengelola",
+	"Manage Arena Models": "",
+	"Manage Models": "Kelola Model",
+	"Manage Ollama Models": "Mengelola Model Ollama",
+	"Manage Pipelines": "Mengelola Saluran Pipa",
+	"March": "Maret",
+	"Max Tokens (num_predict)": "Token Maksimal (num_prediksi)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maksimal 3 model dapat diunduh secara bersamaan. Silakan coba lagi nanti.",
+	"May": "Mei",
+	"Memories accessible by LLMs will be shown here.": "Memori yang dapat diakses oleh LLM akan ditampilkan di sini.",
+	"Memory": "Memori",
+	"Memory added successfully": "Memori berhasil ditambahkan",
+	"Memory cleared successfully": "Memori berhasil dihapus",
+	"Memory deleted successfully": "Memori berhasil dihapus",
+	"Memory updated successfully": "Memori berhasil diperbarui",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Pesan yang Anda kirim setelah membuat tautan tidak akan dibagikan. Pengguna yang memiliki URL tersebut akan dapat melihat obrolan yang dibagikan.",
+	"Min P": "",
+	"Minimum Score": "Skor Minimum",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH: mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY jj: mm: dd A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' telah berhasil diunduh.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' sudah berada dalam antrean untuk diunduh.",
+	"Model {{modelId}} not found": "Model {{modelId}} tidak ditemukan",
+	"Model {{modelName}} is not vision capable": "Model {{modelName}} tidak dapat dilihat",
+	"Model {{name}} is now {{status}}": "Model {{name}} sekarang menjadi {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "Model berhasil dibuat!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Jalur sistem berkas model terdeteksi. Nama pendek model diperlukan untuk pembaruan, tidak dapat dilanjutkan.",
+	"Model ID": "ID Model",
+	"Model Name": "",
+	"Model not selected": "Model tidak dipilih",
+	"Model Params": "Parameter Model",
+	"Model updated successfully": "Model berhasil diperbarui",
+	"Model Whitelisting": "Daftar Putih Model",
+	"Model(s) Whitelisted": "Model(-model) Masuk Daftar Putih",
+	"Modelfile Content": "Konten File Model",
+	"Models": "Model",
+	"more": "",
+	"More": "Lainnya",
+	"Move to Top": "",
+	"Name": "Nama",
+	"Name your model": "Beri nama model Anda",
+	"New Chat": "Obrolan Baru",
+	"New folder": "",
+	"New Password": "Kata Sandi Baru",
+	"No content found": "",
+	"No content to speak": "Tidak ada konten untuk dibicarakan",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Tidak ada file yang dipilih",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Tidak ada hasil yang ditemukan",
+	"No search query generated": "Tidak ada permintaan pencarian yang dibuat",
+	"No source available": "Tidak ada sumber yang tersedia",
+	"No valves to update": "Tidak ada katup untuk diperbarui",
+	"None": "Tidak ada",
+	"Not factually correct": "Tidak benar secara faktual",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Catatan: Jika Anda menetapkan skor minimum, pencarian hanya akan mengembalikan dokumen dengan skor yang lebih besar atau sama dengan skor minimum.",
+	"Notes": "",
+	"Notifications": "Pemberitahuan",
+	"November": "November",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "ID OAuth",
+	"October": "Oktober",
+	"Off": "Mati",
+	"Okay, Let's Go!": "Oke, Ayo Kita Pergi!",
+	"OLED Dark": "OLED Gelap",
+	"Ollama": "Ollama",
+	"Ollama API": "API Ollama",
+	"Ollama API disabled": "API Ollama dinonaktifkan",
+	"Ollama API is disabled": "API Ollama dinonaktifkan",
+	"Ollama Version": "Versi Ollama",
+	"On": "Aktif",
+	"Only": "Hanya",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Hanya karakter alfanumerik dan tanda hubung yang diizinkan dalam string perintah.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ups! Sepertinya URL tidak valid. Mohon periksa ulang dan coba lagi.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Anda menggunakan metode yang tidak didukung (hanya untuk frontend). Silakan sajikan WebUI dari backend.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Buka obrolan baru",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API OpenAI",
+	"OpenAI API Config": "Konfigurasi API OpenAI",
+	"OpenAI API Key is required.": "Diperlukan Kunci API OpenAI.",
+	"OpenAI URL/Key required.": "Diperlukan URL/Kunci OpenAI.",
+	"or": "atau",
+	"Other": "Lainnya",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Kata sandi",
+	"PDF document (.pdf)": "Dokumen PDF (.pdf)",
+	"PDF Extract Images (OCR)": "Ekstrak Gambar PDF (OCR)",
+	"pending": "tertunda",
+	"Permission denied when accessing media devices": "Izin ditolak saat mengakses perangkat media",
+	"Permission denied when accessing microphone": "Izin ditolak saat mengakses mikrofon",
+	"Permission denied when accessing microphone: {{error}}": "Izin ditolak saat mengakses mikrofon: {{error}}",
+	"Personalization": "Personalisasi",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "Pipeline berhasil dihapus",
+	"Pipeline downloaded successfully": "Saluran pipa berhasil diunduh",
+	"Pipelines": "Saluran pipa",
+	"Pipelines Not Detected": "Saluran Pipa Tidak Terdeteksi",
+	"Pipelines Valves": "Katup Saluran Pipa",
+	"Plain text (.txt)": "Teks biasa (.txt)",
+	"Playground": "Taman bermain",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Sikap positif",
+	"Previous 30 days": "30 hari sebelumnya",
+	"Previous 7 days": "7 hari sebelumnya",
+	"Profile Image": "Gambar Profil",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Permintaan (mis. Ceritakan sebuah fakta menarik tentang Kekaisaran Romawi)",
+	"Prompt Content": "Konten yang Diminta",
+	"Prompt suggestions": "Saran yang diminta",
+	"Prompts": "Prompt",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Tarik \"{{searchValue}}\" dari Ollama.com",
+	"Pull a model from Ollama.com": "Tarik model dari Ollama.com",
+	"Query Params": "Parameter Kueri",
+	"RAG Template": "Templat RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Baca dengan Keras",
+	"Record voice": "Rekam suara",
+	"Redirecting you to OpenWebUI Community": "Mengarahkan Anda ke Komunitas OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Merujuk diri Anda sebagai \"Pengguna\" (misalnya, \"Pengguna sedang belajar bahasa Spanyol\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Menolak ketika seharusnya tidak",
+	"Regenerate": "Regenerasi",
+	"Release Notes": "Catatan Rilis",
+	"Relevance": "",
+	"Remove": "Hapus",
+	"Remove Model": "Hapus Model",
+	"Rename": "Ganti nama",
+	"Repeat Last N": "Ulangi N Terakhir",
+	"Request Mode": "Mode Permintaan",
+	"Reranking Model": "Model Pemeringkatan Ulang",
+	"Reranking model disabled": "Model pemeringkatan ulang dinonaktifkan",
+	"Reranking model set to \"{{reranking_model}}\"": "Model pemeringkatan diatur ke \"{{reranking_model}}\"",
+	"Reset": "Atur Ulang",
+	"Reset Upload Directory": "Setel Ulang Direktori Unggahan",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Tanggapan Salin Otomatis ke Papan Klip",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Notifikasi respons tidak dapat diaktifkan karena izin situs web telah ditolak. Silakan kunjungi pengaturan browser Anda untuk memberikan akses yang diperlukan.",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Peran",
+	"Rosé Pine": "Pinus Rosé",
+	"Rosé Pine Dawn": "Rosé Pine Fajar",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "Berjalan",
+	"Save": "Simpan",
+	"Save & Create": "Simpan & Buat",
+	"Save & Update": "Simpan & Perbarui",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Menyimpan log obrolan secara langsung ke penyimpanan browser Anda tidak lagi didukung. Mohon luangkan waktu sejenak untuk mengunduh dan menghapus log obrolan Anda dengan mengeklik tombol di bawah ini. Jangan khawatir, Anda dapat dengan mudah mengimpor kembali log obrolan Anda ke backend melalui",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Cari",
+	"Search a model": "Mencari model",
+	"Search Chats": "Cari Obrolan",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Fungsi Pencarian",
+	"Search Knowledge": "",
+	"Search Models": "Cari Model",
+	"Search Prompts": "Perintah Pencarian",
+	"Search Query Generation Prompt": "Permintaan Pembuatan Kueri Pencarian",
+	"Search Result Count": "Jumlah Hasil Pencarian",
+	"Search Tools": "Alat Pencarian",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Mencari {{count}} situs_satu",
+	"Searched {{count}} sites_other": "Mencari {{count}} situs_lain",
+	"Searching \"{{searchQuery}}\"": "Mencari \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "URL Kueri Pencarian Searxng",
+	"See readme.md for instructions": "Lihat readme.md untuk instruksi",
+	"See what's new": "Lihat apa yang baru",
+	"Seed": "Benih",
+	"Select a base model": "Pilih model dasar",
+	"Select a engine": "Pilih mesin",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Memilih fungsi",
+	"Select a model": "Pilih model",
+	"Select a pipeline": "Pilih saluran pipa",
+	"Select a pipeline url": "Pilih url saluran pipa",
+	"Select a tool": "Pilih alat",
+	"Select an Ollama instance": "Pilih contoh Ollama",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Pilih model",
+	"Select only one model to call": "Pilih hanya satu model untuk dipanggil",
+	"Selected model(s) do not support image inputs": "Model yang dipilih tidak mendukung input gambar",
+	"Semantic distance to query": "",
+	"Send": "Kirim",
+	"Send a Message": "Kirim Pesan",
+	"Send message": "Kirim pesan",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "September",
+	"Serper API Key": "Kunci API Serper",
+	"Serply API Key": "Kunci API Serply",
+	"Serpstack API Key": "Kunci API Serpstack",
+	"Server connection verified": "Koneksi server diverifikasi",
+	"Set as default": "Ditetapkan sebagai default",
+	"Set CFG Scale": "",
+	"Set Default Model": "Tetapkan Model Default",
+	"Set embedding model (e.g. {{model}})": "Tetapkan model penyematan (mis. {{model}})",
+	"Set Image Size": "Mengatur Ukuran Gambar",
+	"Set reranking model (e.g. {{model}})": "Tetapkan model pemeringkatan ulang (mis. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Tetapkan Langkah",
+	"Set Task Model": "Tetapkan Model Tugas",
+	"Set Voice": "Mengatur Suara",
+	"Set whisper model": "",
+	"Settings": "Pengaturan",
+	"Settings saved successfully!": "Pengaturan berhasil disimpan!",
+	"Share": "Berbagi",
+	"Share Chat": "Bagikan Obrolan",
+	"Share to OpenWebUI Community": "Bagikan ke Komunitas OpenWebUI",
+	"short-summary": "ringkasan singkat",
+	"Show": "Tampilkan",
+	"Show Admin Details in Account Pending Overlay": "Tampilkan Detail Admin di Hamparan Akun Tertunda",
+	"Show Model": "Tampilkan Model",
+	"Show shortcuts": "Tampilkan pintasan",
+	"Show your support!": "Tunjukkan dukungan Anda!",
+	"Showcased creativity": "Menampilkan kreativitas",
+	"Sign in": "Masuk",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Keluar",
+	"Sign up": "Daftar",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Sumber",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Kesalahan pengenalan suara: {{error}}",
+	"Speech-to-Text Engine": "Mesin Pengenal Ucapan ke Teks",
+	"Stop": "",
+	"Stop Sequence": "Hentikan Urutan",
+	"Stream Chat Response": "",
+	"STT Model": "Model STT",
+	"STT Settings": "Pengaturan STT",
+	"Subtitle (e.g. about the Roman Empire)": "Subtitle (misalnya tentang Kekaisaran Romawi)",
+	"Success": "Berhasil",
+	"Successfully updated.": "Berhasil diperbarui.",
+	"Suggested": "Disarankan",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "Sistem",
+	"System Instructions": "",
+	"System Prompt": "Permintaan Sistem",
+	"Tags": "Tag",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Ketuk untuk menyela",
+	"Tavily API Key": "Kunci API Tavily",
+	"Tell us more:": "Beri tahu kami lebih lanjut:",
+	"Temperature": "Suhu",
+	"Template": "Templat",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Mesin Teks-ke-Suara",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Terima kasih atas umpan balik Anda!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Nilai yang diberikan haruslah nilai antara 0,0 (0%) dan 1,0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "Berpikir",
+	"This action cannot be undone. Do you wish to continue?": "Tindakan ini tidak dapat dibatalkan. Apakah Anda ingin melanjutkan?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ini akan memastikan bahwa percakapan Anda yang berharga disimpan dengan aman ke basis data backend. Terima kasih!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Ini adalah fitur eksperimental, mungkin tidak berfungsi seperti yang diharapkan dan dapat berubah sewaktu-waktu.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Ini akan menghapus",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Penjelasan menyeluruh",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tips: Perbarui beberapa slot variabel secara berurutan dengan menekan tombol tab di input obrolan setelah setiap penggantian.",
+	"Title": "Judul",
+	"Title (e.g. Tell me a fun fact)": "Judul (misalnya, Ceritakan sebuah fakta menarik)",
+	"Title Auto-Generation": "Pembuatan Judul Secara Otomatis",
+	"Title cannot be an empty string.": "Judul tidak boleh berupa string kosong.",
+	"Title Generation Prompt": "Perintah Pembuatan Judul",
+	"To access the available model names for downloading,": "Untuk mengakses nama model yang tersedia untuk diunduh,",
+	"To access the GGUF models available for downloading,": "Untuk mengakses model GGUF yang tersedia untuk diunduh,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Untuk mengakses WebUI, hubungi administrator. Admin dapat mengelola status pengguna dari Panel Admin.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "Untuk memasukkan input obrolan.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Untuk memilih filter di sini, tambahkan filter ke ruang kerja \"Fungsi\" terlebih dahulu.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Untuk memilih perangkat di sini, tambahkan ke ruang kerja \"Alat\" terlebih dahulu.",
+	"Toast notifications for new updates": "",
+	"Today": "Hari ini",
+	"Toggle settings": "Beralih pengaturan",
+	"Toggle sidebar": "Beralih bilah sisi",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Token Untuk Menyimpan Penyegaran Konteks (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Alat berhasil dibuat",
+	"Tool deleted successfully": "Alat berhasil dihapus",
+	"Tool imported successfully": "Alat berhasil diimpor",
+	"Tool updated successfully": "Alat berhasil diperbarui",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "Alat",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "K atas",
+	"Top P": "P Atas",
+	"Trouble accessing Ollama?": "Kesulitan mengakses Ollama?",
+	"TTS Model": "Model TTS",
+	"TTS Settings": "Pengaturan TTS",
+	"TTS Voice": "Suara TTS",
+	"Type": "Ketik",
+	"Type Hugging Face Resolve (Download) URL": "Ketik Hugging Face Resolve (Unduh) URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Ada masalah saat menyambung ke {{provider}}.",
+	"UI": "UI",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "Memperbarui",
+	"Update and Copy Link": "Perbarui dan Salin Tautan",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Perbarui kata sandi",
+	"Updated": "",
+	"Updated at": "Diperbarui di",
+	"Updated At": "",
+	"Upload": "Unggah",
+	"Upload a GGUF model": "Unggah model GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Unggah File",
+	"Upload Pipeline": "Unggah Pipeline",
+	"Upload Progress": "Kemajuan Unggah",
+	"URL Mode": "Mode URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Gunakan Gravatar",
+	"Use Initials": "Gunakan Inisial",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "pengguna",
+	"User": "",
+	"User location successfully retrieved.": "Lokasi pengguna berhasil diambil.",
+	"User Permissions": "Izin Pengguna",
+	"Users": "Pengguna",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Memanfaatkan",
+	"Valid time units:": "Unit waktu yang valid:",
+	"Valves": "Katup",
+	"Valves updated": "Katup diperbarui",
+	"Valves updated successfully": "Katup berhasil diperbarui",
+	"variable": "variabel",
+	"variable to have them replaced with clipboard content.": "variabel untuk diganti dengan konten papan klip.",
+	"Version": "Versi",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Suara",
+	"Voice Input": "",
+	"Warning": "Peringatan",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Peringatan: Jika Anda memperbarui atau mengubah model penyematan, Anda harus mengimpor ulang semua dokumen.",
+	"Web": "Web",
+	"Web API": "API Web",
+	"Web Loader Settings": "Pengaturan Pemuat Web",
+	"Web Search": "Pencarian Web",
+	"Web Search Engine": "Mesin Pencari Web",
+	"Webhook URL": "URL pengait web",
+	"WebUI Settings": "Pengaturan WebUI",
+	"WebUI will make requests to": "WebUI akan membuat permintaan ke",
+	"What’s New in": "Apa yang Baru di",
+	"Whisper (Local)": "Bisikan (Lokal)",
+	"Widescreen Mode": "Mode Layar Lebar",
+	"Won": "",
+	"Workspace": "Ruang Kerja",
+	"Write a prompt suggestion (e.g. Who are you?)": "Menulis saran cepat (misalnya Siapa kamu?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Tulis ringkasan dalam 50 kata yang merangkum [topik atau kata kunci].",
+	"Write something...": "",
+	"Yesterday": "Kemarin",
+	"You": "Anda",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Anda dapat mempersonalisasi interaksi Anda dengan LLM dengan menambahkan kenangan melalui tombol 'Kelola' di bawah ini, sehingga lebih bermanfaat dan disesuaikan untuk Anda.",
+	"You cannot clone a base model": "Anda tidak dapat mengkloning model dasar",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Anda tidak memiliki percakapan yang diarsipkan.",
+	"You have shared this chat": "Anda telah membagikan obrolan ini",
+	"You're a helpful assistant.": "Anda adalah asisten yang membantu.",
+	"You're now logged in.": "Anda sekarang sudah masuk.",
+	"Your account status is currently pending activation.": "Status akun Anda saat ini sedang menunggu aktivasi.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Pengaturan Pemuat Youtube"
+}
diff --git a/src/lib/i18n/locales/ie-GA/translation.json b/src/lib/i18n/locales/ie-GA/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..e501dc396b55b350d8430fe89ff263c06a55b4eb
--- /dev/null
+++ b/src/lib/i18n/locales/ie-GA/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' nó '-1' gan aon éag.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(m.sh. `sh webui.sh --api --api-auth username_password `)",
+	"(e.g. `sh webui.sh --api`)": "(m.sh. `sh webui.sh --api`)",
+	"(latest)": "(is déanaí)",
+	"{{ models }}": "{{samhlacha}}",
+	"{{ owner }}: You cannot delete a base model": "{{owner}}: Ní féidir leat bunmhúnla a scriosadh",
+	"{{user}}'s Chats": "Comhráite {{user}}",
+	"{{webUIName}} Backend Required": "{{webUIName}} Ceoldeireadh Riachtanach",
+	"*Prompt node ID(s) are required for image generation": "* Tá ID nód pras ag teastáil chun íomhá a ghiniúint",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "Tá leagan nua (v {{LATEST_VERSION}}) ar fáil anois.",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Úsáidtear samhail tasc agus tascanna á ndéanamh agat mar theidil a ghiniúint do chomhráite agus ceisteanna cuardaigh gréasáin",
+	"a user": "úsáideoir",
+	"About": "Maidir",
+	"Account": "Cuntas",
+	"Account Activation Pending": "Gníomhachtaithe Cuntas",
+	"Accurate information": "Faisnéis chruinn",
+	"Actions": "Gníomhartha",
+	"Active Users": "Úsáideoirí Gníomhacha",
+	"Add": "Cuir",
+	"Add a model id": "Cuir id samhail leis",
+	"Add a short description about what this model does": "Cuir cur síos gairid leis faoin méid a dhéanann an tsamhail seo",
+	"Add a short title for this prompt": "Cuir teideal gairid leis an gpras seo",
+	"Add a tag": "Cuir clib leis",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Cuir pras saincheaptha leis",
+	"Add Files": "Cuir Comhaid",
+	"Add Memory": "Cuir Cuimhne",
+	"Add Model": "Cuir múnla leis",
+	"Add Tag": "Cuir Clib leis",
+	"Add Tags": "Cuir Clibeanna leis",
+	"Add text content": "",
+	"Add User": "Cuir Úsáideoir leis",
+	"Adjusting these settings will apply changes universally to all users.": "Cuirfear na socruithe seo ag coigeartú athruithe go huilíoch ar gach úsáideoir.",
+	"admin": "riarachán",
+	"Admin": "Riarachán",
+	"Admin Panel": "Painéal Riaracháin",
+	"Admin Settings": "Socruithe Riaracháin",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Tá rochtain ag riarthóirí ar gach uirlis i gcónaí; teastaíonn uirlisí sannta in aghaidh an tsamhail sa spás oibre ó úsáideoirí.",
+	"Advanced Parameters": "Paraiméadair Casta",
+	"Advanced Params": "Paraiméid Casta",
+	"All chats": "",
+	"All Documents": "Gach Doiciméad",
+	"Allow Chat Deletion": "Cead Scriosadh Comhrá",
+	"Allow Chat Editing": "Ceadaigh Eagarthóireacht",
+	"Allow non-local voices": "Lig guthanna neamh-áitiúla",
+	"Allow Temporary Chat": "Cead Comhrá Sealadach",
+	"Allow User Location": "Ceadaigh Suíomh Úsáideora",
+	"Allow Voice Interruption in Call": "Ceadaigh Briseadh Guth i nGlao",
+	"alphanumeric characters and hyphens": "carachtair alfauméireacha agus idirlíochtaí",
+	"Already have an account?": "Tá cuntas agat cheana féin?",
+	"an assistant": "cúntóir",
+	"and": "agus",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "agus cruthaigh nasc nua roinnte.",
+	"API Base URL": "URL Bonn API",
+	"API Key": "Eochair API",
+	"API Key created.": "Cruthaíodh Eochair API.",
+	"API keys": "Eochracha API",
+	"April": "Aibreán",
+	"Archive": "Cartlann",
+	"Archive All Chats": "Cartlann Gach Comhrá",
+	"Archived Chats": "Comhráite Cartlann",
+	"are allowed - Activate this command by typing": "ceadaítear - Gníomhachtaigh an t-ordú seo trí chló",
+	"Are you sure?": "An bhfuil tú cinnte?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Ceangail comhad",
+	"Attention to detail": "Aird ar mhionsonraí",
+	"Audio": "Fuaim",
+	"August": "Lúnasa",
+	"Auto-playback response": "Freagra uathsheinm",
+	"Automatic1111": "Uathoibríoch1111",
+	"AUTOMATIC1111 Api Auth String": "UATHOMATIC1111 Api Auth Teaghrán",
+	"AUTOMATIC1111 Base URL": "UATHOMATIC1111 BunURL",
+	"AUTOMATIC1111 Base URL is required.": "Tá URL bonn UATHOMATIC1111 ag teastáil.",
+	"Available list": "Liosta atá ar fáil",
+	"available!": "ar fáil!",
+	"Azure AI Speech": "Óráid Azure AI",
+	"Azure Region": "Réigiún Azure",
+	"Back": "Ar ais",
+	"Bad Response": "Droch-fhreagra",
+	"Banners": "Meirgí",
+	"Base Model (From)": "Múnla Bonn (Ó)",
+	"Batch Size (num_batch)": "Méid Baisc (num_batch)",
+	"before": "roimh",
+	"Being lazy": "A bheith leisciúil",
+	"Brave Search API Key": "Eochair API Cuardaigh Brave",
+	"Bypass SSL verification for Websites": "Seachbhachtar fíorú SSL do Láithreáin",
+	"Call": "Glaoigh",
+	"Call feature is not supported when using Web STT engine": "Ní thacaítear le gné glaonna agus inneall Web STT á úsáid",
+	"Camera": "Ceamara",
+	"Cancel": "Cealaigh",
+	"Capabilities": "Cumais",
+	"Change Password": "Athraigh Pasfhocal",
+	"Character": "",
+	"Chat": "Comhrá",
+	"Chat Background Image": "Íomhá Cúlra Comhrá",
+	"Chat Bubble UI": "Comhrá Bubble UI",
+	"Chat Controls": "Rialuithe Comhrá",
+	"Chat direction": "Treo comhrá",
+	"Chat Overview": "Forbhreathnú ar an",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Comhráite",
+	"Check Again": "Seiceáil Arís",
+	"Check for updates": "Seiceáil nuashonruithe",
+	"Checking for updates...": "Seiceáil le haghaidh nuashonruithe...",
+	"Choose a model before saving...": "Roghnaigh samhail sula sábhálann tú...",
+	"Chunk Overlap": "Forluí smután",
+	"Chunk Params": "Chunk Params",
+	"Chunk Size": "Méid an Píosa",
+	"Citation": "Lua",
+	"Clear memory": "Cuimhne ghlan",
+	"Click here for help.": "Cliceáil anseo le haghaidh cabhair.",
+	"Click here to": "Cliceáil anseo chun",
+	"Click here to download user import template file.": "Cliceáil anseo chun an comhad iompórtála úsáideora a íoslódáil.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Cliceáil anseo chun roghnú",
+	"Click here to select a csv file.": "Cliceáil anseo chun comhad csv a roghnú.",
+	"Click here to select a py file.": "Cliceáil anseo chun comhad py a roghnú.",
+	"Click here to upload a workflow.json file.": "Cliceáil anseo chun comhad workflow.json a uaslódáil.",
+	"click here.": "cliceáil anseo.",
+	"Click on the user role button to change a user's role.": "Cliceáil ar an gcnaipe ról úsáideora chun ról úsáideora a athrú.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Diúltaíodh cead scríofa an ghearrthaisce. Seiceáil socruithe do bhrabhsálaí chun an rochtain riachtanach a dheonú.",
+	"Clone": "Clón",
+	"Close": "Dún",
+	"Code execution": "",
+	"Code formatted successfully": "Cód formáidithe go rathúil",
+	"Collection": "Bailiúchán",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL Bonn ComfyUI",
+	"ComfyUI Base URL is required.": "Teastaíonn URL ComfyUI Base.",
+	"ComfyUI Workflow": "Sreabhadh Oibre ComfyUI",
+	"ComfyUI Workflow Nodes": "Nóid Sreabhadh Oibre ComfyUI",
+	"Command": "Ordú",
+	"Completions": "",
+	"Concurrent Requests": "Iarrataí Comhthéime",
+	"Confirm": "Deimhnigh",
+	"Confirm Password": "Deimhnigh Pasfhocal",
+	"Confirm your action": "Deimhnigh do ghníomh",
+	"Connections": "Naisc",
+	"Contact Admin for WebUI Access": "Déan teagmháil le Riarachán le haghaidh Rochtana WebUI",
+	"Content": "Ábhar",
+	"Content Extraction": "Straibhadh Ábhar",
+	"Context Length": "Fad Comhthéacs",
+	"Continue Response": "Leanúint ar aghaidh",
+	"Continue with {{provider}}": "Lean ar aghaidh le {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "Rialú conas a roinntear téacs teachtaireachta d'iarratais TTS. Roinneann 'poncaíocht' ina abairtí, scoilteann 'míreanna' i míreanna, agus coinníonn 'aon' an teachtaireacht mar shreang amháin.",
+	"Controls": "Rialuithe",
+	"Copied": "Cóipeáladh",
+	"Copied shared chat URL to clipboard!": "Cóipeáladh URL an chomhrá roinnte chuig an ngearrthaisce!",
+	"Copied to clipboard": "Cóipeáilte go gear",
+	"Copy": "Cóipeáil",
+	"Copy last code block": "Cóipeáil bloc cód deireanach",
+	"Copy last response": "Cóipeáil an fhreagairt",
+	"Copy Link": "Cóipeáil Nasc",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "D'éirigh le cóipeáil chuig an ngearrthaisce!",
+	"Create a model": "Cruthaigh samhail",
+	"Create Account": "Cruthaigh Cuntas",
+	"Create Knowledge": "",
+	"Create new key": "Cruthaigh eochair nua",
+	"Create new secret key": "Cruthaigh eochair rúnda nua",
+	"Created at": "Cruthaithe ag",
+	"Created At": "Cruthaithe Ag",
+	"Created by": "Cruthaithe ag",
+	"CSV Import": "Iompórtáil CSV",
+	"Current Model": "Samhail Reatha",
+	"Current Password": "Pasfhocal Reatha",
+	"Custom": "Saincheaptha",
+	"Customize models for a specific purpose": "Samhlacha a shaincheapadh chun críche ar leith",
+	"Dark": "Dorcha",
+	"Dashboard": "Deais",
+	"Database": "Bunachar Sonraí",
+	"December": "Nollaig",
+	"Default": "Réamhshocraithe",
+	"Default (Open AI)": "Réamhshocraithe (Oscail AI)",
+	"Default (SentenceTransformers)": "Réamhshocraithe (SentenceTransFormers)",
+	"Default Model": "Samhail Réamhshocraithe",
+	"Default model updated": "An tsamhail réamhshocraithe",
+	"Default Prompt Suggestions": "Moltaí Pras Réamhshocraithe",
+	"Default User Role": "Ról Úsáideora Réamhshoc",
+	"Delete": "Scrios",
+	"Delete a model": "Scrios samhail",
+	"Delete All Chats": "Scrios Gach Comhrá",
+	"Delete chat": "Scrios comhrá",
+	"Delete Chat": "Scrios Comhrá",
+	"Delete chat?": "Scrios comhrá?",
+	"Delete folder?": "",
+	"Delete function?": "Scrios feidhm?",
+	"Delete prompt?": "Scrios pras?",
+	"delete this link": "scrios an nasc seo",
+	"Delete tool?": "Uirlis a scriosadh?",
+	"Delete User": "Scrios Úsáideoir",
+	"Deleted {{deleteModelTag}}": "Scriosta {{deleteModelTag}}",
+	"Deleted {{name}}": "Scriosta {{name}}",
+	"Description": "Cur síos",
+	"Didn't fully follow instructions": "Níor lean sé treoracha go hiomlán",
+	"Disabled": "Díchumasaithe",
+	"Discover a function": "Faigh amach feidhm",
+	"Discover a model": "Faigh amach samhail",
+	"Discover a prompt": "Faigh amach pras",
+	"Discover a tool": "Faigh amach uirlis",
+	"Discover, download, and explore custom functions": "Faigh amach, íoslódáil agus iniúchadh feidhmeanna saincheaptha",
+	"Discover, download, and explore custom prompts": "Leideanna saincheaptha a fháil amach, a íoslódáil agus a iniúchadh",
+	"Discover, download, and explore custom tools": "Uirlisí saincheaptha a fháil amach, íoslódáil agus iniúchadh",
+	"Discover, download, and explore model presets": "Réamhshocruithe samhail a fháil amach, a íoslódáil agus a iniúchadh",
+	"Dismissible": "Dífhostaithe",
+	"Display Emoji in Call": "Taispeáin Emoji i nGlao",
+	"Display the username instead of You in the Chat": "Taispeáin an t-ainm úsáideora in ionad Tú sa Comhrá",
+	"Do not install functions from sources you do not fully trust.": "Ná suiteáil feidhmeanna ó fhoinsí nach bhfuil muinín iomlán agat.",
+	"Do not install tools from sources you do not fully trust.": "Ná suiteáil uirlisí ó fhoinsí nach bhfuil muinín iomlán agat.",
+	"Document": "Doiciméad",
+	"Documentation": "Doiciméadú",
+	"Documents": "Doiciméid",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "ní dhéanann sé aon naisc sheachtracha, agus fanann do chuid sonraí go slán ar do fhreastalaí a óstáiltear go háitiúil.",
+	"Don't have an account?": "Níl cuntas agat?",
+	"don't install random functions from sources you don't trust.": "ná suiteáil feidhmeanna randamacha ó fhoinsí nach bhfuil muinín agat.",
+	"don't install random tools from sources you don't trust.": "ná suiteáil uirlisí randamacha ó fhoinsí nach bhfuil muinín agat.",
+	"Don't like the style": "Ná thaitníonn an stíl",
+	"Done": "Déanta",
+	"Download": "Íoslódáil",
+	"Download canceled": "Íoslódáil cealaithe",
+	"Download Database": "Íoslódáil Bunachair",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Scaoil aon chomhaid anseo le cur leis an gcomhrá",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "m.sh. '30s', '10m'. Is iad aonaid ama bailí ná 's', 'm', 'h'.",
+	"Edit": "Cuir in eagar",
+	"Edit Arena Model": "",
+	"Edit Memory": "Cuir Cuimhne in eagar",
+	"Edit User": "Cuir Úsáideoir in eagar",
+	"ElevenLabs": "Eleven Labs",
+	"Email": "Ríomhphost",
+	"Embedding Batch Size": "Méid Baisc a ionchorprú",
+	"Embedding Model": "Múnla Leabháilte",
+	"Embedding Model Engine": "Inneall Múnla Ionchorprú",
+	"Embedding model set to \"{{embedding_model}}\"": "Samhail leabaithe atá socraithe go “{{embedding_model}}”",
+	"Enable Community Sharing": "Cumasaigh Comhroinnt Pobail",
+	"Enable Message Rating": "Cumasaigh Rátáil Teachtai",
+	"Enable New Sign Ups": "Cumasaigh Clárúcháin Nua",
+	"Enable Web Search": "Cumasaigh Cuardach Gréasáin",
+	"Enable Web Search Query Generation": "Cumasaigh Giniúint Ceist Cuardaigh Gréasáin",
+	"Enabled": "Cumasaithe",
+	"Engine": "Inneall",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Déan cinnte go bhfuil 4 cholún san ord seo i do chomhad CSV: Ainm, Ríomhphost, Pasfhocal, Ról.",
+	"Enter {{role}} message here": "Cuir isteach teachtaireacht {{role}} anseo",
+	"Enter a detail about yourself for your LLMs to recall": "Cuir isteach mionsonraí fút féin chun do LLManna a mheabhrú",
+	"Enter api auth string (e.g. username:password)": "Cuir isteach sreang auth api (m.sh. ainm úsáideora: pasfhocal)",
+	"Enter Brave Search API Key": "Cuir isteach Eochair API Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "Cuir isteach Scála CFG (m.sh. 7.0)",
+	"Enter Chunk Overlap": "Cuir isteach Chunk Forluí",
+	"Enter Chunk Size": "Cuir isteach Méid an Chunc",
+	"Enter description": "",
+	"Enter Github Raw URL": "Cuir isteach URL Github Raw",
+	"Enter Google PSE API Key": "Cuir isteach Eochair API Google PSE",
+	"Enter Google PSE Engine Id": "Cuir isteach ID Inneall Google PSE",
+	"Enter Image Size (e.g. 512x512)": "Iontráil Méid Íomhá (m.sh. 512x512)",
+	"Enter language codes": "Cuir isteach cóid teanga",
+	"Enter Model ID": "Iontráil ID Mhúnla",
+	"Enter model tag (e.g. {{modelTag}})": "Cuir isteach chlib samhail (m.sh. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Iontráil Líon na gCéimeanna (m.sh. 50)",
+	"Enter Sampler (e.g. Euler a)": "Cuir isteach Sampler (m.sh. Euler a)",
+	"Enter Scheduler (e.g. Karras)": "Cuir isteach Sceidealóir (m.sh. Karras)",
+	"Enter Score": "Iontráil Scór",
+	"Enter SearchApi API Key": "Cuir isteach Eochair API SearchAPI",
+	"Enter SearchApi Engine": "Cuir isteach Inneall SearchAPI",
+	"Enter Searxng Query URL": "Cuir isteach URL Ceist Searxng",
+	"Enter Serper API Key": "Cuir isteach Eochair API Serper",
+	"Enter Serply API Key": "Cuir isteach Eochair API Serply",
+	"Enter Serpstack API Key": "Cuir isteach Eochair API Serpstack",
+	"Enter stop sequence": "Cuir isteach seicheamh stad",
+	"Enter system prompt": "Cuir isteach an chóras pras",
+	"Enter Tavily API Key": "Cuir isteach eochair API Tavily",
+	"Enter Tika Server URL": "Cuir isteach URL freastalaí Tika",
+	"Enter Top K": "Cuir isteach Barr K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Iontráil URL (m.sh. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Iontráil URL (m.sh. http://localhost:11434)",
+	"Enter Your Email": "Cuir isteach do Ríomhphost",
+	"Enter Your Full Name": "Cuir isteach d'Ainm Iomlán",
+	"Enter your message": "Cuir isteach do theachtaireacht",
+	"Enter Your Password": "Cuir isteach do phasfhocal",
+	"Enter Your Role": "Cuir isteach do Ról",
+	"Error": "Earráid",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Turgnamhach",
+	"Export": "Easpórtáil",
+	"Export All Chats (All Users)": "Easpórtáil gach comhrá (Gach Úsáideoir)",
+	"Export chat (.json)": "Easpórtáil comhrá (.json)",
+	"Export Chats": "Comhráite Easpórtá",
+	"Export Config to JSON File": "Easpórtáil Cumraíocht chuig Comhad JSON",
+	"Export Functions": "Feidhmeanna Easp",
+	"Export LiteLLM config.yaml": "Easpórtáil LitellM config.yaml",
+	"Export Models": "Samhlacha Easpórtá",
+	"Export Prompts": "Leideanna Easpórtála",
+	"Export Tools": "Uirlisí Easpór",
+	"External Models": "Samhlacha Seachtracha",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Theip ar an eochair API a chruthú.",
+	"Failed to read clipboard contents": "Theip ar ábhar gearrthaisce a lé",
+	"Failed to update settings": "Theip ar shocruithe a nuashonrú",
+	"Failed to upload file.": "",
+	"February": "Feabhra",
+	"Feedback History": "",
+	"Feel free to add specific details": "Ná bíodh leisce ort sonraí ar leith a chur leis",
+	"File": "Comhad",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Mód Comhad",
+	"File not found.": "Níor aimsíodh an comhad.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "Níor chóir go mbeadh méid an chomhaid níos mó ná {{maxSize}} MB.",
+	"Files": "Comhaid",
+	"Filter is now globally disabled": "Tá an scagaire faoi mhíchumas go domhanda",
+	"Filter is now globally enabled": "Tá an scagaire cumasaithe go domhanda anois",
+	"Filters": "Scagairí",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Braithíodh spoofing méarloirg: Ní féidir teachlitreacha a úsáid mar avatar. Réamhshocrú ar íomhá próifíle réamhshocraithe.",
+	"Fluidly stream large external response chunks": "Sruthaigh codanna móra freagartha seachtracha go sreabhach",
+	"Focus chat input": "Ionchur comhrá fócas",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Lean treoracha go foirfe",
+	"Form": "Foirm",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Pionós Minicíochta",
+	"Function": "",
+	"Function created successfully": "Cruthaíodh feidhm go rathúil",
+	"Function deleted successfully": "Feidhm scriosta go rathúil",
+	"Function Description (e.g. A filter to remove profanity from text)": "Cur síos ar an bhfeidhm (m.sh. scagaire chun neamhshuim a bhaint as téacs)",
+	"Function ID (e.g. my_filter)": "Aitheantas Feidhme (m.sh. my_filter)",
+	"Function is now globally disabled": "Tá an fheidhm faoi mhíchumas go domhanda",
+	"Function is now globally enabled": "Tá feidhm cumasaithe go domhanda anois",
+	"Function Name (e.g. My Filter)": "Ainm Feidhme (m.sh. Mo Scagaire)",
+	"Function updated successfully": "Feidhm nuashonraithe",
+	"Functions": "Feidhmeanna",
+	"Functions allow arbitrary code execution": "Ligeann feidhmeanna forghníomhú cód",
+	"Functions allow arbitrary code execution.": "Ceadaíonn feidhmeanna forghníomhú cód treallach.",
+	"Functions imported successfully": "Feidhmeanna allmhairi",
+	"General": "Ginearálta",
+	"General Settings": "Socruithe Ginearálta",
+	"Generate Image": "Ginigh Íomhá",
+	"Generating search query": "Giniúint ceist cuardaigh",
+	"Generation Info": "Eolas Giniúin",
+	"Get up and running with": "Téigh suas agus ag rith le",
+	"Global": "Domhanda",
+	"Good Response": "Freagra Mhaith",
+	"Google PSE API Key": "Eochair API Google PSE",
+	"Google PSE Engine Id": "ID Inneall Google PSE",
+	"h:mm a": "h: mm a",
+	"Haptic Feedback": "Aiseolas Haptic",
+	"has no conversations.": "níl aon chomhráite aige.",
+	"Hello, {{name}}": "Dia duit, {{name}}",
+	"Help": "Cabhair",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Folaigh",
+	"Hide Model": "Folaigh Múnla",
+	"How can I help you today?": "Conas is féidir liom cabhrú leat inniu?",
+	"Hybrid Search": "Cuardach Hibrideach",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Admhaím gur léigh mé agus tuigim impleachtaí mo ghníomhaíochta. Táim ar an eolas faoi na rioscaí a bhaineann le cód treallach a fhorghníomhú agus tá iontaofacht na foinse fíoraithe agam.",
+	"ID": "",
+	"Image Generation (Experimental)": "Giniúint Íomhá (Turgnaimh)",
+	"Image Generation Engine": "Inneall Giniúna Íomh",
+	"Image Settings": "Socruithe Íomhá",
+	"Images": "Íomhánna",
+	"Import Chats": "Comhráite iompórtá",
+	"Import Config from JSON File": "Cumraíocht Iompórtáil ó Chomhad JSON",
+	"Import Functions": "Feidhmeanna Iom",
+	"Import Models": "Samhlacha iompórtá",
+	"Import Prompts": "Leideanna Iompórtála",
+	"Import Tools": "Uirlisí Iomp",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Cuir bratach `--api-auth` san áireamh agus webui stable-diffusion-reatha á rith",
+	"Include `--api` flag when running stable-diffusion-webui": "Cuir bratach `--api` san áireamh agus webui cobhsaí-scaipthe á rith",
+	"Info": "Eolas",
+	"Input commands": "Orduithe ionchuir",
+	"Install from Github URL": "Suiteáil ó Github URL",
+	"Instant Auto-Send After Voice Transcription": "Seoladh Uathoibríoch Láithreach Tar éis",
+	"Interface": "Comhéadan",
+	"Invalid file format.": "",
+	"Invalid Tag": "Clib neamhbhailí",
+	"January": "Eanáir",
+	"join our Discord for help.": "bí inár Discord chun cabhair a fháil.",
+	"JSON": "JSON",
+	"JSON Preview": "Réamhamharc JSON",
+	"July": "Lúil",
+	"June": "Meitheamh",
+	"JWT Expiration": "Éag JWT",
+	"JWT Token": "Comhartha JWT",
+	"Keep Alive": "Coinnigh Beo",
+	"Keyboard shortcuts": "Aicearraí méarchlár",
+	"Knowledge": "Eolas",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Teanga",
+	"large language models, locally.": "samhlacha teanga móra, go háitiúil.",
+	"Last Active": "Gníomhach Deiridh",
+	"Last Modified": "Athraithe Deiridh",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "Fág folamh le haghaidh neamhtheoranta",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "Fág folamh chun an pras réamhshocraithe a úsáid, nó cuir isteach pras saincheaptha",
+	"Light": "Solas",
+	"Listening...": "Éisteacht...",
+	"LLMs can make mistakes. Verify important information.": "Is féidir le LLManna botúin a dhéanamh. Fíoraigh faisnéis thábhachtach.",
+	"Local Models": "Múnlaí Áitiúla",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Déanta ag OpenWebUI Community",
+	"Make sure to enclose them with": "Déan cinnte iad a cheangal le",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "Déan cinnte comhad workflow.json a onnmhairiú mar fhormáid API ó ComfyUI.",
+	"Manage": "Bainistiú",
+	"Manage Arena Models": "",
+	"Manage Models": "Samhlacha a bhainistiú",
+	"Manage Ollama Models": "Bainistigh Samhlacha Ollama",
+	"Manage Pipelines": "Bainistigh píblín",
+	"March": "Márta",
+	"Max Tokens (num_predict)": "Comharthaí Uasta (num_predicate)",
+	"Max Upload Count": "Líon Uaslódála Max",
+	"Max Upload Size": "Méid Uaslódála Max",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Is féidir uasmhéid de 3 mhúnla a íoslódáil ag an am Bain triail as arís níos déanaí.",
+	"May": "Bealtaine",
+	"Memories accessible by LLMs will be shown here.": "Taispeánfar cuimhní atá inrochtana ag LLManna anseo.",
+	"Memory": "Cuimhne",
+	"Memory added successfully": "Cuireadh cuimhne leis go",
+	"Memory cleared successfully": "Cuimhne glanta go rathúil",
+	"Memory deleted successfully": "Cuimhne scriosta go rathúil",
+	"Memory updated successfully": "Cuimhne nuashonraithe",
+	"Merge Responses": "Cumaisc Freagraí",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Ní roinnfear teachtaireachtaí a sheolann tú tar éis do nasc a chruthú. Beidh úsáideoirí leis an URL in ann féachaint ar an gcomhrá roinnte.",
+	"Min P": "Min P",
+	"Minimum Score": "Scór Íosta",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM LL, BBBB",
+	"MMMM DD, YYYY HH:mm": "MMMM LL, BBBB HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM LL, BBBB hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Rinneadh an tsamhail '{{modelName}}' a íoslódáil go rathúil.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Tá múnla ‘{{modelTag}}’ sa scuaine cheana féin le híoslódáil.",
+	"Model {{modelId}} not found": "Múnla {{modelId}} gan aimsiú",
+	"Model {{modelName}} is not vision capable": "Níl samhail {{modelName}} in ann amharc",
+	"Model {{name}} is now {{status}}": "Tá samhail {{name}} {{status}} anois",
+	"Model {{name}} is now at the top": "Tá múnla {{name}} ag an mbarr anois",
+	"Model accepts image inputs": "Glacann múnla le hionchuir",
+	"Model created successfully!": "Cruthaíodh múnla go rathúil!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Braitheadh \u200b\u200bconair chóras comhad samhail. Teastaíonn mionainm múnla le haghaidh nuashonraithe, ní féidir leanúint ar aghaidh.",
+	"Model ID": "ID Mhúnla",
+	"Model Name": "",
+	"Model not selected": "Múnla nach roghnaíodh",
+	"Model Params": "Múnla Params",
+	"Model updated successfully": "An tsamhail nuashonraithe",
+	"Model Whitelisting": "Liostáil Múnla Bán",
+	"Model(s) Whitelisted": "Múnla/múnlaí atá ar liosta bán",
+	"Modelfile Content": "Ábhar Modelfile",
+	"Models": "Múnlaí",
+	"more": "",
+	"More": "Tuilleadh",
+	"Move to Top": "Bogadh go dtí an Barr",
+	"Name": "Ainm",
+	"Name your model": "Ainmnigh do mhúnla",
+	"New Chat": "Comhrá Nua",
+	"New folder": "",
+	"New Password": "Pasfhocal Nua",
+	"No content found": "",
+	"No content to speak": "Níl aon ábhar le labhairt",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Níl aon chomhad roghnaithe",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Níl aon torthaí le fáil",
+	"No search query generated": "Ní ghintear aon cheist cuardaigh",
+	"No source available": "Níl aon fhoinse ar fáil",
+	"No valves to update": "Gan comhlaí le nuashonrú",
+	"None": "Dada",
+	"Not factually correct": "Níl sé ceart go fírineach",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nóta: Má shocraíonn tú íosscór, ní thabharfaidh an cuardach ach doiciméid a bhfuil scór níos mó ná nó cothrom leis an scór íosta ar ais.",
+	"Notes": "",
+	"Notifications": "Fógraí",
+	"November": "Samhain",
+	"num_gpu (Ollama)": "num_gpu (Ollama)",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "Aitheantas OAuth",
+	"October": "Deireadh Fómhair",
+	"Off": "Lasmuigh",
+	"Okay, Let's Go!": "Ceart go leor, Déanaimis Téigh!",
+	"OLED Dark": "OLED Dorcha",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API faoi mhíchumas",
+	"Ollama API is disabled": "Tá API Ollama míchumasaithe",
+	"Ollama Version": "Leagan Ollama",
+	"On": "Ar",
+	"Only": "Amháin",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Ní cheadaítear ach carachtair alfauméireacha agus braithíní sa sreangán ordaithe.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ups! Is cosúil go bhfuil an URL neamhbhailí. Seiceáil faoi dhó le do thoil agus iarracht arís.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Tá modh gan tacaíocht á úsáid agat (tosaigh amháin). Freastal ar an WebUI ón gcúltaca le do thoil.",
+	"Open file": "Oscail comhad",
+	"Open in full screen": "",
+	"Open new chat": "Oscail comhrá nua",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Tá leagan WebUI oscailte (v{{OPEN_WEBUI_VERSION}}) níos ísle ná an leagan riachtanach (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API OpenAI",
+	"OpenAI API Config": "Cumraíocht API OpenAI",
+	"OpenAI API Key is required.": "Tá Eochair API OpenAI ag teastáil.",
+	"OpenAI URL/Key required.": "Teastaíonn URL/eochair OpenAI.",
+	"or": "nó",
+	"Other": "Eile",
+	"OUTPUT": "",
+	"Output format": "Formáid aschuir",
+	"Overview": "Forbhreathnú",
+	"page": "leathanach",
+	"Password": "Pasfhocal",
+	"PDF document (.pdf)": "Doiciméad PDF (.pdf)",
+	"PDF Extract Images (OCR)": "Íomhánna Sliocht PDF (OCR)",
+	"pending": "ar feitheamh",
+	"Permission denied when accessing media devices": "Cead diúltaithe nuair a bhíonn rochtain agat",
+	"Permission denied when accessing microphone": "Cead diúltaithe agus tú ag rochtain ar",
+	"Permission denied when accessing microphone: {{error}}": "Cead diúltaithe agus tú ag teacht ar mhicreafón: {{error}}",
+	"Personalization": "Pearsantú",
+	"Pin": "Bioráin",
+	"Pinned": "Pinneáilte",
+	"Pipeline deleted successfully": "Scriosta píblíne go rathúil",
+	"Pipeline downloaded successfully": "Íoslódáilte píblíne",
+	"Pipelines": "Píblínte",
+	"Pipelines Not Detected": "Níor aimsíodh píblínte",
+	"Pipelines Valves": "Comhlaí Píblíne",
+	"Plain text (.txt)": "Téacs simplí (.txt)",
+	"Playground": "Clós súgartha",
+	"Please carefully review the following warnings:": "Déan athbhreithniú cúramach ar na rabhaidh seo a leanas le do thoil:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "Roghnaigh cúis le do thoil",
+	"Positive attitude": "Dearcadh dearfach",
+	"Previous 30 days": "30 lá roimhe seo",
+	"Previous 7 days": "7 lá roimhe seo",
+	"Profile Image": "Íomhá Próifíl",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Pras (m.sh. inis dom fíric spraíúil faoin Impireacht Rómhánach)",
+	"Prompt Content": "Ábhar Pras",
+	"Prompt suggestions": "Moltaí pras",
+	"Prompts": "Leabhair",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Tarraing “{{searchValue}}” ó Ollama.com",
+	"Pull a model from Ollama.com": "Tarraing samhail ó Ollama.com",
+	"Query Params": "Fiosrúcháin Params",
+	"RAG Template": "Teimpléad RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Léigh Ard",
+	"Record voice": "Taifead guth",
+	"Redirecting you to OpenWebUI Community": "Tú a atreorú chuig OpenWebUI Community",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Tagairt duit féin mar “Úsáideoir” (m.sh., “Tá an úsáideoir ag foghlaim Spáinnis”)",
+	"References from": "",
+	"Refused when it shouldn't have": "Diúltaíodh nuair nár chóir dó",
+	"Regenerate": "Athghiniúint",
+	"Release Notes": "Nótaí Scaoilte",
+	"Relevance": "",
+	"Remove": "Bain",
+	"Remove Model": "Bain Múnla",
+	"Rename": "Athainmnigh",
+	"Repeat Last N": "Déan an N deireanach arís",
+	"Request Mode": "Mód Iarratais",
+	"Reranking Model": "Múnla Athrangú",
+	"Reranking model disabled": "Samhail athrangú faoi mhíchumas",
+	"Reranking model set to \"{{reranking_model}}\"": "Samhail athrangú socraithe go “{{reranking_model}}”",
+	"Reset": "Athshocraigh",
+	"Reset Upload Directory": "Athshocraigh Eolaire Uas",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Freagra AutoCopy go Gearrthaisce",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Ní féidir fógraí freagartha a ghníomhachtú toisc gur diúltaíodh ceadanna an tsuímh Ghréasáin. Tabhair cuairt ar do shocruithe brabhsálaí chun an rochtain riachtanach a dheonú.",
+	"Response splitting": "Scoilt freagartha",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Ról",
+	"Rosé Pine": "Pine Rosé",
+	"Rosé Pine Dawn": "Rose Pine Dawn",
+	"RTL": "RTL",
+	"Run": "Rith",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Rith Llama 2, Code Llama, agus samhlacha eile. Saincheapadh agus cruthaigh do chuid féin.",
+	"Running": "Ag rith",
+	"Save": "Sábháil",
+	"Save & Create": "Sábháil & Cruthaigh",
+	"Save & Update": "Sábháil & Nuashonraigh",
+	"Save As Copy": "Sábháil Mar Cóip",
+	"Save Tag": "Sábháil Clib",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Ní thacaítear le logaí comhrá a shábháil go díreach chuig stóráil do bhrabhsálaí Tóg nóiméad chun do logaí comhrá a íoslódáil agus a scriosadh trí chliceáil an cnaipe thíos. Ná bíodh imní ort, is féidir leat do logaí comhrá a athiompórtáil go héasca chuig an gcúltaca trí",
+	"Scroll to bottom when switching between branches": "Scrollaigh go bun agus tú ag athrú idir brainsí",
+	"Search": "Cuardaigh",
+	"Search a model": "Cuardaigh samhail",
+	"Search Chats": "Cuardaigh Comhráite",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Feidhmeanna Cuardaigh",
+	"Search Knowledge": "",
+	"Search Models": "Múnlaí Cuardaigh",
+	"Search Prompts": "Leideanna Cuardaigh",
+	"Search Query Generation Prompt": "Pras Giniúint Ceist Cuardaigh",
+	"Search Result Count": "Líon Torthaí Cuardaigh",
+	"Search Tools": "Uirlisí Cuardaigh",
+	"SearchApi API Key": "Eochair API SearchAPI",
+	"SearchApi Engine": "Inneall SearchAPI",
+	"Searched {{count}} sites_one": "Cuardaigh {{count}} sites_one",
+	"Searched {{count}} sites_other": "Cuardaigh {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "Ag cuardach “{{searchQuery}}”",
+	"Searching Knowledge for \"{{searchQuery}}\"": "Cuardach Eolas do “{{searchQuery}}”",
+	"Searxng Query URL": "URL ceisteanna cuardaigh",
+	"See readme.md for instructions": "Féach readme.md le haghaidh treoracha",
+	"See what's new": "Féach cad atá nua",
+	"Seed": "Síol",
+	"Select a base model": "Roghnaigh bunmhúnla",
+	"Select a engine": "Roghnaigh inneall",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Roghnaigh feidhm",
+	"Select a model": "Roghnaigh samhail",
+	"Select a pipeline": "Roghnaigh píblíne",
+	"Select a pipeline url": "Roghnaigh url píblíne",
+	"Select a tool": "Roghnaigh uirlis",
+	"Select an Ollama instance": "Roghnaigh sampla Ollama",
+	"Select Engine": "Roghnaigh Inneall",
+	"Select Knowledge": "",
+	"Select model": "Roghnaigh samhail",
+	"Select only one model to call": "Roghnaigh ach samhail amháin le glaoch",
+	"Selected model(s) do not support image inputs": "Ní thacaíonn samhail(í) roghnaithe le hionchur íomhá",
+	"Semantic distance to query": "",
+	"Send": "Seol",
+	"Send a Message": "Seol Teachtaireacht",
+	"Send message": "Seol teachtaireacht",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "Seolann `stream_options: { include_usage: true }` san iarratas.\nTabharfaidh soláthraithe a fhaigheann tacaíocht faisnéis úsáide chomharthaí ar ais sa fhreagra nuair a bheidh sé socraithe.",
+	"September": "Meán Fómhair",
+	"Serper API Key": "Serper API Eochair",
+	"Serply API Key": "Eochair API Serply",
+	"Serpstack API Key": "Eochair API Serpstack",
+	"Server connection verified": "Ceangal freastalaí fíoraithe",
+	"Set as default": "Socraigh mar réamhshocraithe",
+	"Set CFG Scale": "Socraigh Scála CFG",
+	"Set Default Model": "Socraigh Samhail Réamhshocrú",
+	"Set embedding model (e.g. {{model}})": "Socraigh samhail leabaithe (m.sh. {{model}})",
+	"Set Image Size": "Socraigh Méid Íomhá",
+	"Set reranking model (e.g. {{model}})": "Socraigh samhail athrangú (m.sh. {{model}})",
+	"Set Sampler": "Socraigh Sampler",
+	"Set Scheduler": "Socraigh Sceidealóir",
+	"Set Steps": "Socraigh Céimeanna",
+	"Set Task Model": "Socraigh Samhail Tasc",
+	"Set Voice": "Socraigh Guth",
+	"Set whisper model": "",
+	"Settings": "Socruithe",
+	"Settings saved successfully!": "Socruithe sábhálta go rathúil!",
+	"Share": "Comhroinn",
+	"Share Chat": "Comhroinn Comhrá",
+	"Share to OpenWebUI Community": "Comhroinn le Pobal OpenWebUI",
+	"short-summary": "achoimre gearr",
+	"Show": "Taispeáin",
+	"Show Admin Details in Account Pending Overlay": "Taispeáin Sonraí Riaracháin sa Chuntas ar Feitheamh Forleagan",
+	"Show Model": "Taispeáin Múnla",
+	"Show shortcuts": "Taispeáin aicearraí",
+	"Show your support!": "Taispeáin do thacaíocht!",
+	"Showcased creativity": "Cruthaitheacht léirithe",
+	"Sign in": "Sínigh isteach",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Sínigh Amach",
+	"Sign up": "Cláraigh",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Foinse",
+	"Speech Playback Speed": "Luas Athsheinm Urlabhra",
+	"Speech recognition error: {{error}}": "Earráid aitheantais cainte: {{error}}",
+	"Speech-to-Text Engine": "Inneall Cainte-go-Téacs",
+	"Stop": "",
+	"Stop Sequence": "Stop Seicheamh",
+	"Stream Chat Response": "Freagra Comhrá Sruth",
+	"STT Model": "Múnla STT",
+	"STT Settings": "Socruithe STT",
+	"Subtitle (e.g. about the Roman Empire)": "Fotheideal (m.sh. faoin Impireacht Rómhánach)",
+	"Success": "Rath",
+	"Successfully updated.": "Nuashonraithe go rathúil.",
+	"Suggested": "Molta",
+	"Support": "Tacaíocht",
+	"Support this plugin:": "Tacaigh leis an mbreiseán seo:",
+	"Sync directory": "",
+	"System": "Córas",
+	"System Instructions": "",
+	"System Prompt": "Córas Pras",
+	"Tags": "Clibeanna",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Tapáil chun cur isteach",
+	"Tavily API Key": "Eochair API Tavily",
+	"Tell us more:": "Inis dúinn níos mó:",
+	"Temperature": "Teocht",
+	"Template": "Teimpléad",
+	"Temporary Chat": "Comhrá Sealadach",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Inneall téacs-go-labhra",
+	"Tfs Z": "TFS Z",
+	"Thanks for your feedback!": "Go raibh maith agat as do chuid aiseolas!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Is deonacha paiseanta ón bpobal iad na forbróirí taobh thiar den bhreiseán seo. Má aimsíonn an breiseán seo cabhrach leat, smaoinigh ar rannchuidiú lena fhorbairt.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "Uasmhéid an chomhaid i MB. Má sháraíonn méid an chomhaid an teorainn seo, ní uaslódófar an comhad.",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "An líon uasta na gcomhaid is féidir a úsáid ag an am céanna i gcomhrá. Má sháraíonn líon na gcomhaid an teorainn seo, ní uaslódófar na comhaid.",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Ba chóir go mbeadh an scór ina luach idir 0.0 (0%) agus 1.0 (100%).",
+	"Theme": "Téama",
+	"Thinking...": "Smaointeoireacht...",
+	"This action cannot be undone. Do you wish to continue?": "Ní féidir an gníomh seo a chur ar ais. Ar mhaith leat leanúint ar aghaidh?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Cinntíonn sé seo go sábhálfar do chomhráite luachmhara go daingean i do bhunachar sonraí cúltaca Go raibh maith agat!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Is gné turgnamhach í seo, b'fhéidir nach bhfeidhmeoidh sé mar a bhíothas ag súil leis agus tá sé faoi réir athraithe ag am ar bith.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Scriosfaidh sé seo",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Míniú críochnúil",
+	"Tika": "Tika",
+	"Tika Server URL required.": "Teastaíonn URL Freastalaí Tika.",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Leid: Nuashonraigh sliotáin iolracha athróg as a chéile trí bhrú ar an eochair cluaisín san ionchur comhrá tar éis gach athsholáthair.",
+	"Title": "Teideal",
+	"Title (e.g. Tell me a fun fact)": "Teideal (m.sh. inis dom fíric spraíúil)",
+	"Title Auto-Generation": "Teideal Auto-Generation",
+	"Title cannot be an empty string.": "Ní féidir leis an teideal a bheith ina teaghrán folamh.",
+	"Title Generation Prompt": "Pras Giniúint Teideal",
+	"To access the available model names for downloading,": "Chun teacht ar na hainmneacha samhlacha atá ar fáil le híoslódáil,",
+	"To access the GGUF models available for downloading,": "Chun rochtain a fháil ar na samhlacha GGUF atá ar fáil le híoslódáil,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Chun rochtain a fháil ar an WebUI, déan teagmháil leis an riarthóir le do thoil. Is féidir le riarthóirí stádas úsáideora a bhainistiú ón bPainéal Riaracháin.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "chun ionchur comhrá.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Chun gníomhartha a roghnú anseo, cuir iad leis an spás oibre “Feidhmeanna” ar dtús.",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Chun scagairí a roghnú anseo, cuir iad leis an spás oibre “Feidhmeanna” ar dtús.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Chun trealamh uirlisí a roghnú anseo, cuir iad leis an spás oibre “Uirlisí” ar dtús.",
+	"Toast notifications for new updates": "",
+	"Today": "Inniu",
+	"Toggle settings": "Socraigh socruithe",
+	"Toggle sidebar": "Athraigh barra taobh",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Comharthaí le Coinneáil ar Athnuachan Comhthéacs (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Uirlis cruthaithe go rathúil",
+	"Tool deleted successfully": "Uirlis scriosta go rathúil",
+	"Tool imported successfully": "Uirlis iompórtáilte",
+	"Tool updated successfully": "An uirlis nuashonraithe",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Cur síos ar an bhFoireann Uirlisí (m.sh. Foireann uirlisí chun oibríochtaí éagsúla a dhéanamh)",
+	"Toolkit ID (e.g. my_toolkit)": "Aitheantas Foireann Uirlisí (m.sh. my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Ainm an Fhoireann Uirlisí (m.sh. Mo ToolKit)",
+	"Tools": "Uirlisí",
+	"Tools are a function calling system with arbitrary code execution": "Is córas glaonna feidhme iad uirlisí le forghníomhú cód treallach",
+	"Tools have a function calling system that allows arbitrary code execution": "Tá córas glaonna feidhme ag uirlisí a cheadaíonn forghníomhú cód treallach",
+	"Tools have a function calling system that allows arbitrary code execution.": "Tá córas glaonna feidhme ag uirlisí a cheadaíonn forghníomhú cód treallach.",
+	"Top K": "Barr K",
+	"Top P": "Barr P",
+	"Trouble accessing Ollama?": "Deacracht teacht ar Ollama?",
+	"TTS Model": "TTS Múnla",
+	"TTS Settings": "Socruithe TTS",
+	"TTS Voice": "Guth TTS",
+	"Type": "Cineál",
+	"Type Hugging Face Resolve (Download) URL": "Cineál Hugging Face Resolve (Íoslódáil) URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Bhí ceist ann ag nascadh le {{provider}}.",
+	"UI": "UI",
+	"Unpin": "Díphoráil",
+	"Untagged": "",
+	"Update": "Nuashonraigh",
+	"Update and Copy Link": "Nuashonraigh agus Cóipeáil Nasc",
+	"Update for the latest features and improvements.": "Nuashonrú le haghaidh na gnéithe agus na feabhsuithe is déanaí.",
+	"Update password": "Nuashonrú pasfhocal",
+	"Updated": "",
+	"Updated at": "Nuashonraithe ag",
+	"Updated At": "",
+	"Upload": "Uaslódáil",
+	"Upload a GGUF model": "Uaslódáil samhail GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Uaslódáil Comhaid",
+	"Upload Pipeline": "Uaslódáil píblíne",
+	"Upload Progress": "Uaslódáil an Dul",
+	"URL Mode": "Mód URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Úsáid Gravatar",
+	"Use Initials": "Úsáid ceannlitreacha",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "úsáideoir",
+	"User": "",
+	"User location successfully retrieved.": "Fuarthas suíomh an úsáideora go rathúil.",
+	"User Permissions": "Ceadanna Úsáideora",
+	"Users": "Úsáideoirí",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Úsáid",
+	"Valid time units:": "Aonaid ama bailí:",
+	"Valves": "Comhlaí",
+	"Valves updated": "Comhlaí dáta",
+	"Valves updated successfully": "Comhlaí nuashonraíodh",
+	"variable": "athraitheach",
+	"variable to have them replaced with clipboard content.": "athróg chun ábhar gearrthaisce a chur in ionad iad.",
+	"Version": "Leagan",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Guth",
+	"Voice Input": "",
+	"Warning": "Rabhadh",
+	"Warning:": "Rabhadh:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Rabhadh: Má nuashonraíonn tú nó má athraíonn tú do mhúnla leabaithe, beidh ort gach doiciméad a athiompórtáil.",
+	"Web": "Gréasán",
+	"Web API": "API Gréasáin",
+	"Web Loader Settings": "Socruithe Luchtaire Gréasáin",
+	"Web Search": "Cuardach Gréasáin",
+	"Web Search Engine": "Inneall Cuardaigh Gréasáin",
+	"Webhook URL": "URL Webhook",
+	"WebUI Settings": "Socruithe WebUI",
+	"WebUI will make requests to": "Déanfaidh WebUI iarratais chuig",
+	"What’s New in": "Cad atá Nua i",
+	"Whisper (Local)": "Whisper (Áitiúil)",
+	"Widescreen Mode": "Mód Leathanscáileán",
+	"Won": "",
+	"Workspace": "Spás oibre",
+	"Write a prompt suggestion (e.g. Who are you?)": "Scríobh moladh pras (m.sh. Cé hé tú?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Scríobh achoimre i 50 focal a dhéanann achoimre ar [ábhar nó eochairfhocal].",
+	"Write something...": "",
+	"Yesterday": "Inné",
+	"You": "Tú",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Ní féidir leat comhrá a dhéanamh ach le comhad {{maxCount}} ar a mhéad ag an am.",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Is féidir leat do chuid idirghníomhaíochtaí le LLManna a phearsantú ach cuimhní cinn a chur leis tríd an gcnaipe 'Bainistigh' thíos, rud a fhágann go mbeidh siad níos cabhrach agus níos oiriúnaí duit.",
+	"You cannot clone a base model": "Ní féidir leat bunmhúnla a chlónáil",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Níl aon chomhráite cartlainne agat.",
+	"You have shared this chat": "Tá an comhrá seo roinnte agat",
+	"You're a helpful assistant.": "Is cúntóir cabhrach tú.",
+	"You're now logged in.": "Tá tú logáilte isteach anois.",
+	"Your account status is currently pending activation.": "Tá stádas do chuntais ar feitheamh faoi ghníomhachtú.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Rachaidh do ranníocaíocht iomlán go díreach chuig an bhforbróir breiseán; Ní ghlacann Open WebUI aon chéatadán. Mar sin féin, d'fhéadfadh a tháillí féin a bheith ag an ardán maoinithe roghnaithe.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Socruithe Luchtaire Youtube"
+}
diff --git a/src/lib/i18n/locales/it-IT/translation.json b/src/lib/i18n/locales/it-IT/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..53f741a6d82e6fac8ccebda4d78f757260e53042
--- /dev/null
+++ b/src/lib/i18n/locales/it-IT/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' o '-1' per nessuna scadenza.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(p.e. `sh webui.sh --api`)",
+	"(latest)": "(ultima)",
+	"{{ models }}": "{{ modelli }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: non è possibile eliminare un modello di base",
+	"{{user}}'s Chats": "{{user}} Chat",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend richiesto",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un modello di attività viene utilizzato durante l'esecuzione di attività come la generazione di titoli per chat e query di ricerca Web",
+	"a user": "un utente",
+	"About": "Informazioni",
+	"Account": "Account",
+	"Account Activation Pending": "",
+	"Accurate information": "Informazioni accurate",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "Aggiungi",
+	"Add a model id": "Aggiungere un ID modello",
+	"Add a short description about what this model does": "Aggiungi una breve descrizione di ciò che fa questo modello",
+	"Add a short title for this prompt": "Aggiungi un titolo breve per questo prompt",
+	"Add a tag": "Aggiungi un tag",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Aggiungi un prompt custom",
+	"Add Files": "Aggiungi file",
+	"Add Memory": "Aggiungi memoria",
+	"Add Model": "Aggiungi modello",
+	"Add Tag": "",
+	"Add Tags": "Aggiungi tag",
+	"Add text content": "",
+	"Add User": "Aggiungi utente",
+	"Adjusting these settings will apply changes universally to all users.": "La modifica di queste impostazioni applicherà le modifiche universalmente a tutti gli utenti.",
+	"admin": "amministratore",
+	"Admin": "",
+	"Admin Panel": "Pannello di amministrazione",
+	"Admin Settings": "Impostazioni amministratore",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "Parametri avanzati",
+	"Advanced Params": "Parametri avanzati",
+	"All chats": "",
+	"All Documents": "Tutti i documenti",
+	"Allow Chat Deletion": "Consenti l'eliminazione della chat",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "caratteri alfanumerici e trattini",
+	"Already have an account?": "Hai già un account?",
+	"an assistant": "un assistente",
+	"and": "e",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "e crea un nuovo link condiviso.",
+	"API Base URL": "URL base API",
+	"API Key": "Chiave API",
+	"API Key created.": "Chiave API creata.",
+	"API keys": "Chiavi API",
+	"April": "Aprile",
+	"Archive": "Archivio",
+	"Archive All Chats": "Archivia tutte le chat",
+	"Archived Chats": "Chat archiviate",
+	"are allowed - Activate this command by typing": "sono consentiti - Attiva questo comando digitando",
+	"Are you sure?": "Sei sicuro?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Allega file",
+	"Attention to detail": "Attenzione ai dettagli",
+	"Audio": "Audio",
+	"August": "Agosto",
+	"Auto-playback response": "Riproduzione automatica della risposta",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "URL base AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "L'URL base AUTOMATIC1111 è obbligatorio.",
+	"Available list": "",
+	"available!": "disponibile!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Indietro",
+	"Bad Response": "Risposta non valida",
+	"Banners": "Banner",
+	"Base Model (From)": "Modello base (da)",
+	"Batch Size (num_batch)": "",
+	"before": "prima",
+	"Being lazy": "Essere pigri",
+	"Brave Search API Key": "Chiave API di ricerca Brave",
+	"Bypass SSL verification for Websites": "Aggira la verifica SSL per i siti web",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "Annulla",
+	"Capabilities": "Funzionalità",
+	"Change Password": "Cambia password",
+	"Character": "",
+	"Chat": "Chat",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "UI bolle chat",
+	"Chat Controls": "",
+	"Chat direction": "Direzione chat",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Chat",
+	"Check Again": "Controlla di nuovo",
+	"Check for updates": "Controlla aggiornamenti",
+	"Checking for updates...": "Controllo aggiornamenti...",
+	"Choose a model before saving...": "Scegli un modello prima di salvare...",
+	"Chunk Overlap": "Sovrapposizione chunk",
+	"Chunk Params": "Parametri chunk",
+	"Chunk Size": "Dimensione chunk",
+	"Citation": "Citazione",
+	"Clear memory": "",
+	"Click here for help.": "Clicca qui per aiuto.",
+	"Click here to": "Clicca qui per",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Clicca qui per selezionare",
+	"Click here to select a csv file.": "Clicca qui per selezionare un file csv.",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "clicca qui.",
+	"Click on the user role button to change a user's role.": "Clicca sul pulsante del ruolo utente per modificare il ruolo di un utente.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "Clone",
+	"Close": "Chiudi",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Collezione",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL base ComfyUI",
+	"ComfyUI Base URL is required.": "L'URL base ComfyUI è obbligatorio.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Comando",
+	"Completions": "",
+	"Concurrent Requests": "Richieste simultanee",
+	"Confirm": "",
+	"Confirm Password": "Conferma password",
+	"Confirm your action": "",
+	"Connections": "Connessioni",
+	"Contact Admin for WebUI Access": "",
+	"Content": "Contenuto",
+	"Content Extraction": "",
+	"Context Length": "Lunghezza contesto",
+	"Continue Response": "Continua risposta",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "URL della chat condivisa copiato negli appunti!",
+	"Copied to clipboard": "",
+	"Copy": "Copia",
+	"Copy last code block": "Copia ultimo blocco di codice",
+	"Copy last response": "Copia ultima risposta",
+	"Copy Link": "Copia link",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Copia negli appunti riuscita!",
+	"Create a model": "Creare un modello",
+	"Create Account": "Crea account",
+	"Create Knowledge": "",
+	"Create new key": "Crea nuova chiave",
+	"Create new secret key": "Crea nuova chiave segreta",
+	"Created at": "Creato il",
+	"Created At": "Creato il",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "Modello corrente",
+	"Current Password": "Password corrente",
+	"Custom": "Personalizzato",
+	"Customize models for a specific purpose": "Personalizza i modelli per uno scopo specifico",
+	"Dark": "Scuro",
+	"Dashboard": "",
+	"Database": "Database",
+	"December": "Dicembre",
+	"Default": "Predefinito",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Predefinito (SentenceTransformers)",
+	"Default Model": "Modello di default",
+	"Default model updated": "Modello predefinito aggiornato",
+	"Default Prompt Suggestions": "Suggerimenti prompt predefiniti",
+	"Default User Role": "Ruolo utente predefinito",
+	"Delete": "Elimina",
+	"Delete a model": "Elimina un modello",
+	"Delete All Chats": "Elimina tutte le chat",
+	"Delete chat": "Elimina chat",
+	"Delete Chat": "Elimina chat",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "elimina questo link",
+	"Delete tool?": "",
+	"Delete User": "Elimina utente",
+	"Deleted {{deleteModelTag}}": "Eliminato {{deleteModelTag}}",
+	"Deleted {{name}}": "Eliminato {{name}}",
+	"Description": "Descrizione",
+	"Didn't fully follow instructions": "Non ha seguito completamente le istruzioni",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "Scopri un modello",
+	"Discover a prompt": "Scopri un prompt",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Scopri, scarica ed esplora prompt personalizzati",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Scopri, scarica ed esplora i preset del modello",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "Visualizza il nome utente invece di Tu nella chat",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Documento",
+	"Documentation": "",
+	"Documents": "Documenti",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "non effettua connessioni esterne e i tuoi dati rimangono al sicuro sul tuo server ospitato localmente.",
+	"Don't have an account?": "Non hai un account?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "Non ti piace lo stile",
+	"Done": "",
+	"Download": "Scarica",
+	"Download canceled": "Scaricamento annullato",
+	"Download Database": "Scarica database",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Trascina qui i file da aggiungere alla conversazione",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "ad esempio '30s','10m'. Le unità di tempo valide sono 's', 'm', 'h'.",
+	"Edit": "Modifica",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "Modifica utente",
+	"ElevenLabs": "",
+	"Email": "Email",
+	"Embedding Batch Size": "",
+	"Embedding Model": "Modello di embedding",
+	"Embedding Model Engine": "Motore del modello di embedding",
+	"Embedding model set to \"{{embedding_model}}\"": "Modello di embedding impostato su \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Abilita la condivisione della community",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Abilita nuove iscrizioni",
+	"Enable Web Search": "Abilita ricerca Web",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Assicurati che il tuo file CSV includa 4 colonne in questo ordine: Nome, Email, Password, Ruolo.",
+	"Enter {{role}} message here": "Inserisci il messaggio per {{role}} qui",
+	"Enter a detail about yourself for your LLMs to recall": "Inserisci un dettaglio su di te per che i LLM possano ricordare",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Inserisci la chiave API di Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Inserisci la sovrapposizione chunk",
+	"Enter Chunk Size": "Inserisci la dimensione chunk",
+	"Enter description": "",
+	"Enter Github Raw URL": "Immettere l'URL grezzo di Github",
+	"Enter Google PSE API Key": "Inserisci la chiave API PSE di Google",
+	"Enter Google PSE Engine Id": "Inserisci l'ID motore PSE di Google",
+	"Enter Image Size (e.g. 512x512)": "Inserisci la dimensione dell'immagine (ad esempio 512x512)",
+	"Enter language codes": "Inserisci i codici lingua",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Inserisci il tag del modello (ad esempio {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Inserisci il numero di passaggi (ad esempio 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Inserisci il punteggio",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Immettere l'URL della query Searxng",
+	"Enter Serper API Key": "Inserisci la chiave API Serper",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "Inserisci la chiave API Serpstack",
+	"Enter stop sequence": "Inserisci la sequenza di arresto",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Inserisci Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Inserisci URL (ad esempio http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Inserisci URL (ad esempio http://localhost:11434)",
+	"Enter Your Email": "Inserisci la tua email",
+	"Enter Your Full Name": "Inserisci il tuo nome completo",
+	"Enter your message": "",
+	"Enter Your Password": "Inserisci la tua password",
+	"Enter Your Role": "Inserisci il tuo ruolo",
+	"Error": "Errore",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Sperimentale",
+	"Export": "Esportazione",
+	"Export All Chats (All Users)": "Esporta tutte le chat (tutti gli utenti)",
+	"Export chat (.json)": "",
+	"Export Chats": "Esporta chat",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "Esporta modelli",
+	"Export Prompts": "Esporta prompt",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Impossibile creare la chiave API.",
+	"Failed to read clipboard contents": "Impossibile leggere il contenuto degli appunti",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "Febbraio",
+	"Feedback History": "",
+	"Feel free to add specific details": "Sentiti libero/a di aggiungere dettagli specifici",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Modalità file",
+	"File not found.": "File non trovato.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Rilevato spoofing delle impronte digitali: impossibile utilizzare le iniziali come avatar. Ripristino all'immagine del profilo predefinita.",
+	"Fluidly stream large external response chunks": "Trasmetti in modo fluido blocchi di risposta esterni di grandi dimensioni",
+	"Focus chat input": "Metti a fuoco l'input della chat",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Ha seguito le istruzioni alla perfezione",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Penalità di frequenza",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Generale",
+	"General Settings": "Impostazioni generali",
+	"Generate Image": "",
+	"Generating search query": "Generazione di query di ricerca",
+	"Generation Info": "Informazioni generazione",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "Buona risposta",
+	"Google PSE API Key": "Chiave API PSE di Google",
+	"Google PSE Engine Id": "ID motore PSE di Google",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "non ha conversazioni.",
+	"Hello, {{name}}": "Ciao, {{name}}",
+	"Help": "Aiuto",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Nascondi",
+	"Hide Model": "",
+	"How can I help you today?": "Come posso aiutarti oggi?",
+	"Hybrid Search": "Ricerca ibrida",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Generazione di immagini (sperimentale)",
+	"Image Generation Engine": "Motore di generazione immagini",
+	"Image Settings": "Impostazioni immagine",
+	"Images": "Immagini",
+	"Import Chats": "Importa chat",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "Importazione di modelli",
+	"Import Prompts": "Importa prompt",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Includi il flag `--api` quando esegui stable-diffusion-webui",
+	"Info": "Informazioni",
+	"Input commands": "Comandi di input",
+	"Install from Github URL": "Eseguire l'installazione dall'URL di Github",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "Interfaccia",
+	"Invalid file format.": "",
+	"Invalid Tag": "Tag non valido",
+	"January": "Gennaio",
+	"join our Discord for help.": "unisciti al nostro Discord per ricevere aiuto.",
+	"JSON": "JSON",
+	"JSON Preview": "Anteprima JSON",
+	"July": "Luglio",
+	"June": "Giugno",
+	"JWT Expiration": "Scadenza JWT",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Mantieni attivo",
+	"Keyboard shortcuts": "Scorciatoie da tastiera",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Lingua",
+	"large language models, locally.": "",
+	"Last Active": "Ultima attività",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Chiaro",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "Gli LLM possono commettere errori. Verifica le informazioni importanti.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Realizzato dalla comunità OpenWebUI",
+	"Make sure to enclose them with": "Assicurati di racchiuderli con",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "Gestisci modelli",
+	"Manage Ollama Models": "Gestisci modelli Ollama",
+	"Manage Pipelines": "Gestire le pipeline",
+	"March": "Marzo",
+	"Max Tokens (num_predict)": "Numero massimo di gettoni (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "È possibile scaricare un massimo di 3 modelli contemporaneamente. Riprova più tardi.",
+	"May": "Maggio",
+	"Memories accessible by LLMs will be shown here.": "I memori accessibili ai LLM saranno mostrati qui.",
+	"Memory": "Memoria",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "I messaggi inviati dopo la creazione del link non verranno condivisi. Gli utenti con l'URL saranno in grado di visualizzare la chat condivisa.",
+	"Min P": "",
+	"Minimum Score": "Punteggio minimo",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Il modello '{{modelName}}' è stato scaricato con successo.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Il modello '{{modelTag}}' è già in coda per il download.",
+	"Model {{modelId}} not found": "Modello {{modelId}} non trovato",
+	"Model {{modelName}} is not vision capable": "Il modello {{modelName}} non è in grado di vedere",
+	"Model {{name}} is now {{status}}": "Il modello {{name}} è ora {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Percorso del filesystem del modello rilevato. Il nome breve del modello è richiesto per l'aggiornamento, impossibile continuare.",
+	"Model ID": "ID modello",
+	"Model Name": "",
+	"Model not selected": "Modello non selezionato",
+	"Model Params": "Parametri del modello",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Whitelisting del modello",
+	"Model(s) Whitelisted": "Modello/i in whitelist",
+	"Modelfile Content": "Contenuto del file modello",
+	"Models": "Modelli",
+	"more": "",
+	"More": "Altro",
+	"Move to Top": "",
+	"Name": "Nome",
+	"Name your model": "Assegna un nome al tuo modello",
+	"New Chat": "Nuova chat",
+	"New folder": "",
+	"New Password": "Nuova password",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Nessun risultato trovato",
+	"No search query generated": "Nessuna query di ricerca generata",
+	"No source available": "Nessuna fonte disponibile",
+	"No valves to update": "",
+	"None": "Nessuno",
+	"Not factually correct": "Non corretto dal punto di vista fattuale",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: se imposti un punteggio minimo, la ricerca restituirà solo i documenti con un punteggio maggiore o uguale al punteggio minimo.",
+	"Notes": "",
+	"Notifications": "Notifiche desktop",
+	"November": "Novembre",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "",
+	"October": "Ottobre",
+	"Off": "Disattivato",
+	"Okay, Let's Go!": "Ok, andiamo!",
+	"OLED Dark": "OLED scuro",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "API Ollama disabilitata",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Versione Ollama",
+	"On": "Attivato",
+	"Only": "Solo",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Nella stringa di comando sono consentiti solo caratteri alfanumerici e trattini.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ops! Sembra che l'URL non sia valido. Si prega di ricontrollare e riprovare.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ops! Stai utilizzando un metodo non supportato (solo frontend). Si prega di servire la WebUI dal backend.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Apri nuova chat",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API OpenAI",
+	"OpenAI API Config": "Configurazione API OpenAI",
+	"OpenAI API Key is required.": "La chiave API OpenAI è obbligatoria.",
+	"OpenAI URL/Key required.": "URL/Chiave OpenAI obbligatori.",
+	"or": "o",
+	"Other": "Altro",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Password",
+	"PDF document (.pdf)": "Documento PDF (.pdf)",
+	"PDF Extract Images (OCR)": "Estrazione immagini PDF (OCR)",
+	"pending": "in sospeso",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "Autorizzazione negata durante l'accesso al microfono: {{error}}",
+	"Personalization": "Personalizzazione",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "Condutture",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "Valvole per tubazioni",
+	"Plain text (.txt)": "Testo normale (.txt)",
+	"Playground": "Terreno di gioco",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Attitudine positiva",
+	"Previous 30 days": "Ultimi 30 giorni",
+	"Previous 7 days": "Ultimi 7 giorni",
+	"Profile Image": "Immagine del profilo",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (ad esempio Dimmi un fatto divertente sull'Impero Romano)",
+	"Prompt Content": "Contenuto del prompt",
+	"Prompt suggestions": "Suggerimenti prompt",
+	"Prompts": "Prompt",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Estrai \"{{searchValue}}\" da Ollama.com",
+	"Pull a model from Ollama.com": "Estrai un modello da Ollama.com",
+	"Query Params": "Parametri query",
+	"RAG Template": "Modello RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Leggi ad alta voce",
+	"Record voice": "Registra voce",
+	"Redirecting you to OpenWebUI Community": "Reindirizzamento alla comunità OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "Rifiutato quando non avrebbe dovuto",
+	"Regenerate": "Rigenera",
+	"Release Notes": "Note di rilascio",
+	"Relevance": "",
+	"Remove": "Rimuovi",
+	"Remove Model": "Rimuovi modello",
+	"Rename": "Rinomina",
+	"Repeat Last N": "Ripeti ultimi N",
+	"Request Mode": "Modalità richiesta",
+	"Reranking Model": "Modello di riclassificazione",
+	"Reranking model disabled": "Modello di riclassificazione disabilitato",
+	"Reranking model set to \"{{reranking_model}}\"": "Modello di riclassificazione impostato su \"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Copia automatica della risposta negli appunti",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Ruolo",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "Salva",
+	"Save & Create": "Salva e crea",
+	"Save & Update": "Salva e aggiorna",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Il salvataggio dei registri della chat direttamente nell'archivio del browser non è più supportato. Si prega di dedicare un momento per scaricare ed eliminare i registri della chat facendo clic sul pulsante in basso. Non preoccuparti, puoi facilmente reimportare i registri della chat nel backend tramite",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Cerca",
+	"Search a model": "Cerca un modello",
+	"Search Chats": "Cerca nelle chat",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "Cerca modelli",
+	"Search Prompts": "Cerca prompt",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "Conteggio dei risultati della ricerca",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Ricercato {{count}} sites_one",
+	"Searched {{count}} sites_many": "Ricercato {{count}} sites_many",
+	"Searched {{count}} sites_other": "Ricercato {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng Query URL",
+	"See readme.md for instructions": "Vedi readme.md per le istruzioni",
+	"See what's new": "Guarda le novità",
+	"Seed": "Seme",
+	"Select a base model": "Selezionare un modello di base",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "Seleziona un modello",
+	"Select a pipeline": "Selezionare una tubazione",
+	"Select a pipeline url": "Selezionare l'URL di una pipeline",
+	"Select a tool": "",
+	"Select an Ollama instance": "Seleziona un'istanza Ollama",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Seleziona modello",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "I modelli selezionati non supportano l'input di immagini",
+	"Semantic distance to query": "",
+	"Send": "Invia",
+	"Send a Message": "Invia un messaggio",
+	"Send message": "Invia messaggio",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "Settembre",
+	"Serper API Key": "Chiave API Serper",
+	"Serply API Key": "",
+	"Serpstack API Key": "Chiave API Serpstack",
+	"Server connection verified": "Connessione al server verificata",
+	"Set as default": "Imposta come predefinito",
+	"Set CFG Scale": "",
+	"Set Default Model": "Imposta modello predefinito",
+	"Set embedding model (e.g. {{model}})": "Imposta modello di embedding (ad esempio {{model}})",
+	"Set Image Size": "Imposta dimensione immagine",
+	"Set reranking model (e.g. {{model}})": "Imposta modello di riclassificazione (ad esempio {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Imposta passaggi",
+	"Set Task Model": "Imposta modello di attività",
+	"Set Voice": "Imposta voce",
+	"Set whisper model": "",
+	"Settings": "Impostazioni",
+	"Settings saved successfully!": "Impostazioni salvate con successo!",
+	"Share": "Condividi",
+	"Share Chat": "Condividi chat",
+	"Share to OpenWebUI Community": "Condividi con la comunità OpenWebUI",
+	"short-summary": "riassunto-breve",
+	"Show": "Mostra",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "Mostra",
+	"Show your support!": "",
+	"Showcased creativity": "Creatività messa in mostra",
+	"Sign in": "Accedi",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Esci",
+	"Sign up": "Registrati",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Fonte",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Errore di riconoscimento vocale: {{error}}",
+	"Speech-to-Text Engine": "Motore da voce a testo",
+	"Stop": "",
+	"Stop Sequence": "Sequenza di arresto",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "Impostazioni STT",
+	"Subtitle (e.g. about the Roman Empire)": "Sottotitolo (ad esempio sull'Impero Romano)",
+	"Success": "Successo",
+	"Successfully updated.": "Aggiornato con successo.",
+	"Suggested": "Suggerito",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "Sistema",
+	"System Instructions": "",
+	"System Prompt": "Prompt di sistema",
+	"Tags": "Tag",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "Raccontaci di più:",
+	"Temperature": "Temperatura",
+	"Template": "Modello",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Motore da testo a voce",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Grazie per il tuo feedback!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Il punteggio dovrebbe essere un valore compreso tra 0.0 (0%) e 1.0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ciò garantisce che le tue preziose conversazioni siano salvate in modo sicuro nel tuo database backend. Grazie!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Spiegazione dettagliata",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Suggerimento: aggiorna più slot di variabili consecutivamente premendo il tasto tab nell'input della chat dopo ogni sostituzione.",
+	"Title": "Titolo",
+	"Title (e.g. Tell me a fun fact)": "Titolo (ad esempio Dimmi un fatto divertente)",
+	"Title Auto-Generation": "Generazione automatica del titolo",
+	"Title cannot be an empty string.": "Il titolo non può essere una stringa vuota.",
+	"Title Generation Prompt": "Prompt di generazione del titolo",
+	"To access the available model names for downloading,": "Per accedere ai nomi dei modelli disponibili per il download,",
+	"To access the GGUF models available for downloading,": "Per accedere ai modelli GGUF disponibili per il download,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "all'input della chat.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "Oggi",
+	"Toggle settings": "Attiva/disattiva impostazioni",
+	"Toggle sidebar": "Attiva/disattiva barra laterale",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problemi di accesso a Ollama?",
+	"TTS Model": "",
+	"TTS Settings": "Impostazioni TTS",
+	"TTS Voice": "",
+	"Type": "Digitare",
+	"Type Hugging Face Resolve (Download) URL": "Digita l'URL di Hugging Face Resolve (Download)",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Si è verificato un problema durante la connessione a {{provider}}.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "Aggiorna e copia link",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Aggiorna password",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Carica un modello GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Carica file",
+	"Upload Pipeline": "",
+	"Upload Progress": "Avanzamento caricamento",
+	"URL Mode": "Modalità URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Usa Gravatar",
+	"Use Initials": "Usa iniziali",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "utente",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "Autorizzazioni utente",
+	"Users": "Utenti",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Utilizza",
+	"Valid time units:": "Unità di tempo valide:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "variabile",
+	"variable to have them replaced with clipboard content.": "variabile per farli sostituire con il contenuto degli appunti.",
+	"Version": "Versione",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "Avvertimento",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Attenzione: se aggiorni o cambi il tuo modello di embedding, dovrai reimportare tutti i documenti.",
+	"Web": "Web",
+	"Web API": "",
+	"Web Loader Settings": "Impostazioni del caricatore Web",
+	"Web Search": "Ricerca sul Web",
+	"Web Search Engine": "Motore di ricerca Web",
+	"Webhook URL": "URL webhook",
+	"WebUI Settings": "Impostazioni WebUI",
+	"WebUI will make requests to": "WebUI effettuerà richieste a",
+	"What’s New in": "Novità in",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "Area di lavoro",
+	"Write a prompt suggestion (e.g. Who are you?)": "Scrivi un suggerimento per il prompt (ad esempio Chi sei?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Scrivi un riassunto in 50 parole che riassume [argomento o parola chiave].",
+	"Write something...": "",
+	"Yesterday": "Ieri",
+	"You": "Tu",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "Non è possibile clonare un modello di base",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Non hai conversazioni archiviate.",
+	"You have shared this chat": "Hai condiviso questa chat",
+	"You're a helpful assistant.": "Sei un assistente utile.",
+	"You're now logged in.": "Ora hai effettuato l'accesso.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Impostazioni del caricatore Youtube"
+}
diff --git a/src/lib/i18n/locales/ja-JP/translation.json b/src/lib/i18n/locales/ja-JP/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..40b5334f3a712de40c05f61b2c9fd78096858d7e
--- /dev/null
+++ b/src/lib/i18n/locales/ja-JP/translation.json
@@ -0,0 +1,850 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' または '-1' で無期限。",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(例: `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(例: `sh webui.sh --api`)",
+	"(latest)": "(最新)",
+	"{{ models }}": "{{ モデル }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: ベースモデルは削除できません",
+	"{{user}}'s Chats": "{{user}} のチャット",
+	"{{webUIName}} Backend Required": "{{webUIName}} バックエンドが必要です",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "新しいバージョンが利用可能です。",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "タスクモデルは、チャットやウェブ検索クエリのタイトルの生成などのタスクを実行するときに使用されます",
+	"a user": "ユーザー",
+	"About": "概要",
+	"Account": "アカウント",
+	"Account Activation Pending": "アカウント承認待ち",
+	"Accurate information": "情報が正確",
+	"Actions": "アクション",
+	"Active Users": "アクティブユーザー",
+	"Add": "追加",
+	"Add a model id": "モデル ID を追加する",
+	"Add a short description about what this model does": "このモデルの機能に関する簡単な説明を追加します",
+	"Add a short title for this prompt": "このプロンプトへ名前を追加",
+	"Add a tag": "タグを追加",
+	"Add Arena Model": "",
+	"Add Content": "コンテンツを追加",
+	"Add content here": "ここへコンテンツを追加",
+	"Add custom prompt": "カスタムプロンプトを追加",
+	"Add Files": "ファイルを追加",
+	"Add Memory": "メモリを追加",
+	"Add Model": "モデルを追加",
+	"Add Tag": "タグを追加",
+	"Add Tags": "タグを追加",
+	"Add text content": "コンテンツを追加",
+	"Add User": "ユーザーを追加",
+	"Adjusting these settings will apply changes universally to all users.": "これらの設定を調整すると、すべてのユーザーに変更が適用されます。",
+	"admin": "管理者",
+	"Admin": "管理者",
+	"Admin Panel": "管理者パネル",
+	"Admin Settings": "管理者設定",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "管理者は全てのツールにアクセス出来ます。ユーザーはワークスペースのモデル毎に割り当てて下さい。",
+	"Advanced Parameters": "詳細パラメーター",
+	"Advanced Params": "高度なパラメータ",
+	"All chats": "",
+	"All Documents": "全てのドキュメント",
+	"Allow Chat Deletion": "チャットの削除を許可",
+	"Allow Chat Editing": "チャットの編集を許可",
+	"Allow non-local voices": "ローカル以外のボイスを許可",
+	"Allow Temporary Chat": "一時的なチャットを許可",
+	"Allow User Location": "ユーザーロケーションの許可",
+	"Allow Voice Interruption in Call": "通話中に音声の割り込みを許可",
+	"alphanumeric characters and hyphens": "英数字とハイフン",
+	"Already have an account?": "すでにアカウントをお持ちですか?",
+	"an assistant": "アシスタント",
+	"and": "および",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "し、新しい共有リンクを作成します。",
+	"API Base URL": "API ベース URL",
+	"API Key": "API キー",
+	"API Key created.": "API キーが作成されました。",
+	"API keys": "API キー",
+	"April": "4月",
+	"Archive": "アーカイブ",
+	"Archive All Chats": "すべてのチャットをアーカイブする",
+	"Archived Chats": "チャット記録",
+	"are allowed - Activate this command by typing": "が許可されています - 次のように入力してこのコマンドをアクティブ化します",
+	"Are you sure?": "よろしいですか?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "質問して下さい。",
+	"Assistant": "",
+	"Attach file": "ファイルを添付する",
+	"Attention to detail": "詳細に注意する",
+	"Audio": "オーディオ",
+	"August": "8月",
+	"Auto-playback response": "応答の自動再生",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111のAuthを入力",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 ベース URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 ベース URL が必要です。",
+	"Available list": "利用可能リスト",
+	"available!": "利用可能!",
+	"Azure AI Speech": "AzureAIスピーチ",
+	"Azure Region": "Azureリージョン",
+	"Back": "戻る",
+	"Bad Response": "応答が悪い",
+	"Banners": "バナー",
+	"Base Model (From)": "ベースモデル (From)",
+	"Batch Size (num_batch)": "バッチサイズ (num_batch)",
+	"before": "より前",
+	"Being lazy": "怠惰な",
+	"Brave Search API Key": "Brave Search APIキー",
+	"Bypass SSL verification for Websites": "SSL 検証をバイパスする",
+	"Call": "コール",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "カメラ",
+	"Cancel": "キャンセル",
+	"Capabilities": "資格",
+	"Change Password": "パスワードを変更",
+	"Character": "",
+	"Chat": "チャット",
+	"Chat Background Image": "チャットの背景画像",
+	"Chat Bubble UI": "チャットバブルUI",
+	"Chat Controls": "チャットコントロール",
+	"Chat direction": "チャットの方向",
+	"Chat Overview": "チャット概要",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "チャット",
+	"Check Again": "再確認",
+	"Check for updates": "アップデートを確認",
+	"Checking for updates...": "アップデートを確認しています...",
+	"Choose a model before saving...": "保存する前にモデルを選択してください...",
+	"Chunk Overlap": "チャンクオーバーラップ",
+	"Chunk Params": "チャンクパラメーター",
+	"Chunk Size": "チャンクサイズ",
+	"Citation": "引用文",
+	"Clear memory": "メモリをクリア",
+	"Click here for help.": "ヘルプについてはここをクリックしてください。",
+	"Click here to": "ここをクリックして",
+	"Click here to download user import template file.": "ユーザーテンプレートをインポートするにはここをクリックしてください。",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "選択するにはここをクリックしてください",
+	"Click here to select a csv file.": "CSVファイルを選択するにはここをクリックしてください。",
+	"Click here to select a py file.": "Pythonスクリプトファイルを選択するにはここをクリックしてください。",
+	"Click here to upload a workflow.json file.": "workflow.jsonファイルをアップロードするにはここをクリックしてください。",
+	"click here.": "ここをクリックしてください。",
+	"Click on the user role button to change a user's role.": "ユーザーの役割を変更するには、ユーザー役割ボタンをクリックしてください。",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "クリップボードへの書き込み許可がありません。ブラウザ設定を確認し許可してください。",
+	"Clone": "クローン",
+	"Close": "閉じる",
+	"Code execution": "",
+	"Code formatted successfully": "コードフォーマットに成功しました",
+	"Collection": "コレクション",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUIベースURL",
+	"ComfyUI Base URL is required.": "ComfyUIベースURLが必要です。",
+	"ComfyUI Workflow": "ComfyUIワークフロー",
+	"ComfyUI Workflow Nodes": "ComfyUIワークフローノード",
+	"Command": "コマンド",
+	"Completions": "",
+	"Concurrent Requests": "同時リクエスト",
+	"Confirm": "確認",
+	"Confirm Password": "パスワードを確認",
+	"Confirm your action": "あなたのアクションの確認",
+	"Connections": "接続",
+	"Contact Admin for WebUI Access": "WEBUIへの接続について管理者に問い合わせ下さい。",
+	"Content": "コンテンツ",
+	"Content Extraction": "コンテンツ抽出",
+	"Context Length": "コンテキストの長さ",
+	"Continue Response": "続きの応答",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "コントロール",
+	"Copied": "コピー",
+	"Copied shared chat URL to clipboard!": "共有チャットURLをクリップボードにコピーしました!",
+	"Copied to clipboard": "クリップボードにコピーしました。",
+	"Copy": "コピー",
+	"Copy last code block": "最後のコードブロックをコピー",
+	"Copy last response": "最後の応答をコピー",
+	"Copy Link": "リンクをコピー",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "クリップボードへのコピーが成功しました!",
+	"Create a model": "モデルを作成する",
+	"Create Account": "アカウントを作成",
+	"Create Knowledge": "知識データ作成",
+	"Create new key": "新しいキーを作成",
+	"Create new secret key": "新しいシークレットキーを作成",
+	"Created at": "作成日時",
+	"Created At": "作成日時",
+	"Created by": "",
+	"CSV Import": "CSVインポート",
+	"Current Model": "現在のモデル",
+	"Current Password": "現在のパスワード",
+	"Custom": "カスタム",
+	"Customize models for a specific purpose": "特定の目的に合わせてモデルをカスタマイズする",
+	"Dark": "ダーク",
+	"Dashboard": "ダッシュボード",
+	"Database": "データベース",
+	"December": "12月",
+	"Default": "デフォルト",
+	"Default (Open AI)": "デフォルト(OpenAI)",
+	"Default (SentenceTransformers)": "デフォルト (SentenceTransformers)",
+	"Default Model": "デフォルトモデル",
+	"Default model updated": "デフォルトモデルが更新されました",
+	"Default Prompt Suggestions": "デフォルトのプロンプトの提案",
+	"Default User Role": "デフォルトのユーザー役割",
+	"Delete": "削除",
+	"Delete a model": "モデルを削除",
+	"Delete All Chats": "すべてのチャットを削除",
+	"Delete chat": "チャットを削除",
+	"Delete Chat": "チャットを削除",
+	"Delete chat?": "チャットを削除しますか?",
+	"Delete folder?": "",
+	"Delete function?": "Functionを削除しますか?",
+	"Delete prompt?": "プロンプトを削除しますか?",
+	"delete this link": "このリンクを削除します",
+	"Delete tool?": "ツールを削除しますか?",
+	"Delete User": "ユーザーを削除",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} を削除しました",
+	"Deleted {{name}}": "{{name}}を削除しました",
+	"Description": "説明",
+	"Didn't fully follow instructions": "説明に沿って操作していませんでした",
+	"Disabled": "無効",
+	"Discover a function": "Functionを探す",
+	"Discover a model": "モデルを探す",
+	"Discover a prompt": "プロンプトを探す",
+	"Discover a tool": "ツールを探す",
+	"Discover, download, and explore custom functions": "カスタムFunctionを探してダウンロードする",
+	"Discover, download, and explore custom prompts": "カスタムプロンプトを探してダウンロードする",
+	"Discover, download, and explore custom tools": "カスタムツールを探てしダウンロードする",
+	"Discover, download, and explore model presets": "モデルプリセットを探してダウンロードする",
+	"Dismissible": "",
+	"Display Emoji in Call": "コールで絵文字を表示",
+	"Display the username instead of You in the Chat": "チャットで「あなた」の代わりにユーザー名を表示",
+	"Do not install functions from sources you do not fully trust.": "信頼できないソースからFunctionをインストールしないでください。",
+	"Do not install tools from sources you do not fully trust.": "信頼出来ないソースからツールをインストールしないでください。",
+	"Document": "ドキュメント",
+	"Documentation": "ドキュメント",
+	"Documents": "ドキュメント",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "外部接続を行わず、データはローカルでホストされているサーバー上に安全に保持されます。",
+	"Don't have an account?": "アカウントをお持ちではありませんか?",
+	"don't install random functions from sources you don't trust.": "信頼出来ないソースからランダムFunctionをインストールしないでください。",
+	"don't install random tools from sources you don't trust.": "信頼出来ないソースからランダムツールをインストールしないでください。",
+	"Don't like the style": "デザインが好きでない",
+	"Done": "完了",
+	"Download": "ダウンロード",
+	"Download canceled": "ダウンロードをキャンセルしました",
+	"Download Database": "データベースをダウンロード",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "会話を追加するには、ここにファイルをドロップしてください",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "例: '30秒'、'10分'。有効な時間単位は '秒'、'分'、'時間' です。",
+	"Edit": "編集",
+	"Edit Arena Model": "",
+	"Edit Memory": "メモリを編集",
+	"Edit User": "ユーザーを編集",
+	"ElevenLabs": "",
+	"Email": "メールアドレス",
+	"Embedding Batch Size": "埋め込みモデルバッチサイズ",
+	"Embedding Model": "埋め込みモデル",
+	"Embedding Model Engine": "埋め込みモデルエンジン",
+	"Embedding model set to \"{{embedding_model}}\"": "埋め込みモデルを\"{{embedding_model}}\"に設定しました",
+	"Enable Community Sharing": "コミュニティ共有を有効にする",
+	"Enable Message Rating": "メッセージ評価を有効にする",
+	"Enable New Sign Ups": "新規登録を有効にする",
+	"Enable Web Search": "ウェブ検索を有効にする",
+	"Enable Web Search Query Generation": "ウェブ検索クエリ生成を有効にする",
+	"Enabled": "有効",
+	"Engine": "エンジン",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "CSVファイルに4つの列が含まれていることを確認してください: Name, Email, Password, Role.",
+	"Enter {{role}} message here": "{{role}} メッセージをここに入力してください",
+	"Enter a detail about yourself for your LLMs to recall": "LLM が記憶するために、自分についての詳細を入力してください",
+	"Enter api auth string (e.g. username:password)": "API AuthStringを入力(例: Username:Password)",
+	"Enter Brave Search API Key": "Brave Search APIキーの入力",
+	"Enter CFG Scale (e.g. 7.0)": "CFGスケースを入力してください (例: 7.0)",
+	"Enter Chunk Overlap": "チャンクオーバーラップを入力してください",
+	"Enter Chunk Size": "チャンクサイズを入力してください",
+	"Enter description": "",
+	"Enter Github Raw URL": "Github Raw URLを入力",
+	"Enter Google PSE API Key": "Google PSE APIキーの入力",
+	"Enter Google PSE Engine Id": "Google PSE エンジン ID を入力します。",
+	"Enter Image Size (e.g. 512x512)": "画像サイズを入力してください (例: 512x512)",
+	"Enter language codes": "言語コードを入力してください",
+	"Enter Model ID": "モデルIDを入力してください。",
+	"Enter model tag (e.g. {{modelTag}})": "モデルタグを入力してください (例: {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "ステップ数を入力してください (例: 50)",
+	"Enter Sampler (e.g. Euler a)": "サンプラーを入力してください(e.g. Euler a)。",
+	"Enter Scheduler (e.g. Karras)": "スケジューラーを入力してください。(e.g. Karras)",
+	"Enter Score": "スコアを入力してください",
+	"Enter SearchApi API Key": "SearchApi API Keyを入力してください。",
+	"Enter SearchApi Engine": "SearchApi Engineを入力してください。",
+	"Enter Searxng Query URL": "SearxngクエリURLを入力",
+	"Enter Serper API Key": "Serper APIキーの入力",
+	"Enter Serply API Key": "Serply API Keyを入力してください。",
+	"Enter Serpstack API Key": "Serpstack APIキーの入力",
+	"Enter stop sequence": "ストップシーケンスを入力してください",
+	"Enter system prompt": "システムプロンプト入力",
+	"Enter Tavily API Key": "Tavily API Keyを入力してください。",
+	"Enter Tika Server URL": "Tika Server URLを入力してください。",
+	"Enter Top K": "トップ K を入力してください",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "URL を入力してください (例: http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "URL を入力してください (例: http://localhost:11434)",
+	"Enter Your Email": "メールアドレスを入力してください",
+	"Enter Your Full Name": "フルネームを入力してください",
+	"Enter your message": "メッセージを入力してください",
+	"Enter Your Password": "パスワードを入力してください",
+	"Enter Your Role": "ロールを入力してください",
+	"Error": "エラー",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "実験的",
+	"Export": "エクスポート",
+	"Export All Chats (All Users)": "すべてのチャットをエクスポート (すべてのユーザー)",
+	"Export chat (.json)": "チャットをエクスポート(.json)",
+	"Export Chats": "チャットをエクスポート",
+	"Export Config to JSON File": "設定をJSONファイルでエクスポート",
+	"Export Functions": "Functionのエクスポート",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "モデルのエクスポート",
+	"Export Prompts": "プロンプトをエクスポート",
+	"Export Tools": "ツールのエクスポート",
+	"External Models": "外部モデル",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "APIキーの作成に失敗しました。",
+	"Failed to read clipboard contents": "クリップボードの内容を読み取れませんでした",
+	"Failed to update settings": "設定アップデート失敗",
+	"Failed to upload file.": "ファイルアップロード失敗",
+	"February": "2月",
+	"Feedback History": "",
+	"Feel free to add specific details": "詳細を追加してください",
+	"File": "ファイル",
+	"File added successfully.": "ファイル追加が成功しました。",
+	"File content updated successfully.": "ファイルコンテンツ追加が成功しました。",
+	"File Mode": "ファイルモード",
+	"File not found.": "ファイルが見つかりません。",
+	"File removed successfully.": "ファイル削除が成功しました。",
+	"File size should not exceed {{maxSize}} MB.": "ファイルサイズ最大値{{maxSize}} MB",
+	"Files": "ファイル",
+	"Filter is now globally disabled": "グローバルフィルタが無効です。",
+	"Filter is now globally enabled": "グローバルフィルタが有効です。",
+	"Filters": "フィルター",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "指紋のなりすましが検出されました: イニシャルをアバターとして使用できません。デフォルトのプロファイル画像にデフォルト設定されています。",
+	"Fluidly stream large external response chunks": "大規模な外部応答チャンクをスムーズにストリーミングする",
+	"Focus chat input": "チャット入力をフォーカス",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "完全に指示に従った",
+	"Form": "フォーム",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "頻度ペナルティ",
+	"Function": "",
+	"Function created successfully": "Functionの作成が成功しました。",
+	"Function deleted successfully": "Functionの削除が成功しました。",
+	"Function Description (e.g. A filter to remove profanity from text)": "Function詳細",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "Functionはグローバルで無効です。",
+	"Function is now globally enabled": "Functionはグローバルで有効です。",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "Functionのアップデートが成功しました。",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "Functionsのインポートが成功しました",
+	"General": "一般",
+	"General Settings": "一般設定",
+	"Generate Image": "",
+	"Generating search query": "検索クエリの生成",
+	"Generation Info": "生成情報",
+	"Get up and running with": "",
+	"Global": "グローバル",
+	"Good Response": "良い応答",
+	"Google PSE API Key": "Google PSE APIキー",
+	"Google PSE Engine Id": "Google PSE エンジン ID",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "触覚フィードバック",
+	"has no conversations.": "対話はありません。",
+	"Hello, {{name}}": "こんにちは、{{name}} さん",
+	"Help": "ヘルプ",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "非表示",
+	"Hide Model": "モデルを隠す",
+	"How can I help you today?": "今日はどのようにお手伝いしましょうか?",
+	"Hybrid Search": "ブリッジ検索",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "画像生成 (実験的)",
+	"Image Generation Engine": "画像生成エンジン",
+	"Image Settings": "画像設定",
+	"Images": "画像",
+	"Import Chats": "チャットをインポート",
+	"Import Config from JSON File": "設定をJSONファイルからインポート",
+	"Import Functions": "Functionのインポート",
+	"Import Models": "モデルのインポート",
+	"Import Prompts": "プロンプトをインポート",
+	"Import Tools": "ツールのインポート",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webuiを実行する際に`--api`フラグを含める",
+	"Info": "情報",
+	"Input commands": "入力コマンド",
+	"Install from Github URL": "Github URLからインストール",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "インターフェース",
+	"Invalid file format.": "",
+	"Invalid Tag": "無効なタグ",
+	"January": "1月",
+	"join our Discord for help.": "ヘルプについては、Discord に参加してください。",
+	"JSON": "JSON",
+	"JSON Preview": "JSON プレビュー",
+	"July": "7月",
+	"June": "6月",
+	"JWT Expiration": "JWT 有効期限",
+	"JWT Token": "JWT トークン",
+	"Keep Alive": "キープアライブ",
+	"Keyboard shortcuts": "キーボードショートカット",
+	"Knowledge": "知識",
+	"Knowledge created successfully.": "知識の作成に成功しました",
+	"Knowledge deleted successfully.": "知識の削除に成功しました",
+	"Knowledge reset successfully.": "知識のリセットに成功しました",
+	"Knowledge updated successfully": "知識のアップデートに成功しました",
+	"Landing Page Mode": "ランディングページモード",
+	"Language": "言語",
+	"large language models, locally.": "",
+	"Last Active": "最終アクティブ",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "空欄なら無制限",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "カスタムプロンプトを入力。空欄ならデフォルトプロンプト",
+	"Light": "ライト",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "LLM は間違いを犯す可能性があります。重要な情報を検証してください。",
+	"Local Models": "ローカルモデル",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "OpenWebUI コミュニティによって作成",
+	"Make sure to enclose them with": "必ず次で囲んでください",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "管理",
+	"Manage Arena Models": "",
+	"Manage Models": "モデルを管理",
+	"Manage Ollama Models": "Ollama モデルを管理",
+	"Manage Pipelines": "パイプラインの管理",
+	"March": "3月",
+	"Max Tokens (num_predict)": "最大トークン数 (num_predict)",
+	"Max Upload Count": "最大アップロード数",
+	"Max Upload Size": "最大アップロードサイズ",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "同時にダウンロードできるモデルは最大 3 つです。後でもう一度お試しください。",
+	"May": "5月",
+	"Memories accessible by LLMs will be shown here.": "LLM がアクセスできるメモリはここに表示されます。",
+	"Memory": "メモリ",
+	"Memory added successfully": "メモリに追加されました。",
+	"Memory cleared successfully": "メモリをクリアしました。",
+	"Memory deleted successfully": "メモリを削除しました。",
+	"Memory updated successfully": "メモリアップデート成功",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "リンクを作成した後、送信したメッセージは共有されません。URL を持つユーザーは共有チャットを閲覧できます。",
+	"Min P": "",
+	"Minimum Score": "最低スコア",
+	"Mirostat": "ミロスタット",
+	"Mirostat Eta": "ミロスタット Eta",
+	"Mirostat Tau": "ミロスタット Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "モデル '{{modelName}}' が正常にダウンロードされました。",
+	"Model '{{modelTag}}' is already in queue for downloading.": "モデル '{{modelTag}}' はすでにダウンロード待ち行列に入っています。",
+	"Model {{modelId}} not found": "モデル {{modelId}} が見つかりません",
+	"Model {{modelName}} is not vision capable": "モデル {{modelName}} は視覚に対応していません",
+	"Model {{name}} is now {{status}}": "モデル {{name}} は {{status}} になりました。",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "モデルファイルシステムパスが検出されました。モデルの短縮名が必要です。更新できません。",
+	"Model ID": "モデルID",
+	"Model Name": "",
+	"Model not selected": "モデルが選択されていません",
+	"Model Params": "モデルパラメータ",
+	"Model updated successfully": "",
+	"Model Whitelisting": "モデルホワイトリスト",
+	"Model(s) Whitelisted": "ホワイトリストに登録されたモデル",
+	"Modelfile Content": "モデルファイルの内容",
+	"Models": "モデル",
+	"more": "",
+	"More": "もっと見る",
+	"Move to Top": "",
+	"Name": "名前",
+	"Name your model": "モデルに名前を付ける",
+	"New Chat": "新しいチャット",
+	"New folder": "",
+	"New Password": "新しいパスワード",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "知識が見つかりません",
+	"No models found": "",
+	"No results found": "結果が見つかりません",
+	"No search query generated": "検索クエリは生成されません",
+	"No source available": "使用可能なソースがありません",
+	"No valves to update": "",
+	"None": "何一つ",
+	"Not factually correct": "実事上正しくない",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "注意:最小スコアを設定した場合、検索は最小スコア以上のスコアを持つドキュメントのみを返します。",
+	"Notes": "",
+	"Notifications": "デスクトップ通知",
+	"November": "11月",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "",
+	"OAuth ID": "",
+	"October": "10月",
+	"Off": "オフ",
+	"Okay, Let's Go!": "OK、始めましょう!",
+	"OLED Dark": "OLED ダーク",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API が無効になっています",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Ollama バージョン",
+	"On": "オン",
+	"Only": "のみ",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "コマンド文字列には英数字とハイフンのみが許可されています。",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "おっと! URL が無効なようです。もう一度確認してやり直してください。",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "おっと! サポートされていない方法 (フロントエンドのみ) を使用しています。バックエンドから WebUI を提供してください。",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "新しいチャットを開く",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API 設定",
+	"OpenAI API Key is required.": "OpenAI API キーが必要です。",
+	"OpenAI URL/Key required.": "OpenAI URL/Key が必要です。",
+	"or": "または",
+	"Other": "その他",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "パスワード",
+	"PDF document (.pdf)": "PDF ドキュメント (.pdf)",
+	"PDF Extract Images (OCR)": "PDF 画像抽出 (OCR)",
+	"pending": "保留中",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "マイクへのアクセス時に権限が拒否されました: {{error}}",
+	"Personalization": "個人化",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "パイプライン",
+	"Pipelines Not Detected": "パイプラインは検出されませんでした",
+	"Pipelines Valves": "パイプラインバルブ",
+	"Plain text (.txt)": "プレーンテキスト (.txt)",
+	"Playground": "プレイグラウンド",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "前向きな態度",
+	"Previous 30 days": "前の30日間",
+	"Previous 7 days": "前の7日間",
+	"Profile Image": "プロフィール画像",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "プロンプト(例:ローマ帝国についての楽しい事を教えてください)",
+	"Prompt Content": "プロンプトの内容",
+	"Prompt suggestions": "プロンプトの提案",
+	"Prompts": "プロンプト",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com から \"{{searchValue}}\" をプル",
+	"Pull a model from Ollama.com": "Ollama.com からモデルをプル",
+	"Query Params": "クエリパラメーター",
+	"RAG Template": "RAG テンプレート",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "読み上げ",
+	"Record voice": "音声を録音",
+	"Redirecting you to OpenWebUI Community": "OpenWebUI コミュニティにリダイレクトしています",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "拒否すべきでないのに拒否した",
+	"Regenerate": "再生成",
+	"Release Notes": "リリースノート",
+	"Relevance": "",
+	"Remove": "削除",
+	"Remove Model": "モデルを削除",
+	"Rename": "名前を変更",
+	"Repeat Last N": "最後の N を繰り返す",
+	"Request Mode": "リクエストモード",
+	"Reranking Model": "モデルの再ランキング",
+	"Reranking model disabled": "再ランキングモデルが無効です",
+	"Reranking model set to \"{{reranking_model}}\"": "再ランキングモデルを \"{{reranking_model}}\" に設定しました",
+	"Reset": "",
+	"Reset Upload Directory": "アップロードディレクトリをリセット",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "クリップボードへの応答の自動コピー",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "応答の分割",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "役割",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "保存",
+	"Save & Create": "保存して作成",
+	"Save & Update": "保存して更新",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "チャットログをブラウザのストレージに直接保存する機能はサポートされなくなりました。下のボタンをクリックして、チャットログをダウンロードして削除してください。ご心配なく。チャットログは、次の方法でバックエンドに簡単に再インポートできます。",
+	"Scroll to bottom when switching between branches": "ブランチの切り替え時にボタンをスクロールする",
+	"Search": "検索",
+	"Search a model": "モデルを検索",
+	"Search Chats": "チャットの検索",
+	"Search Collection": "Collectionの検索",
+	"search for tags": "",
+	"Search Functions": "Functionの検索",
+	"Search Knowledge": "知識の検索",
+	"Search Models": "モデル検索",
+	"Search Prompts": "プロンプトを検索",
+	"Search Query Generation Prompt": "検索クエリ生成プロンプト",
+	"Search Result Count": "検索結果数",
+	"Search Tools": "ツールの検索",
+	"SearchApi API Key": "SearchApiのAPIKey",
+	"SearchApi Engine": "SearchApiエンジン",
+	"Searched {{count}} sites_other": "{{count}} sites_other検索",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng クエリ URL",
+	"See readme.md for instructions": "手順については readme.md を参照してください",
+	"See what's new": "新機能を見る",
+	"Seed": "シード",
+	"Select a base model": "基本モデルの選択",
+	"Select a engine": "エンジンの選択",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Functionの選択",
+	"Select a model": "モデルを選択",
+	"Select a pipeline": "パイプラインの選択",
+	"Select a pipeline url": "パイプラインの URL を選択する",
+	"Select a tool": "ツールの選択",
+	"Select an Ollama instance": "Ollama インスタンスを選択",
+	"Select Engine": "エンジンの選択",
+	"Select Knowledge": "知識の選択",
+	"Select model": "モデルを選択",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "一部のモデルは画像入力をサポートしていません",
+	"Semantic distance to query": "",
+	"Send": "送信",
+	"Send a Message": "メッセージを送信",
+	"Send message": "メッセージを送信",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "9月",
+	"Serper API Key": "Serper APIキー",
+	"Serply API Key": "",
+	"Serpstack API Key": "Serpstack APIキー",
+	"Server connection verified": "サーバー接続が確認されました",
+	"Set as default": "デフォルトに設定",
+	"Set CFG Scale": "",
+	"Set Default Model": "デフォルトモデルを設定",
+	"Set embedding model (e.g. {{model}})": "埋め込みモデルを設定します(例:{{model}})",
+	"Set Image Size": "画像サイズを設定",
+	"Set reranking model (e.g. {{model}})": "モデルを設定します(例:{{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "ステップを設定",
+	"Set Task Model": "タスクモデルの設定",
+	"Set Voice": "音声を設定",
+	"Set whisper model": "",
+	"Settings": "設定",
+	"Settings saved successfully!": "設定が正常に保存されました!",
+	"Share": "共有",
+	"Share Chat": "チャットを共有",
+	"Share to OpenWebUI Community": "OpenWebUI コミュニティに共有",
+	"short-summary": "short-summary",
+	"Show": "表示",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "表示",
+	"Show your support!": "",
+	"Showcased creativity": "創造性を披露",
+	"Sign in": "サインイン",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "サインアウト",
+	"Sign up": "サインアップ",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "ソース",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "音声認識エラー: {{error}}",
+	"Speech-to-Text Engine": "音声テキスト変換エンジン",
+	"Stop": "",
+	"Stop Sequence": "ストップシーケンス",
+	"Stream Chat Response": "",
+	"STT Model": "STTモデル",
+	"STT Settings": "STT設定",
+	"Subtitle (e.g. about the Roman Empire)": "タイトル (例: ローマ帝国)",
+	"Success": "成功",
+	"Successfully updated.": "正常に更新されました。",
+	"Suggested": "提案",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "システム",
+	"System Instructions": "",
+	"System Prompt": "システムプロンプト",
+	"Tags": "タグ",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "もっと話してください:",
+	"Temperature": "温度",
+	"Template": "テンプレート",
+	"Temporary Chat": "一時的なチャット",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "テキスト音声変換エンジン",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "ご意見ありがとうございます!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "スコアは0.0(0%)から1.0(100%)の間の値にしてください。",
+	"Theme": "テーマ",
+	"Thinking...": "思考中...",
+	"This action cannot be undone. Do you wish to continue?": "このアクションは取り消し不可です。続けますか?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "これは、貴重な会話がバックエンドデータベースに安全に保存されることを保証します。ありがとうございます!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "実験的機能であり正常動作しない場合があります。",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "詳細な説明",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "ヒント: 各置換後にチャット入力で Tab キーを押すことで、複数の変数スロットを連続して更新できます。",
+	"Title": "タイトル",
+	"Title (e.g. Tell me a fun fact)": "タイトル (例: 楽しい事を教えて)",
+	"Title Auto-Generation": "タイトル自動生成",
+	"Title cannot be an empty string.": "タイトルは空文字列にできません。",
+	"Title Generation Prompt": "タイトル生成プロンプト",
+	"To access the available model names for downloading,": "ダウンロード可能なモデル名にアクセスするには、",
+	"To access the GGUF models available for downloading,": "ダウンロード可能な GGUF モデルにアクセスするには、",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "チャット入力へ。",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "今日",
+	"Toggle settings": "設定を切り替え",
+	"Toggle sidebar": "サイドバーを切り替え",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "トップ K",
+	"Top P": "トップ P",
+	"Trouble accessing Ollama?": "Ollama へのアクセスに問題がありますか?",
+	"TTS Model": "TTSモデル",
+	"TTS Settings": "TTS 設定",
+	"TTS Voice": "TTSボイス",
+	"Type": "種類",
+	"Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (ダウンロード) URL を入力してください",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "おっと! {{provider}} への接続に問題が発生しました。",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "リンクの更新とコピー",
+	"Update for the latest features and improvements.": "",
+	"Update password": "パスワードを更新",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "アップロード",
+	"Upload a GGUF model": "GGUF モデルをアップロード",
+	"Upload directory": "アップロードディレクトリ",
+	"Upload files": "アップロードファイル",
+	"Upload Files": "ファイルのアップロード",
+	"Upload Pipeline": "アップロードパイプライン",
+	"Upload Progress": "アップロードの進行状況",
+	"URL Mode": "URL モード",
+	"Use '#' in the prompt input to load and include your knowledge.": "#を入力すると知識データを参照することが出来ます。",
+	"Use Gravatar": "Gravatar を使用する",
+	"Use Initials": "初期値を使用する",
+	"use_mlock (Ollama)": "",
+	"use_mmap (Ollama)": "",
+	"user": "ユーザー",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "ユーザー権限",
+	"Users": "ユーザー",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "活用",
+	"Valid time units:": "有効な時間単位:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "変数",
+	"variable to have them replaced with clipboard content.": "クリップボードの内容に置き換える変数。",
+	"Version": "バージョン",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "ボイス",
+	"Voice Input": "",
+	"Warning": "警告",
+	"Warning:": "警告:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "警告: 埋め込みモデルを更新または変更した場合は、すべてのドキュメントを再インポートする必要があります。",
+	"Web": "ウェブ",
+	"Web API": "ウェブAPI",
+	"Web Loader Settings": "Web 読み込み設定",
+	"Web Search": "ウェブ検索",
+	"Web Search Engine": "ウェブ検索エンジン",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "WebUI 設定",
+	"WebUI will make requests to": "WebUI は次に対してリクエストを行います",
+	"What’s New in": "新機能",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "ワイドスクリーンモード",
+	"Won": "",
+	"Workspace": "ワークスペース",
+	"Write a prompt suggestion (e.g. Who are you?)": "プロンプトの提案を書いてください (例: あなたは誰ですか?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "[トピックまたはキーワード] を要約する 50 語の概要を書いてください。",
+	"Write something...": "",
+	"Yesterday": "昨日",
+	"You": "あなた",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "基本モデルのクローンは作成できません",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "これまでにアーカイブされた会話はありません。",
+	"You have shared this chat": "このチャットを共有しました",
+	"You're a helpful assistant.": "あなたは有能なアシスタントです。",
+	"You're now logged in.": "ログインしました。",
+	"Your account status is currently pending activation.": "あなたのアカウント状態は現在登録認証待ちです。",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "YouTube",
+	"Youtube Loader Settings": "YouTubeローダー設定(日本語はja)"
+}
diff --git a/src/lib/i18n/locales/ka-GE/translation.json b/src/lib/i18n/locales/ka-GE/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..36d21437b6805e76a0161a0610626ac5503856b7
--- /dev/null
+++ b/src/lib/i18n/locales/ka-GE/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' ან '-1' ვადის გასვლისთვის.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(მაგ. `sh webui.sh --api`)",
+	"(latest)": "(უახლესი)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: თქვენ არ შეგიძლიათ წაშალოთ ბაზის მოდელი",
+	"{{user}}'s Chats": "{{user}}-ის ჩათები",
+	"{{webUIName}} Backend Required": "{{webUIName}} საჭიროა ბექენდი",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "დავალების მოდელი გამოიყენება ისეთი ამოცანების შესრულებისას, როგორიცაა ჩეთების სათაურების გენერირება და ვებ – ძიების მოთხოვნები",
+	"a user": "მომხმარებელი",
+	"About": "შესახებ",
+	"Account": "ანგარიში",
+	"Account Activation Pending": "",
+	"Accurate information": "დიდი ინფორმაცია",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "დამატება",
+	"Add a model id": "დაამატეთ მოდელის ID",
+	"Add a short description about what this model does": "დაამატეთ მოკლე აღწერა იმის შესახებ, თუ რას აკეთებს ეს მოდელი",
+	"Add a short title for this prompt": "დაამატე მოკლე სათაური ამ მოთხოვნისთვის",
+	"Add a tag": "დაამატე ტეგი",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "პირველადი მოთხოვნის დამატება",
+	"Add Files": "ფაილების დამატება",
+	"Add Memory": "მემორიის დამატება",
+	"Add Model": "მოდელის დამატება",
+	"Add Tag": "",
+	"Add Tags": "ტეგების დამატება",
+	"Add text content": "",
+	"Add User": "მომხმარებლის დამატება",
+	"Adjusting these settings will apply changes universally to all users.": "ამ პარამეტრების რეგულირება ცვლილებებს უნივერსალურად გამოიყენებს ყველა მომხმარებლისთვის",
+	"admin": "ადმინისტრატორი",
+	"Admin": "",
+	"Admin Panel": "ადმინ პანელი",
+	"Admin Settings": "ადმინისტრატორის ხელსაწყოები",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "დამატებითი პარამეტრები",
+	"Advanced Params": "მოწინავე პარამები",
+	"All chats": "",
+	"All Documents": "ყველა დოკუმენტი",
+	"Allow Chat Deletion": "მიმოწერის წაშლის დაშვება",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "ალფანუმერული სიმბოლოები და დეფისები",
+	"Already have an account?": "უკვე გაქვს ანგარიში?",
+	"an assistant": "ასისტენტი",
+	"and": "და",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "და შექმენით ახალი გაზიარებული ბმული.",
+	"API Base URL": "API საბაზისო URL",
+	"API Key": "API გასაღები",
+	"API Key created.": "API გასაღები შექმნილია.",
+	"API keys": "API გასაღები",
+	"April": "აპრილი",
+	"Archive": "არქივი",
+	"Archive All Chats": "არქივი ყველა ჩატი",
+	"Archived Chats": "ჩატის ისტორიის არქივი",
+	"are allowed - Activate this command by typing": "დაშვებულია - ბრძანების გასააქტიურებლად აკრიფეთ:",
+	"Are you sure?": "დარწმუნებული ხარ?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "ფაილის ჩაწერა",
+	"Attention to detail": "დეტალური მიმართვა",
+	"Audio": "ხმოვანი",
+	"August": "აგვისტო",
+	"Auto-playback response": "ავტომატური დაკვრის პასუხი",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 საბაზისო მისამართი",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 საბაზისო მისამართი აუცილებელია",
+	"Available list": "",
+	"available!": "ხელმისაწვდომია!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "უკან",
+	"Bad Response": "ხარვეზი",
+	"Banners": "რეკლამა",
+	"Base Model (From)": "საბაზო მოდელი (-დან)",
+	"Batch Size (num_batch)": "",
+	"before": "ადგილზე",
+	"Being lazy": "ჩაიტყვევა",
+	"Brave Search API Key": "Brave Search API გასაღები",
+	"Bypass SSL verification for Websites": "SSL-ის ვერიფიკაციის გააუქმება ვებსაიტებზე",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "გაუქმება",
+	"Capabilities": "შესაძლებლობები",
+	"Change Password": "პაროლის შეცვლა",
+	"Character": "",
+	"Chat": "მიმოწერა",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "ჩატის ბულბი",
+	"Chat Controls": "",
+	"Chat direction": "ჩატის მიმართულება",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "მიმოწერები",
+	"Check Again": "თავიდან შემოწმება",
+	"Check for updates": "განახლებების ძიება",
+	"Checking for updates...": "მიმდინარეობს განახლებების ძიება...",
+	"Choose a model before saving...": "აირჩიეთ მოდელი შენახვამდე...",
+	"Chunk Overlap": "გადახურვა ფრაგმენტულია",
+	"Chunk Params": "გადახურვის პარამეტრები",
+	"Chunk Size": "გადახურვის ზომა",
+	"Citation": "ციტატა",
+	"Clear memory": "",
+	"Click here for help.": "დახმარებისთვის, დააკლიკე აქ",
+	"Click here to": "დააკლიკე აქ",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "ასარჩევად, დააკლიკე აქ",
+	"Click here to select a csv file.": "ასარჩევად, დააკლიკე აქ",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "დააკლიკე აქ",
+	"Click on the user role button to change a user's role.": "დააკლიკეთ მომხმარებლის როლის ღილაკს რომ შეცვალოთ მომხმარების როლი",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "კლონი",
+	"Close": "დახურვა",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "ნაკრები",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI საბაზისო URL",
+	"ComfyUI Base URL is required.": "ComfyUI საბაზისო URL აუცილებელია.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "ბრძანება",
+	"Completions": "",
+	"Concurrent Requests": "თანმხლები მოთხოვნები",
+	"Confirm": "",
+	"Confirm Password": "პაროლის დამოწმება",
+	"Confirm your action": "",
+	"Connections": "კავშირები",
+	"Contact Admin for WebUI Access": "",
+	"Content": "კონტენტი",
+	"Content Extraction": "",
+	"Context Length": "კონტექსტის სიგრძე",
+	"Continue Response": "პასუხის გაგრძელება",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "ყავს ჩათის URL-ი კლიპბორდში!",
+	"Copied to clipboard": "",
+	"Copy": "კოპირება",
+	"Copy last code block": "ბოლო ბლოკის კოპირება",
+	"Copy last response": "ბოლო პასუხის კოპირება",
+	"Copy Link": "კოპირება",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "კლავიატურაზე კოპირება წარმატებით დასრულდა",
+	"Create a model": "შექმენით მოდელი",
+	"Create Account": "ანგარიშის შექმნა",
+	"Create Knowledge": "",
+	"Create new key": "პირადი ღირებულბრის შექმნა",
+	"Create new secret key": "პირადი ღირებულბრის შექმნა",
+	"Created at": "შექმნილია",
+	"Created At": "შექმნილია",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "მიმდინარე მოდელი",
+	"Current Password": "მიმდინარე პაროლი",
+	"Custom": "საკუთარი",
+	"Customize models for a specific purpose": "მოდელების მორგება კონკრეტული მიზნისთვის",
+	"Dark": "მუქი",
+	"Dashboard": "",
+	"Database": "მონაცემთა ბაზა",
+	"December": "დეკემბერი",
+	"Default": "დეფოლტი",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "დეფოლტ (SentenceTransformers)",
+	"Default Model": "ნაგულისხმები მოდელი",
+	"Default model updated": "დეფოლტ მოდელი განახლებულია",
+	"Default Prompt Suggestions": "დეფოლტ პრომპტი პირველი პირველი",
+	"Default User Role": "მომხმარებლის დეფოლტ როლი",
+	"Delete": "წაშლა",
+	"Delete a model": "მოდელის წაშლა",
+	"Delete All Chats": "ყველა ჩატის წაშლა",
+	"Delete chat": "შეტყობინების წაშლა",
+	"Delete Chat": "შეტყობინების წაშლა",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "ბმულის წაშლა",
+	"Delete tool?": "",
+	"Delete User": "მომხმარებლის წაშლა",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} წაშლილია",
+	"Deleted {{name}}": "Deleted {{name}}",
+	"Description": "აღწერა",
+	"Didn't fully follow instructions": "ვერ ყველა ინფორმაციისთვის ვერ ხელახლა ჩაწერე",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "გაიგეთ მოდელი",
+	"Discover a prompt": "აღმოაჩინეთ მოთხოვნა",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "აღმოაჩინეთ, ჩამოტვირთეთ და შეისწავლეთ მორგებული მოთხოვნები",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "აღმოაჩინეთ, ჩამოტვირთეთ და შეისწავლეთ მოდელის წინასწარ პარამეტრები",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "ჩატში აჩვენე მომხმარებლის სახელი თქვენს ნაცვლად",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "დოკუმენტი",
+	"Documentation": "",
+	"Documents": "დოკუმენტები",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "არ ამყარებს გარე კავშირებს და თქვენი მონაცემები უსაფრთხოდ რჩება თქვენს ადგილობრივ სერვერზე.",
+	"Don't have an account?": "არ გაქვს ანგარიში?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "არ ეთიკურია ფართოდ",
+	"Done": "",
+	"Download": "ჩამოტვირთვა გაუქმებულია",
+	"Download canceled": "ჩამოტვირთვა გაუქმებულია",
+	"Download Database": "გადმოწერე მონაცემთა ბაზა",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "გადაიტანეთ ფაილები აქ, რათა დაამატოთ ისინი მიმოწერაში",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "მაგალითად, '30წ', '10მ'. მოქმედი დროის ერთეულები: 'წ', 'წთ', 'სთ'.",
+	"Edit": "რედაქტირება",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "მომხმარებლის ედიტირება",
+	"ElevenLabs": "",
+	"Email": "ელ-ფოსტა",
+	"Embedding Batch Size": "",
+	"Embedding Model": "ჩასმის ძირითადი პროგრამა",
+	"Embedding Model Engine": "ჩასმის ძირითადი პროგრამა",
+	"Embedding model set to \"{{embedding_model}}\"": "ჩასმის ძირითადი პროგრამა ჩართულია \"{{embedding_model}}\"",
+	"Enable Community Sharing": "საზოგადოების გაზიარების ჩართვა",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "ახალი რეგისტრაციების ჩართვა",
+	"Enable Web Search": "ვებ ძიების ჩართვა",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "გთხოვთ, უზრუნველყოთ, რომთქვევის CSV-ფაილი შეიცავს 4 ველი, ჩაწერილი ორივე ველი უდრის პირველი ველით.",
+	"Enter {{role}} message here": "შეიყვანე {{role}} შეტყობინება აქ",
+	"Enter a detail about yourself for your LLMs to recall": "შეიყვანე დეტალი ჩემთათვის, რომ ჩვენი LLMs-ს შეიძლოს აღაქვს",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "შეიყვანეთ Brave Search API გასაღები",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "შეიყვანეთ ნაწილის გადახურვა",
+	"Enter Chunk Size": "შეიყვანე ბლოკის ზომა",
+	"Enter description": "",
+	"Enter Github Raw URL": "შეიყვანეთ Github Raw URL",
+	"Enter Google PSE API Key": "შეიყვანეთ Google PSE API გასაღები",
+	"Enter Google PSE Engine Id": "შეიყვანეთ Google PSE ძრავის ID",
+	"Enter Image Size (e.g. 512x512)": "შეიყვანეთ სურათის ზომა (მაგ. 512x512)",
+	"Enter language codes": "შეიყვანეთ ენის კოდი",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "შეიყვანეთ მოდელის ტეგი (მაგ. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "შეიყვანეთ ნაბიჯების რაოდენობა (მაგ. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "შეიყვანეთ ქულა",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "შეიყვანეთ Searxng Query URL",
+	"Enter Serper API Key": "შეიყვანეთ Serper API Key",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "შეიყვანეთ Serpstack API Key",
+	"Enter stop sequence": "შეიყვანეთ ტოპ თანმიმდევრობა",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "შეიყვანეთ Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "შეიყვანეთ მისამართი (მაგალითად http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "შეიყვანეთ მისამართი (მაგალითად http://localhost:11434)",
+	"Enter Your Email": "შეიყვანეთ თქვენი ელ-ფოსტა",
+	"Enter Your Full Name": "შეიყვანეთ თქვენი სრული სახელი",
+	"Enter your message": "",
+	"Enter Your Password": "შეიყვანეთ თქვენი პაროლი",
+	"Enter Your Role": "შეიყვანეთ თქვენი როლი",
+	"Error": "შეცდომა",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "ექსპერიმენტალური",
+	"Export": "ექსპორტი",
+	"Export All Chats (All Users)": "ექსპორტი ყველა ჩათი (ყველა მომხმარებელი)",
+	"Export chat (.json)": "",
+	"Export Chats": "მიმოწერის ექსპორტირება",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "ექსპორტის მოდელები",
+	"Export Prompts": "მოთხოვნების ექსპორტი",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "API ღილაკის შექმნა ვერ მოხერხდა.",
+	"Failed to read clipboard contents": "ბუფერში შიგთავსის წაკითხვა ვერ მოხერხდა",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "თებერვალი",
+	"Feedback History": "",
+	"Feel free to add specific details": "უფასოდ დაამატეთ დეტალები",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "ფაილური რეჟიმი",
+	"File not found.": "ფაილი ვერ მოიძებნა",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "აღმოჩენილია თითის ანაბეჭდის გაყალბება: ინიციალების გამოყენება ავატარად შეუძლებელია. დეფოლტ პროფილის დეფოლტ სურათი.",
+	"Fluidly stream large external response chunks": "თხევადი ნაკადი დიდი გარე საპასუხო ნაწილაკების",
+	"Focus chat input": "ჩეთის შეყვანის ფოკუსი",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "ყველა ინსტრუქცია უზრუნველყოფა",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "სიხშირის ჯარიმა",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "ზოგადი",
+	"General Settings": "ზოგადი პარამეტრები",
+	"Generate Image": "",
+	"Generating search query": "საძიებო მოთხოვნის გენერირება",
+	"Generation Info": "გენერაციის ინფორმაცია",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "დიდი პასუხი",
+	"Google PSE API Key": "Google PSE API გასაღები",
+	"Google PSE Engine Id": "Google PSE ძრავის Id",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "არა უფლება ჩაწერა",
+	"Hello, {{name}}": "გამარჯობა, {{name}}",
+	"Help": "დახმარება",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "დამალვა",
+	"Hide Model": "",
+	"How can I help you today?": "როგორ შემიძლია დაგეხმარო დღეს?",
+	"Hybrid Search": "ჰიბრიდური ძებნა",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "სურათების გენერაცია (ექსპერიმენტული)",
+	"Image Generation Engine": "სურათის გენერაციის ძრავა",
+	"Image Settings": "სურათის პარამეტრები",
+	"Images": "სურათები",
+	"Import Chats": "მიმოწერების იმპორტი",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "იმპორტის მოდელები",
+	"Import Prompts": "მოთხოვნების იმპორტი",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "ჩართეთ `--api` დროშა stable-diffusion-webui-ის გაშვებისას",
+	"Info": "ინფორმაცია",
+	"Input commands": "შეყვანით ბრძანებებს",
+	"Install from Github URL": "დააინსტალირეთ Github URL- დან",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "ინტერფეისი",
+	"Invalid file format.": "",
+	"Invalid Tag": "არასწორი ტეგი",
+	"January": "იანვარი",
+	"join our Discord for help.": "შეუერთდით ჩვენს Discord-ს დახმარებისთვის",
+	"JSON": "JSON",
+	"JSON Preview": "JSON გადახედვა",
+	"July": "ივნისი",
+	"June": "ივლა",
+	"JWT Expiration": "JWT-ის ვადა",
+	"JWT Token": "JWT ტოკენი",
+	"Keep Alive": "აქტიურად დატოვება",
+	"Keyboard shortcuts": "კლავიატურის მალსახმობები",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "ენა",
+	"large language models, locally.": "",
+	"Last Active": "ბოლო აქტიური",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "მსუბუქი",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "შესაძლოა LLM-ებმა შეცდომები დაუშვან. გადაამოწმეთ მნიშვნელოვანი ინფორმაცია.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "დამზადებულია OpenWebUI საზოგადოების მიერ",
+	"Make sure to enclose them with": "დარწმუნდით, რომ დაურთეთ ისინი",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "მოდელების მართვა",
+	"Manage Ollama Models": "Ollama მოდელების მართვა",
+	"Manage Pipelines": "მილსადენების მართვა",
+	"March": "მარტივი",
+	"Max Tokens (num_predict)": "მაქს ტოკენსი (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "მაქსიმუმ 3 მოდელის ჩამოტვირთვა შესაძლებელია ერთდროულად. Გთხოვთ სცადოთ მოგვიანებით.",
+	"May": "მაი",
+	"Memories accessible by LLMs will be shown here.": "ლლმ-ს აქვს ხელმისაწვდომი მემორიები აქ იქნება.",
+	"Memory": "მემორია",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "შეტყობინებები, რომელსაც თქვენ აგზავნით თქვენი ბმულის შექმნის შემდეგ, არ იქნება გაზიარებული. URL– ის მქონე მომხმარებლებს შეეძლებათ ნახონ საერთო ჩატი.",
+	"Min P": "",
+	"Minimum Score": "მინიმალური ქულა",
+	"Mirostat": "მიროსტატი",
+	"Mirostat Eta": "მიროსტატი ეტა",
+	"Mirostat Tau": "მიროსტატი ტაუ",
+	"MMMM DD, YYYY": "თვე დღე, წელი",
+	"MMMM DD, YYYY HH:mm": "თვე დღე, წელი HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "მოდელი „{{modelName}}“ წარმატებით ჩამოიტვირთა.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "მოდელი „{{modelTag}}“ უკვე ჩამოტვირთვის რიგშია.",
+	"Model {{modelId}} not found": "მოდელი {{modelId}} ვერ მოიძებნა",
+	"Model {{modelName}} is not vision capable": "Model {{modelName}} is not vision capable",
+	"Model {{name}} is now {{status}}": "Model {{name}} is now {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "აღმოჩენილია მოდელის ფაილური სისტემის გზა. განახლებისთვის საჭიროა მოდელის მოკლე სახელი, გაგრძელება შეუძლებელია.",
+	"Model ID": "მოდელის ID",
+	"Model Name": "",
+	"Model not selected": "მოდელი არ არის არჩეული",
+	"Model Params": "მოდელის პარამები",
+	"Model updated successfully": "",
+	"Model Whitelisting": "მოდელის თეთრ სიაში შეყვანა",
+	"Model(s) Whitelisted": "მოდელ(ებ)ი თეთრ სიაშია",
+	"Modelfile Content": "მოდელური ფაილის კონტენტი",
+	"Models": "მოდელები",
+	"more": "",
+	"More": "ვრცლად",
+	"Move to Top": "",
+	"Name": "სახელი",
+	"Name your model": "დაასახელეთ თქვენი მოდელი",
+	"New Chat": "ახალი მიმოწერა",
+	"New folder": "",
+	"New Password": "ახალი პაროლი",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "ჩვენ ვერ პოულობით ნაპოვნი ჩაწერები",
+	"No search query generated": "ძიების მოთხოვნა არ არის გენერირებული",
+	"No source available": "წყარო არ არის ხელმისაწვდომი",
+	"No valves to update": "",
+	"None": "არცერთი",
+	"Not factually correct": "არ ვეთანხმები პირდაპირ ვერც ვეთანხმები",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "შენიშვნა: თუ თქვენ დააყენებთ მინიმალურ ქულას, ძებნა დააბრუნებს მხოლოდ დოკუმენტებს მინიმალური ქულის მეტი ან ტოლი ქულით.",
+	"Notes": "",
+	"Notifications": "შეტყობინება",
+	"November": "ნოემბერი",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (ოლამა)",
+	"OAuth ID": "",
+	"October": "ოქტომბერი",
+	"Off": "გამორთვა",
+	"Okay, Let's Go!": "კარგი, წავედით!",
+	"OLED Dark": "OLED მუქი",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API გამორთულია",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Ollama ვერსია",
+	"On": "ჩართვა",
+	"Only": "მხოლოდ",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "ბრძანების სტრიქონში დაშვებულია მხოლოდ ალფანუმერული სიმბოლოები და დეფისები.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "უი! როგორც ჩანს, მისამართი არასწორია. გთხოვთ, გადაამოწმოთ და ისევ სცადოთ.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "უპს! თქვენ იყენებთ მხარდაუჭერელ მეთოდს (მხოლოდ frontend). გთხოვთ, მოემსახუროთ WebUI-ს ბექენდიდან",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "ახალი მიმოწერის გახსნა",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API პარამეტრები",
+	"OpenAI API Key is required.": "OpenAI API გასაღები აუცილებელია",
+	"OpenAI URL/Key required.": "OpenAI URL/Key აუცილებელია",
+	"or": "ან",
+	"Other": "სხვა",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "პაროლი",
+	"PDF document (.pdf)": "PDF დოკუმენტი (.pdf)",
+	"PDF Extract Images (OCR)": "PDF იდან ამოღებული სურათები (OCR)",
+	"pending": "ლოდინის რეჟიმშია",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "ნებართვა უარყოფილია მიკროფონზე წვდომისას: {{error}}",
+	"Personalization": "პერსონალიზაცია",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "მილსადენები",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "მილსადენების სარქველები",
+	"Plain text (.txt)": "ტექსტი (.txt)",
+	"Playground": "სათამაშო მოედანი",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "პოზიტიური ანგარიში",
+	"Previous 30 days": "უკან 30 დღე",
+	"Previous 7 days": "უკან 7 დღე",
+	"Profile Image": "პროფილის სურათი",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (მაგ. მითხარი სახალისო ფაქტი რომის იმპერიის შესახებ)",
+	"Prompt Content": "მოთხოვნის შინაარსი",
+	"Prompt suggestions": "მოთხოვნის რჩევები",
+	"Prompts": "მოთხოვნები",
+	"Pull \"{{searchValue}}\" from Ollama.com": "ჩაიამოვეთ \"{{searchValue}}\" Ollama.com-იდან",
+	"Pull a model from Ollama.com": "Ollama.com იდან მოდელის გადაწერა ",
+	"Query Params": "პარამეტრების ძიება",
+	"RAG Template": "RAG შაბლონი",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "ხმის ჩაწერა",
+	"Record voice": "ხმის ჩაწერა",
+	"Redirecting you to OpenWebUI Community": "გადამისამართდებით OpenWebUI საზოგადოებაში",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "უარა, როგორც უნდა იყოს",
+	"Regenerate": "ხელახლა გენერირება",
+	"Release Notes": "Გამოშვების შენიშვნები",
+	"Relevance": "",
+	"Remove": "პოპულარობის რაოდენობა",
+	"Remove Model": "პოპულარობის რაოდენობა",
+	"Rename": "პოპულარობის რაოდენობა",
+	"Repeat Last N": "გაიმეორეთ ბოლო N",
+	"Request Mode": "მოთხოვნის რეჟიმი",
+	"Reranking Model": "რექვექტირება",
+	"Reranking model disabled": "რექვექტირება არაა ჩართული",
+	"Reranking model set to \"{{reranking_model}}\"": "Reranking model set to \"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "პასუხის ავტომატური კოპირება ბუფერში",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "როლი",
+	"Rosé Pine": "ვარდისფერი ფიჭვის ხე",
+	"Rosé Pine Dawn": "ვარდისფერი ფიჭვის გარიჟრაჟი",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "შენახვა",
+	"Save & Create": "დამახსოვრება და შექმნა",
+	"Save & Update": "დამახსოვრება და განახლება",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "ჩეთის ისტორიის შენახვა პირდაპირ თქვენი ბრაუზერის საცავში აღარ არის მხარდაჭერილი. გთხოვთ, დაუთმოთ და წაშალოთ თქვენი ჩატის ჟურნალები ქვემოთ მოცემულ ღილაკზე დაწკაპუნებით. არ ინერვიულოთ, თქვენ შეგიძლიათ მარტივად ხელახლა შემოიტანოთ თქვენი ჩეთის ისტორია ბექენდში",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "ძიება",
+	"Search a model": "მოდელის ძიება",
+	"Search Chats": "ჩატების ძებნა",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "საძიებო მოდელები",
+	"Search Prompts": "მოთხოვნების ძიება",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "ძიების შედეგების რაოდენობა",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Searched {{count}} sites_one",
+	"Searched {{count}} sites_other": "Searched {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng Query URL",
+	"See readme.md for instructions": "იხილეთ readme.md ინსტრუქციებისთვის",
+	"See what's new": "სიახლეების ნახვა",
+	"Seed": "სიდი",
+	"Select a base model": "აირჩიეთ ბაზის მოდელი",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "მოდელის არჩევა",
+	"Select a pipeline": "აირჩიეთ მილსადენი",
+	"Select a pipeline url": "აირჩიეთ მილსადენის url",
+	"Select a tool": "",
+	"Select an Ollama instance": "Ollama ინსტანსის არჩევა",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "მოდელის არჩევა",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "შერჩეული მოდელი (ებ) ი არ უჭერს მხარს გამოსახულების შეყვანას",
+	"Semantic distance to query": "",
+	"Send": "გაგზავნა",
+	"Send a Message": "შეტყობინების გაგზავნა",
+	"Send message": "შეტყობინების გაგზავნა",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "სექტემბერი",
+	"Serper API Key": "Serper API Key",
+	"Serply API Key": "",
+	"Serpstack API Key": "Serpstack API Key",
+	"Server connection verified": "სერვერთან კავშირი დადასტურებულია",
+	"Set as default": "დეფოლტად დაყენება",
+	"Set CFG Scale": "",
+	"Set Default Model": "დეფოლტ მოდელის დაყენება",
+	"Set embedding model (e.g. {{model}})": "ჩვენება მოდელის დაყენება (მაგ. {{model}})",
+	"Set Image Size": "სურათის ზომის დაყენება",
+	"Set reranking model (e.g. {{model}})": "რეტარირება მოდელის დაყენება (მაგ. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "ნაბიჯების დაყენება",
+	"Set Task Model": "დააყენეთ სამუშაო მოდელი",
+	"Set Voice": "ხმის დაყენება",
+	"Set whisper model": "",
+	"Settings": "ხელსაწყოები",
+	"Settings saved successfully!": "პარამეტრები წარმატებით განახლდა!",
+	"Share": "გაზიარება",
+	"Share Chat": "გაზიარება",
+	"Share to OpenWebUI Community": "გააზიარე OpenWebUI საზოგადოებაში ",
+	"short-summary": "მოკლე შინაარსი",
+	"Show": "ჩვენება",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "მალსახმობების ჩვენება",
+	"Show your support!": "",
+	"Showcased creativity": "ჩვენებული ქონება",
+	"Sign in": "ავტორიზაცია",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "გასვლა",
+	"Sign up": "რეგისტრაცია",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "წყარო",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "მეტყველების ამოცნობის შეცდომა: {{error}}",
+	"Speech-to-Text Engine": "ხმოვან-ტექსტური ძრავი",
+	"Stop": "",
+	"Stop Sequence": "შეჩერების თანმიმდევრობა",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "მეტყველების ამოცნობის პარამეტრები",
+	"Subtitle (e.g. about the Roman Empire)": "სუბტიტრები (მაგ. რომის იმპერიის შესახებ)",
+	"Success": "წარმატება",
+	"Successfully updated.": "წარმატებით განახლდა",
+	"Suggested": "პირდაპირ პოპულარული",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "სისტემა",
+	"System Instructions": "",
+	"System Prompt": "სისტემური მოთხოვნა",
+	"Tags": "ტეგები",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "ჩვენთან დავუკავშირდით",
+	"Temperature": "ტემპერატურა",
+	"Template": "შაბლონი",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "ტექსტურ-ხმოვანი ძრავი",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "მადლობა გამოხმაურებისთვის!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "ქულა 0.0 (0%) და 1.0 (100%) ჩაშენებული უნდა იყოს.",
+	"Theme": "თემა",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "ეს უზრუნველყოფს, რომ თქვენი ძვირფასი საუბრები უსაფრთხოდ შეინახება თქვენს backend მონაცემთა ბაზაში. Გმადლობთ!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "ვრცლად აღწერა",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "რჩევა: განაახლეთ რამდენიმე ცვლადი სლოტი თანმიმდევრულად, ყოველი ჩანაცვლების შემდეგ ჩატის ღილაკზე დაჭერით.",
+	"Title": "სათაური",
+	"Title (e.g. Tell me a fun fact)": "სათაური (მაგ. გაიხსნე რაღაც ხარისხი)",
+	"Title Auto-Generation": "სათაურის ავტო-გენერაცია",
+	"Title cannot be an empty string.": "სათაური ცარიელი ველი ვერ უნდა იყოს.",
+	"Title Generation Prompt": "სათაურის გენერაციის მოთხოვნა ",
+	"To access the available model names for downloading,": "ჩამოტვირთვისთვის ხელმისაწვდომი მოდელების სახელებზე წვდომისთვის",
+	"To access the GGUF models available for downloading,": "ჩასატვირთად ხელმისაწვდომი GGUF მოდელებზე წვდომისთვის",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "ჩატში",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "დღეს",
+	"Toggle settings": "პარამეტრების გადართვა",
+	"Toggle sidebar": "გვერდითი ზოლის გადართვა",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "ტოპ K",
+	"Top P": "ტოპ P",
+	"Trouble accessing Ollama?": "Ollama-ს ვერ უკავშირდები?",
+	"TTS Model": "",
+	"TTS Settings": "TTS პარამეტრები",
+	"TTS Voice": "",
+	"Type": "ტიპი",
+	"Type Hugging Face Resolve (Download) URL": "სცადე გადმოწერო Hugging Face Resolve URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "{{provider}}-თან დაკავშირების პრობლემა წარმოიშვა.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "განახლება და ბმულის კოპირება",
+	"Update for the latest features and improvements.": "",
+	"Update password": "პაროლის განახლება",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "GGUF მოდელის ატვირთვა",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "ატვირთეთ ფაილები",
+	"Upload Pipeline": "",
+	"Upload Progress": "პროგრესის ატვირთვა",
+	"URL Mode": "URL რეჟიმი",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "გამოიყენე Gravatar",
+	"Use Initials": "გამოიყენე ინიციალები",
+	"use_mlock (Ollama)": "use_mlock (ოლამა)",
+	"use_mmap (Ollama)": "use_mmap (ოლამა)",
+	"user": "მომხმარებელი",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "მომხმარებლის უფლებები",
+	"Users": "მომხმარებლები",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "გამოყენება",
+	"Valid time units:": "მოქმედი დროის ერთეულები",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "ცვლადი",
+	"variable to have them replaced with clipboard content.": "ცვლადი, რომ შეცვალოს ისინი ბუფერში შიგთავსით.",
+	"Version": "ვერსია",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "გაფრთხილება",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "გაფრთხილება: თუ განაახლებთ ან შეცვლით ჩანერგვის მოდელს, მოგიწევთ ყველა დოკუმენტის ხელახლა იმპორტი.",
+	"Web": "ვები",
+	"Web API": "",
+	"Web Loader Settings": "ვების ჩატარების პარამეტრები",
+	"Web Search": "ვებ ძებნა",
+	"Web Search Engine": "ვებ საძიებო სისტემა",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "WebUI პარამეტრები",
+	"WebUI will make requests to": "WebUI გამოგიგზავნით მოთხოვნებს",
+	"What’s New in": "რა არის ახალი",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "ვულერი",
+	"Write a prompt suggestion (e.g. Who are you?)": "დაწერეთ მოკლე წინადადება (მაგ. ვინ ხარ?",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "დაწერეთ რეზიუმე 50 სიტყვით, რომელიც აჯამებს [თემას ან საკვანძო სიტყვას].",
+	"Write something...": "",
+	"Yesterday": "აღდგენა",
+	"You": "ჩემი",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "თქვენ არ შეგიძლიათ ბაზის მოდელის კლონირება",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "არ ხართ არქივირებული განხილვები.",
+	"You have shared this chat": "ამ ჩატის გააგზავნა",
+	"You're a helpful assistant.": "თქვენ სასარგებლო ასისტენტი ხართ.",
+	"You're now logged in.": "თქვენ შესული ხართ.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube Loader Settings"
+}
diff --git a/src/lib/i18n/locales/ko-KR/translation.json b/src/lib/i18n/locales/ko-KR/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..eb12916344284b10110296cb46ade7ebad221d34
--- /dev/null
+++ b/src/lib/i18n/locales/ko-KR/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "만료 없음은 '-1', 아니면 's', 'm', 'h', 'd', 'w' 중 하나를 사용하세요.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(예: `sh webui.sh --api`)",
+	"(latest)": "(latest)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: 기본 모델은 삭제할 수 없습니다.",
+	"{{user}}'s Chats": "{{user}}의 채팅",
+	"{{webUIName}} Backend Required": "{{webUIName}} 백엔드가 필요합니다.",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "작업 모델은 채팅 및 웹 검색 쿼리에 대한 제목 생성 등의 작업 수행 시 사용됩니다.",
+	"a user": "사용자",
+	"About": "정보",
+	"Account": "계정",
+	"Account Activation Pending": "계정 활성화 보류",
+	"Accurate information": "정확한 정보",
+	"Actions": "",
+	"Active Users": "활성 사용자",
+	"Add": "추가",
+	"Add a model id": "모델 ID 추가",
+	"Add a short description about what this model does": "모델의 기능에 대한 간단한 설명 추가",
+	"Add a short title for this prompt": "프롬프트에 대한 간단한 제목 추가",
+	"Add a tag": "태그 추가",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "프롬프트 추가",
+	"Add Files": "파일 추가",
+	"Add Memory": "메모리 추가",
+	"Add Model": "모델 추가",
+	"Add Tag": "",
+	"Add Tags": "태그 추가",
+	"Add text content": "",
+	"Add User": "사용자 추가",
+	"Adjusting these settings will apply changes universally to all users.": "이 설정을 조정하면 모든 사용자에게 적용됩니다.",
+	"admin": "관리자",
+	"Admin": "관리자",
+	"Admin Panel": "관리자 패널",
+	"Admin Settings": "관리자 설정",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "관리자는 항상 모든 도구에 접근할 수 있지만, 사용자는 워크스페이스에서 모델마다 도구를 할당받아야 합니다.",
+	"Advanced Parameters": "고급 파라미터",
+	"Advanced Params": "고급 파라미터",
+	"All chats": "",
+	"All Documents": "모든 문서",
+	"Allow Chat Deletion": "채팅 삭제 허용",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "외부 음성 허용",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "사용자 위치 활용 허용",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "영문자, 숫자, 하이픈",
+	"Already have an account?": "이미 계정이 있으신가요?",
+	"an assistant": "어시스턴트",
+	"and": "그리고",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "새로운 공유 링크를 생성합니다.",
+	"API Base URL": "API 기본 URL",
+	"API Key": "API 키",
+	"API Key created.": "API 키가 생성되었습니다.",
+	"API keys": "API 키",
+	"April": "4월",
+	"Archive": "아카이브",
+	"Archive All Chats": "모든 채팅 아카이브",
+	"Archived Chats": "아카이브된 채팅",
+	"are allowed - Activate this command by typing": "허용됩니다. - 이 명령을 활성화하려면 입력하세요.",
+	"Are you sure?": "확실합니까?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "파일 첨부",
+	"Attention to detail": "세부 사항에 대한 주의",
+	"Audio": "오디오",
+	"August": "8월",
+	"Auto-playback response": "응답 자동 재생",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 기본 URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 기본 URL 설정이 필요합니다.",
+	"Available list": "",
+	"available!": "사용 가능!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "뒤로가기",
+	"Bad Response": "잘못된 응답",
+	"Banners": "배너",
+	"Base Model (From)": "기본 모델(시작)",
+	"Batch Size (num_batch)": "배치 크기 (num_batch)",
+	"before": "이전",
+	"Being lazy": "게으름 피우기",
+	"Brave Search API Key": "Brave Search API 키",
+	"Bypass SSL verification for Websites": "웹 사이트에 대한 SSL 검증 무시: ",
+	"Call": "콜",
+	"Call feature is not supported when using Web STT engine": "웹 STT 엔진 사용 시, 콜 기능은 지원되지 않습니다.",
+	"Camera": "카메라",
+	"Cancel": "취소",
+	"Capabilities": "기능",
+	"Change Password": "비밀번호 변경",
+	"Character": "",
+	"Chat": "채팅",
+	"Chat Background Image": "채팅 배경 이미지",
+	"Chat Bubble UI": "버블형 채팅 UI",
+	"Chat Controls": "",
+	"Chat direction": "채팅 방향",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "채팅",
+	"Check Again": "다시 확인",
+	"Check for updates": "업데이트 확인",
+	"Checking for updates...": "업데이트 확인중...",
+	"Choose a model before saving...": "저장하기 전에 모델을 선택하세요...",
+	"Chunk Overlap": "Chunk 오버랩",
+	"Chunk Params": "Chunk 파라미터",
+	"Chunk Size": "Chunk 크기",
+	"Citation": "인용",
+	"Clear memory": "메모리 초기화",
+	"Click here for help.": "도움말을 보려면 여기를 클릭하세요.",
+	"Click here to": "여기를 클릭하면",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "선택하려면 여기를 클릭하세요.",
+	"Click here to select a csv file.": "csv 파일을 선택하려면 여기를 클릭하세요.",
+	"Click here to select a py file.": "py 파일을 선택하려면 여기를 클릭하세요.",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "여기를 클릭하세요.",
+	"Click on the user role button to change a user's role.": "사용자 역할 버튼을 클릭하여 사용자의 역할을 변경하세요.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "복제",
+	"Close": "닫기",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "컬렉션",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI 기본 URL",
+	"ComfyUI Base URL is required.": "ComfyUI 기본 URL이 필요합니다.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "명령",
+	"Completions": "",
+	"Concurrent Requests": "동시 요청 수",
+	"Confirm": "확인",
+	"Confirm Password": "비밀번호 확인",
+	"Confirm your action": "액션 확인",
+	"Connections": "연결",
+	"Contact Admin for WebUI Access": "WebUI 접속을 위해서는 관리자에게 연락 필요",
+	"Content": "내용",
+	"Content Extraction": "",
+	"Context Length": "내용 길이",
+	"Continue Response": "대화 계속",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "공유 채팅 URL이 클립보드에 복사되었습니다!",
+	"Copied to clipboard": "",
+	"Copy": "복사",
+	"Copy last code block": "마지막 코드 블록 복사",
+	"Copy last response": "마지막 응답 복사",
+	"Copy Link": "링크 복사",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "클립보드에 복사되었습니다!",
+	"Create a model": "모델 만들기",
+	"Create Account": "계정 만들기",
+	"Create Knowledge": "",
+	"Create new key": "새 키 만들기",
+	"Create new secret key": "새 비밀 키 만들기",
+	"Created at": "생성일",
+	"Created At": "생성일",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "현재 모델",
+	"Current Password": "현재 비밀번호",
+	"Custom": "사용자 정의",
+	"Customize models for a specific purpose": "특정 목적을 위한 모델 사용자 지정",
+	"Dark": "Dark",
+	"Dashboard": "대시보드",
+	"Database": "데이터베이스",
+	"December": "12월",
+	"Default": "기본값",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "기본값 (SentenceTransformers)",
+	"Default Model": "기본 모델",
+	"Default model updated": "기본 모델이 업데이트되었습니다.",
+	"Default Prompt Suggestions": "기본 프롬프트 제안",
+	"Default User Role": "기본 사용자 역할",
+	"Delete": "삭제",
+	"Delete a model": "모델 삭제",
+	"Delete All Chats": "모든 채팅 삭제",
+	"Delete chat": "채팅 삭제",
+	"Delete Chat": "채팅 삭제",
+	"Delete chat?": "채팅을 삭제하겠습니까?",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "이 링크를 삭제합니다.",
+	"Delete tool?": "",
+	"Delete User": "사용자 삭제",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} 삭제됨",
+	"Deleted {{name}}": "{{name}}을(를) 삭제했습니다.",
+	"Description": "설명",
+	"Didn't fully follow instructions": "완전히 지침을 따르지 않음",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "모델 검색",
+	"Discover a prompt": "프롬프트 검색",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "사용자 정의 프롬프트 검색, 다운로드 및 탐색",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "모델 사전 설정 검색, 다운로드 및 탐색",
+	"Dismissible": "제외가능",
+	"Display Emoji in Call": "콜(call)에서 이모지 표시",
+	"Display the username instead of You in the Chat": "채팅에서 '당신' 대신 사용자 이름 표시",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "문서",
+	"Documentation": "문서 조사",
+	"Documents": "문서",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "어떠한 외부 연결도 하지 않으며, 데이터는 로컬에서 호스팅되는 서버에 안전하게 유지됩니다.",
+	"Don't have an account?": "계정이 없으신가요?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "스타일을 좋아하지 않으세요?",
+	"Done": "",
+	"Download": "다운로드",
+	"Download canceled": "다운로드 취소",
+	"Download Database": "데이터베이스 다운로드",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "대화에 추가할 파일을 여기에 드롭하세요.",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "예: '30s','10m'. 유효한 시간 단위는 's', 'm', 'h'입니다.",
+	"Edit": "편집",
+	"Edit Arena Model": "",
+	"Edit Memory": "메모리 편집",
+	"Edit User": "사용자 편집",
+	"ElevenLabs": "",
+	"Email": "이메일",
+	"Embedding Batch Size": "임베딩 배치 크기",
+	"Embedding Model": "임베딩 모델",
+	"Embedding Model Engine": "임베딩 모델 엔진",
+	"Embedding model set to \"{{embedding_model}}\"": "임베딩 모델을 \"{{embedding_model}}\"로 설정함",
+	"Enable Community Sharing": "커뮤니티 공유 활성화",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "새 회원가입 활성화",
+	"Enable Web Search": "웹 검색 활성화",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "CSV 파일에 이름, 이메일, 비밀번호, 역할 4개의 컬럼이 순서대로 포함되어 있는지 확인하세요.",
+	"Enter {{role}} message here": "여기에 {{role}} 메시지 입력",
+	"Enter a detail about yourself for your LLMs to recall": "자신에 대한 세부사항을 입력하여 LLM들이 기억할 수 있도록 하세요.",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Brave Search API Key 입력",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "청크 오버랩 입력",
+	"Enter Chunk Size": "청크 크기 입력",
+	"Enter description": "",
+	"Enter Github Raw URL": "Github Raw URL 입력",
+	"Enter Google PSE API Key": "Google PSE API 키 입력",
+	"Enter Google PSE Engine Id": "Google PSE 엔진 ID 입력",
+	"Enter Image Size (e.g. 512x512)": "이미지 크기 입력(예: 512x512)",
+	"Enter language codes": "언어 코드 입력",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "모델 태그 입력(예: {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "단계 수 입력(예: 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "점수 입력",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Searxng 쿼리 URL 입력",
+	"Enter Serper API Key": "Serper API 키 입력",
+	"Enter Serply API Key": "Serply API 키 입력",
+	"Enter Serpstack API Key": "Serpstack API 키 입력",
+	"Enter stop sequence": "중지 시퀀스 입력",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "Tavily API 키 입력",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Top K 입력",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "URL 입력(예: http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "URL 입력(예: http://localhost:11434)",
+	"Enter Your Email": "이메일 입력",
+	"Enter Your Full Name": "이름 입력",
+	"Enter your message": "",
+	"Enter Your Password": "비밀번호 입력",
+	"Enter Your Role": "역할 입력",
+	"Error": "오류",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "실험적",
+	"Export": "내보내기",
+	"Export All Chats (All Users)": "모든 채팅 내보내기(모든 사용자)",
+	"Export chat (.json)": "채팅 내보내기(.json)",
+	"Export Chats": "채팅 내보내기",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "모델 내보내기",
+	"Export Prompts": "프롬프트 내보내기",
+	"Export Tools": "도구 내보내기",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "API 키 생성에 실패했습니다.",
+	"Failed to read clipboard contents": "클립보드 내용을 읽는 데 실패하였습니다.",
+	"Failed to update settings": "설정 업데이트에 실패하였습니다.",
+	"Failed to upload file.": "",
+	"February": "2월",
+	"Feedback History": "",
+	"Feel free to add specific details": "자세한 내용을 자유롭게 추가하세요.",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "파일 모드",
+	"File not found.": "파일을 찾을 수 없습니다.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingerprint spoofing 감지: 이니셜을 아바타로 사용할 수 없습니다. 기본 프로필 이미지로 설정합니다.",
+	"Fluidly stream large external response chunks": "대규모 외부 응답 청크를 유연하게 스트리밍",
+	"Focus chat input": "채팅 입력창에 포커스",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "명령을 완벽히 따름",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "프리퀀시 페널티",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "일반",
+	"General Settings": "일반 설정",
+	"Generate Image": "이미지 생성",
+	"Generating search query": "검색 쿼리 생성",
+	"Generation Info": "생성 정보",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "좋은 응답",
+	"Google PSE API Key": "Google PSE API 키",
+	"Google PSE Engine Id": "Google PSE 엔진 ID",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "대화가 없습니다.",
+	"Hello, {{name}}": "안녕하세요, {{name}}",
+	"Help": "도움말",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "숨기기",
+	"Hide Model": "",
+	"How can I help you today?": "오늘 어떻게 도와드릴까요?",
+	"Hybrid Search": "하이브리드 검색",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "이미지 생성(실험적)",
+	"Image Generation Engine": "이미지 생성 엔진",
+	"Image Settings": "이미지 설정",
+	"Images": "이미지",
+	"Import Chats": "채팅 가져오기",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "모델 가져오기",
+	"Import Prompts": "프롬프트 가져오기",
+	"Import Tools": "도구 가져오기",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui를 실행 시 `--api` 플래그 포함 필요",
+	"Info": "정보",
+	"Input commands": "입력 명령",
+	"Install from Github URL": "Github URL에서 설치",
+	"Instant Auto-Send After Voice Transcription": "음성 변환 후 즉시 자동 전송",
+	"Interface": "인터페이스",
+	"Invalid file format.": "",
+	"Invalid Tag": "잘못된 태그",
+	"January": "1월",
+	"join our Discord for help.": "도움말을 보려면 Discord에 가입하세요.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON 미리 보기",
+	"July": "7월",
+	"June": "6월",
+	"JWT Expiration": "JWT 만료",
+	"JWT Token": "JWT 토큰",
+	"Keep Alive": "계속 유지하기",
+	"Keyboard shortcuts": "키보드 단축키",
+	"Knowledge": "지식 기반",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "언어",
+	"large language models, locally.": "",
+	"Last Active": "최근 활동",
+	"Last Modified": "마지막 수정",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Light",
+	"Listening...": "듣는 중...",
+	"LLMs can make mistakes. Verify important information.": "LLM은 실수를 할 수 있습니다. 중요한 정보는 확인이 필요합니다.",
+	"Local Models": "로컬 모델",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "OpenWebUI 커뮤니티에 의해 개발됨",
+	"Make sure to enclose them with": "꼭 다음으로 감싸세요:",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "관리",
+	"Manage Arena Models": "",
+	"Manage Models": "모델 관리",
+	"Manage Ollama Models": "Ollama 모델 관리",
+	"Manage Pipelines": "파이프라인 관리",
+	"March": "3월",
+	"Max Tokens (num_predict)": "최대 토큰(num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "최대 3개의 모델을 동시에 다운로드할 수 있습니다. 나중에 다시 시도하세요.",
+	"May": "5월",
+	"Memories accessible by LLMs will be shown here.": "LLM에서 액세스할 수 있는 메모리는 여기에 표시됩니다.",
+	"Memory": "메모리",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "링크 생성 후에 보낸 메시지는 공유되지 않습니다. URL이 있는 사용자는 공유된 채팅을 볼 수 있습니다.",
+	"Min P": "",
+	"Minimum Score": "최소 점수",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' 모델이 성공적으로 다운로드되었습니다.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' 모델은 이미 다운로드 대기열에 있습니다.",
+	"Model {{modelId}} not found": "{{modelId}} 모델을 찾을 수 없습니다.",
+	"Model {{modelName}} is not vision capable": "{{modelName}} 모델은 비전을 사용할 수 없습니다.",
+	"Model {{name}} is now {{status}}": "{{name}} 모델은 이제 {{status}} 상태입니다.",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "모델 파일 시스템 경로가 감지되었습니다. 업데이트하려면 모델 단축 이름이 필요하며 계속할 수 없습니다.",
+	"Model ID": "모델 ID",
+	"Model Name": "",
+	"Model not selected": "모델이 선택되지 않았습니다.",
+	"Model Params": "모델 파라미터",
+	"Model updated successfully": "",
+	"Model Whitelisting": "허용 모델 명시",
+	"Model(s) Whitelisted": "허용 모델",
+	"Modelfile Content": "Modelfile 내용",
+	"Models": "모델",
+	"more": "",
+	"More": "더보기",
+	"Move to Top": "",
+	"Name": "이름",
+	"Name your model": "모델 이름 지정",
+	"New Chat": "새 채팅",
+	"New folder": "",
+	"New Password": "새 비밀번호",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "결과 없음",
+	"No search query generated": "검색어가 생성되지 않았습니다.",
+	"No source available": "사용 가능한 소스 없음",
+	"No valves to update": "",
+	"None": "없음",
+	"Not factually correct": "사실상 맞지 않음",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "참고: 최소 점수를 설정하면, 검색 결과로 최소 점수 이상의 점수를 가진 문서만 반환합니다.",
+	"Notes": "",
+	"Notifications": "알림",
+	"November": "11월",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (올라마)",
+	"OAuth ID": "",
+	"October": "10월",
+	"Off": "끄기",
+	"Okay, Let's Go!": "좋아요, 시작합시다!",
+	"OLED Dark": "OLED Dark",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API 비활성화",
+	"Ollama API is disabled": "Ollama API 비활성화",
+	"Ollama Version": "Ollama 버전",
+	"On": "켜기",
+	"Only": "오직",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "명령어 문자열에는 영문자, 숫자 및 하이픈만 허용됩니다.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "이런! URL이 잘못된 것 같습니다. 다시 한번 확인하고 다시 시도해주세요.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "이런! 지원되지 않는 방식(프론트엔드만)을 사용하고 계십니다. 백엔드에서 WebUI를 제공해주세요.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "새 채팅 열기",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API 설정",
+	"OpenAI API Key is required.": "OpenAI API 키가 필요합니다.",
+	"OpenAI URL/Key required.": "OpenAI URL/키가 필요합니다.",
+	"or": "또는",
+	"Other": "기타",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "비밀번호",
+	"PDF document (.pdf)": "PDF 문서(.pdf)",
+	"PDF Extract Images (OCR)": "PDF 이미지 추출(OCR)",
+	"pending": "보류 중",
+	"Permission denied when accessing media devices": "미디어 장치 액세스가 거부되었습니다.",
+	"Permission denied when accessing microphone": "마이크 액세스가 거부되었습니다.",
+	"Permission denied when accessing microphone: {{error}}": "마이크 액세스가 거부되었습니다: {{error}}",
+	"Personalization": "개인화",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "파이프라인",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "파이프라인 밸브",
+	"Plain text (.txt)": "일반 텍스트(.txt)",
+	"Playground": "놀이터",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "긍정적인 자세",
+	"Previous 30 days": "이전 30일",
+	"Previous 7 days": "이전 7일",
+	"Profile Image": "프로필 이미지",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "프롬프트 (예: 로마 황제에 대해 재미있는 사실을 알려주세요)",
+	"Prompt Content": "프롬프트 내용",
+	"Prompt suggestions": "프롬프트 제안",
+	"Prompts": "프롬프트",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com에서 \"{{searchValue}}\" 가져오기",
+	"Pull a model from Ollama.com": "Ollama.com에서 모델 가져오기(pull)",
+	"Query Params": "쿼리 파라미터",
+	"RAG Template": "RAG 템플릿",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "읽어주기",
+	"Record voice": "음성 녹음",
+	"Redirecting you to OpenWebUI Community": "OpenWebUI 커뮤니티로 리디렉션 중",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "스스로를 \"User\" 라고 지칭하세요. (예: \"User is learning Spanish\")",
+	"References from": "",
+	"Refused when it shouldn't have": "허용되지 않았지만 허용되어야 합니다.",
+	"Regenerate": "재생성",
+	"Release Notes": "릴리스 노트",
+	"Relevance": "",
+	"Remove": "삭제",
+	"Remove Model": "모델 삭제",
+	"Rename": "이름 변경",
+	"Repeat Last N": "마지막 N 반복",
+	"Request Mode": "요청 모드",
+	"Reranking Model": "Reranking 모델",
+	"Reranking model disabled": "Reranking 모델 비활성화",
+	"Reranking model set to \"{{reranking_model}}\"": "Reranking 모델을 \"{{reranking_model}}\"로 설정",
+	"Reset": "초기화",
+	"Reset Upload Directory": "업로드 디렉토리 초기화",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "응답을 클립보드에 자동 복사",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "역할",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "실행 중",
+	"Save": "저장",
+	"Save & Create": "저장 및 생성",
+	"Save & Update": "저장 및 업데이트",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "브라우저의 저장소에 채팅 로그를 직접 저장하는 것은 더 이상 지원되지 않습니다. 아래 버튼을 클릭하여 채팅 로그를 다운로드하고 삭제하세요. 걱정 마세요. 백엔드를 통해 채팅 로그를 쉽게 다시 가져올 수 있습니다.",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "검색",
+	"Search a model": "모델 검색",
+	"Search Chats": "채팅 검색",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "모델 검색",
+	"Search Prompts": "프롬프트 검색",
+	"Search Query Generation Prompt": "검색 쿼리 생성 프롬프트",
+	"Search Result Count": "검색 결과 수",
+	"Search Tools": "검색 도구",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "sites_one {{count}} 검색됨",
+	"Searched {{count}} sites_other": "sites_other {{count}} 검색됨",
+	"Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" 검색 중",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng 쿼리 URL",
+	"See readme.md for instructions": "설명은 readme.md를 참조하세요.",
+	"See what's new": "새로운 기능 보기",
+	"Seed": "시드",
+	"Select a base model": "기본 모델 선택",
+	"Select a engine": "엔진 선택",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "모델 선택",
+	"Select a pipeline": "파이프라인 선택",
+	"Select a pipeline url": "파이프라인 URL 선택",
+	"Select a tool": "",
+	"Select an Ollama instance": "Ollama 인스턴스 선택",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "모델 선택",
+	"Select only one model to call": "콜을 위해서는 모델을 하나만 선택해야 합니다.",
+	"Selected model(s) do not support image inputs": "선택한 모델은 이미지 입력을 지원하지 않습니다.",
+	"Semantic distance to query": "",
+	"Send": "보내기",
+	"Send a Message": "메시지 보내기",
+	"Send message": "메시지 보내기",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "9월",
+	"Serper API Key": "Serper API 키",
+	"Serply API Key": "Serply API 키",
+	"Serpstack API Key": "Serpstack API 키",
+	"Server connection verified": "서버 연결 확인됨",
+	"Set as default": "기본값으로 설정",
+	"Set CFG Scale": "",
+	"Set Default Model": "기본 모델 설정",
+	"Set embedding model (e.g. {{model}})": "임베딩 모델 설정 (예: {{model}})",
+	"Set Image Size": "이미지 크기 설정",
+	"Set reranking model (e.g. {{model}})": "reranking 모델 설정 (예: {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "단계 설정",
+	"Set Task Model": "작업 모델 설정",
+	"Set Voice": "음성 설정",
+	"Set whisper model": "",
+	"Settings": "설정",
+	"Settings saved successfully!": "설정이 성공적으로 저장되었습니다!",
+	"Share": "공유",
+	"Share Chat": "채팅 공유",
+	"Share to OpenWebUI Community": "OpenWebUI 커뮤니티에 공유",
+	"short-summary": "간단한 요약",
+	"Show": "보이기",
+	"Show Admin Details in Account Pending Overlay": "사용자용 계정 보류 설명창에, 관리자 상세 정보 노출",
+	"Show Model": "",
+	"Show shortcuts": "단축키 보기",
+	"Show your support!": "",
+	"Showcased creativity": "창의성 발휘",
+	"Sign in": "로그인",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "로그아웃",
+	"Sign up": "가입",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "출처",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "음성 인식 오류: {{error}}",
+	"Speech-to-Text Engine": "음성-텍스트 변환 엔진",
+	"Stop": "",
+	"Stop Sequence": "중지 시퀀스",
+	"Stream Chat Response": "",
+	"STT Model": "STT 모델",
+	"STT Settings": "STT 설정",
+	"Subtitle (e.g. about the Roman Empire)": "자막 (예: 로마 황제)",
+	"Success": "성공",
+	"Successfully updated.": "성공적으로 업데이트되었습니다.",
+	"Suggested": "제안",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "시스템",
+	"System Instructions": "",
+	"System Prompt": "시스템 프롬프트",
+	"Tags": "태그",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "탭하여 중단",
+	"Tavily API Key": "Tavily API 키",
+	"Tell us more:": "더 알려주세요:",
+	"Temperature": "온도",
+	"Template": "템플릿",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "텍스트-음성 변환 엔진",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "피드백 감사합니다!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "점수는 0.0(0%)에서 1.0(100%) 사이의 값이어야 합니다.",
+	"Theme": "테마",
+	"Thinking...": "생각 중...",
+	"This action cannot be undone. Do you wish to continue?": "이 액션은 되돌릴 수 없습니다. 계속하시겠습니까?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "이렇게 하면 소중한 대화 내용이 백엔드 데이터베이스에 안전하게 저장됩니다. 감사합니다!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "이것은 실험적 기능으로, 예상대로 작동하지 않을 수 있으며 언제든지 변경될 수 있습니다.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "이것은 다음을 삭제합니다.",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "완전한 설명",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "팁: 각 대체 후 채팅 입력에서 탭 키를 눌러 여러 개의 변수 슬롯을 연속적으로 업데이트하세요.",
+	"Title": "제목",
+	"Title (e.g. Tell me a fun fact)": "제목 (예: 재미있는 사실을 알려주세요)",
+	"Title Auto-Generation": "제목 자동 생성",
+	"Title cannot be an empty string.": "제목은 빈 문자열일 수 없습니다.",
+	"Title Generation Prompt": "제목 생성 프롬프트",
+	"To access the available model names for downloading,": "다운로드 가능한 모델명을 확인하려면,",
+	"To access the GGUF models available for downloading,": "다운로드 가능한 GGUF 모델을 확인하려면,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "WebUI에 접속하려면 관리자에게 문의하십시오. 관리자는 관리자 패널에서 사용자 상태를 관리할 수 있습니다.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "채팅 입력으로.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "여기서 도구를 선택하려면, \"도구\" 워크스페이스에 먼저 추가하세요.",
+	"Toast notifications for new updates": "",
+	"Today": "오늘",
+	"Toggle settings": "설정 전환",
+	"Toggle sidebar": "사이드바 전환",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "컨텍스트 새로 고침 시 유지할 토큰 수(num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "도구",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Ollama에 접근하는 데 문제가 있나요?",
+	"TTS Model": "TTS 모델",
+	"TTS Settings": "TTS 설정",
+	"TTS Voice": "TTS 음성",
+	"Type": "입력",
+	"Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (다운로드) URL 입력",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "앗! {{provider}}에 연결하는 데 문제가 있었습니다.",
+	"UI": "UI",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "업데이트",
+	"Update and Copy Link": "링크 업데이트 및 복사",
+	"Update for the latest features and improvements.": "",
+	"Update password": "비밀번호 업데이트",
+	"Updated": "",
+	"Updated at": "다음에 업데이트됨",
+	"Updated At": "",
+	"Upload": "업로드",
+	"Upload a GGUF model": "GGUF 모델 업로드",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "파일 업로드",
+	"Upload Pipeline": "업로드 파이프라인",
+	"Upload Progress": "업로드 진행 상황",
+	"URL Mode": "URL 모드",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Gravatar 사용",
+	"Use Initials": "초성 사용",
+	"use_mlock (Ollama)": "use_mlock (올라마)",
+	"use_mmap (Ollama)": "use_mmap (올라마)",
+	"user": "사용자",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "사용자 권한",
+	"Users": "사용자",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "활용",
+	"Valid time units:": "유효 시간 단위:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "변수",
+	"variable to have them replaced with clipboard content.": "변수를 사용하여 클립보드 내용으로 바꾸세요.",
+	"Version": "버전",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "음성",
+	"Voice Input": "",
+	"Warning": "경고",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "주의: 기존 임베딩 모델을 변경 또는 업데이트하는 경우, 모든 문서를 다시 가져와야 합니다.",
+	"Web": "웹",
+	"Web API": "웹 API",
+	"Web Loader Settings": "웹 로더 설정",
+	"Web Search": "웹 검색",
+	"Web Search Engine": "웹 검색 엔진",
+	"Webhook URL": "웹훅 URL",
+	"WebUI Settings": "WebUI 설정",
+	"WebUI will make requests to": "WebUI 요청 대상:",
+	"What’s New in": "새로운 기능:",
+	"Whisper (Local)": "Whisper (로컬)",
+	"Widescreen Mode": "와이드스크린 모드",
+	"Won": "",
+	"Workspace": "워크스페이스",
+	"Write a prompt suggestion (e.g. Who are you?)": "프롬프트 제안 작성 (예: 당신은 누구인가요?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "[주제 또는 키워드]에 대한 50단어 요약문 작성.",
+	"Write something...": "",
+	"Yesterday": "어제",
+	"You": "당신",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "아래 '관리' 버튼으로 메모리를 추가하여 LLM들과의 상호작용을 개인화할 수 있습니다. 이를 통해 더 유용하고 맞춤화된 경험을 제공합니다.",
+	"You cannot clone a base model": "기본 모델은 복제할 수 없습니다",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "채팅을 아카이브한 적이 없습니다.",
+	"You have shared this chat": "이 채팅을 공유했습니다.",
+	"You're a helpful assistant.": "당신은 유용한 어시스턴트입니다.",
+	"You're now logged in.": "로그인되었습니다.",
+	"Your account status is currently pending activation.": "현재 계정은 아직 활성화되지 않았습니다.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "유튜브",
+	"Youtube Loader Settings": "유튜브 로더 설정"
+}
diff --git a/src/lib/i18n/locales/languages.json b/src/lib/i18n/locales/languages.json
new file mode 100644
index 0000000000000000000000000000000000000000..d560e33cbfaf82e0a9ff5d83326d4f0658f23826
--- /dev/null
+++ b/src/lib/i18n/locales/languages.json
@@ -0,0 +1,178 @@
+[
+	{
+		"code": "en-US",
+		"title": "English (US)"
+	},
+	{
+		"code": "en-GB",
+		"title": "English (GB)"
+	},
+	{
+		"code": "ar-BH",
+		"title": "Arabic (عربي)"
+	},
+	{
+		"code": "bn-BD",
+		"title": "Bengali (বাংলা)"
+	},
+	{
+		"code": "bg-BG",
+		"title": "Bulgarian (български)"
+	},
+	{
+		"code": "ca-ES",
+		"title": "Catalan (català)"
+	},
+	{
+		"code": "ceb-PH",
+		"title": "Cebuano (Filipino)"
+	},
+	{
+		"code": "da-DK",
+		"title": "Danish (Denmark)"
+	},
+	{
+		"code": "de-DE",
+		"title": "German (Deutsch)"
+	},
+	{
+		"code": "es-ES",
+		"title": "Spanish (Español)"
+	},
+	{
+		"code": "fa-IR",
+		"title": "Persian (فارسی)"
+	},
+	{
+		"code": "fi-FI",
+		"title": "Finnish (Suomalainen)"
+	},
+	{
+		"code": "fr-CA",
+		"title": "French (Canada)"
+	},
+	{
+		"code": "fr-FR",
+		"title": "French (France)"
+	},
+	{
+		"code": "he-IL",
+		"title": "Hebrew (עברית)"
+	},
+	{
+		"code": "hi-IN",
+		"title": "Hindi (हिंदी)"
+	},
+	{
+		"code": "hr-HR",
+		"title": "Croatian (Hrvatski)"
+	},
+	{
+		"code": "hu-HU",
+		"title": "Hungarian (Magyar)"
+	},
+	{
+		"code": "id-ID",
+		"title": "Indonesian (Bahasa Indonesia)"
+	},
+	{
+		"code": "ie-GA",
+		"title": "Irish (Gaeilge)"
+	},
+	{
+		"code": "it-IT",
+		"title": "Italian (Italiano)"
+	},
+	{
+		"code": "ja-JP",
+		"title": "Japanese (日本語)"
+	},
+	{
+		"code": "ka-GE",
+		"title": "Georgian (ქართული)"
+	},
+	{
+		"code": "ko-KR",
+		"title": "Korean (한국어)"
+	},
+	{
+		"code": "lt-LT",
+		"title": "Lithuanian (Lietuvių)"
+	},
+	{
+		"code": "ms-MY",
+		"title": "Malay (Bahasa Malaysia)"
+	},
+	{
+		"code": "nb-NO",
+		"title": "Norwegian Bokmål (Norway)"
+	},
+	{
+		"code": "nl-NL",
+		"title": "Dutch (Netherlands)"
+	},
+	{
+		"code": "pa-IN",
+		"title": "Punjabi (India)"
+	},
+	{
+		"code": "pl-PL",
+		"title": "Polish (Polski)"
+	},
+	{
+		"code": "pt-BR",
+		"title": "Portuguese (Brazil)"
+	},
+	{
+		"code": "pt-PT",
+		"title": "Portuguese (Portugal)"
+	},
+	{
+		"code": "ro-RO",
+		"title": "Romanian (Romania)"
+	},
+	{
+		"code": "ru-RU",
+		"title": "Russian (Russia)"
+	},
+	{
+		"code": "sv-SE",
+		"title": "Swedish (Svenska)"
+	},
+	{
+		"code": "sr-RS",
+		"title": "Serbian (Српски)"
+	},
+	{
+		"code": "th-TH",
+		"title": "Thailand (ไทย)"
+	},
+	{
+		"code": "tr-TR",
+		"title": "Turkish (Türkçe)"
+	},
+	{
+		"code": "tk-TW",
+		"title": "Turkmen (Türkmençe)"
+	},
+	{
+		"code": "uk-UA",
+		"title": "Ukrainian (Українська)"
+	},
+	{
+		"code": "vi-VN",
+		"title": "Vietnamese (Tiếng Việt)"
+	},
+	{
+		"code": "zh-CN",
+		"title": "Chinese (简体中文)"
+	},
+	{
+		"code": "zh-TW",
+		"title": "Chinese (繁體中文)"
+	},
+	{
+		"code": "dg-DG",
+		"title": "Doge (🐶)"
+	}
+]
diff --git a/src/lib/i18n/locales/lt-LT/translation.json b/src/lib/i18n/locales/lt-LT/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..9941e710916b0ff28719914777da7bdd4937c189
--- /dev/null
+++ b/src/lib/i18n/locales/lt-LT/translation.json
@@ -0,0 +1,853 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' arba '-1' kad neišteitų iš galiojimo.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(pvz. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(pvz. `sh webui.sh --api`)",
+	"(latest)": "(naujausias)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Negalite ištrinti bazinio modelio",
+	"{{user}}'s Chats": "{{user}} susirašinėjimai",
+	"{{webUIName}} Backend Required": "{{webUIName}} būtinas serveris",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Užduočių modelis naudojamas pokalbių pavadinimų ir paieškos užklausų generavimui.",
+	"a user": "naudotojas",
+	"About": "Apie",
+	"Account": "Paskyra",
+	"Account Activation Pending": "Laukiama paskyros patvirtinimo",
+	"Accurate information": "Tiksli informacija",
+	"Actions": "Veiksmai",
+	"Active Users": "Aktyvūs naudotojai",
+	"Add": "Pridėti",
+	"Add a model id": "Pridėti modelio ID",
+	"Add a short description about what this model does": "Pridėti trumpą modelio aprašymą",
+	"Add a short title for this prompt": "Pridėti trumpą šios užklausos pavadinimą",
+	"Add a tag": "Pridėti žymą",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Pridėti užklausos šabloną",
+	"Add Files": "Pridėti failus",
+	"Add Memory": "Pridėti atminį",
+	"Add Model": "Pridėti modelį",
+	"Add Tag": "Pridėti žymą",
+	"Add Tags": "Pridėti žymas",
+	"Add text content": "",
+	"Add User": "Pridėti naudotoją",
+	"Adjusting these settings will apply changes universally to all users.": "Šių nustatymų pakeitimas bus pritakytas visiems naudotojams.",
+	"admin": "Administratorius",
+	"Admin": "Administratorius",
+	"Admin Panel": "Administratorių panelė",
+	"Admin Settings": "Administratorių nustatymai",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Administratoriai visada turi visus įrankius. Naudotojai turi tuėti prieigą prie dokumentų per modelių nuostatas",
+	"Advanced Parameters": "Pažengę nustatymai",
+	"Advanced Params": "Pažengę nustatymai",
+	"All chats": "",
+	"All Documents": "Visi dokumentai",
+	"Allow Chat Deletion": "Leisti pokalbių ištrynimą",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Leisti nelokalius balsus",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "Leisti naudotojo vietos matymą",
+	"Allow Voice Interruption in Call": "Leisti pertraukimą skambučio metu",
+	"alphanumeric characters and hyphens": "skaičiai, raidės ir brūkšneliai",
+	"Already have an account?": "Ar jau turite paskyrą?",
+	"an assistant": "assistentas",
+	"and": "ir",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "sukurti naują dalinimosi nuorodą",
+	"API Base URL": "API basės nuoroda",
+	"API Key": "API raktas",
+	"API Key created.": "API raktas sukurtas",
+	"API keys": "API raktai",
+	"April": "Balandis",
+	"Archive": "Archyvai",
+	"Archive All Chats": "Archyvuoti visus pokalbius",
+	"Archived Chats": "Archyvuoti pokalbiai",
+	"are allowed - Activate this command by typing": "leistina - aktyvuokite komandą rašydami",
+	"Are you sure?": "Are esate tikri?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Pridėti failą",
+	"Attention to detail": "Dėmesys detalėms",
+	"Audio": "Audio įrašas",
+	"August": "Rugpjūtis",
+	"Auto-playback response": "Automatinis atsakymo skaitymas",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Api Auth String",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 bazės nuoroda",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 bazės nuoroda reikalinga.",
+	"Available list": "",
+	"available!": "prieinama!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Atgal",
+	"Bad Response": "Neteisingas atsakymas",
+	"Banners": "Baneriai",
+	"Base Model (From)": "Bazinis modelis",
+	"Batch Size (num_batch)": "Batch dydis",
+	"before": "prieš",
+	"Being lazy": "Būvimas tingiu",
+	"Brave Search API Key": "Brave Search API raktas",
+	"Bypass SSL verification for Websites": "Išvengti SSL patikros puslapiams",
+	"Call": "Skambinti",
+	"Call feature is not supported when using Web STT engine": "Skambučio funkcionalumas neleidžiamas naudojant Web STT variklį",
+	"Camera": "Kamera",
+	"Cancel": "Atšaukti",
+	"Capabilities": "Gebėjimai",
+	"Change Password": "Keisti slaptažodį",
+	"Character": "",
+	"Chat": "Pokalbis",
+	"Chat Background Image": "Pokalbio galinė užsklanda",
+	"Chat Bubble UI": "Pokalbio burbulo sąsaja",
+	"Chat Controls": "Pokalbio valdymas",
+	"Chat direction": "Pokalbio linkmė",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Pokalbiai",
+	"Check Again": "Patikrinti iš naujo",
+	"Check for updates": "Patikrinti atnaujinimus",
+	"Checking for updates...": "Ieškoma atnaujinimų...",
+	"Choose a model before saving...": "Pasirinkite modelį prieš išsaugant...",
+	"Chunk Overlap": "Blokų persidengimas",
+	"Chunk Params": "Blokų nustatymai",
+	"Chunk Size": "Blokų dydis",
+	"Citation": "Citata",
+	"Clear memory": "Ištrinti atmintį",
+	"Click here for help.": "Paspauskite čia dėl pagalbos.",
+	"Click here to": "Paspauskite čia, kad:",
+	"Click here to download user import template file.": "Pasauskite čia norėdami sukurti naudotojo įkėlimo šablono rinkmeną",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Spauskite čia norėdami pasirinkti",
+	"Click here to select a csv file.": "Spauskite čia tam, kad pasirinkti csv failą",
+	"Click here to select a py file.": "Spauskite čia norėdami pasirinkti py failą",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "paspauskite čia.",
+	"Click on the user role button to change a user's role.": "Paspauskite ant naudotojo rolės mygtuko tam, kad pakeisti naudotojo rolę.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Iškarpinės naudojimas neleidžiamas naršyklės.",
+	"Clone": "Klonuoti",
+	"Close": "Uždaryti",
+	"Code execution": "",
+	"Code formatted successfully": "Kodas suformatuotas sėkmingai",
+	"Collection": "Kolekcija",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI bazės nuoroda",
+	"ComfyUI Base URL is required.": "ComfyUI bazės nuoroda privaloma",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Command",
+	"Completions": "",
+	"Concurrent Requests": "Kelios užklausos vienu metu",
+	"Confirm": "Patvrtinti",
+	"Confirm Password": "Patvirtinkite slaptažodį",
+	"Confirm your action": "Patvirtinkite veiksmą",
+	"Connections": "Ryšiai",
+	"Contact Admin for WebUI Access": "Susisiekite su administratoriumi dėl prieigos",
+	"Content": "Turinys",
+	"Content Extraction": "Turinio ištraukimas",
+	"Context Length": "Konteksto ilgis",
+	"Continue Response": "Tęsti atsakymą",
+	"Continue with {{provider}}": "Tęstti su {{tiekėju}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "Valdymas",
+	"Copied": "Nukopijuota",
+	"Copied shared chat URL to clipboard!": "Nukopijavote pokalbio nuorodą",
+	"Copied to clipboard": "",
+	"Copy": "Kopijuoti",
+	"Copy last code block": "Kopijuoti paskutinį kodo bloką",
+	"Copy last response": "Kopijuoti paskutinį atsakymą",
+	"Copy Link": "Kopijuoti nuorodą",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "La copie dans le presse-papiers a réussi !",
+	"Create a model": "Sukurti modelį",
+	"Create Account": "Créer un compte",
+	"Create Knowledge": "",
+	"Create new key": "Sukurti naują raktą",
+	"Create new secret key": "Sukurti naują slaptą raktą",
+	"Created at": "Sukurta",
+	"Created At": "Sukurta",
+	"Created by": "Sukurta",
+	"CSV Import": "CSV importavimas",
+	"Current Model": "Dabartinis modelis",
+	"Current Password": "Esamas slaptažodis",
+	"Custom": "Personalizuota",
+	"Customize models for a specific purpose": "Pritaikykite modelius specifiniams tikslams",
+	"Dark": "Tamsus",
+	"Dashboard": "Valdymo panelė",
+	"Database": "Duomenų bazė",
+	"December": "Gruodis",
+	"Default": "Numatytasis",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Numatytasis (SentenceTransformers)",
+	"Default Model": "Numatytasis modelis",
+	"Default model updated": "Numatytasis modelis atnaujintas",
+	"Default Prompt Suggestions": "Numatytieji užklausų pasiūlymai",
+	"Default User Role": "Numatytoji naudotojo rolė",
+	"Delete": "ištrinti",
+	"Delete a model": "Ištrinti modėlį",
+	"Delete All Chats": "Ištrinti visus pokalbius",
+	"Delete chat": "Išrinti pokalbį",
+	"Delete Chat": "Ištrinti pokalbį",
+	"Delete chat?": "Ištrinti pokalbį?",
+	"Delete folder?": "",
+	"Delete function?": "Ištrinti funkciją",
+	"Delete prompt?": "Ištrinti užklausą?",
+	"delete this link": "Ištrinti nuorodą",
+	"Delete tool?": "Ištrinti įrankį?",
+	"Delete User": "Ištrinti naudotoją",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} ištrinta",
+	"Deleted {{name}}": "Ištrinta {{name}}",
+	"Description": "Aprašymas",
+	"Didn't fully follow instructions": "Pilnai nesekė instrukcijų",
+	"Disabled": "Išjungta",
+	"Discover a function": "Atrasti funkciją",
+	"Discover a model": "Atrasti modelį",
+	"Discover a prompt": "Atrasti užklausas",
+	"Discover a tool": "Atrasti įrankį",
+	"Discover, download, and explore custom functions": "Atrasti, atsisiųsti arba rasti naujas funkcijas",
+	"Discover, download, and explore custom prompts": "Atrasti ir parsisiųsti užklausas",
+	"Discover, download, and explore custom tools": "Atrasti, atsisiųsti arba rasti naujų įrankių",
+	"Discover, download, and explore model presets": "Atrasti ir parsisiųsti modelių konfigūracija",
+	"Dismissible": "Atemtama",
+	"Display Emoji in Call": "Rodyti emoji pokalbiuose",
+	"Display the username instead of You in the Chat": "Rodyti naudotojo vardą vietoje žodžio Jūs pokalbyje",
+	"Do not install functions from sources you do not fully trust.": "Neinstaliuokite funkcijų iš nepatikimų šaltinių",
+	"Do not install tools from sources you do not fully trust.": "Neinstaliuokite įrankių iš nepatikimų šaltinių",
+	"Document": "Dokumentas",
+	"Documentation": "Dokumentacija",
+	"Documents": "Dokumentai",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "neturi jokių išorinių ryšių ir duomenys lieka serveryje.",
+	"Don't have an account?": "Neturite paskyros?",
+	"don't install random functions from sources you don't trust.": "neinstaliuokite funkcijų iš nepatikimų šaltinių",
+	"don't install random tools from sources you don't trust.": "neinstaliuokite įrankių iš nepatikimų šaltinių",
+	"Don't like the style": "Nepatinka stilius",
+	"Done": "Atlikta",
+	"Download": "Parsisiųsti",
+	"Download canceled": "Parsisiuntimas atšauktas",
+	"Download Database": "Parsisiųsti duomenų bazę",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Įkelkite dokumentus čia, kad juos pridėti į pokalbį",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "pvz. '30s', '10m'. Laiko vienetai yra 's', 'm', 'h'.",
+	"Edit": "Redaguoti",
+	"Edit Arena Model": "",
+	"Edit Memory": "Koreguoti atminį",
+	"Edit User": "Redaguoti naudotoją",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "El. paštas",
+	"Embedding Batch Size": "Embedding dydis",
+	"Embedding Model": "Embedding modelis",
+	"Embedding Model Engine": "Embedding modelio variklis",
+	"Embedding model set to \"{{embedding_model}}\"": "Embedding modelis nustatytas kaip\"{{embedding_model}}\"",
+	"Enable Community Sharing": "Leisti dalinimąsi su bendruomene",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Aktyvuoti naujas registracijas",
+	"Enable Web Search": "Leisti paiešką internete",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "Leisti",
+	"Engine": "Variklis",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Įsitikinkite, kad CSV failas turi 4 kolonas šiuo eiliškumu: Name, Email, Password, Role.",
+	"Enter {{role}} message here": "Įveskite {{role}} žinutę čia",
+	"Enter a detail about yourself for your LLMs to recall": "Įveskite informaciją apie save jūsų modelio atminčiai",
+	"Enter api auth string (e.g. username:password)": "Įveskite API autentifikacijos kodą (pvz. username:password)",
+	"Enter Brave Search API Key": "Įveskite Bravo Search API raktą",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Įveskite blokų persidengimą",
+	"Enter Chunk Size": "Įveskite blokų dydį",
+	"Enter description": "",
+	"Enter Github Raw URL": "Įveskite GitHub Raw nuorodą",
+	"Enter Google PSE API Key": "Įveskite Google PSE API raktą",
+	"Enter Google PSE Engine Id": "Įveskite Google PSE variklio ID",
+	"Enter Image Size (e.g. 512x512)": "Įveskite paveiksliuko dydį (pvz. 512x512)",
+	"Enter language codes": "Įveskite kalbos kodus",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Įveskite modelio žymą (pvz. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Įveskite žingsnių kiekį (pvz. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Įveskite rezultatą",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Įveskite Searxng Query nuorodą",
+	"Enter Serper API Key": "Įveskite Serper API raktą",
+	"Enter Serply API Key": "Įveskite Serply API raktą",
+	"Enter Serpstack API Key": "Įveskite Serpstack API raktą",
+	"Enter stop sequence": "Įveskite pabaigos sekvenciją",
+	"Enter system prompt": "Įveskite sistemos užklausą",
+	"Enter Tavily API Key": "Įveskite Tavily API raktą",
+	"Enter Tika Server URL": "Įveskite Tika serverio nuorodą",
+	"Enter Top K": "Įveskite Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Įveskite nuorodą (pvz. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Įveskite nuorododą (pvz. http://localhost:11434",
+	"Enter Your Email": "Įveskite el. pašto adresą",
+	"Enter Your Full Name": "Įveskite vardą bei pavardę",
+	"Enter your message": "Įveskite žinutę",
+	"Enter Your Password": "Įveskite slaptažodį",
+	"Enter Your Role": "Įveskite savo rolę",
+	"Error": "Klaida",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Eksperimentinis",
+	"Export": "Eksportuoti",
+	"Export All Chats (All Users)": "Eksportuoti visų naudotojų visus pokalbius",
+	"Export chat (.json)": "Eksportuoti pokalbį (.json)",
+	"Export Chats": "Eksportuoti pokalbius",
+	"Export Config to JSON File": "",
+	"Export Functions": "Eksportuoti funkcijas",
+	"Export LiteLLM config.yaml": "Eksportuoti LiteLLM config.yaml",
+	"Export Models": "Eksportuoti modelius",
+	"Export Prompts": "Eksportuoti užklausas",
+	"Export Tools": "Eksportuoti įrankius",
+	"External Models": "Išoriniai modeliai",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Nepavyko sukurti API rakto",
+	"Failed to read clipboard contents": "Nepavyko perskaityti kopijuoklės",
+	"Failed to update settings": "Nepavyko atnaujinti nustatymų",
+	"Failed to upload file.": "",
+	"February": "Vasaris",
+	"Feedback History": "",
+	"Feel free to add specific details": "Galite pridėti specifinių detalių",
+	"File": "Rinkmena",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Rinkmenų rėžimas",
+	"File not found.": "Failas nerastas.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "Rinkmenos",
+	"Filter is now globally disabled": "Filtrai nėra leidžiami globaliai",
+	"Filter is now globally enabled": "Filtrai globaliai leidžiami",
+	"Filters": "Filtrai",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Nepavyko nsutatyti profilio nuotraukos",
+	"Fluidly stream large external response chunks": "Sklandžiai transliuoti ilgus atsakymus",
+	"Focus chat input": "Fokusuoti žinutės įvestį",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Tobulai sekė instrukcijas",
+	"Form": "Forma",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Dažnumo bauda",
+	"Function": "",
+	"Function created successfully": "Funkcija sukurta sėkmingai",
+	"Function deleted successfully": "Funkcija ištrinta sėkmingai",
+	"Function Description (e.g. A filter to remove profanity from text)": "Funkcijos aprašymas (pvz. filtras keiksmažodžių išėmimui)",
+	"Function ID (e.g. my_filter)": "Funkcijos ID",
+	"Function is now globally disabled": "Funkcijos šiuo metu neleidžiamos",
+	"Function is now globally enabled": "Funkcijos leidžiamos",
+	"Function Name (e.g. My Filter)": "Funkcijos pavadinimas",
+	"Function updated successfully": "Funkcija atnaujinta sėkmingai",
+	"Functions": "Funkcijos",
+	"Functions allow arbitrary code execution": "Funkcijos leidžia nekontroliuojamo kodo vykdymą",
+	"Functions allow arbitrary code execution.": "Funkcijos leidžia nekontroliuojamo kodo vykdymą",
+	"Functions imported successfully": "Funkcijos importuotos sėkmingai",
+	"General": "Bendri",
+	"General Settings": "Bendri nustatymai",
+	"Generate Image": "Generuoti paveikslėlį",
+	"Generating search query": "Generuoti paieškos užklausą",
+	"Generation Info": "Generavimo informacija",
+	"Get up and running with": "Pradėti dirbti su",
+	"Global": "Globalu",
+	"Good Response": "Geras atsakymas",
+	"Google PSE API Key": "Google PSE API raktas",
+	"Google PSE Engine Id": "Google PSE variklio ID",
+	"h:mm a": "valanda:mėnesis:metai",
+	"Haptic Feedback": "",
+	"has no conversations.": "neturi pokalbių",
+	"Hello, {{name}}": "Sveiki, {{name}}",
+	"Help": "Pagalba",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Paslėpti",
+	"Hide Model": "Paslėpti modelį",
+	"How can I help you today?": "Kuo galėčiau Jums padėti ?",
+	"Hybrid Search": "Hibridinė paieška",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Suprantu veiksmų ir kodo vykdymo rizikas.",
+	"ID": "",
+	"Image Generation (Experimental)": "Vaizdų generavimas (eksperimentinis)",
+	"Image Generation Engine": "Vaizdų generavimo variklis",
+	"Image Settings": "Vaizdų nustatymai",
+	"Images": "Vaizdai",
+	"Import Chats": "Importuoti pokalbius",
+	"Import Config from JSON File": "",
+	"Import Functions": "Importuoti funkcijas",
+	"Import Models": "Importuoti modelius",
+	"Import Prompts": "Importuoti užklausas",
+	"Import Tools": "Importuoti įrankius",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Įtraukti `--api-auth` flag when running stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Pridėti `--api` kai vykdomas stable-diffusion-webui",
+	"Info": "Informacija",
+	"Input commands": "Įvesties komandos",
+	"Install from Github URL": "Instaliuoti Github nuorodą",
+	"Instant Auto-Send After Voice Transcription": "Siųsti iškart po balso transkripcijos",
+	"Interface": "Sąsaja",
+	"Invalid file format.": "",
+	"Invalid Tag": "Neteisinga žyma",
+	"January": "Sausis",
+	"join our Discord for help.": "prisijunkite prie mūsų Discord.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON peržiūra",
+	"July": "liepa",
+	"June": "birželis",
+	"JWT Expiration": "JWT išėjimas iš galiojimo",
+	"JWT Token": "JWT žetonas",
+	"Keep Alive": "Išlaikyti aktyviu",
+	"Keyboard shortcuts": "Klaviatūros trumpiniai",
+	"Knowledge": "Žinios",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Kalba",
+	"large language models, locally.": "dideli kalbos modeliai, lokaliai",
+	"Last Active": "Paskutinį kartą aktyvus",
+	"Last Modified": "Paskutinis pakeitimas",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Šviesus",
+	"Listening...": "Klausoma...",
+	"LLMs can make mistakes. Verify important information.": "Dideli kalbos modeliai gali klysti. Patikrinkite atsakymų teisingumą.",
+	"Local Models": "Lokalūs modeliai",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Sukurta OpenWebUI bendruomenės",
+	"Make sure to enclose them with": "Užtikrinktie, kad įtraukiate viduje:",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Tvarkyti",
+	"Manage Arena Models": "",
+	"Manage Models": "Tvarkyti modelius",
+	"Manage Ollama Models": "Tvarkyti Ollama modelius",
+	"Manage Pipelines": "Tvarkyti procesus",
+	"March": "Kovas",
+	"Max Tokens (num_predict)": "Maksimalus žetonų kiekis (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Daugiausiai trys modeliai gali būti parsisiunčiami vienu metu.",
+	"May": "gegužė",
+	"Memories accessible by LLMs will be shown here.": "Atminitis prieinama kalbos modelio bus rodoma čia.",
+	"Memory": "Atmintis",
+	"Memory added successfully": "Atmintis pridėta sėkmingai",
+	"Memory cleared successfully": "Atmintis ištrinta sėkmingai",
+	"Memory deleted successfully": "Atmintis ištrinta sėkmingai",
+	"Memory updated successfully": "Atmintis atnaujinta sėkmingai",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Žinutės, kurias siunčiate po nuorodos sukūrimo nebus matomos nuorodos turėtojams. Naudotojai su nuoroda matys žinutes iki nuorodos sukūrimo.",
+	"Min P": "Mažiausias p",
+	"Minimum Score": "Minimalus rezultatas",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' modelis sėkmingai atsisiųstas.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Modelis '{{modelTag}}' jau atsisiuntimų eilėje.",
+	"Model {{modelId}} not found": "Modelis {{modelId}} nerastas",
+	"Model {{modelName}} is not vision capable": "Modelis {{modelName}} neturi vaizdo gebėjimų",
+	"Model {{name}} is now {{status}}": "Modelis {{name}} dabar {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "Modelis sukurtas sėkmingai",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modelio failų sistemos kelias aptiktas. Reikalingas trumpas modelio pavadinimas atnaujinimui.",
+	"Model ID": "Modelio ID",
+	"Model Name": "",
+	"Model not selected": "Modelis nepasirinktas",
+	"Model Params": "Modelio parametrai",
+	"Model updated successfully": "Modelis atnaujintas sėkmingai",
+	"Model Whitelisting": "Modeliu baltasis sąrašas",
+	"Model(s) Whitelisted": "Modelis baltąjame sąraše",
+	"Modelfile Content": "Modelio failo turinys",
+	"Models": "Modeliai",
+	"more": "",
+	"More": "Daugiau",
+	"Move to Top": "",
+	"Name": "Pavadinimas",
+	"Name your model": "Pavadinkite savo modelį",
+	"New Chat": "Naujas pokalbis",
+	"New folder": "",
+	"New Password": "Naujas slaptažodis",
+	"No content found": "",
+	"No content to speak": "Nėra turinio kalbėjimui",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Nėra pasirinktų dokumentų",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Rezultatų nerasta",
+	"No search query generated": "Paieškos užklausa nesugeneruota",
+	"No source available": "Šaltinių nerasta",
+	"No valves to update": "Nėra atnaujinamų įeičių",
+	"None": "Nėra",
+	"Not factually correct": "Faktiškai netikslu",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Jei turite minimalų įvertį, paieška gražins tik tą informaciją, kuri viršyje šį įvertį",
+	"Notes": "",
+	"Notifications": "Pranešimai",
+	"November": "lapkritis",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth ID",
+	"October": "spalis",
+	"Off": "Išjungta",
+	"Okay, Let's Go!": "Gerai, važiuojam!",
+	"OLED Dark": "OLED tamsus",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API išjungtas",
+	"Ollama API is disabled": "Ollama API išjungtas",
+	"Ollama Version": "Ollama versija",
+	"On": "Aktyvuota",
+	"Only": "Tiktais",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Leistinos tik raidės, skaičiai ir brūkšneliai.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Regis nuoroda nevalidi. Prašau patikrtinkite ir pabandykite iš naujo.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Naudojate nepalaikomą (front-end) web ui rėžimą. Prašau serviruokite WebUI iš back-end",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Atverti naują pokalbį",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Tortue Chat versija per sena. Reikalinga (v{{REQUIRED_VERSION}}) versija.",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "Open AI API nustatymai",
+	"OpenAI API Key is required.": "OpenAI API raktas būtinas.",
+	"OpenAI URL/Key required.": "OpenAI API nuoroda ir raktas būtini",
+	"or": "arba",
+	"Other": "Kita",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Slaptažodis",
+	"PDF document (.pdf)": "PDF dokumentas (.pdf)",
+	"PDF Extract Images (OCR)": "PDF paveikslėlių skaitymas (OCR)",
+	"pending": "laukiama",
+	"Permission denied when accessing media devices": "Leidimas atmestas bandant prisijungti prie medijos įrenginių",
+	"Permission denied when accessing microphone": "Mikrofono leidimas atmestas",
+	"Permission denied when accessing microphone: {{error}}": "Leidimas naudoti mikrofoną atmestas: {{error}}",
+	"Personalization": "Personalizacija",
+	"Pin": "Smeigtukas",
+	"Pinned": "Įsmeigta",
+	"Pipeline deleted successfully": "Procesas ištrintas sėkmingai",
+	"Pipeline downloaded successfully": "Procesas atsisiųstas sėkmingai",
+	"Pipelines": "Procesai",
+	"Pipelines Not Detected": "Procesai neaptikti",
+	"Pipelines Valves": "Procesų įeitys",
+	"Plain text (.txt)": "Grynas tekstas (.txt)",
+	"Playground": "Eksperimentavimo erdvė",
+	"Please carefully review the following warnings:": "Peržiūrėkite šiuos perspėjimus:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Pozityvus elgesys",
+	"Previous 30 days": "Paskutinės 30 dienų",
+	"Previous 7 days": "Paskutinės 7 dienos",
+	"Profile Image": "Profilio nuotrauka",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Užklausa (pvz. supaprastink šį laišką)",
+	"Prompt Content": "Užklausos turinys",
+	"Prompt suggestions": "Užklausų pavyzdžiai",
+	"Prompts": "Užklausos",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Rasti \"{{searchValue}}\" iš Ollama.com",
+	"Pull a model from Ollama.com": "Gauti modelį iš Ollama.com",
+	"Query Params": "Užklausos parametrai",
+	"RAG Template": "RAG šablonas",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Skaityti garsiai",
+	"Record voice": "Įrašyti balsą",
+	"Redirecting you to OpenWebUI Community": "Perkeliam Jus į OpenWebUI bendruomenę",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Vadinkite save Naudotoju (pvz. Naudotojas mokosi prancūzų kalbos)",
+	"References from": "",
+	"Refused when it shouldn't have": "Atmesta kai neturėtų būti atmesta",
+	"Regenerate": "Generuoti iš naujo",
+	"Release Notes": "Naujovės",
+	"Relevance": "",
+	"Remove": "Pašalinti",
+	"Remove Model": "Pašalinti modelį",
+	"Rename": "Pervadinti",
+	"Repeat Last N": "Pakartoti paskutinius N",
+	"Request Mode": "Užklausos rėžimas",
+	"Reranking Model": "Reranking modelis",
+	"Reranking model disabled": "Reranking modelis neleidžiamas",
+	"Reranking model set to \"{{reranking_model}}\"": "Nustatytas rereanking modelis: \"{{reranking_model}}\"",
+	"Reset": "Atkurti",
+	"Reset Upload Directory": "Atkurti įkėlimų direktoiją",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Automatiškai nukopijuoti atsakymą",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Naršyklė neleidžia siųsti pranešimų",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rolė",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Naudokite egzistuojančius modelius ir sukurkite saviškius.",
+	"Running": "Veikia",
+	"Save": "Išsaugoti",
+	"Save & Create": "Išsaugoti ir sukurti",
+	"Save & Update": "Išsaugoti ir atnaujinti",
+	"Save As Copy": "",
+	"Save Tag": "Išsaugoti žymą",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Pokalbių saugojimas naršyklėje nebegalimas.",
+	"Scroll to bottom when switching between branches": "Slikite link apačios norėdami pakeisti šakas",
+	"Search": "Ieškoti",
+	"Search a model": "Ieškoti modelio",
+	"Search Chats": "Ieškoti pokalbiuose",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Ieškoti funkcijų",
+	"Search Knowledge": "",
+	"Search Models": "Ieškoti modelių",
+	"Search Prompts": "Ieškoti užklausų",
+	"Search Query Generation Prompt": "Paieškos užklausos generavimo formuluotė",
+	"Search Result Count": "Paieškos rezultatų skaičius",
+	"Search Tools": "Paieškos įrankiai",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Ieškota {{count}} sites_one",
+	"Searched {{count}} sites_few": "Ieškota {{count}} sites_few",
+	"Searched {{count}} sites_many": "Ieškota {{count}} sites_many",
+	"Searched {{count}} sites_other": "Ieškota {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "Ieškoma \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng užklausos URL",
+	"See readme.md for instructions": "Žiūrėti readme.md papildomoms instrukcijoms",
+	"See what's new": "Žiūrėti naujoves",
+	"Seed": "Sėkla",
+	"Select a base model": "Pasirinkite bazinį modelį",
+	"Select a engine": "Pasirinkite variklį",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Pasirinkite funkciją",
+	"Select a model": "Pasirinkti modelį",
+	"Select a pipeline": "Pasirinkite procesą",
+	"Select a pipeline url": "Pasirinkite proceso nuorodą",
+	"Select a tool": "Pasirinkite įrankį",
+	"Select an Ollama instance": "Pasirinkti Ollama instanciją",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Pasirinkti modelį",
+	"Select only one model to call": "Pasirinkite vieną modelį",
+	"Selected model(s) do not support image inputs": "Pasirinkti modeliai nepalaiko vaizdinių užklausų",
+	"Semantic distance to query": "",
+	"Send": "Siųsti",
+	"Send a Message": "Siųsti žinutę",
+	"Send message": "Siųsti žinutę",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "rugsėjis",
+	"Serper API Key": "Serper API raktas",
+	"Serply API Key": "Serply API raktas",
+	"Serpstack API Key": "Serpstach API raktas",
+	"Server connection verified": "Serverio sujungimas patvirtintas",
+	"Set as default": "Nustatyti numatytąjį",
+	"Set CFG Scale": "",
+	"Set Default Model": "Nustatyti numatytąjį modelį",
+	"Set embedding model (e.g. {{model}})": "Nustatyti embedding modelį",
+	"Set Image Size": "Nustatyti paveikslėlių dydį",
+	"Set reranking model (e.g. {{model}})": "Nustatyti reranking modelį",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Numatyti etapus",
+	"Set Task Model": "Numatyti užduočių modelį",
+	"Set Voice": "Numatyti balsą",
+	"Set whisper model": "",
+	"Settings": "Nustatymai",
+	"Settings saved successfully!": "Parametrai sėkmingai išsaugoti!",
+	"Share": "Dalintis",
+	"Share Chat": "Dalintis pokalbiu",
+	"Share to OpenWebUI Community": "Dalintis su OpenWebUI bendruomene",
+	"short-summary": "trumpinys",
+	"Show": "Rodyti",
+	"Show Admin Details in Account Pending Overlay": "Rodyti administratoriaus duomenis laukiant paskyros patvirtinimo",
+	"Show Model": "Rodyti modelį",
+	"Show shortcuts": "Rodyti trumpinius",
+	"Show your support!": "Palaikykite",
+	"Showcased creativity": "Kūrybingų užklausų paroda",
+	"Sign in": "Prisijungti",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Atsijungti",
+	"Sign up": "Sukurti paskyrą",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Šaltinis",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Balso atpažinimo problema: {{error}}",
+	"Speech-to-Text Engine": "Balso atpažinimo modelis",
+	"Stop": "",
+	"Stop Sequence": "Baigt sekvenciją",
+	"Stream Chat Response": "",
+	"STT Model": "STT modelis",
+	"STT Settings": "STT nustatymai",
+	"Subtitle (e.g. about the Roman Empire)": "Subtitras",
+	"Success": "Sėkmingai",
+	"Successfully updated.": "Sėkmingai atnaujinta.",
+	"Suggested": "Siūloma",
+	"Support": "Palaikyti",
+	"Support this plugin:": "Palaikykite šitą modulį",
+	"Sync directory": "",
+	"System": "Sistema",
+	"System Instructions": "",
+	"System Prompt": "Sistemos užklausa",
+	"Tags": "Žymos",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Paspauskite norėdami pertraukti",
+	"Tavily API Key": "Tavily API raktas",
+	"Tell us more:": "Papasakokite daugiau",
+	"Temperature": "Temperatūra",
+	"Template": "Modelis",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Balso sintezės modelis",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Ačiū už atsiliepimus",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Šis modulis kuriamas savanorių. Palaikykite jų darbus finansiškai arba prisidėdami kodu.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Rezultatas turėtų būti tarp 0.0 (0%) ir 1.0 (100%)",
+	"Theme": "Tema",
+	"Thinking...": "Mąsto...",
+	"This action cannot be undone. Do you wish to continue?": "Šis veiksmas negali būti atšauktas. Ar norite tęsti?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Tai užtikrina, kad Jūsų pokalbiai saugiai saugojami duomenų bazėje. Ačiū!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Tai eksperimentinė funkcija ir gali veikti nevisada.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Tai ištrins",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Platus paaiškinimas",
+	"Tika": "Tika",
+	"Tika Server URL required.": "Reiklainga Tika serverio nuorodą",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Jei norite pakeisti keletą kintamųjų vieną po kitos, spauskite Tab",
+	"Title": "Pavadinimas",
+	"Title (e.g. Tell me a fun fact)": "Pavadinimas",
+	"Title Auto-Generation": "Automatinis pavadinimų generavimas",
+	"Title cannot be an empty string.": "Pavadinimas negali būti tuščias",
+	"Title Generation Prompt": "Pavadinimo generavimo užklausa",
+	"To access the available model names for downloading,": "Tam, kad prieiti prie galimų parsisiųsti modelių",
+	"To access the GGUF models available for downloading,": "Tam, kad prieiti prie galimų parsisiųsti GGUF,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Norėdami prieiti prie programos, susisiekite su administratoriumi, kuris Jus patvirtins.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "į pokalbio įvestį",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Norėdami pasirinkti veiksmus, pirmiausia pridėkite juos funkcijų nuostatuose",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Norėdami pasirinkti filtrus, pirmiausia pridėkite juos funkcijų nuostatuose",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Norėdami pasirinkti įrankius, pirmiausia pridėkite juos prie įrankių nuostatuose",
+	"Toast notifications for new updates": "",
+	"Today": "Šiandien",
+	"Toggle settings": "Atverti/užverti parametrus",
+	"Toggle sidebar": "Atverti/užverti šoninį meniu",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Žetonų kiekis konteksto atnaujinimui (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Įrankis sukurtas sėkmingai",
+	"Tool deleted successfully": "Įrankis ištrintas sėkmingai",
+	"Tool imported successfully": "Įrankis importuotas sėkmingai",
+	"Tool updated successfully": "Įrankis atnaujintas sėkmingai",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Įrankių aprašymas",
+	"Toolkit ID (e.g. my_toolkit)": "Įrakinių ID",
+	"Toolkit Name (e.g. My ToolKit)": "Įrankių pavadinimas",
+	"Tools": "Įrankiai",
+	"Tools are a function calling system with arbitrary code execution": "Įrankiai gali naudoti funkcijas ir vykdyti kodą",
+	"Tools have a function calling system that allows arbitrary code execution": "Įrankiai gali naudoti funkcijas ir leisti vykdyti kodą",
+	"Tools have a function calling system that allows arbitrary code execution.": "Įrankiai gali naudoti funkcijas ir leisti vykdyti kodą",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problemos prieinant prie Ollama?",
+	"TTS Model": "TTS modelis",
+	"TTS Settings": "TTS parametrai",
+	"TTS Voice": "TTS balsas",
+	"Type": "Tipas",
+	"Type Hugging Face Resolve (Download) URL": "Įveskite Hugging Face Resolve nuorodą",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "O ne! Prisijungiant prie {{provider}} kilo problema.",
+	"UI": "sąsaja",
+	"Unpin": "Atsemigti",
+	"Untagged": "",
+	"Update": "Atnaujinti",
+	"Update and Copy Link": "Atnaujinti ir kopijuoti nuorodą",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Atnaujinti slaptažodį",
+	"Updated": "",
+	"Updated at": "Atnaujinta",
+	"Updated At": "",
+	"Upload": "Atnaujinti",
+	"Upload a GGUF model": "Parsisiųsti GGUF modelį",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Atnaujinti rinkmenas",
+	"Upload Pipeline": "Atnaujinti procesą",
+	"Upload Progress": "Įkėlimo progresas",
+	"URL Mode": "URL režimas",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Naudoti Gravatar",
+	"Use Initials": "Naudotojo inicialai",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "naudotojas",
+	"User": "",
+	"User location successfully retrieved.": "Naudotojo vieta sėkmingai gauta",
+	"User Permissions": "Naudotojo leidimai",
+	"Users": "Naudotojai",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Naudoti",
+	"Valid time units:": "Teisingūs laiko vienetai :",
+	"Valves": "Įeitys",
+	"Valves updated": "Įeitys atnaujintos",
+	"Valves updated successfully": "Įeitys atnaujintos sėkmingai",
+	"variable": "kintamasis",
+	"variable to have them replaced with clipboard content.": "kintamoji pakeičiama kopijuoklės turiniu.",
+	"Version": "Versija",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Balsas",
+	"Voice Input": "",
+	"Warning": "Perspėjimas",
+	"Warning:": "Perspėjimas",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Jei pakeisite embedding modelį, turėsite reimportuoti visus dokumentus",
+	"Web": "Web",
+	"Web API": "Web API",
+	"Web Loader Settings": "Web krovimo nustatymai",
+	"Web Search": "Web paieška",
+	"Web Search Engine": "Web paieškos variklis",
+	"Webhook URL": "Webhook nuoroda",
+	"WebUI Settings": "WebUI parametrai",
+	"WebUI will make requests to": "WebUI vykdys užklausas",
+	"What’s New in": "Kas naujo",
+	"Whisper (Local)": "Whisper (lokalus)",
+	"Widescreen Mode": "Plataus ekrano rėžimas",
+	"Won": "",
+	"Workspace": "Nuostatos",
+	"Write a prompt suggestion (e.g. Who are you?)": "Parašykite užklausą",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Parašyk santrumpą trumpesnę nei 50 žodžių šiam tekstui: [tekstas]",
+	"Write something...": "",
+	"Yesterday": "Vakar",
+	"You": "Jūs",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Galite pagerinti modelių darbą suteikdami jiems atminties funkcionalumą.",
+	"You cannot clone a base model": "Negalite klonuoti bazinio modelio",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Jūs neturite archyvuotų pokalbių",
+	"You have shared this chat": "Pasidalinote šiuo pokalbiu",
+	"You're a helpful assistant.": "Esi asistentas.",
+	"You're now logged in.": "Esate prisijungę.",
+	"Your account status is currently pending activation.": "Jūsų paskyra laukia administratoriaus patvirtinimo.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Jūsų finansinis prisidėjimas tiesiogiai keliaus modulio kūrėjui.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube krovimo nustatymai"
+}
diff --git a/src/lib/i18n/locales/ms-MY/translation.json b/src/lib/i18n/locales/ms-MY/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..0965ca36fc86743d8cf798e26949bb5f18e5e5c7
--- /dev/null
+++ b/src/lib/i18n/locales/ms-MY/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' or '-1' untuk tiada tempoh luput.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(contoh `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(contoh `sh webui.sh --api`)",
+	"(latest)": "(terkini)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Anda tidak boleh memadamkan model asas",
+	"{{user}}'s Chats": "Perbualan {{user}}",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend diperlukan",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Model tugas digunakan semasa melaksanakan tugas seperti menjana tajuk untuk perbualan dan pertanyaan carian web.",
+	"a user": "seorang pengguna",
+	"About": "Mengenai",
+	"Account": "Akaun",
+	"Account Activation Pending": "Pengaktifan Akaun belum selesai",
+	"Accurate information": "Informasi tepat",
+	"Actions": "Tindakan",
+	"Active Users": "Pengguna Aktif",
+	"Add": "Tambah",
+	"Add a model id": "Tambah id model",
+	"Add a short description about what this model does": "Tambah penerangan ringkas tentang apa yang model ini boleh lakukan",
+	"Add a short title for this prompt": "Tambah tajuk pendek untuk arahan ini",
+	"Add a tag": "Tambah tag",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Tambah arahan khusus",
+	"Add Files": "Tambah Fail",
+	"Add Memory": "Tambah Memori",
+	"Add Model": "Tambah Model",
+	"Add Tag": "Tambah Tag",
+	"Add Tags": "Tambah Tag",
+	"Add text content": "",
+	"Add User": "Tambah Pengguna",
+	"Adjusting these settings will apply changes universally to all users.": "Melaraskan tetapan ini akan menggunakan perubahan secara universal kepada semua pengguna.",
+	"admin": "pentadbir",
+	"Admin": "Pentadbir",
+	"Admin Panel": "Panel Pentadbir",
+	"Admin Settings": "Tetapan Pentadbir",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Pentadbir mempunyai akses kepada semua alat pada setiap masa; pengguna memerlukan alat yang ditetapkan mengikut model dalam ruang kerja.",
+	"Advanced Parameters": "Parameter Lanjutan",
+	"Advanced Params": "Parameter Lanjutan",
+	"All chats": "",
+	"All Documents": "Semua Dokumen",
+	"Allow Chat Deletion": "Benarkan Penghapusan Perbualan",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Benarkan suara bukan tempatan ",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "Benarkan Lokasi Pengguna",
+	"Allow Voice Interruption in Call": "Benarkan gangguan suara dalam panggilan",
+	"alphanumeric characters and hyphens": "aksara alfanumerik dan tanda sempang",
+	"Already have an account?": "Telah mempunyai akaun?",
+	"an assistant": "seorang pembantu",
+	"and": "dan",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "dan cipta pautan kongsi baharu",
+	"API Base URL": "URL Asas API",
+	"API Key": "Kunci API",
+	"API Key created.": "Kunci API dicipta",
+	"API keys": "Kekunci API",
+	"April": "April",
+	"Archive": "Arkib",
+	"Archive All Chats": "Arkibkan Semua Perbualan",
+	"Archived Chats": "Perbualan yang diarkibkan",
+	"are allowed - Activate this command by typing": "adalah dibenarkan - Aktifkan arahan in dengan menaip",
+	"Are you sure?": "Adakah anda pasti",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Kepilkan Fail",
+	"Attention to detail": "Perincian",
+	"Audio": "Audio",
+	"August": "Ogos",
+	"Auto-playback response": "Main semula respons secara automatik",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Api Auth String",
+	"AUTOMATIC1111 Base URL": "URL Asas AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "URL Asas AUTOMATIC1111 diperlukan.",
+	"Available list": "",
+	"available!": "tersedia!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Kembali",
+	"Bad Response": "Maklumbalas Salah",
+	"Banners": "Sepanduk",
+	"Base Model (From)": "Model Asas (Dari)",
+	"Batch Size (num_batch)": "Saiz Kumpulan (num_batch)",
+	"before": "sebelum,",
+	"Being lazy": "Menjadi Malas",
+	"Brave Search API Key": "Kunci API Carian Brave",
+	"Bypass SSL verification for Websites": "Pintas pengesahan SSL untuk Laman Web",
+	"Call": "Hubungi",
+	"Call feature is not supported when using Web STT engine": "Ciri panggilan tidak disokong apabila menggunakan enjin Web STT",
+	"Camera": "Kamera",
+	"Cancel": "Batal",
+	"Capabilities": "Keupayaan",
+	"Change Password": "Tukar Kata Laluan",
+	"Character": "",
+	"Chat": "Perbualan",
+	"Chat Background Image": "Imej Latar Belakang Perbualan",
+	"Chat Bubble UI": "Antaramuka Buih Perbualan",
+	"Chat Controls": "Kawalan Perbualan",
+	"Chat direction": "Arah Perbualan",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Perbualan",
+	"Check Again": "Semak Kembali",
+	"Check for updates": "Semak kemas kini",
+	"Checking for updates...": "Kemas kini sedang disemak",
+	"Choose a model before saving...": "Pilih model sebelum menyimpan",
+	"Chunk Overlap": "Tindihan 'Çhunk'",
+	"Chunk Params": "Parameter 'Çhunk'",
+	"Chunk Size": "Saiz 'Chunk'",
+	"Citation": "Petikan",
+	"Clear memory": "Kosongkan memori",
+	"Click here for help.": "Klik disini untuk mendapatkan bantuan",
+	"Click here to": "Klik disini untuk",
+	"Click here to download user import template file.": "Klik disini untuk memuat turun fail templat import pengguna",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Klik disini untuk memilih",
+	"Click here to select a csv file.": "Klik disini untuk memilih fail csv",
+	"Click here to select a py file.": "Klik disini untuk memilih fail py",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "klik disini.",
+	"Click on the user role button to change a user's role.": "Klik pada butang peranan pengguna untuk menukar peranan pengguna",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Kebenaran untuk menulis di papan klip ditolak. Sila semak tetapan pelayan web anda untuk memberikan akses yang diperlukan",
+	"Clone": "Klon",
+	"Close": "Tutup",
+	"Code execution": "",
+	"Code formatted successfully": "Kod berjaya diformatkan",
+	"Collection": "Koleksi",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL asas ComfyUI",
+	"ComfyUI Base URL is required.": "URL asas ComfyUI diperlukan",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Arahan",
+	"Completions": "",
+	"Concurrent Requests": "Permintaan Serentak",
+	"Confirm": "Sahkan",
+	"Confirm Password": "Sahkan kata laluan",
+	"Confirm your action": "Sahkan tindakan anda",
+	"Connections": "Sambungan",
+	"Contact Admin for WebUI Access": "Hubungi admin untuk akses WebUI",
+	"Content": "Kandungan",
+	"Content Extraction": "Pengekstrakan Kandungan",
+	"Context Length": "Panjang Konteks",
+	"Continue Response": "Teruskan Respons",
+	"Continue with {{provider}}": "Teruskan dengan {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "Kawalan",
+	"Copied": "Disalin",
+	"Copied shared chat URL to clipboard!": "Menyalin URL sembang kongsi ke papan klip",
+	"Copied to clipboard": "",
+	"Copy": "Salin",
+	"Copy last code block": "Salin Blok Kod Terakhir",
+	"Copy last response": "Salin Respons Terakhir",
+	"Copy Link": "Salin Pautan",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Menyalin ke papan klip berjaya!",
+	"Create a model": "Cipta model",
+	"Create Account": "Cipta Akaun",
+	"Create Knowledge": "",
+	"Create new key": "Cipta kekunci baharu",
+	"Create new secret key": "Cipta kekunci rahsia baharu",
+	"Created at": "Dicipta di",
+	"Created At": "Dicipta Pada",
+	"Created by": "Dicipta oleh",
+	"CSV Import": "Import CSV",
+	"Current Model": "Model Semasa",
+	"Current Password": "Kata laluan semasa",
+	"Custom": "Tersuai",
+	"Customize models for a specific purpose": "Sesuaikan model untuk tujuan tertentu",
+	"Dark": "Gelap",
+	"Dashboard": "Papan Pemuka",
+	"Database": "Pangkalan Data",
+	"December": "Disember",
+	"Default": "Lalai",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Lalai (SentenceTransformers)",
+	"Default Model": "Model Lalai",
+	"Default model updated": "Model lalai dikemas kini",
+	"Default Prompt Suggestions": "Cadangan Gesaan Lalai",
+	"Default User Role": "Peranan Pengguna Lalai",
+	"Delete": "Padam",
+	"Delete a model": "Padam Model",
+	"Delete All Chats": "Padam Semua Perbualan",
+	"Delete chat": "Padam perbualan",
+	"Delete Chat": "Padam Perbualan",
+	"Delete chat?": "Padam perbualan?",
+	"Delete folder?": "",
+	"Delete function?": "Padam fungsi?",
+	"Delete prompt?": "Padam Gesaan?",
+	"delete this link": "Padam pautan ini?",
+	"Delete tool?": "Padam alat?",
+	"Delete User": "Padam Pengguna",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} dipadam",
+	"Deleted {{name}}": "{{name}} dipadam",
+	"Description": "Penerangan",
+	"Didn't fully follow instructions": "Tidak mengikut arahan sepenuhnya",
+	"Disabled": "Dilumpuhkan",
+	"Discover a function": "Temui fungsi",
+	"Discover a model": "Temui model",
+	"Discover a prompt": "Temui gesaan",
+	"Discover a tool": "Temui alat",
+	"Discover, download, and explore custom functions": "Temui, muat turun dan teroka fungsi tersuai",
+	"Discover, download, and explore custom prompts": "Temui, muat turun dan teroka gesaan tersuai",
+	"Discover, download, and explore custom tools": "Temui, muat turun dan teroka alat tersuai",
+	"Discover, download, and explore model presets": "Temui, muat turun dan teroka model pratetap",
+	"Dismissible": "Diketepikan",
+	"Display Emoji in Call": "Paparkan Emoji dalam Panggilan",
+	"Display the username instead of You in the Chat": "Paparkan nama pengguna dan bukannya 'Anda' dalam Sembang",
+	"Do not install functions from sources you do not fully trust.": "Jangan pasang fungsi daripada sumber yang anda tidak percayai sepenuhnya.",
+	"Do not install tools from sources you do not fully trust.": "Jangan pasang alat daripada sumber yang anda tidak percaya sepenuhnya.",
+	"Document": "Dokumen",
+	"Documentation": "Dokumentasi",
+	"Documents": "Dokumen",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "tidak membuat sebarang sambungan luaran, dan data anda kekal selamat pada pelayan yang dihoskan ditempat anda",
+	"Don't have an account?": "Anda tidak mempunyai akaun?",
+	"don't install random functions from sources you don't trust.": "jangan pasang mana-mana fungsi daripada sumber yang anda tidak percayai.",
+	"don't install random tools from sources you don't trust.": "jangan pasang mana-mana alat daripada sumber yang anda tidak percayai.",
+	"Don't like the style": "Tidak suka gaya ini",
+	"Done": "Selesai",
+	"Download": "Muat Turun",
+	"Download canceled": "Muat Turun dibatalkan",
+	"Download Database": "Muat turun Pangkalan Data",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Letakkan mana-mana fail di sini untuk ditambahkan pada perbualan",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "cth '30s','10m'. Unit masa yang sah ialah 's', 'm', 'h'.",
+	"Edit": "Edit",
+	"Edit Arena Model": "",
+	"Edit Memory": "Edit Memori",
+	"Edit User": "Edit Penggunal",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "E-mel",
+	"Embedding Batch Size": "Membenamkan Saiz Kelompok",
+	"Embedding Model": "Model Benamkan",
+	"Embedding Model Engine": "Enjin Model Benamkan",
+	"Embedding model set to \"{{embedding_model}}\"": "Model Benamkan ditetapkan kepada \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Benarkan Perkongsian Komuniti",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Benarkan Pendaftaran Baharu",
+	"Enable Web Search": "Benarkan Carian Web",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "Dibenarkan",
+	"Engine": "Enjin",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "astikan fail CSV anda mengandungi 4 lajur dalam susunan ini: Nama, E-mel, Kata Laluan, Peranan.",
+	"Enter {{role}} message here": "Masukkan mesej {{role}} di sini",
+	"Enter a detail about yourself for your LLMs to recall": "Masukkan butiran tentang diri anda untuk diingati oleh LLM anda",
+	"Enter api auth string (e.g. username:password)": "Masukkan kekunci auth api ( cth nama pengguna:kata laluan )",
+	"Enter Brave Search API Key": "Masukkan Kekunci API carian Brave",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Masukkan Tindihan 'Chunk'",
+	"Enter Chunk Size": "Masukkan Saiz 'Chunk'",
+	"Enter description": "",
+	"Enter Github Raw URL": "Masukkan URL 'Github Raw'",
+	"Enter Google PSE API Key": "Masukkan kunci API Google PSE",
+	"Enter Google PSE Engine Id": "Masukkan Id Enjin Google PSE",
+	"Enter Image Size (e.g. 512x512)": "Masukkan Saiz Imej (cth 512x512)",
+	"Enter language codes": "Masukkan kod bahasa",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Masukkan tag model (cth {{ modelTag }})",
+	"Enter Number of Steps (e.g. 50)": "Masukkan Bilangan Langkah (cth 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Masukkan Skor",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Masukkan URL 'Searxng Query'",
+	"Enter Serper API Key": "Masukkan Kunci API Serper",
+	"Enter Serply API Key": "Masukkan Kunci API Serply",
+	"Enter Serpstack API Key": "Masukkan Kunci API Serpstack",
+	"Enter stop sequence": "Masukkan urutan hentian",
+	"Enter system prompt": "Masukkan gesaan sistem",
+	"Enter Tavily API Key": "Masukkan Kunci API Tavily",
+	"Enter Tika Server URL": "Masukkan URL Pelayan Tika",
+	"Enter Top K": "Masukkan 'Top K'",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Masukkan URL (cth http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Masukkan URL (cth http://localhost:11434)",
+	"Enter Your Email": "Masukkan E-mel Anda",
+	"Enter Your Full Name": "Masukkan Nama Penuh Anda",
+	"Enter your message": "Masukkan mesej anda",
+	"Enter Your Password": "Masukkan Kata Laluan Anda",
+	"Enter Your Role": "Masukkan Peranan Anda",
+	"Error": "Ralat",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Percubaan",
+	"Export": "Eksport",
+	"Export All Chats (All Users)": "Eksport Semua Perbualan (Semua Pengguna)",
+	"Export chat (.json)": "Eksport perbualan (.json)",
+	"Export Chats": "Eksport Perbualan",
+	"Export Config to JSON File": "",
+	"Export Functions": "Eksport Fungsi",
+	"Export LiteLLM config.yaml": "Eksport LiteLLM config.yaml",
+	"Export Models": "Eksport Model",
+	"Export Prompts": "Eksport Gesaan",
+	"Export Tools": "Eksport Alat",
+	"External Models": "Model Luaran",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Gagal mencipta kekunci API",
+	"Failed to read clipboard contents": "Gagal membaca konten papan klip",
+	"Failed to update settings": "Gagal mengemaskini tetapan",
+	"Failed to upload file.": "",
+	"February": "Febuari",
+	"Feedback History": "",
+	"Feel free to add specific details": "Jangan ragu untuk menambah butiran khusus",
+	"File": "Fail",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Mod Fail",
+	"File not found.": "Fail tidak dijumpai",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "Fail-Fail",
+	"Filter is now globally disabled": "Tapisan kini dilumpuhkan secara global",
+	"Filter is now globally enabled": "Tapisan kini dibenarkan secara global",
+	"Filters": "Tapisan",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Peniruan cap jari dikesan, tidak dapat menggunakan nama pendek sebagai avatar. Lalai kepada imej profail asal",
+	"Fluidly stream large external response chunks": "Strim 'chunks' respons luaran yang besar dengan lancar",
+	"Focus chat input": "Fokus kepada input perbualan",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Mengikut arahan dengan sempurna",
+	"Form": "Borang",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Penalti Kekerapan",
+	"Function": "",
+	"Function created successfully": "Fungsi berjaya dibuat",
+	"Function deleted successfully": "Fungsi berjaya dipadamkan",
+	"Function Description (e.g. A filter to remove profanity from text)": "Perihalan Fungsi (cth. Tapisan untuk membuang kata-kata kotor daripada teks)",
+	"Function ID (e.g. my_filter)": "ID Fungsi (cth. my_filter)",
+	"Function is now globally disabled": "Fungsi kini dilumpuhkan secara global",
+	"Function is now globally enabled": "Fungsi kini dibenarkan secara global",
+	"Function Name (e.g. My Filter)": "Nama Fungsi (cth. 'My Filter')",
+	"Function updated successfully": "Fungsi berjaya dikemaskini",
+	"Functions": "Fungsi",
+	"Functions allow arbitrary code execution": "Fungsi membenarkan pelaksanaan kod sewenang-wenangnya",
+	"Functions allow arbitrary code execution.": "Fungsi membenarkan pelaksanaan kod sewenang-wenangnya.",
+	"Functions imported successfully": "Fungsi berjaya diimport",
+	"General": "Umum",
+	"General Settings": "Tetapan Umum",
+	"Generate Image": "Jana Imej",
+	"Generating search query": "Jana pertanyaan carian",
+	"Generation Info": "Maklumat Penjanaan",
+	"Get up and running with": "Bangun dan berlari dengan",
+	"Global": "Global",
+	"Good Response": "Respons Baik",
+	"Google PSE API Key": "Kunci API Google PSE",
+	"Google PSE Engine Id": "ID Enjin Google PSE",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "tidak mempunyai perbualan.",
+	"Hello, {{name}}": "Hello, {{name}}",
+	"Help": "Bantuan",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Sembunyi",
+	"Hide Model": "Sembunyikan Model",
+	"How can I help you today?": "Bagaimana saya boleh membantu anda hari ini?",
+	"Hybrid Search": "Carian Hibrid",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Saya mengakui bahawa saya telah membaca dan saya memahami implikasi tindakan saya. Saya sedar tentang risiko yang berkaitan dengan melaksanakan kod sewenang-wenangnya dan saya telah mengesahkan kebolehpercayaan sumber tersebut.",
+	"ID": "",
+	"Image Generation (Experimental)": "Penjanaan Imej (Percubaan)",
+	"Image Generation Engine": "Enjin Penjanaan Imej",
+	"Image Settings": "Tetapan Imej",
+	"Images": "Imej",
+	"Import Chats": "Import Perbualan",
+	"Import Config from JSON File": "",
+	"Import Functions": "Import Fungsi",
+	"Import Models": "Import Model",
+	"Import Prompts": "Import Gesaan",
+	"Import Tools": "Import Alat",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Sertakan bendera `-- api -auth` semasa menjalankan stable-diffusion-webui ",
+	"Include `--api` flag when running stable-diffusion-webui": "Sertakan bendera `-- api ` semasa menjalankan stable-diffusion-webui",
+	"Info": "Maklumat",
+	"Input commands": "Masukkan Arahan",
+	"Install from Github URL": "Pasang daripada URL Github",
+	"Instant Auto-Send After Voice Transcription": "Hantar Secara Automatik Dengan Segera Selepas Transkripsi Suara",
+	"Interface": "Antaramuka",
+	"Invalid file format.": "",
+	"Invalid Tag": "Tag tidak sah",
+	"January": "Januari",
+	"join our Discord for help.": "sertai Discord kami untuk mendapatkan bantuan.",
+	"JSON": "JSON",
+	"JSON Preview": "Pratonton JSON",
+	"July": "Julai",
+	"June": "Jun",
+	"JWT Expiration": "Tempoh Tamat JWT",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Kekalkan Hidup",
+	"Keyboard shortcuts": "Pintasan papan kekunci",
+	"Knowledge": "Pengetahuan",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Bahasa",
+	"large language models, locally.": "model bahasa besar, tempatan.",
+	"Last Active": "Dilihat aktif terakhir pada",
+	"Last Modified": "Kemaskini terakhir pada",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Cerah",
+	"Listening...": "Mendengar...",
+	"LLMs can make mistakes. Verify important information.": "LLM boleh membuat kesilapan. Sahkan maklumat penting",
+	"Local Models": "Model Tempatan",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Dicipta oleh Komuniti OpenWebUI",
+	"Make sure to enclose them with": "Pastikan untuk melampirkannya dengan",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Urus",
+	"Manage Arena Models": "",
+	"Manage Models": "Urus Model",
+	"Manage Ollama Models": "Urus Model Ollama",
+	"Manage Pipelines": "Urus 'Pipelines'",
+	"March": "Mac",
+	"Max Tokens (num_predict)": "Token Maksimum ( num_predict )",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maksimum 3 model boleh dimuat turun serentak. Sila cuba sebentar lagi.",
+	"May": "Mei",
+	"Memories accessible by LLMs will be shown here.": "Memori yang boleh diakses oleh LLM akan ditunjukkan di sini.",
+	"Memory": "Memori",
+	"Memory added successfully": "Memori berjaya ditambah",
+	"Memory cleared successfully": "Memori berjaya dikosongkan",
+	"Memory deleted successfully": "Memori berjaya dihapuskan",
+	"Memory updated successfully": "Memori berjaya dikemaskini",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Mesej yang anda hantar selepas membuat pautan anda tidak akan dikongsi. Pengguna dengan URL akan dapat melihat perbualan yang dikongsi.",
+	"Min P": "P Minimum",
+	"Minimum Score": "Skor Minimum",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "DD MMMM YYYY",
+	"MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY HH:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Model '{{ modelName }}' telah berjaya dimuat turun.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{ modelTag }}' sudah dalam baris gilir untuk dimuat turun.",
+	"Model {{modelId}} not found": "Model {{ modelId }} tidak dijumpai",
+	"Model {{modelName}} is not vision capable": "Model {{ modelName }} tidak mempunyai keupayaan penglihatan",
+	"Model {{name}} is now {{status}}": "Model {{name}} kini {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "Model berjaya dibuat!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Laluan sistem fail model dikesan. Nama pendek model diperlukan untuk kemas kini, tidak boleh diteruskan.",
+	"Model ID": "ID Model",
+	"Model Name": "",
+	"Model not selected": "Model tidak dipilih",
+	"Model Params": "Model Params",
+	"Model updated successfully": "Model berjaya dikemas kini",
+	"Model Whitelisting": "Senarai Putih Model",
+	"Model(s) Whitelisted": "Model Disenarai Putih",
+	"Modelfile Content": "Kandungan Modelfail",
+	"Models": "Model",
+	"more": "",
+	"More": "Lagi",
+	"Move to Top": "",
+	"Name": "Nama",
+	"Name your model": "Namakan Model Anda",
+	"New Chat": "Perbualan Baru",
+	"New folder": "",
+	"New Password": "Kata Laluan Baru",
+	"No content found": "",
+	"No content to speak": "Tiada kandungan untuk bercakap",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Tiada fail dipilih",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Tiada keputusan dijumpai",
+	"No search query generated": "Tiada pertanyaan carian dijana",
+	"No source available": "Tiada sumber tersedia",
+	"No valves to update": "Tiada 'valve' untuk dikemas kini",
+	"None": "Tiada",
+	"Not factually correct": "Tidak tepat secara fakta",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Jika anda menetapkan skor minimum, carian hanya akan mengembalikan dokumen dengan skor lebih besar daripada atau sama dengan skor minimum.",
+	"Notes": "",
+	"Notifications": "Pemberitahuan",
+	"November": "November",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "ID OAuth",
+	"October": "Oktober",
+	"Off": "Mati",
+	"Okay, Let's Go!": "Baiklah, Jom!",
+	"OLED Dark": "OLED Gelap",
+	"Ollama": "Ollama",
+	"Ollama API": "API Ollama",
+	"Ollama API disabled": "API Ollama dilumpuhkan",
+	"Ollama API is disabled": "API Ollama telah dilumpuhkan",
+	"Ollama Version": "Versi Ollama",
+	"On": "Hidup",
+	"Only": "Hanya",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Hanya aksara alfanumerik dan sempang dibenarkan dalam rentetan arahan.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Maaf, didapati URL tidak sah. Sila semak semula dan cuba lagi.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Maaf, Anda menggunakan kaedah yang tidak disokong (bahagian 'frontend' sahaja). Sila sediakan WebUI dari 'backend'.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Buka perbualan baru",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) adalah lebih rendah daripada versi yang diperlukan iaitu (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API OpenAI",
+	"OpenAI API Config": "Tetapan API OpenAI",
+	"OpenAI API Key is required.": "Kekunci API OpenAI diperlukan",
+	"OpenAI URL/Key required.": "URL/Kekunci OpenAI diperlukan",
+	"or": "atau",
+	"Other": "Lain-lain",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Kata Laluan",
+	"PDF document (.pdf)": "Dokumen PDF (.pdf)",
+	"PDF Extract Images (OCR)": "Imej Ekstrak PDF (OCR)",
+	"pending": "tertunda",
+	"Permission denied when accessing media devices": "Tidak mendapat kebenaran apabila cuba mengakses peranti media",
+	"Permission denied when accessing microphone": "Tidak mendapat kebenaran apabila cuba mengakses mikrofon",
+	"Permission denied when accessing microphone: {{error}}": "Tidak mendapat kebenaran apabila cuba mengakses mikrofon: {{error}}",
+	"Personalization": "Personalisasi",
+	"Pin": "Pin",
+	"Pinned": "Disemat",
+	"Pipeline deleted successfully": "'Pipeline' berjaya dipadam",
+	"Pipeline downloaded successfully": "'Pipeline' berjaya dimuat turun",
+	"Pipelines": "'Pipeline'",
+	"Pipelines Not Detected": "'Pipeline' tidak ditemui",
+	"Pipelines Valves": "'Pipeline Valves'",
+	"Plain text (.txt)": "Teks biasa (.txt)",
+	"Playground": "Taman Permainan",
+	"Please carefully review the following warnings:": "Sila semak dengan teliti amaran berikut:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Sikap positif",
+	"Previous 30 days": "30 hari sebelumnya",
+	"Previous 7 days": "7 hari sebelumnya",
+	"Profile Image": "Imej Profail",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Gesaan (cth Beritahu saya fakta yang menyeronokkan tentang Kesultanan Melaka)",
+	"Prompt Content": "Kandungan Gesaan",
+	"Prompt suggestions": "Cadangan Gesaan",
+	"Prompts": "Gesaan",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Tarik \"{{ searchValue }}\" daripada Ollama.com",
+	"Pull a model from Ollama.com": "Tarik model dari Ollama.com",
+	"Query Params": "'Query Params'",
+	"RAG Template": "Templat RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Baca dengan lantang",
+	"Record voice": "Rakam suara",
+	"Redirecting you to OpenWebUI Community": "Membawa anda ke Komuniti OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Rujuk diri anda sebagai \"User\" (cth, \"Pengguna sedang belajar bahasa Sepanyol\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Menolak dimana ia tidak sepatutnya",
+	"Regenerate": "Jana semula",
+	"Release Notes": "Nota Keluaran",
+	"Relevance": "",
+	"Remove": "Hapuskan",
+	"Remove Model": "Hapuskan Model",
+	"Rename": "Namakan Semula",
+	"Repeat Last N": "Ulang N Terakhir",
+	"Request Mode": "Mod Permintaan",
+	"Reranking Model": "Model 'Reranking'",
+	"Reranking model disabled": "Model 'Reranking' dilumpuhkan",
+	"Reranking model set to \"{{reranking_model}}\"": "Model 'Reranking' ditetapkan kepada \"{{reranking_model}}\"",
+	"Reset": "Tetapkan Semula",
+	"Reset Upload Directory": "Tetapkan Semula Direktori Muat Naik",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Salin Response secara Automatik ke Papan Klip",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Pemberitahuan respons tidak boleh diaktifkan kerana kebenaran tapak web tidak diberi. Sila lawati tetapan pelayar web anda untuk memberikan akses yang diperlukan.",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Peranan",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "Jalankan",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Jalankan Llama 2, Code Llama dan model lain. Sesuaikan dan buat sendiri.",
+	"Running": "Sedang dijalankan",
+	"Save": "Simpan",
+	"Save & Create": "Simpan & Cipta",
+	"Save & Update": "Simpan & Kemas Kini",
+	"Save As Copy": "",
+	"Save Tag": "Simpan Tag",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Penyimpanan log perbualan terus ke storan pelayan web anda tidak lagi disokong. Sila luangkan sedikit masa untuk memuat turun dan memadam log perbualan anda dengan mengklik butang di bawah. Jangan risau, anda boleh mengimport semula log perbualan anda dengan mudah melalui 'backend'",
+	"Scroll to bottom when switching between branches": "Skrol ke bawah apabila bertukar antara cawangan",
+	"Search": "Carian",
+	"Search a model": "Cari Model",
+	"Search Chats": "Cari Perbualan",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Carian Fungsi",
+	"Search Knowledge": "",
+	"Search Models": "Carian Model",
+	"Search Prompts": "Carian Gesaan",
+	"Search Query Generation Prompt": "Carian Penjanaan Pertanyaan Gesaan",
+	"Search Result Count": "Kiraan Hasil Carian",
+	"Search Tools": "Alat Carian",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Mencari {{count}} sites_one",
+	"Searched {{count}} sites_other": "Mencari {{count}} tapak_lain",
+	"Searching \"{{searchQuery}}\"": "encari \"{{ searchQuery }}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "URL Pertanyaan Searxng",
+	"See readme.md for instructions": "Lihat readme.md untuk arahan",
+	"See what's new": "Lihat apa yang terbaru",
+	"Seed": "Benih",
+	"Select a base model": "Pilih model asas",
+	"Select a engine": "Pilih enjin",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Pilih fungsi",
+	"Select a model": "Pilih model",
+	"Select a pipeline": "Pilih 'pipeline'",
+	"Select a pipeline url": "Pilih url 'pipeline'",
+	"Select a tool": "Pilih alat",
+	"Select an Ollama instance": "Pilih Ollama contoh",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Pilih model",
+	"Select only one model to call": "Pilih hanya satu model untuk dipanggil",
+	"Selected model(s) do not support image inputs": "Model dipilih tidak menyokong input imej",
+	"Semantic distance to query": "",
+	"Send": "Hantar",
+	"Send a Message": "Hantar Pesanan",
+	"Send message": "Hantar pesanan",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "September",
+	"Serper API Key": "Kunci API Serper",
+	"Serply API Key": "Kunci API Serply",
+	"Serpstack API Key": "Kunci API Serpstack",
+	"Server connection verified": "Sambungan pelayan disahkan",
+	"Set as default": "Tetapkan sebagai lalai",
+	"Set CFG Scale": "",
+	"Set Default Model": "Tetapkan Model Lalai",
+	"Set embedding model (e.g. {{model}})": "Tetapkan model benamkan (cth {{model}})",
+	"Set Image Size": "Tetapkan saiz imej",
+	"Set reranking model (e.g. {{model}})": "Tetapkan model 'reranking' (cth {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "tapkan Langkah",
+	"Set Task Model": "Tetapkan Model Tugasan",
+	"Set Voice": "Tetapan Suara",
+	"Set whisper model": "",
+	"Settings": "Tetapan",
+	"Settings saved successfully!": "Tetapan berjaya disimpan!",
+	"Share": "Kongsi",
+	"Share Chat": "Kongsi Perbualan",
+	"Share to OpenWebUI Community": "Kongsi kepada Komuniti OpenWebUI",
+	"short-summary": "ringkasan",
+	"Show": "Tunjukkan",
+	"Show Admin Details in Account Pending Overlay": "Tunjukkan Butiran Pentadbir dalam Akaun Menunggu Tindanan",
+	"Show Model": "Tunjukkan Model",
+	"Show shortcuts": "Tunjukkan pintasan",
+	"Show your support!": "Tunjukkan sokongan anda!",
+	"Showcased creativity": "eativiti yang dipamerkan",
+	"Sign in": "Daftar masuk",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Daftar keluar",
+	"Sign up": "Daftar",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Sumber",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Ralat pengecaman pertuturan: {{error}}",
+	"Speech-to-Text Engine": "Enjin Ucapan-ke-Teks",
+	"Stop": "",
+	"Stop Sequence": "Jujukan Henti",
+	"Stream Chat Response": "",
+	"STT Model": "Model STT",
+	"STT Settings": "Tetapan STT",
+	"Subtitle (e.g. about the Roman Empire)": "Sari kata (cth tentang Kesultanan Melaka)",
+	"Success": "Berjaya",
+	"Successfully updated.": "Berjaya Dikemaskini",
+	"Suggested": "Cadangan",
+	"Support": "Sokongan",
+	"Support this plugin:": "Sokong plugin ini",
+	"Sync directory": "",
+	"System": "Sistem",
+	"System Instructions": "",
+	"System Prompt": "Gesaan Sistem",
+	"Tags": "Tag",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Sentuh untuk mengganggu",
+	"Tavily API Key": "Kunci API Tavily",
+	"Tell us more:": "Beritahu kami lebih lanjut",
+	"Temperature": "Suhu",
+	"Template": "Templat",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Enjin Teks-ke-Ucapan",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Terima kasih atas maklum balas anda!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Pembangun di sebalik 'plugin' ini adalah sukarelawan yang bersemangat daripada komuniti. Jika anda mendapati 'plugin' ini membantu, sila pertimbangkan untuk menyumbang kepada pembangunannya.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Skor hendaklah berada diantara 0.0 (0%) dan 1.0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "Berfikir...",
+	"This action cannot be undone. Do you wish to continue?": "Tindakan ini tidak boleh diubah semula kepada asal. Adakah anda ingin teruskan",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ini akan memastikan bahawa perbualan berharga anda disimpan dengan selamat ke pangkalan data 'backend' anda. Terima kasih!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "ni adalah ciri percubaan, ia mungkin tidak berfungsi seperti yang diharapkan dan tertakluk kepada perubahan pada bila-bila masa.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Ini akan memadam",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Penjelasan menyeluruh",
+	"Tika": "Tika",
+	"Tika Server URL required.": "URL Pelayan Tika diperlukan.",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Petua: Kemas kini berbilang slot pembolehubah secara berturut-turut dengan menekan kekunci tab dalam input perbualan selepas setiap penggantian.",
+	"Title": "Tajuk",
+	"Title (e.g. Tell me a fun fact)": "Tajuk (cth Beritahu saya fakta yang menyeronokkan)",
+	"Title Auto-Generation": "Penjanaan Auto Tajuk",
+	"Title cannot be an empty string.": "Tajuk tidak boleh menjadi rentetan kosong",
+	"Title Generation Prompt": "Gesaan Penjanaan Tajuk",
+	"To access the available model names for downloading,": "Untuk mengakses nama model yang tersedia untuk dimuat turun,",
+	"To access the GGUF models available for downloading,": "Untuk mengakses model GGUF yang tersedia untuk dimuat turun,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Untuk mengakses WebUI , sila hubungi pentadbir. Pentadbir boleh menguruskan status pengguna daripada Panel Pentadbiran",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "untuk input perbualan.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Untuk memilih tindakan di sini, tambahkannya pada ruang kerja \"Functions\" dahulu.",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Untuk memilih tapisan di sini, tambahkannya pada ruang kerja \"Functions\" dahulu.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Untuk memilih kit alatan di sini, tambahkannya pada ruang kerja \"Tools\" dahulu.",
+	"Toast notifications for new updates": "",
+	"Today": "Hari Ini",
+	"Toggle settings": "Suis Tetapan ",
+	"Toggle sidebar": "Suis Bar Sisi",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Token Untuk Teruskan Dalam Muat Semula Konteks ( num_keep )",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Alat berjaya dibuat",
+	"Tool deleted successfully": "Alat berjaya dipadamkan",
+	"Tool imported successfully": "Alat berjaya diimport",
+	"Tool updated successfully": "Alat berjaya dikemas kini",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Perihalan Kit Alatan (cth. Kit alatan untuk melaksanakan pelbagai operasi)",
+	"Toolkit ID (e.g. my_toolkit)": "ID Kit Alatan (cth my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Nama Kit Alatan (cth My ToolKit)",
+	"Tools": "Alatan",
+	"Tools are a function calling system with arbitrary code execution": "Alatan ialah sistem panggilan fungsi dengan pelaksanaan kod sewenang-wenangnya",
+	"Tools have a function calling system that allows arbitrary code execution": "Alatan mempunyai sistem panggilan fungsi yang membolehkan pelaksanaan kod sewenang-wenangnya",
+	"Tools have a function calling system that allows arbitrary code execution.": "Alatan mempunyai sistem panggilan fungsi yang membolehkan pelaksanaan kod sewenang-wenangnya.",
+	"Top K": "'Top K'",
+	"Top P": "'Top P'",
+	"Trouble accessing Ollama?": "Masalah mengakses Ollama?",
+	"TTS Model": "Model TTS",
+	"TTS Settings": "Tetapan TTS",
+	"TTS Voice": "Suara TTS",
+	"Type": "jenis",
+	"Type Hugging Face Resolve (Download) URL": "Taip URL 'Hugging Face Resolve (Download)'",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Maaf! Terdapat masalah menyambung ke {{provider}}.",
+	"UI": "UI",
+	"Unpin": "Nyahsematkan",
+	"Untagged": "",
+	"Update": "Kemaskini",
+	"Update and Copy Link": "Kemaskini dan salin pautan",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Kemaskini Kata Laluan",
+	"Updated": "",
+	"Updated at": "Dikemaskini pada",
+	"Updated At": "",
+	"Upload": "Muatnaik",
+	"Upload a GGUF model": "Muatnaik model GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Muatnaik fail",
+	"Upload Pipeline": "Muatnaik 'Pipeline'",
+	"Upload Progress": "Kemajuan Muatnaik",
+	"URL Mode": "Mod URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Gunakan Gravatar",
+	"Use Initials": "Gunakan nama pendek",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "se_mmap (Ollama)",
+	"user": "pengguna",
+	"User": "",
+	"User location successfully retrieved.": "Lokasi pengguna berjaya diambil.",
+	"User Permissions": "Kebenaran Pengguna",
+	"Users": "Pengguna",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Gunakan",
+	"Valid time units:": "Unit masa yang sah:",
+	"Valves": "'Valves'",
+	"Valves updated": "'Valves' dikemaskini",
+	"Valves updated successfully": "'Valves' berjaya dikemaskini",
+	"variable": "pembolehubah",
+	"variable to have them replaced with clipboard content.": "pembolehubah untuk ia digantikan dengan kandungan papan klip.",
+	"Version": "Versi",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Suara",
+	"Voice Input": "",
+	"Warning": "Amaran",
+	"Warning:": "Amaran:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Amaran: Jika anda mengemas kini atau menukar model benam anda, anda perlu mengimport semula semua dokumen.",
+	"Web": "Web",
+	"Web API": "API Web",
+	"Web Loader Settings": "Tetapan Pemuat Web",
+	"Web Search": "Carian Web",
+	"Web Search Engine": "Enjin Carian Web",
+	"Webhook URL": "URL 'Webhook'",
+	"WebUI Settings": "Tetapan WebUI",
+	"WebUI will make requests to": "WebUI akan membuat permintaan kepada",
+	"What’s New in": "Apakah yang terbaru dalam",
+	"Whisper (Local)": "Whisper (Local)",
+	"Widescreen Mode": "Mod Skrin Lebar",
+	"Won": "",
+	"Workspace": "Ruangan Kerja",
+	"Write a prompt suggestion (e.g. Who are you?)": "Tulis cadangan gesaan (cth Siapakah anda?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Tulis ringkasan dalam 50 patah perkataan yang meringkaskan [topik atau kata kunci].",
+	"Write something...": "",
+	"Yesterday": "Semalam",
+	"You": "Anda",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Anda boleh memperibadikan interaksi anda dengan LLM dengan menambahkan memori melalui butang 'Urus' di bawah, menjadikannya lebih membantu dan disesuaikan dengan anda.",
+	"You cannot clone a base model": "Anda tidak boleh mengklon model asas",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Anda tidak mempunyai perbualan yang diarkibkan",
+	"You have shared this chat": "Anda telah berkongsi perbualan ini",
+	"You're a helpful assistant.": "Anda seorang pembantu yang bagus",
+	"You're now logged in.": "Anda kini telah log masuk.",
+	"Your account status is currently pending activation.": "Status akaun anda ialah sedang menunggu pengaktifan.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Seluruh sumbangan anda akan dihantar terus kepada pembangun 'plugin'; Open WebUI tidak mengambil sebarang peratusan keuntungan daripadanya. Walau bagaimanapun, platform pembiayaan yang dipilih mungkin mempunyai caj tersendiri.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Tetapan Pemuat Youtube"
+}
diff --git a/src/lib/i18n/locales/nb-NO/translation.json b/src/lib/i18n/locales/nb-NO/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..abd9c9cde44ed57697f970802eaeaae95cb97c07
--- /dev/null
+++ b/src/lib/i18n/locales/nb-NO/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 't', 'd', 'u' eller '-1' for ingen utløp.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(f.eks. `sh webui.sh --api --api-auth brukernavn_passord`)",
+	"(e.g. `sh webui.sh --api`)": "(f.eks. `sh webui.sh --api`)",
+	"(latest)": "(siste)",
+	"{{ models }}": "{{ modeller }}",
+	"{{ owner }}: You cannot delete a base model": "{{ eier }}: Du kan ikke slette en grunnmodell",
+	"{{user}}'s Chats": "{{user}}s samtaler",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend kreves",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "En oppgavemodell brukes når du utfører oppgaver som å generere titler for samtaler og websøkeforespørsler",
+	"a user": "en bruker",
+	"About": "Om",
+	"Account": "Konto",
+	"Account Activation Pending": "Venter på kontoaktivering",
+	"Accurate information": "Nøyaktig informasjon",
+	"Actions": "Handlinger",
+	"Active Users": "Aktive brukere",
+	"Add": "Legg til",
+	"Add a model id": "Legg til en modell-ID",
+	"Add a short description about what this model does": "Legg til en kort beskrivelse av hva denne modellen gjør",
+	"Add a short title for this prompt": "Legg til en kort tittel for denne prompten",
+	"Add a tag": "Legg til en tag",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Legg til egendefinert prompt",
+	"Add Files": "Legg til filer",
+	"Add Memory": "Legg til minne",
+	"Add Model": "Legg til modell",
+	"Add Tag": "Legg til tag",
+	"Add Tags": "Legg til tagger",
+	"Add text content": "",
+	"Add User": "Legg til bruker",
+	"Adjusting these settings will apply changes universally to all users.": "Endringer i disse innstillingene vil gjelde for alle brukere uten unntak.",
+	"admin": "administrator",
+	"Admin": "Administrator",
+	"Admin Panel": "Administrasjonspanel",
+	"Admin Settings": "Administrasjonsinnstillinger",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Administratorer har alltid tilgang til alle verktøy, mens brukere må få tildelt verktøy for hver enkelt modell i arbeidsområdet.",
+	"Advanced Parameters": "Avanserte parametere",
+	"Advanced Params": "Avanserte parametere",
+	"All chats": "",
+	"All Documents": "Alle dokumenter",
+	"Allow Chat Deletion": "Tillat sletting av chatter",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Tillat ikke-lokale stemmer",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "Aktiver stedstjenester",
+	"Allow Voice Interruption in Call": "Muliggjør stemmeavbrytelse i samtale",
+	"alphanumeric characters and hyphens": "alfanumeriske tegn og bindestreker",
+	"Already have an account?": "Har du allerede en konto?",
+	"an assistant": "en assistent",
+	"and": "og",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "og opprett en ny delt lenke.",
+	"API Base URL": "API Grunn-URL",
+	"API Key": "API-nøkkel",
+	"API Key created.": "API-nøkkel opprettet.",
+	"API keys": "API-nøkler",
+	"April": "april",
+	"Archive": "Arkiv",
+	"Archive All Chats": "Arkiver alle chatter",
+	"Archived Chats": "Arkiverte chatter",
+	"are allowed - Activate this command by typing": "er tillatt - Aktiver denne kommandoen ved å skrive",
+	"Are you sure?": "Er du sikker?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Legg ved fil",
+	"Attention to detail": "Sans for detaljer",
+	"Audio": "Lyd",
+	"August": "august",
+	"Auto-playback response": "Automatisk avspilling av svar",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Api Autentiseringsstreng",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Grunn-URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Grunn-URL kreves.",
+	"Available list": "",
+	"available!": "tilgjengelig!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Tilbake",
+	"Bad Response": "Dårlig svar",
+	"Banners": "Bannere",
+	"Base Model (From)": "Grunnmodell (Fra)",
+	"Batch Size (num_batch)": "Batchstørrelse (num_batch)",
+	"before": "før",
+	"Being lazy": "Er lat",
+	"Brave Search API Key": "Brave Search API-nøkkel",
+	"Bypass SSL verification for Websites": "Omgå SSL-verifisering for nettsteder",
+	"Call": "Ring",
+	"Call feature is not supported when using Web STT engine": "Ringefunksjonen støttes ikke når du bruker Web STT-motoren",
+	"Camera": "Kamera",
+	"Cancel": "Avbryt",
+	"Capabilities": "Muligheter",
+	"Change Password": "Endre passord",
+	"Character": "",
+	"Chat": "Chat",
+	"Chat Background Image": "Bakgrunnsbilde for chat",
+	"Chat Bubble UI": "Chat-boble UI",
+	"Chat Controls": "Chat-kontroller",
+	"Chat direction": "Chat-retning",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Chatter",
+	"Check Again": "Sjekk igjen",
+	"Check for updates": "Sjekk for oppdateringer",
+	"Checking for updates...": "Sjekker for oppdateringer...",
+	"Choose a model before saving...": "Velg en modell før du lagrer...",
+	"Chunk Overlap": "Chunk Overlap",
+	"Chunk Params": "Chunk-parametere",
+	"Chunk Size": "Chunk-størrelse",
+	"Citation": "Sitering",
+	"Clear memory": "Tøm minnet",
+	"Click here for help.": "Klikk her for hjelp.",
+	"Click here to": "Klikk her for å",
+	"Click here to download user import template file.": "Klikk her for å hente ned importmal for brukere.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Klikk her for å velge",
+	"Click here to select a csv file.": "Klikk her for å velge en csv-fil.",
+	"Click here to select a py file.": "Klikk her for å velge en py-fil.",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "klikk her.",
+	"Click on the user role button to change a user's role.": "Klikk på brukerrolle-knappen for å endre en brukers rolle.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Skrivetilgang til utklippstavlen ble avslått. Kontroller nettleserinnstillingene for å gi nødvendig tillatelse.",
+	"Clone": "Klon",
+	"Close": "Lukk",
+	"Code execution": "",
+	"Code formatted successfully": "Koden ble formatert",
+	"Collection": "Samling",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Grunn-URL",
+	"ComfyUI Base URL is required.": "ComfyUI Grunn-URL kreves.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Kommando",
+	"Completions": "",
+	"Concurrent Requests": "Samtidige forespørsler",
+	"Confirm": "Bekreft",
+	"Confirm Password": "Bekreft passord",
+	"Confirm your action": "Bekreft din handling",
+	"Connections": "Tilkoblinger",
+	"Contact Admin for WebUI Access": "Kontakt administrator for WebUI-tilgang",
+	"Content": "Innhold",
+	"Content Extraction": "Uthenting av innhold",
+	"Context Length": "Kontekstlengde",
+	"Continue Response": "Fortsett svar",
+	"Continue with {{provider}}": "Fortsett med {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "Kontroller",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "Kopiert delt chat-URL til utklippstavlen!",
+	"Copied to clipboard": "",
+	"Copy": "Kopier",
+	"Copy last code block": "Kopier siste kodeblokk",
+	"Copy last response": "Kopier siste svar",
+	"Copy Link": "Kopier lenke",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Kopiering til utklippstavlen var vellykket!",
+	"Create a model": "Lag en modell",
+	"Create Account": "Opprett konto",
+	"Create Knowledge": "",
+	"Create new key": "Lag ny nøkkel",
+	"Create new secret key": "Lag ny hemmelig nøkkel",
+	"Created at": "Opprettet",
+	"Created At": "Opprettet",
+	"Created by": "Opprettet av",
+	"CSV Import": "CSV-import",
+	"Current Model": "Nåværende modell",
+	"Current Password": "Nåværende passord",
+	"Custom": "Tilpasset",
+	"Customize models for a specific purpose": "Tilpass modeller for et spesifikt formål",
+	"Dark": "Mørk",
+	"Dashboard": "Instrumentbord",
+	"Database": "Database",
+	"December": "desember",
+	"Default": "Standard",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
+	"Default Model": "Standardmodell",
+	"Default model updated": "Standardmodell oppdatert",
+	"Default Prompt Suggestions": "Standard promptforslag",
+	"Default User Role": "Standard brukerrolle",
+	"Delete": "Slett",
+	"Delete a model": "Slett en modell",
+	"Delete All Chats": "Slett alle chatter",
+	"Delete chat": "Slett chat",
+	"Delete Chat": "Slett chat",
+	"Delete chat?": "Slett chat?",
+	"Delete folder?": "",
+	"Delete function?": "Slett funksjon?",
+	"Delete prompt?": "Slett prompt?",
+	"delete this link": "slett denne lenken",
+	"Delete tool?": "Slett verktøy?",
+	"Delete User": "Slett bruker",
+	"Deleted {{deleteModelTag}}": "Slettet {{deleteModelTag}}",
+	"Deleted {{name}}": "Slettet {{name}}",
+	"Description": "Beskrivelse",
+	"Didn't fully follow instructions": "Fulgte ikke instruksjonene fullt ut",
+	"Disabled": "Deaktivert",
+	"Discover a function": "Oppdag en funksjon",
+	"Discover a model": "Oppdag en modell",
+	"Discover a prompt": "Oppdag en prompt",
+	"Discover a tool": "Oppdag et verktøy",
+	"Discover, download, and explore custom functions": "Oppdag, last ned og utforsk egendefinerte funksjoner",
+	"Discover, download, and explore custom prompts": "Oppdag, last ned og utforsk egendefinerte prompts",
+	"Discover, download, and explore custom tools": "Oppdag, last ned og utforsk egendefinerte verktøy",
+	"Discover, download, and explore model presets": "Oppdag, last ned og utforsk modellforhåndsinnstillinger",
+	"Dismissible": "Kan lukkes",
+	"Display Emoji in Call": "Vis emoji i samtale",
+	"Display the username instead of You in the Chat": "Vis brukernavnet i stedet for Du i chatten",
+	"Do not install functions from sources you do not fully trust.": "Ikke installer funksjoner fra kilder du ikke fullt ut stoler på.",
+	"Do not install tools from sources you do not fully trust.": "Ikke installer verktøy fra kilder du ikke fullt ut stoler på.",
+	"Document": "Dokument",
+	"Documentation": "Dokumentasjon",
+	"Documents": "Dokumenter",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "har ingen tilkobling til eksterne tjenester, og dataene dine blir værende sikkert på din lokale tjener.",
+	"Don't have an account?": "Har du ikke en konto?",
+	"don't install random functions from sources you don't trust.": "ikke installer tilfeldige funksjoner fra kilder du ikke stoler på.",
+	"don't install random tools from sources you don't trust.": "ikke installer tilfeldige verktøy fra kilder du ikke stoler på.",
+	"Don't like the style": "Liker ikke stilen",
+	"Done": "Ferdig",
+	"Download": "Last ned",
+	"Download canceled": "Nedlasting avbrutt",
+	"Download Database": "Last ned database",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Slipp filer her for å legge dem til i samtalen",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "f.eks. '30s','10m'. Gyldige tidsenheter er 's', 'm', 't'.",
+	"Edit": "Rediger",
+	"Edit Arena Model": "",
+	"Edit Memory": "Rediger minne",
+	"Edit User": "Rediger bruker",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "E-post",
+	"Embedding Batch Size": "Batch-størrelse for embedding",
+	"Embedding Model": "Embedding-modell",
+	"Embedding Model Engine": "Embedding-modellmotor",
+	"Embedding model set to \"{{embedding_model}}\"": "Embedding-modell satt til \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Aktiver deling i fellesskap",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Aktiver nye registreringer",
+	"Enable Web Search": "Aktiver websøk",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "Aktivert",
+	"Engine": "Motor",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Sørg for at CSV-filen din inkluderer 4 kolonner i denne rekkefølgen: Navn, E-post, Passord, Rolle.",
+	"Enter {{role}} message here": "Skriv inn {{role}} melding her",
+	"Enter a detail about yourself for your LLMs to recall": "Skriv inn en detalj om deg selv som språkmodellene dine kan huske",
+	"Enter api auth string (e.g. username:password)": "Skriv inn api-autentiseringsstreng (f.eks. brukernavn:passord)",
+	"Enter Brave Search API Key": "Skriv inn Brave Search API-nøkkel",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Skriv inn Chunk Overlap",
+	"Enter Chunk Size": "Skriv inn Chunk-størrelse",
+	"Enter description": "",
+	"Enter Github Raw URL": "Skriv inn Github Raw-URL",
+	"Enter Google PSE API Key": "Skriv inn Google PSE API-nøkkel",
+	"Enter Google PSE Engine Id": "Skriv inn Google PSE Motor-ID",
+	"Enter Image Size (e.g. 512x512)": "Skriv inn bildestørrelse (f.eks. 512x512)",
+	"Enter language codes": "Skriv inn språkkoder",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Skriv inn modelltag (f.eks. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Skriv inn antall steg (f.eks. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Skriv inn poengsum",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Skriv inn Searxng forespørsels-URL",
+	"Enter Serper API Key": "Skriv inn Serper API-nøkkel",
+	"Enter Serply API Key": "Skriv inn Serply API-nøkkel",
+	"Enter Serpstack API Key": "Skriv inn Serpstack API-nøkkel",
+	"Enter stop sequence": "Skriv inn stoppsekvens",
+	"Enter system prompt": "Skriv inn systemprompt",
+	"Enter Tavily API Key": "Skriv inn Tavily API-nøkkel",
+	"Enter Tika Server URL": "Skriv inn Tika Server-URL",
+	"Enter Top K": "Skriv inn Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Skriv inn URL (f.eks. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Skriv inn URL (f.eks. http://localhost:11434)",
+	"Enter Your Email": "Skriv inn din e-post",
+	"Enter Your Full Name": "Skriv inn ditt fulle navn",
+	"Enter your message": "Skriv inn meldingen din",
+	"Enter Your Password": "Skriv inn ditt passord",
+	"Enter Your Role": "Skriv inn din rolle",
+	"Error": "Feil",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Eksperimentell",
+	"Export": "Eksporter",
+	"Export All Chats (All Users)": "Eksporter alle chatter (alle brukere)",
+	"Export chat (.json)": "Eksporter chat (.json)",
+	"Export Chats": "Eksporter chatter",
+	"Export Config to JSON File": "",
+	"Export Functions": "Eksporter funksjoner",
+	"Export LiteLLM config.yaml": "Eksporter LiteLLM config.yaml",
+	"Export Models": "Eksporter modeller",
+	"Export Prompts": "Eksporter prompts",
+	"Export Tools": "Eksporter verktøy",
+	"External Models": "Eksterne modeller",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Kunne ikke opprette API-nøkkel.",
+	"Failed to read clipboard contents": "Kunne ikke lese utklippstavleinnhold",
+	"Failed to update settings": "Kunne ikke oppdatere innstillinger",
+	"Failed to upload file.": "",
+	"February": "februar",
+	"Feedback History": "",
+	"Feel free to add specific details": "Legg gjerne til spesifikke detaljer",
+	"File": "Fil",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Filmodus",
+	"File not found.": "Fil ikke funnet.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "Filer",
+	"Filter is now globally disabled": "Filteret er nå deaktivert på systemnivå",
+	"Filter is now globally enabled": "Filteret er nå aktivert på systemnivå",
+	"Filters": "Filtere",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingeravtrykk-spoofing oppdaget: Kan ikke bruke initialer som avatar. Bruker standard profilbilde.",
+	"Fluidly stream large external response chunks": "Flytende strømming av store eksterne responsbiter",
+	"Focus chat input": "Fokuser chatinput",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Fulgte instruksjonene perfekt",
+	"Form": "Form",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Frekvensstraff",
+	"Function": "",
+	"Function created successfully": "Funksjonen ble opprettet",
+	"Function deleted successfully": "Funksjonen ble slettet",
+	"Function Description (e.g. A filter to remove profanity from text)": "Funksjonsbeskrivelse (f.eks. Et filter for å fjerne banning fra tekst)",
+	"Function ID (e.g. my_filter)": "Funksjons-id (f.eks. my_filter)",
+	"Function is now globally disabled": "Funksjonen er nå deaktivert på systemnivå",
+	"Function is now globally enabled": "Funksjonen er nå aktivert på systemnivå",
+	"Function Name (e.g. My Filter)": "Funksjonsnavn (f.eks. Mitt Filter)",
+	"Function updated successfully": "Funksjonen ble oppdatert",
+	"Functions": "Funksjoner",
+	"Functions allow arbitrary code execution": "Funksjoner tillater vilkårlig kodeeksekvering",
+	"Functions allow arbitrary code execution.": "Funksjoner tillater vilkårlig kodeeksekvering.",
+	"Functions imported successfully": "Funksjoner importert",
+	"General": "Generelt",
+	"General Settings": "Generelle innstillinger",
+	"Generate Image": "Generer bilde",
+	"Generating search query": "Genererer søkeforespørsel",
+	"Generation Info": "Generasjonsinfo",
+	"Get up and running with": "Kom i gang med",
+	"Global": "Systemnivå",
+	"Good Response": "Godt svar",
+	"Google PSE API Key": "Google PSE API-nøkkel",
+	"Google PSE Engine Id": "Google PSE Motor-ID",
+	"h:mm a": "t:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "har ingen samtaler.",
+	"Hello, {{name}}": "Hei, {{name}}",
+	"Help": "Hjelp",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Skjul",
+	"Hide Model": "Skjul modell",
+	"How can I help you today?": "Hvordan kan jeg hjelpe deg i dag?",
+	"Hybrid Search": "Hybrid-søk",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Jeg bekrefter at jeg har lest og forstår konsekvensene av min handling. Jeg er klar over risikoen forbundet med å kjøre vilkårlig kode, og jeg har verifisert kildens pålitelighet.",
+	"ID": "",
+	"Image Generation (Experimental)": "Bildegenerering (Eksperimentell)",
+	"Image Generation Engine": "Bildegenereringsmotor",
+	"Image Settings": "Bildeinnstillinger",
+	"Images": "Bilder",
+	"Import Chats": "Importer chatter",
+	"Import Config from JSON File": "",
+	"Import Functions": "Funksjoner",
+	"Import Models": "Importer modeller",
+	"Import Prompts": "Importer prompts",
+	"Import Tools": "Importer verktøy",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Inkluder `--api-auth`-flagget når du kjører stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Inkluder `--api`-flagget når du kjører stable-diffusion-webui",
+	"Info": "Info",
+	"Input commands": "Inntast kommandoer",
+	"Install from Github URL": "Installer fra Github-URL",
+	"Instant Auto-Send After Voice Transcription": "Direkte autosending etter stemmegjenkjenning",
+	"Interface": "Grensesnitt",
+	"Invalid file format.": "",
+	"Invalid Tag": "Ugyldig tag",
+	"January": "januar",
+	"join our Discord for help.": "bli med i vår Discord for hjelp.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON-forhåndsvisning",
+	"July": "juli",
+	"June": "juni",
+	"JWT Expiration": "JWT-utløp",
+	"JWT Token": "JWT-token",
+	"Keep Alive": "Hold i live",
+	"Keyboard shortcuts": "Hurtigtaster",
+	"Knowledge": "Kunnskap",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Språk",
+	"large language models, locally.": "Store språkmodeller, lokalt.",
+	"Last Active": "Sist aktiv",
+	"Last Modified": "Sist endret",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Lys",
+	"Listening...": "Lytter ...",
+	"LLMs can make mistakes. Verify important information.": "Språkmodeller kan gjøre feil. Verifiser viktige opplysninger.",
+	"Local Models": "Lokale modeller",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Laget av OpenWebUI-fellesskapet",
+	"Make sure to enclose them with": "Sørg for å omslutte dem med",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Administrer",
+	"Manage Arena Models": "",
+	"Manage Models": "Administrer modeller",
+	"Manage Ollama Models": "Administrer Ollama-modeller",
+	"Manage Pipelines": "Administrer pipelines",
+	"March": "mars",
+	"Max Tokens (num_predict)": "Maks antall tokens (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maksimalt 3 modeller kan lastes ned samtidig. Vennligst prøv igjen senere.",
+	"May": "mai",
+	"Memories accessible by LLMs will be shown here.": "Minner tilgjengelige for språkmodeller vil vises her.",
+	"Memory": "Minne",
+	"Memory added successfully": "Minne lagt til",
+	"Memory cleared successfully": "Minne tømt",
+	"Memory deleted successfully": "Minne slettet",
+	"Memory updated successfully": "Minne oppdatert",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Meldinger du sender etter at du har opprettet lenken din vil ikke bli delt. Brukere med URL-en vil kunne se den delte chatten.",
+	"Min P": "",
+	"Minimum Score": "Minimum poengsum",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Modellen '{{modelName}}' er lastet ned.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Modellen '{{modelTag}}' er allerede i nedlastingskøen.",
+	"Model {{modelId}} not found": "Modellen {{modelId}} ble ikke funnet",
+	"Model {{modelName}} is not vision capable": "Modellen {{modelName}} er ikke visjonsdyktig",
+	"Model {{name}} is now {{status}}": "Modellen {{name}} er nå {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "Modellen ble opprettet!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modellens filsystemsti oppdaget. Modellens kortnavn er påkrevd for oppdatering, kan ikke fortsette.",
+	"Model ID": "Modell-ID",
+	"Model Name": "",
+	"Model not selected": "Modell ikke valgt",
+	"Model Params": "Modellparametere",
+	"Model updated successfully": "Modell oppdatert",
+	"Model Whitelisting": "Modell hvitlisting",
+	"Model(s) Whitelisted": "Modell(er) hvitlistet",
+	"Modelfile Content": "Modellfilinnhold",
+	"Models": "Modeller",
+	"more": "",
+	"More": "Mer",
+	"Move to Top": "",
+	"Name": "Navn",
+	"Name your model": "Gi modellen din et navn",
+	"New Chat": "Ny chat",
+	"New folder": "",
+	"New Password": "Nytt passord",
+	"No content found": "",
+	"No content to speak": "Mangler innhold for tale",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Ingen fil valgt",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Ingen resultater funnet",
+	"No search query generated": "Ingen søkeforespørsel generert",
+	"No source available": "Ingen kilde tilgjengelig",
+	"No valves to update": "Ingen ventiler å oppdatere",
+	"None": "Ingen",
+	"Not factually correct": "Uriktig informasjon",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Merk: Hvis du setter en minimums poengsum, vil søket kun returnere dokumenter med en poengsum som er større enn eller lik minimums poengsummen.",
+	"Notes": "",
+	"Notifications": "Varsler",
+	"November": "november",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth-ID",
+	"October": "oktober",
+	"Off": "Av",
+	"Okay, Let's Go!": "Ok, la oss gå!",
+	"OLED Dark": "OLED mørk",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API deaktivert",
+	"Ollama API is disabled": "Ollama API er deaktivert",
+	"Ollama Version": "Ollama versjon",
+	"On": "På",
+	"Only": "Kun",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Kun alfanumeriske tegn og bindestreker er tillatt i kommandostrengen.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Ser ut som URL-en er ugyldig. Vennligst dobbeltsjekk og prøv igjen.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! Du bruker en ikke-støttet metode (kun frontend). Vennligst server WebUI fra backend.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Åpne ny chat",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI-versjon (v{{OPEN_WEBUI_VERSION}}) er lavere enn nødvendig versjon (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API-konfigurasjon",
+	"OpenAI API Key is required.": "OpenAI API-nøkkel kreves.",
+	"OpenAI URL/Key required.": "OpenAI URL/nøkkel kreves.",
+	"or": "eller",
+	"Other": "Annet",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Passord",
+	"PDF document (.pdf)": "PDF-dokument (.pdf)",
+	"PDF Extract Images (OCR)": "PDF-ekstraktbilder (OCR)",
+	"pending": "avventer",
+	"Permission denied when accessing media devices": "Tillatelse nektet ved tilgang til medieenheter",
+	"Permission denied when accessing microphone": "Tillatelse nektet ved tilgang til mikrofon",
+	"Permission denied when accessing microphone: {{error}}": "Tillatelse nektet ved tilgang til mikrofon: {{error}}",
+	"Personalization": "Personalisering",
+	"Pin": "Fest",
+	"Pinned": "Festet",
+	"Pipeline deleted successfully": "Pipeline slettet",
+	"Pipeline downloaded successfully": "Pipeline lastet ned",
+	"Pipelines": "Pipelines",
+	"Pipelines Not Detected": "Pipelines ikke oppdaget",
+	"Pipelines Valves": "Pipeline-ventiler",
+	"Plain text (.txt)": "Ren tekst (.txt)",
+	"Playground": "Lekeplass",
+	"Please carefully review the following warnings:": "Vær vennlig å lese gjennom følgende advarsler grundig:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Positiv holdning",
+	"Previous 30 days": "Forrige 30 dager",
+	"Previous 7 days": "Forrige 7 dager",
+	"Profile Image": "Profilbilde",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (f.eks. Fortell meg en morsom fakta om Romerriket)",
+	"Prompt Content": "Prompt-innhold",
+	"Prompt suggestions": "Promptforslag",
+	"Prompts": "Prompter",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Trekk \"{{searchValue}}\" fra Ollama.com",
+	"Pull a model from Ollama.com": "Trekk en modell fra Ollama.com",
+	"Query Params": "Forespørselsparametere",
+	"RAG Template": "RAG-mal",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Les høyt",
+	"Record voice": "Ta opp stemme",
+	"Redirecting you to OpenWebUI Community": "Omdirigerer deg til OpenWebUI-fellesskapet",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Omtal deg selv som \"Bruker\" (f.eks. \"Bruker lærer spansk\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Avvist når det ikke skulle ha vært det",
+	"Regenerate": "Regenerer",
+	"Release Notes": "Utgivelsesnotater",
+	"Relevance": "",
+	"Remove": "Fjern",
+	"Remove Model": "Fjern modell",
+	"Rename": "Gi nytt navn",
+	"Repeat Last N": "Gjenta siste N",
+	"Request Mode": "Forespørselsmodus",
+	"Reranking Model": "Reranking-modell",
+	"Reranking model disabled": "Reranking-modell deaktivert",
+	"Reranking model set to \"{{reranking_model}}\"": "Reranking-modell satt til \"{{reranking_model}}\"",
+	"Reset": "Tilbakestill",
+	"Reset Upload Directory": "Tilbakestill opplastingskatalog",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Respons auto-kopi til utklippstavle",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Respons-varsler kan ikke aktiveres da nettstedsrettighetene er nektet. Vennligst se nettleserinnstillingene dine for å gi nødvendig tilgang.",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rolle",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Kjør Llama 2, Code Llama og andre modeller. Tilpass og lag egne versjoner.",
+	"Running": "Kjører",
+	"Save": "Lagre",
+	"Save & Create": "Lagre og opprett",
+	"Save & Update": "Lagre og oppdater",
+	"Save As Copy": "",
+	"Save Tag": "Lagre tag",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Lagring av chatlogger direkte til nettleserens lagring støttes ikke lenger. Vennligst ta et øyeblikk for å laste ned og slette chatloggene dine ved å klikke på knappen nedenfor. Ikke bekymre deg, du kan enkelt re-importere chatloggene dine til backend via",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Søk",
+	"Search a model": "Søk en modell",
+	"Search Chats": "Søk chatter",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Søk funksjoner",
+	"Search Knowledge": "",
+	"Search Models": "Søk modeller",
+	"Search Prompts": "Søk prompter",
+	"Search Query Generation Prompt": "Instruksjon for å lage søkeord",
+	"Search Result Count": "Antall søkeresultater",
+	"Search Tools": "Søkeverktøy",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Søkte på {{count}} side",
+	"Searched {{count}} sites_other": "Søkte på {{count}} sider",
+	"Searching \"{{searchQuery}}\"": "Søker etter \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng forespørsels-URL",
+	"See readme.md for instructions": "Se readme.md for instruksjoner",
+	"See what's new": "Se hva som er nytt",
+	"Seed": "Seed",
+	"Select a base model": "Velg en grunnmodell",
+	"Select a engine": "Velg en motor",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Velg en funksjon",
+	"Select a model": "Velg en modell",
+	"Select a pipeline": "Velg en pipeline",
+	"Select a pipeline url": "Velg en pipeline-URL",
+	"Select a tool": "Velg et verktøy",
+	"Select an Ollama instance": "Velg en Ollama-instans",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Velg modell",
+	"Select only one model to call": "Velg kun én modell å kalle",
+	"Selected model(s) do not support image inputs": "Valgte modell(er) støtter ikke bildeforslag",
+	"Semantic distance to query": "",
+	"Send": "Send",
+	"Send a Message": "Send en melding",
+	"Send message": "Send melding",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "september",
+	"Serper API Key": "Serper API-nøkkel",
+	"Serply API Key": "Serply API-nøkkel",
+	"Serpstack API Key": "Serpstack API-nøkkel",
+	"Server connection verified": "Servertilkobling bekreftet",
+	"Set as default": "Sett som standard",
+	"Set CFG Scale": "",
+	"Set Default Model": "Sett standardmodell",
+	"Set embedding model (e.g. {{model}})": "Sett embedding-modell (f.eks. {{model}})",
+	"Set Image Size": "Sett bildestørrelse",
+	"Set reranking model (e.g. {{model}})": "Sett reranking-modell (f.eks. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Sett steg",
+	"Set Task Model": "Sett oppgavemodell",
+	"Set Voice": "Sett stemme",
+	"Set whisper model": "",
+	"Settings": "Innstillinger",
+	"Settings saved successfully!": "Innstillinger lagret!",
+	"Share": "Del",
+	"Share Chat": "Del chat",
+	"Share to OpenWebUI Community": "Del med OpenWebUI-fellesskapet",
+	"short-summary": "kort sammendrag",
+	"Show": "Vis",
+	"Show Admin Details in Account Pending Overlay": "Vis administratordetaljer i ventende kontooverlay",
+	"Show Model": "Vis modell",
+	"Show shortcuts": "Vis snarveier",
+	"Show your support!": "Vis din støtte!",
+	"Showcased creativity": "Vist frem kreativitet",
+	"Sign in": "Logg inn",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Logg ut",
+	"Sign up": "Registrer deg",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Kilde",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Feil ved talegjenkjenning: {{error}}",
+	"Speech-to-Text Engine": "Tale-til-tekst-motor",
+	"Stop": "",
+	"Stop Sequence": "Stoppsekvens",
+	"Stream Chat Response": "",
+	"STT Model": "STT-modell",
+	"STT Settings": "STT-innstillinger",
+	"Subtitle (e.g. about the Roman Empire)": "Undertittel (f.eks. om Romerriket)",
+	"Success": "Suksess",
+	"Successfully updated.": "Oppdatert.",
+	"Suggested": "Foreslått",
+	"Support": "Bidra",
+	"Support this plugin:": "Bidra til denne utvidelsen:",
+	"Sync directory": "",
+	"System": "System",
+	"System Instructions": "",
+	"System Prompt": "Systemprompt",
+	"Tags": "Tagger",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Berør for å avbryte",
+	"Tavily API Key": "Tavily API-nøkkel",
+	"Tell us more:": "Fortell oss mer:",
+	"Temperature": "Temperatur",
+	"Template": "Mal",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Tekst-til-tale-motor",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Takk for tilbakemeldingen!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Utviklerne bak denne utvidelsen er lidenskapelige frivillige fra fellesskapet. Hvis du finner denne utvidelsen nyttig, vennligst vurder å bidra til utviklingen.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Poengsummen skal være en verdi mellom 0,0 (0%) og 1,0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "Tenker ...",
+	"This action cannot be undone. Do you wish to continue?": "Denne handlingen kan ikke angres. Ønsker du å fortsette?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dette sikrer at dine verdifulle samtaler er trygt lagret i backend-databasen din. Takk!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dette er en eksperimentell funksjon, det er mulig den ikke fungerer som forventet og kan endres når som helst.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Dette vil slette",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Grundig forklaring",
+	"Tika": "Tika",
+	"Tika Server URL required.": "Tika Server-URL kreves.",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tips: Oppdater flere variabelplasser etter hverandre ved å trykke på tab-tasten i chatinputen etter hver erstatning.",
+	"Title": "Tittel",
+	"Title (e.g. Tell me a fun fact)": "Tittel (f.eks. Fortell meg et morsomt faktum)",
+	"Title Auto-Generation": "Automatisk tittelgenerering",
+	"Title cannot be an empty string.": "Tittelen kan ikke være en tom streng.",
+	"Title Generation Prompt": "Tittelgenereringsprompt",
+	"To access the available model names for downloading,": "For å få tilgang til tilgjengelige modelnavn for nedlasting,",
+	"To access the GGUF models available for downloading,": "For å få tilgang til GGUF-modellene som er tilgjengelige for nedlasting,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "For å få tilgang til WebUI, vennligst kontakt administratoren. Administratorer kan administrere brukerstatus fra Admin-panelet.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "til chatinput.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "For å velge handlinger her, legg dem til i \"Funksjoner\"-arbeidsområdet først.",
+	"To select filters here, add them to the \"Functions\" workspace first.": "For å velge filtre her, legg dem til i \"Funksjoner\"-arbeidsområdet først.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "For å velge verktøysett her, legg dem til i \"Verktøy\"-arbeidsområdet først.",
+	"Toast notifications for new updates": "",
+	"Today": "I dag",
+	"Toggle settings": "Veksle innstillinger",
+	"Toggle sidebar": "Veksle sidefelt",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Tokens å beholde ved kontekstoppdatering (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Verktøy opprettet",
+	"Tool deleted successfully": "Verktøy slettet",
+	"Tool imported successfully": "Verktøy importert",
+	"Tool updated successfully": "Verktøy oppdatert",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Verktøysettbeskrivelse (f.eks. Et verktøysett for å utføre ulike operasjoner)",
+	"Toolkit ID (e.g. my_toolkit)": "Verktøysett-ID (f.eks. my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Verktøysett-navn (f.eks. Mitt verktøysett)",
+	"Tools": "Verktøy",
+	"Tools are a function calling system with arbitrary code execution": "Vertkøy er et funksjonskallssystem med vilkårlig kodeeksekvering",
+	"Tools have a function calling system that allows arbitrary code execution": "Verktøy har et funksjonskallssystem som tillater vilkårlig kodeeksekvering",
+	"Tools have a function calling system that allows arbitrary code execution.": "Verktøy har et funksjonskallssystem som tillater vilkårlig kodeeksekvering.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problemer med tilgang til Ollama?",
+	"TTS Model": "TTS-modell",
+	"TTS Settings": "TTS-innstillinger",
+	"TTS Voice": "TTS-stemme",
+	"Type": "Type",
+	"Type Hugging Face Resolve (Download) URL": "Skriv inn Hugging Face Resolve (nedlasting) URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Oops! Det oppsto et problem med tilkoblingen til {{provider}}.",
+	"UI": "UI",
+	"Unpin": "Løsne",
+	"Untagged": "",
+	"Update": "Oppdater",
+	"Update and Copy Link": "Oppdater og kopier lenke",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Oppdater passord",
+	"Updated": "",
+	"Updated at": "Oppdatert",
+	"Updated At": "",
+	"Upload": "Last opp",
+	"Upload a GGUF model": "Last opp en GGUF-modell",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Last opp filer",
+	"Upload Pipeline": "Last opp pipeline",
+	"Upload Progress": "Opplastingsfremdrift",
+	"URL Mode": "URL-modus",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Bruk Gravatar",
+	"Use Initials": "Bruk initialer",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "bruker",
+	"User": "",
+	"User location successfully retrieved.": "Brukerlokasjon hentet",
+	"User Permissions": "Brukertillatelser",
+	"Users": "Brukere",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Utnytt",
+	"Valid time units:": "Gyldige tidsenheter:",
+	"Valves": "Ventiler",
+	"Valves updated": "Ventiler oppdatert",
+	"Valves updated successfully": "Ventiler oppdatert",
+	"variable": "variabel",
+	"variable to have them replaced with clipboard content.": "variabel for å få dem erstattet med utklippstavleinnhold.",
+	"Version": "Versjon",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Stemme",
+	"Voice Input": "",
+	"Warning": "Advarsel",
+	"Warning:": "Advarsel:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Advarsel: Hvis du oppdaterer eller endrer embedding-modellen din, må du re-importere alle dokumenter.",
+	"Web": "Web",
+	"Web API": "Web-API",
+	"Web Loader Settings": "Web-lasterinnstillinger",
+	"Web Search": "Websøk",
+	"Web Search Engine": "Websøkemotor",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "WebUI innstillinger",
+	"WebUI will make requests to": "WebUI vil gjøre forespørsler til",
+	"What’s New in": "Hva er nytt i",
+	"Whisper (Local)": "Whisper (Lokal)",
+	"Widescreen Mode": "Bredskjermmodus",
+	"Won": "",
+	"Workspace": "Arbeidsområde",
+	"Write a prompt suggestion (e.g. Who are you?)": "Skriv et promptforslag (f.eks. Hvem er du?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Skriv et sammendrag på 50 ord som oppsummerer [emne eller nøkkelord].",
+	"Write something...": "",
+	"Yesterday": "I går",
+	"You": "Du",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Du kan tilpasse interaksjonene dine med språkmodeller ved å legge til minner gjennom 'Administrer'-knappen nedenfor, slik at de blir mer hjelpsomme og tilpasset deg.",
+	"You cannot clone a base model": "Du kan ikke klone en grunnmodell",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Du har ingen arkiverte samtaler.",
+	"You have shared this chat": "Du har delt denne chatten",
+	"You're a helpful assistant.": "Du er en hjelpsom assistent.",
+	"You're now logged in.": "Du er nå logget inn.",
+	"Your account status is currently pending activation.": "Din konto venter for øyeblikket på aktivering.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Hele beløpet du gir går uavkortet til utvikleren av tillegget; Open WebUI tar ikke noe av dette. Den valgte finansieringsplattformen kan derimot ha egne gebyrer.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Innstillinger for YouTube-laster"
+}
diff --git a/src/lib/i18n/locales/nl-NL/translation.json b/src/lib/i18n/locales/nl-NL/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..a28747f48f76b72db0c6ca6e7c5c2d4739ec52eb
--- /dev/null
+++ b/src/lib/i18n/locales/nl-NL/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' of '-1' for geen vervaldatum.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(e.g. `sh webui.sh --api`)",
+	"(latest)": "(nieuwste)",
+	"{{ models }}": "{{ modellen }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: U kunt een basismodel niet verwijderen",
+	"{{user}}'s Chats": "{{user}}'s Chats",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend Verlpicht",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Een taakmodel wordt gebruikt bij het uitvoeren van taken zoals het genereren van titels voor chats en zoekopdrachten op internet",
+	"a user": "een gebruiker",
+	"About": "Over",
+	"Account": "Account",
+	"Account Activation Pending": "",
+	"Accurate information": "Accurate informatie",
+	"Actions": "Acties",
+	"Active Users": "Actieve Gebruikers",
+	"Add": "Toevoegen",
+	"Add a model id": "Een model-id toevoegen",
+	"Add a short description about what this model does": "Voeg een korte beschrijving toe over wat dit model doet",
+	"Add a short title for this prompt": "Voeg een korte titel toe voor deze prompt",
+	"Add a tag": "Voeg een tag toe",
+	"Add Arena Model": "Voeg Arena Model toe",
+	"Add Content": "Voeg Content toe",
+	"Add content here": "Voeg hier content toe",
+	"Add custom prompt": "Voeg een aangepaste prompt toe",
+	"Add Files": "Voege Bestanden toe",
+	"Add Memory": "Voeg Geheugen toe",
+	"Add Model": "Voeg Model toe",
+	"Add Tag": "Voeg Tag toe",
+	"Add Tags": "Voeg Tags toe",
+	"Add text content": "Voeg Text inhoud toe",
+	"Add User": "Voeg Gebruiker toe",
+	"Adjusting these settings will apply changes universally to all users.": "Het aanpassen van deze instellingen zal universeel worden toegepast op alle gebruikers.",
+	"admin": "admin",
+	"Admin": "",
+	"Admin Panel": "Administratieve Paneel",
+	"Admin Settings": "Administratieve Settings",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "Geavanceerde Parameters",
+	"Advanced Params": "Geavanceerde parameters",
+	"All chats": "Alle chats",
+	"All Documents": "Alle Documenten",
+	"Allow Chat Deletion": "Sta Chat Verwijdering toe",
+	"Allow Chat Editing": "Chatbewerking toestaan",
+	"Allow non-local voices": "Niet-lokale stemmen toestaan",
+	"Allow Temporary Chat": "Tijdelijke chat toestaan",
+	"Allow User Location": "Gebruikerslocatie toestaan",
+	"Allow Voice Interruption in Call": "Stemonderbreking tijdens gesprek toestaan",
+	"alphanumeric characters and hyphens": "alfanumerieke karakters en streepjes",
+	"Already have an account?": "Heb je al een account?",
+	"an assistant": "een assistent",
+	"and": "en",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "en maak een nieuwe gedeelde link.",
+	"API Base URL": "API Base URL",
+	"API Key": "API Key",
+	"API Key created.": "API Key gemaakt.",
+	"API keys": "API keys",
+	"April": "April",
+	"Archive": "Archief",
+	"Archive All Chats": "Archiveer alle chats",
+	"Archived Chats": "chatrecord",
+	"are allowed - Activate this command by typing": "zijn toegestaan - Activeer deze commando door te typen",
+	"Are you sure?": "Zeker weten?",
+	"Arena Models": "Arena Modellen",
+	"Artifacts": "",
+	"Ask a question": "Stel een vraag",
+	"Assistant": "Assistent",
+	"Attach file": "Voeg een bestand toe",
+	"Attention to detail": "Attention to detail",
+	"Audio": "Audio",
+	"August": "Augustus",
+	"Auto-playback response": "Automatisch afspelen van antwoord",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Basis URL is verplicht",
+	"Available list": "Beschikbare lijst",
+	"available!": "beschikbaar!",
+	"Azure AI Speech": "",
+	"Azure Region": "Azure Regio",
+	"Back": "Terug",
+	"Bad Response": "Ongeldig antwoord",
+	"Banners": "Banners",
+	"Base Model (From)": "Basismodel (vanaf)",
+	"Batch Size (num_batch)": "",
+	"before": "voor",
+	"Being lazy": "Lustig zijn",
+	"Brave Search API Key": "Brave Search API-sleutel",
+	"Bypass SSL verification for Websites": "SSL-verificatie omzeilen voor websites",
+	"Call": "Oproep",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "Annuleren",
+	"Capabilities": "Mogelijkheden",
+	"Change Password": "Wijzig Wachtwoord",
+	"Character": "",
+	"Chat": "Chat",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "Chat Bubble UI",
+	"Chat Controls": "",
+	"Chat direction": "Chat Richting",
+	"Chat Overview": "Chat Overzicht",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Chats",
+	"Check Again": "Controleer Opnieuw",
+	"Check for updates": "Controleer op updates",
+	"Checking for updates...": "Controleren op updates...",
+	"Choose a model before saving...": "Kies een model voordat je opslaat...",
+	"Chunk Overlap": "Chunk Overlap",
+	"Chunk Params": "Chunk Params",
+	"Chunk Size": "Chunk Grootte",
+	"Citation": "Citaat",
+	"Clear memory": "Geheugen wissen",
+	"Click here for help.": "Klik hier voor hulp.",
+	"Click here to": "Klik hier om",
+	"Click here to download user import template file.": "Klik hier om het sjabloonbestand voor gebruikersimport te downloaden.",
+	"Click here to learn more about faster-whisper and see the available models.": "Klik hier om meer te leren over faster-whisper en de beschikbare modellen te bekijken.",
+	"Click here to select": "Klik hier om te selecteren",
+	"Click here to select a csv file.": "Klik hier om een csv file te selecteren.",
+	"Click here to select a py file.": "Klik hier om een py-bestand te selecteren.",
+	"Click here to upload a workflow.json file.": "Klik hier om een workflow.json-bestand te uploaden.",
+	"click here.": "klik hier.",
+	"Click on the user role button to change a user's role.": "Klik op de gebruikersrol knop om de rol van een gebruiker te wijzigen.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "Kloon",
+	"Close": "Sluiten",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Verzameling",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Base URL",
+	"ComfyUI Base URL is required.": "ComfyUI Base URL is required.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Commando",
+	"Completions": "",
+	"Concurrent Requests": "Gelijktijdige verzoeken",
+	"Confirm": "Bevestigen",
+	"Confirm Password": "Bevestig Wachtwoord",
+	"Confirm your action": "Bevestig uw actie",
+	"Connections": "Verbindingen",
+	"Contact Admin for WebUI Access": "Neem contact op met de beheerder voor WebUI-toegang",
+	"Content": "Inhoud",
+	"Content Extraction": "",
+	"Context Length": "Context Lengte",
+	"Continue Response": "Doorgaan met Antwoord",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "Gekopieerd",
+	"Copied shared chat URL to clipboard!": "URL van gedeelde gesprekspagina gekopieerd naar klembord!",
+	"Copied to clipboard": "Gekopieerd naar klembord",
+	"Copy": "Kopieer",
+	"Copy last code block": "Kopieer laatste code blok",
+	"Copy last response": "Kopieer laatste antwoord",
+	"Copy Link": "Kopieer Link",
+	"Copy to clipboard": "Kopier naar klembord",
+	"Copying to clipboard was successful!": "Kopiëren naar klembord was succesvol!",
+	"Create a model": "Een model maken",
+	"Create Account": "Maak Account",
+	"Create Knowledge": "Creër kennis",
+	"Create new key": "Maak nieuwe sleutel",
+	"Create new secret key": "Maak nieuwe geheim sleutel",
+	"Created at": "Gemaakt op",
+	"Created At": "Gemaakt op",
+	"Created by": "Gemaakt door",
+	"CSV Import": "",
+	"Current Model": "Huidig Model",
+	"Current Password": "Huidig Wachtwoord",
+	"Custom": "Aangepast",
+	"Customize models for a specific purpose": "Modellen aanpassen voor een specifiek doel",
+	"Dark": "Donker",
+	"Dashboard": "",
+	"Database": "Database",
+	"December": "December",
+	"Default": "Standaard",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Standaard (SentenceTransformers)",
+	"Default Model": "Standaard model",
+	"Default model updated": "Standaard model bijgewerkt",
+	"Default Prompt Suggestions": "Standaard Prompt Suggesties",
+	"Default User Role": "Standaard Gebruikersrol",
+	"Delete": "Verwijderen",
+	"Delete a model": "Verwijder een model",
+	"Delete All Chats": "Verwijder alle chats",
+	"Delete chat": "Verwijder chat",
+	"Delete Chat": "Verwijder Chat",
+	"Delete chat?": "Verwijder chat?",
+	"Delete folder?": "Verwijder map?",
+	"Delete function?": "Verwijder functie?",
+	"Delete prompt?": "Verwijder prompt?",
+	"delete this link": "verwijder deze link",
+	"Delete tool?": "Verwijder tool?",
+	"Delete User": "Verwijder Gebruiker",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} is verwijderd",
+	"Deleted {{name}}": "{{name}} verwijderd",
+	"Description": "Beschrijving",
+	"Didn't fully follow instructions": "Ik heb niet alle instructies volgt",
+	"Disabled": "Uitgeschakeld",
+	"Discover a function": "Ontdek een functie",
+	"Discover a model": "Ontdek een model",
+	"Discover a prompt": "Ontdek een prompt",
+	"Discover a tool": "Ontdek een tool",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Ontdek, download en verken aangepaste prompts",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Ontdek, download en verken model presets",
+	"Dismissible": "Afwijsbaar",
+	"Display Emoji in Call": "Emoji weergeven tijdens gesprek",
+	"Display the username instead of You in the Chat": "Toon de gebruikersnaam in plaats van Jij in de Chat",
+	"Do not install functions from sources you do not fully trust.": "Installeer geen functies vanuit bronnen die je niet volledig vertrouwt",
+	"Do not install tools from sources you do not fully trust.": "Installeer geen tools vanuit bronnen die je niet volledig vertrouwt.",
+	"Document": "Document",
+	"Documentation": "Documentatie",
+	"Documents": "Documenten",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "maakt geen externe verbindingen, en je gegevens blijven veilig op je lokaal gehoste server.",
+	"Don't have an account?": "Heb je geen account?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "Je vindt het stijl niet?",
+	"Done": "Voltooid",
+	"Download": "Download",
+	"Download canceled": "Download geannuleerd",
+	"Download Database": "Download Database",
+	"Draw": "Teken",
+	"Drop any files here to add to the conversation": "Sleep bestanden hier om toe te voegen aan het gesprek",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "bijv. '30s', '10m'. Geldige tijdseenheden zijn 's', 'm', 'h'.",
+	"Edit": "Wijzig",
+	"Edit Arena Model": "Bewerk Arena Model",
+	"Edit Memory": "Bewerk Geheugen",
+	"Edit User": "Wijzig Gebruiker",
+	"ElevenLabs": "",
+	"Email": "Email",
+	"Embedding Batch Size": "",
+	"Embedding Model": "Embedding Model",
+	"Embedding Model Engine": "Embedding Model Engine",
+	"Embedding model set to \"{{embedding_model}}\"": "Embedding model ingesteld op \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Delen via de community inschakelen",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Schakel Nieuwe Registraties in",
+	"Enable Web Search": "Zoeken op het web inschakelen",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "Ingeschakeld",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Zorg ervoor dat uw CSV-bestand de volgende vier kolommen in deze volgorde bevat: Naam, E-mail, Wachtwoord, Rol.",
+	"Enter {{role}} message here": "Voeg {{role}} bericht hier toe",
+	"Enter a detail about yourself for your LLMs to recall": "Voer een detail over jezelf in voor je LLMs om het her te onthouden",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Voer de Brave Search API-sleutel in",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Voeg Chunk Overlap toe",
+	"Enter Chunk Size": "Voeg Chunk Size toe",
+	"Enter description": "Voer beschrijving in",
+	"Enter Github Raw URL": "Voer de Github Raw-URL in",
+	"Enter Google PSE API Key": "Voer de Google PSE API-sleutel in",
+	"Enter Google PSE Engine Id": "Voer Google PSE Engine-ID in",
+	"Enter Image Size (e.g. 512x512)": "Voeg afbeelding formaat toe (Bijv. 512x512)",
+	"Enter language codes": "Voeg taal codes toe",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Voeg model tag toe (Bijv. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Voeg aantal stappen toe (Bijv. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Voeg score toe",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Voer de URL van de Searxng-query in",
+	"Enter Serper API Key": "Voer de Serper API-sleutel in",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "Voer de Serpstack API-sleutel in",
+	"Enter stop sequence": "Zet stop sequentie",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Voeg Top K toe",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Zet URL (Bijv. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Zet URL (Bijv. http://localhost:11434)",
+	"Enter Your Email": "Voer je Email in",
+	"Enter Your Full Name": "Voer je Volledige Naam in",
+	"Enter your message": "Voer je bericht in",
+	"Enter Your Password": "Voer je Wachtwoord in",
+	"Enter Your Role": "Voer je Rol in",
+	"Error": "Fout",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Experimenteel",
+	"Export": "Exporteren",
+	"Export All Chats (All Users)": "Exporteer Alle Chats (Alle Gebruikers)",
+	"Export chat (.json)": "",
+	"Export Chats": "Exporteer Chats",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "Modellen exporteren",
+	"Export Prompts": "Exporteer Prompts",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "Het is niet gelukt om het bestand toe te voegen.",
+	"Failed to create API Key.": "Kan API Key niet aanmaken.",
+	"Failed to read clipboard contents": "Kan klembord inhoud niet lezen",
+	"Failed to update settings": "Instellingen konden niet worden bijgewerkt.",
+	"Failed to upload file.": "Bestand kon niet worden geüpload.",
+	"February": "Februari",
+	"Feedback History": "",
+	"Feel free to add specific details": "Voeg specifieke details toe",
+	"File": "Bestand",
+	"File added successfully.": "Bestand succesvol toegevoegd.",
+	"File content updated successfully.": "Bestandsinhoud succesvol bijgewerkt.",
+	"File Mode": "Bestandsmodus",
+	"File not found.": "Bestand niet gevonden.",
+	"File removed successfully.": "Bestand succesvol verwijderd.",
+	"File size should not exceed {{maxSize}} MB.": "Bestandsgrootte mag niet groter zijn dan {{maxSize}} MB.",
+	"Files": "Bestanden",
+	"Filter is now globally disabled": "Filter is nu globaal uitgeschakeld",
+	"Filter is now globally enabled": "Filter is nu globaal ingeschakeld",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Vingerafdruk spoofing gedetecteerd: kan initialen niet gebruiken als avatar. Standaardprofielafbeelding wordt gebruikt.",
+	"Fluidly stream large external response chunks": "Stream vloeiend grote externe responsbrokken",
+	"Focus chat input": "Focus chat input",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Volgde instructies perfect",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Frequentie Straf",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Algemeen",
+	"General Settings": "Algemene Instellingen",
+	"Generate Image": "",
+	"Generating search query": "Zoekopdracht genereren",
+	"Generation Info": "Generatie Info",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "Goede Antwoord",
+	"Google PSE API Key": "Google PSE API-sleutel",
+	"Google PSE Engine Id": "Google PSE-engine-ID",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "heeft geen gesprekken.",
+	"Hello, {{name}}": "Hallo, {{name}}",
+	"Help": "Help",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Verberg",
+	"Hide Model": "",
+	"How can I help you today?": "Hoe kan ik je vandaag helpen?",
+	"Hybrid Search": "Hybride Zoeken",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Afbeelding Generatie (Experimenteel)",
+	"Image Generation Engine": "Afbeelding Generatie Engine",
+	"Image Settings": "Afbeelding Instellingen",
+	"Images": "Afbeeldingen",
+	"Import Chats": "Importeer Chats",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "Modellen importeren",
+	"Import Prompts": "Importeer Prompts",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Voeg `--api` vlag toe bij het uitvoeren van stable-diffusion-webui",
+	"Info": "Info",
+	"Input commands": "Voer commando's in",
+	"Install from Github URL": "Installeren vanaf Github-URL",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "Interface",
+	"Invalid file format.": "",
+	"Invalid Tag": "Ongeldige Tag",
+	"January": "Januari",
+	"join our Discord for help.": "join onze Discord voor hulp.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON-voorbeeld",
+	"July": "Juli",
+	"June": "Juni",
+	"JWT Expiration": "JWT Expiration",
+	"JWT Token": "JWT Token",
+	"Keep Alive": "Houd Actief",
+	"Keyboard shortcuts": "Toetsenbord snelkoppelingen",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Taal",
+	"large language models, locally.": "",
+	"Last Active": "Laatst Actief",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Licht",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "LLMs kunnen fouten maken. Verifieer belangrijke informatie.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Gemaakt door OpenWebUI Community",
+	"Make sure to enclose them with": "Zorg ervoor dat je ze omringt met",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "Beheer Modellen",
+	"Manage Ollama Models": "Beheer Ollama Modellen",
+	"Manage Pipelines": "Pijplijnen beheren",
+	"March": "Maart",
+	"Max Tokens (num_predict)": "Max Tokens (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maximaal 3 modellen kunnen tegelijkertijd worden gedownload. Probeer het later opnieuw.",
+	"May": "Mei",
+	"Memories accessible by LLMs will be shown here.": "Geheugen toegankelijk voor LLMs wordt hier getoond.",
+	"Memory": "Geheugen",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Berichten die u verzendt nadat u uw link hebt gemaakt, worden niet gedeeld. Gebruikers met de URL kunnen de gedeelde chat bekijken.",
+	"Min P": "",
+	"Minimum Score": "Minimale Score",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' is succesvol gedownload.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' staat al in de wachtrij voor downloaden.",
+	"Model {{modelId}} not found": "Model {{modelId}} niet gevonden",
+	"Model {{modelName}} is not vision capable": "Model {{modelName}} is niet geschikt voor visie",
+	"Model {{name}} is now {{status}}": "Model {{name}} is nu {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model filesystem path gedetecteerd. Model shortname is vereist voor update, kan niet doorgaan.",
+	"Model ID": "Model-ID",
+	"Model Name": "Model naam",
+	"Model not selected": "Model niet geselecteerd",
+	"Model Params": "Model Params",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Model Whitelisting",
+	"Model(s) Whitelisted": "Model(len) zijn ge-whitelist",
+	"Modelfile Content": "Modelfile Inhoud",
+	"Models": "Modellen",
+	"more": "Meer",
+	"More": "Meer",
+	"Move to Top": "Verplaats naar boven",
+	"Name": "Naam",
+	"Name your model": "Geef uw model een naam",
+	"New Chat": "Nieuwe Chat",
+	"New folder": "Nieuwe map",
+	"New Password": "Nieuw Wachtwoord",
+	"No content found": "Geen content gevonden",
+	"No content to speak": "Geen inhoud om over te spreken",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Geen bestand geselecteerd",
+	"No files found.": "Geen bestanden gevonden",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "Geen modellen gevonden",
+	"No results found": "Geen resultaten gevonden",
+	"No search query generated": "Geen zoekopdracht gegenereerd",
+	"No source available": "Geen bron beschikbaar",
+	"No valves to update": "",
+	"None": "Geen",
+	"Not factually correct": "Feitelijk niet juist",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Opmerking: Als u een minimumscore instelt, levert de zoekopdracht alleen documenten op met een score groter dan of gelijk aan de minimumscore.",
+	"Notes": "",
+	"Notifications": "Desktop Notificaties",
+	"November": "November",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "",
+	"October": "Oktober",
+	"Off": "Uit",
+	"Okay, Let's Go!": "Okay, Laten we gaan!",
+	"OLED Dark": "OLED Donker",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API uitgeschakeld",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Ollama Versie",
+	"On": "Aan",
+	"Only": "Alleen",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Alleen alfanumerieke karakters en streepjes zijn toegestaan in de commando string.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Het lijkt erop dat de URL ongeldig is. Controleer het nogmaals en probeer opnieuw.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! Je gebruikt een niet-ondersteunde methode (alleen frontend). Serveer de WebUI vanuit de backend.",
+	"Open file": "Open bestand",
+	"Open in full screen": "",
+	"Open new chat": "Open nieuwe chat",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API Config",
+	"OpenAI API Key is required.": "OpenAI API Sleutel is verplicht",
+	"OpenAI URL/Key required.": "OpenAI URL/Sleutel vereist.",
+	"or": "of",
+	"Other": "Andere",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "Overzicht",
+	"page": "Pagina",
+	"Password": "Wachtwoord",
+	"PDF document (.pdf)": "PDF document (.pdf)",
+	"PDF Extract Images (OCR)": "PDF Extract Afbeeldingen (OCR)",
+	"pending": "wachtend",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "Toestemming geweigerd bij toegang tot microfoon: {{error}}",
+	"Personalization": "Personalisatie",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "Pijpleidingen",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "Pijpleidingen Kleppen",
+	"Plain text (.txt)": "Platte tekst (.txt)",
+	"Playground": "Speeltuin",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Positieve positie",
+	"Previous 30 days": "Vorige 30 dagen",
+	"Previous 7 days": "Vorige 7 dagen",
+	"Profile Image": "Profielafbeelding",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (bijvoorbeeld: vertel me een leuke gebeurtenis over het Romeinse eeuw)",
+	"Prompt Content": "Prompt Inhoud",
+	"Prompt suggestions": "Prompt suggesties",
+	"Prompts": "Prompts",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Haal \"{{searchValue}}\" uit Ollama.com",
+	"Pull a model from Ollama.com": "Haal een model van Ollama.com",
+	"Query Params": "Query Params",
+	"RAG Template": "RAG Template",
+	"Rating": "Beoordeling",
+	"Re-rank models by topic similarity": "Herordeneer modellen op basis van onderwerpsovereenkomst",
+	"Read Aloud": "Voorlezen",
+	"Record voice": "Neem stem op",
+	"Redirecting you to OpenWebUI Community": "Je wordt doorgestuurd naar OpenWebUI Community",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "Geweigerd terwijl het niet had moeten",
+	"Regenerate": "Regenereren",
+	"Release Notes": "Release Notes",
+	"Relevance": "Relevantie",
+	"Remove": "Verwijderen",
+	"Remove Model": "Verwijder Model",
+	"Rename": "Hernoemen",
+	"Repeat Last N": "Herhaal Laatste N",
+	"Request Mode": "Request Modus",
+	"Reranking Model": "Reranking Model",
+	"Reranking model disabled": "Reranking model uitgeschakeld",
+	"Reranking model set to \"{{reranking_model}}\"": "Reranking model ingesteld op \"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Antwoord Automatisch Kopiëren naar Klembord",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "Antwoord splitsing",
+	"Result": "Resultaat",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rol",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "Opslaan",
+	"Save & Create": "Opslaan & Creëren",
+	"Save & Update": "Opslaan & Bijwerken",
+	"Save As Copy": "Bewaar als kopie",
+	"Save Tag": "Bewaar Tag",
+	"Saved": "Opgeslagen",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Chat logs direct opslaan in de opslag van je browser wordt niet langer ondersteund. Neem even de tijd om je chat logs te downloaden en te verwijderen door op de knop hieronder te klikken. Maak je geen zorgen, je kunt je chat logs eenvoudig opnieuw importeren naar de backend via",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Zoeken",
+	"Search a model": "Zoek een model",
+	"Search Chats": "Chats zoeken",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "Modellen zoeken",
+	"Search Prompts": "Zoek Prompts",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "Aantal zoekresultaten",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Gezocht op {{count}} sites_one",
+	"Searched {{count}} sites_other": "Gezocht op {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng Query URL",
+	"See readme.md for instructions": "Zie readme.md voor instructies",
+	"See what's new": "Zie wat er nieuw is",
+	"Seed": "Seed",
+	"Select a base model": "Selecteer een basismodel",
+	"Select a engine": "Selecteer een engine",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Selecteer een functie",
+	"Select a model": "Selecteer een model",
+	"Select a pipeline": "Selecteer een pijplijn",
+	"Select a pipeline url": "Selecteer een pijplijn-URL",
+	"Select a tool": "Selecteer een tool",
+	"Select an Ollama instance": "Selecteer een Ollama instantie",
+	"Select Engine": "Selecteer Engine",
+	"Select Knowledge": "Selecteer Kennis",
+	"Select model": "Selecteer een model",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "Geselecteerde modellen ondersteunen geen beeldinvoer",
+	"Semantic distance to query": "",
+	"Send": "Verzenden",
+	"Send a Message": "Stuur een Bericht",
+	"Send message": "Stuur bericht",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "September",
+	"Serper API Key": "Serper API-sleutel",
+	"Serply API Key": "",
+	"Serpstack API Key": "Serpstack API-sleutel",
+	"Server connection verified": "Server verbinding geverifieerd",
+	"Set as default": "Stel in als standaard",
+	"Set CFG Scale": "",
+	"Set Default Model": "Stel Standaard Model in",
+	"Set embedding model (e.g. {{model}})": "Stel embedding model in (bv. {{model}})",
+	"Set Image Size": "Stel Afbeelding Grootte in",
+	"Set reranking model (e.g. {{model}})": "Stel reranking model in (bv. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Stel Stappen in",
+	"Set Task Model": "Taakmodel instellen",
+	"Set Voice": "Stel Stem in",
+	"Set whisper model": "",
+	"Settings": "Instellingen",
+	"Settings saved successfully!": "Instellingen succesvol opgeslagen!",
+	"Share": "Deel Chat",
+	"Share Chat": "Deel Chat",
+	"Share to OpenWebUI Community": "Deel naar OpenWebUI Community",
+	"short-summary": "korte-samenvatting",
+	"Show": "Toon",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "Toon snelkoppelingen",
+	"Show your support!": "",
+	"Showcased creativity": "Tooncase creativiteit",
+	"Sign in": "Inloggen",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Uitloggen",
+	"Sign up": "Registreren",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Bron",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Spraakherkenning fout: {{error}}",
+	"Speech-to-Text Engine": "Spraak-naar-tekst Engine",
+	"Stop": "",
+	"Stop Sequence": "Stop Sequentie",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "STT Instellingen",
+	"Subtitle (e.g. about the Roman Empire)": "Ondertitel (bijv. over de Romeinse Empire)",
+	"Success": "Succes",
+	"Successfully updated.": "Succesvol bijgewerkt.",
+	"Suggested": "Suggestie",
+	"Support": "Ondersteuning",
+	"Support this plugin:": "ondersteun deze plugin",
+	"Sync directory": "Synchroniseer map",
+	"System": "Systeem",
+	"System Instructions": "Systeem instructies",
+	"System Prompt": "Systeem Prompt",
+	"Tags": "Tags",
+	"Tags Generation Prompt": "Prompt voor taggeneratie",
+	"Tap to interrupt": "Tik om te onderbreken",
+	"Tavily API Key": "",
+	"Tell us more:": "Vertel ons meer:",
+	"Temperature": "Temperatuur",
+	"Template": "Template",
+	"Temporary Chat": "Tijdelijke chat",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Tekst-naar-Spraak Engine",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Bedankt voor uw feedback!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Het score moet een waarde zijn tussen 0.0 (0%) en 1.0 (100%).",
+	"Theme": "Thema",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "Deze actie kan niet ongedaan worden gemaakt. Wilt u doorgaan?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dit zorgt ervoor dat je waardevolle gesprekken veilig worden opgeslagen in je backend database. Dank je wel!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dit is een experimentele functie, het kan functioneren zoals verwacht en kan op elk moment veranderen.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Deze optie verwijdert alle bestaande bestanden in de collectie en vervangt ze door nieuw geüploade bestanden.",
+	"This response was generated by \"{{model}}\"": "Dit antwoord is gegenereerd door  \"{{model}}\"",
+	"This will delete": "Dit zal verwijderen",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "Dit zal <strong>{{NAME}}</strong> verwijderen en <strong>al zijn inhoud</strong>.",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "Dit zal de kennisdatabase resetten en alle bestanden synchroniseren. Wilt u doorgaan?",
+	"Thorough explanation": "Gevorderde uitleg",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tip: Werk meerdere variabele slots achtereenvolgens bij door op de tab-toets te drukken in de chat input na elke vervanging.",
+	"Title": "Titel",
+	"Title (e.g. Tell me a fun fact)": "Titel (bv. Vertel me een leuke gebeurtenis)",
+	"Title Auto-Generation": "Titel Auto-Generatie",
+	"Title cannot be an empty string.": "Titel kan niet leeg zijn.",
+	"Title Generation Prompt": "Titel Generatie Prompt",
+	"To access the available model names for downloading,": "Om de beschikbare modelnamen voor downloaden te openen,",
+	"To access the GGUF models available for downloading,": "Om toegang te krijgen tot de GGUF modellen die beschikbaar zijn voor downloaden,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Om toegang te krijgen tot de WebUI, neem contact op met de administrator. Beheerders kunnen de gebruikersstatussen beheren vanuit het Beheerderspaneel.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "naar chat input.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "Vandaag",
+	"Toggle settings": "Wissel instellingen",
+	"Toggle sidebar": "Wissel sidebar",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problemen met toegang tot Ollama?",
+	"TTS Model": "",
+	"TTS Settings": "TTS instellingen",
+	"TTS Voice": "",
+	"Type": "Type",
+	"Type Hugging Face Resolve (Download) URL": "Type Hugging Face Resolve (Download) URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Er was een probleem met verbinden met {{provider}}.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "Update en Kopieer Link",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Wijzig wachtwoord",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Upload een GGUF model",
+	"Upload directory": "Upload map",
+	"Upload files": "Bestanden uploaden",
+	"Upload Files": "Bestanden uploaden",
+	"Upload Pipeline": "",
+	"Upload Progress": "Upload Voortgang",
+	"URL Mode": "URL Modus",
+	"Use '#' in the prompt input to load and include your knowledge.": "Gebruik '#' in de promptinvoer om je kennis te laden en op te nemen.",
+	"Use Gravatar": "Gebruik Gravatar",
+	"Use Initials": "Gebruik Initials",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "user",
+	"User": "User",
+	"User location successfully retrieved.": "",
+	"User Permissions": "Gebruikers Rechten",
+	"Users": "Gebruikers",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "Het standaard arena-model gebruiken met alle modellen. Klik op de plusknop om aangepaste modellen toe te voegen.",
+	"Utilize": "Utilize",
+	"Valid time units:": "Geldige tijdseenheden:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "variabele",
+	"variable to have them replaced with clipboard content.": "variabele om ze te laten vervangen door klembord inhoud.",
+	"Version": "Versie",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Stem",
+	"Voice Input": "Steminvoer",
+	"Warning": "Waarschuwing",
+	"Warning:": "Waarschuwing",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Warning: Als je de embedding model bijwerkt of wijzigt, moet je alle documenten opnieuw importeren.",
+	"Web": "Web",
+	"Web API": "",
+	"Web Loader Settings": "Web Loader instellingen",
+	"Web Search": "Zoeken op het web",
+	"Web Search Engine": "Zoekmachine op het web",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "WebUI Instellingen",
+	"WebUI will make requests to": "WebUI zal verzoeken doen naar",
+	"What’s New in": "Wat is nieuw in",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "Gewonnen",
+	"Workspace": "Werkruimte",
+	"Write a prompt suggestion (e.g. Who are you?)": "Schrijf een prompt suggestie (bijv. Wie ben je?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Schrijf een samenvatting in 50 woorden die [onderwerp of trefwoord] samenvat.",
+	"Write something...": "Schrijf iets...",
+	"Yesterday": "gisteren",
+	"You": "U",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Je kunt slechts met maximaal {{maxCount}} bestand(en) tegelijk chatten",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Je kunt je interacties met LLM's personaliseren door herinneringen toe te voegen via de 'Beheer'-knop hieronder, waardoor ze nuttiger en op maat gemaakt voor jou worden.",
+	"You cannot clone a base model": "U kunt een basismodel niet klonen",
+	"You cannot upload an empty file.": "Je kunt een leeg bestand niet uploaden.",
+	"You have no archived conversations.": "U heeft geen gearchiveerde gesprekken.",
+	"You have shared this chat": "U heeft dit gesprek gedeeld",
+	"You're a helpful assistant.": "Jij bent een behulpzame assistent.",
+	"You're now logged in.": "Je bent nu ingelogd.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube-laderinstellingen"
+}
diff --git a/src/lib/i18n/locales/pa-IN/translation.json b/src/lib/i18n/locales/pa-IN/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..1168d92d4a294d29b9da6fc858d5d94d060d9f55
--- /dev/null
+++ b/src/lib/i18n/locales/pa-IN/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'ਸ', 'ਮ', 'ਘੰ', 'ਦ', 'ਹਫ਼ਤਾ' ਜਾਂ '-1' ਬਿਨਾ ਮਿਆਦ ਦੇ।",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(ਉਦਾਹਰਣ ਦੇ ਤੌਰ ਤੇ `sh webui.sh --api`)",
+	"(latest)": "(ਤਾਜ਼ਾ)",
+	"{{ models }}": "{{ ਮਾਡਲ }}",
+	"{{ owner }}: You cannot delete a base model": "{{ ਮਾਲਕ }}: ਤੁਸੀਂ ਬੇਸ ਮਾਡਲ ਨੂੰ ਮਿਟਾ ਨਹੀਂ ਸਕਦੇ",
+	"{{user}}'s Chats": "{{user}} ਦੀਆਂ ਗੱਲਾਂ",
+	"{{webUIName}} Backend Required": "{{webUIName}} ਬੈਕਐਂਡ ਲੋੜੀਂਦਾ ਹੈ",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "ਚੈਟਾਂ ਅਤੇ ਵੈੱਬ ਖੋਜ ਪੁੱਛਗਿੱਛਾਂ ਵਾਸਤੇ ਸਿਰਲੇਖ ਤਿਆਰ ਕਰਨ ਵਰਗੇ ਕਾਰਜ ਾਂ ਨੂੰ ਕਰਦੇ ਸਮੇਂ ਇੱਕ ਕਾਰਜ ਮਾਡਲ ਦੀ ਵਰਤੋਂ ਕੀਤੀ ਜਾਂਦੀ ਹੈ",
+	"a user": "ਇੱਕ ਉਪਭੋਗਤਾ",
+	"About": "ਬਾਰੇ",
+	"Account": "ਖਾਤਾ",
+	"Account Activation Pending": "",
+	"Accurate information": "ਸਹੀ ਜਾਣਕਾਰੀ",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "ਸ਼ਾਮਲ ਕਰੋ",
+	"Add a model id": "ਇੱਕ ਮਾਡਲ ID ਸ਼ਾਮਲ ਕਰੋ",
+	"Add a short description about what this model does": "ਇਸ ਬਾਰੇ ਇੱਕ ਸੰਖੇਪ ਵੇਰਵਾ ਸ਼ਾਮਲ ਕਰੋ ਕਿ ਇਹ ਮਾਡਲ ਕੀ ਕਰਦਾ ਹੈ",
+	"Add a short title for this prompt": "ਇਸ ਪ੍ਰੰਪਟ ਲਈ ਇੱਕ ਛੋਟਾ ਸਿਰਲੇਖ ਸ਼ਾਮਲ ਕਰੋ",
+	"Add a tag": "ਇੱਕ ਟੈਗ ਸ਼ਾਮਲ ਕਰੋ",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "ਕਸਟਮ ਪ੍ਰੰਪਟ ਸ਼ਾਮਲ ਕਰੋ",
+	"Add Files": "ਫਾਈਲਾਂ ਸ਼ਾਮਲ ਕਰੋ",
+	"Add Memory": "ਮਿਹਾਨ ਸ਼ਾਮਲ ਕਰੋ",
+	"Add Model": "ਮਾਡਲ ਸ਼ਾਮਲ ਕਰੋ",
+	"Add Tag": "",
+	"Add Tags": "ਟੈਗ ਸ਼ਾਮਲ ਕਰੋ",
+	"Add text content": "",
+	"Add User": "ਉਪਭੋਗਤਾ ਸ਼ਾਮਲ ਕਰੋ",
+	"Adjusting these settings will apply changes universally to all users.": "ਇਹ ਸੈਟਿੰਗਾਂ ਨੂੰ ਠੀਕ ਕਰਨ ਨਾਲ ਸਾਰੇ ਉਪਭੋਗਤਾਵਾਂ ਲਈ ਬਦਲਾਅ ਲਾਗੂ ਹੋਣਗੇ।",
+	"admin": "ਪ੍ਰਬੰਧਕ",
+	"Admin": "",
+	"Admin Panel": "ਪ੍ਰਬੰਧਕ ਪੈਨਲ",
+	"Admin Settings": "ਪ੍ਰਬੰਧਕ ਸੈਟਿੰਗਾਂ",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "ਉੱਚ ਸਤਰ ਦੇ ਪੈਰਾਮੀਟਰ",
+	"Advanced Params": "ਐਡਵਾਂਸਡ ਪਰਮਜ਼",
+	"All chats": "",
+	"All Documents": "ਸਾਰੇ ਡਾਕੂਮੈਂਟ",
+	"Allow Chat Deletion": "ਗੱਲਬਾਤ ਮਿਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "ਅਲਫ਼ਾਨਯੂਮੈਰਿਕ ਅੱਖਰ ਅਤੇ ਹਾਈਫਨ",
+	"Already have an account?": "ਪਹਿਲਾਂ ਹੀ ਖਾਤਾ ਹੈ?",
+	"an assistant": "ਇੱਕ ਸਹਾਇਕ",
+	"and": "ਅਤੇ",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "ਅਤੇ ਇੱਕ ਨਵਾਂ ਸਾਂਝਾ ਲਿੰਕ ਬਣਾਓ।",
+	"API Base URL": "API ਬੇਸ URL",
+	"API Key": "API ਕੁੰਜੀ",
+	"API Key created.": "API ਕੁੰਜੀ ਬਣਾਈ ਗਈ।",
+	"API keys": "API ਕੁੰਜੀਆਂ",
+	"April": "ਅਪ੍ਰੈਲ",
+	"Archive": "ਆਰਕਾਈਵ",
+	"Archive All Chats": "ਸਾਰੀਆਂ ਚੈਟਾਂ ਨੂੰ ਆਰਕਾਈਵ ਕਰੋ",
+	"Archived Chats": "ਆਰਕਾਈਵ ਕੀਤੀਆਂ ਗੱਲਾਂ",
+	"are allowed - Activate this command by typing": "ਅਨੁਮਤ ਹਨ - ਇਸ ਕਮਾਂਡ ਨੂੰ ਟਾਈਪ ਕਰਕੇ ਸਰਗਰਮ ਕਰੋ",
+	"Are you sure?": "ਕੀ ਤੁਸੀਂ ਯਕੀਨਨ ਹੋ?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "ਫਾਈਲ ਜੋੜੋ",
+	"Attention to detail": "ਵੇਰਵੇ 'ਤੇ ਧਿਆਨ",
+	"Audio": "ਆਡੀਓ",
+	"August": "ਅਗਸਤ",
+	"Auto-playback response": "ਆਟੋ-ਪਲੇਬੈਕ ਜਵਾਬ",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 ਬੇਸ URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 ਬੇਸ URL ਦੀ ਲੋੜ ਹੈ।",
+	"Available list": "",
+	"available!": "ਉਪਲਬਧ ਹੈ!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "ਵਾਪਸ",
+	"Bad Response": "ਖਰਾਬ ਜਵਾਬ",
+	"Banners": "ਬੈਨਰ",
+	"Base Model (From)": "ਬੇਸ ਮਾਡਲ (ਤੋਂ)",
+	"Batch Size (num_batch)": "",
+	"before": "ਪਹਿਲਾਂ",
+	"Being lazy": "ਆਲਸੀ ਹੋਣਾ",
+	"Brave Search API Key": "ਬਹਾਦਰ ਖੋਜ API ਕੁੰਜੀ",
+	"Bypass SSL verification for Websites": "ਵੈਬਸਾਈਟਾਂ ਲਈ SSL ਪ੍ਰਮਾਣਿਕਤਾ ਨੂੰ ਬਾਈਪਾਸ ਕਰੋ",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "ਰੱਦ ਕਰੋ",
+	"Capabilities": "ਸਮਰੱਥਾਵਾਂ",
+	"Change Password": "ਪਾਸਵਰਡ ਬਦਲੋ",
+	"Character": "",
+	"Chat": "ਗੱਲਬਾਤ",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "ਗੱਲਬਾਤ ਬਬਲ UI",
+	"Chat Controls": "",
+	"Chat direction": "ਗੱਲਬਾਤ ਡਿਰੈਕਟਨ",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "ਗੱਲਾਂ",
+	"Check Again": "ਮੁੜ ਜਾਂਚ ਕਰੋ",
+	"Check for updates": "ਅੱਪਡੇਟ ਲਈ ਜਾਂਚ ਕਰੋ",
+	"Checking for updates...": "ਅੱਪਡੇਟ ਲਈ ਜਾਂਚ ਰਿਹਾ ਹੈ...",
+	"Choose a model before saving...": "ਸੰਭਾਲਣ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਮਾਡਲ ਚੁਣੋ...",
+	"Chunk Overlap": "ਚੰਕ ਓਵਰਲੈਪ",
+	"Chunk Params": "ਚੰਕ ਪੈਰਾਮੀਟਰ",
+	"Chunk Size": "ਚੰਕ ਆਕਾਰ",
+	"Citation": "ਹਵਾਲਾ",
+	"Clear memory": "",
+	"Click here for help.": "ਮਦਦ ਲਈ ਇੱਥੇ ਕਲਿੱਕ ਕਰੋ।",
+	"Click here to": "ਇੱਥੇ ਕਲਿੱਕ ਕਰੋ",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "ਚੁਣਨ ਲਈ ਇੱਥੇ ਕਲਿੱਕ ਕਰੋ",
+	"Click here to select a csv file.": "CSV ਫਾਈਲ ਚੁਣਨ ਲਈ ਇੱਥੇ ਕਲਿੱਕ ਕਰੋ।",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "ਇੱਥੇ ਕਲਿੱਕ ਕਰੋ।",
+	"Click on the user role button to change a user's role.": "ਉਪਭੋਗਤਾ ਦੀ ਭੂਮਿਕਾ ਬਦਲਣ ਲਈ ਉਪਭੋਗਤਾ ਭੂਮਿਕਾ ਬਟਨ 'ਤੇ ਕਲਿੱਕ ਕਰੋ।",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "ਕਲੋਨ",
+	"Close": "ਬੰਦ ਕਰੋ",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "ਸੰਗ੍ਰਹਿ",
+	"ComfyUI": "ਕੰਫੀਯੂਆਈ",
+	"ComfyUI Base URL": "ਕੰਫੀਯੂਆਈ ਬੇਸ URL",
+	"ComfyUI Base URL is required.": "ਕੰਫੀਯੂਆਈ ਬੇਸ URL ਦੀ ਲੋੜ ਹੈ।",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "ਕਮਾਂਡ",
+	"Completions": "",
+	"Concurrent Requests": "ਸਮਕਾਲੀ ਬੇਨਤੀਆਂ",
+	"Confirm": "",
+	"Confirm Password": "ਪਾਸਵਰਡ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ",
+	"Confirm your action": "",
+	"Connections": "ਕਨੈਕਸ਼ਨ",
+	"Contact Admin for WebUI Access": "",
+	"Content": "ਸਮੱਗਰੀ",
+	"Content Extraction": "",
+	"Context Length": "ਸੰਦਰਭ ਲੰਬਾਈ",
+	"Continue Response": "ਜਵਾਬ ਜਾਰੀ ਰੱਖੋ",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "ਸਾਂਝੇ ਕੀਤੇ ਗੱਲਬਾਤ URL ਨੂੰ ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਕਾਪੀ ਕਰ ਦਿੱਤਾ!",
+	"Copied to clipboard": "",
+	"Copy": "ਕਾਪੀ ਕਰੋ",
+	"Copy last code block": "ਆਖਰੀ ਕੋਡ ਬਲਾਕ ਨੂੰ ਕਾਪੀ ਕਰੋ",
+	"Copy last response": "ਆਖਰੀ ਜਵਾਬ ਨੂੰ ਕਾਪੀ ਕਰੋ",
+	"Copy Link": "ਲਿੰਕ ਕਾਪੀ ਕਰੋ",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਕਾਪੀ ਕਰਨਾ ਸਫਲ ਰਿਹਾ!",
+	"Create a model": "ਇੱਕ ਮਾਡਲ ਬਣਾਓ",
+	"Create Account": "ਖਾਤਾ ਬਣਾਓ",
+	"Create Knowledge": "",
+	"Create new key": "ਨਵੀਂ ਕੁੰਜੀ ਬਣਾਓ",
+	"Create new secret key": "ਨਵੀਂ ਗੁਪਤ ਕੁੰਜੀ ਬਣਾਓ",
+	"Created at": "ਤੇ ਬਣਾਇਆ ਗਿਆ",
+	"Created At": "ਤੇ ਬਣਾਇਆ ਗਿਆ",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "ਮੌਜੂਦਾ ਮਾਡਲ",
+	"Current Password": "ਮੌਜੂਦਾ ਪਾਸਵਰਡ",
+	"Custom": "ਕਸਟਮ",
+	"Customize models for a specific purpose": "ਕਿਸੇ ਖਾਸ ਉਦੇਸ਼ ਲਈ ਮਾਡਲਾਂ ਨੂੰ ਅਨੁਕੂਲਿਤ ਕਰੋ",
+	"Dark": "ਗੂੜ੍ਹਾ",
+	"Dashboard": "",
+	"Database": "ਡਾਟਾਬੇਸ",
+	"December": "ਦਸੰਬਰ",
+	"Default": "ਮੂਲ",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "ਮੂਲ (ਸੈਂਟੈਂਸਟ੍ਰਾਂਸਫਾਰਮਰਸ)",
+	"Default Model": "ਡਿਫਾਲਟ ਮਾਡਲ",
+	"Default model updated": "ਮੂਲ ਮਾਡਲ ਅੱਪਡੇਟ ਕੀਤਾ ਗਿਆ",
+	"Default Prompt Suggestions": "ਮੂਲ ਪ੍ਰੰਪਟ ਸੁਝਾਅ",
+	"Default User Role": "ਮੂਲ ਉਪਭੋਗਤਾ ਭੂਮਿਕਾ",
+	"Delete": "ਮਿਟਾਓ",
+	"Delete a model": "ਇੱਕ ਮਾਡਲ ਮਿਟਾਓ",
+	"Delete All Chats": "ਸਾਰੀਆਂ ਚੈਟਾਂ ਨੂੰ ਮਿਟਾਓ",
+	"Delete chat": "ਗੱਲਬਾਤ ਮਿਟਾਓ",
+	"Delete Chat": "ਗੱਲਬਾਤ ਮਿਟਾਓ",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "ਇਸ ਲਿੰਕ ਨੂੰ ਮਿਟਾਓ",
+	"Delete tool?": "",
+	"Delete User": "ਉਪਭੋਗਤਾ ਮਿਟਾਓ",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} ਮਿਟਾਇਆ ਗਿਆ",
+	"Deleted {{name}}": "ਮਿਟਾ ਦਿੱਤਾ ਗਿਆ {{name}}",
+	"Description": "ਵਰਣਨਾ",
+	"Didn't fully follow instructions": "ਹਦਾਇਤਾਂ ਨੂੰ ਪੂਰੀ ਤਰ੍ਹਾਂ ਫਾਲੋ ਨਹੀਂ ਕੀਤਾ",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "ਇੱਕ ਮਾਡਲ ਲੱਭੋ",
+	"Discover a prompt": "ਇੱਕ ਪ੍ਰੰਪਟ ਖੋਜੋ",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "ਕਸਟਮ ਪ੍ਰੰਪਟਾਂ ਨੂੰ ਖੋਜੋ, ਡਾਊਨਲੋਡ ਕਰੋ ਅਤੇ ਪੜਚੋਲ ਕਰੋ",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "ਮਾਡਲ ਪ੍ਰੀਸੈਟਾਂ ਨੂੰ ਖੋਜੋ, ਡਾਊਨਲੋਡ ਕਰੋ ਅਤੇ ਪੜਚੋਲ ਕਰੋ",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "ਗੱਲਬਾਤ 'ਚ ਤੁਹਾਡੇ ਸਥਾਨ 'ਤੇ ਉਪਭੋਗਤਾ ਨਾਮ ਦਿਖਾਓ",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "ਡਾਕੂਮੈਂਟ",
+	"Documentation": "",
+	"Documents": "ਡਾਕੂਮੈਂਟ",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "ਕੋਈ ਬਾਹਰੀ ਕਨੈਕਸ਼ਨ ਨਹੀਂ ਬਣਾਉਂਦਾ, ਅਤੇ ਤੁਹਾਡਾ ਡਾਟਾ ਤੁਹਾਡੇ ਸਥਾਨਕ ਸਰਵਰ 'ਤੇ ਸੁਰੱਖਿਅਤ ਰਹਿੰਦਾ ਹੈ।",
+	"Don't have an account?": "ਖਾਤਾ ਨਹੀਂ ਹੈ?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "ਸਟਾਈਲ ਪਸੰਦ ਨਹੀਂ ਹੈ",
+	"Done": "",
+	"Download": "ਡਾਊਨਲੋਡ",
+	"Download canceled": "ਡਾਊਨਲੋਡ ਰੱਦ ਕੀਤਾ ਗਿਆ",
+	"Download Database": "ਡਾਟਾਬੇਸ ਡਾਊਨਲੋਡ ਕਰੋ",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "ਗੱਲਬਾਤ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਕੋਈ ਵੀ ਫਾਈਲ ਇੱਥੇ ਛੱਡੋ",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "ਉਦਾਹਰਣ ਲਈ '30ਸ','10ਮਿ'. ਸਹੀ ਸਮਾਂ ਇਕਾਈਆਂ ਹਨ 'ਸ', 'ਮ', 'ਘੰ'.",
+	"Edit": "ਸੰਪਾਦਨ ਕਰੋ",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "ਉਪਭੋਗਤਾ ਸੰਪਾਦਨ ਕਰੋ",
+	"ElevenLabs": "",
+	"Email": "ਈਮੇਲ",
+	"Embedding Batch Size": "",
+	"Embedding Model": "ਐਮਬੈੱਡਿੰਗ ਮਾਡਲ",
+	"Embedding Model Engine": "ਐਮਬੈੱਡਿੰਗ ਮਾਡਲ ਇੰਜਣ",
+	"Embedding model set to \"{{embedding_model}}\"": "ਐਮਬੈੱਡਿੰਗ ਮਾਡਲ ਨੂੰ \"{{embedding_model}}\" 'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ",
+	"Enable Community Sharing": "ਕਮਿਊਨਿਟੀ ਸ਼ੇਅਰਿੰਗ ਨੂੰ ਸਮਰੱਥ ਕਰੋ",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "ਨਵੇਂ ਸਾਈਨ ਅਪ ਯੋਗ ਕਰੋ",
+	"Enable Web Search": "ਵੈੱਬ ਖੋਜ ਨੂੰ ਸਮਰੱਥ ਕਰੋ",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "ਸੁਨਿਸ਼ਚਿਤ ਕਰੋ ਕਿ ਤੁਹਾਡੀ CSV ਫਾਈਲ ਵਿੱਚ ਇਸ ਕ੍ਰਮ ਵਿੱਚ 4 ਕਾਲਮ ਹਨ: ਨਾਮ, ਈਮੇਲ, ਪਾਸਵਰਡ, ਭੂਮਿਕਾ।",
+	"Enter {{role}} message here": "{{role}} ਸੁਨੇਹਾ ਇੱਥੇ ਦਰਜ ਕਰੋ",
+	"Enter a detail about yourself for your LLMs to recall": "ਤੁਹਾਡੇ LLMs ਨੂੰ ਸੁਨੇਹਾ ਕਰਨ ਲਈ ਸੁਨੇਹਾ ਇੱਥੇ ਦਰਜ ਕਰੋ",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "ਬਹਾਦਰ ਖੋਜ API ਕੁੰਜੀ ਦਾਖਲ ਕਰੋ",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "ਚੰਕ ਓਵਰਲੈਪ ਦਰਜ ਕਰੋ",
+	"Enter Chunk Size": "ਚੰਕ ਆਕਾਰ ਦਰਜ ਕਰੋ",
+	"Enter description": "",
+	"Enter Github Raw URL": "Github ਕੱਚਾ URL ਦਾਖਲ ਕਰੋ",
+	"Enter Google PSE API Key": "Google PSE API ਕੁੰਜੀ ਦਾਖਲ ਕਰੋ",
+	"Enter Google PSE Engine Id": "Google PSE ਇੰਜਣ ID ਦਾਖਲ ਕਰੋ",
+	"Enter Image Size (e.g. 512x512)": "ਚਿੱਤਰ ਆਕਾਰ ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ 512x512)",
+	"Enter language codes": "ਭਾਸ਼ਾ ਕੋਡ ਦਰਜ ਕਰੋ",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "ਮਾਡਲ ਟੈਗ ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "ਕਦਮਾਂ ਦੀ ਗਿਣਤੀ ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "ਸਕੋਰ ਦਰਜ ਕਰੋ",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Searxng Query URL ਦਾਖਲ ਕਰੋ",
+	"Enter Serper API Key": "Serper API ਕੁੰਜੀ ਦਾਖਲ ਕਰੋ",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "Serpstack API ਕੁੰਜੀ ਦਾਖਲ ਕਰੋ",
+	"Enter stop sequence": "ਰੋਕਣ ਦਾ ਕ੍ਰਮ ਦਰਜ ਕਰੋ",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "ਸਿਖਰ K ਦਰਜ ਕਰੋ",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "URL ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "URL ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ http://localhost:11434)",
+	"Enter Your Email": "ਆਪਣੀ ਈਮੇਲ ਦਰਜ ਕਰੋ",
+	"Enter Your Full Name": "ਆਪਣਾ ਪੂਰਾ ਨਾਮ ਦਰਜ ਕਰੋ",
+	"Enter your message": "",
+	"Enter Your Password": "ਆਪਣਾ ਪਾਸਵਰਡ ਦਰਜ ਕਰੋ",
+	"Enter Your Role": "ਆਪਣੀ ਭੂਮਿਕਾ ਦਰਜ ਕਰੋ",
+	"Error": "ਗਲਤੀ",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "ਪਰਮਾਣੂਕ੍ਰਿਤ",
+	"Export": "ਨਿਰਯਾਤ",
+	"Export All Chats (All Users)": "ਸਾਰੀਆਂ ਗੱਲਾਂ ਨਿਰਯਾਤ ਕਰੋ (ਸਾਰੇ ਉਪਭੋਗਤਾ)",
+	"Export chat (.json)": "",
+	"Export Chats": "ਗੱਲਾਂ ਨਿਰਯਾਤ ਕਰੋ",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "ਨਿਰਯਾਤ ਮਾਡਲ",
+	"Export Prompts": "ਪ੍ਰੰਪਟ ਨਿਰਯਾਤ ਕਰੋ",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "API ਕੁੰਜੀ ਬਣਾਉਣ ਵਿੱਚ ਅਸਫਲ।",
+	"Failed to read clipboard contents": "ਕਲਿੱਪਬੋਰਡ ਸਮੱਗਰੀ ਪੜ੍ਹਣ ਵਿੱਚ ਅਸਫਲ",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "ਫਰਵਰੀ",
+	"Feedback History": "",
+	"Feel free to add specific details": "ਖੁੱਲ੍ਹੇ ਦਿਲ ਨਾਲ ਖਾਸ ਵੇਰਵੇ ਸ਼ਾਮਲ ਕਰੋ",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "ਫਾਈਲ ਮੋਡ",
+	"File not found.": "ਫਾਈਲ ਨਹੀਂ ਮਿਲੀ।",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "ਫਿੰਗਰਪ੍ਰਿੰਟ ਸਪੂਫਿੰਗ ਪਾਈ ਗਈ: ਅਵਤਾਰ ਵਜੋਂ ਸ਼ੁਰੂਆਤੀ ਅੱਖਰ ਵਰਤਣ ਵਿੱਚ ਅਸਮਰੱਥ। ਮੂਲ ਪ੍ਰੋਫਾਈਲ ਚਿੱਤਰ 'ਤੇ ਡਿਫਾਲਟ।",
+	"Fluidly stream large external response chunks": "ਵੱਡੇ ਬਾਹਰੀ ਜਵਾਬ ਚੰਕਾਂ ਨੂੰ ਸਹੀ ਢੰਗ ਨਾਲ ਸਟ੍ਰੀਮ ਕਰੋ",
+	"Focus chat input": "ਗੱਲਬਾਤ ਇਨਪੁਟ 'ਤੇ ਧਿਆਨ ਦਿਓ",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "ਹਦਾਇਤਾਂ ਨੂੰ ਬਿਲਕੁਲ ਫਾਲੋ ਕੀਤਾ",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "ਬਾਰੰਬਾਰਤਾ ਜੁਰਮਾਨਾ",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "ਆਮ",
+	"General Settings": "ਆਮ ਸੈਟਿੰਗਾਂ",
+	"Generate Image": "",
+	"Generating search query": "ਖੋਜ ਪੁੱਛਗਿੱਛ ਤਿਆਰ ਕਰਨਾ",
+	"Generation Info": "ਜਨਰੇਸ਼ਨ ਜਾਣਕਾਰੀ",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "ਵਧੀਆ ਜਵਾਬ",
+	"Google PSE API Key": "Google PSE API ਕੁੰਜੀ",
+	"Google PSE Engine Id": "ਗੂਗਲ PSE ਇੰਜਣ ID",
+	"h:mm a": "ਹ:ਮਿੰਟ ਪੂਃ",
+	"Haptic Feedback": "",
+	"has no conversations.": "ਕੋਈ ਗੱਲਬਾਤ ਨਹੀਂ ਹੈ।",
+	"Hello, {{name}}": "ਸਤ ਸ੍ਰੀ ਅਕਾਲ, {{name}}",
+	"Help": "ਮਦਦ",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "ਲੁਕਾਓ",
+	"Hide Model": "",
+	"How can I help you today?": "ਮੈਂ ਅੱਜ ਤੁਹਾਡੀ ਕਿਵੇਂ ਮਦਦ ਕਰ ਸਕਦਾ ਹਾਂ?",
+	"Hybrid Search": "ਹਾਈਬ੍ਰਿਡ ਖੋਜ",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "ਚਿੱਤਰ ਜਨਰੇਸ਼ਨ (ਪਰਮਾਣੂਕ੍ਰਿਤ)",
+	"Image Generation Engine": "ਚਿੱਤਰ ਜਨਰੇਸ਼ਨ ਇੰਜਣ",
+	"Image Settings": "ਚਿੱਤਰ ਸੈਟਿੰਗਾਂ",
+	"Images": "ਚਿੱਤਰ",
+	"Import Chats": "ਗੱਲਾਂ ਆਯਾਤ ਕਰੋ",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "ਮਾਡਲ ਆਯਾਤ ਕਰੋ",
+	"Import Prompts": "ਪ੍ਰੰਪਟ ਆਯਾਤ ਕਰੋ",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "ਸਟੇਬਲ-ਡਿਫਿਊਸ਼ਨ-ਵੈਬਯੂਆਈ ਚਲਾਉਣ ਸਮੇਂ `--api` ਝੰਡਾ ਸ਼ਾਮਲ ਕਰੋ",
+	"Info": "ਜਾਣਕਾਰੀ",
+	"Input commands": "ਇਨਪੁਟ ਕਮਾਂਡਾਂ",
+	"Install from Github URL": "Github URL ਤੋਂ ਇੰਸਟਾਲ ਕਰੋ",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "ਇੰਟਰਫੇਸ",
+	"Invalid file format.": "",
+	"Invalid Tag": "ਗਲਤ ਟੈਗ",
+	"January": "ਜਨਵਰੀ",
+	"join our Discord for help.": "ਮਦਦ ਲਈ ਸਾਡੇ ਡਿਸਕੋਰਡ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਵੋ।",
+	"JSON": "JSON",
+	"JSON Preview": "JSON ਪੂਰਵ-ਦਰਸ਼ਨ",
+	"July": "ਜੁਲਾਈ",
+	"June": "ਜੂਨ",
+	"JWT Expiration": "JWT ਮਿਆਦ ਖਤਮ",
+	"JWT Token": "JWT ਟੋਕਨ",
+	"Keep Alive": "ਜੀਵਿਤ ਰੱਖੋ",
+	"Keyboard shortcuts": "ਕੀਬੋਰਡ ਸ਼ਾਰਟਕਟ",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "ਭਾਸ਼ਾ",
+	"large language models, locally.": "",
+	"Last Active": "ਆਖਰੀ ਸਰਗਰਮ",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "ਹਲਕਾ",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "LLMs ਗਲਤੀਆਂ ਕਰ ਸਕਦੇ ਹਨ। ਮਹੱਤਵਪੂਰਨ ਜਾਣਕਾਰੀ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ।",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "ਓਪਨਵੈਬਯੂਆਈ ਕਮਿਊਨਿਟੀ ਦੁਆਰਾ ਬਣਾਇਆ ਗਿਆ",
+	"Make sure to enclose them with": "ਸੁਨਿਸ਼ਚਿਤ ਕਰੋ ਕਿ ਉਨ੍ਹਾਂ ਨੂੰ ਘੇਰੋ",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "ਮਾਡਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ",
+	"Manage Ollama Models": "ਓਲਾਮਾ ਮਾਡਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ",
+	"Manage Pipelines": "ਪਾਈਪਲਾਈਨਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ",
+	"March": "ਮਾਰਚ",
+	"Max Tokens (num_predict)": "ਮੈਕਸ ਟੋਕਨ (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "ਇੱਕ ਸਮੇਂ ਵਿੱਚ ਵੱਧ ਤੋਂ ਵੱਧ 3 ਮਾਡਲ ਡਾਊਨਲੋਡ ਕੀਤੇ ਜਾ ਸਕਦੇ ਹਨ। ਕਿਰਪਾ ਕਰਕੇ ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।",
+	"May": "ਮਈ",
+	"Memories accessible by LLMs will be shown here.": "LLMs ਲਈ ਸਮਰੱਥ ਕਾਰਨ ਇੱਕ ਸੂਚਨਾ ਨੂੰ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ।",
+	"Memory": "ਮੀਮਰ",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "ਤੁਹਾਡਾ ਲਿੰਕ ਬਣਾਉਣ ਤੋਂ ਬਾਅਦ ਤੁਹਾਡੇ ਵੱਲੋਂ ਭੇਜੇ ਗਏ ਸੁਨੇਹੇ ਸਾਂਝੇ ਨਹੀਂ ਕੀਤੇ ਜਾਣਗੇ। URL ਵਾਲੇ ਉਪਭੋਗਤਾ ਸਾਂਝੀ ਚੈਟ ਨੂੰ ਵੇਖ ਸਕਣਗੇ।",
+	"Min P": "",
+	"Minimum Score": "ਘੱਟੋ-ਘੱਟ ਸਕੋਰ",
+	"Mirostat": "ਮਿਰੋਸਟੈਟ",
+	"Mirostat Eta": "ਮਿਰੋਸਟੈਟ ਈਟਾ",
+	"Mirostat Tau": "ਮਿਰੋਸਟੈਟ ਟਾਉ",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "ਮਾਡਲ '{{modelName}}' ਸਫਲਤਾਪੂਰਵਕ ਡਾਊਨਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ।",
+	"Model '{{modelTag}}' is already in queue for downloading.": "ਮਾਡਲ '{{modelTag}}' ਪਹਿਲਾਂ ਹੀ ਡਾਊਨਲੋਡ ਲਈ ਕਤਾਰ ਵਿੱਚ ਹੈ।",
+	"Model {{modelId}} not found": "ਮਾਡਲ {{modelId}} ਨਹੀਂ ਮਿਲਿਆ",
+	"Model {{modelName}} is not vision capable": "ਮਾਡਲ {{modelName}} ਦ੍ਰਿਸ਼ਟੀ ਸਮਰੱਥ ਨਹੀਂ ਹੈ",
+	"Model {{name}} is now {{status}}": "ਮਾਡਲ {{name}} ਹੁਣ {{status}} ਹੈ",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "ਮਾਡਲ ਫਾਈਲਸਿਸਟਮ ਪੱਥ ਪਾਇਆ ਗਿਆ। ਅੱਪਡੇਟ ਲਈ ਮਾਡਲ ਸ਼ੌਰਟਨੇਮ ਦੀ ਲੋੜ ਹੈ, ਜਾਰੀ ਨਹੀਂ ਰੱਖ ਸਕਦੇ।",
+	"Model ID": "ਮਾਡਲ ID",
+	"Model Name": "",
+	"Model not selected": "ਮਾਡਲ ਚੁਣਿਆ ਨਹੀਂ ਗਿਆ",
+	"Model Params": "ਮਾਡਲ ਪਰਮਜ਼",
+	"Model updated successfully": "",
+	"Model Whitelisting": "ਮਾਡਲ ਵ੍ਹਾਈਟਲਿਸਟਿੰਗ",
+	"Model(s) Whitelisted": "ਮਾਡਲ(ਜ਼) ਵ੍ਹਾਈਟਲਿਸਟ ਕੀਤਾ ਗਿਆ",
+	"Modelfile Content": "ਮਾਡਲਫਾਈਲ ਸਮੱਗਰੀ",
+	"Models": "ਮਾਡਲ",
+	"more": "",
+	"More": "ਹੋਰ",
+	"Move to Top": "",
+	"Name": "ਨਾਮ",
+	"Name your model": "ਆਪਣੇ ਮਾਡਲ ਦਾ ਨਾਮ ਦੱਸੋ",
+	"New Chat": "ਨਵੀਂ ਗੱਲਬਾਤ",
+	"New folder": "",
+	"New Password": "ਨਵਾਂ ਪਾਸਵਰਡ",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "ਕੋਈ ਨਤੀਜੇ ਨਹੀਂ ਮਿਲੇ",
+	"No search query generated": "ਕੋਈ ਖੋਜ ਪੁੱਛਗਿੱਛ ਤਿਆਰ ਨਹੀਂ ਕੀਤੀ ਗਈ",
+	"No source available": "ਕੋਈ ਸਰੋਤ ਉਪਲਬਧ ਨਹੀਂ",
+	"No valves to update": "",
+	"None": "ਕੋਈ ਨਹੀਂ",
+	"Not factually correct": "ਤੱਥਕ ਰੂਪ ਵਿੱਚ ਸਹੀ ਨਹੀਂ",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "ਨੋਟ: ਜੇ ਤੁਸੀਂ ਘੱਟੋ-ਘੱਟ ਸਕੋਰ ਸੈੱਟ ਕਰਦੇ ਹੋ, ਤਾਂ ਖੋਜ ਸਿਰਫ਼ ਉਹੀ ਡਾਕੂਮੈਂਟ ਵਾਪਸ ਕਰੇਗੀ ਜਿਨ੍ਹਾਂ ਦਾ ਸਕੋਰ ਘੱਟੋ-ਘੱਟ ਸਕੋਰ ਦੇ ਬਰਾਬਰ ਜਾਂ ਵੱਧ ਹੋਵੇ।",
+	"Notes": "",
+	"Notifications": "ਸੂਚਨਾਵਾਂ",
+	"November": "ਨਵੰਬਰ",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (ਓਲਾਮਾ)",
+	"OAuth ID": "",
+	"October": "ਅਕਤੂਬਰ",
+	"Off": "ਬੰਦ",
+	"Okay, Let's Go!": "ਠੀਕ ਹੈ, ਚੱਲੋ ਚੱਲੀਏ!",
+	"OLED Dark": "OLED ਗੂੜ੍ਹਾ",
+	"Ollama": "ਓਲਾਮਾ",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API ਅਸਮਰੱਥ",
+	"Ollama API is disabled": "",
+	"Ollama Version": "ਓਲਾਮਾ ਵਰਜਨ",
+	"On": "ਚਾਲੂ",
+	"Only": "ਸਿਰਫ਼",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "ਕਮਾਂਡ ਸਤਰ ਵਿੱਚ ਸਿਰਫ਼ ਅਲਫ਼ਾਨਯੂਮੈਰਿਕ ਅੱਖਰ ਅਤੇ ਹਾਈਫਨ ਦੀ ਆਗਿਆ ਹੈ।",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "ਓਹੋ! ਲੱਗਦਾ ਹੈ ਕਿ URL ਗਲਤ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਜਾਂਚ ਕਰੋ ਅਤੇ ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ।",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "ਓਹੋ! ਤੁਸੀਂ ਇੱਕ ਅਣਸਮਰਥਿਤ ਢੰਗ ਵਰਤ ਰਹੇ ਹੋ (ਸਿਰਫ਼ ਫਰੰਟਐਂਡ)। ਕਿਰਪਾ ਕਰਕੇ ਵੈਬਯੂਆਈ ਨੂੰ ਬੈਕਐਂਡ ਤੋਂ ਸਰਵ ਕਰੋ।",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "ਨਵੀਂ ਗੱਲਬਾਤ ਖੋਲ੍ਹੋ",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "ਓਪਨਏਆਈ",
+	"OpenAI API": "ਓਪਨਏਆਈ API",
+	"OpenAI API Config": "ਓਪਨਏਆਈ API ਕਨਫਿਗ",
+	"OpenAI API Key is required.": "ਓਪਨਏਆਈ API ਕੁੰਜੀ ਦੀ ਲੋੜ ਹੈ।",
+	"OpenAI URL/Key required.": "ਓਪਨਏਆਈ URL/ਕੁੰਜੀ ਦੀ ਲੋੜ ਹੈ।",
+	"or": "ਜਾਂ",
+	"Other": "ਹੋਰ",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "ਪਾਸਵਰਡ",
+	"PDF document (.pdf)": "PDF ਡਾਕੂਮੈਂਟ (.pdf)",
+	"PDF Extract Images (OCR)": "PDF ਚਿੱਤਰ ਕੱਢੋ (OCR)",
+	"pending": "ਬਕਾਇਆ",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਤੱਕ ਪਹੁੰਚਣ ਸਮੇਂ ਆਗਿਆ ਰੱਦ ਕੀਤੀ ਗਈ: {{error}}",
+	"Personalization": "ਪਰਸੋਨਲਿਸ਼ਮ",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "ਪਾਈਪਲਾਈਨਾਂ",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "ਪਾਈਪਲਾਈਨਾਂ ਵਾਲਵ",
+	"Plain text (.txt)": "ਸਧਾਰਨ ਪਾਠ (.txt)",
+	"Playground": "ਖੇਡ ਦਾ ਮੈਦਾਨ",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "ਸਕਾਰਾਤਮਕ ਰਵੱਈਆ",
+	"Previous 30 days": "ਪਿਛਲੇ 30 ਦਿਨ",
+	"Previous 7 days": "ਪਿਛਲੇ 7 ਦਿਨ",
+	"Profile Image": "ਪ੍ਰੋਫਾਈਲ ਚਿੱਤਰ",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "ਪ੍ਰੰਪਟ (ਉਦਾਹਰਣ ਲਈ ਮੈਨੂੰ ਰੋਮਨ ਸਾਮਰਾਜ ਬਾਰੇ ਇੱਕ ਮਜ਼ੇਦਾਰ ਤੱਥ ਦੱਸੋ)",
+	"Prompt Content": "ਪ੍ਰੰਪਟ ਸਮੱਗਰੀ",
+	"Prompt suggestions": "ਪ੍ਰੰਪਟ ਸੁਝਾਅ",
+	"Prompts": "ਪ੍ਰੰਪਟ",
+	"Pull \"{{searchValue}}\" from Ollama.com": "ਓਲਾਮਾ.ਕਾਮ ਤੋਂ \"{{searchValue}}\" ਖਿੱਚੋ",
+	"Pull a model from Ollama.com": "ਓਲਾਮਾ.ਕਾਮ ਤੋਂ ਇੱਕ ਮਾਡਲ ਖਿੱਚੋ",
+	"Query Params": "ਪ੍ਰਸ਼ਨ ਪੈਰਾਮੀਟਰ",
+	"RAG Template": "RAG ਟੈਮਪਲੇਟ",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "ਜੋਰ ਨਾਲ ਪੜ੍ਹੋ",
+	"Record voice": "ਆਵਾਜ਼ ਰਿਕਾਰਡ ਕਰੋ",
+	"Redirecting you to OpenWebUI Community": "ਤੁਹਾਨੂੰ ਓਪਨਵੈਬਯੂਆਈ ਕਮਿਊਨਿਟੀ ਵੱਲ ਰੀਡਾਇਰੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "ਜਦੋਂ ਇਹ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ ਸੀ ਤਾਂ ਇਨਕਾਰ ਕੀਤਾ",
+	"Regenerate": "ਮੁੜ ਬਣਾਓ",
+	"Release Notes": "ਰਿਲੀਜ਼ ਨੋਟਸ",
+	"Relevance": "",
+	"Remove": "ਹਟਾਓ",
+	"Remove Model": "ਮਾਡਲ ਹਟਾਓ",
+	"Rename": "ਨਾਮ ਬਦਲੋ",
+	"Repeat Last N": "ਆਖਰੀ N ਨੂੰ ਦੁਹਰਾਓ",
+	"Request Mode": "ਬੇਨਤੀ ਮੋਡ",
+	"Reranking Model": "ਮਾਡਲ ਮੁੜ ਰੈਂਕਿੰਗ",
+	"Reranking model disabled": "ਮਾਡਲ ਮੁੜ ਰੈਂਕਿੰਗ ਅਯੋਗ ਕੀਤਾ ਗਿਆ",
+	"Reranking model set to \"{{reranking_model}}\"": "ਮਾਡਲ ਮੁੜ ਰੈਂਕਿੰਗ ਨੂੰ \"{{reranking_model}}\" 'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "ਜਵਾਬ ਆਟੋ ਕਾਪੀ ਕਲਿੱਪਬੋਰਡ 'ਤੇ",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "ਭੂਮਿਕਾ",
+	"Rosé Pine": "ਰੋਜ਼ ਪਾਈਨ",
+	"Rosé Pine Dawn": "ਰੋਜ਼ ਪਾਈਨ ਡਾਨ",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "ਸੰਭਾਲੋ",
+	"Save & Create": "ਸੰਭਾਲੋ ਅਤੇ ਬਣਾਓ",
+	"Save & Update": "ਸੰਭਾਲੋ ਅਤੇ ਅੱਪਡੇਟ ਕਰੋ",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "ਤੁਹਾਡੇ ਬ੍ਰਾਊਜ਼ਰ ਦੇ ਸਟੋਰੇਜ ਵਿੱਚ ਸਿੱਧੇ ਗੱਲਬਾਤ ਲੌਗ ਸੰਭਾਲਣਾ ਹੁਣ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਹੇਠਾਂ ਦਿੱਤੇ ਬਟਨ 'ਤੇ ਕਲਿੱਕ ਕਰਕੇ ਆਪਣੇ ਗੱਲਬਾਤ ਲੌਗ ਡਾਊਨਲੋਡ ਅਤੇ ਮਿਟਾਉਣ ਲਈ ਕੁਝ ਸਮਾਂ ਲਓ। ਚਿੰਤਾ ਨਾ ਕਰੋ, ਤੁਸੀਂ ਆਪਣੇ ਗੱਲਬਾਤ ਲੌਗ ਨੂੰ ਬੈਕਐਂਡ ਵਿੱਚ ਆਸਾਨੀ ਨਾਲ ਮੁੜ ਆਯਾਤ ਕਰ ਸਕਦੇ ਹੋ",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "ਖੋਜ",
+	"Search a model": "ਇੱਕ ਮਾਡਲ ਖੋਜੋ",
+	"Search Chats": "ਖੋਜ ਚੈਟਾਂ",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "ਖੋਜ ਮਾਡਲ",
+	"Search Prompts": "ਪ੍ਰੰਪਟ ਖੋਜੋ",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "ਖੋਜ ਨਤੀਜੇ ਦੀ ਗਿਣਤੀ",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "ਖੋਜਿਆ {{count}} sites_one",
+	"Searched {{count}} sites_other": "ਖੋਜਿਆ {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng Query URL",
+	"See readme.md for instructions": "ਹਦਾਇਤਾਂ ਲਈ readme.md ਵੇਖੋ",
+	"See what's new": "ਨਵਾਂ ਕੀ ਹੈ ਵੇਖੋ",
+	"Seed": "ਬੀਜ",
+	"Select a base model": "ਆਧਾਰ ਮਾਡਲ ਚੁਣੋ",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "ਇੱਕ ਮਾਡਲ ਚੁਣੋ",
+	"Select a pipeline": "ਪਾਈਪਲਾਈਨ ਚੁਣੋ",
+	"Select a pipeline url": "ਪਾਈਪਲਾਈਨ URL ਚੁਣੋ",
+	"Select a tool": "",
+	"Select an Ollama instance": "ਇੱਕ ਓਲਾਮਾ ਇੰਸਟੈਂਸ ਚੁਣੋ",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "ਮਾਡਲ ਚੁਣੋ",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "ਚੁਣੇ ਗਏ ਮਾਡਲ(ਆਂ) ਚਿੱਤਰ ਇਨਪੁੱਟਾਂ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੇ",
+	"Semantic distance to query": "",
+	"Send": "ਭੇਜੋ",
+	"Send a Message": "ਇੱਕ ਸੁਨੇਹਾ ਭੇਜੋ",
+	"Send message": "ਸੁਨੇਹਾ ਭੇਜੋ",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "ਸਤੰਬਰ",
+	"Serper API Key": "Serper API ਕੁੰਜੀ",
+	"Serply API Key": "",
+	"Serpstack API Key": "Serpstack API ਕੁੰਜੀ",
+	"Server connection verified": "ਸਰਵਰ ਕਨੈਕਸ਼ਨ ਦੀ ਪੁਸ਼ਟੀ ਕੀਤੀ ਗਈ",
+	"Set as default": "ਮੂਲ ਵਜੋਂ ਸੈੱਟ ਕਰੋ",
+	"Set CFG Scale": "",
+	"Set Default Model": "ਮੂਲ ਮਾਡਲ ਸੈੱਟ ਕਰੋ",
+	"Set embedding model (e.g. {{model}})": "ਐਮਬੈੱਡਿੰਗ ਮਾਡਲ ਸੈੱਟ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ {{model}})",
+	"Set Image Size": "ਚਿੱਤਰ ਆਕਾਰ ਸੈੱਟ ਕਰੋ",
+	"Set reranking model (e.g. {{model}})": "ਮੁੜ ਰੈਂਕਿੰਗ ਮਾਡਲ ਸੈੱਟ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "ਕਦਮ ਸੈੱਟ ਕਰੋ",
+	"Set Task Model": "ਟਾਸਕ ਮਾਡਲ ਸੈੱਟ ਕਰੋ",
+	"Set Voice": "ਆਵਾਜ਼ ਸੈੱਟ ਕਰੋ",
+	"Set whisper model": "",
+	"Settings": "ਸੈਟਿੰਗਾਂ",
+	"Settings saved successfully!": "ਸੈਟਿੰਗਾਂ ਸਫਲਤਾਪੂਰਵਕ ਸੰਭਾਲੀਆਂ ਗਈਆਂ!",
+	"Share": "ਸਾਂਝਾ ਕਰੋ",
+	"Share Chat": "ਗੱਲਬਾਤ ਸਾਂਝੀ ਕਰੋ",
+	"Share to OpenWebUI Community": "ਓਪਨਵੈਬਯੂਆਈ ਕਮਿਊਨਿਟੀ ਨਾਲ ਸਾਂਝਾ ਕਰੋ",
+	"short-summary": "ਛੋਟੀ-ਸੰਖੇਪ",
+	"Show": "ਦਿਖਾਓ",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "ਸ਼ਾਰਟਕਟ ਦਿਖਾਓ",
+	"Show your support!": "",
+	"Showcased creativity": "ਸਿਰਜਣਾਤਮਕਤਾ ਦਿਖਾਈ",
+	"Sign in": "ਸਾਈਨ ਇਨ ਕਰੋ",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "ਸਾਈਨ ਆਊਟ ਕਰੋ",
+	"Sign up": "ਰਜਿਸਟਰ ਕਰੋ",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "ਸਰੋਤ",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "ਬੋਲ ਪਛਾਣ ਗਲਤੀ: {{error}}",
+	"Speech-to-Text Engine": "ਬੋਲ-ਤੋਂ-ਪਾਠ ਇੰਜਣ",
+	"Stop": "",
+	"Stop Sequence": "ਰੋਕੋ ਕ੍ਰਮ",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "STT ਸੈਟਿੰਗਾਂ",
+	"Subtitle (e.g. about the Roman Empire)": "ਉਪਸਿਰਲੇਖ (ਉਦਾਹਰਣ ਲਈ ਰੋਮਨ ਸਾਮਰਾਜ ਬਾਰੇ)",
+	"Success": "ਸਫਲਤਾ",
+	"Successfully updated.": "ਸਫਲਤਾਪੂਰਵਕ ਅੱਪਡੇਟ ਕੀਤਾ ਗਿਆ।",
+	"Suggested": "ਸੁਝਾਇਆ ਗਿਆ",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "ਸਿਸਟਮ",
+	"System Instructions": "",
+	"System Prompt": "ਸਿਸਟਮ ਪ੍ਰੰਪਟ",
+	"Tags": "ਟੈਗ",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "ਸਾਨੂੰ ਹੋਰ ਦੱਸੋ:",
+	"Temperature": "ਤਾਪਮਾਨ",
+	"Template": "ਟੈਮਪਲੇਟ",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "ਪਾਠ-ਤੋਂ-ਬੋਲ ਇੰਜਣ",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "ਤੁਹਾਡੇ ਫੀਡਬੈਕ ਲਈ ਧੰਨਵਾਦ!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "ਸਕੋਰ 0.0 (0%) ਅਤੇ 1.0 (100%) ਦੇ ਵਿਚਕਾਰ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ।",
+	"Theme": "ਥੀਮ",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "ਇਹ ਯਕੀਨੀ ਬਣਾਉਂਦਾ ਹੈ ਕਿ ਤੁਹਾਡੀਆਂ ਕੀਮਤੀ ਗੱਲਾਂ ਤੁਹਾਡੇ ਬੈਕਐਂਡ ਡਾਟਾਬੇਸ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਤੌਰ 'ਤੇ ਸੰਭਾਲੀਆਂ ਗਈਆਂ ਹਨ। ਧੰਨਵਾਦ!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "ਵਿਸਥਾਰ ਨਾਲ ਵਿਆਖਿਆ",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "ਸਲਾਹ: ਹਰ ਬਦਲਾਅ ਦੇ ਬਾਅਦ ਗੱਲਬਾਤ ਇਨਪੁਟ ਵਿੱਚ ਟੈਬ ਕੀ ਦਬਾ ਕੇ ਲਗਾਤਾਰ ਕਈ ਵੈਰੀਏਬਲ ਸਲਾਟਾਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰੋ।",
+	"Title": "ਸਿਰਲੇਖ",
+	"Title (e.g. Tell me a fun fact)": "ਸਿਰਲੇਖ (ਉਦਾਹਰਣ ਲਈ ਮੈਨੂੰ ਇੱਕ ਮਜ਼ੇਦਾਰ ਤੱਥ ਦੱਸੋ)",
+	"Title Auto-Generation": "ਸਿਰਲੇਖ ਆਟੋ-ਜਨਰੇਸ਼ਨ",
+	"Title cannot be an empty string.": "ਸਿਰਲੇਖ ਖਾਲੀ ਸਤਰ ਨਹੀਂ ਹੋ ਸਕਦਾ।",
+	"Title Generation Prompt": "ਸਿਰਲੇਖ ਜਨਰੇਸ਼ਨ ਪ੍ਰੰਪਟ",
+	"To access the available model names for downloading,": "ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਉਪਲਬਧ ਮਾਡਲ ਨਾਮਾਂ ਤੱਕ ਪਹੁੰਚਣ ਲਈ,",
+	"To access the GGUF models available for downloading,": "ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਉਪਲਬਧ GGUF ਮਾਡਲਾਂ ਤੱਕ ਪਹੁੰਚਣ ਲਈ,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "ਗੱਲਬਾਤ ਇਨਪੁਟ ਲਈ।",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "ਅੱਜ",
+	"Toggle settings": "ਸੈਟਿੰਗਾਂ ਟੌਗਲ ਕਰੋ",
+	"Toggle sidebar": "ਸਾਈਡਬਾਰ ਟੌਗਲ ਕਰੋ",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "ਸਿਖਰ K",
+	"Top P": "ਸਿਖਰ P",
+	"Trouble accessing Ollama?": "ਓਲਾਮਾ ਤੱਕ ਪਹੁੰਚਣ ਵਿੱਚ ਮੁਸ਼ਕਲ?",
+	"TTS Model": "",
+	"TTS Settings": "TTS ਸੈਟਿੰਗਾਂ",
+	"TTS Voice": "",
+	"Type": "ਕਿਸਮ",
+	"Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (ਡਾਊਨਲੋਡ) URL ਟਾਈਪ ਕਰੋ",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "ਓਹੋ! {{provider}} ਨਾਲ ਕਨੈਕਟ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆ ਆਈ।",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "ਅੱਪਡੇਟ ਕਰੋ ਅਤੇ ਲਿੰਕ ਕਾਪੀ ਕਰੋ",
+	"Update for the latest features and improvements.": "",
+	"Update password": "ਪਾਸਵਰਡ ਅੱਪਡੇਟ ਕਰੋ",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "ਇੱਕ GGUF ਮਾਡਲ ਅਪਲੋਡ ਕਰੋ",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "ਫਾਇਲਾਂ ਅੱਪਲੋਡ ਕਰੋ",
+	"Upload Pipeline": "",
+	"Upload Progress": "ਅਪਲੋਡ ਪ੍ਰਗਤੀ",
+	"URL Mode": "URL ਮੋਡ",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "ਗ੍ਰਾਵਾਟਾਰ ਵਰਤੋ",
+	"Use Initials": "ਸ਼ੁਰੂਆਤੀ ਅੱਖਰ ਵਰਤੋ",
+	"use_mlock (Ollama)": "use_mlock (ਓਲਾਮਾ)",
+	"use_mmap (Ollama)": "use_mmap (ਓਲਾਮਾ)",
+	"user": "ਉਪਭੋਗਤਾ",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "ਉਪਭੋਗਤਾ ਅਧਿਕਾਰ",
+	"Users": "ਉਪਭੋਗਤਾ",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "ਵਰਤੋਂ",
+	"Valid time units:": "ਵੈਧ ਸਮਾਂ ਇਕਾਈਆਂ:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "ਵੈਰੀਏਬਲ",
+	"variable to have them replaced with clipboard content.": "ਕਲਿੱਪਬੋਰਡ ਸਮੱਗਰੀ ਨਾਲ ਬਦਲਣ ਲਈ ਵੈਰੀਏਬਲ।",
+	"Version": "ਵਰਜਨ",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "ਚੇਤਾਵਨੀ",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "ਚੇਤਾਵਨੀ: ਜੇ ਤੁਸੀਂ ਆਪਣਾ ਐਮਬੈੱਡਿੰਗ ਮਾਡਲ ਅੱਪਡੇਟ ਜਾਂ ਬਦਲਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਨੂੰ ਸਾਰੇ ਡਾਕੂਮੈਂਟ ਮੁੜ ਆਯਾਤ ਕਰਨ ਦੀ ਲੋੜ ਹੋਵੇਗੀ।",
+	"Web": "ਵੈਬ",
+	"Web API": "",
+	"Web Loader Settings": "ਵੈਬ ਲੋਡਰ ਸੈਟਿੰਗਾਂ",
+	"Web Search": "ਵੈੱਬ ਖੋਜ",
+	"Web Search Engine": "ਵੈੱਬ ਖੋਜ ਇੰਜਣ",
+	"Webhook URL": "ਵੈਬਹੁੱਕ URL",
+	"WebUI Settings": "ਵੈਬਯੂਆਈ ਸੈਟਿੰਗਾਂ",
+	"WebUI will make requests to": "ਵੈਬਯੂਆਈ ਬੇਨਤੀਆਂ ਕਰੇਗਾ",
+	"What’s New in": "ਨਵਾਂ ਕੀ ਹੈ",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "ਕਾਰਜਸਥਲ",
+	"Write a prompt suggestion (e.g. Who are you?)": "ਇੱਕ ਪ੍ਰੰਪਟ ਸੁਝਾਅ ਲਿਖੋ (ਉਦਾਹਰਣ ਲਈ ਤੁਸੀਂ ਕੌਣ ਹੋ?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "50 ਸ਼ਬਦਾਂ ਵਿੱਚ ਇੱਕ ਸੰਖੇਪ ਲਿਖੋ ਜੋ [ਵਿਸ਼ਾ ਜਾਂ ਕੁੰਜੀ ਸ਼ਬਦ] ਨੂੰ ਸੰਖੇਪ ਕਰਦਾ ਹੈ।",
+	"Write something...": "",
+	"Yesterday": "ਕੱਲ੍ਹ",
+	"You": "ਤੁਸੀਂ",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "ਤੁਸੀਂ ਆਧਾਰ ਮਾਡਲ ਨੂੰ ਕਲੋਨ ਨਹੀਂ ਕਰ ਸਕਦੇ",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਆਰਕਾਈਵ ਕੀਤੀਆਂ ਗੱਲਾਂ ਨਹੀਂ ਹਨ।",
+	"You have shared this chat": "ਤੁਸੀਂ ਇਹ ਗੱਲਬਾਤ ਸਾਂਝੀ ਕੀਤੀ ਹੈ",
+	"You're a helpful assistant.": "ਤੁਸੀਂ ਇੱਕ ਮਦਦਗਾਰ ਸਹਾਇਕ ਹੋ।",
+	"You're now logged in.": "ਤੁਸੀਂ ਹੁਣ ਲੌਗ ਇਨ ਹੋ ਗਏ ਹੋ।",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "ਯੂਟਿਊਬ",
+	"Youtube Loader Settings": "ਯੂਟਿਊਬ ਲੋਡਰ ਸੈਟਿੰਗਾਂ"
+}
diff --git a/src/lib/i18n/locales/pl-PL/translation.json b/src/lib/i18n/locales/pl-PL/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..5754650535fd22aa567b996716724894bf993530
--- /dev/null
+++ b/src/lib/i18n/locales/pl-PL/translation.json
@@ -0,0 +1,853 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' lub '-1' dla bez wygaśnięcia.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(np. `sh webui.sh --api`)",
+	"(latest)": "(najnowszy)",
+	"{{ models }}": "{{ modele }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Nie można usunąć modelu podstawowego",
+	"{{user}}'s Chats": "{{user}} - czaty",
+	"{{webUIName}} Backend Required": "Backend {{webUIName}} wymagane",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Model zadań jest używany podczas wykonywania zadań, takich jak generowanie tytułów czatów i zapytań wyszukiwania w Internecie",
+	"a user": "użytkownik",
+	"About": "O nas",
+	"Account": "Konto",
+	"Account Activation Pending": "",
+	"Accurate information": "Dokładna informacja",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "Dodaj",
+	"Add a model id": "Dodawanie identyfikatora modelu",
+	"Add a short description about what this model does": "Dodaj krótki opis działania tego modelu",
+	"Add a short title for this prompt": "Dodaj krótki tytuł tego polecenia",
+	"Add a tag": "Dodaj tag",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Dodaj własne polecenie",
+	"Add Files": "Dodaj pliki",
+	"Add Memory": "Dodaj pamięć",
+	"Add Model": "Dodaj model",
+	"Add Tag": "",
+	"Add Tags": "Dodaj tagi",
+	"Add text content": "",
+	"Add User": "Dodaj użytkownika",
+	"Adjusting these settings will apply changes universally to all users.": "Dostosowanie tych ustawień spowoduje zastosowanie zmian uniwersalnie do wszystkich użytkowników.",
+	"admin": "admin",
+	"Admin": "",
+	"Admin Panel": "Panel administracyjny",
+	"Admin Settings": "Ustawienia administratora",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "Zaawansowane parametry",
+	"Advanced Params": "Zaawansowane parametry",
+	"All chats": "",
+	"All Documents": "Wszystkie dokumenty",
+	"Allow Chat Deletion": "Pozwól na usuwanie czatu",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "znaki alfanumeryczne i myślniki",
+	"Already have an account?": "Masz już konto?",
+	"an assistant": "asystent",
+	"and": "i",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "i utwórz nowy udostępniony link",
+	"API Base URL": "Podstawowy adres URL interfejsu API",
+	"API Key": "Klucz API",
+	"API Key created.": "Klucz API utworzony.",
+	"API keys": "Klucze API",
+	"April": "Kwiecień",
+	"Archive": "Archiwum",
+	"Archive All Chats": "Archiwizuj wszystkie czaty",
+	"Archived Chats": "Zarchiwizowane czaty",
+	"are allowed - Activate this command by typing": "są dozwolone - Aktywuj to polecenie, wpisując",
+	"Are you sure?": "Jesteś pewien?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Dołącz plik",
+	"Attention to detail": "Dbałość o szczegóły",
+	"Audio": "Dźwięk",
+	"August": "Sierpień",
+	"Auto-playback response": "Odtwarzanie automatyczne odpowiedzi",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "Podstawowy adres URL AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "Podstawowy adres URL AUTOMATIC1111 jest wymagany.",
+	"Available list": "",
+	"available!": "dostępny!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Wstecz",
+	"Bad Response": "Zła odpowiedź",
+	"Banners": "Banery",
+	"Base Model (From)": "Model podstawowy (od)",
+	"Batch Size (num_batch)": "",
+	"before": "przed",
+	"Being lazy": "Jest leniwy",
+	"Brave Search API Key": "Klucz API wyszukiwania Brave",
+	"Bypass SSL verification for Websites": "Pomiń weryfikację SSL dla stron webowych",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "Anuluj",
+	"Capabilities": "Możliwości",
+	"Change Password": "Zmień hasło",
+	"Character": "",
+	"Chat": "Czat",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "Bąbelki czatu",
+	"Chat Controls": "",
+	"Chat direction": "Kierunek czatu",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Czaty",
+	"Check Again": "Sprawdź ponownie",
+	"Check for updates": "Sprawdź aktualizacje",
+	"Checking for updates...": "Sprawdzanie aktualizacji...",
+	"Choose a model before saving...": "Wybierz model przed zapisaniem...",
+	"Chunk Overlap": "Zachodzenie bloku",
+	"Chunk Params": "Parametry bloku",
+	"Chunk Size": "Rozmiar bloku",
+	"Citation": "Cytat",
+	"Clear memory": "",
+	"Click here for help.": "Kliknij tutaj, aby uzyskać pomoc.",
+	"Click here to": "Kliknij tutaj, żeby",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Kliknij tutaj, aby wybrać",
+	"Click here to select a csv file.": "Kliknij tutaj, żeby wybrać plik CSV",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "kliknij tutaj.",
+	"Click on the user role button to change a user's role.": "Kliknij przycisk roli użytkownika, aby zmienić rolę użytkownika.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "Klon",
+	"Close": "Zamknij",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Kolekcja",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "Bazowy URL ComfyUI",
+	"ComfyUI Base URL is required.": "Bazowy URL ComfyUI jest wymagany.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Polecenie",
+	"Completions": "",
+	"Concurrent Requests": "Równoczesne żądania",
+	"Confirm": "",
+	"Confirm Password": "Potwierdź hasło",
+	"Confirm your action": "",
+	"Connections": "Połączenia",
+	"Contact Admin for WebUI Access": "",
+	"Content": "Zawartość",
+	"Content Extraction": "",
+	"Context Length": "Długość kontekstu",
+	"Continue Response": "Kontynuuj odpowiedź",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "Skopiowano URL czatu do schowka!",
+	"Copied to clipboard": "",
+	"Copy": "Kopiuj",
+	"Copy last code block": "Skopiuj ostatni blok kodu",
+	"Copy last response": "Skopiuj ostatnią odpowiedź",
+	"Copy Link": "Kopiuj link",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Kopiowanie do schowka zakończone powodzeniem!",
+	"Create a model": "Tworzenie modelu",
+	"Create Account": "Utwórz konto",
+	"Create Knowledge": "",
+	"Create new key": "Utwórz nowy klucz",
+	"Create new secret key": "Utwórz nowy klucz bezpieczeństwa",
+	"Created at": "Utworzono o",
+	"Created At": "Utworzono o",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "Bieżący model",
+	"Current Password": "Bieżące hasło",
+	"Custom": "Niestandardowy",
+	"Customize models for a specific purpose": "Dostosowywanie modeli do określonego celu",
+	"Dark": "Ciemny",
+	"Dashboard": "",
+	"Database": "Baza danych",
+	"December": "Grudzień",
+	"Default": "Domyślny",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Domyślny (SentenceTransformers)",
+	"Default Model": "Model domyślny",
+	"Default model updated": "Domyślny model zaktualizowany",
+	"Default Prompt Suggestions": "Domyślne sugestie promptów",
+	"Default User Role": "Domyślna rola użytkownika",
+	"Delete": "Usuń",
+	"Delete a model": "Usuń model",
+	"Delete All Chats": "Usuń wszystkie czaty",
+	"Delete chat": "Usuń czat",
+	"Delete Chat": "Usuń czat",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "usuń ten link",
+	"Delete tool?": "",
+	"Delete User": "Usuń użytkownika",
+	"Deleted {{deleteModelTag}}": "Usunięto {{deleteModelTag}}",
+	"Deleted {{name}}": "Usunięto {{name}}",
+	"Description": "Opis",
+	"Didn't fully follow instructions": "Nie postępował zgodnie z instrukcjami",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "Odkryj model",
+	"Discover a prompt": "Odkryj prompt",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Odkryj, pobierz i eksploruj niestandardowe prompty",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Odkryj, pobierz i eksploruj ustawienia modeli",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "Wyświetl nazwę użytkownika zamiast Ty w czacie",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Dokument",
+	"Documentation": "",
+	"Documents": "Dokumenty",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "nie nawiązuje żadnych zewnętrznych połączeń, a Twoje dane pozostają bezpiecznie na Twoim lokalnie hostowanym serwerze.",
+	"Don't have an account?": "Nie masz konta?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "Nie podobał mi się styl",
+	"Done": "",
+	"Download": "Pobieranie",
+	"Download canceled": "Pobieranie przerwane",
+	"Download Database": "Pobierz bazę danych",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Upuść pliki tutaj, aby dodać do rozmowy",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "np. '30s', '10m'. Poprawne jednostki czasu to 's', 'm', 'h'.",
+	"Edit": "Edytuj",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "Edytuj użytkownika",
+	"ElevenLabs": "",
+	"Email": "Email",
+	"Embedding Batch Size": "",
+	"Embedding Model": "Model osadzania",
+	"Embedding Model Engine": "Silnik modelu osadzania",
+	"Embedding model set to \"{{embedding_model}}\"": "Model osadzania ustawiono na \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Włączanie udostępniania społecznościowego",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Włącz nowe rejestracje",
+	"Enable Web Search": "Włączanie wyszukiwania w Internecie",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Upewnij się, że twój plik CSV zawiera 4 kolumny w następującym porządku: Nazwa, Email, Hasło, Rola.",
+	"Enter {{role}} message here": "Wprowadź wiadomość {{role}} tutaj",
+	"Enter a detail about yourself for your LLMs to recall": "Wprowadź szczegóły o sobie, aby LLMs mogli pamiętać",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Wprowadź klucz API Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Wprowadź zakchodzenie bloku",
+	"Enter Chunk Size": "Wprowadź rozmiar bloku",
+	"Enter description": "",
+	"Enter Github Raw URL": "Wprowadź nieprzetworzony adres URL usługi Github",
+	"Enter Google PSE API Key": "Wprowadź klucz API Google PSE",
+	"Enter Google PSE Engine Id": "Wprowadź identyfikator aparatu Google PSE",
+	"Enter Image Size (e.g. 512x512)": "Wprowadź rozmiar obrazu (np. 512x512)",
+	"Enter language codes": "Wprowadź kody języków",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Wprowadź tag modelu (np. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Wprowadź liczbę kroków (np. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Wprowadź wynik",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Wprowadź adres URL zapytania Searxng",
+	"Enter Serper API Key": "Wprowadź klucz API Serper",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "Wprowadź klucz API Serpstack",
+	"Enter stop sequence": "Wprowadź sekwencję zatrzymania",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Wprowadź Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Wprowadź adres URL (np. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Wprowadź adres URL (np. http://localhost:11434/)",
+	"Enter Your Email": "Wprowadź swój adres email",
+	"Enter Your Full Name": "Wprowadź swoje imię i nazwisko",
+	"Enter your message": "",
+	"Enter Your Password": "Wprowadź swoje hasło",
+	"Enter Your Role": "Wprowadź swoją rolę",
+	"Error": "Błąd",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Eksperymentalne",
+	"Export": "Eksport",
+	"Export All Chats (All Users)": "Eksportuj wszystkie czaty (wszyscy użytkownicy)",
+	"Export chat (.json)": "",
+	"Export Chats": "Eksportuj czaty",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "Eksportuj modele",
+	"Export Prompts": "Eksportuj prompty",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Nie udało się utworzyć klucza API.",
+	"Failed to read clipboard contents": "Nie udało się odczytać zawartości schowka",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "Luty",
+	"Feedback History": "",
+	"Feel free to add specific details": "Podaj inne szczegóły",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Tryb pliku",
+	"File not found.": "Plik nie został znaleziony.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Wykryto podszywanie się pod odcisk palca: Nie można używać inicjałów jako awatara. Przechodzenie do domyślnego obrazu profilowego.",
+	"Fluidly stream large external response chunks": "Płynnie przesyłaj strumieniowo duże fragmenty odpowiedzi zewnętrznych",
+	"Focus chat input": "Skoncentruj na czacie",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Postępował z idealnie według instrukcji",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Kara za częstotliwość",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Ogólne",
+	"General Settings": "Ogólne ustawienia",
+	"Generate Image": "",
+	"Generating search query": "Generowanie zapytania",
+	"Generation Info": "Informacja o generacji",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "Dobra odpowiedź",
+	"Google PSE API Key": "Klucz API Google PSE",
+	"Google PSE Engine Id": "Identyfikator silnika Google PSE",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "nie ma rozmów.",
+	"Hello, {{name}}": "Witaj, {{name}}",
+	"Help": "Pomoc",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Ukryj",
+	"Hide Model": "",
+	"How can I help you today?": "Jak mogę Ci dzisiaj pomóc?",
+	"Hybrid Search": "Szukanie hybrydowe",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Generowanie obrazu (eksperymentalne)",
+	"Image Generation Engine": "Silnik generowania obrazu",
+	"Image Settings": "Ustawienia obrazu",
+	"Images": "Obrazy",
+	"Import Chats": "Importuj czaty",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "Importowanie modeli",
+	"Import Prompts": "Importuj prompty",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Dołącz flagę `--api` podczas uruchamiania stable-diffusion-webui",
+	"Info": "Informacji",
+	"Input commands": "Wprowadź komendy",
+	"Install from Github URL": "Instalowanie z adresu URL usługi Github",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "Interfejs",
+	"Invalid file format.": "",
+	"Invalid Tag": "Nieprawidłowy tag",
+	"January": "Styczeń",
+	"join our Discord for help.": "Dołącz do naszego Discorda po pomoc.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON (wersja zapoznawcza)",
+	"July": "Lipiec",
+	"June": "Czerwiec",
+	"JWT Expiration": "Wygaśnięcie JWT",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Zachowaj łączność",
+	"Keyboard shortcuts": "Skróty klawiszowe",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Język",
+	"large language models, locally.": "",
+	"Last Active": "Ostatnio aktywny",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Jasny",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "LLMy mogą popełniać błędy. Zweryfikuj ważne informacje.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Stworzone przez społeczność OpenWebUI",
+	"Make sure to enclose them with": "Upewnij się, że są one zamknięte w",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "Zarządzaj modelami",
+	"Manage Ollama Models": "Zarządzaj modelami Ollama",
+	"Manage Pipelines": "Zarządzanie potokami",
+	"March": "Marzec",
+	"Max Tokens (num_predict)": "Maksymalna liczba żetonów (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maksymalnie 3 modele można pobierać jednocześnie. Spróbuj ponownie później.",
+	"May": "Maj",
+	"Memories accessible by LLMs will be shown here.": "Pamięci używane przez LLM będą tutaj widoczne.",
+	"Memory": "Pamięć",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Wiadomości wysyłane po utworzeniu linku nie będą udostępniane. Użytkownicy z adresem URL będą mogli wyświetlić udostępniony czat.",
+	"Min P": "",
+	"Minimum Score": "Minimalny wynik",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' został pomyślnie pobrany.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' jest już w kolejce do pobrania.",
+	"Model {{modelId}} not found": "Model {{modelId}} nie został znaleziony",
+	"Model {{modelName}} is not vision capable": "Model {{modelName}} nie jest w stanie zobaczyć",
+	"Model {{name}} is now {{status}}": "Model {{name}} to teraz {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Wykryto ścieżkę systemu plików modelu. Wymagana jest krótka nazwa modelu do aktualizacji, nie można kontynuować.",
+	"Model ID": "Identyfikator modelu",
+	"Model Name": "",
+	"Model not selected": "Model nie został wybrany",
+	"Model Params": "Parametry modelu",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Whitelisting modelu",
+	"Model(s) Whitelisted": "Model(e) dodane do listy białej",
+	"Modelfile Content": "Zawartość pliku modelu",
+	"Models": "Modele",
+	"more": "",
+	"More": "Więcej",
+	"Move to Top": "",
+	"Name": "Nazwa",
+	"Name your model": "Nazwij swój model",
+	"New Chat": "Nowy czat",
+	"New folder": "",
+	"New Password": "Nowe hasło",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Nie znaleziono rezultatów",
+	"No search query generated": "Nie wygenerowano zapytania wyszukiwania",
+	"No source available": "Źródło nie dostępne",
+	"No valves to update": "",
+	"None": "Żaden",
+	"Not factually correct": "Nie zgodne z faktami",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Uwaga: Jeśli ustawisz minimalny wynik, szukanie zwróci jedynie dokumenty z wynikiem większym lub równym minimalnemu.",
+	"Notes": "",
+	"Notifications": "Powiadomienia",
+	"November": "Listopad",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "",
+	"October": "Październik",
+	"Off": "Wyłączony",
+	"Okay, Let's Go!": "Okej, zaczynamy!",
+	"OLED Dark": "Ciemny OLED",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Interfejs API Ollama wyłączony",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Wersja Ollama",
+	"On": "Włączony",
+	"Only": "Tylko",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "W poleceniu dozwolone są tylko znaki alfanumeryczne i myślniki.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ups! Wygląda na to, że URL jest nieprawidłowy. Sprawdź jeszcze raz i spróbuj ponownie.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Używasz nieobsługiwanej metody (tylko interfejs front-end). Proszę obsłużyć interfejs WebUI z poziomu backendu.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Otwórz nowy czat",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "Konfiguracja OpenAI API",
+	"OpenAI API Key is required.": "Klucz API OpenAI jest wymagany.",
+	"OpenAI URL/Key required.": "URL/Klucz OpenAI jest wymagany.",
+	"or": "lub",
+	"Other": "Inne",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Hasło",
+	"PDF document (.pdf)": "Dokument PDF (.pdf)",
+	"PDF Extract Images (OCR)": "PDF Wyodrębnij obrazy (OCR)",
+	"pending": "oczekujące",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "Odmowa dostępu do mikrofonu: {{error}}",
+	"Personalization": "Personalizacja",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "Rurociągów",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "Rurociągi Zawory",
+	"Plain text (.txt)": "Zwykły tekst (.txt)",
+	"Playground": "Plac zabaw",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Pozytywne podejście",
+	"Previous 30 days": "Poprzednie 30 dni",
+	"Previous 7 days": "Poprzednie 7 dni",
+	"Profile Image": "Obrazek profilowy",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (np. powiedz mi zabawny fakt o Imperium Rzymskim",
+	"Prompt Content": "Zawartość prompta",
+	"Prompt suggestions": "Sugestie prompta",
+	"Prompts": "Prompty",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Pobierz \"{{searchValue}}\" z Ollama.com",
+	"Pull a model from Ollama.com": "Pobierz model z Ollama.com",
+	"Query Params": "Parametry zapytania",
+	"RAG Template": "Szablon RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Czytaj na głos",
+	"Record voice": "Nagraj głos",
+	"Redirecting you to OpenWebUI Community": "Przekierowujemy Cię do społeczności OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "Odmówił, kiedy nie powinien",
+	"Regenerate": "Generuj ponownie",
+	"Release Notes": "Notatki wydania",
+	"Relevance": "",
+	"Remove": "Usuń",
+	"Remove Model": "Usuń model",
+	"Rename": "ZMień nazwę",
+	"Repeat Last N": "Powtórz ostatnie N",
+	"Request Mode": "Tryb żądania",
+	"Reranking Model": "Zmiana rankingu modelu",
+	"Reranking model disabled": "Zmiana rankingu modelu zablokowana",
+	"Reranking model set to \"{{reranking_model}}\"": "Zmiana rankingu modelu ustawiona na \"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Automatyczne kopiowanie odpowiedzi do schowka",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rola",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RLT",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "Zapisz",
+	"Save & Create": "Zapisz i utwórz",
+	"Save & Update": "Zapisz i zaktualizuj",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Bezpośrednie zapisywanie dzienników czatu w pamięci przeglądarki nie jest już obsługiwane. Prosimy o pobranie i usunięcie dzienników czatu, klikając poniższy przycisk. Nie martw się, możesz łatwo ponownie zaimportować dzienniki czatu do backendu za pomocą",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Szukaj",
+	"Search a model": "Szukaj modelu",
+	"Search Chats": "Szukaj w czatach",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "Szukaj modeli",
+	"Search Prompts": "Szukaj promptów",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "Liczba wyników wyszukiwania",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Wyszukiwano {{count}} sites_one",
+	"Searched {{count}} sites_few": "Wyszukiwano {{count}} sites_few",
+	"Searched {{count}} sites_many": "Wyszukiwano {{count}} sites_many",
+	"Searched {{count}} sites_other": "Wyszukiwano {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Adres URL zapytania Searxng",
+	"See readme.md for instructions": "Zajrzyj do readme.md po instrukcje",
+	"See what's new": "Zobacz co nowego",
+	"Seed": "Seed",
+	"Select a base model": "Wybieranie modelu bazowego",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "Wybierz model",
+	"Select a pipeline": "Wybieranie potoku",
+	"Select a pipeline url": "Wybieranie adresu URL potoku",
+	"Select a tool": "",
+	"Select an Ollama instance": "Wybierz instancję Ollama",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Wybierz model",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "Wybrane modele nie obsługują danych wejściowych obrazu",
+	"Semantic distance to query": "",
+	"Send": "Wyślij",
+	"Send a Message": "Wyślij Wiadomość",
+	"Send message": "Wyślij wiadomość",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "Wrzesień",
+	"Serper API Key": "Klucz API Serper",
+	"Serply API Key": "",
+	"Serpstack API Key": "Klucz API Serpstack",
+	"Server connection verified": "Połączenie z serwerem zweryfikowane",
+	"Set as default": "Ustaw jako domyślne",
+	"Set CFG Scale": "",
+	"Set Default Model": "Ustaw domyślny model",
+	"Set embedding model (e.g. {{model}})": "Ustaw model osadzania (e.g. {{model}})",
+	"Set Image Size": "Ustaw rozmiar obrazu",
+	"Set reranking model (e.g. {{model}})": "Ustaw zmianę rankingu modelu (e.g. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Ustaw kroki",
+	"Set Task Model": "Ustawianie modelu zadań",
+	"Set Voice": "Ustaw głos",
+	"Set whisper model": "",
+	"Settings": "Ustawienia",
+	"Settings saved successfully!": "Ustawienia zapisane pomyślnie!",
+	"Share": "Udostępnij",
+	"Share Chat": "Udostępnij czat",
+	"Share to OpenWebUI Community": "Dziel się z społecznością OpenWebUI",
+	"short-summary": "Krótkie podsumowanie",
+	"Show": "Pokaż",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "Pokaż skróty",
+	"Show your support!": "",
+	"Showcased creativity": "Pokaz kreatywności",
+	"Sign in": "Zaloguj się",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Wyloguj się",
+	"Sign up": "Zarejestruj się",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Źródło",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Błąd rozpoznawania mowy: {{error}}",
+	"Speech-to-Text Engine": "Silnik mowy na tekst",
+	"Stop": "",
+	"Stop Sequence": "Zatrzymaj sekwencję",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "Ustawienia STT",
+	"Subtitle (e.g. about the Roman Empire)": "Podtytuł (np. o Imperium Rzymskim)",
+	"Success": "Sukces",
+	"Successfully updated.": "Pomyślnie zaktualizowano.",
+	"Suggested": "Sugerowane",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "System",
+	"System Instructions": "",
+	"System Prompt": "Prompt systemowy",
+	"Tags": "Tagi",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "Powiedz nam więcej",
+	"Temperature": "Temperatura",
+	"Template": "Szablon",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Silnik tekstu na mowę",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Dzięki za informację zwrotną!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Wynik powinien być wartością pomiędzy 0.0 (0%) a 1.0 (100%).",
+	"Theme": "Motyw",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "To zapewnia, że Twoje cenne rozmowy są bezpiecznie zapisywane w bazie danych backendowej. Dziękujemy!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Dokładne wyjaśnienie",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Porada: Aktualizuj wiele zmiennych kolejno, naciskając klawisz tabulatora w polu wprowadzania czatu po każdej zmianie.",
+	"Title": "Tytuł",
+	"Title (e.g. Tell me a fun fact)": "Tytuł (np. Powiedz mi jakiś zabawny fakt)",
+	"Title Auto-Generation": "Automatyczne generowanie tytułu",
+	"Title cannot be an empty string.": "Tytuł nie może być pusty",
+	"Title Generation Prompt": "Prompt generowania tytułu",
+	"To access the available model names for downloading,": "Aby uzyskać dostęp do dostępnych nazw modeli do pobrania,",
+	"To access the GGUF models available for downloading,": "Aby uzyskać dostęp do dostępnych modeli GGUF do pobrania,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "do pola wprowadzania czatu.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "Dzisiaj",
+	"Toggle settings": "Przełącz ustawienia",
+	"Toggle sidebar": "Przełącz panel boczny",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Najlepsze K",
+	"Top P": "Najlepsze P",
+	"Trouble accessing Ollama?": "Problemy z dostępem do Ollama?",
+	"TTS Model": "",
+	"TTS Settings": "Ustawienia TTS",
+	"TTS Voice": "",
+	"Type": "Typ",
+	"Type Hugging Face Resolve (Download) URL": "Wprowadź adres URL do pobrania z Hugging Face",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "O nie! Wystąpił problem z połączeniem z {{provider}}.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "Uaktualnij i skopiuj link",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Aktualizacja hasła",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Prześlij model GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Prześlij pliki",
+	"Upload Pipeline": "",
+	"Upload Progress": "Postęp przesyłania",
+	"URL Mode": "Tryb adresu URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Użyj Gravatara",
+	"Use Initials": "Użyj inicjałów",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "użytkownik",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "Uprawnienia użytkownika",
+	"Users": "Użytkownicy",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Wykorzystaj",
+	"Valid time units:": "Poprawne jednostki czasu:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "zmienna",
+	"variable to have them replaced with clipboard content.": "zmienna która zostanie zastąpiona zawartością schowka.",
+	"Version": "Wersja",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "Ostrzeżenie",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Uwaga: Jeśli uaktualnisz lub zmienisz model osadzania, będziesz musiał ponownie zaimportować wszystkie dokumenty.",
+	"Web": "Sieć",
+	"Web API": "",
+	"Web Loader Settings": "Ustawienia pobierania z sieci",
+	"Web Search": "Wyszukiwarka w Internecie",
+	"Web Search Engine": "Wyszukiwarka internetowa",
+	"Webhook URL": "URL webhook",
+	"WebUI Settings": "Ustawienia interfejsu WebUI",
+	"WebUI will make requests to": "Interfejs sieciowy będzie wysyłał żądania do",
+	"What’s New in": "Co nowego w",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "Obszar roboczy",
+	"Write a prompt suggestion (e.g. Who are you?)": "Napisz sugestię do polecenia (np. Kim jesteś?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Napisz podsumowanie w 50 słowach, które podsumowuje [temat lub słowo kluczowe].",
+	"Write something...": "",
+	"Yesterday": "Wczoraj",
+	"You": "Ty",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "Nie można sklonować modelu podstawowego",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Nie masz zarchiwizowanych rozmów.",
+	"You have shared this chat": "Udostępniłeś ten czat",
+	"You're a helpful assistant.": "Jesteś pomocnym asystentem.",
+	"You're now logged in.": "Jesteś teraz zalogowany.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Ustawienia pobierania z Youtube"
+}
diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..55fc977e6d99404562608246dfd03dd7e086e1d5
--- /dev/null
+++ b/src/lib/i18n/locales/pt-BR/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' ou '-1' para sem expiração.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(por exemplo, `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(por exemplo, `sh webui.sh --api`)",
+	"(latest)": "(último)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Você não pode deletar um modelo base",
+	"{{user}}'s Chats": "Chats de {{user}}",
+	"{{webUIName}} Backend Required": "Backend {{webUIName}} necessário",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Um modelo de tarefa é usado ao realizar tarefas como gerar títulos para chats e consultas de pesquisa na web",
+	"a user": "um usuário",
+	"About": "Sobre",
+	"Account": "Conta",
+	"Account Activation Pending": "Ativação da Conta Pendente",
+	"Accurate information": "Informação precisa",
+	"Actions": "",
+	"Active Users": "Usuários Ativos",
+	"Add": "Adicionar",
+	"Add a model id": "Adicionar um ID de modelo",
+	"Add a short description about what this model does": "Adicione uma descrição curta sobre o que este modelo faz",
+	"Add a short title for this prompt": "Adicione um título curto para este prompt",
+	"Add a tag": "Adicionar uma tag",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Adicionar prompt personalizado",
+	"Add Files": "Adicionar Arquivos",
+	"Add Memory": "Adicionar Memória",
+	"Add Model": "Adicionar Modelo",
+	"Add Tag": "Adicionar Tag",
+	"Add Tags": "Adicionar Tags",
+	"Add text content": "",
+	"Add User": "Adicionar Usuário",
+	"Adjusting these settings will apply changes universally to all users.": "Ajustar essas configurações aplicará mudanças universalmente a todos os usuários.",
+	"admin": "admin",
+	"Admin": "Admin",
+	"Admin Panel": "Painel do Admin",
+	"Admin Settings": "Configurações do Admin",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Os admins têm acesso a todas as ferramentas o tempo todo; os usuários precisam de ferramentas atribuídas por modelo no workspace.",
+	"Advanced Parameters": "Parâmetros Avançados",
+	"Advanced Params": "Parâmetros Avançados",
+	"All chats": "",
+	"All Documents": "Todos os Documentos",
+	"Allow Chat Deletion": "Permitir Exclusão de Chats",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Permitir vozes não locais",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "Permitir Localização do Usuário",
+	"Allow Voice Interruption in Call": "Permitir Interrupção de Voz na Chamada",
+	"alphanumeric characters and hyphens": "caracteres alfanuméricos e hífens",
+	"Already have an account?": "Já tem uma conta?",
+	"an assistant": "um assistente",
+	"and": "e",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "e criar um novo link compartilhado.",
+	"API Base URL": "URL Base da API",
+	"API Key": "Chave API",
+	"API Key created.": "Chave API criada.",
+	"API keys": "Chaves API",
+	"April": "Abril",
+	"Archive": "Arquivar",
+	"Archive All Chats": "Arquivar Todos os Chats",
+	"Archived Chats": "Chats Arquivados",
+	"are allowed - Activate this command by typing": "são permitidos - Ative este comando digitando",
+	"Are you sure?": "Você tem certeza?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Anexar arquivo",
+	"Attention to detail": "Atenção aos detalhes",
+	"Audio": "Áudio",
+	"August": "Agosto",
+	"Auto-playback response": "Resposta de reprodução automática",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "String de Autenticação da API AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL": "URL Base AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "URL Base AUTOMATIC1111 é necessária.",
+	"Available list": "",
+	"available!": "disponível!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Voltar",
+	"Bad Response": "Resposta Ruim",
+	"Banners": "Banners",
+	"Base Model (From)": "Modelo Base (From)",
+	"Batch Size (num_batch)": "Tamanho do Lote (num_batch)",
+	"before": "antes",
+	"Being lazy": "Sendo preguiçoso",
+	"Brave Search API Key": "Chave API do Brave Search",
+	"Bypass SSL verification for Websites": "Ignorar verificação SSL para Sites",
+	"Call": "Chamada",
+	"Call feature is not supported when using Web STT engine": "O recurso de chamada não é suportado ao usar o mecanismo Web STT",
+	"Camera": "Câmera",
+	"Cancel": "Cancelar",
+	"Capabilities": "Capacidades",
+	"Change Password": "Mudar Senha",
+	"Character": "",
+	"Chat": "Chat",
+	"Chat Background Image": "Imagem de Fundo do Chat",
+	"Chat Bubble UI": "Interface de Bolha de Chat",
+	"Chat Controls": "Controles de Chat",
+	"Chat direction": "Direção do Chat",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Chats",
+	"Check Again": "Verificar Novamente",
+	"Check for updates": "Verificar atualizações",
+	"Checking for updates...": "Verificando atualizações...",
+	"Choose a model before saving...": "Escolha um modelo antes de salvar...",
+	"Chunk Overlap": "Sobreposição de Chunk",
+	"Chunk Params": "Parâmetros de Chunk",
+	"Chunk Size": "Tamanho do Chunk",
+	"Citation": "Citação",
+	"Clear memory": "Limpar memória",
+	"Click here for help.": "Clique aqui para obter ajuda.",
+	"Click here to": "Clique aqui para",
+	"Click here to download user import template file.": "Clique aqui para baixar o arquivo de modelo de importação de usuários.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Clique aqui para enviar",
+	"Click here to select a csv file.": "Clique aqui para enviar um arquivo csv.",
+	"Click here to select a py file.": "Clique aqui para enviar um arquivo py.",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "clique aqui.",
+	"Click on the user role button to change a user's role.": "Clique no botão de função do usuário para alterar a função de um usuário.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Permissão de escrita na área de transferência negada. Verifique as configurações do seu navegador para conceder o acesso necessário.",
+	"Clone": "Clonar",
+	"Close": "Fechar",
+	"Code execution": "",
+	"Code formatted successfully": "Código formatado com sucesso",
+	"Collection": "Coleção",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL Base do ComfyUI",
+	"ComfyUI Base URL is required.": "URL Base do ComfyUI é necessária.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Comando",
+	"Completions": "",
+	"Concurrent Requests": "Solicitações Concomitantes",
+	"Confirm": "Confirmar",
+	"Confirm Password": "Confirmar Senha",
+	"Confirm your action": "Confirme sua ação",
+	"Connections": "Conexões",
+	"Contact Admin for WebUI Access": "Contate o Admin para Acesso ao WebUI",
+	"Content": "Conteúdo",
+	"Content Extraction": "Extração de Conteúdo",
+	"Context Length": "Context Length",
+	"Continue Response": "Continuar Resposta",
+	"Continue with {{provider}}": "Continuar com {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "Controles",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "URL de chat compartilhado copiado para a área de transferência!",
+	"Copied to clipboard": "",
+	"Copy": "Copiar",
+	"Copy last code block": "Copiar último bloco de código",
+	"Copy last response": "Copiar última resposta",
+	"Copy Link": "Copiar Link",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Cópia para a área de transferência bem-sucedida!",
+	"Create a model": "Criar um modelo",
+	"Create Account": "Criar Conta",
+	"Create Knowledge": "",
+	"Create new key": "Criar nova chave",
+	"Create new secret key": "Criar nova chave secreta",
+	"Created at": "Criado em",
+	"Created At": "Criado Em",
+	"Created by": "Criado por",
+	"CSV Import": "Importação CSV",
+	"Current Model": "Modelo Atual",
+	"Current Password": "Senha Atual",
+	"Custom": "Personalizado",
+	"Customize models for a specific purpose": "Personalize modelos para um propósito específico",
+	"Dark": "Escuro",
+	"Dashboard": "Dashboard",
+	"Database": "Banco de Dados",
+	"December": "Dezembro",
+	"Default": "Padrão",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Padrão (SentenceTransformers)",
+	"Default Model": "Modelo Padrão",
+	"Default model updated": "Modelo padrão atualizado",
+	"Default Prompt Suggestions": "Sugestões de Prompt Padrão",
+	"Default User Role": "Função de Usuário Padrão",
+	"Delete": "Deletar",
+	"Delete a model": "Deletar um modelo",
+	"Delete All Chats": "Deletar Todos os Chats",
+	"Delete chat": "Deletar chat",
+	"Delete Chat": "Deletar Chat",
+	"Delete chat?": "Deletar chat?",
+	"Delete folder?": "",
+	"Delete function?": "Deletar função?",
+	"Delete prompt?": "Deletar prompt?",
+	"delete this link": "deletar este link",
+	"Delete tool?": "Deletar ferramenta?",
+	"Delete User": "Deletar Usuário",
+	"Deleted {{deleteModelTag}}": "Deletado {{deleteModelTag}}",
+	"Deleted {{name}}": "Deletado {{name}}",
+	"Description": "Descrição",
+	"Didn't fully follow instructions": "Não seguiu completamente as instruções",
+	"Disabled": "Desativado",
+	"Discover a function": "Descubra uma função",
+	"Discover a model": "Descubra um modelo",
+	"Discover a prompt": "Descubra um prompt",
+	"Discover a tool": "Descubra uma ferramenta",
+	"Discover, download, and explore custom functions": "Descubra, baixe e explore funções personalizadas",
+	"Discover, download, and explore custom prompts": "Descubra, baixe e explore prompts personalizados",
+	"Discover, download, and explore custom tools": "Descubra, baixe e explore ferramentas personalizadas",
+	"Discover, download, and explore model presets": "Descubra, baixe e explore predefinições de modelos",
+	"Dismissible": "Descartável",
+	"Display Emoji in Call": "Exibir Emoji na Chamada",
+	"Display the username instead of You in the Chat": "Exibir o nome de usuário em vez de Você no Chat",
+	"Do not install functions from sources you do not fully trust.": "Não instale funções de fontes que você não confia totalmente.",
+	"Do not install tools from sources you do not fully trust.": "Não instale ferramentas de fontes que você não confia totalmente.",
+	"Document": "Documento",
+	"Documentation": "Documentação",
+	"Documents": "Documentos",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "não faz nenhuma conexão externa, e seus dados permanecem seguros no seu servidor local.",
+	"Don't have an account?": "Não tem uma conta?",
+	"don't install random functions from sources you don't trust.": "não instale funções aleatórias de fontes que você não confia.",
+	"don't install random tools from sources you don't trust.": "não instale ferramentas aleatórias de fontes que você não confia.",
+	"Don't like the style": "Não gosta do estilo",
+	"Done": "Concluído",
+	"Download": "Baixar",
+	"Download canceled": "Download cancelado",
+	"Download Database": "Baixar Banco de Dados",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Solte qualquer arquivo aqui para adicionar à conversa",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "por exemplo, '30s', '10m'. Unidades de tempo válidas são 's', 'm', 'h'.",
+	"Edit": "Editar",
+	"Edit Arena Model": "",
+	"Edit Memory": "Editar Memória",
+	"Edit User": "Editar Usuário",
+	"ElevenLabs": "",
+	"Email": "Email",
+	"Embedding Batch Size": "Tamanho do Lote de Embedding",
+	"Embedding Model": "Modelo de Embedding",
+	"Embedding Model Engine": "Motor do Modelo de Embedding",
+	"Embedding model set to \"{{embedding_model}}\"": "Modelo de embedding definido para \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Ativar Compartilhamento Comunitário",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Ativar Novos Cadastros",
+	"Enable Web Search": "Ativar Pesquisa na Web",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "Ativado",
+	"Engine": "Motor",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Certifique-se de que seu arquivo CSV inclua 4 colunas nesta ordem: Nome, Email, Senha, Função.",
+	"Enter {{role}} message here": "Digite a mensagem de {{role}} aqui",
+	"Enter a detail about yourself for your LLMs to recall": "Digite um detalhe sobre você para seus LLMs lembrarem",
+	"Enter api auth string (e.g. username:password)": "Digite a string de autenticação da API (por exemplo, username:password)",
+	"Enter Brave Search API Key": "Digite a Chave API do Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Digite a Sobreposição de Chunk",
+	"Enter Chunk Size": "Digite o Tamanho do Chunk",
+	"Enter description": "",
+	"Enter Github Raw URL": "Digite a URL bruta do Github",
+	"Enter Google PSE API Key": "Digite a Chave API do Google PSE",
+	"Enter Google PSE Engine Id": "Digite o ID do Motor do Google PSE",
+	"Enter Image Size (e.g. 512x512)": "Digite o Tamanho da Imagem (por exemplo, 512x512)",
+	"Enter language codes": "Digite os códigos de idioma",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Digite a tag do modelo (por exemplo, {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Digite o Número de Passos (por exemplo, 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Digite a Pontuação",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Digite a URL de Consulta do Searxng",
+	"Enter Serper API Key": "Digite a Chave API do Serper",
+	"Enter Serply API Key": "Digite a Chave API do Serply",
+	"Enter Serpstack API Key": "Digite a Chave API do Serpstack",
+	"Enter stop sequence": "Digite a sequência de parada",
+	"Enter system prompt": "Digite o prompt do sistema",
+	"Enter Tavily API Key": "Digite a Chave API do Tavily",
+	"Enter Tika Server URL": "Digite a URL do Servidor Tika",
+	"Enter Top K": "Digite o Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Digite a URL (por exemplo, http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Digite a URL (por exemplo, http://localhost:11434)",
+	"Enter Your Email": "Digite Seu Email",
+	"Enter Your Full Name": "Digite Seu Nome Completo",
+	"Enter your message": "Digite sua mensagem",
+	"Enter Your Password": "Digite Sua Senha",
+	"Enter Your Role": "Digite Sua Função",
+	"Error": "Erro",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Experimental",
+	"Export": "Exportar",
+	"Export All Chats (All Users)": "Exportar Todos os Chats (Todos os Usuários)",
+	"Export chat (.json)": "Exportar chat (.json)",
+	"Export Chats": "Exportar Chats",
+	"Export Config to JSON File": "",
+	"Export Functions": "Exportar Funções",
+	"Export LiteLLM config.yaml": "Exportar config.yaml do LiteLLM",
+	"Export Models": "Exportar Modelos",
+	"Export Prompts": "Exportar Prompts",
+	"Export Tools": "Exportar Ferramentas",
+	"External Models": "Modelos Externos",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Falha ao criar a Chave API.",
+	"Failed to read clipboard contents": "Falha ao ler o conteúdo da área de transferência",
+	"Failed to update settings": "Falha ao atualizar as configurações",
+	"Failed to upload file.": "",
+	"February": "Fevereiro",
+	"Feedback History": "",
+	"Feel free to add specific details": "Sinta-se à vontade para adicionar detalhes específicos",
+	"File": "Arquivo",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Modo de Arquivo",
+	"File not found.": "Arquivo não encontrado.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "Arquivos",
+	"Filter is now globally disabled": "O filtro está agora desativado globalmente",
+	"Filter is now globally enabled": "O filtro está agora ativado globalmente",
+	"Filters": "Filtros",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Falsificação de impressão digital detectada: Não foi possível usar as iniciais como avatar. Usando a imagem de perfil padrão.",
+	"Fluidly stream large external response chunks": "Transmitir fluentemente grandes blocos de respostas externas",
+	"Focus chat input": "Focar entrada de chat",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Seguiu as instruções perfeitamente",
+	"Form": "Formulário",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Frequency Penalty",
+	"Function": "",
+	"Function created successfully": "Função criada com sucesso",
+	"Function deleted successfully": "Função deletada com sucesso",
+	"Function Description (e.g. A filter to remove profanity from text)": "Descrição da Função (por exemplo, Um filtro para remover palavrões do texto)",
+	"Function ID (e.g. my_filter)": "ID da Função (por exemplo, my_filter)",
+	"Function is now globally disabled": "A função está agora desativada globalmente",
+	"Function is now globally enabled": "A função está agora ativada globalmente",
+	"Function Name (e.g. My Filter)": "Nome da Função (por exemplo, My Filter)",
+	"Function updated successfully": "Função atualizada com sucesso",
+	"Functions": "Funções",
+	"Functions allow arbitrary code execution": "Funções permitem a execução arbitrária de código",
+	"Functions allow arbitrary code execution.": "Funções permitem a execução arbitrária de código.",
+	"Functions imported successfully": "Funções importadas com sucesso",
+	"General": "Geral",
+	"General Settings": "Configurações Gerais",
+	"Generate Image": "Gerar Imagem",
+	"Generating search query": "Gerando consulta de pesquisa",
+	"Generation Info": "Informações de Geração",
+	"Get up and running with": "Comece e esteja em funcionamento com",
+	"Global": "Global",
+	"Good Response": "Boa Resposta",
+	"Google PSE API Key": "Chave API do Google PSE",
+	"Google PSE Engine Id": "ID do Motor do Google PSE",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "não tem conversas.",
+	"Hello, {{name}}": "Olá, {{name}}",
+	"Help": "Ajuda",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Ocultar",
+	"Hide Model": "Ocultar Modelo",
+	"How can I help you today?": "Como posso ajudar você hoje?",
+	"Hybrid Search": "Pesquisa Híbrida",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Eu reconheço que li e entendi as implicações da minha ação. Estou ciente dos riscos associados à execução de código arbitrário e verifiquei a confiabilidade da fonte.",
+	"ID": "",
+	"Image Generation (Experimental)": "Geração de Imagem (Experimental)",
+	"Image Generation Engine": "Motor de Geração de Imagem",
+	"Image Settings": "Configurações de Imagem",
+	"Images": "Imagens",
+	"Import Chats": "Importar Chats",
+	"Import Config from JSON File": "",
+	"Import Functions": "Importar Funções",
+	"Import Models": "Importar Modelos",
+	"Import Prompts": "Importar Prompts",
+	"Import Tools": "Importar Ferramentas",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Incluir a flag `--api-auth` ao executar stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Incluir a flag `--api` ao executar stable-diffusion-webui",
+	"Info": "Informação",
+	"Input commands": "Comandos de entrada",
+	"Install from Github URL": "Instalar da URL do Github",
+	"Instant Auto-Send After Voice Transcription": "Envio Automático Instantâneo Após Transcrição de Voz",
+	"Interface": "Interface",
+	"Invalid file format.": "",
+	"Invalid Tag": "Tag Inválida",
+	"January": "Janeiro",
+	"join our Discord for help.": "junte-se ao nosso Discord para ajuda.",
+	"JSON": "JSON",
+	"JSON Preview": "Pré-visualização JSON",
+	"July": "Julho",
+	"June": "Junho",
+	"JWT Expiration": "Expiração do JWT",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Manter Vivo",
+	"Keyboard shortcuts": "Atalhos de Teclado",
+	"Knowledge": "Conhecimento",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Idioma",
+	"large language models, locally.": "grandes modelos de linguagem, localmente.",
+	"Last Active": "Última Atividade",
+	"Last Modified": "Última Modificação",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Claro",
+	"Listening...": "Escutando...",
+	"LLMs can make mistakes. Verify important information.": "LLMs podem cometer erros. Verifique informações importantes.",
+	"Local Models": "Modelos Locais",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Feito pela Comunidade OpenWebUI",
+	"Make sure to enclose them with": "Certifique-se de encerrá-los com",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Gerenciar",
+	"Manage Arena Models": "",
+	"Manage Models": "Gerenciar Modelos",
+	"Manage Ollama Models": "Gerenciar Modelos Ollama",
+	"Manage Pipelines": "Gerenciar Pipelines",
+	"March": "Março",
+	"Max Tokens (num_predict)": "Máximo de Tokens (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Máximo de 3 modelos podem ser baixados simultaneamente. Por favor, tente novamente mais tarde.",
+	"May": "Maio",
+	"Memories accessible by LLMs will be shown here.": "Memórias acessíveis por LLMs serão mostradas aqui.",
+	"Memory": "Memória",
+	"Memory added successfully": "Memória adicionada com sucesso",
+	"Memory cleared successfully": "Memória limpa com sucesso",
+	"Memory deleted successfully": "Memória excluída com sucesso",
+	"Memory updated successfully": "Memória atualizada com sucesso",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Mensagens enviadas após criar seu link não serão compartilhadas. Usuários com o URL poderão visualizar o chat compartilhado.",
+	"Min P": "",
+	"Minimum Score": "Pontuação Mínima",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Modelo '{{modelName}}' foi baixado com sucesso.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Modelo '{{modelTag}}' já está na fila para download.",
+	"Model {{modelId}} not found": "Modelo {{modelId}} não encontrado",
+	"Model {{modelName}} is not vision capable": "Modelo {{modelName}} não é capaz de visão",
+	"Model {{name}} is now {{status}}": "Modelo {{name}} está agora {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "Modelo criado com sucesso!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Caminho do sistema de arquivos do modelo detectado. Nome curto do modelo é necessário para atualização, não é possível continuar.",
+	"Model ID": "ID do Modelo",
+	"Model Name": "",
+	"Model not selected": "Modelo não selecionado",
+	"Model Params": "Parâmetros do Modelo",
+	"Model updated successfully": "Modelo atualizado com sucesso",
+	"Model Whitelisting": "Lista Branca de Modelos",
+	"Model(s) Whitelisted": "Modelo(s) na Lista Branca",
+	"Modelfile Content": "Conteúdo do Arquivo do Modelo",
+	"Models": "Modelos",
+	"more": "",
+	"More": "Mais",
+	"Move to Top": "",
+	"Name": "Nome",
+	"Name your model": "Nomeie seu modelo",
+	"New Chat": "Novo Chat",
+	"New folder": "",
+	"New Password": "Nova Senha",
+	"No content found": "",
+	"No content to speak": "Sem conteúdo para falar",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Nenhum arquivo selecionado",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Nenhum resultado encontrado",
+	"No search query generated": "Nenhuma consulta de pesquisa gerada",
+	"No source available": "Nenhuma fonte disponível",
+	"No valves to update": "Nenhuma válvula para atualizar",
+	"None": "Nenhum",
+	"Not factually correct": "Não está factualmente correto",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Se você definir uma pontuação mínima, a pesquisa retornará apenas documentos com pontuação igual ou superior à pontuação mínima.",
+	"Notes": "",
+	"Notifications": "Notificações",
+	"November": "Novembro",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth ID",
+	"October": "Outubro",
+	"Off": "Desligado",
+	"Okay, Let's Go!": "Ok, Vamos Lá!",
+	"OLED Dark": "OLED Escuro",
+	"Ollama": "Ollama",
+	"Ollama API": "API Ollama",
+	"Ollama API disabled": "API Ollama desativada",
+	"Ollama API is disabled": "API Ollama está desativada",
+	"Ollama Version": "Versão Ollama",
+	"On": "Ligado",
+	"Only": "Apenas",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Apenas caracteres alfanuméricos e hífens são permitidos na string de comando.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ops! Parece que a URL é inválida. Por favor, verifique novamente e tente de novo.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ops! Você está usando um método não suportado (somente frontend). Por favor, sirva a WebUI a partir do backend.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Abrir novo chat",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "A versão do Open WebUI (v{{OPEN_WEBUI_VERSION}}) é inferior à versão necessária (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API OpenAI",
+	"OpenAI API Config": "Configuração da API OpenAI",
+	"OpenAI API Key is required.": "Chave API OpenAI é necessária.",
+	"OpenAI URL/Key required.": "URL/Chave OpenAI necessária.",
+	"or": "ou",
+	"Other": "Outro",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Senha",
+	"PDF document (.pdf)": "Documento PDF (.pdf)",
+	"PDF Extract Images (OCR)": "Extrair Imagens do PDF (OCR)",
+	"pending": "pendente",
+	"Permission denied when accessing media devices": "Permissão negada ao acessar dispositivos de mídia",
+	"Permission denied when accessing microphone": "Permissão negada ao acessar o microfone",
+	"Permission denied when accessing microphone: {{error}}": "Permissão negada ao acessar o microfone: {{error}}",
+	"Personalization": "Personalização",
+	"Pin": "Fixar",
+	"Pinned": "Fixado",
+	"Pipeline deleted successfully": "Pipeline excluído com sucesso",
+	"Pipeline downloaded successfully": "Pipeline baixado com sucesso",
+	"Pipelines": "Pipelines",
+	"Pipelines Not Detected": "Pipelines Não Detectados",
+	"Pipelines Valves": "Válvulas de Pipelines",
+	"Plain text (.txt)": "Texto simples (.txt)",
+	"Playground": "Playground",
+	"Please carefully review the following warnings:": "Por favor, revise cuidadosamente os seguintes avisos:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Atitude positiva",
+	"Previous 30 days": "Últimos 30 dias",
+	"Previous 7 days": "Últimos 7 dias",
+	"Profile Image": "Imagem de Perfil",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (por exemplo, Diga-me um fato divertido sobre o Império Romano)",
+	"Prompt Content": "Conteúdo do Prompt",
+	"Prompt suggestions": "Sugestões de Prompt",
+	"Prompts": "Prompts",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Obter \"{{searchValue}}\" de Ollama.com",
+	"Pull a model from Ollama.com": "Obter um modelo de Ollama.com",
+	"Query Params": "Parâmetros de Consulta",
+	"RAG Template": "Modelo RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Ler em Voz Alta",
+	"Record voice": "Gravar voz",
+	"Redirecting you to OpenWebUI Community": "Redirecionando você para a Comunidade OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Refira-se como \"Usuário\" (por exemplo, \"Usuário está aprendendo espanhol\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Recusado quando não deveria",
+	"Regenerate": "Regenerar",
+	"Release Notes": "Notas de Lançamento",
+	"Relevance": "",
+	"Remove": "Remover",
+	"Remove Model": "Remover Modelo",
+	"Rename": "Renomear",
+	"Repeat Last N": "Repetir Último N",
+	"Request Mode": "Modo de Solicitação",
+	"Reranking Model": "Modelo de Reclassificação",
+	"Reranking model disabled": "Modelo de Reclassificação desativado",
+	"Reranking model set to \"{{reranking_model}}\"": "Modelo de Reclassificação definido como \"{{reranking_model}}\"",
+	"Reset": "Redefinir",
+	"Reset Upload Directory": "Redefinir Diretório de Upload",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Cópia Automática da Resposta para a Área de Transferência",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Notificações de resposta não podem ser ativadas pois as permissões do site foram negadas. Por favor, visite as configurações do seu navegador para conceder o acesso necessário.",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Função",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Execute Llama 2, Code Llama e outros modelos. Personalize e crie os seus próprios.",
+	"Running": "Executando",
+	"Save": "Salvar",
+	"Save & Create": "Salvar e Criar",
+	"Save & Update": "Salvar e Atualizar",
+	"Save As Copy": "",
+	"Save Tag": "Salvar Tag",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Salvar registros de chat diretamente no armazenamento do seu navegador não é mais suportado. Por favor, reserve um momento para baixar e excluir seus registros de chat clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar seus registros de chat para o backend através de",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Pesquisar",
+	"Search a model": "Pesquisar um modelo",
+	"Search Chats": "Pesquisar Chats",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Pesquisar Funções",
+	"Search Knowledge": "",
+	"Search Models": "Pesquisar Modelos",
+	"Search Prompts": "Pesquisar Prompts",
+	"Search Query Generation Prompt": "Prompt de Geração de Consulta de Pesquisa",
+	"Search Result Count": "Contagem de Resultados da Pesquisa",
+	"Search Tools": "Pesquisar Ferramentas",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Pesquisou {{count}} sites_one",
+	"Searched {{count}} sites_many": "Pesquisou {{count}} sites_many",
+	"Searched {{count}} sites_other": "Pesquisou {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "Pesquisando \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "URL da Consulta Searxng",
+	"See readme.md for instructions": "Veja readme.md para instruções",
+	"See what's new": "Veja o que há de novo",
+	"Seed": "Seed",
+	"Select a base model": "Selecione um modelo base",
+	"Select a engine": "Selecione um motor",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Selecione uma função",
+	"Select a model": "Selecione um modelo",
+	"Select a pipeline": "Selecione um pipeline",
+	"Select a pipeline url": "Selecione uma URL de pipeline",
+	"Select a tool": "Selecione uma ferramenta",
+	"Select an Ollama instance": "Selecione uma instância Ollama",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Selecionar modelo",
+	"Select only one model to call": "Selecione apenas um modelo para chamar",
+	"Selected model(s) do not support image inputs": "Modelo(s) selecionado(s) não suportam entradas de imagem",
+	"Semantic distance to query": "",
+	"Send": "Enviar",
+	"Send a Message": "Enviar uma Mensagem",
+	"Send message": "Enviar mensagem",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "Setembro",
+	"Serper API Key": "Chave da API Serper",
+	"Serply API Key": "Chave da API Serply",
+	"Serpstack API Key": "Chave da API Serpstack",
+	"Server connection verified": "Conexão com o servidor verificada",
+	"Set as default": "Definir como padrão",
+	"Set CFG Scale": "",
+	"Set Default Model": "Definir Modelo Padrão",
+	"Set embedding model (e.g. {{model}})": "Definir modelo de embedding (por exemplo, {{model}})",
+	"Set Image Size": "Definir Tamanho da Imagem",
+	"Set reranking model (e.g. {{model}})": "Definir modelo de reclassificação (por exemplo, {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Definir Etapas",
+	"Set Task Model": "Definir Modelo de Tarefa",
+	"Set Voice": "Definir Voz",
+	"Set whisper model": "",
+	"Settings": "Configurações",
+	"Settings saved successfully!": "Configurações salvas com sucesso!",
+	"Share": "Compartilhar",
+	"Share Chat": "Compartilhar Chat",
+	"Share to OpenWebUI Community": "Compartilhar com a Comunidade OpenWebUI",
+	"short-summary": "resumo-curto",
+	"Show": "Mostrar",
+	"Show Admin Details in Account Pending Overlay": "Mostrar Detalhes do Administrador na Sobreposição de Conta Pendentes",
+	"Show Model": "Mostrar Modelo",
+	"Show shortcuts": "Mostrar atalhos",
+	"Show your support!": "Mostre seu apoio!",
+	"Showcased creativity": "Criatividade exibida",
+	"Sign in": "Entrar",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Sair",
+	"Sign up": "Inscrever-se",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Fonte",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Erro de reconhecimento de fala: {{error}}",
+	"Speech-to-Text Engine": "Motor de Transcrição de Fala",
+	"Stop": "",
+	"Stop Sequence": "Sequência de Parada",
+	"Stream Chat Response": "",
+	"STT Model": "Modelo STT",
+	"STT Settings": "Configurações STT",
+	"Subtitle (e.g. about the Roman Empire)": "Legenda (por exemplo, sobre o Império Romano)",
+	"Success": "Sucesso",
+	"Successfully updated.": "Atualizado com sucesso.",
+	"Suggested": "Sugerido",
+	"Support": "Suporte",
+	"Support this plugin:": "Apoie este plugin:",
+	"Sync directory": "",
+	"System": "Sistema",
+	"System Instructions": "",
+	"System Prompt": "Prompt do Sistema",
+	"Tags": "Tags",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Toque para interromper",
+	"Tavily API Key": "Chave da API Tavily",
+	"Tell us more:": "Conte-nos mais:",
+	"Temperature": "Temperatura",
+	"Template": "Modelo",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Motor de Texto para Fala",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Obrigado pelo seu feedback!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Os desenvolvedores por trás deste plugin são voluntários apaixonados da comunidade. Se você achar este plugin útil, considere contribuir para o seu desenvolvimento.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "A pontuação deve ser um valor entre 0.0 (0%) e 1.0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "Pensando...",
+	"This action cannot be undone. Do you wish to continue?": "Esta ação não pode ser desfeita. Você deseja continuar?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Isso garante que suas conversas valiosas sejam salvas com segurança no banco de dados do backend. Obrigado!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Esta é uma funcionalidade experimental, pode não funcionar como esperado e está sujeita a alterações a qualquer momento.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Isso vai excluir",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Explicação detalhada",
+	"Tika": "Tika",
+	"Tika Server URL required.": "URL do servidor Tika necessária.",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Dica: Atualize vários slots de variáveis consecutivamente pressionando a tecla Tab na entrada de chat após cada substituição.",
+	"Title": "Título",
+	"Title (e.g. Tell me a fun fact)": "Título (por exemplo, Conte-me um fato divertido)",
+	"Title Auto-Generation": "Geração Automática de Título",
+	"Title cannot be an empty string.": "O Título não pode ser uma string vazia.",
+	"Title Generation Prompt": "Prompt de Geração de Título",
+	"To access the available model names for downloading,": "Para acessar os nomes de modelos disponíveis para download,",
+	"To access the GGUF models available for downloading,": "Para acessar os modelos GGUF disponíveis para download,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Para acessar a WebUI, entre em contato com o administrador. Os administradores podem gerenciar os status dos usuários no Painel de Administração.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "para entrada de chat.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Para selecionar ações aqui, adicione-os ao espaço de trabalho \"Ações\" primeiro.",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Para selecionar filtros aqui, adicione-os ao espaço de trabalho \"Funções\" primeiro.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Para selecionar kits de ferramentas aqui, adicione-os ao espaço de trabalho \"Ferramentas\" primeiro.",
+	"Toast notifications for new updates": "",
+	"Today": "Hoje",
+	"Toggle settings": "Alternar configurações",
+	"Toggle sidebar": "Alternar barra lateral",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Tokens a Manter na Atualização do Contexto (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Ferramenta criada com sucesso",
+	"Tool deleted successfully": "Ferramenta excluída com sucesso",
+	"Tool imported successfully": "Ferramenta importada com sucesso",
+	"Tool updated successfully": "Ferramenta atualizada com sucesso",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Descrição do Kit de Ferramentas (por exemplo, Um kit de ferramentas para realizar várias operações)",
+	"Toolkit ID (e.g. my_toolkit)": "ID do Kit de Ferramentas (por exemplo, my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Nome do Kit de Ferramentas (por exemplo, Meu Kit de Ferramentas)",
+	"Tools": "Ferramentas",
+	"Tools are a function calling system with arbitrary code execution": "Ferramentas são um sistema de chamada de funções com execução de código arbitrário",
+	"Tools have a function calling system that allows arbitrary code execution": "Ferramentas possuem um sistema de chamada de funções que permite a execução de código arbitrário",
+	"Tools have a function calling system that allows arbitrary code execution.": "Ferramentas possuem um sistema de chamada de funções que permite a execução de código arbitrário.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problemas para acessar o Ollama?",
+	"TTS Model": "Modelo TTS",
+	"TTS Settings": "Configurações TTS",
+	"TTS Voice": "Voz TTS",
+	"Type": "Tipo",
+	"Type Hugging Face Resolve (Download) URL": "Digite o URL de download do Hugging Face",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Ops! Houve um problema ao conectar-se ao {{provider}}.",
+	"UI": "Interface",
+	"Unpin": "Desfixar",
+	"Untagged": "",
+	"Update": "Atualizar",
+	"Update and Copy Link": "Atualizar e Copiar Link",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Atualizar senha",
+	"Updated": "",
+	"Updated at": "Atualizado em",
+	"Updated At": "",
+	"Upload": "Fazer upload",
+	"Upload a GGUF model": "Fazer upload de um modelo GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Fazer upload de Arquivos",
+	"Upload Pipeline": "Fazer upload de Pipeline",
+	"Upload Progress": "Progresso do Upload",
+	"URL Mode": "Modo URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Usar Gravatar",
+	"Use Initials": "Usar Iniciais",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "usuário",
+	"User": "",
+	"User location successfully retrieved.": "Localização do usuário recuperada com sucesso.",
+	"User Permissions": "Permissões do Usuário",
+	"Users": "Usuários",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Utilizar",
+	"Valid time units:": "Unidades de tempo válidas:",
+	"Valves": "Válvulas",
+	"Valves updated": "Válvulas atualizadas",
+	"Valves updated successfully": "Válvulas atualizadas com sucesso",
+	"variable": "variável",
+	"variable to have them replaced with clipboard content.": "variável para ser substituída pelo conteúdo da área de transferência.",
+	"Version": "Versão",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Voz",
+	"Voice Input": "",
+	"Warning": "Aviso",
+	"Warning:": "Aviso:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Aviso: Se você atualizar ou alterar seu modelo de incorporação, será necessário reimportar todos os documentos.",
+	"Web": "Web",
+	"Web API": "API Web",
+	"Web Loader Settings": "Configurações do Carregador Web",
+	"Web Search": "Pesquisa na Web",
+	"Web Search Engine": "Mecanismo de Busca na Web",
+	"Webhook URL": "URL do Webhook",
+	"WebUI Settings": "Configurações da WebUI",
+	"WebUI will make requests to": "A WebUI fará solicitações para",
+	"What’s New in": "O que há de novo em",
+	"Whisper (Local)": "Whisper (Local)",
+	"Widescreen Mode": "Modo Tela Cheia",
+	"Won": "",
+	"Workspace": "Espaço de Trabalho",
+	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
+	"Write something...": "",
+	"Yesterday": "Ontem",
+	"You": "Você",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Você pode personalizar suas interações com LLMs adicionando memórias através do botão 'Gerenciar' abaixo, tornando-as mais úteis e adaptadas a você.",
+	"You cannot clone a base model": "Você não pode clonar um modelo base",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Você não tem conversas arquivadas.",
+	"You have shared this chat": "Você compartilhou este chat",
+	"You're a helpful assistant.": "Você é um assistente útil.",
+	"You're now logged in.": "Você agora está logado.",
+	"Your account status is currently pending activation.": "O status da sua conta está atualmente aguardando ativação.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Toda a sua contribuição irá diretamente para o desenvolvedor do plugin; o Open WebUI não retém nenhuma porcentagem. No entanto, a plataforma de financiamento escolhida pode ter suas próprias taxas.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Configurações do Carregador Youtube"
+}
diff --git a/src/lib/i18n/locales/pt-PT/translation.json b/src/lib/i18n/locales/pt-PT/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..d39d18afd4398fb6bd21d68e58662b4902a40113
--- /dev/null
+++ b/src/lib/i18n/locales/pt-PT/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' ou '-1' para nenhuma expiração.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(por exemplo, `sh webui.sh --api`)",
+	"(latest)": "(mais recente)",
+	"{{ models }}": "{{ modelos }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Não é possível excluir um modelo base",
+	"{{user}}'s Chats": "{{user}}'s Chats",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Um modelo de tarefa é usado ao executar tarefas como gerar títulos para bate-papos e consultas de pesquisa na Web",
+	"a user": "um utilizador",
+	"About": "Acerca de",
+	"Account": "Conta",
+	"Account Activation Pending": "Ativação da Conta Pendente",
+	"Accurate information": "Informações precisas",
+	"Actions": "",
+	"Active Users": "Utilizadores Ativos",
+	"Add": "Adicionar",
+	"Add a model id": "Adicionar um ID de modelo",
+	"Add a short description about what this model does": "Adicione uma breve descrição sobre o que este modelo faz",
+	"Add a short title for this prompt": "Adicione um título curto para este prompt",
+	"Add a tag": "Adicionar uma tag",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Adicionar um prompt curto",
+	"Add Files": "Adicionar Ficheiros",
+	"Add Memory": "Adicionar memória",
+	"Add Model": "Adicionar modelo",
+	"Add Tag": "",
+	"Add Tags": "adicionar tags",
+	"Add text content": "",
+	"Add User": "Adicionar Utilizador",
+	"Adjusting these settings will apply changes universally to all users.": "Ajustar essas configurações aplicará alterações universalmente a todos os utilizadores.",
+	"admin": "administrador",
+	"Admin": "Admin",
+	"Admin Panel": "Painel do Administrador",
+	"Admin Settings": "Configurações do Administrador",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "Parâmetros Avançados",
+	"Advanced Params": "Params Avançados",
+	"All chats": "",
+	"All Documents": "Todos os Documentos",
+	"Allow Chat Deletion": "Permitir Exclusão de Conversa",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Permitir vozes não locais",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "caracteres alfanuméricos e hífens",
+	"Already have an account?": "Já tem uma conta?",
+	"an assistant": "um assistente",
+	"and": "e",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "e criar um novo link partilhado.",
+	"API Base URL": "URL Base da API",
+	"API Key": "Chave da API",
+	"API Key created.": "Chave da API criada.",
+	"API keys": "Chaves da API",
+	"April": "Abril",
+	"Archive": "Arquivo",
+	"Archive All Chats": "Arquivar todos os chats",
+	"Archived Chats": "Conversas arquivadas",
+	"are allowed - Activate this command by typing": "são permitidos - Ative este comando escrevendo",
+	"Are you sure?": "Tem a certeza?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Anexar ficheiro",
+	"Attention to detail": "Detalhado",
+	"Audio": "Áudio",
+	"August": "Agosto",
+	"Auto-playback response": "Reprodução automática da resposta",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "URL Base do AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "O URL Base do AUTOMATIC1111 é obrigatório.",
+	"Available list": "",
+	"available!": "disponível!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Voltar",
+	"Bad Response": "Resposta má",
+	"Banners": "Estandartes",
+	"Base Model (From)": "Modelo Base (De)",
+	"Batch Size (num_batch)": "",
+	"before": "antes",
+	"Being lazy": "Ser preguiçoso",
+	"Brave Search API Key": "Chave da API de Pesquisa Brave",
+	"Bypass SSL verification for Websites": "Ignorar verificação SSL para sites",
+	"Call": "Chamar",
+	"Call feature is not supported when using Web STT engine": "A funcionalide de Chamar não é suportada quando usa um motor Web STT",
+	"Camera": "Camera",
+	"Cancel": "Cancelar",
+	"Capabilities": "Capacidades",
+	"Change Password": "Alterar Senha",
+	"Character": "",
+	"Chat": "Conversa",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "Bolha UI da Conversa",
+	"Chat Controls": "",
+	"Chat direction": "Direção da Conversa",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Conversas",
+	"Check Again": "Verifique novamente",
+	"Check for updates": "Verificar atualizações",
+	"Checking for updates...": "Verificando atualizações...",
+	"Choose a model before saving...": "Escolha um modelo antes de guardar...",
+	"Chunk Overlap": "Sobreposição de Fragmento",
+	"Chunk Params": "Parâmetros de Fragmento",
+	"Chunk Size": "Tamanho do Fragmento",
+	"Citation": "Citação",
+	"Clear memory": "Limpar memória",
+	"Click here for help.": "Clique aqui para obter ajuda.",
+	"Click here to": "Clique aqui para",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Clique aqui para selecionar",
+	"Click here to select a csv file.": "Clique aqui para selecionar um ficheiro csv.",
+	"Click here to select a py file.": "Clique aqui para selecionar um ficheiro py",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "clique aqui.",
+	"Click on the user role button to change a user's role.": "Clique no botão de função do utilizador para alterar a função de um utilizador.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "Clonar",
+	"Close": "Fechar",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Coleção",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL Base do ComfyUI",
+	"ComfyUI Base URL is required.": "O URL Base do ComfyUI é obrigatório.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Comando",
+	"Completions": "",
+	"Concurrent Requests": "Solicitações simultâneas",
+	"Confirm": "",
+	"Confirm Password": "Confirmar Senha",
+	"Confirm your action": "",
+	"Connections": "Conexões",
+	"Contact Admin for WebUI Access": "Contatar Admin para acesso ao WebUI",
+	"Content": "Conteúdo",
+	"Content Extraction": "",
+	"Context Length": "Comprimento do Contexto",
+	"Continue Response": "Continuar resposta",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "URL de Conversa partilhado copiada com sucesso!",
+	"Copied to clipboard": "",
+	"Copy": "Copiar",
+	"Copy last code block": "Copiar último bloco de código",
+	"Copy last response": "Copiar última resposta",
+	"Copy Link": "Copiar link",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Cópia para a área de transferência bem-sucedida!",
+	"Create a model": "Criar um modelo",
+	"Create Account": "Criar Conta",
+	"Create Knowledge": "",
+	"Create new key": "Criar nova chave",
+	"Create new secret key": "Criar nova chave secreta",
+	"Created at": "Criado em",
+	"Created At": "Criado em",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "Modelo Atual",
+	"Current Password": "Senha Atual",
+	"Custom": "Personalizado",
+	"Customize models for a specific purpose": "Personalizar modelos para uma finalidade específica",
+	"Dark": "Escuro",
+	"Dashboard": "Painel",
+	"Database": "Base de dados",
+	"December": "Dezembro",
+	"Default": "Padrão",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Padrão (SentenceTransformers)",
+	"Default Model": "Modelo padrão",
+	"Default model updated": "Modelo padrão atualizado",
+	"Default Prompt Suggestions": "Sugestões de Prompt Padrão",
+	"Default User Role": "Função de Utilizador Padrão",
+	"Delete": "Apagar",
+	"Delete a model": "Apagar um modelo",
+	"Delete All Chats": "Apagar todas as conversas",
+	"Delete chat": "Apagar conversa",
+	"Delete Chat": "Apagar Conversa",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "apagar este link",
+	"Delete tool?": "",
+	"Delete User": "Apagar Utilizador",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} apagado",
+	"Deleted {{name}}": "Apagado {{name}}",
+	"Description": "Descrição",
+	"Didn't fully follow instructions": "Não seguiu instruções com precisão",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "Descubra um modelo",
+	"Discover a prompt": "Descobrir um prompt",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Descubra, descarregue e explore prompts personalizados",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Descubra, descarregue e explore predefinições de modelo",
+	"Dismissible": "Dispensável",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "Exibir o nome de utilizador em vez de Você na Conversa",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Documento",
+	"Documentation": "Documentação",
+	"Documents": "Documentos",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "não faz conexões externas e os seus dados permanecem seguros no seu servidor alojado localmente.",
+	"Don't have an account?": "Não tem uma conta?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "Não gosta do estilo",
+	"Done": "",
+	"Download": "Descarregar",
+	"Download canceled": "Download cancelado",
+	"Download Database": "Descarregar Base de Dados",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Largue os ficheiros aqui para adicionar à conversa",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "por exemplo, '30s', '10m'. Unidades de tempo válidas são 's', 'm', 'h'.",
+	"Edit": "Editar",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "Editar Utilizador",
+	"ElevenLabs": "",
+	"Email": "E-mail",
+	"Embedding Batch Size": "Tamanho do Lote do Embedding",
+	"Embedding Model": "Modelo de Embedding",
+	"Embedding Model Engine": "Motor de Modelo de Embedding",
+	"Embedding model set to \"{{embedding_model}}\"": "Modelo de Embedding definido como \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Active a Partilha da Comunidade",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Ativar Novas Inscrições",
+	"Enable Web Search": "Ativar pesquisa na Web",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Confirme que o seu ficheiro CSV inclui 4 colunas nesta ordem: Nome, E-mail, Senha, Função.",
+	"Enter {{role}} message here": "Escreva a mensagem de {{role}} aqui",
+	"Enter a detail about yourself for your LLMs to recall": "Escreva um detalhe sobre você para que os seus LLMs possam lembrar-se",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Escreva a chave da API do Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Escreva a Sobreposição de Fragmento",
+	"Enter Chunk Size": "Escreva o Tamanho do Fragmento",
+	"Enter description": "",
+	"Enter Github Raw URL": "Escreva o URL cru do Github",
+	"Enter Google PSE API Key": "Escreva a chave da API PSE do Google",
+	"Enter Google PSE Engine Id": "Escreva o ID do mecanismo PSE do Google",
+	"Enter Image Size (e.g. 512x512)": "Escreva o Tamanho da Imagem (por exemplo, 512x512)",
+	"Enter language codes": "Escreva os códigos de idioma",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Escreva a tag do modelo (por exemplo, {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Escreva o Número de Etapas (por exemplo, 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Escreva a Pontuação",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Escreva o URL da Pesquisa Searxng",
+	"Enter Serper API Key": "Escreva a chave da API Serper",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "Escreva a chave da API Serpstack",
+	"Enter stop sequence": "Escreva a sequência de paragem",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Escreva o Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Escreva o URL (por exemplo, http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Escreva o URL (por exemplo, http://localhost:11434)",
+	"Enter Your Email": "Escreva o seu E-mail",
+	"Enter Your Full Name": "Escreva o seu Nome Completo",
+	"Enter your message": "",
+	"Enter Your Password": "Escreva a sua Senha",
+	"Enter Your Role": "Escreva a sua Função",
+	"Error": "Erro",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Experimental",
+	"Export": "Exportar",
+	"Export All Chats (All Users)": "Exportar Todas as Conversas (Todos os Utilizadores)",
+	"Export chat (.json)": "Exportar Conversa (.json)",
+	"Export Chats": "Exportar Conversas",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "Modelos de Exportação",
+	"Export Prompts": "Exportar Prompts",
+	"Export Tools": "",
+	"External Models": "Modelos Externos",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Falha ao criar a Chave da API.",
+	"Failed to read clipboard contents": "Falha ao ler o conteúdo da área de transferência",
+	"Failed to update settings": "Falha ao atualizar as definições",
+	"Failed to upload file.": "",
+	"February": "Fevereiro",
+	"Feedback History": "",
+	"Feel free to add specific details": "Sinta-se à vontade para adicionar detalhes específicos",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Modo de Ficheiro",
+	"File not found.": "Ficheiro não encontrado.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Detectada falsificação da impressão digital: Não é possível usar iniciais como avatar. A usar a imagem de perfil padrão.",
+	"Fluidly stream large external response chunks": "Transmita com fluidez grandes blocos de resposta externa",
+	"Focus chat input": "Focar na conversa",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Seguiu instruções perfeitamente",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Penalidade de Frequência",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Geral",
+	"General Settings": "Configurações Gerais",
+	"Generate Image": "Gerar imagem",
+	"Generating search query": "A gerar a consulta da pesquisa",
+	"Generation Info": "Informações de Geração",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "Boa Resposta",
+	"Google PSE API Key": "Chave da API PSE do Google",
+	"Google PSE Engine Id": "ID do mecanismo PSE do Google",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "não possui conversas.",
+	"Hello, {{name}}": "Olá, {{name}}",
+	"Help": "Ajuda",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Ocultar",
+	"Hide Model": "",
+	"How can I help you today?": "Como posso ajudá-lo hoje?",
+	"Hybrid Search": "Pesquisa Híbrida",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Geração de Imagens (Experimental)",
+	"Image Generation Engine": "Mecanismo de Geração de Imagens",
+	"Image Settings": "Configurações da Imagem",
+	"Images": "Imagens",
+	"Import Chats": "Importar Conversas",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "Importar Modelos",
+	"Import Prompts": "Importar Prompts",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Inclua a flag `--api` ao executar stable-diffusion-webui",
+	"Info": "Informação",
+	"Input commands": "Comandos de entrada",
+	"Install from Github URL": "Instalar a partir do URL do Github",
+	"Instant Auto-Send After Voice Transcription": "Enviar automaticamente depois da transcrição da voz",
+	"Interface": "Interface",
+	"Invalid file format.": "",
+	"Invalid Tag": "Etiqueta Inválida",
+	"January": "Janeiro",
+	"join our Discord for help.": "junte-se ao nosso Discord para obter ajuda.",
+	"JSON": "JSON",
+	"JSON Preview": "Pré-visualização JSON",
+	"July": "Julho",
+	"June": "Junho",
+	"JWT Expiration": "Expiração JWT",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Manter Vivo",
+	"Keyboard shortcuts": "Atalhos de teclado",
+	"Knowledge": "Conhecimento",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Idioma",
+	"large language models, locally.": "",
+	"Last Active": "Último Ativo",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Claro",
+	"Listening...": "A escutar...",
+	"LLMs can make mistakes. Verify important information.": "LLMs podem cometer erros. Verifique informações importantes.",
+	"Local Models": "Modelos Locais",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Feito pela Comunidade OpenWebUI",
+	"Make sure to enclose them with": "Certifique-se de colocá-los entre",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Gerir",
+	"Manage Arena Models": "",
+	"Manage Models": "Gerir Modelos",
+	"Manage Ollama Models": "Gerir Modelos Ollama",
+	"Manage Pipelines": "Gerir pipelines",
+	"March": "Março",
+	"Max Tokens (num_predict)": "Máx Tokens (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "O máximo de 3 modelos podem ser descarregados simultaneamente. Tente novamente mais tarde.",
+	"May": "Maio",
+	"Memories accessible by LLMs will be shown here.": "Memórias acessíveis por LLMs serão mostradas aqui.",
+	"Memory": "Memória",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Mensagens que você enviar após criar o seu link não serão partilhadas. Os utilizadores com o URL poderão visualizar a conversa partilhada.",
+	"Min P": "",
+	"Minimum Score": "Mínimo de Pontuação",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "DD/MM/YYYY",
+	"MMMM DD, YYYY HH:mm": "DD/MM/YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "O modelo '{{modelName}}' foi descarregado com sucesso.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "O modelo '{{modelTag}}' já está na fila para descarregar.",
+	"Model {{modelId}} not found": "Modelo {{modelId}} não foi encontrado",
+	"Model {{modelName}} is not vision capable": "O modelo {{modelName}} não é capaz de visão",
+	"Model {{name}} is now {{status}}": "Modelo {{name}} agora é {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Dtectado caminho do sistema de ficheiros do modelo. É necessário o nome curto do modelo para atualização, não é possível continuar.",
+	"Model ID": "ID do modelo",
+	"Model Name": "",
+	"Model not selected": "Modelo não selecionado",
+	"Model Params": "Params Modelo",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Lista de Permissões do Modelo",
+	"Model(s) Whitelisted": "Modelo(s) na Lista de Permissões",
+	"Modelfile Content": "Conteúdo do Ficheiro do Modelo",
+	"Models": "Modelos",
+	"more": "",
+	"More": "Mais",
+	"Move to Top": "",
+	"Name": "Nome",
+	"Name your model": "Atribua um nome ao seu modelo",
+	"New Chat": "Nova Conversa",
+	"New folder": "",
+	"New Password": "Nova Senha",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Não foram encontrados resultados",
+	"No search query generated": "Não foi gerada nenhuma consulta de pesquisa",
+	"No source available": "Nenhuma fonte disponível",
+	"No valves to update": "",
+	"None": "Nenhum",
+	"Not factually correct": "Não é correto em termos factuais",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Se você definir uma pontuação mínima, a pesquisa só retornará documentos com uma pontuação maior ou igual à pontuação mínima.",
+	"Notes": "",
+	"Notifications": "Notificações da Área de Trabalho",
+	"November": "Novembro",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "",
+	"October": "Outubro",
+	"Off": "Desligado",
+	"Okay, Let's Go!": "Ok, Vamos Lá!",
+	"OLED Dark": "OLED Escuro",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "API do Ollama desativada",
+	"Ollama API is disabled": "A API do Ollama está desactivada",
+	"Ollama Version": "Versão do Ollama",
+	"On": "Ligado",
+	"Only": "Apenas",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Apenas caracteres alfanuméricos e hífens são permitidos na string de comando.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Epá! Parece que o URL é inválido. Verifique novamente e tente outra vez.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Epá! Você está a usar um método não suportado (somente frontend). Por favor, sirva o WebUI a partir do backend.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Abrir nova conversa",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API OpenAI",
+	"OpenAI API Config": "Configuração da API OpenAI",
+	"OpenAI API Key is required.": "A Chave da API OpenAI é obrigatória.",
+	"OpenAI URL/Key required.": "URL/Chave da API OpenAI é necessária.",
+	"or": "ou",
+	"Other": "Outro",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Senha",
+	"PDF document (.pdf)": "Documento PDF (.pdf)",
+	"PDF Extract Images (OCR)": "Extrair Imagens de PDF (OCR)",
+	"pending": "pendente",
+	"Permission denied when accessing media devices": "A permissão foi negada ao aceder aos dispositivos de media",
+	"Permission denied when accessing microphone": "A permissão foi negada ao aceder ao microfone",
+	"Permission denied when accessing microphone: {{error}}": "A permissão foi negada ao aceder o microfone: {{error}}",
+	"Personalization": "Personalização",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "Condutas",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "Válvulas de Condutas",
+	"Plain text (.txt)": "Texto sem formatação (.txt)",
+	"Playground": "Recreio",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Atitude Positiva",
+	"Previous 30 days": "Últimos 30 dias",
+	"Previous 7 days": "Últimos 7 dias",
+	"Profile Image": "Imagem de Perfil",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (ex.: Dê-me um facto divertido sobre o Império Romano)",
+	"Prompt Content": "Conteúdo do Prompt",
+	"Prompt suggestions": "Sugestões de Prompt",
+	"Prompts": "Prompts",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Puxar \"{{searchValue}}\" do Ollama.com",
+	"Pull a model from Ollama.com": "Puxar um modelo do Ollama.com",
+	"Query Params": "Parâmetros de Consulta",
+	"RAG Template": "Modelo RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Ler em Voz Alta",
+	"Record voice": "Gravar voz",
+	"Redirecting you to OpenWebUI Community": "Redirecionando-o para a Comunidade OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Refera-se a si próprio como \"User\" (por exemplo, \"User está a aprender Espanhol\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Recusado quando não deveria",
+	"Regenerate": "Regenerar",
+	"Release Notes": "Notas de Lançamento",
+	"Relevance": "",
+	"Remove": "Remover",
+	"Remove Model": "Remover Modelo",
+	"Rename": "Renomear",
+	"Repeat Last N": "Repetir Últimos N",
+	"Request Mode": "Modo de Pedido",
+	"Reranking Model": "Modelo de Reranking",
+	"Reranking model disabled": "Modelo de Reranking desativado",
+	"Reranking model set to \"{{reranking_model}}\"": "Modelo de Reranking definido como \"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "Limpar Pasta de Carregamento",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Cópia Automática da Resposta para a Área de Transferência",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Função",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "A correr",
+	"Save": "Guardar",
+	"Save & Create": "Guardar e Criar",
+	"Save & Update": "Guardar e Atualizar",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Guardar o registo das conversas diretamente no armazenamento do seu navegador já não é suportado. Reserve um momento para descarregar e eliminar os seus registos de conversas clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar os seus registos de conversas para o backend através de",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Pesquisar",
+	"Search a model": "Pesquisar um modelo",
+	"Search Chats": "Pesquisar Conversas",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "Modelos de pesquisa",
+	"Search Prompts": "Pesquisar Prompts",
+	"Search Query Generation Prompt": "Prompt de geração de consulta de pesquisa",
+	"Search Result Count": "Contagem de resultados da pesquisa",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Pesquisado {{count}} sites_one",
+	"Searched {{count}} sites_many": "Pesquisado {{count}} sites_many",
+	"Searched {{count}} sites_other": "Pesquisado {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "URL de consulta Searxng",
+	"See readme.md for instructions": "Consulte readme.md para obter instruções",
+	"See what's new": "Veja o que há de novo",
+	"Seed": "Semente",
+	"Select a base model": "Selecione um modelo base",
+	"Select a engine": "Selecione um motor",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "Selecione um modelo",
+	"Select a pipeline": "Selecione um pipeline",
+	"Select a pipeline url": "Selecione um URL de pipeline",
+	"Select a tool": "",
+	"Select an Ollama instance": "Selecione uma instância Ollama",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Selecione o modelo",
+	"Select only one model to call": "Selecione apenas um modelo para a chamada",
+	"Selected model(s) do not support image inputs": "O(s) modelo(s) selecionado(s) não suporta(m) entradas de imagem",
+	"Semantic distance to query": "",
+	"Send": "Enviar",
+	"Send a Message": "Enviar uma Mensagem",
+	"Send message": "Enviar mensagem",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "Setembro",
+	"Serper API Key": "Chave API Serper",
+	"Serply API Key": "",
+	"Serpstack API Key": "Chave da API Serpstack",
+	"Server connection verified": "Conexão com o servidor verificada",
+	"Set as default": "Definir como padrão",
+	"Set CFG Scale": "",
+	"Set Default Model": "Definir Modelo Padrão",
+	"Set embedding model (e.g. {{model}})": "Definir modelo de vetorização (ex.: {{model}})",
+	"Set Image Size": "Definir Tamanho da Imagem",
+	"Set reranking model (e.g. {{model}})": "Definir modelo de reranking (ex.: {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Definir Etapas",
+	"Set Task Model": "Definir modelo de tarefa",
+	"Set Voice": "Definir Voz",
+	"Set whisper model": "",
+	"Settings": "Configurações",
+	"Settings saved successfully!": "Configurações guardadas com sucesso!",
+	"Share": "Partilhar",
+	"Share Chat": "Partilhar Conversa",
+	"Share to OpenWebUI Community": "Partilhar com a Comunidade OpenWebUI",
+	"short-summary": "resumo-curto",
+	"Show": "Mostrar",
+	"Show Admin Details in Account Pending Overlay": "Mostrar Detalhes do Administrador na sobreposição de Conta Pendente",
+	"Show Model": "",
+	"Show shortcuts": "Mostrar atalhos",
+	"Show your support!": "",
+	"Showcased creativity": "Criatividade Exibida",
+	"Sign in": "Entrar",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Sair",
+	"Sign up": "Inscrever-se",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Fonte",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Erro de reconhecimento de fala: {{error}}",
+	"Speech-to-Text Engine": "Motor de Fala para Texto",
+	"Stop": "",
+	"Stop Sequence": "Sequência de Paragem",
+	"Stream Chat Response": "",
+	"STT Model": "Modelo STT",
+	"STT Settings": "Configurações STT",
+	"Subtitle (e.g. about the Roman Empire)": "Subtítulo (ex.: sobre o Império Romano)",
+	"Success": "Sucesso",
+	"Successfully updated.": "Atualizado com sucesso.",
+	"Suggested": "Sugerido",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "Sistema",
+	"System Instructions": "",
+	"System Prompt": "Prompt do Sistema",
+	"Tags": "Etiquetas",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "Diga-nos mais:",
+	"Temperature": "Temperatura",
+	"Template": "Modelo",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Motor de Texto para Fala",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Obrigado pelo seu feedback!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "A pontuação deve ser um valor entre 0.0 (0%) e 1.0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "A pensar...",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Isto garante que suas conversas valiosas sejam guardadas com segurança na sua base de dados de backend. Obrigado!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Isto é um recurso experimental, pode não funcionar conforme o esperado e está sujeito a alterações a qualquer momento.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Explicação Minuciosa",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Dica: Atualize vários slots de variáveis consecutivamente pressionando a tecla Tab na entrada da conversa após cada substituição.",
+	"Title": "Título",
+	"Title (e.g. Tell me a fun fact)": "Título (ex.: Diz-me um facto divertido)",
+	"Title Auto-Generation": "Geração Automática de Título",
+	"Title cannot be an empty string.": "Título não pode ser uma string vazia.",
+	"Title Generation Prompt": "Prompt de Geração de Título",
+	"To access the available model names for downloading,": "Para aceder aos nomes de modelo disponíveis para descarregar,",
+	"To access the GGUF models available for downloading,": "Para aceder aos modelos GGUF disponíveis para descarregar,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Para aceder ao WebUI, entre em contato com o administrador. Os administradores podem gerir o status dos utilizadores no Painel de Administração.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "para a entrada da conversa.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "Hoje",
+	"Toggle settings": "Alternar configurações",
+	"Toggle sidebar": "Alternar barra lateral",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problemas a aceder ao Ollama?",
+	"TTS Model": "Modelo TTS",
+	"TTS Settings": "Configurações TTS",
+	"TTS Voice": "Voz TTS",
+	"Type": "Tipo",
+	"Type Hugging Face Resolve (Download) URL": "Escreva o URL do Hugging Face Resolve (Descarregar)",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Houve um problema ao conectar a {{provider}}.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "Atualizar e Copiar Link",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Atualizar senha",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Carregar um modelo GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Carregar ficheiros",
+	"Upload Pipeline": "Carregar Pipeline",
+	"Upload Progress": "Progresso do Carregamento",
+	"URL Mode": "Modo de URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Usar Gravatar",
+	"Use Initials": "Usar Iniciais",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "utilizador",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "Permissões do Utilizador",
+	"Users": "Utilizadores",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Utilizar",
+	"Valid time units:": "Unidades de tempo válidas:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "variável",
+	"variable to have them replaced with clipboard content.": "variável para que sejam substituídos pelo conteúdo da área de transferência.",
+	"Version": "Versão",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "Aviso",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Aviso: Se você atualizar ou alterar o seu modelo de vetorização, você tem de reimportar todos os documentos.",
+	"Web": "Web",
+	"Web API": "Web API",
+	"Web Loader Settings": "Configurações do Carregador da Web",
+	"Web Search": "Pesquisa na Web",
+	"Web Search Engine": "Motor de Pesquisa Web",
+	"Webhook URL": "URL do Webhook",
+	"WebUI Settings": "Configurações WebUI",
+	"WebUI will make requests to": "WebUI fará pedidos a",
+	"What’s New in": "O que há de novo em",
+	"Whisper (Local)": "Whisper (Local)",
+	"Widescreen Mode": "Modo Widescreen",
+	"Won": "",
+	"Workspace": "Espaço de Trabalho",
+	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem és tu?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
+	"Write something...": "",
+	"Yesterday": "Ontem",
+	"You": "Você",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Você pode personalizar as suas interações com LLMs adicionando memórias através do botão ‘Gerir’ abaixo, tornando-as mais úteis e personalizadas para você.",
+	"You cannot clone a base model": "Não é possível clonar um modelo base",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Você não tem conversas arquivadas.",
+	"You have shared this chat": "Você partilhou esta conversa",
+	"You're a helpful assistant.": "Você é um assistente útil.",
+	"You're now logged in.": "Você agora está conectado.",
+	"Your account status is currently pending activation.": "O status da sua conta está atualmente com a ativação pendente.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Configurações do Carregador do Youtube"
+}
diff --git a/src/lib/i18n/locales/ro-RO/translation.json b/src/lib/i18n/locales/ro-RO/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..b492e3212ea95d96889790628ff179e895908d33
--- /dev/null
+++ b/src/lib/i18n/locales/ro-RO/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' sau '-1' fără expirare.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(de ex. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(de ex. `sh webui.sh --api`)",
+	"(latest)": "(ultimul)",
+	"{{ models }}": "{{ modele }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Nu puteți șterge un model de bază",
+	"{{user}}'s Chats": "Conversațiile lui {{user}}",
+	"{{webUIName}} Backend Required": "Este necesar backend-ul {{webUIName}}",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un model de sarcină este utilizat pentru realizarea unor sarcini precum generarea de titluri pentru conversații și interogări de căutare pe web",
+	"a user": "un utilizator",
+	"About": "Despre",
+	"Account": "Cont",
+	"Account Activation Pending": "Activarea contului în așteptare",
+	"Accurate information": "Informații precise",
+	"Actions": "Acțiuni",
+	"Active Users": "Utilizatori activi",
+	"Add": "Adaugă",
+	"Add a model id": "Adaugă un id de model",
+	"Add a short description about what this model does": "Adaugă o scurtă descriere despre ce face acest model",
+	"Add a short title for this prompt": "Adaugă un titlu scurt pentru acest prompt",
+	"Add a tag": "Adaugă o etichetă",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Adaugă prompt personalizat",
+	"Add Files": "Adaugă Fișiere",
+	"Add Memory": "Adaugă Memorie",
+	"Add Model": "Adaugă Model",
+	"Add Tag": "Adaugă Etichetă",
+	"Add Tags": "Adaugă Etichete",
+	"Add text content": "",
+	"Add User": "Adaugă Utilizator",
+	"Adjusting these settings will apply changes universally to all users.": "Ajustarea acestor setări va aplica modificările universal pentru toți utilizatorii.",
+	"admin": "administrator",
+	"Admin": "Administrator",
+	"Admin Panel": "Panoul de Administrare",
+	"Admin Settings": "Setări de Administrator",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Administratorii au acces la toate instrumentele în orice moment; utilizatorii au nevoie de instrumente asignate pe model în spațiul de lucru.",
+	"Advanced Parameters": "Parametri Avansați",
+	"Advanced Params": "Parametri Avansați",
+	"All chats": "",
+	"All Documents": "Toate Documentele",
+	"Allow Chat Deletion": "Permite Ștergerea Conversațiilor",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Permite voci non-locale",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "Permite Localizarea Utilizatorului",
+	"Allow Voice Interruption in Call": "Permite Întreruperea Vocii în Apel",
+	"alphanumeric characters and hyphens": "caractere alfanumerice și cratime",
+	"Already have an account?": "Deja ai un cont?",
+	"an assistant": "un asistent",
+	"and": "și",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "și creează un nou link partajat.",
+	"API Base URL": "URL Bază API",
+	"API Key": "Cheie API",
+	"API Key created.": "Cheie API creată.",
+	"API keys": "Chei API",
+	"April": "Aprilie",
+	"Archive": "Arhivează",
+	"Archive All Chats": "Arhivează Toate Conversațiile",
+	"Archived Chats": "Conversații Arhivate",
+	"are allowed - Activate this command by typing": "sunt permise - Activează această comandă tastând",
+	"Are you sure?": "Ești sigur?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Atașează fișier",
+	"Attention to detail": "Atenție la detalii",
+	"Audio": "Audio",
+	"August": "August",
+	"Auto-playback response": "Redare automată a răspunsului",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "Șir de Autentificare API AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL": "URL Bază AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "Este necesar URL-ul Bază AUTOMATIC1111.",
+	"Available list": "",
+	"available!": "disponibil!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Înapoi",
+	"Bad Response": "Răspuns Greșit",
+	"Banners": "Bannere",
+	"Base Model (From)": "Model de Bază (De la)",
+	"Batch Size (num_batch)": "Dimensiune Lot (num_batch)",
+	"before": "înainte",
+	"Being lazy": "Fiind leneș",
+	"Brave Search API Key": "Cheie API Brave Search",
+	"Bypass SSL verification for Websites": "Ocolește verificarea SSL pentru site-uri web",
+	"Call": "Apel",
+	"Call feature is not supported when using Web STT engine": "Funcția de apel nu este suportată când se utilizează motorul Web STT",
+	"Camera": "Cameră",
+	"Cancel": "Anulează",
+	"Capabilities": "Capabilități",
+	"Change Password": "Schimbă Parola",
+	"Character": "",
+	"Chat": "Conversație",
+	"Chat Background Image": "Imagine de Fundal pentru Conversație",
+	"Chat Bubble UI": "Interfață cu Bule de Conversație",
+	"Chat Controls": "Controale pentru Conversație",
+	"Chat direction": "Direcția conversației",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Conversații",
+	"Check Again": "Verifică din Nou",
+	"Check for updates": "Verifică actualizări",
+	"Checking for updates...": "Se verifică actualizările...",
+	"Choose a model before saving...": "Alege un model înainte de a salva...",
+	"Chunk Overlap": "Suprapunere Bloc",
+	"Chunk Params": "Parametri Bloc",
+	"Chunk Size": "Dimensiune Bloc",
+	"Citation": "Citație",
+	"Clear memory": "Șterge memoria",
+	"Click here for help.": "Apasă aici pentru ajutor.",
+	"Click here to": "Apasă aici pentru",
+	"Click here to download user import template file.": "Apasă aici pentru a descărca fișierul șablon de import utilizator.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Apasă aici pentru a selecta",
+	"Click here to select a csv file.": "Apasă aici pentru a selecta un fișier csv.",
+	"Click here to select a py file.": "Apasă aici pentru a selecta un fișier py.",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "apasă aici.",
+	"Click on the user role button to change a user's role.": "Apasă pe butonul rolului utilizatorului pentru a schimba rolul unui utilizator.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Permisiunea de scriere în clipboard a fost refuzată. Vă rugăm să verificați setările browserului pentru a acorda accesul necesar.",
+	"Clone": "Clonează",
+	"Close": "Închide",
+	"Code execution": "",
+	"Code formatted successfully": "Cod formatat cu succes",
+	"Collection": "Colecție",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL De Bază ComfyUI",
+	"ComfyUI Base URL is required.": "Este necesar URL-ul De Bază ComfyUI.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Comandă",
+	"Completions": "",
+	"Concurrent Requests": "Cereri Concurente",
+	"Confirm": "Confirmă",
+	"Confirm Password": "Confirmă Parola",
+	"Confirm your action": "Confirmă acțiunea ta",
+	"Connections": "Conexiuni",
+	"Contact Admin for WebUI Access": "Contactează administratorul pentru acces WebUI",
+	"Content": "Conținut",
+	"Content Extraction": "Extragere Conținut",
+	"Context Length": "Lungime Context",
+	"Continue Response": "Continuă Răspunsul",
+	"Continue with {{provider}}": "Continuă cu {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "Controale",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "URL-ul conversației partajate a fost copiat în clipboard!",
+	"Copied to clipboard": "",
+	"Copy": "Copiază",
+	"Copy last code block": "Copiază ultimul bloc de cod",
+	"Copy last response": "Copiază ultimul răspuns",
+	"Copy Link": "Copiază Link",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Copierea în clipboard a fost realizată cu succes!",
+	"Create a model": "Creează un model",
+	"Create Account": "Creează Cont",
+	"Create Knowledge": "",
+	"Create new key": "Creează cheie nouă",
+	"Create new secret key": "Creează cheie secretă nouă",
+	"Created at": "Creat la",
+	"Created At": "Creat La",
+	"Created by": "Creat de",
+	"CSV Import": "Import CSV",
+	"Current Model": "Model Curent",
+	"Current Password": "Parola Curentă",
+	"Custom": "Personalizat",
+	"Customize models for a specific purpose": "Personalizează modele pentru un scop specific",
+	"Dark": "Întunecat",
+	"Dashboard": "Tablou de Bord",
+	"Database": "Bază de Date",
+	"December": "Decembrie",
+	"Default": "Implicit",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Implicit (SentenceTransformers)",
+	"Default Model": "Model Implicit",
+	"Default model updated": "Modelul implicit a fost actualizat",
+	"Default Prompt Suggestions": "Sugestii de Prompt Implicite",
+	"Default User Role": "Rolul Implicit al Utilizatorului",
+	"Delete": "Șterge",
+	"Delete a model": "Șterge un model",
+	"Delete All Chats": "Șterge Toate Conversațiile",
+	"Delete chat": "Șterge conversația",
+	"Delete Chat": "Șterge Conversația",
+	"Delete chat?": "Șterge conversația?",
+	"Delete folder?": "",
+	"Delete function?": "Șterge funcția?",
+	"Delete prompt?": "Șterge promptul?",
+	"delete this link": "șterge acest link",
+	"Delete tool?": "Șterge instrumentul?",
+	"Delete User": "Șterge Utilizatorul",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} șters",
+	"Deleted {{name}}": "{{name}} șters",
+	"Description": "Descriere",
+	"Didn't fully follow instructions": "Nu a urmat complet instrucțiunile",
+	"Disabled": "Dezactivat",
+	"Discover a function": "Descoperă o funcție",
+	"Discover a model": "Descoperă un model",
+	"Discover a prompt": "Descoperă un prompt",
+	"Discover a tool": "Descoperă un instrument",
+	"Discover, download, and explore custom functions": "Descoperă, descarcă și explorează funcții personalizate",
+	"Discover, download, and explore custom prompts": "Descoperă, descarcă și explorează prompturi personalizate",
+	"Discover, download, and explore custom tools": "Descoperă, descarcă și explorează instrumente personalizate",
+	"Discover, download, and explore model presets": "Descoperă, descarcă și explorează presetări de model",
+	"Dismissible": "Ignorabil",
+	"Display Emoji in Call": "Afișează Emoji în Apel",
+	"Display the username instead of You in the Chat": "Afișează numele utilizatorului în loc de Tu în Conversație",
+	"Do not install functions from sources you do not fully trust.": "Nu instalați funcții din surse în care nu aveți încredere completă.",
+	"Do not install tools from sources you do not fully trust.": "Nu instalați instrumente din surse în care nu aveți încredere completă.",
+	"Document": "Document",
+	"Documentation": "Documentație",
+	"Documents": "Documente",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "nu face nicio conexiune externă, iar datele tale rămân în siguranță pe serverul găzduit local.",
+	"Don't have an account?": "Nu ai un cont?",
+	"don't install random functions from sources you don't trust.": "nu instala funcții aleatorii din surse în care nu ai încredere.",
+	"don't install random tools from sources you don't trust.": "nu instala instrumente aleatorii din surse în care nu ai încredere.",
+	"Don't like the style": "Nu îți place stilul",
+	"Done": "Gata",
+	"Download": "Descarcă",
+	"Download canceled": "Descărcare anulată",
+	"Download Database": "Descarcă Baza de Date",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Plasează orice fișiere aici pentru a le adăuga la conversație",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "de ex. '30s', '10m'. Unitățile de timp valide sunt 's', 'm', 'h'.",
+	"Edit": "Editează",
+	"Edit Arena Model": "",
+	"Edit Memory": "Editează Memorie",
+	"Edit User": "Editează Utilizator",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "Email",
+	"Embedding Batch Size": "Dimensiune Lot de Încapsulare",
+	"Embedding Model": "Model de Încapsulare",
+	"Embedding Model Engine": "Motor de Model de Încapsulare",
+	"Embedding model set to \"{{embedding_model}}\"": "Modelul de încapsulare setat la \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Activează Partajarea Comunitară",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Activează Înscrierile Noi",
+	"Enable Web Search": "Activează Căutarea pe Web",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "Activat",
+	"Engine": "Motor",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Asigurați-vă că fișierul CSV include 4 coloane în această ordine: Nume, Email, Parolă, Rol.",
+	"Enter {{role}} message here": "Introduceți mesajul pentru {{role}} aici",
+	"Enter a detail about yourself for your LLMs to recall": "Introduceți un detaliu despre dvs. pe care LLM-urile să-l rețină",
+	"Enter api auth string (e.g. username:password)": "Introduceți șirul de autentificare API (de ex. username:password)",
+	"Enter Brave Search API Key": "Introduceți Cheia API Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Introduceți Suprapunerea Blocului",
+	"Enter Chunk Size": "Introduceți Dimensiunea Blocului",
+	"Enter description": "",
+	"Enter Github Raw URL": "Introduceți URL-ul Raw de pe Github",
+	"Enter Google PSE API Key": "Introduceți Cheia API Google PSE",
+	"Enter Google PSE Engine Id": "Introduceți ID-ul Motorului Google PSE",
+	"Enter Image Size (e.g. 512x512)": "Introduceți Dimensiunea Imaginii (de ex. 512x512)",
+	"Enter language codes": "Introduceți codurile limbilor",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Introduceți eticheta modelului (de ex. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Introduceți Numărul de Pași (de ex. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Introduceți Scorul",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Introduceți URL-ul Interogării Searxng",
+	"Enter Serper API Key": "Introduceți Cheia API Serper",
+	"Enter Serply API Key": "Introduceți Cheia API Serply",
+	"Enter Serpstack API Key": "Introduceți Cheia API Serpstack",
+	"Enter stop sequence": "Introduceți secvența de oprire",
+	"Enter system prompt": "Introduceți promptul de sistem",
+	"Enter Tavily API Key": "Introduceți Cheia API Tavily",
+	"Enter Tika Server URL": "Introduceți URL-ul Serverului Tika",
+	"Enter Top K": "Introduceți Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Introduceți URL-ul (de ex. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Introduceți URL-ul (de ex. http://localhost:11434)",
+	"Enter Your Email": "Introduceți Email-ul Dvs.",
+	"Enter Your Full Name": "Introduceți Numele Dvs. Complet",
+	"Enter your message": "Introduceți mesajul dvs.",
+	"Enter Your Password": "Introduceți Parola Dvs.",
+	"Enter Your Role": "Introduceți Rolul Dvs.",
+	"Error": "Eroare",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Experimental",
+	"Export": "Exportă",
+	"Export All Chats (All Users)": "Exportă Toate Conversațiile (Toți Utilizatorii)",
+	"Export chat (.json)": "Exportă conversația (.json)",
+	"Export Chats": "Exportă Conversațiile",
+	"Export Config to JSON File": "",
+	"Export Functions": "Exportă Funcțiile",
+	"Export LiteLLM config.yaml": "Exportă Configurația LiteLLM config.yaml",
+	"Export Models": "Exportă Modelele",
+	"Export Prompts": "Exportă Prompturile",
+	"Export Tools": "Exportă Instrumentele",
+	"External Models": "Modele Externe",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Crearea cheii API a eșuat.",
+	"Failed to read clipboard contents": "Citirea conținutului clipboard-ului a eșuat",
+	"Failed to update settings": "Actualizarea setărilor a eșuat",
+	"Failed to upload file.": "",
+	"February": "Februarie",
+	"Feedback History": "",
+	"Feel free to add specific details": "Adăugați detalii specifice fără nicio ezitare",
+	"File": "Fișier",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Mod Fișier",
+	"File not found.": "Fișierul nu a fost găsit.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "Fișiere",
+	"Filter is now globally disabled": "Filtrul este acum dezactivat global",
+	"Filter is now globally enabled": "Filtrul este acum activat global",
+	"Filters": "Filtre",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Detectată falsificarea amprentelor: Nu se pot folosi inițialele ca avatar. Se utilizează imaginea de profil implicită.",
+	"Fluidly stream large external response chunks": "Transmite fluent blocuri mari de răspuns extern",
+	"Focus chat input": "Focalizează câmpul de intrare pentru conversație",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "A urmat instrucțiunile perfect",
+	"Form": "Formular",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Penalizare de Frecvență",
+	"Function": "",
+	"Function created successfully": "Funcția a fost creată cu succes",
+	"Function deleted successfully": "Funcția a fost ștearsă cu succes",
+	"Function Description (e.g. A filter to remove profanity from text)": "Descrierea Funcției (de ex. Un filtru pentru a elimina profanitatea din text)",
+	"Function ID (e.g. my_filter)": "ID Funcție (de ex. my_filter)",
+	"Function is now globally disabled": "Funcția este acum dezactivată global",
+	"Function is now globally enabled": "Funcția este acum activată global",
+	"Function Name (e.g. My Filter)": "Nume Funcție (de ex. My Filter)",
+	"Function updated successfully": "Funcția a fost actualizată cu succes",
+	"Functions": "Funcții",
+	"Functions allow arbitrary code execution": "Funcțiile permit executarea arbitrară a codului",
+	"Functions allow arbitrary code execution.": "Funcțiile permit executarea arbitrară a codului.",
+	"Functions imported successfully": "Funcțiile au fost importate cu succes",
+	"General": "General",
+	"General Settings": "Setări Generale",
+	"Generate Image": "Generează Imagine",
+	"Generating search query": "Se generează interogarea de căutare",
+	"Generation Info": "Informații Generare",
+	"Get up and running with": "Începeți și rulați cu",
+	"Global": "Global",
+	"Good Response": "Răspuns Bun",
+	"Google PSE API Key": "Cheie API Google PSE",
+	"Google PSE Engine Id": "ID Motor Google PSE",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "nu are conversații.",
+	"Hello, {{name}}": "Salut, {{name}}",
+	"Help": "Ajutor",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Ascunde",
+	"Hide Model": "Ascunde Modelul",
+	"How can I help you today?": "Cum te pot ajuta astăzi?",
+	"Hybrid Search": "Căutare Hibridă",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Recunosc că am citit și înțeleg implicațiile acțiunii mele. Sunt conștient de riscurile asociate cu executarea codului arbitrar și am verificat fiabilitatea sursei.",
+	"ID": "",
+	"Image Generation (Experimental)": "Generare Imagine (Experimental)",
+	"Image Generation Engine": "Motor de Generare a Imaginilor",
+	"Image Settings": "Setări Imagine",
+	"Images": "Imagini",
+	"Import Chats": "Importă Conversațiile",
+	"Import Config from JSON File": "",
+	"Import Functions": "Importă Funcțiile",
+	"Import Models": "Importă Modelele",
+	"Import Prompts": "Importă Prompturile",
+	"Import Tools": "Importă Instrumentele",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Includeți flag-ul `--api-auth` când rulați stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Includeți flag-ul `--api` când rulați stable-diffusion-webui",
+	"Info": "Informații",
+	"Input commands": "Comenzi de intrare",
+	"Install from Github URL": "Instalează de la URL-ul Github",
+	"Instant Auto-Send After Voice Transcription": "Trimitere Automată Instantanee După Transcrierea Vocii",
+	"Interface": "Interfață",
+	"Invalid file format.": "",
+	"Invalid Tag": "Etichetă Invalidă",
+	"January": "Ianuarie",
+	"join our Discord for help.": "alătură-te Discord-ului nostru pentru ajutor.",
+	"JSON": "JSON",
+	"JSON Preview": "Previzualizare JSON",
+	"July": "Iulie",
+	"June": "Iunie",
+	"JWT Expiration": "Expirarea JWT",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Menține Activ",
+	"Keyboard shortcuts": "Scurtături de la Tastatură",
+	"Knowledge": "Cunoștințe",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Limbă",
+	"large language models, locally.": "modele mari de limbaj, local.",
+	"Last Active": "Ultima Activitate",
+	"Last Modified": "Ultima Modificare",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Luminos",
+	"Listening...": "Ascult...",
+	"LLMs can make mistakes. Verify important information.": "LLM-urile pot face greșeli. Verificați informațiile importante.",
+	"Local Models": "Modele Locale",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Realizat de Comunitatea OpenWebUI",
+	"Make sure to enclose them with": "Asigurați-vă că le închideți cu",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Gestionează",
+	"Manage Arena Models": "",
+	"Manage Models": "Gestionează Modelele",
+	"Manage Ollama Models": "Gestionează Modelele Ollama",
+	"Manage Pipelines": "Gestionează Conductele",
+	"March": "Martie",
+	"Max Tokens (num_predict)": "Număr Maxim de Tokeni (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maxim 3 modele pot fi descărcate simultan. Vă rugăm să încercați din nou mai târziu.",
+	"May": "Mai",
+	"Memories accessible by LLMs will be shown here.": "Memoriile accesibile de LLM-uri vor fi afișate aici.",
+	"Memory": "Memorie",
+	"Memory added successfully": "Memoria a fost adăugată cu succes",
+	"Memory cleared successfully": "Memoria a fost ștearsă cu succes",
+	"Memory deleted successfully": "Memoria a fost ștearsă cu succes",
+	"Memory updated successfully": "Memoria a fost actualizată cu succes",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Mesajele pe care le trimiteți după crearea link-ului dvs. nu vor fi partajate. Utilizatorii cu URL-ul vor putea vizualiza conversația partajată.",
+	"Min P": "",
+	"Minimum Score": "Scor Minim",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Modelul '{{modelName}}' a fost descărcat cu succes.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Modelul '{{modelTag}}' este deja în coada de descărcare.",
+	"Model {{modelId}} not found": "Modelul {{modelId}} nu a fost găsit",
+	"Model {{modelName}} is not vision capable": "Modelul {{modelName}} nu are capacități de viziune",
+	"Model {{name}} is now {{status}}": "Modelul {{name}} este acum {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "Modelul a fost creat cu succes!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Calea sistemului de fișiere al modelului detectată. Este necesar numele scurt al modelului pentru actualizare, nu se poate continua.",
+	"Model ID": "ID Model",
+	"Model Name": "",
+	"Model not selected": "Modelul nu a fost selectat",
+	"Model Params": "Parametri Model",
+	"Model updated successfully": "Modelul a fost actualizat cu succes",
+	"Model Whitelisting": "Model pe Lista Albă",
+	"Model(s) Whitelisted": "Model(e) pe Lista Albă",
+	"Modelfile Content": "Conținutul Fișierului Model",
+	"Models": "Modele",
+	"more": "",
+	"More": "Mai multe",
+	"Move to Top": "",
+	"Name": "Nume",
+	"Name your model": "Denumirea modelului",
+	"New Chat": "Conversație Nouă",
+	"New folder": "",
+	"New Password": "Parolă Nouă",
+	"No content found": "",
+	"No content to speak": "Nu există conținut de vorbit",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Nu a fost selectat niciun fișier",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Nu au fost găsite rezultate",
+	"No search query generated": "Nu a fost generată nicio interogare de căutare",
+	"No source available": "Nicio sursă disponibilă",
+	"No valves to update": "Nu există valve de actualizat",
+	"None": "Niciunul",
+	"Not factually correct": "Nu este corect din punct de vedere factual",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Notă: Dacă setați un scor minim, căutarea va returna doar documente cu un scor mai mare sau egal cu scorul minim.",
+	"Notes": "",
+	"Notifications": "Notificări",
+	"November": "Noiembrie",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "ID OAuth",
+	"October": "Octombrie",
+	"Off": "Dezactivat",
+	"Okay, Let's Go!": "Ok, Să Începem!",
+	"OLED Dark": "Întunecat OLED",
+	"Ollama": "Ollama",
+	"Ollama API": "API Ollama",
+	"Ollama API disabled": "API Ollama dezactivat",
+	"Ollama API is disabled": "API Ollama este dezactivat",
+	"Ollama Version": "Versiune Ollama",
+	"On": "Activat",
+	"Only": "Doar",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Doar caracterele alfanumerice și cratimele sunt permise în șirul de comandă.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Se pare că URL-ul este invalid. Vă rugăm să verificați din nou și să încercați din nou.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! Utilizați o metodă nesuportată (doar frontend). Vă rugăm să serviți WebUI din backend.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Deschide conversație nouă",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Versiunea Open WebUI (v{{OPEN_WEBUI_VERSION}}) este mai mică decât versiunea necesară (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API OpenAI",
+	"OpenAI API Config": "Configurația API OpenAI",
+	"OpenAI API Key is required.": "Este necesară cheia API OpenAI.",
+	"OpenAI URL/Key required.": "Este necesar URL-ul/Cheia OpenAI.",
+	"or": "sau",
+	"Other": "Altele",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Parolă",
+	"PDF document (.pdf)": "Document PDF (.pdf)",
+	"PDF Extract Images (OCR)": "Extrage Imagini PDF (OCR)",
+	"pending": "în așteptare",
+	"Permission denied when accessing media devices": "Permisiunea refuzată la accesarea dispozitivelor media",
+	"Permission denied when accessing microphone": "Permisiunea refuzată la accesarea microfonului",
+	"Permission denied when accessing microphone: {{error}}": "Permisiunea refuzată la accesarea microfonului: {{error}}",
+	"Personalization": "Personalizare",
+	"Pin": "Fixează",
+	"Pinned": "Fixat",
+	"Pipeline deleted successfully": "Conducta a fost ștearsă cu succes",
+	"Pipeline downloaded successfully": "Conducta a fost descărcată cu succes",
+	"Pipelines": "Conducte",
+	"Pipelines Not Detected": "Conducte Nedetectate",
+	"Pipelines Valves": "Valvele Conductelor",
+	"Plain text (.txt)": "Text simplu (.txt)",
+	"Playground": "Teren de Joacă",
+	"Please carefully review the following warnings:": "Vă rugăm să revizuiți cu atenție următoarele avertismente:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Atitudine pozitivă",
+	"Previous 30 days": "Ultimele 30 de zile",
+	"Previous 7 days": "Ultimele 7 zile",
+	"Profile Image": "Imagine de Profil",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (de ex. Spune-mi un fapt amuzant despre Imperiul Roman)",
+	"Prompt Content": "Conținut Prompt",
+	"Prompt suggestions": "Sugestii de Prompt",
+	"Prompts": "Prompturi",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Extrage \"{{searchValue}}\" de pe Ollama.com",
+	"Pull a model from Ollama.com": "Extrage un model de pe Ollama.com",
+	"Query Params": "Parametri Interogare",
+	"RAG Template": "Șablon RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Citește cu Voce Tare",
+	"Record voice": "Înregistrează vocea",
+	"Redirecting you to OpenWebUI Community": "Vă redirecționăm către Comunitatea OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Referiți-vă la dvs. ca \"Utilizator\" (de ex., \"Utilizatorul învață spaniolă\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Refuzat când nu ar fi trebuit",
+	"Regenerate": "Regenerare",
+	"Release Notes": "Note de Lansare",
+	"Relevance": "",
+	"Remove": "Înlătură",
+	"Remove Model": "Înlătură Modelul",
+	"Rename": "Redenumește",
+	"Repeat Last N": "Repetă Ultimele N",
+	"Request Mode": "Mod de Cerere",
+	"Reranking Model": "Model de Rearanjare",
+	"Reranking model disabled": "Modelul de Rearanjare este dezactivat",
+	"Reranking model set to \"{{reranking_model}}\"": "Modelul de Rearanjare setat la \"{{reranking_model}}\"",
+	"Reset": "Resetează",
+	"Reset Upload Directory": "Resetează Directorul de Încărcare",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Copiere Automată a Răspunsului în Clipboard",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Notificările de răspuns nu pot fi activate deoarece permisiunile site-ului au fost refuzate. Vă rugăm să vizitați setările browserului pentru a acorda accesul necesar.",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rol",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Rulați Llama 2, Code Llama și alte modele. Personalizați și creați-vă propriile modele.",
+	"Running": "Rulare",
+	"Save": "Salvează",
+	"Save & Create": "Salvează & Creează",
+	"Save & Update": "Salvează & Actualizează",
+	"Save As Copy": "",
+	"Save Tag": "Salvează Eticheta",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Salvarea jurnalelor de conversație direct în stocarea browserului dvs. nu mai este suportată. Vă rugăm să luați un moment pentru a descărca și a șterge jurnalele de conversație făcând clic pe butonul de mai jos. Nu vă faceți griji, puteți reimporta ușor jurnalele de conversație în backend prin",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Caută",
+	"Search a model": "Caută un model",
+	"Search Chats": "Caută în Conversații",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Caută Funcții",
+	"Search Knowledge": "",
+	"Search Models": "Caută Modele",
+	"Search Prompts": "Caută Prompturi",
+	"Search Query Generation Prompt": "Prompt de Generare Interogare de Căutare",
+	"Search Result Count": "Număr Rezultate Căutare",
+	"Search Tools": "Caută Instrumente",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "{{count}} site căutat",
+	"Searched {{count}} sites_few": "",
+	"Searched {{count}} sites_other": "{{count}} alte site-uri căutate",
+	"Searching \"{{searchQuery}}\"": "Căutare \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "URL Interogare Searxng",
+	"See readme.md for instructions": "Consultați readme.md pentru instrucțiuni",
+	"See what's new": "Vezi ce e nou",
+	"Seed": "Sămânță",
+	"Select a base model": "Selectează un model de bază",
+	"Select a engine": "Selectează un motor",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Selectează o funcție",
+	"Select a model": "Selectează un model",
+	"Select a pipeline": "Selectează o conductă",
+	"Select a pipeline url": "Selectează un URL de conductă",
+	"Select a tool": "Selectează un instrument",
+	"Select an Ollama instance": "Selectează o instanță Ollama",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Selectează model",
+	"Select only one model to call": "Selectează doar un singur model pentru apel",
+	"Selected model(s) do not support image inputs": "Modelul(e) selectat(e) nu suportă intrări de imagine",
+	"Semantic distance to query": "",
+	"Send": "Trimite",
+	"Send a Message": "Trimite un Mesaj",
+	"Send message": "Trimite mesajul",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "Septembrie",
+	"Serper API Key": "Cheie API Serper",
+	"Serply API Key": "Cheie API Serply",
+	"Serpstack API Key": "Cheie API Serpstack",
+	"Server connection verified": "Conexiunea la server a fost verificată",
+	"Set as default": "Setează ca implicit",
+	"Set CFG Scale": "",
+	"Set Default Model": "Setează Modelul Implicit",
+	"Set embedding model (e.g. {{model}})": "Setează modelul de încapsulare (de ex. {{model}})",
+	"Set Image Size": "Setează Dimensiunea Imaginilor",
+	"Set reranking model (e.g. {{model}})": "Setează modelul de rearanjare (de ex. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Setează Pași",
+	"Set Task Model": "Setează Model de Sarcină",
+	"Set Voice": "Setează Voce",
+	"Set whisper model": "",
+	"Settings": "Setări",
+	"Settings saved successfully!": "Setările au fost salvate cu succes!",
+	"Share": "Partajează",
+	"Share Chat": "Partajează Conversația",
+	"Share to OpenWebUI Community": "Partajează cu Comunitatea OpenWebUI",
+	"short-summary": "scurt-sumar",
+	"Show": "Afișează",
+	"Show Admin Details in Account Pending Overlay": "Afișează Detaliile Administratorului în Suprapunerea Contului În Așteptare",
+	"Show Model": "Afișează Modelul",
+	"Show shortcuts": "Afișează scurtături",
+	"Show your support!": "Arată-ți susținerea!",
+	"Showcased creativity": "Creativitate expusă",
+	"Sign in": "Autentificare",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Deconectare",
+	"Sign up": "Înregistrare",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Sursă",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Eroare de recunoaștere vocală: {{error}}",
+	"Speech-to-Text Engine": "Motor de Conversie a Vocii în Text",
+	"Stop": "",
+	"Stop Sequence": "Oprește Secvența",
+	"Stream Chat Response": "",
+	"STT Model": "Model STT",
+	"STT Settings": "Setări STT",
+	"Subtitle (e.g. about the Roman Empire)": "Subtitlu (de ex. despre Imperiul Roman)",
+	"Success": "Succes",
+	"Successfully updated.": "Actualizat cu succes.",
+	"Suggested": "Sugerat",
+	"Support": "Suport",
+	"Support this plugin:": "Susține acest plugin:",
+	"Sync directory": "",
+	"System": "Sistem",
+	"System Instructions": "",
+	"System Prompt": "Prompt de Sistem",
+	"Tags": "Etichete",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Apasă pentru a întrerupe",
+	"Tavily API Key": "Cheie API Tavily",
+	"Tell us more:": "Spune-ne mai multe:",
+	"Temperature": "Temperatură",
+	"Template": "Șablon",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Motor de Conversie a Textului în Vorbire",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Mulțumim pentru feedback!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Dezvoltatorii din spatele acestui plugin sunt voluntari pasionați din comunitate. Dacă considerați acest plugin util, vă rugăm să luați în considerare contribuția la dezvoltarea sa.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Scorul ar trebui să fie o valoare între 0.0 (0%) și 1.0 (100%).",
+	"Theme": "Temă",
+	"Thinking...": "Gândește...",
+	"This action cannot be undone. Do you wish to continue?": "Această acțiune nu poate fi anulată. Doriți să continuați?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Acest lucru asigură că conversațiile dvs. valoroase sunt salvate în siguranță în baza de date a backend-ului dvs. Mulțumim!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Aceasta este o funcție experimentală, poate să nu funcționeze așa cum vă așteptați și este supusă schimbării în orice moment.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Aceasta va șterge",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Explicație detaliată",
+	"Tika": "Tika",
+	"Tika Server URL required.": "Este necesar URL-ul serverului Tika.",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Sfat: Actualizați mai multe sloturi de variabile consecutiv apăsând tasta tab în câmpul de intrare al conversației după fiecare înlocuire.",
+	"Title": "Titlu",
+	"Title (e.g. Tell me a fun fact)": "Titlu (de ex. Spune-mi un fapt amuzant)",
+	"Title Auto-Generation": "Generare Automată a Titlului",
+	"Title cannot be an empty string.": "Titlul nu poate fi un șir gol.",
+	"Title Generation Prompt": "Prompt de Generare a Titlului",
+	"To access the available model names for downloading,": "Pentru a accesa numele modelelor disponibile pentru descărcare,",
+	"To access the GGUF models available for downloading,": "Pentru a accesa modelele GGUF disponibile pentru descărcare,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Pentru a accesa WebUI, vă rugăm să contactați administratorul. Administratorii pot gestiona statusurile utilizatorilor din Panoul de Administrare.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "către câmpul de intrare al conversației.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Pentru a selecta acțiuni aici, adăugați-le mai întâi în spațiul de lucru \"Funcții\".",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Pentru a selecta filtrele aici, adăugați-le mai întâi în spațiul de lucru \"Funcții\".",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Pentru a selecta kiturile de instrumente aici, adăugați-le mai întâi în spațiul de lucru \"Instrumente\".",
+	"Toast notifications for new updates": "",
+	"Today": "Astăzi",
+	"Toggle settings": "Comută setările",
+	"Toggle sidebar": "Comută bara laterală",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Tokeni de Păstrat la Reîmprospătarea Contextului (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Instrumentul a fost creat cu succes",
+	"Tool deleted successfully": "Instrumentul a fost șters cu succes",
+	"Tool imported successfully": "Instrumentul a fost importat cu succes",
+	"Tool updated successfully": "Instrumentul a fost actualizat cu succes",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Descrierea Kiturilor de Instrumente (de ex. Un kit de instrumente pentru efectuarea diferitelor operațiuni)",
+	"Toolkit ID (e.g. my_toolkit)": "ID Kit de Instrumente (de ex. my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Nume Kit de Instrumente (de ex. My ToolKit)",
+	"Tools": "Instrumente",
+	"Tools are a function calling system with arbitrary code execution": "Instrumentele sunt un sistem de apelare a funcțiilor cu executare arbitrară a codului",
+	"Tools have a function calling system that allows arbitrary code execution": "Instrumentele au un sistem de apelare a funcțiilor care permite executarea arbitrară a codului",
+	"Tools have a function calling system that allows arbitrary code execution.": "Instrumentele au un sistem de apelare a funcțiilor care permite executarea arbitrară a codului.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Probleme la accesarea Ollama?",
+	"TTS Model": "Model TTS",
+	"TTS Settings": "Setări TTS",
+	"TTS Voice": "Voce TTS",
+	"Type": "Tip",
+	"Type Hugging Face Resolve (Download) URL": "Introduceți URL-ul de Rezolvare (Descărcare) Hugging Face",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! A apărut o problemă la conectarea la {{provider}}.",
+	"UI": "Interfață Utilizator",
+	"Unpin": "Anulează Fixarea",
+	"Untagged": "",
+	"Update": "Actualizează",
+	"Update and Copy Link": "Actualizează și Copiază Link-ul",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Actualizează parola",
+	"Updated": "",
+	"Updated at": "Actualizat la",
+	"Updated At": "",
+	"Upload": "Încărcare",
+	"Upload a GGUF model": "Încarcă un model GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Încarcă Fișiere",
+	"Upload Pipeline": "Încarcă Conducta",
+	"Upload Progress": "Progres Încărcare",
+	"URL Mode": "Mod URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Folosește Gravatar",
+	"Use Initials": "Folosește Inițialele",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "utilizator",
+	"User": "",
+	"User location successfully retrieved.": "Localizarea utilizatorului a fost preluată cu succes.",
+	"User Permissions": "Permisiuni Utilizator",
+	"Users": "Utilizatori",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Utilizează",
+	"Valid time units:": "Unități de timp valide:",
+	"Valves": "Valve",
+	"Valves updated": "Valve actualizate",
+	"Valves updated successfully": "Valve actualizate cu succes",
+	"variable": "variabilă",
+	"variable to have them replaced with clipboard content.": "variabilă pentru a fi înlocuite cu conținutul clipboard-ului.",
+	"Version": "Versiune",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Voce",
+	"Voice Input": "",
+	"Warning": "Avertisment",
+	"Warning:": "Avertisment:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Avertisment: Dacă actualizați sau schimbați modelul de încapsulare, va trebui să reimportați toate documentele.",
+	"Web": "Web",
+	"Web API": "API Web",
+	"Web Loader Settings": "Setări Încărcător Web",
+	"Web Search": "Căutare Web",
+	"Web Search Engine": "Motor de Căutare Web",
+	"Webhook URL": "URL Webhook",
+	"WebUI Settings": "Setări WebUI",
+	"WebUI will make requests to": "WebUI va face cereri către",
+	"What’s New in": "Ce e Nou în",
+	"Whisper (Local)": "Whisper (Local)",
+	"Widescreen Mode": "Mod Ecran Larg",
+	"Won": "",
+	"Workspace": "Spațiu de Lucru",
+	"Write a prompt suggestion (e.g. Who are you?)": "Scrieți o sugestie de prompt (de ex. Cine ești?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Scrieți un rezumat în 50 de cuvinte care rezumă [subiect sau cuvânt cheie].",
+	"Write something...": "",
+	"Yesterday": "Ieri",
+	"You": "Tu",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Puteți personaliza interacțiunile dvs. cu LLM-urile adăugând amintiri prin butonul 'Gestionează' de mai jos, făcându-le mai utile și adaptate la dvs.",
+	"You cannot clone a base model": "Nu puteți clona un model de bază",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Nu aveți conversații arhivate.",
+	"You have shared this chat": "Ați partajat această conversație",
+	"You're a helpful assistant.": "Ești un asistent util.",
+	"You're now logged in.": "Acum ești autentificat.",
+	"Your account status is currently pending activation.": "Statusul contului dvs. este în așteptare pentru activare.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Întreaga dvs. contribuție va merge direct la dezvoltatorul plugin-ului; Open WebUI nu ia niciun procent. Cu toate acestea, platforma de finanțare aleasă ar putea avea propriile taxe.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Setări Încărcător Youtube"
+}
diff --git a/src/lib/i18n/locales/ru-RU/translation.json b/src/lib/i18n/locales/ru-RU/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..8fc8422dfc33ff2808d36ed57952f5d6d8bf3c4c
--- /dev/null
+++ b/src/lib/i18n/locales/ru-RU/translation.json
@@ -0,0 +1,853 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' или '-1' чтобы был без срока годности.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(например, `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(например, `sh webui.sh --api`)",
+	"(latest)": "(последняя)",
+	"{{ models }}": "{{ модели }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Вы не можете удалить базовую модель",
+	"{{user}}'s Chats": "Чаты {{user}}'а",
+	"{{webUIName}} Backend Required": "Необходимо подключение к серверу {{webUIName}}",
+	"*Prompt node ID(s) are required for image generation": "ID узлов промптов обязательны для генерации изображения",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "Новая версия (v{{LATEST_VERSION}}) теперь доступна.",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Модель задач используется при выполнении таких задач, как генерация заголовков для чатов и поисковых запросов в Интернете",
+	"a user": "пользователь",
+	"About": "О программе",
+	"Account": "Учетная запись",
+	"Account Activation Pending": "Ожидание активации учетной записи",
+	"Accurate information": "Точная информация",
+	"Actions": "Действия",
+	"Active Users": "Активные пользователи",
+	"Add": "Добавить",
+	"Add a model id": "Добавьте ID модели",
+	"Add a short description about what this model does": "Добавьте краткое описание того, что делает эта модель",
+	"Add a short title for this prompt": "Добавьте краткий заголовок для этого ввода",
+	"Add a tag": "Добавьте тег",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Добавьте пользовательский промпт",
+	"Add Files": "Добавить файлы",
+	"Add Memory": "Добавить воспоминание",
+	"Add Model": "Добавить модель",
+	"Add Tag": "Добавить тег",
+	"Add Tags": "Добавить теги",
+	"Add text content": "",
+	"Add User": "Добавить пользователя",
+	"Adjusting these settings will apply changes universally to all users.": "Изменения в этих настройках будут применены для всех пользователей.",
+	"admin": "админ",
+	"Admin": "Админ",
+	"Admin Panel": "Админ панель",
+	"Admin Settings": "Настройки администратора",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Администраторы всегда имеют доступ ко всем инструментам; пользователям нужны инструменты, назначенные для каждой модели в рабочем пространстве.",
+	"Advanced Parameters": "Расширенные Параметры",
+	"Advanced Params": "Расширенные параметры",
+	"All chats": "",
+	"All Documents": "Все документы",
+	"Allow Chat Deletion": "Разрешить удаление чата",
+	"Allow Chat Editing": "Разрешить редактирование чата",
+	"Allow non-local voices": "Разрешить не локальные голоса",
+	"Allow Temporary Chat": "Разрешить временные чаты",
+	"Allow User Location": "Разрешить доступ к местоположению пользователя",
+	"Allow Voice Interruption in Call": "Разрешить прерывание голоса во время вызова",
+	"alphanumeric characters and hyphens": "буквенно цифровые символы и дефисы",
+	"Already have an account?": "У вас уже есть учетная запись?",
+	"an assistant": "ассистент",
+	"and": "и",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "и создайте новую общую ссылку.",
+	"API Base URL": "Базовый адрес API",
+	"API Key": "Ключ API",
+	"API Key created.": "Ключ API создан.",
+	"API keys": "Ключи API",
+	"April": "Апрель",
+	"Archive": "Архив",
+	"Archive All Chats": "Архивировать все чаты",
+	"Archived Chats": "Архив чатов",
+	"are allowed - Activate this command by typing": "разрешено - активируйте эту команду вводом",
+	"Are you sure?": "Вы уверены?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Прикрепить файл",
+	"Attention to detail": "Внимание к деталям",
+	"Audio": "Аудио",
+	"August": "Август",
+	"Auto-playback response": "Автоматическое воспроизведение ответа",
+	"Automatic1111": "Automatic1111",
+	"AUTOMATIC1111 Api Auth String": "строка авторизации API AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL": "Базовый URL адрес AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "Необходим базовый адрес URL AUTOMATIC1111.",
+	"Available list": "Список доступных",
+	"available!": "доступно!",
+	"Azure AI Speech": "Azure AI Speech",
+	"Azure Region": "Регион Azure",
+	"Back": "Назад",
+	"Bad Response": "Плохой ответ",
+	"Banners": "Баннеры",
+	"Base Model (From)": "Базовая модель (от)",
+	"Batch Size (num_batch)": "Размер партии (num_batch)",
+	"before": "до",
+	"Being lazy": "Лениво",
+	"Brave Search API Key": "Ключ API поиска Brave",
+	"Bypass SSL verification for Websites": "Обход проверки SSL для веб-сайтов",
+	"Call": "Вызов",
+	"Call feature is not supported when using Web STT engine": "Функция вызова не поддерживается при использовании Web STT (распознавание речи) движка",
+	"Camera": "Камера",
+	"Cancel": "Отменить",
+	"Capabilities": "Возможности",
+	"Change Password": "Изменить пароль",
+	"Character": "",
+	"Chat": "Чат",
+	"Chat Background Image": "Фоновое изображение чата",
+	"Chat Bubble UI": "Bubble UI чат",
+	"Chat Controls": "Управление чатом",
+	"Chat direction": "Направление чата",
+	"Chat Overview": "Обзор чата",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Чаты",
+	"Check Again": "Перепроверьте ещё раз",
+	"Check for updates": "Проверить обновления",
+	"Checking for updates...": "Проверка обновлений...",
+	"Choose a model before saving...": "Выберите модель перед сохранением...",
+	"Chunk Overlap": "Перекрытие фрагментов",
+	"Chunk Params": "Параметры фрагментов",
+	"Chunk Size": "Размер фрагмента",
+	"Citation": "Цитирование",
+	"Clear memory": "Очистить воспоминания",
+	"Click here for help.": "Нажмите здесь для получения помощи.",
+	"Click here to": "Нажмите здесь, чтобы",
+	"Click here to download user import template file.": "Нажмите здесь, чтобы загрузить файл шаблона импорта пользователя",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Нажмите здесь, чтобы выбрать",
+	"Click here to select a csv file.": "Нажмите здесь, чтобы выбрать csv-файл.",
+	"Click here to select a py file.": "Нажмите здесь, чтобы выбрать py-файл",
+	"Click here to upload a workflow.json file.": "Нажмите здесь, чтобы загрузить файл workflow.json.",
+	"click here.": "нажмите здесь.",
+	"Click on the user role button to change a user's role.": "Нажмите кнопку роли пользователя, чтобы изменить роль пользователя.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "В разрешении на запись в буфер обмена отказано. Пожалуйста, проверьте настройки своего браузера, чтобы предоставить необходимый доступ.",
+	"Clone": "Клонировать",
+	"Close": "Закрыть",
+	"Code execution": "",
+	"Code formatted successfully": "Код успешно отформатирован",
+	"Collection": "Коллекция",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "Базовый адрес URL ComfyUI",
+	"ComfyUI Base URL is required.": "Необходим базовый адрес URL ComfyUI.",
+	"ComfyUI Workflow": "ComfyUI Workflow",
+	"ComfyUI Workflow Nodes": "Узлы ComfyUI Workflow",
+	"Command": "Команда",
+	"Completions": "",
+	"Concurrent Requests": "Одновременные запросы",
+	"Confirm": "Подтвердить",
+	"Confirm Password": "Подтвердите пароль",
+	"Confirm your action": "Подтвердите свое действие",
+	"Connections": "Соединение",
+	"Contact Admin for WebUI Access": "Обратитесь к администратору для получения доступа к WebUI",
+	"Content": "Содержание",
+	"Content Extraction": "Извлечение контента",
+	"Context Length": "Длина контекста",
+	"Continue Response": "Продолжить ответ",
+	"Continue with {{provider}}": "Продолжить с {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "Управляйте разделением текста сообщения для запросов TTS. 'Пунктуация' разделяет на предложения, 'абзацы' - разделяет на абзацы, а 'нет' сохраняет сообщение в виде одной строки.",
+	"Controls": "Управление",
+	"Copied": "Скопировано",
+	"Copied shared chat URL to clipboard!": "Копирование в буфер обмена выполнено успешно!",
+	"Copied to clipboard": "Скопировано в буфер обмена",
+	"Copy": "Копировать",
+	"Copy last code block": "Копировать последний блок кода",
+	"Copy last response": "Копировать последний ответ",
+	"Copy Link": "Копировать ссылку",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Копирование в буфер обмена прошло успешно!",
+	"Create a model": "Создание модели",
+	"Create Account": "Создать аккаунт",
+	"Create Knowledge": "",
+	"Create new key": "Создать новый ключ",
+	"Create new secret key": "Создать новый секретный ключ",
+	"Created at": "Создано",
+	"Created At": "Создано",
+	"Created by": "Создано",
+	"CSV Import": "Импорт CSV",
+	"Current Model": "Текущая модель",
+	"Current Password": "Текущий пароль",
+	"Custom": "Пользовательский",
+	"Customize models for a specific purpose": "Настройка моделей для конкретных целей",
+	"Dark": "Темная",
+	"Dashboard": "Панель управления",
+	"Database": "База данных",
+	"December": "Декабрь",
+	"Default": "По умолчанию",
+	"Default (Open AI)": "По умолчанию (Open AI)",
+	"Default (SentenceTransformers)": "По умолчанию (SentenceTransformers)",
+	"Default Model": "Модель по умолчанию",
+	"Default model updated": "Модель по умолчанию обновлена",
+	"Default Prompt Suggestions": "Предложения промптов по умолчанию",
+	"Default User Role": "Роль пользователя по умолчанию",
+	"Delete": "Удалить",
+	"Delete a model": "Удалить модель",
+	"Delete All Chats": "Удалить все чаты",
+	"Delete chat": "Удалить чат",
+	"Delete Chat": "Удалить чат",
+	"Delete chat?": "Удалить чат?",
+	"Delete folder?": "",
+	"Delete function?": "Удалить функцию?",
+	"Delete prompt?": "Удалить промпт?",
+	"delete this link": "удалить эту ссылку",
+	"Delete tool?": "Удалить этот инструмент?",
+	"Delete User": "Удалить пользователя",
+	"Deleted {{deleteModelTag}}": "Удалено {{deleteModelTag}}",
+	"Deleted {{name}}": "Удалено {{name}}",
+	"Description": "Описание",
+	"Didn't fully follow instructions": "Не полностью следует инструкциям",
+	"Disabled": "Отключено",
+	"Discover a function": "Найти функцию",
+	"Discover a model": "Найти модель",
+	"Discover a prompt": "Найти промпт",
+	"Discover a tool": "Найти инструмент",
+	"Discover, download, and explore custom functions": "Находите, загружайте и исследуйте пользовательские функции",
+	"Discover, download, and explore custom prompts": "Находите, загружайте и исследуйте пользовательские промпты",
+	"Discover, download, and explore custom tools": "Находите, загружайте и исследуйте пользовательские инструменты",
+	"Discover, download, and explore model presets": "Находите, загружайте и исследуйте пользовательские предустановки моделей",
+	"Dismissible": "Можно отклонить",
+	"Display Emoji in Call": "Отображать эмодзи в вызовах",
+	"Display the username instead of You in the Chat": "Отображать имя пользователя вместо 'Вы' в чате",
+	"Do not install functions from sources you do not fully trust.": "Не устанавливайте функции из источников, которым вы не полностью доверяете.",
+	"Do not install tools from sources you do not fully trust.": "Не устанавливайте инструменты из источников, которым вы не полностью доверяете.",
+	"Document": "Документ",
+	"Documentation": "Документация",
+	"Documents": "Документы",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "не устанавливает никаких внешних соединений, и ваши данные надежно хранятся на вашем локальном сервере.",
+	"Don't have an account?": "У вас нет аккаунта?",
+	"don't install random functions from sources you don't trust.": "не устанавливайте случайные функции из источников, которым вы не доверяете.",
+	"don't install random tools from sources you don't trust.": "не устанавливайте случайные инструменты из источников, которым вы не доверяете.",
+	"Don't like the style": "Не нравится стиль",
+	"Done": "Готово",
+	"Download": "Загрузить",
+	"Download canceled": "Загрузка отменена",
+	"Download Database": "Загрузить базу данных",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Перетащите сюда файлы, чтобы добавить их в разговор",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "например, '30s','10m'. Допустимые единицы времени: 's', 'm', 'h'.",
+	"Edit": "Редактировать",
+	"Edit Arena Model": "",
+	"Edit Memory": "Редактировать воспоминание",
+	"Edit User": "Редактировать пользователя",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "Электронная почта",
+	"Embedding Batch Size": "Размер пакета для встраивания",
+	"Embedding Model": "Модель встраивания",
+	"Embedding Model Engine": "Движок модели встраивания",
+	"Embedding model set to \"{{embedding_model}}\"": "Модель встраивания установлена в \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Включить совместное использование",
+	"Enable Message Rating": "Разрешить оценку ответов",
+	"Enable New Sign Ups": "Разрешить новые регистрации",
+	"Enable Web Search": "Включить поиск в Интернете",
+	"Enable Web Search Query Generation": "Включить генерацию веб-поисковых запросов",
+	"Enabled": "Включено",
+	"Engine": "Движок",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Убедитесь, что ваш CSV-файл включает в себя 4 столбца в следующем порядке: Имя, Электронная почта, Пароль, Роль.",
+	"Enter {{role}} message here": "Введите сообщение {{role}} здесь",
+	"Enter a detail about yourself for your LLMs to recall": "Введите детали о себе, чтобы LLMs могли запомнить",
+	"Enter api auth string (e.g. username:password)": "Введите строку авторизации api (например, username:password)",
+	"Enter Brave Search API Key": "Введите ключ API поиска Brave",
+	"Enter CFG Scale (e.g. 7.0)": "Введите CFG Scale (например, 7.0)",
+	"Enter Chunk Overlap": "Введите перекрытие фрагмента",
+	"Enter Chunk Size": "Введите размер фрагмента",
+	"Enter description": "",
+	"Enter Github Raw URL": "Введите необработанный URL-адрес Github",
+	"Enter Google PSE API Key": "Введите ключ API Google PSE",
+	"Enter Google PSE Engine Id": "Введите Id движка Google PSE",
+	"Enter Image Size (e.g. 512x512)": "Введите размер изображения (например, 512x512)",
+	"Enter language codes": "Введите коды языков",
+	"Enter Model ID": "Введите ID модели",
+	"Enter model tag (e.g. {{modelTag}})": "Введите тег модели (например, {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Введите количество шагов (например, 50)",
+	"Enter Sampler (e.g. Euler a)": "Введите сэмплер (например, Euler a)",
+	"Enter Scheduler (e.g. Karras)": "Введите планировщик (например, Karras)",
+	"Enter Score": "Введите оценку",
+	"Enter SearchApi API Key": "Введите ключ API SearchApi",
+	"Enter SearchApi Engine": "Введите SearchApi движок",
+	"Enter Searxng Query URL": "Введите URL-адрес запроса Searxng",
+	"Enter Serper API Key": "Введите ключ API Serper",
+	"Enter Serply API Key": "Введите ключ API Serply",
+	"Enter Serpstack API Key": "Введите ключ API Serpstack",
+	"Enter stop sequence": "Введите последовательность остановки",
+	"Enter system prompt": "Введите системный промпт",
+	"Enter Tavily API Key": "Введите ключ API Tavily",
+	"Enter Tika Server URL": "Введите URL-адрес сервера Tika",
+	"Enter Top K": "Введите Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Введите URL-адрес (например, http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Введите URL-адрес (например, http://localhost:11434)",
+	"Enter Your Email": "Введите вашу электронную почту",
+	"Enter Your Full Name": "Введите ваше полное имя",
+	"Enter your message": "Введите ваше сообщение",
+	"Enter Your Password": "Введите ваш пароль",
+	"Enter Your Role": "Введите вашу роль",
+	"Error": "Ошибка",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Экспериментальное",
+	"Export": "Экспорт",
+	"Export All Chats (All Users)": "Экспортировать все чаты (всех пользователей)",
+	"Export chat (.json)": "Экспортировать чат (.json)",
+	"Export Chats": "Экспортировать чаты",
+	"Export Config to JSON File": "Экспорт конфигурации в JSON-файл",
+	"Export Functions": "Экспортировать функции",
+	"Export LiteLLM config.yaml": "Экспортировать LiteLLM config.yaml",
+	"Export Models": "Экспортировать модели",
+	"Export Prompts": "Экспортировать промпты",
+	"Export Tools": "Экспортировать инструменты",
+	"External Models": "Внешние модели",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Не удалось создать ключ API.",
+	"Failed to read clipboard contents": "Не удалось прочитать содержимое буфера обмена",
+	"Failed to update settings": "Не удалось обновить настройки",
+	"Failed to upload file.": "",
+	"February": "Февраль",
+	"Feedback History": "",
+	"Feel free to add specific details": "Не стесняйтесь добавлять конкретные детали",
+	"File": "Файл",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Режим файла",
+	"File not found.": "Файл не найден.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "Размер файла не должен превышать {{maxSize}} МБ.",
+	"Files": "Файлы",
+	"Filter is now globally disabled": "Фильтр теперь отключен глобально",
+	"Filter is now globally enabled": "Фильтр теперь включен глобально",
+	"Filters": "Фильтры",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Определение подделки отпечатка: Невозможно использовать инициалы в качестве аватара. По умолчанию используется изображение профиля по умолчанию.",
+	"Fluidly stream large external response chunks": "Плавная потоковая передача больших фрагментов внешних ответов",
+	"Focus chat input": "Фокус ввода чата",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Идеально соответствует инструкциям",
+	"Form": "Форма",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Штраф за частоту",
+	"Function": "",
+	"Function created successfully": "Функция успешно создана",
+	"Function deleted successfully": "Функция успешно удалена",
+	"Function Description (e.g. A filter to remove profanity from text)": "Описание функции (например, фильтр для удаления ненормативной лексики из текста)",
+	"Function ID (e.g. my_filter)": "ID функции (например my_filter)",
+	"Function is now globally disabled": "Функция теперь глобально отключена",
+	"Function is now globally enabled": "Функция теперь глобально включена",
+	"Function Name (e.g. My Filter)": "Имя функции (например My Filter)",
+	"Function updated successfully": "Функция успешно обновлена",
+	"Functions": "Функции",
+	"Functions allow arbitrary code execution": "Функции позволяют выполнять произвольный код",
+	"Functions allow arbitrary code execution.": "Функции позволяют выполнять произвольный код.",
+	"Functions imported successfully": "Функции успешно импортированы",
+	"General": "Общее",
+	"General Settings": "Общие настройки",
+	"Generate Image": "Сгенерировать изображение",
+	"Generating search query": "Генерация поискового запроса",
+	"Generation Info": "Информация о генерации",
+	"Get up and running with": "Начните работать с",
+	"Global": "Глобально",
+	"Good Response": "Хороший ответ",
+	"Google PSE API Key": "Ключ API Google PSE",
+	"Google PSE Engine Id": "Id движка Google PSE",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "Тактильная обратная связь",
+	"has no conversations.": "не имеет разговоров.",
+	"Hello, {{name}}": "Привет, {{name}}",
+	"Help": "Помощь",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Скрыть",
+	"Hide Model": "Скрыть модель",
+	"How can I help you today?": "Чем я могу помочь вам сегодня?",
+	"Hybrid Search": "Гибридная поисковая система",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Я подтверждаю, что прочитал и осознаю последствия своих действий. Я осознаю риски, связанные с выполнением произвольного кода, и я проверил достоверность источника.",
+	"ID": "",
+	"Image Generation (Experimental)": "Генерация изображений (Экспериментально)",
+	"Image Generation Engine": "Механизм генерации изображений",
+	"Image Settings": "Настройки изображения",
+	"Images": "Изображения",
+	"Import Chats": "Импортировать чаты",
+	"Import Config from JSON File": "Импорт конфигурации из JSON-файла",
+	"Import Functions": "Импортировать функции",
+	"Import Models": "Импортировать модели",
+	"Import Prompts": "Импортировать промпты",
+	"Import Tools": "Импортировать инструменты",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Добавьте флаг '--api-auth' при запуске stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Добавьте флаг `--api` при запуске stable-diffusion-webui",
+	"Info": "Информация",
+	"Input commands": "Введите команды",
+	"Install from Github URL": "Установка с URL-адреса Github",
+	"Instant Auto-Send After Voice Transcription": "Мгновенная автоматическая отправка после расшифровки голоса",
+	"Interface": "Интерфейс",
+	"Invalid file format.": "",
+	"Invalid Tag": "Недопустимый тег",
+	"January": "Январь",
+	"join our Discord for help.": "присоединяйтесь к нашему Discord для помощи.",
+	"JSON": "JSON",
+	"JSON Preview": "Предварительный просмотр JSON",
+	"July": "Июль",
+	"June": "Июнь",
+	"JWT Expiration": "Истечение срока JWT",
+	"JWT Token": "Токен JWT",
+	"Keep Alive": "Поддерживать активность",
+	"Keyboard shortcuts": "Горячие клавиши",
+	"Knowledge": "Знания",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Язык",
+	"large language models, locally.": "большими языковыми моделями, локально.",
+	"Last Active": "Последний активный",
+	"Last Modified": "Последнее изменение",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "Оставьте пустым для неограниченного",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "Оставьте пустым, чтобы использовать промпт по умолчанию, или введите пользовательский промпт",
+	"Light": "Светлый",
+	"Listening...": "Слушаю...",
+	"LLMs can make mistakes. Verify important information.": "LLMs могут допускать ошибки. Проверяйте важную информацию.",
+	"Local Models": "Локальные модели",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Сделано сообществом OpenWebUI",
+	"Make sure to enclose them with": "Убедитесь, что они заключены в",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "Убедитесь, что экспортируете файл workflow.json в формате API из ComfyUI.",
+	"Manage": "Управлять",
+	"Manage Arena Models": "",
+	"Manage Models": "Управление моделями",
+	"Manage Ollama Models": "Управление моделями Ollama",
+	"Manage Pipelines": "Управление конвейерами",
+	"March": "Март",
+	"Max Tokens (num_predict)": "Максимальное количество токенов (num_predict)",
+	"Max Upload Count": "Максимальное количество загрузок",
+	"Max Upload Size": "Максимальный размер загрузок",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимальное количество моделей для загрузки одновременно - 3. Пожалуйста, попробуйте позже.",
+	"May": "Май",
+	"Memories accessible by LLMs will be shown here.": "Воспоминания, доступные LLMs, будут отображаться здесь.",
+	"Memory": "Воспоминания",
+	"Memory added successfully": "Воспоминание успешно добавлено",
+	"Memory cleared successfully": "Воспоминания успешно очищены",
+	"Memory deleted successfully": "Воспоминание успешно удалено",
+	"Memory updated successfully": "Воспоминание успешно обновлено",
+	"Merge Responses": "Объединить ответы",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Сообщения, отправленные вами после создания ссылки, не будут передаваться другим. Пользователи, у которых есть URL, смогут просматривать общий чат.",
+	"Min P": "Min P",
+	"Minimum Score": "Минимальный балл",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "DD MMMM YYYY",
+	"MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Модель '{{modelName}}' успешно загружена.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' уже находится в очереди на загрузку.",
+	"Model {{modelId}} not found": "Модель {{modelId}} не найдена",
+	"Model {{modelName}} is not vision capable": "Модель {{modelName}} не поддерживает зрение",
+	"Model {{name}} is now {{status}}": "Модель {{name}} теперь {{status}}",
+	"Model {{name}} is now at the top": "Модель {{name}} теперь сверху",
+	"Model accepts image inputs": "Модель принимает изображения как входные данные",
+	"Model created successfully!": "Модель успешно создана!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Обнаружен путь к файловой системе модели. Для обновления требуется краткое имя модели, не удается продолжить.",
+	"Model ID": "ID модели",
+	"Model Name": "",
+	"Model not selected": "Модель не выбрана",
+	"Model Params": "Параметры модели",
+	"Model updated successfully": "Модель успешно обновлена",
+	"Model Whitelisting": "Включение модели в белый список",
+	"Model(s) Whitelisted": "Модель(и) включены в белый список",
+	"Modelfile Content": "Содержимое файла модели",
+	"Models": "Модели",
+	"more": "",
+	"More": "Больше",
+	"Move to Top": "Поднять вверх",
+	"Name": "Имя",
+	"Name your model": "Присвойте модели имя",
+	"New Chat": "Новый чат",
+	"New folder": "",
+	"New Password": "Новый пароль",
+	"No content found": "",
+	"No content to speak": "Нечего говорить",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Файлы не выбраны",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Результатов не найдено",
+	"No search query generated": "Поисковый запрос не сгенерирован",
+	"No source available": "Нет доступных источников",
+	"No valves to update": "Нет вентилей для обновления",
+	"None": "Нет",
+	"Not factually correct": "Не соответствует действительности",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Обратите внимание: Если вы установите минимальный балл, поиск будет возвращать только документы с баллом больше или равным минимальному баллу.",
+	"Notes": "",
+	"Notifications": "Уведомления",
+	"November": "Ноябрь",
+	"num_gpu (Ollama)": "num_gpu (Ollama)",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth ID",
+	"October": "Октябрь",
+	"Off": "Выключено",
+	"Okay, Let's Go!": "Давайте начнём!",
+	"OLED Dark": "OLED темная",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API отключен",
+	"Ollama API is disabled": "Ollama API отключен",
+	"Ollama Version": "Версия Ollama",
+	"On": "Включено",
+	"Only": "Только",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "В строке команды разрешено использовать только буквенно-цифровые символы и дефисы.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Похоже, что URL-адрес недействителен. Пожалуйста, перепроверьте и попробуйте еще раз.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Вы используете неподдерживаемый метод (только фронтенд). Пожалуйста, обслуживайте веб-интерфейс из бэкенда.",
+	"Open file": "Открыть файл",
+	"Open in full screen": "",
+	"Open new chat": "Открыть новый чат",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Версия Open WebUI (v{{OPEN_WEBUI_VERSION}}) ниже требуемой версии (v{{REQUIRED_VERSION}})",
+	"OpenAI": "Open AI",
+	"OpenAI API": "API OpenAI",
+	"OpenAI API Config": "Конфигурация API OpenAI",
+	"OpenAI API Key is required.": "Требуется ключ API OpenAI.",
+	"OpenAI URL/Key required.": "Требуется URL-адрес API OpenAI или ключ API.",
+	"or": "или",
+	"Other": "Прочее",
+	"OUTPUT": "",
+	"Output format": "Формат вывода",
+	"Overview": "Обзор",
+	"page": "страница",
+	"Password": "Пароль",
+	"PDF document (.pdf)": "PDF-документ (.pdf)",
+	"PDF Extract Images (OCR)": "Извлечение изображений из PDF (OCR)",
+	"pending": "ожидающий",
+	"Permission denied when accessing media devices": "Отказано в разрешении на доступ к мультимедийным устройствам",
+	"Permission denied when accessing microphone": "Отказано в разрешении на доступ к микрофону",
+	"Permission denied when accessing microphone: {{error}}": "Отказано в разрешении на доступ к микрофону: {{error}}",
+	"Personalization": "Персонализация",
+	"Pin": "Закрепить",
+	"Pinned": "Закреплено",
+	"Pipeline deleted successfully": "Конвейер успешно удален",
+	"Pipeline downloaded successfully": "Конвейер успешно загружен",
+	"Pipelines": "Конвейеры",
+	"Pipelines Not Detected": "Конвейеры не обнаружены",
+	"Pipelines Valves": "Вентили конвейеров",
+	"Plain text (.txt)": "Текст в формате .txt",
+	"Playground": "Песочница",
+	"Please carefully review the following warnings:": "Пожалуйста, внимательно ознакомьтесь со следующими предупреждениями:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "Пожалуйста, выберите причину",
+	"Positive attitude": "Позитивный настрой",
+	"Previous 30 days": "Предыдущие 30 дней",
+	"Previous 7 days": "Предыдущие 7 дней",
+	"Profile Image": "Изображение профиля",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Промпт (например, Расскажи мне интересный факт о Римской империи)",
+	"Prompt Content": "Содержание промпта",
+	"Prompt suggestions": "Предложения промптов",
+	"Prompts": "Промпты",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Загрузить \"{{searchValue}}\" с Ollama.com",
+	"Pull a model from Ollama.com": "Загрузить модель с Ollama.com",
+	"Query Params": "Параметры запроса",
+	"RAG Template": "Шаблон RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Прочитать вслух",
+	"Record voice": "Записать голос",
+	"Redirecting you to OpenWebUI Community": "Перенаправляем вас в сообщество OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Называйте себя \"User\" (например, \"User is learning Spanish\").",
+	"References from": "",
+	"Refused when it shouldn't have": "Отказано в доступе, когда это не должно было произойти",
+	"Regenerate": "Перегенерировать",
+	"Release Notes": "Примечания к выпуску",
+	"Relevance": "",
+	"Remove": "Удалить",
+	"Remove Model": "Удалить модель",
+	"Rename": "Переименовать",
+	"Repeat Last N": "Повторить последние N",
+	"Request Mode": "Режим запроса",
+	"Reranking Model": "Модель реранжирования",
+	"Reranking model disabled": "Модель реранжирования отключена",
+	"Reranking model set to \"{{reranking_model}}\"": "Модель реранжирования установлена на \"{{reranking_model}}\"",
+	"Reset": "Сбросить",
+	"Reset Upload Directory": "Сбросить каталог загрузок",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Автоматическое копирование ответа в буфер обмена",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Уведомления об ответах не могут быть активированы, поскольку доступ к веб-сайту был заблокирован. Пожалуйста, перейдите к настройкам своего браузера, чтобы предоставить необходимый доступ.",
+	"Response splitting": "Разделение ответов",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Роль",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "Запустить",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Запустите Llama 2, Code Llama и другие модели. Настройте и создайте свою собственную.",
+	"Running": "Выполняется",
+	"Save": "Сохранить",
+	"Save & Create": "Сохранить и создать",
+	"Save & Update": "Сохранить и обновить",
+	"Save As Copy": "Сохранить как копию",
+	"Save Tag": "Сохранить тег",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Прямое сохранение журналов чата в хранилище вашего браузера больше не поддерживается. Пожалуйста, потратьте минуту, чтобы скачать и удалить ваши журналы чата, нажав на кнопку ниже. Не волнуйтесь, вы легко сможете повторно импортировать свои журналы чата в бэкенд через",
+	"Scroll to bottom when switching between branches": "Прокручивать вниз при переключении веток",
+	"Search": "Поиск",
+	"Search a model": "Поиск модели",
+	"Search Chats": "Поиск в чатах",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Поиск функций",
+	"Search Knowledge": "",
+	"Search Models": "Поиск моделей",
+	"Search Prompts": "Поиск промптов",
+	"Search Query Generation Prompt": "Промпт для генерации поискового запроса",
+	"Search Result Count": "Количество результатов поиска",
+	"Search Tools": "Поиск инструментов",
+	"SearchApi API Key": "Ключ SearchApi API",
+	"SearchApi Engine": "Движок SearchApi",
+	"Searched {{count}} sites_one": "Просмотрено {count}} sites_one",
+	"Searched {{count}} sites_few": "Просмотрено {{count}} sites_few",
+	"Searched {{count}} sites_many": "Просмотрено {{count}} sites_many",
+	"Searched {{count}} sites_other": "Просмотрено {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "Поиск \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "Поиск знания для \"{{searchQuery}}\"",
+	"Searxng Query URL": "URL-адрес запроса Searxng",
+	"See readme.md for instructions": "Смотрите readme.md для инструкций",
+	"See what's new": "Посмотреть, что нового",
+	"Seed": "Сид",
+	"Select a base model": "Выберите базовую модель",
+	"Select a engine": "Выберите движок",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Выберите функцию",
+	"Select a model": "Выберите модель",
+	"Select a pipeline": "Выберите конвейер",
+	"Select a pipeline url": "Выберите URL-адрес конвейера",
+	"Select a tool": "Выберите инструмент",
+	"Select an Ollama instance": "Выберите экземпляр Ollama",
+	"Select Engine": "Выберите движок",
+	"Select Knowledge": "",
+	"Select model": "Выберите модель",
+	"Select only one model to call": "Выберите только одну модель для вызова",
+	"Selected model(s) do not support image inputs": "Выбранные модели не поддерживают ввод изображений",
+	"Semantic distance to query": "",
+	"Send": "Отправить",
+	"Send a Message": "Отправить сообщение",
+	"Send message": "Отправить сообщение",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "Отправляет в запросе \"stream_options: { include_usage: true }\".\nПоддерживаемые провайдеры будут возвращать информацию об использовании токена в ответе, когда это будет установлено.",
+	"September": "Сентябрь",
+	"Serper API Key": "Ключ API Serper",
+	"Serply API Key": "Ключ API Serply",
+	"Serpstack API Key": "Ключ API Serpstack",
+	"Server connection verified": "Соединение с сервером проверено",
+	"Set as default": "Установить по умолчанию",
+	"Set CFG Scale": "Установить CFG Scale",
+	"Set Default Model": "Установить модель по умолчанию",
+	"Set embedding model (e.g. {{model}})": "Установить модель эмбеддинга (например, {{model}})",
+	"Set Image Size": "Установить размер изображения",
+	"Set reranking model (e.g. {{model}})": "Установить модель реранжирования (например, {{model}})",
+	"Set Sampler": "Установить сэмплер",
+	"Set Scheduler": "Установить планировщик",
+	"Set Steps": "Установить шаги",
+	"Set Task Model": "Установить модель задачи",
+	"Set Voice": "Установить голос",
+	"Set whisper model": "",
+	"Settings": "Настройки",
+	"Settings saved successfully!": "Настройки успешно сохранены!",
+	"Share": "Поделиться",
+	"Share Chat": "Поделиться чатом",
+	"Share to OpenWebUI Community": "Поделиться с сообществом OpenWebUI",
+	"short-summary": "краткое описание",
+	"Show": "Показать",
+	"Show Admin Details in Account Pending Overlay": "Показывать данные администратора в оверлее ожидающей учетной записи",
+	"Show Model": "Показать модель",
+	"Show shortcuts": "Показать горячие клавиши",
+	"Show your support!": "Поддержите нас!",
+	"Showcased creativity": "Продемонстрирован творческий подход",
+	"Sign in": "Войти",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Выйти",
+	"Sign up": "Зарегистрироваться",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Источник",
+	"Speech Playback Speed": "Скорость воспроизведения речи",
+	"Speech recognition error: {{error}}": "Ошибка распознавания речи: {{error}}",
+	"Speech-to-Text Engine": "Система распознавания речи",
+	"Stop": "",
+	"Stop Sequence": "Последовательность остановки",
+	"Stream Chat Response": "Потоковый вывод ответа",
+	"STT Model": "Модель распознавания речи",
+	"STT Settings": "Настройки распознавания речи",
+	"Subtitle (e.g. about the Roman Empire)": "Подзаголовок (например, о Римской империи)",
+	"Success": "Успех",
+	"Successfully updated.": "Успешно обновлено.",
+	"Suggested": "Предложено",
+	"Support": "Поддержать",
+	"Support this plugin:": "Поддержите этот плагин",
+	"Sync directory": "",
+	"System": "Система",
+	"System Instructions": "",
+	"System Prompt": "Системный промпт",
+	"Tags": "Теги",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Нажмите, чтобы прервать",
+	"Tavily API Key": "Ключ API Tavily",
+	"Tell us more:": "Пожалуйста, расскажите нам больше:",
+	"Temperature": "Температура",
+	"Template": "Шаблон",
+	"Temporary Chat": "Временный чат",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Система синтеза речи",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Спасибо за вашу обратную связь!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Разработчики этого плагина - увлеченные волонтеры из сообщества. Если вы считаете этот плагин полезным, пожалуйста, подумайте о том, чтобы внести свой вклад в его разработку.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "Максимальный размер файла в МБ. Если размер файла превысит это ограничение, файл не будет загружен.",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "Максимальное количество файлов, которые могут быть использованы одновременно в чате. Если количество файлов превысит это ограничение, файлы не будут загружены.",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Оценка должна быть значением между 0,0 (0%) и 1,0 (100%).",
+	"Theme": "Тема",
+	"Thinking...": "Думаю...",
+	"This action cannot be undone. Do you wish to continue?": "Это действие нельзя отменить. Вы хотите продолжить?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Это обеспечивает сохранение ваших ценных разговоров в безопасной базе данных на вашем сервере. Спасибо!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Это экспериментальная функция, она может работать не так, как ожидалось, и может быть изменена в любое время.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Это приведет к удалению",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Подробное объяснение",
+	"Tika": "Tika",
+	"Tika Server URL required.": "Требуется URL-адрес сервера Tika.",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Совет: Обновляйте несколько переменных подряд, нажимая клавишу Tab в поле ввода чата после каждой замены.",
+	"Title": "Заголовок",
+	"Title (e.g. Tell me a fun fact)": "Заголовок (например, Расскажи мне интересный факт)",
+	"Title Auto-Generation": "Автогенерация заголовка",
+	"Title cannot be an empty string.": "Заголовок не может быть пустой строкой.",
+	"Title Generation Prompt": "Промпт для генерации заголовка",
+	"To access the available model names for downloading,": "Чтобы получить доступ к доступным для загрузки именам моделей,",
+	"To access the GGUF models available for downloading,": "Чтобы получить доступ к моделям GGUF, доступным для загрузки,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Чтобы получить доступ к WebUI, пожалуйста, обратитесь к администратору. Администраторы могут управлять статусами пользователей из панели администратора.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "в чате.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Чтобы выбрать действия, сначала добавьте их в \"Функции\" рабочего пространства.",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Чтобы выбрать фильтры, сначала добавьте их в \"Функции\" рабочего пространства.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Чтобы выбрать инструменты, сначала добавьте их в \"Инструменты\" рабочего пространства.",
+	"Toast notifications for new updates": "",
+	"Today": "Сегодня",
+	"Toggle settings": "Переключить настройки",
+	"Toggle sidebar": "Переключить боковую панель",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Количество токенов для сохранения при обновлении контекста (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Инструмент успешно создан",
+	"Tool deleted successfully": "Инструмент успешно удален",
+	"Tool imported successfully": "Инструмент успешно импортирован",
+	"Tool updated successfully": "Инструмент успешно обновлен",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Описание инструмента (например, инструмент для выполнения различных операций)",
+	"Toolkit ID (e.g. my_toolkit)": "ID инструмента (например my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Имя инструмента (например My ToolKit)",
+	"Tools": "Инструменты",
+	"Tools are a function calling system with arbitrary code execution": "Инструменты - это система вызова функций с выполнением произвольного кода",
+	"Tools have a function calling system that allows arbitrary code execution": "Инструменты имеют систему вызова функций, которая позволяет выполнять произвольный код",
+	"Tools have a function calling system that allows arbitrary code execution.": "Инструменты имеют систему вызова функций, которая позволяет выполнять произвольный код.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Проблемы с доступом к Ollama?",
+	"TTS Model": "Модель TTS",
+	"TTS Settings": "Настройки TTS",
+	"TTS Voice": "Голос TTS",
+	"Type": "Тип",
+	"Type Hugging Face Resolve (Download) URL": "Введите URL-адрес Hugging Face Resolve (загрузки)",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Упс! Возникла проблема подключения к {{provider}}.",
+	"UI": "Пользовательский интерфейс",
+	"Unpin": "Открепить",
+	"Untagged": "",
+	"Update": "Обновить",
+	"Update and Copy Link": "Обновить и скопировать ссылку",
+	"Update for the latest features and improvements.": "Обновитесь для получения последних функций и улучшений.",
+	"Update password": "Обновить пароль",
+	"Updated": "",
+	"Updated at": "Обновлено",
+	"Updated At": "",
+	"Upload": "Загрузить",
+	"Upload a GGUF model": "Загрузить модель GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Загрузить файлы",
+	"Upload Pipeline": "Загрузить конвейер",
+	"Upload Progress": "Прогресс загрузки",
+	"URL Mode": "Режим URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Использовать Gravatar",
+	"Use Initials": "Использовать инициалы",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "пользователь",
+	"User": "",
+	"User location successfully retrieved.": "Местоположение пользователя успешно получено.",
+	"User Permissions": "Права пользователя",
+	"Users": "Пользователи",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Используйте",
+	"Valid time units:": "Допустимые единицы времени:",
+	"Valves": "Вентили",
+	"Valves updated": "Вентили обновлены",
+	"Valves updated successfully": "Вентили успешно обновлены",
+	"variable": "переменная",
+	"variable to have them replaced with clipboard content.": "переменную, чтобы заменить их содержимым буфера обмена.",
+	"Version": "Версия",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Голос",
+	"Voice Input": "",
+	"Warning": "Предупреждение",
+	"Warning:": "Предупреждение:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Предупреждение: Если вы обновите или измените модель эмбеддинга, вам нужно будет повторно импортировать все документы.",
+	"Web": "Веб",
+	"Web API": "Веб API",
+	"Web Loader Settings": "Настройки веб-загрузчика",
+	"Web Search": "Веб-поиск",
+	"Web Search Engine": "Поисковая система",
+	"Webhook URL": "URL-адрес веб-хука",
+	"WebUI Settings": "Настройки WebUI",
+	"WebUI will make requests to": "WebUI будет отправлять запросы на",
+	"What’s New in": "Что нового в",
+	"Whisper (Local)": "Whisper (Локально)",
+	"Widescreen Mode": "Широкоэкранный режим",
+	"Won": "",
+	"Workspace": "Рабочее пространство",
+	"Write a prompt suggestion (e.g. Who are you?)": "Напишите предложение промпта (например, Кто вы?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишите резюме в 50 словах, которое кратко описывает [тему или ключевое слово].",
+	"Write something...": "",
+	"Yesterday": "Вчера",
+	"You": "Вы",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Одновременно вы можете общаться только с максимальным количеством файлов {{maxCount}}.",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Вы можете персонализировать свое взаимодействие с LLMs, добавив воспоминания с помощью кнопки \"Управлять\" ниже, что сделает их более полезными и адаптированными для вас.",
+	"You cannot clone a base model": "Клонировать базовую модель невозможно",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "У вас нет архивированных бесед.",
+	"You have shared this chat": "Вы поделились этим чатом",
+	"You're a helpful assistant.": "Вы полезный ассистент.",
+	"You're now logged in.": "Вы вошли в систему.",
+	"Your account status is currently pending activation.": "В настоящее время ваша учетная запись ожидает активации.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Весь ваш взнос будет направлен непосредственно разработчику плагина; Open WebUI не взимает никаких процентов. Однако выбранная платформа финансирования может иметь свои собственные сборы.",
+	"Youtube": "YouTube",
+	"Youtube Loader Settings": "Настройки загрузчика YouTube"
+}
diff --git a/src/lib/i18n/locales/sr-RS/translation.json b/src/lib/i18n/locales/sr-RS/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..e8f22bcf621e520a3e6c2c36a772e4102b8693af
--- /dev/null
+++ b/src/lib/i18n/locales/sr-RS/translation.json
@@ -0,0 +1,852 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "„s“, „m“, „h“, „d“, „w“ или „-1“ за без истека.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(нпр. `sh webui.sh --api`)",
+	"(latest)": "(најновије)",
+	"{{ models }}": "{{ модели }}",
+	"{{ owner }}: You cannot delete a base model": "{{ оwнер }}: Не можете избрисати основни модел",
+	"{{user}}'s Chats": "Ћаскања корисника {{user}}",
+	"{{webUIName}} Backend Required": "Захтева се {{webUIName}} позадинац",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Модел задатка се користи приликом извршавања задатака као што су генерисање наслова за ћаскања и упите за Веб претрагу",
+	"a user": "корисник",
+	"About": "О нама",
+	"Account": "Налог",
+	"Account Activation Pending": "",
+	"Accurate information": "Прецизне информације",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "Додај",
+	"Add a model id": "Додавање ИД-а модела",
+	"Add a short description about what this model does": "Додавање кратког описа о томе шта овај модел ради",
+	"Add a short title for this prompt": "Додај кратак наслов за овај упит",
+	"Add a tag": "Додај ознаку",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Додај прилагођен упит",
+	"Add Files": "Додај датотеке",
+	"Add Memory": "Додај меморију",
+	"Add Model": "Додај модел",
+	"Add Tag": "",
+	"Add Tags": "Додај ознаке",
+	"Add text content": "",
+	"Add User": "Додај корисника",
+	"Adjusting these settings will apply changes universally to all users.": "Прилагођавање ових подешавања ће применити промене на све кориснике.",
+	"admin": "админ",
+	"Admin": "",
+	"Admin Panel": "Админ табла",
+	"Admin Settings": "Админ подешавања",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "Напредни параметри",
+	"Advanced Params": "Напредни парамови",
+	"All chats": "",
+	"All Documents": "Сви документи",
+	"Allow Chat Deletion": "Дозволи брисање ћаскања",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "алфанумерички знакови и цртице",
+	"Already have an account?": "Већ имате налог?",
+	"an assistant": "помоћник",
+	"and": "и",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "и направи нову дељену везу.",
+	"API Base URL": "Основна адреса API-ја",
+	"API Key": "API кључ",
+	"API Key created.": "API кључ направљен.",
+	"API keys": "API кључеви",
+	"April": "Април",
+	"Archive": "Архива",
+	"Archive All Chats": "Архивирај све ћаскања",
+	"Archived Chats": "Архивирана ћаскања",
+	"are allowed - Activate this command by typing": "су дозвољени - Покрените ову наредбу уношењем",
+	"Are you sure?": "Да ли сте сигурни?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Приложи датотеку",
+	"Attention to detail": "Пажња на детаље",
+	"Audio": "Звук",
+	"August": "Август",
+	"Auto-playback response": "Самостално пуштање одговора",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "Основна адреса за AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "Потребна је основна адреса за AUTOMATIC1111.",
+	"Available list": "",
+	"available!": "доступно!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Назад",
+	"Bad Response": "Лош одговор",
+	"Banners": "Барјаке",
+	"Base Model (From)": "Основни модел (од)",
+	"Batch Size (num_batch)": "",
+	"before": "пре",
+	"Being lazy": "Бити лењ",
+	"Brave Search API Key": "Апи кључ за храбру претрагу",
+	"Bypass SSL verification for Websites": "Заобиђи SSL потврђивање за веб странице",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "Откажи",
+	"Capabilities": "Могућности",
+	"Change Password": "Промени лозинку",
+	"Character": "",
+	"Chat": "Ћаскање",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "Интерфејс балона ћаскања",
+	"Chat Controls": "",
+	"Chat direction": "Смер ћаскања",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Ћаскања",
+	"Check Again": "Провери поново",
+	"Check for updates": "Потражи ажурирања",
+	"Checking for updates...": "Траже се ажурирања...",
+	"Choose a model before saving...": "Изабери модел пре чувања...",
+	"Chunk Overlap": "Преклапање делова",
+	"Chunk Params": "Параметри делова",
+	"Chunk Size": "Величина дела",
+	"Citation": "Цитат",
+	"Clear memory": "",
+	"Click here for help.": "Кликните овде за помоћ.",
+	"Click here to": "Кликните овде да",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Кликните овде да изаберете",
+	"Click here to select a csv file.": "Кликните овде да изаберете csv датотеку.",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "кликните овде.",
+	"Click on the user role button to change a user's role.": "Кликните на дугме за улогу корисника да промените улогу корисника.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "Клон",
+	"Close": "Затвори",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Колекција",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "Основна адреса за ComfyUI",
+	"ComfyUI Base URL is required.": "Потребна је основна адреса за ComfyUI.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Наредба",
+	"Completions": "",
+	"Concurrent Requests": "Упоредни захтеви",
+	"Confirm": "",
+	"Confirm Password": "Потврди лозинку",
+	"Confirm your action": "",
+	"Connections": "Везе",
+	"Contact Admin for WebUI Access": "",
+	"Content": "Садржај",
+	"Content Extraction": "",
+	"Context Length": "Дужина контекста",
+	"Continue Response": "Настави одговор",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "Адреса дељеног ћаскања ископирана у оставу!",
+	"Copied to clipboard": "",
+	"Copy": "Копирај",
+	"Copy last code block": "Копирај последњи блок кода",
+	"Copy last response": "Копирај последњи одговор",
+	"Copy Link": "Копирај везу",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Успешно копирање у оставу!",
+	"Create a model": "Креирање модела",
+	"Create Account": "Направи налог",
+	"Create Knowledge": "",
+	"Create new key": "Направи нови кључ",
+	"Create new secret key": "Направи нови тајни кључ",
+	"Created at": "Направљено у",
+	"Created At": "Направљено у",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "Тренутни модел",
+	"Current Password": "Тренутна лозинка",
+	"Custom": "Прилагођено",
+	"Customize models for a specific purpose": "Прилагођавање модела у одређене сврхе",
+	"Dark": "Тамна",
+	"Dashboard": "",
+	"Database": "База података",
+	"December": "Децембар",
+	"Default": "Подразумевано",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Подразумевано (SentenceTransformers)",
+	"Default Model": "Подразумевани модел",
+	"Default model updated": "Подразумевани модел ажуриран",
+	"Default Prompt Suggestions": "Подразумевани предлози упита",
+	"Default User Role": "Подразумевана улога корисника",
+	"Delete": "Обриши",
+	"Delete a model": "Обриши модел",
+	"Delete All Chats": "Избриши сва ћаскања",
+	"Delete chat": "Обриши ћаскање",
+	"Delete Chat": "Обриши ћаскање",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "обриши ову везу",
+	"Delete tool?": "",
+	"Delete User": "Обриши корисника",
+	"Deleted {{deleteModelTag}}": "Обрисано {{deleteModelTag}}",
+	"Deleted {{name}}": "Избрисано {{наме}}",
+	"Description": "Опис",
+	"Didn't fully follow instructions": "Упутства нису праћена у потпуности",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "Откријте модел",
+	"Discover a prompt": "Откриј упит",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Откријте, преузмите и истражите прилагођене упите",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Откријте, преузмите и истражите образце модела",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "Прикажи корисничко име уместо Ти у чату",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Документ",
+	"Documentation": "",
+	"Documents": "Документи",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "не отвара никакве спољне везе и ваши подаци остају сигурно на вашем локално хостованом серверу.",
+	"Don't have an account?": "Немате налог?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "Не свиђа ми се стил",
+	"Done": "",
+	"Download": "Преузми",
+	"Download canceled": "Преузимање отказано",
+	"Download Database": "Преузми базу података",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Убаците било које датотеке овде да их додате у разговор",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "нпр. '30s', '10m'. Важеће временске јединице су 's', 'm', 'h'.",
+	"Edit": "Уреди",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "Уреди корисника",
+	"ElevenLabs": "",
+	"Email": "Е-пошта",
+	"Embedding Batch Size": "",
+	"Embedding Model": "Модел уградње",
+	"Embedding Model Engine": "Мотор модела уградње",
+	"Embedding model set to \"{{embedding_model}}\"": "Модел уградње подешен на \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Омогући дељење заједнице",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Омогући нове пријаве",
+	"Enable Web Search": "Омогући Wеб претрагу",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Уверите се да ваша CSV датотека укључује 4 колоне у овом редоследу: Име, Е-пошта, Лозинка, Улога.",
+	"Enter {{role}} message here": "Унесите {{role}} поруку овде",
+	"Enter a detail about yourself for your LLMs to recall": "Унесите детаље за себе да ће LLMs преузимати",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Унесите БРАВЕ Сеарцх АПИ кључ",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Унесите преклапање делова",
+	"Enter Chunk Size": "Унесите величину дела",
+	"Enter description": "",
+	"Enter Github Raw URL": "Унесите Гитхуб Раw УРЛ адресу",
+	"Enter Google PSE API Key": "Унесите Гоогле ПСЕ АПИ кључ",
+	"Enter Google PSE Engine Id": "Унесите Гоогле ПСЕ ИД машине",
+	"Enter Image Size (e.g. 512x512)": "Унесите величину слике (нпр. 512x512)",
+	"Enter language codes": "Унесите кодове језика",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Унесите ознаку модела (нпр. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Унесите број корака (нпр. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Унесите резултат",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Унесите УРЛ адресу Сеарxнг упита",
+	"Enter Serper API Key": "Унесите Серпер АПИ кључ",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "Унесите Серпстацк АПИ кључ",
+	"Enter stop sequence": "Унесите секвенцу заустављања",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Унесите Топ К",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Унесите адресу (нпр. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Унесите адресу (нпр. http://localhost:11434)",
+	"Enter Your Email": "Унесите вашу е-пошту",
+	"Enter Your Full Name": "Унесите ваше име и презиме",
+	"Enter your message": "",
+	"Enter Your Password": "Унесите вашу лозинку",
+	"Enter Your Role": "Унесите вашу улогу",
+	"Error": "Грешка",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Експериментално",
+	"Export": "Извоз",
+	"Export All Chats (All Users)": "Извези сва ћаскања (сви корисници)",
+	"Export chat (.json)": "",
+	"Export Chats": "Извези ћаскања",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "Извези моделе",
+	"Export Prompts": "Извези упите",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Неуспешно стварање API кључа.",
+	"Failed to read clipboard contents": "Неуспешно читање садржаја оставе",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "Фебруар",
+	"Feedback History": "",
+	"Feel free to add specific details": "Слободно додајте специфичне детаље",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Режим датотеке",
+	"File not found.": "Датотека није пронађена.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Откривено лажно представљање отиска прста: Немогуће је користити иницијале као аватар. Прелазак на подразумевану профилну слику.",
+	"Fluidly stream large external response chunks": "Течно стримујте велике спољне делове одговора",
+	"Focus chat input": "Усредсредите унос ћаскања",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Упутства су савршено праћена",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Фреквентна казна",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Опште",
+	"General Settings": "Општа подешавања",
+	"Generate Image": "",
+	"Generating search query": "Генерисање упита претраге",
+	"Generation Info": "Информације о стварању",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "Добар одговор",
+	"Google PSE API Key": "Гоогле ПСЕ АПИ кључ",
+	"Google PSE Engine Id": "Гоогле ПСЕ ИД мотора",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "нема разговора.",
+	"Hello, {{name}}": "Здраво, {{name}}",
+	"Help": "Помоћ",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Сакриј",
+	"Hide Model": "",
+	"How can I help you today?": "Како могу да вам помогнем данас?",
+	"Hybrid Search": "Хибридна претрага",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Стварање слика (експериментално)",
+	"Image Generation Engine": "Мотор за стварање слика",
+	"Image Settings": "Подешавања слике",
+	"Images": "Слике",
+	"Import Chats": "Увези ћаскања",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "Увези моделе",
+	"Import Prompts": "Увези упите",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Укључи `--api` заставицу при покретању stable-diffusion-webui",
+	"Info": "Инфо",
+	"Input commands": "Унеси наредбе",
+	"Install from Github URL": "Инсталирај из Гитхуб УРЛ адресе",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "Изглед",
+	"Invalid file format.": "",
+	"Invalid Tag": "Неисправна ознака",
+	"January": "Јануар",
+	"join our Discord for help.": "придружите се нашем Дискорду за помоћ.",
+	"JSON": "JSON",
+	"JSON Preview": "ЈСОН Преглед",
+	"July": "Јул",
+	"June": "Јун",
+	"JWT Expiration": "Истек JWT-а",
+	"JWT Token": "JWT жетон",
+	"Keep Alive": "Одржи трајање",
+	"Keyboard shortcuts": "Пречице на тастатури",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Језик",
+	"large language models, locally.": "",
+	"Last Active": "Последња активност",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Светла",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "ВЈМ-ови (LLM-ови) могу правити грешке. Проверите важне податке.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "ЛНД",
+	"Made by OpenWebUI Community": "Израдила OpenWebUI заједница",
+	"Make sure to enclose them with": "Уверите се да их затворите са",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "Управљај моделима",
+	"Manage Ollama Models": "Управљај Ollama моделима",
+	"Manage Pipelines": "Управљање цевоводима",
+	"March": "Март",
+	"Max Tokens (num_predict)": "Маx Токенс (нум_предицт)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Највише 3 модела могу бити преузета истовремено. Покушајте поново касније.",
+	"May": "Мај",
+	"Memories accessible by LLMs will be shown here.": "Памћења које ће бити појављена од овог LLM-а ће бити приказана овде.",
+	"Memory": "Памћење",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Поруке које пошаљете након стварања ваше везе неће бити подељене. Корисници са URL-ом ће моћи да виде дељено ћаскање.",
+	"Min P": "",
+	"Minimum Score": "Најмањи резултат",
+	"Mirostat": "Миростат",
+	"Mirostat Eta": "Миростат Ета",
+	"Mirostat Tau": "Миростат Тау",
+	"MMMM DD, YYYY": "ММММ ДД, ГГГГ",
+	"MMMM DD, YYYY HH:mm": "ММММ ДД, ГГГГ ЧЧ:мм",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Модел „{{modelName}}“ је успешно преузет.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Модел „{{modelTag}}“ је већ у реду за преузимање.",
+	"Model {{modelId}} not found": "Модел {{modelId}} није пронађен",
+	"Model {{modelName}} is not vision capable": "Модел {{моделНаме}} није способан за вид",
+	"Model {{name}} is now {{status}}": "Модел {{наме}} је сада {{статус}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Откривена путања система датотека модела. За ажурирање је потребан кратак назив модела, не може се наставити.",
+	"Model ID": "ИД модела",
+	"Model Name": "",
+	"Model not selected": "Модел није изабран",
+	"Model Params": "Модел Парамс",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Бели списак модела",
+	"Model(s) Whitelisted": "Модел(и) на белом списку",
+	"Modelfile Content": "Садржај модел-датотеке",
+	"Models": "Модели",
+	"more": "",
+	"More": "Више",
+	"Move to Top": "",
+	"Name": "Име",
+	"Name your model": "Наведи свој модел",
+	"New Chat": "Ново ћаскање",
+	"New folder": "",
+	"New Password": "Нова лозинка",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Нема резултата",
+	"No search query generated": "Није генерисан упит за претрагу",
+	"No source available": "Нема доступног извора",
+	"No valves to update": "",
+	"None": "Нико",
+	"Not factually correct": "Није чињенично тачно",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Напомена: ако подесите најмањи резултат, претрага ће вратити само документе са резултатом већим или једнаким најмањем резултату.",
+	"Notes": "",
+	"Notifications": "Обавештења",
+	"November": "Новембар",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "нум _тхреад (Оллама)",
+	"OAuth ID": "",
+	"October": "Октобар",
+	"Off": "Искључено",
+	"Okay, Let's Go!": "У реду, хајде да кренемо!",
+	"OLED Dark": "OLED тамна",
+	"Ollama": "Ollama",
+	"Ollama API": "Оллама АПИ",
+	"Ollama API disabled": "Оллама АПИ онемогућен",
+	"Ollama API is disabled": "",
+	"Ollama Version": "Издање Ollama-е",
+	"On": "Укључено",
+	"Only": "Само",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Само алфанумерички знакови и цртице су дозвољени у низу наредби.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Изгледа да је адреса неважећа. Молимо вас да проверите и покушате поново.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Користите неподржани метод (само фронтенд). Молимо вас да покренете WebUI са бекенда.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Покрени ново ћаскање",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "Подешавање OpenAI API-ја",
+	"OpenAI API Key is required.": "Потребан је OpenAI API кључ.",
+	"OpenAI URL/Key required.": "Потребан је OpenAI URL/кључ.",
+	"or": "или",
+	"Other": "Остало",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Лозинка",
+	"PDF document (.pdf)": "PDF документ (.pdf)",
+	"PDF Extract Images (OCR)": "Извлачење PDF слика (OCR)",
+	"pending": "на чекању",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "Приступ микрофону је одбијен: {{error}}",
+	"Personalization": "Прилагођавање",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "Цевоводи",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "Вентили за цевоводе",
+	"Plain text (.txt)": "Обичан текст (.txt)",
+	"Playground": "Игралиште",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Позитиван став",
+	"Previous 30 days": "Претходних 30 дана",
+	"Previous 7 days": "Претходних 7 дана",
+	"Profile Image": "Слика профила",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Упит (нпр. „реци ми занимљивост о Римском царству“)",
+	"Prompt Content": "Садржај упита",
+	"Prompt suggestions": "Предлози упита",
+	"Prompts": "Упити",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Повуците \"{{searchValue}}\" са Ollama.com",
+	"Pull a model from Ollama.com": "Повуците модел са Ollama.com",
+	"Query Params": "Параметри упита",
+	"RAG Template": "RAG шаблон",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Прочитај наглас",
+	"Record voice": "Сними глас",
+	"Redirecting you to OpenWebUI Community": "Преусмеравање на OpenWebUI заједницу",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "Одбијено када није требало",
+	"Regenerate": "Регенериши",
+	"Release Notes": "Напомене о издању",
+	"Relevance": "",
+	"Remove": "Уклони",
+	"Remove Model": "Уклони модел",
+	"Rename": "Преименуј",
+	"Repeat Last N": "Понови последњих N",
+	"Request Mode": "Режим захтева",
+	"Reranking Model": "Модел поновног рангирања",
+	"Reranking model disabled": "Модел поновног рангирања онемогућен",
+	"Reranking model set to \"{{reranking_model}}\"": "Модел поновног рангирања подешен на \"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Самостално копирање одговора у оставу",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Улога",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "ДНЛ",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "Сачувај",
+	"Save & Create": "Сачувај и направи",
+	"Save & Update": "Сачувај и ажурирај",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Чување ћаскања директно у складиште вашег прегледача више није подржано. Одвојите тренутак да преузмете и избришете ваша ћаскања кликом на дугме испод. Не брините, можете лако поново увезти ваша ћаскања у бекенд кроз",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Претражи",
+	"Search a model": "Претражи модел",
+	"Search Chats": "Претражи ћаскања",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "Модели претраге",
+	"Search Prompts": "Претражи упите",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "Број резултата претраге",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Претражио {{цоунт}} ситес_оне",
+	"Searched {{count}} sites_few": "Претражио {{цоунт}} ситес_феw",
+	"Searched {{count}} sites_other": "Претражио {{цоунт}} ситес_отхер",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "УРЛ адреса Сеарxнг упита",
+	"See readme.md for instructions": "Погледај readme.md за упутства",
+	"See what's new": "Погледај шта је ново",
+	"Seed": "Семе",
+	"Select a base model": "Избор основног модела",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "Изабери модел",
+	"Select a pipeline": "Избор цевовода",
+	"Select a pipeline url": "Избор урл адресе цевовода",
+	"Select a tool": "",
+	"Select an Ollama instance": "Изабери Ollama инстанцу",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Изабери модел",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "Изабрани модели не подржавају уносе слика",
+	"Semantic distance to query": "",
+	"Send": "Пошаљи",
+	"Send a Message": "Пошаљи поруку",
+	"Send message": "Пошаљи поруку",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "Септембар",
+	"Serper API Key": "Серпер АПИ кључ",
+	"Serply API Key": "",
+	"Serpstack API Key": "Серпстацк АПИ кључ",
+	"Server connection verified": "Веза са сервером потврђена",
+	"Set as default": "Подеси као подразумевано",
+	"Set CFG Scale": "",
+	"Set Default Model": "Подеси као подразумевани модел",
+	"Set embedding model (e.g. {{model}})": "Подеси модел уградње (нпр. {{model}})",
+	"Set Image Size": "Подеси величину слике",
+	"Set reranking model (e.g. {{model}})": "Подеси модел поновног рангирања (нпр. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Подеси кораке",
+	"Set Task Model": "Постављање модела задатка",
+	"Set Voice": "Подеси глас",
+	"Set whisper model": "",
+	"Settings": "Подешавања",
+	"Settings saved successfully!": "Подешавања успешно сачувана!",
+	"Share": "Подели",
+	"Share Chat": "Подели ћаскање",
+	"Share to OpenWebUI Community": "Подели са OpenWebUI заједницом",
+	"short-summary": "кратак сажетак",
+	"Show": "Прикажи",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "Прикажи пречице",
+	"Show your support!": "",
+	"Showcased creativity": "Приказана креативност",
+	"Sign in": "Пријави се",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Одјави се",
+	"Sign up": "Региструј се",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Извор",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Грешка у препознавању говора: {{error}}",
+	"Speech-to-Text Engine": "Мотор за говор у текст",
+	"Stop": "",
+	"Stop Sequence": "Секвенца заустављања",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "STT подешавања",
+	"Subtitle (e.g. about the Roman Empire)": "Поднаслов (нпр. о Римском царству)",
+	"Success": "Успех",
+	"Successfully updated.": "Успешно ажурирано.",
+	"Suggested": "Предложено",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "Систем",
+	"System Instructions": "",
+	"System Prompt": "Системски упит",
+	"Tags": "Ознаке",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "Реците нам више:",
+	"Temperature": "Температура",
+	"Template": "Шаблон",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Мотор за текст у говор",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Хвала на вашем коментару!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Резултат треба да буде вредност између 0.0 (0%) и 1.0 (100%).",
+	"Theme": "Тема",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ово осигурава да су ваши вредни разговори безбедно сачувани у вашој бекенд бази података. Хвала вам!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Детаљно објашњење",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Савет: ажурирајте више променљивих слотова узастопно притиском на тастер Таб у уносу ћаскања након сваке замене.",
+	"Title": "Наслов",
+	"Title (e.g. Tell me a fun fact)": "Наслов (нпр. „реци ми занимљивост“)",
+	"Title Auto-Generation": "Самостално стварање наслова",
+	"Title cannot be an empty string.": "Наслов не може бити празан низ.",
+	"Title Generation Prompt": "Упит за стварање наслова",
+	"To access the available model names for downloading,": "Да бисте приступили доступним именима модела за преузимање,",
+	"To access the GGUF models available for downloading,": "Да бисте приступили GGUF моделима доступним за преузимање,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "у унос ћаскања.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "Данас",
+	"Toggle settings": "Пребаци подешавања",
+	"Toggle sidebar": "Пребаци бочну траку",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Топ К",
+	"Top P": "Топ П",
+	"Trouble accessing Ollama?": "Проблеми са приступом Ollama-и?",
+	"TTS Model": "",
+	"TTS Settings": "TTS подешавања",
+	"TTS Voice": "",
+	"Type": "Тип",
+	"Type Hugging Face Resolve (Download) URL": "Унесите Hugging Face Resolve (Download) адресу",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Упс! Дошло је до проблема при повезивању са {{provider}}.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "Ажурирај и копирај везу",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Ажурирај лозинку",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Отпреми GGUF модел",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Отпремање датотека",
+	"Upload Pipeline": "",
+	"Upload Progress": "Напредак отпремања",
+	"URL Mode": "Режим адресе",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Користи Граватар",
+	"Use Initials": "Користи иницијале",
+	"use_mlock (Ollama)": "усе _млоцк (Оллама)",
+	"use_mmap (Ollama)": "усе _ммап (Оллама)",
+	"user": "корисник",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "Овлашћења корисника",
+	"Users": "Корисници",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Искористи",
+	"Valid time units:": "Важеће временске јединице:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "променљива",
+	"variable to have them replaced with clipboard content.": "променљива за замену са садржајем оставе.",
+	"Version": "Издање",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "Упозорење",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Упозорење: ако ажурирате или промените ваш модел уградње, мораћете поново да увезете све документе.",
+	"Web": "Веб",
+	"Web API": "",
+	"Web Loader Settings": "Подешавања веб учитавача",
+	"Web Search": "Wеб претрага",
+	"Web Search Engine": "Wеб претраживач",
+	"Webhook URL": "Адреса веб-куке",
+	"WebUI Settings": "Подешавања веб интерфејса",
+	"WebUI will make requests to": "Веб интерфејс ће слати захтеве на",
+	"What’s New in": "Шта је ново у",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "Радни простор",
+	"Write a prompt suggestion (e.g. Who are you?)": "Напишите предлог упита (нпр. „ко си ти?“)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишите сажетак у 50 речи који резимира [тему или кључну реч].",
+	"Write something...": "",
+	"Yesterday": "Јуче",
+	"You": "Ти",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "Не можеш клонирати основни модел",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Немате архивиране разговоре.",
+	"You have shared this chat": "Поделили сте ово ћаскање",
+	"You're a helpful assistant.": "Ти си користан помоћник.",
+	"You're now logged in.": "Сада сте пријављени.",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Јутјуб",
+	"Youtube Loader Settings": "Подешавања Јутјуб учитавача"
+}
diff --git a/src/lib/i18n/locales/sv-SE/translation.json b/src/lib/i18n/locales/sv-SE/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..87761e5dbb254e72f1cea908fb64d7a8853b1096
--- /dev/null
+++ b/src/lib/i18n/locales/sv-SE/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' eller '-1' för inget utgångsdatum",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(t.ex. `sh webui.sh --api`)",
+	"(latest)": "(senaste)",
+	"{{ models }}": "{{ modeller }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Du kan inte ta bort en basmodell",
+	"{{user}}'s Chats": "{{user}}s Chats",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend krävs",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "En uppgiftsmodell används när du utför uppgifter som att generera titlar för chattar och webbsökningsfrågor",
+	"a user": "en användare",
+	"About": "Om",
+	"Account": "Konto",
+	"Account Activation Pending": "Kontoaktivering väntar",
+	"Accurate information": "Exakt information",
+	"Actions": "",
+	"Active Users": "Aktiva användare",
+	"Add": "Lägg till",
+	"Add a model id": "Lägga till ett modell-ID",
+	"Add a short description about what this model does": "Lägg till en kort beskrivning av vad den här modellen gör",
+	"Add a short title for this prompt": "Lägg till en kort titel för denna instruktion",
+	"Add a tag": "Lägg till en tagg",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Lägg till en anpassad instruktion",
+	"Add Files": "Lägg till filer",
+	"Add Memory": "Lägg till minne",
+	"Add Model": "Lägg till modell",
+	"Add Tag": "",
+	"Add Tags": "Lägg till taggar",
+	"Add text content": "",
+	"Add User": "Lägg till användare",
+	"Adjusting these settings will apply changes universally to all users.": "Justering av dessa inställningar kommer att tillämpa ändringar universellt för alla användare.",
+	"admin": "administratör",
+	"Admin": "Admin",
+	"Admin Panel": "Administrationspanel",
+	"Admin Settings": "Administratörsinställningar",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Administratörer har tillgång till alla verktyg hela tiden, medan användare behöver verktyg som tilldelas per modell i arbetsytan.",
+	"Advanced Parameters": "Avancerade parametrar",
+	"Advanced Params": "Avancerade parametrar",
+	"All chats": "",
+	"All Documents": "Alla dokument",
+	"Allow Chat Deletion": "Tillåt chattborttagning",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "Tillåt icke-lokala röster",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "alfanumeriska tecken och bindestreck",
+	"Already have an account?": "Har du redan ett konto?",
+	"an assistant": "en assistent",
+	"and": "och",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "och skapa en ny delad länk.",
+	"API Base URL": "API-bas-URL",
+	"API Key": "API-nyckel",
+	"API Key created.": "API-nyckel skapad.",
+	"API keys": "API-nycklar",
+	"April": "april",
+	"Archive": "Arkiv",
+	"Archive All Chats": "Arkivera alla chattar",
+	"Archived Chats": "Arkiverade chattar",
+	"are allowed - Activate this command by typing": "är tillåtna - Aktivera detta kommando genom att skriva",
+	"Are you sure?": "Är du säker?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Bifoga fil",
+	"Attention to detail": "Detaljerad uppmärksamhet",
+	"Audio": "Ljud",
+	"August": "augusti",
+	"Auto-playback response": "Automatisk uppspelning",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 bas-URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 bas-URL krävs.",
+	"Available list": "",
+	"available!": "tillgänglig!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Tillbaka",
+	"Bad Response": "Felaktig respons",
+	"Banners": "Banners",
+	"Base Model (From)": "Basmodell (Från)",
+	"Batch Size (num_batch)": "Batchstorlek (num_batch)",
+	"before": "för",
+	"Being lazy": "Lägg till",
+	"Brave Search API Key": "API-nyckel för Brave Search",
+	"Bypass SSL verification for Websites": "Kringgå SSL-verifiering för webbplatser",
+	"Call": "Samtal",
+	"Call feature is not supported when using Web STT engine": "Samtalsfunktionen är inte kompatibel med Web Tal-till-text motor",
+	"Camera": "Kamera",
+	"Cancel": "Avbryt",
+	"Capabilities": "Kapaciteter",
+	"Change Password": "Ändra lösenord",
+	"Character": "",
+	"Chat": "Chatt",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "Chatbubblar UI",
+	"Chat Controls": "",
+	"Chat direction": "Chattriktning",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Chattar",
+	"Check Again": "Kontrollera igen",
+	"Check for updates": "Sök efter uppdateringar",
+	"Checking for updates...": "Söker efter uppdateringar...",
+	"Choose a model before saving...": "Välj en modell innan du sparar...",
+	"Chunk Overlap": "Överlappning",
+	"Chunk Params": "Chunk-parametrar",
+	"Chunk Size": "Chunk-storlek",
+	"Citation": "Citat",
+	"Clear memory": "Rensa minnet",
+	"Click here for help.": "Klicka här för hjälp.",
+	"Click here to": "Klicka här för att",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Klicka här för att välja",
+	"Click here to select a csv file.": "Klicka här för att välja en csv-fil.",
+	"Click here to select a py file.": "Klicka här för att välja en python-fil.",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "klicka här.",
+	"Click on the user role button to change a user's role.": "Klicka på knappen för användarroll för att ändra en användares roll.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "Klon",
+	"Close": "Stäng",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "Samling",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Base URL",
+	"ComfyUI Base URL is required.": "ComfyUI Base URL krävs.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Kommando",
+	"Completions": "",
+	"Concurrent Requests": "Parallella anrop",
+	"Confirm": "",
+	"Confirm Password": "Bekräfta lösenord",
+	"Confirm your action": "",
+	"Connections": "Anslutningar",
+	"Contact Admin for WebUI Access": "Kontakta administratören för att få åtkomst till WebUI",
+	"Content": "Innehåll",
+	"Content Extraction": "",
+	"Context Length": "Kontextlängd",
+	"Continue Response": "Fortsätt svar",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "Kopierad delad chatt-URL till urklipp!",
+	"Copied to clipboard": "",
+	"Copy": "Kopiera",
+	"Copy last code block": "Kopiera sista kodblock",
+	"Copy last response": "Kopiera sista svar",
+	"Copy Link": "Kopiera länk",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Kopiering till urklipp lyckades!",
+	"Create a model": "Skapa en modell",
+	"Create Account": "Skapa konto",
+	"Create Knowledge": "",
+	"Create new key": "Skapa ny nyckel",
+	"Create new secret key": "Skapa ny hemlig nyckel",
+	"Created at": "Skapad",
+	"Created At": "Skapad",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "Aktuell modell",
+	"Current Password": "Nuvarande lösenord",
+	"Custom": "Anpassad",
+	"Customize models for a specific purpose": "Anpassa modeller för ett specifikt syfte",
+	"Dark": "Mörk",
+	"Dashboard": "Instrumentpanel",
+	"Database": "Databas",
+	"December": "december",
+	"Default": "Standard",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
+	"Default Model": "Standardmodell",
+	"Default model updated": "Standardmodell uppdaterad",
+	"Default Prompt Suggestions": "Standardinstruktionsförslag",
+	"Default User Role": "Standardanvändarroll",
+	"Delete": "Radera",
+	"Delete a model": "Ta bort en modell",
+	"Delete All Chats": "Ta bort alla chattar",
+	"Delete chat": "Radera chatt",
+	"Delete Chat": "Radera chatt",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "radera denna länk",
+	"Delete tool?": "",
+	"Delete User": "Radera användare",
+	"Deleted {{deleteModelTag}}": "Raderad {{deleteModelTag}}",
+	"Deleted {{name}}": "Borttagen {{name}}",
+	"Description": "Beskrivning",
+	"Didn't fully follow instructions": "Följde inte instruktionerna",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "Upptäck en modell",
+	"Discover a prompt": "Upptäck en instruktion",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "Upptäck, ladda ner och utforska anpassade instruktioner",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "Upptäck, ladda ner och utforska modellförinställningar",
+	"Dismissible": "Kan stängas",
+	"Display Emoji in Call": "Visa Emoji under samtal",
+	"Display the username instead of You in the Chat": "Visa användarnamnet istället för du i chatten",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "Dokument",
+	"Documentation": "Dokumentation",
+	"Documents": "Dokument",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "gör inga externa anslutningar, och dina data förblir säkra på din lokalt värdade server.",
+	"Don't have an account?": "Har du inget konto?",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "Tycker inte om utseendet",
+	"Done": "",
+	"Download": "Ladda ner",
+	"Download canceled": "Nedladdning avbruten",
+	"Download Database": "Ladda ner databas",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Släpp filer här för att lägga till i samtalet",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "t.ex. '30s', '10m'. Giltiga tidsenheter är 's', 'm', 'h'.",
+	"Edit": "Redigera",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "Redigera användare",
+	"ElevenLabs": "",
+	"Email": "E-post",
+	"Embedding Batch Size": "Batchstorlek för inbäddning",
+	"Embedding Model": "Inbäddningsmodell",
+	"Embedding Model Engine": "Motor för inbäddningsmodell",
+	"Embedding model set to \"{{embedding_model}}\"": "Inbäddningsmodell inställd på \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Aktivera community-delning",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "Aktivera nya registreringar",
+	"Enable Web Search": "Aktivera webbsökning",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Se till att din CSV-fil innehåller fyra kolumner i denna ordning: Name, Email, Password, Role.",
+	"Enter {{role}} message here": "Skriv {{role}} meddelande här",
+	"Enter a detail about yourself for your LLMs to recall": "Skriv en detalj om dig själv för att dina LLMs ska komma ihåg",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "Ange API-nyckel för Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Ange chunköverlappning",
+	"Enter Chunk Size": "Ange chunkstorlek",
+	"Enter description": "",
+	"Enter Github Raw URL": "Ange Github Raw URL",
+	"Enter Google PSE API Key": "Ange Google PSE API-nyckel",
+	"Enter Google PSE Engine Id": "Ange Google PSE Engine Id",
+	"Enter Image Size (e.g. 512x512)": "Ange bildstorlek (t.ex. 512x512)",
+	"Enter language codes": "Skriv språkkoder",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Ange modelltagg (t.ex. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Ange antal steg (t.ex. 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Ange betyg",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Ange Searxng Query URL",
+	"Enter Serper API Key": "Ange Serper API-nyckel",
+	"Enter Serply API Key": "Ange Serply API-nyckel",
+	"Enter Serpstack API Key": "Ange Serpstack API-nyckel",
+	"Enter stop sequence": "Ange stoppsekvens",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "Ange Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Ange URL (t.ex. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Ange URL (t.ex. http://localhost:11434)",
+	"Enter Your Email": "Ange din e-post",
+	"Enter Your Full Name": "Ange ditt fullständiga namn",
+	"Enter your message": "",
+	"Enter Your Password": "Ange ditt lösenord",
+	"Enter Your Role": "Ange din roll",
+	"Error": "Fel",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Experimentell",
+	"Export": "Export",
+	"Export All Chats (All Users)": "Exportera alla chattar (alla användare)",
+	"Export chat (.json)": "Exportera chatt (.json)",
+	"Export Chats": "Exportera chattar",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "Exportera modeller",
+	"Export Prompts": "Exportera instruktioner",
+	"Export Tools": "Exportera verktyg",
+	"External Models": "Externa modeller",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Misslyckades med att skapa API-nyckel.",
+	"Failed to read clipboard contents": "Misslyckades med att läsa urklippsinnehåll",
+	"Failed to update settings": "Misslyckades med att uppdatera inställningarna",
+	"Failed to upload file.": "",
+	"February": "februari",
+	"Feedback History": "",
+	"Feel free to add specific details": "Tveka inte att lägga till specifika detaljer",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Fil-läge",
+	"File not found.": "Fil hittades inte.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingeravtrycksmanipulering upptäckt: Kan inte använda initialer som avatar. Återställning till standardprofilbild.",
+	"Fluidly stream large external response chunks": "Strömma flytande stora externa svarschunks",
+	"Focus chat input": "Fokusera på chattfältet",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Följde instruktionerna perfekt",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Straff för frekvens",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "Allmän",
+	"General Settings": "Allmänna inställningar",
+	"Generate Image": "Generera bild",
+	"Generating search query": "Genererar sökfråga",
+	"Generation Info": "Info om generation",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "Bra svar",
+	"Google PSE API Key": "Google PSE API-nyckel",
+	"Google PSE Engine Id": "Google PSE Engine Id",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "har inga samtal.",
+	"Hello, {{name}}": "Hej, {{name}}",
+	"Help": "Hjälp",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Dölj",
+	"Hide Model": "",
+	"How can I help you today?": "Hur kan jag hjälpa dig idag?",
+	"Hybrid Search": "Hybrid sökning",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "Bildgenerering (experimentell)",
+	"Image Generation Engine": "Bildgenereringsmotor",
+	"Image Settings": "Bildinställningar",
+	"Images": "Bilder",
+	"Import Chats": "Importera chattar",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "Importera modeller",
+	"Import Prompts": "Importera instruktioner",
+	"Import Tools": "Importera verktyg",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Inkludera flaggan `--api` när du kör stable-diffusion-webui",
+	"Info": "Information",
+	"Input commands": "Indatakommandon",
+	"Install from Github URL": "Installera från Github-URL",
+	"Instant Auto-Send After Voice Transcription": "Skicka automatiskt efter rösttranskribering",
+	"Interface": "Gränssnitt",
+	"Invalid file format.": "",
+	"Invalid Tag": "Ogiltig tagg",
+	"January": "januari",
+	"join our Discord for help.": "gå med i vår Discord för hjälp.",
+	"JSON": "JSON",
+	"JSON Preview": "Förhandsversion av JSON",
+	"July": "juli",
+	"June": "juni",
+	"JWT Expiration": "JWT-utgångsdatum",
+	"JWT Token": "JWT-token",
+	"Keep Alive": "Keep Alive",
+	"Keyboard shortcuts": "Tangentbordsgenvägar",
+	"Knowledge": "Kunskap",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Språk",
+	"large language models, locally.": "",
+	"Last Active": "Senast aktiv",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Ljus",
+	"Listening...": "Lyssnar...",
+	"LLMs can make mistakes. Verify important information.": "LLM:er kan göra misstag. Granska viktig information.",
+	"Local Models": "Lokala modeller",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Skapad av OpenWebUI Community",
+	"Make sure to enclose them with": "Se till att bifoga dem med",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "Hantera",
+	"Manage Arena Models": "",
+	"Manage Models": "Hantera modeller",
+	"Manage Ollama Models": "Hantera Ollama-modeller",
+	"Manage Pipelines": "Hantera rörledningar",
+	"March": "mars",
+	"Max Tokens (num_predict)": "Maximalt antal tokens (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Högst 3 modeller kan laddas ner samtidigt. Vänligen försök igen senare.",
+	"May": "maj",
+	"Memories accessible by LLMs will be shown here.": "Minnen som LLM:er kan komma åt visas här.",
+	"Memory": "Minnen",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Meddelanden du skickar efter att ha skapat din länk kommer inte att delas. Användare med URL:en kommer att kunna se delad chatt.",
+	"Min P": "",
+	"Minimum Score": "Tröskel",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Modellen '{{modelName}}' har laddats ner framgångsrikt.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Modellen '{{modelTag}}' är redan i kö för nedladdning.",
+	"Model {{modelId}} not found": "Modell {{modelId}} hittades inte",
+	"Model {{modelName}} is not vision capable": "Modellen {{modelName}} är inte synkapabel",
+	"Model {{name}} is now {{status}}": "Modellen {{name}} är nu {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modellens filsystemväg upptäckt. Modellens kortnamn krävs för uppdatering, kan inte fortsätta.",
+	"Model ID": "Modell-ID",
+	"Model Name": "",
+	"Model not selected": "Modell inte vald",
+	"Model Params": "Modell Params",
+	"Model updated successfully": "",
+	"Model Whitelisting": "Modellens vitlista",
+	"Model(s) Whitelisted": "Vitlistade modeller",
+	"Modelfile Content": "Modelfilens innehåll",
+	"Models": "Modeller",
+	"more": "",
+	"More": "Mer",
+	"Move to Top": "",
+	"Name": "Namn",
+	"Name your model": "Namnge din modell",
+	"New Chat": "Ny chatt",
+	"New folder": "",
+	"New Password": "Nytt lösenord",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Inga resultat hittades",
+	"No search query generated": "Ingen sökfråga genererad",
+	"No source available": "Ingen tillgänglig källa",
+	"No valves to update": "",
+	"None": "Ingen",
+	"Not factually correct": "Inte faktiskt korrekt",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Obs: Om du anger en tröskel kommer sökningen endast att returnera dokument med ett betyg som är större än eller lika med tröskeln.",
+	"Notes": "",
+	"Notifications": "Notifikationer",
+	"November": "november",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "",
+	"October": "oktober",
+	"Off": "Av",
+	"Okay, Let's Go!": "Okej, nu kör vi!",
+	"OLED Dark": "Mörk (OLED)",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API inaktiverat",
+	"Ollama API is disabled": "Ollama API är inaktiverat",
+	"Ollama Version": "Ollama-version",
+	"On": "På",
+	"Only": "Endast",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Endast alfanumeriska tecken och bindestreck är tillåtna i kommandosträngen.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppsan! Det ser ut som om URL:en är ogiltig. Dubbelkolla gärna och försök igen.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppsan! Du använder en ej stödd metod (endast frontend). Vänligen servera WebUI från backend.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Öppna ny chatt",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API-konfig",
+	"OpenAI API Key is required.": "OpenAI API-nyckel krävs.",
+	"OpenAI URL/Key required.": "OpenAI-URL/nyckel krävs.",
+	"or": "eller",
+	"Other": "Andra",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Lösenord",
+	"PDF document (.pdf)": "PDF-dokument (.pdf)",
+	"PDF Extract Images (OCR)": "PDF Extrahera bilder (OCR)",
+	"pending": "väntande",
+	"Permission denied when accessing media devices": "Nekad behörighet vid åtkomst till mediaenheter",
+	"Permission denied when accessing microphone": "Nekad behörighet vid åtkomst till mikrofon",
+	"Permission denied when accessing microphone: {{error}}": "Tillstånd nekades vid åtkomst till mikrofon: {{error}}",
+	"Personalization": "Personalisering",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "Rörledningar",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "Ventiler för rörledningar",
+	"Plain text (.txt)": "Text (.txt)",
+	"Playground": "Lekplats",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Positivt inställning",
+	"Previous 30 days": "Föregående 30 dagar",
+	"Previous 7 days": "Föregående 7 dagar",
+	"Profile Image": "Profilbild",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Instruktion (t.ex. Berätta en kuriosa om Romerska Imperiet)",
+	"Prompt Content": "Instruktionens innehåll",
+	"Prompt suggestions": "Instruktionsförslag",
+	"Prompts": "Instruktioner",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Ladda ner \"{{searchValue}}\" från Ollama.com",
+	"Pull a model from Ollama.com": "Ladda ner en modell från Ollama.com",
+	"Query Params": "Inställningar för sökfråga",
+	"RAG Template": "RAG-mall",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Läs igenom",
+	"Record voice": "Spela in röst",
+	"Redirecting you to OpenWebUI Community": "Omdirigerar dig till OpenWebUI Community",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Referera till dig själv som \"Användare\" (t.ex. \"Användaren lär sig spanska\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Avvisades när det inte borde ha gjort det",
+	"Regenerate": "Regenerera",
+	"Release Notes": "Versionsinformation",
+	"Relevance": "",
+	"Remove": "Ta bort",
+	"Remove Model": "Ta bort modell",
+	"Rename": "Byt namn",
+	"Repeat Last N": "Upprepa senaste N",
+	"Request Mode": "Frågeläge",
+	"Reranking Model": "Reranking modell",
+	"Reranking model disabled": "Reranking modell inaktiverad",
+	"Reranking model set to \"{{reranking_model}}\"": "Reranking modell inställd på \"{{reranking_model}}\"",
+	"Reset": "",
+	"Reset Upload Directory": "Återställ uppladdningskatalog",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Svara AutoCopy till urklipp",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Roll",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "Kör",
+	"Save": "Spara",
+	"Save & Create": "Spara och skapa",
+	"Save & Update": "Spara och uppdatera",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Att spara chatloggar direkt till din webbläsares lagring stöds inte längre. Ta en stund och ladda ner och radera dina chattloggar genom att klicka på knappen nedan. Oroa dig inte, du kan enkelt importera dina chattloggar till backend genom",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "Sök",
+	"Search a model": "Sök efter en modell",
+	"Search Chats": "Sök i chattar",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "Sök modeller",
+	"Search Prompts": "Sök instruktioner",
+	"Search Query Generation Prompt": "Instruktion för generering av sökfrågor",
+	"Search Result Count": "Antal sökresultat",
+	"Search Tools": "Sökverktyg",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "Sökte på {{count}} sites_one",
+	"Searched {{count}} sites_other": "Sökte på {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "Söker \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "Searxng Query URL",
+	"See readme.md for instructions": "Se readme.md för instruktioner",
+	"See what's new": "Se vad som är nytt",
+	"Seed": "Seed",
+	"Select a base model": "Välj en basmodell",
+	"Select a engine": "Välj en motor",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "Välj en modell",
+	"Select a pipeline": "Välj en rörledning",
+	"Select a pipeline url": "Välj en URL för rörledningen",
+	"Select a tool": "",
+	"Select an Ollama instance": "Välj en Ollama-instans",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "Välj en modell",
+	"Select only one model to call": "Välj endast en modell att ringa",
+	"Selected model(s) do not support image inputs": "Valda modeller stöder inte bildinmatningar",
+	"Semantic distance to query": "",
+	"Send": "Skicka",
+	"Send a Message": "Skicka ett meddelande",
+	"Send message": "Skicka meddelande",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "september",
+	"Serper API Key": "Serper API-nyckel",
+	"Serply API Key": "Serply API-nyckel",
+	"Serpstack API Key": "Serpstack API-nyckel",
+	"Server connection verified": "Serveranslutning verifierad",
+	"Set as default": "Ange som standard",
+	"Set CFG Scale": "",
+	"Set Default Model": "Ange standardmodell",
+	"Set embedding model (e.g. {{model}})": "Ställ in embedding modell (t.ex. {{model}})",
+	"Set Image Size": "Ange bildstorlek",
+	"Set reranking model (e.g. {{model}})": "Ställ in reranking modell (t.ex. {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Ange steg",
+	"Set Task Model": "Ange uppgiftsmodell",
+	"Set Voice": "Ange röst",
+	"Set whisper model": "",
+	"Settings": "Inställningar",
+	"Settings saved successfully!": "Inställningar sparades framgångsrikt!",
+	"Share": "Dela",
+	"Share Chat": "Dela chatt",
+	"Share to OpenWebUI Community": "Dela till OpenWebUI Community",
+	"short-summary": "kort sammanfattning",
+	"Show": "Visa",
+	"Show Admin Details in Account Pending Overlay": "Visa administratörsinformation till väntande konton",
+	"Show Model": "",
+	"Show shortcuts": "Visa genvägar",
+	"Show your support!": "",
+	"Showcased creativity": "Visade kreativitet",
+	"Sign in": "Logga in",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Logga ut",
+	"Sign up": "Registrera dig",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Källa",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Fel vid taligenkänning: {{error}}",
+	"Speech-to-Text Engine": "Tal-till-text-motor",
+	"Stop": "",
+	"Stop Sequence": "Stoppsekvens",
+	"Stream Chat Response": "",
+	"STT Model": "Tal-till-text-modell",
+	"STT Settings": "Tal-till-text-inställningar",
+	"Subtitle (e.g. about the Roman Empire)": "Undertext (t.ex. om Romerska Imperiet)",
+	"Success": "Framgång",
+	"Successfully updated.": "Uppdaterades framgångsrikt.",
+	"Suggested": "Föreslagen",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "System",
+	"System Instructions": "",
+	"System Prompt": "Systeminstruktion",
+	"Tags": "Taggar",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "Berätta mer:",
+	"Temperature": "Temperatur",
+	"Template": "Mall",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Text-till-tal-motor",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Tack för din feedback!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Betyget ska vara ett värde mellan 0.0 (0%) och 1.0 (100%).",
+	"Theme": "Tema",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Detta säkerställer att dina värdefulla samtal sparas säkert till din backend-databas. Tack!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Detta är en experimentell funktion som kanske inte fungerar som förväntat och som kan komma att ändras när som helst.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Djupare förklaring",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tips: Uppdatera fler variabler genom att trycka på tabb-tangenten i chattinmatningen efter varje ersättning.",
+	"Title": "Titel",
+	"Title (e.g. Tell me a fun fact)": "Titel (t.ex. Berätta en kuriosa)",
+	"Title Auto-Generation": "Automatisk generering av titel",
+	"Title cannot be an empty string.": "Titeln får inte vara en tom sträng.",
+	"Title Generation Prompt": "Instruktion för titelgenerering",
+	"To access the available model names for downloading,": "För att komma åt de tillgängliga modellnamnen för nedladdning,",
+	"To access the GGUF models available for downloading,": "För att komma åt de GGUF-modellerna som finns tillgängliga för nedladdning,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "För att få tillgång till WebUI, kontakta administratören. Administratörer kan hantera behörigheter från administrationspanelen.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "till chattinmatning.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Om du vill välja verktygslådor här måste du först lägga till dem i arbetsytan \"Verktyg\".",
+	"Toast notifications for new updates": "",
+	"Today": "Idag",
+	"Toggle settings": "Växla inställningar",
+	"Toggle sidebar": "Växla sidofält",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Tokens att behålla vid kontextuppdatering (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "Verktyg",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "Topp K",
+	"Top P": "Topp P",
+	"Trouble accessing Ollama?": "Problem med att komma åt Ollama?",
+	"TTS Model": "Text-till-tal-modell",
+	"TTS Settings": "Text-till-tal-inställningar",
+	"TTS Voice": "Text-till-tal-röst",
+	"Type": "Typ",
+	"Type Hugging Face Resolve (Download) URL": "Skriv Hugging Face Resolve (nedladdning) URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Oj då! Det uppstod ett problem med anslutningen till {{provider}}.",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "Uppdatera och kopiera länk",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Uppdatera lösenord",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Ladda upp en GGUF-modell",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Ladda upp filer",
+	"Upload Pipeline": "Ladda upp rörledning",
+	"Upload Progress": "Uppladdningsframsteg",
+	"URL Mode": "URL-läge",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Använd Gravatar",
+	"Use Initials": "Använd initialer",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "användare",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "Användarbehörigheter",
+	"Users": "Användare",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Använd",
+	"Valid time units:": "Giltiga tidsenheter:",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "variabel",
+	"variable to have them replaced with clipboard content.": "variabel för att få dem ersatta med urklippsinnehåll.",
+	"Version": "Version",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "Varning",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Varning: Om du uppdaterar eller ändrar din embedding modell måste du importera alla dokument igen.",
+	"Web": "Webb",
+	"Web API": "Webb-API",
+	"Web Loader Settings": "Web Loader-inställningar",
+	"Web Search": "Webbsökning",
+	"Web Search Engine": "Webbsökmotor",
+	"Webhook URL": "Webhook-URL",
+	"WebUI Settings": "WebUI-inställningar",
+	"WebUI will make requests to": "WebUI kommer att skicka förfrågningar till",
+	"What’s New in": "Vad är nytt i",
+	"Whisper (Local)": "Whisper (lokal)",
+	"Widescreen Mode": "Bredbildsläge",
+	"Won": "",
+	"Workspace": "Arbetsyta",
+	"Write a prompt suggestion (e.g. Who are you?)": "Skriv ett instruktionsförslag (t.ex. Vem är du?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Skriv en sammanfattning på 50 ord som sammanfattar [ämne eller nyckelord].",
+	"Write something...": "",
+	"Yesterday": "Igår",
+	"You": "Dig",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Du kan anpassa dina interaktioner med stora språkmodeller genom att lägga till minnen via knappen 'Hantera' nedan, så att de blir mer användbara och skräddarsydda för dig.",
+	"You cannot clone a base model": "Du kan inte klona en basmodell",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Du har inga arkiverade samtal.",
+	"You have shared this chat": "Du har delat denna chatt",
+	"You're a helpful assistant.": "Du är en hjälpsam assistent.",
+	"You're now logged in.": "Du är nu inloggad.",
+	"Your account status is currently pending activation.": "Ditt konto väntar på att bli aktiverat",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube Loader-inställningar"
+}
diff --git a/src/lib/i18n/locales/th-TH/translation.json b/src/lib/i18n/locales/th-TH/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..a14f25d86b567013bab7e21a382b1eb609999791
--- /dev/null
+++ b/src/lib/i18n/locales/th-TH/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' หรือ '-1' สำหรับไม่มีการหมดอายุ",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(เช่น `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(เช่น `sh webui.sh --api`)",
+	"(latest)": "(ล่าสุด)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: คุณไม่สามารถลบโมเดลพื้นฐานได้",
+	"{{user}}'s Chats": "การสนทนาของ {{user}}",
+	"{{webUIName}} Backend Required": "ต้องการ Backend ของ {{webUIName}}",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "ใช้โมเดลงานเมื่อทำงานเช่นการสร้างหัวข้อสำหรับการสนทนาและการค้นหาเว็บ",
+	"a user": "ผู้ใช้",
+	"About": "เกี่ยวกับ",
+	"Account": "บัญชี",
+	"Account Activation Pending": "การเปิดใช้งานบัญชีอยู่ระหว่างดำเนินการ",
+	"Accurate information": "ข้อมูลที่ถูกต้อง",
+	"Actions": "",
+	"Active Users": "ผู้ใช้ที่ใช้งานอยู่",
+	"Add": "เพิ่ม",
+	"Add a model id": "เพิ่มรหัสโมเดล",
+	"Add a short description about what this model does": "เพิ่มคำอธิบายสั้นๆ เกี่ยวกับสิ่งที่โมเดลนี้ทำ",
+	"Add a short title for this prompt": "เพิ่มหัวข้อสั้นๆ สำหรับพรอมต์นี้",
+	"Add a tag": "เพิ่มแท็ก",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "เพิ่มพรอมต์ที่กำหนดเอง",
+	"Add Files": "เพิ่มไฟล์",
+	"Add Memory": "เพิ่มความจำ",
+	"Add Model": "เพิ่มโมเดล",
+	"Add Tag": "",
+	"Add Tags": "เพิ่มแท็ก",
+	"Add text content": "",
+	"Add User": "เพิ่มผู้ใช้",
+	"Adjusting these settings will apply changes universally to all users.": "การปรับการตั้งค่าเหล่านี้จะนำไปใช้กับผู้ใช้ทั้งหมด",
+	"admin": "ผู้ดูแลระบบ",
+	"Admin": "ผู้ดูแลระบบ",
+	"Admin Panel": "แผงผู้ดูแลระบบ",
+	"Admin Settings": "การตั้งค่าผู้ดูแลระบบ",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "ผู้ดูแลระบบสามารถเข้าถึงเครื่องมือทั้งหมดได้ตลอดเวลา; ผู้ใช้ต้องการเครื่องมือที่กำหนดต่อโมเดลในพื้นที่ทำงาน",
+	"Advanced Parameters": "พารามิเตอร์ขั้นสูง",
+	"Advanced Params": "พารามิเตอร์ขั้นสูง",
+	"All chats": "",
+	"All Documents": "เอกสารทั้งหมด",
+	"Allow Chat Deletion": "อนุญาตการลบการสนทนา",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "อนุญาตเสียงที่ไม่ใช่ท้องถิ่น",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "อนุญาตตำแหน่งผู้ใช้",
+	"Allow Voice Interruption in Call": "อนุญาตการแทรกเสียงในสาย",
+	"alphanumeric characters and hyphens": "อักขระตัวเลขและขีดกลาง",
+	"Already have an account?": "มีบัญชีอยู่แล้ว?",
+	"an assistant": "ผู้ช่วย",
+	"and": "และ",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "และสร้างลิงก์ที่แชร์ใหม่",
+	"API Base URL": "URL ฐานของ API",
+	"API Key": "คีย์ API",
+	"API Key created.": "สร้างคีย์ API แล้ว",
+	"API keys": "คีย์ API",
+	"April": "เมษายน",
+	"Archive": "เก็บถาวร",
+	"Archive All Chats": "เก็บถาวรการสนทนาทั้งหมด",
+	"Archived Chats": "การสนทนาที่เก็บถาวร",
+	"are allowed - Activate this command by typing": "ได้รับอนุญาต - เปิดใช้งานคำสั่งนี้โดยการพิมพ์",
+	"Are you sure?": "คุณแน่ใจหรือ?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "แนบไฟล์",
+	"Attention to detail": "ใส่ใจในรายละเอียด",
+	"Audio": "เสียง",
+	"August": "สิงหาคม",
+	"Auto-playback response": "ตอบสนองการเล่นอัตโนมัติ",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "สตริงการตรวจสอบ API ของ AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL": "URL ฐานของ AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "ต้องการ URL ฐานของ AUTOMATIC1111",
+	"Available list": "",
+	"available!": "พร้อมใช้งาน!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "กลับ",
+	"Bad Response": "การตอบสนองที่ไม่ดี",
+	"Banners": "แบนเนอร์",
+	"Base Model (From)": "โมเดลพื้นฐาน (จาก)",
+	"Batch Size (num_batch)": "ขนาดชุด (num_batch)",
+	"before": "ก่อน",
+	"Being lazy": "ขี้เกียจ",
+	"Brave Search API Key": "คีย์ API ของ Brave Search",
+	"Bypass SSL verification for Websites": "ข้ามการตรวจสอบ SSL สำหรับเว็บไซต์",
+	"Call": "โทร",
+	"Call feature is not supported when using Web STT engine": "ไม่รองรับฟีเจอร์การโทรเมื่อใช้เครื่องยนต์ Web STT",
+	"Camera": "กล้อง",
+	"Cancel": "ยกเลิก",
+	"Capabilities": "ความสามารถ",
+	"Change Password": "เปลี่ยนรหัสผ่าน",
+	"Character": "",
+	"Chat": "แชท",
+	"Chat Background Image": "ภาพพื้นหลังแชท",
+	"Chat Bubble UI": "UI ฟองแชท",
+	"Chat Controls": "การควบคุมแชท",
+	"Chat direction": "ทิศทางการแชท",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "แชท",
+	"Check Again": "ตรวจสอบอีกครั้ง",
+	"Check for updates": "ตรวจสอบการอัปเดต",
+	"Checking for updates...": "กำลังตรวจสอบการอัปเดต...",
+	"Choose a model before saving...": "เลือกโมเดลก่อนบันทึก...",
+	"Chunk Overlap": "ทับซ้อนส่วนข้อมูล",
+	"Chunk Params": "พารามิเตอร์ส่วนข้อมูล",
+	"Chunk Size": "ขนาดส่วนข้อมูล",
+	"Citation": "การอ้างอิง",
+	"Clear memory": "ล้างความจำ",
+	"Click here for help.": "คลิกที่นี่เพื่อขอความช่วยเหลือ",
+	"Click here to": "คลิกที่นี่เพื่อ",
+	"Click here to download user import template file.": "คลิกที่นี่เพื่อดาวน์โหลดไฟล์แม่แบบนำเข้าผู้ใช้",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "คลิกที่นี่เพื่อเลือก",
+	"Click here to select a csv file.": "คลิกที่นี่เพื่อเลือกไฟล์ csv",
+	"Click here to select a py file.": "คลิกที่นี่เพื่อเลือกไฟล์ py",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "คลิกที่นี่",
+	"Click on the user role button to change a user's role.": "คลิกที่ปุ่มบทบาทผู้ใช้เพื่อเปลี่ยนบทบาทของผู้ใช้",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "การอนุญาตเขียนคลิปบอร์ดถูกปฏิเสธ โปรดตรวจสอบการตั้งค่าเบราว์เซอร์ของคุณเพื่อให้สิทธิ์ที่จำเป็น",
+	"Clone": "โคลน",
+	"Close": "ปิด",
+	"Code execution": "",
+	"Code formatted successfully": "จัดรูปแบบโค้ดสำเร็จแล้ว",
+	"Collection": "คอลเลคชัน",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL ฐานของ ComfyUI",
+	"ComfyUI Base URL is required.": "ต้องการ URL ฐานของ ComfyUI",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "คำสั่ง",
+	"Completions": "",
+	"Concurrent Requests": "คำขอพร้อมกัน",
+	"Confirm": "ยืนยัน",
+	"Confirm Password": "ยืนยันรหัสผ่าน",
+	"Confirm your action": "ยืนยันการดำเนินการของคุณ",
+	"Connections": "การเชื่อมต่อ",
+	"Contact Admin for WebUI Access": "ติดต่อผู้ดูแลระบบสำหรับการเข้าถึง WebUI",
+	"Content": "เนื้อหา",
+	"Content Extraction": "การสกัดเนื้อหา",
+	"Context Length": "ความยาวของบริบท",
+	"Continue Response": "ตอบสนองต่อไป",
+	"Continue with {{provider}}": "ดำเนินการต่อด้วย {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "การควบคุม",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "คัดลอก URL แชทที่แชร์ไปยังคลิปบอร์ดแล้ว!",
+	"Copied to clipboard": "",
+	"Copy": "คัดลอก",
+	"Copy last code block": "คัดลอกบล็อกโค้ดสุดท้าย",
+	"Copy last response": "คัดลอกการตอบสนองล่าสุด",
+	"Copy Link": "คัดลอกลิงก์",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "คัดลอกไปยังคลิปบอร์ดสำเร็จแล้ว!",
+	"Create a model": "สร้างโมเดล",
+	"Create Account": "สร้างบัญชี",
+	"Create Knowledge": "",
+	"Create new key": "สร้างคีย์ใหม่",
+	"Create new secret key": "สร้างคีย์ลับใหม่",
+	"Created at": "สร้างเมื่อ",
+	"Created At": "สร้างเมื่อ",
+	"Created by": "สร้างโดย",
+	"CSV Import": "นำเข้า CSV",
+	"Current Model": "โมเดลปัจจุบัน",
+	"Current Password": "รหัสผ่านปัจจุบัน",
+	"Custom": "กำหนดเอง",
+	"Customize models for a specific purpose": "ปรับแต่งโมเดลสำหรับวัตถุประสงค์เฉพาะ",
+	"Dark": "มืด",
+	"Dashboard": "แดชบอร์ด",
+	"Database": "ฐานข้อมูล",
+	"December": "ธันวาคม",
+	"Default": "ค่าเริ่มต้น",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "ค่าเริ่มต้น (SentenceTransformers)",
+	"Default Model": "โมเดลค่าเริ่มต้น",
+	"Default model updated": "อัปเดตโมเดลค่าเริ่มต้นแล้ว",
+	"Default Prompt Suggestions": "คำแนะนำพรอมต์ค่าเริ่มต้น",
+	"Default User Role": "บทบาทผู้ใช้ค่าเริ่มต้น",
+	"Delete": "ลบ",
+	"Delete a model": "ลบโมเดล",
+	"Delete All Chats": "ลบการสนทนาทั้งหมด",
+	"Delete chat": "ลบแชท",
+	"Delete Chat": "ลบแชท",
+	"Delete chat?": "ลบแชท?",
+	"Delete folder?": "",
+	"Delete function?": "ลบฟังก์ชัน?",
+	"Delete prompt?": "ลบพรอมต์?",
+	"delete this link": "ลบลิงก์นี้",
+	"Delete tool?": "ลบเครื่องมือ?",
+	"Delete User": "ลบผู้ใช้",
+	"Deleted {{deleteModelTag}}": "ลบ {{deleteModelTag}}",
+	"Deleted {{name}}": "ลบ {{name}}",
+	"Description": "คำอธิบาย",
+	"Didn't fully follow instructions": "ไม่ได้ปฏิบัติตามคำแนะนำทั้งหมด",
+	"Disabled": "ปิดใช้งาน",
+	"Discover a function": "ค้นหาฟังก์ชัน",
+	"Discover a model": "ค้นหาโมเดล",
+	"Discover a prompt": "ค้นหาพรอมต์",
+	"Discover a tool": "ค้นหาเครื่องมือ",
+	"Discover, download, and explore custom functions": "ค้นหา ดาวน์โหลด และสำรวจฟังก์ชันที่กำหนดเอง",
+	"Discover, download, and explore custom prompts": "ค้นหา ดาวน์โหลด และสำรวจพรอมต์ที่กำหนดเอง",
+	"Discover, download, and explore custom tools": "ค้นหา ดาวน์โหลด และสำรวจเครื่องมือที่กำหนดเอง",
+	"Discover, download, and explore model presets": "ค้นหา ดาวน์โหลด และสำรวจพรีเซ็ตโมเดล",
+	"Dismissible": "ยกเลิกได้",
+	"Display Emoji in Call": "แสดงอิโมจิในการโทร",
+	"Display the username instead of You in the Chat": "แสดงชื่อผู้ใช้แทนคุณในการแชท",
+	"Do not install functions from sources you do not fully trust.": "อย่าติดตั้งฟังก์ชันจากแหล่งที่คุณไม่ไว้วางใจอย่างเต็มที่",
+	"Do not install tools from sources you do not fully trust.": "อย่าติดตั้งเครื่องมือจากแหล่งที่คุณไม่ไว้วางใจอย่างเต็มที่",
+	"Document": "เอกสาร",
+	"Documentation": "เอกสารประกอบ",
+	"Documents": "เอกสาร",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "ไม่เชื่อมต่อภายนอกใดๆ และข้อมูลของคุณจะอยู่บนเซิร์ฟเวอร์ที่โฮสต์ในท้องถิ่นของคุณอย่างปลอดภัย",
+	"Don't have an account?": "ยังไม่มีบัญชี?",
+	"don't install random functions from sources you don't trust.": "อย่าติดตั้งฟังก์ชันแบบสุ่มจากแหล่งที่คุณไม่ไว้วางใจ",
+	"don't install random tools from sources you don't trust.": "อย่าติดตั้งเครื่องมือแบบสุ่มจากแหล่งที่คุณไม่ไว้วางใจ",
+	"Don't like the style": "ไม่ชอบสไตล์นี้",
+	"Done": "เสร็จสิ้น",
+	"Download": "ดาวน์โหลด",
+	"Download canceled": "ยกเลิกการดาวน์โหลด",
+	"Download Database": "ดาวน์โหลดฐานข้อมูล",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "วางไฟล์ใดๆ ที่นี่เพื่อเพิ่มในการสนทนา",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "เช่น '30s', '10m' หน่วยเวลาที่ถูกต้องคือ 's', 'm', 'h'",
+	"Edit": "แก้ไข",
+	"Edit Arena Model": "",
+	"Edit Memory": "แก้ไขความจำ",
+	"Edit User": "แก้ไขผู้ใช้",
+	"ElevenLabs": "",
+	"Email": "อีเมล",
+	"Embedding Batch Size": "ขนาดชุดการฝัง",
+	"Embedding Model": "โมเดลการฝัง",
+	"Embedding Model Engine": "เครื่องยนต์โมเดลการฝัง",
+	"Embedding model set to \"{{embedding_model}}\"": "ตั้งค่าโมเดลการฝังเป็น \"{{embedding_model}}\"",
+	"Enable Community Sharing": "เปิดใช้งานการแชร์ในชุมชน",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "เปิดใช้งานการสมัครใหม่",
+	"Enable Web Search": "เปิดใช้งานการค้นหาเว็บ",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "เปิดใช้งาน",
+	"Engine": "เครื่องยนต์",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "ตรวจสอบว่าไฟล์ CSV ของคุณมี 4 คอลัมน์ในลำดับนี้: ชื่อ, อีเมล, รหัสผ่าน, บทบาท",
+	"Enter {{role}} message here": "ใส่ข้อความ {{role}} ที่นี่",
+	"Enter a detail about yourself for your LLMs to recall": "ใส่รายละเอียดเกี่ยวกับตัวคุณสำหรับ LLMs ของคุณให้จดจำ",
+	"Enter api auth string (e.g. username:password)": "ใส่สตริงการตรวจสอบ API (เช่น username:password)",
+	"Enter Brave Search API Key": "ใส่คีย์ API ของ Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "ใส่การทับซ้อนส่วนข้อมูล",
+	"Enter Chunk Size": "ใส่ขนาดส่วนข้อมูล",
+	"Enter description": "",
+	"Enter Github Raw URL": "ใส่ URL ดิบของ Github",
+	"Enter Google PSE API Key": "ใส่คีย์ API ของ Google PSE",
+	"Enter Google PSE Engine Id": "ใส่รหัสเครื่องยนต์ของ Google PSE",
+	"Enter Image Size (e.g. 512x512)": "ใส่ขนาดภาพ (เช่น 512x512)",
+	"Enter language codes": "ใส่รหัสภาษา",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "ใส่แท็กโมเดล (เช่น {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "ใส่จำนวนขั้นตอน (เช่น 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "ใส่คะแนน",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "ใส URL การค้นหาของ Searxng",
+	"Enter Serper API Key": "ใส่คีย์ API ของ Serper",
+	"Enter Serply API Key": "ใส่คีย์ API ของ Serply",
+	"Enter Serpstack API Key": "ใส่คีย์ API ของ Serpstack",
+	"Enter stop sequence": "ใส่ลำดับหยุด",
+	"Enter system prompt": "ใส่พรอมต์ระบบ",
+	"Enter Tavily API Key": "ใส่คีย์ API ของ Tavily",
+	"Enter Tika Server URL": "ใส่ URL เซิร์ฟเวอร์ของ Tika",
+	"Enter Top K": "ใส่ Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "ใส่ URL (เช่น http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "ใส่ URL (เช่น http://localhost:11434)",
+	"Enter Your Email": "ใส่อีเมลของคุณ",
+	"Enter Your Full Name": "ใส่ชื่อเต็มของคุณ",
+	"Enter your message": "ใส่ข้อความของคุณ",
+	"Enter Your Password": "ใส่รหัสผ่านของคุณ",
+	"Enter Your Role": "ใส่บทบาทของคุณ",
+	"Error": "ข้อผิดพลาด",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "การทดลอง",
+	"Export": "ส่งออก",
+	"Export All Chats (All Users)": "ส่งออกการสนทนาทั้งหมด (ผู้ใช้ทั้งหมด)",
+	"Export chat (.json)": "ส่งออกการสนทนา (.json)",
+	"Export Chats": "ส่งออกการสนทนา",
+	"Export Config to JSON File": "",
+	"Export Functions": "ส่งออกฟังก์ชัน",
+	"Export LiteLLM config.yaml": "ส่งออกการตั้งค่า LiteLLM config.yaml",
+	"Export Models": "ส่งออกโมเดล",
+	"Export Prompts": "ส่งออกพรอมต์",
+	"Export Tools": "ส่งออกเครื่องมือ",
+	"External Models": "โมเดลภายนอก",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "สร้างคีย์ API ล้มเหลว",
+	"Failed to read clipboard contents": "อ่านเนื้อหาคลิปบอร์ดล้มเหลว",
+	"Failed to update settings": "อัปเดตการตั้งค่าล้มเหลว",
+	"Failed to upload file.": "",
+	"February": "กุมภาพันธ์",
+	"Feedback History": "",
+	"Feel free to add specific details": "สามารถเพิ่มรายละเอียดเฉพาะได้",
+	"File": "ไฟล์",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "โหมดไฟล์",
+	"File not found.": "ไม่พบไฟล์",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "ไฟล์",
+	"Filter is now globally disabled": "การกรองถูกปิดใช้งานทั่วโลกแล้ว",
+	"Filter is now globally enabled": "การกรองถูกเปิดใช้งานทั่วโลกแล้ว",
+	"Filters": "ตัวกรอง",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "ตรวจพบการปลอมแปลงลายนิ้วมือ: ไม่สามารถใช้ชื่อย่อเป็นอวตารได้ ใช้รูปโปรไฟล์เริ่มต้น",
+	"Fluidly stream large external response chunks": "สตรีมชิ้นส่วนการตอบสนองขนาดใหญ่จากภายนอกอย่างลื่นไหล",
+	"Focus chat input": "โฟกัสการป้อนแชท",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "ปฏิบัติตามคำแนะนำอย่างสมบูรณ์แบบ",
+	"Form": "ฟอร์ม",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "การลงโทษความถี่",
+	"Function": "",
+	"Function created successfully": "สร้างฟังก์ชันสำเร็จ",
+	"Function deleted successfully": "ลบฟังก์ชันสำเร็จ",
+	"Function Description (e.g. A filter to remove profanity from text)": "คำอธิบายฟังก์ชัน (เช่น ตัวกรองเพื่อเอาคำหยาบออกจากข้อความ)",
+	"Function ID (e.g. my_filter)": "รหัสฟังก์ชัน (เช่น my_filter)",
+	"Function is now globally disabled": "ฟังก์ชันถูกปิดใช้งานทั่วโลกแล้ว",
+	"Function is now globally enabled": "ฟังก์ชันถูกเปิดใช้งานทั่วโลกแล้ว",
+	"Function Name (e.g. My Filter)": "ชื่อฟังก์ชัน (เช่น My Filter)",
+	"Function updated successfully": "อัปเดตฟังก์ชันสำเร็จ",
+	"Functions": "ฟังก์ชัน",
+	"Functions allow arbitrary code execution": "ฟังก์ชันอนุญาตการเรียกใช้โค้ดโดยพลการ",
+	"Functions allow arbitrary code execution.": "ฟังก์ชันอนุญาตการเรียกใช้โค้ดโดยพลการ",
+	"Functions imported successfully": "นำเข้าฟังก์ชันสำเร็จ",
+	"General": "ทั่วไป",
+	"General Settings": "การตั้งค่าทั่วไป",
+	"Generate Image": "สร้างภาพ",
+	"Generating search query": "สร้างคำค้นหา",
+	"Generation Info": "ข้อมูลการสร้าง",
+	"Get up and running with": "เริ่มต้นใช้งานด้วย",
+	"Global": "ทั่วโลก",
+	"Good Response": "การตอบสนองที่ดี",
+	"Google PSE API Key": "คีย์ API ของ Google PSE",
+	"Google PSE Engine Id": "รหัสเครื่องยนต์ของ Google PSE",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "",
+	"has no conversations.": "ไม่มีการสนทนา",
+	"Hello, {{name}}": "สวัสดี, {{name}}",
+	"Help": "ช่วยเหลือ",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "ซ่อน",
+	"Hide Model": "ซ่อนโมเดล",
+	"How can I help you today?": "วันนี้ฉันจะช่วยอะไรคุณได้บ้าง?",
+	"Hybrid Search": "การค้นหาแบบไฮบริด",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "ฉันรับทราบว่าฉันได้อ่านและเข้าใจผลกระทบของการกระทำของฉัน ฉันทราบถึงความเสี่ยงที่เกี่ยวข้องกับการเรียกใช้โค้ดโดยพลการและฉันได้ตรวจสอบความน่าเชื่อถือของแหล่งที่มาแล้ว",
+	"ID": "",
+	"Image Generation (Experimental)": "การสร้างภาพ (การทดลอง)",
+	"Image Generation Engine": "เครื่องยนต์การสร้างภาพ",
+	"Image Settings": "การตั้งค่าภาพ",
+	"Images": "ภาพ",
+	"Import Chats": "นำเข้าการสนทนา",
+	"Import Config from JSON File": "",
+	"Import Functions": "นำเข้าฟังก์ชัน",
+	"Import Models": "นำเข้าโมเดล",
+	"Import Prompts": "นำเข้าพรอมต์",
+	"Import Tools": "นำเข้าเครื่องมือ",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "รวมแฟลก `--api-auth` เมื่อเรียกใช้ stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "รวมแฟลก `--api` เมื่อเรียกใช้ stable-diffusion-webui",
+	"Info": "ข้อมูล",
+	"Input commands": "คำสั่งป้อนข้อมูล",
+	"Install from Github URL": "ติดตั้งจาก URL ของ Github",
+	"Instant Auto-Send After Voice Transcription": "ส่งอัตโนมัติทันทีหลังจากการถอดเสียง",
+	"Interface": "อินเทอร์เฟซ",
+	"Invalid file format.": "",
+	"Invalid Tag": "แท็กไม่ถูกต้อง",
+	"January": "มกราคม",
+	"join our Discord for help.": "เข้าร่วม Discord ของเราเพื่อขอความช่วยเหลือ",
+	"JSON": "JSON",
+	"JSON Preview": "ดูตัวอย่าง JSON",
+	"July": "กรกฎาคม",
+	"June": "มิถุนายน",
+	"JWT Expiration": "การหมดอายุของ JWT",
+	"JWT Token": "โทเค็น JWT",
+	"Keep Alive": "คงอยู่",
+	"Keyboard shortcuts": "ทางลัดแป้นพิมพ์",
+	"Knowledge": "ความรู้",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "ภาษา",
+	"large language models, locally.": "โมเดลภาษาขนาดใหญ่ในเครื่อง",
+	"Last Active": "ใช้งานล่าสุด",
+	"Last Modified": "แก้ไขล่าสุด",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "แสง",
+	"Listening...": "กำลังฟัง...",
+	"LLMs can make mistakes. Verify important information.": "LLMs สามารถทำผิดพลาดได้ ตรวจสอบข้อมูลสำคัญ",
+	"Local Models": "โมเดลท้องถิ่น",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "สร้างโดยชุมชน OpenWebUI",
+	"Make sure to enclose them with": "",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "จัดการ",
+	"Manage Arena Models": "",
+	"Manage Models": "จัดการโมเดล",
+	"Manage Ollama Models": "จัดการโมเดล Ollama",
+	"Manage Pipelines": "จัดการไปป์ไลน์",
+	"March": "มีนาคม",
+	"Max Tokens (num_predict)": "โทเค็นสูงสุด (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "สามารถดาวน์โหลดโมเดลได้สูงสุด 3 โมเดลในเวลาเดียวกัน โปรดลองอีกครั้งในภายหลัง",
+	"May": "พฤษภาคม",
+	"Memories accessible by LLMs will be shown here.": "",
+	"Memory": "ความจำ",
+	"Memory added successfully": "เพิ่มโมเดลสำเร็จ",
+	"Memory cleared successfully": "ล้าง",
+	"Memory deleted successfully": "ลบโมเดลสำเร็จ",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "ข้อความที่คุณส่งหลังจากสร้างลิงก์ของคุณแล้วจะไม่ถูกแชร์ ผู้ใช้ที่มี URL จะสามารถดูแชทที่แชร์ได้",
+	"Min P": "",
+	"Minimum Score": "คะแนนขั้นต่ำ",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "โมเดล '{{modelName}}' ถูกดาวน์โหลดเรียบร้อยแล้ว",
+	"Model '{{modelTag}}' is already in queue for downloading.": "โมเดล '{{modelTag}}' กำลังอยู่ในคิวสำหรับการดาวน์โหลด",
+	"Model {{modelId}} not found": "ไม่พบโมเดล {{modelId}}",
+	"Model {{modelName}} is not vision capable": "โมเดล {{modelName}} ไม่มีคุณสมบัติวิสชั่น",
+	"Model {{name}} is now {{status}}": "โมเดล {{name}} ขณะนี้ {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "สร้างโมเดลสำเร็จ!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "ตรวจพบเส้นทางระบบไฟล์ของโมเดล ต้องการชื่อย่อของโมเดลสำหรับการอัปเดต ไม่สามารถดำเนินการต่อได้",
+	"Model ID": "รหัสโมเดล",
+	"Model Name": "",
+	"Model not selected": "ยังไม่ได้เลือกโมเดล",
+	"Model Params": "พารามิเตอร์ของโมเดล",
+	"Model updated successfully": "อัปเดตโมเดลเรียบร้อยแล้ว",
+	"Model Whitelisting": "การอนุญาตโมเดล",
+	"Model(s) Whitelisted": "โมเดลที่ได้รับอนุญาต",
+	"Modelfile Content": "เนื้อหาของไฟล์โมเดล",
+	"Models": "โมเดล",
+	"more": "",
+	"More": "เพิ่มเติม",
+	"Move to Top": "",
+	"Name": "ชื่อ",
+	"Name your model": "ตั้งชื่อโมเดลของคุณ",
+	"New Chat": "แชทใหม่",
+	"New folder": "",
+	"New Password": "รหัสผ่านใหม่",
+	"No content found": "",
+	"No content to speak": "ไม่มีเนื้อหาที่จะพูด",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "ไม่ได้เลือกไฟล์",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "ไม่มีผลลัพธ์",
+	"No search query generated": "ไม่มีการสร้างคำค้นหา",
+	"No source available": "ไม่มีแหล่งข้อมูล",
+	"No valves to update": "ไม่มีวาล์วที่จะอัปเดต",
+	"None": "ไม่มี",
+	"Not factually correct": "ไม่ถูกต้องตามข้อเท็จจริง",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "หมายเหตุ: หากคุณตั้งค่าคะแนนขั้นต่ำ การค้นหาจะคืนเอกสารที่มีคะแนนมากกว่าหรือเท่ากับคะแนนขั้นต่ำเท่านั้น",
+	"Notes": "",
+	"Notifications": "การแจ้งเตือน",
+	"November": "พฤศจิกายน",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth ID",
+	"October": "ตุลาคม",
+	"Off": "ปิด",
+	"Okay, Let's Go!": "ตกลง ไปกัน!",
+	"OLED Dark": "OLED โหมดมื",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "ปิด Ollama API",
+	"Ollama API is disabled": "Ollama API ถูกปิดใช้งาน",
+	"Ollama Version": "เวอร์ชั่น Ollama",
+	"On": "เปิด",
+	"Only": "เท่านั้น",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "อนุญาตให้ใช้เฉพาะอักขระตัวอักษรและตัวเลข รวมถึงเครื่องหมายขีดกลางในสตริงคำสั่งเท่านั้น",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "อุ๊บส์! ดูเหมือนว่า URL ไม่ถูกต้อง กรุณาตรวจสอบและลองใหม่อีกครั้ง",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "อุ๊บส์! คุณกำลังใช้วิธีที่ไม่รองรับ (เฉพาะเว็บส่วนหน้า) กรุณาให้บริการ WebUI จากเว็บส่วนแบ็กเอนด์",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "เปิดแชทใหม่",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "เวอร์ชั่น Open WebUI (v{{OPEN_WEBUI_VERSION}}) ต่ำกว่าเวอร์ชั่นที่ต้องการ (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "การตั้งค่า OpenAI API",
+	"OpenAI API Key is required.": "จำเป็นต้องใช้คีย์ OpenAI API",
+	"OpenAI URL/Key required.": "จำเป็นต้องใช้ URL/คีย์ OpenAI",
+	"or": "หรือ",
+	"Other": "อื่น ๆ",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "รหัสผ่าน",
+	"PDF document (.pdf)": "เอกสาร PDF (.pdf)",
+	"PDF Extract Images (OCR)": "การแยกรูปภาพจาก PDF (OCR)",
+	"pending": "รอดำเนินการ",
+	"Permission denied when accessing media devices": "ถูกปฏิเสธเมื่อเข้าถึงอุปกรณ์",
+	"Permission denied when accessing microphone": "ถูกปฏิเสธเมื่อเข้าถึงไมโครโฟน",
+	"Permission denied when accessing microphone: {{error}}": "การอนุญาตถูกปฏิเสธเมื่อเข้าถึงไมโครโฟน: {{error}}",
+	"Personalization": "การปรับแต่ง",
+	"Pin": "ปักหมุด",
+	"Pinned": "ปักหมุดแล้ว",
+	"Pipeline deleted successfully": "ลบไปป์ไลน์เรียบร้อยแล้ว",
+	"Pipeline downloaded successfully": "ดาวน์โหลดไปป์ไลน์เรียบร้อยแล้ว",
+	"Pipelines": "ไปป์ไลน์",
+	"Pipelines Not Detected": "ไม่พบไปป์ไลน์",
+	"Pipelines Valves": "วาล์วของไปป์ไลน์",
+	"Plain text (.txt)": "ไฟล์ข้อความ (.txt)",
+	"Playground": "สนามทดสอบ",
+	"Please carefully review the following warnings:": "โปรดตรวจสอบคำเตือนต่อไปนี้อย่างละเอียด:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "ทัศนคติด้านบวก",
+	"Previous 30 days": "30 วันที่ผ่านมา",
+	"Previous 7 days": "7 วันที่ผ่านมา",
+	"Profile Image": "รูปโปรไฟล์",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "พรอมต์ (เช่น บอกข้อเท็จจริงที่น่าสนุกเกี่ยวกับจักรวรรดิโรมัน)",
+	"Prompt Content": "เนื้อหาพรอมต์",
+	"Prompt suggestions": "",
+	"Prompts": "พรอมต์",
+	"Pull \"{{searchValue}}\" from Ollama.com": "",
+	"Pull a model from Ollama.com": "",
+	"Query Params": "พารามิเตอร์การค้นหา",
+	"RAG Template": "แม่แบบ RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "อ่านออกเสียง",
+	"Record voice": "บันทึกเสียง",
+	"Redirecting you to OpenWebUI Community": "กำลังเปลี่ยนเส้นทางคุณไปยังชุมชน OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "เรียกตัวเองว่า \"ผู้ใช้\" (เช่น \"ผู้ใช้กำลังเรียนภาษาสเปน\")",
+	"References from": "",
+	"Refused when it shouldn't have": "ปฏิเสธเมื่อไม่ควรทำ",
+	"Regenerate": "สร้างใหม่",
+	"Release Notes": "บันทึกรุ่น",
+	"Relevance": "",
+	"Remove": "ลบ",
+	"Remove Model": "ลบโมเดล",
+	"Rename": "เปลี่ยนชื่อ",
+	"Repeat Last N": "ทำซ้ำครั้งล่าสุด N",
+	"Request Mode": "โหมดคำขอ",
+	"Reranking Model": "จัดอันดับใหม่โมเดล",
+	"Reranking model disabled": "ปิดการใช้งานโมเดลการจัดอันดับใหม่",
+	"Reranking model set to \"{{reranking_model}}\"": "ตั้งค่าโมเดลการจัดอันดับใหม่เป็น \"{{reranking_model}}\"",
+	"Reset": "รีเซ็ต",
+	"Reset Upload Directory": "รีเซ็ตไดเร็กทอรีการอัปโหลด",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "ตอบสนองการคัดลอกอัตโนมัติไปยังคลิปบอร์ด",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "ไม่สามารถเปิดการแจ้งเตือนการตอบสนองได้เนื่องจากเว็บไซต์ปฏิเสธ กรุณาเข้าการตั้งค่าเบราว์เซอร์ของคุณเพื่อให้สิทธิ์การเข้าถึงที่จำเป็น",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "บทบาท",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "รัน Llama 2, Code Llama และโมเดลอื่นๆ ปรับแต่งและสร้างของคุณเอง",
+	"Running": "กำลังทำงาน",
+	"Save": "บันทึก",
+	"Save & Create": "บันทึกและสร้าง",
+	"Save & Update": "บันทึกและอัปเดต",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "การบันทึกบันทึกการสนทนาโดยตรงไปยังที่จัดเก็บในเบราว์เซอร์ของคุณไม่ได้รับการสนับสนุนอีกต่อไป โปรดสละเวลาสักครู่เพื่อดาวน์โหลดและลบบันทึกการสนทนาของคุณโดยคลิกที่ปุ่มด้านล่าง ไม่ต้องกังวล คุณสามารถนำเข้าบันทึกการสนทนาของคุณกลับไปยังส่วนแบ็กเอนด์ได้อย่างง่ายดายผ่าน",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "ค้นหา",
+	"Search a model": "ค้นหาโมเดล",
+	"Search Chats": "ค้นหาแชท",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "ค้นหาฟังก์ชัน",
+	"Search Knowledge": "",
+	"Search Models": "ค้นหาโมเดล",
+	"Search Prompts": "ค้นหาพรอมต์",
+	"Search Query Generation Prompt": "พรอมต์การสร้างคำค้นหา",
+	"Search Result Count": "จำนวนผลลัพธ์การค้นหา",
+	"Search Tools": "เครื่องมือค้นหา",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "ค้นหา {{count}} เว็บไซต์",
+	"Searched {{count}} sites_other": "ค้นหา {{count}} เว็บไซต์",
+	"Searching \"{{searchQuery}}\"": "กำลังค้นหา \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "URL คำค้นหา",
+	"See readme.md for instructions": "ดู readme.md สำหรับคำแนะนำ",
+	"See what's new": "ดูสิ่งที่ใหม่",
+	"Seed": "Seed",
+	"Select a base model": "เลือกโมเดลฐาน",
+	"Select a engine": "เลือกเอนจิน",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "เลือกฟังก์ชัน",
+	"Select a model": "เลือกโมเดล",
+	"Select a pipeline": "เลือกไปป์ไลน์",
+	"Select a pipeline url": "เลือก URL ไปป์ไลน์",
+	"Select a tool": "เลือกเครื่องมือ",
+	"Select an Ollama instance": "เลือกอินสแตนซ์ Ollama",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "เลือกโมเดล",
+	"Select only one model to call": "เลือกเพียงโมเดลเดียวที่จะใช้",
+	"Selected model(s) do not support image inputs": "โมเดลที่เลือกไม่รองรับภาพ",
+	"Semantic distance to query": "",
+	"Send": "ส่ง",
+	"Send a Message": "ส่งข้อความ",
+	"Send message": "ส่งข้อความ",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "กันยายน",
+	"Serper API Key": "คีย์ API ของ Serper",
+	"Serply API Key": "คีย์ API ของ Serply",
+	"Serpstack API Key": "คีย์ API ของ Serpstack",
+	"Server connection verified": "ยืนยันการเชื่อมต่อเซิร์ฟเวอร์แล้ว",
+	"Set as default": "ตั้งเป็นค่าเริ่มต้น",
+	"Set CFG Scale": "",
+	"Set Default Model": "ตั้งโมเดลเริ่มต้น",
+	"Set embedding model (e.g. {{model}})": "ตั้งค่าโมเดลการฝัง (เช่น {{model}})",
+	"Set Image Size": "ตั้งค่าขนาดภาพ",
+	"Set reranking model (e.g. {{model}})": "ตั้งค่าโมเดลการจัดอันดับใหม่ (เช่น {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "ตั้งค่าขั้นตอน",
+	"Set Task Model": "ตั้งค่าโมเดลงาน",
+	"Set Voice": "ตั้งค่าเสียง",
+	"Set whisper model": "",
+	"Settings": "การตั้งค่า",
+	"Settings saved successfully!": "บันทึกการตั้งค่าเรียบร้อยแล้ว!",
+	"Share": "แชร์",
+	"Share Chat": "แชร์แชท",
+	"Share to OpenWebUI Community": "แชร์ไปยังชุมชน OpenWebUI",
+	"short-summary": "สรุปสั้นๆ",
+	"Show": "แสดง",
+	"Show Admin Details in Account Pending Overlay": "แสดงรายละเอียดผู้ดูแลระบบในหน้าจอรอการอนุมัติบัญชี",
+	"Show Model": "แสดงโมเดล",
+	"Show shortcuts": "แสดงทางลัด",
+	"Show your support!": "แสดงการสนับสนุนของคุณ!",
+	"Showcased creativity": "แสดงความคิดสร้างสรรค์",
+	"Sign in": "ลงชื่อเข้าใช้",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "ลงชื่อออก",
+	"Sign up": "สมัครสมาชิก",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "แหล่งที่มา",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "ข้อผิดพลาดในการรู้จำเสียง: {{error}}",
+	"Speech-to-Text Engine": "เครื่องมือแปลงเสียงเป็นข้อความ",
+	"Stop": "",
+	"Stop Sequence": "หยุดลำดับ",
+	"Stream Chat Response": "",
+	"STT Model": "โมเดลแปลงเสียงเป็นข้อความ",
+	"STT Settings": "การตั้งค่าแปลงเสียงเป็นข้อความ",
+	"Subtitle (e.g. about the Roman Empire)": "คำบรรยาย (เช่น เกี่ยวกับจักรวรรดิโรมัน)",
+	"Success": "สำเร็จ",
+	"Successfully updated.": "อัปเดตเรียบร้อยแล้ว",
+	"Suggested": "แนะนำ",
+	"Support": "สนับสนุน",
+	"Support this plugin:": "สนับสนุนปลั๊กอินนี้:",
+	"Sync directory": "",
+	"System": "ระบบ",
+	"System Instructions": "",
+	"System Prompt": "ระบบพรอมต์",
+	"Tags": "ป้ายชื่อ",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "แตะเพื่อขัดจังหวะ",
+	"Tavily API Key": "คีย์ API ของ Tavily",
+	"Tell us more:": "บอกเรามากขึ้น:",
+	"Temperature": "อุณหภูมิ",
+	"Template": "แม่แบบ",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "เครื่องมือแปลงข้อความเป็นเสียง",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "ขอบคุณสำหรับความคิดเห็นของคุณ!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "นักพัฒนาที่อยู่เบื้องหลังปลั๊กอินนี้เป็นอาสาสมัครที่มีชื่นชอบการแบ่งบัน หากคุณพบว่าปลั๊กอินนี้มีประโยชน์ โปรดพิจารณาสนับสนุนการพัฒนาของเขา",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "คะแนนควรอยู่ระหว่าง 0.0 (0%) ถึง 1.0 (100%)",
+	"Theme": "ธีม",
+	"Thinking...": "กำลังคิด...",
+	"This action cannot be undone. Do you wish to continue?": "การกระทำนี้ไม่สามารถย้อนกลับได้ คุณต้องการดำเนินการต่อหรือไม่?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "สิ่งนี้ทำให้มั่นใจได้ว่าการสนทนาที่มีค่าของคุณจะถูกบันทึกอย่างปลอดภัยในฐานข้อมูลแบ็กเอนด์ของคุณ ขอบคุณ!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "นี่เป็นฟีเจอร์ทดลอง อาจไม่ทำงานตามที่คาดไว้และอาจมีการเปลี่ยนแปลงได้ตลอดเวลา",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "สิ่งนี้จะลบ",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "คำอธิบายอย่างละเอียด",
+	"Tika": "Tika",
+	"Tika Server URL required.": "จำเป็นต้องมี URL ของเซิร์ฟเวอร์ Tika",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "เคล็ดลับ: อัปเดตช่องตัวแปรหลายช่องติดต่อกันโดยการกดปุ่มแท็บในช่องใส่ข้อความแชทหลังจากแต่ละการแทนที่",
+	"Title": "ชื่อเรื่อง",
+	"Title (e.g. Tell me a fun fact)": "ชื่อเรื่อง (เช่น บอกข้อเท็จจริงที่น่าสนุก)",
+	"Title Auto-Generation": "การสร้างชื่ออัตโนมัติ",
+	"Title cannot be an empty string.": "ชื่อเรื่องไม่สามารถเป็นสตริงว่างได้",
+	"Title Generation Prompt": "พรอมต์การสร้างชื่อเรื่อง",
+	"To access the available model names for downloading,": "ในการเข้าถึงชื่อโมเดลที่มีให้ดาวน์โหลด",
+	"To access the GGUF models available for downloading,": "ในการเข้าถึงโมเดล GGUF ที่มีให้ดาวน์โหลด",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "ในการเข้าถึง WebUI โปรดติดต่อผู้ดูแลระบบ ผู้ดูแลระบบสามารถจัดการสถานะผู้ใช้จากแผงควบคุมผู้ดูแลระบบ",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "ไปยังช่องใส่ข้อความแชท",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "ในการเลือกฟิลเตอร์ที่นี่ ให้เพิ่มไปยังพื้นที่ทำงาน \"ฟังก์ชัน\" ก่อน",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "ในการเลือกชุดเครื่องมือที่นี่ ให้เพิ่มไปยังพื้นที่ทำงาน \"เครื่องมือ\" ก่อน",
+	"Toast notifications for new updates": "",
+	"Today": "วันนี้",
+	"Toggle settings": "สลับการตั้งค่า",
+	"Toggle sidebar": "สลับแถบด้านข้าง",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "โทเค็นที่เก็บไว้เมื่อรีเฟรชบริบท (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "สร้างเครื่องมือเรียบร้อยแล้ว",
+	"Tool deleted successfully": "ลบเครื่องมือเรียบร้อยแล้ว",
+	"Tool imported successfully": "นำเข้าเครื่องมือเรียบร้อยแล้ว",
+	"Tool updated successfully": "อัปเดตเครื่องมือเรียบร้อยแล้ว",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "คำอธิบายชุดเครื่องมือ (เช่น ชุดเครื่องมือสำหรับการดำเนินการต่างๆ)",
+	"Toolkit ID (e.g. my_toolkit)": "ID ชุดเครื่องมือ (เช่น my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "ชื่อชุดเครื่องมือ (เช่น My ToolKit)",
+	"Tools": "เครื่องมือ",
+	"Tools are a function calling system with arbitrary code execution": "เครื่องมือคือระบบการเรียกใช้ฟังก์ชันที่สามารถดำเนินการโค้ดใดๆ ได้",
+	"Tools have a function calling system that allows arbitrary code execution": "เครื่องมือมีระบบการเรียกใช้ฟังก์ชันที่สามารถดำเนินการโค้ดใดๆ ได้",
+	"Tools have a function calling system that allows arbitrary code execution.": "เครื่องมือมีระบบการเรียกใช้ฟังก์ชันที่สามารถดำเนินการโค้ดใดๆ ได้",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "มีปัญหาในการเข้าถึง Ollama?",
+	"TTS Model": "โมเดลแปลงข้อความเป็นเสียง",
+	"TTS Settings": "การตั้งค่าแปลงข้อความเป็นเสียง",
+	"TTS Voice": "เสียงแปลงข้อความเป็นเสียง",
+	"Type": "ประเภท",
+	"Type Hugging Face Resolve (Download) URL": "พิมพ์ URL ของ Hugging Face Resolve (Download)",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "อุ๊ย! มีปัญหาในการเชื่อมต่อกับ {{provider}}",
+	"UI": "ส่วนติดต่อผู้ใช้",
+	"Unpin": "ยกเลิกการปักหมุด",
+	"Untagged": "",
+	"Update": "อัปเดต",
+	"Update and Copy Link": "อัปเดตและคัดลอกลิงก์",
+	"Update for the latest features and improvements.": "",
+	"Update password": "อัปเดตรหัสผ่าน",
+	"Updated": "",
+	"Updated at": "อัปเดตเมื่อ",
+	"Updated At": "",
+	"Upload": "อัปโหลด",
+	"Upload a GGUF model": "อัปโหลดโมเดล GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "อัปโหลดไฟล์",
+	"Upload Pipeline": "อัปโหลดพายป์ไลน์",
+	"Upload Progress": "ความคืบหน้าการอัปโหลด",
+	"URL Mode": "โหมด URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "ใช้ Gravatar",
+	"Use Initials": "ใช้ตัวย่อ",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "ผู้ใช้",
+	"User": "",
+	"User location successfully retrieved.": "ดึงตำแหน่งที่ตั้งของผู้ใช้เรียบร้อยแล้ว",
+	"User Permissions": "สิทธิ์ของผู้ใช้",
+	"Users": "ผู้ใช้",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "ใช้",
+	"Valid time units:": "หน่วยเวลาใช้ได้:",
+	"Valves": "วาล์ว",
+	"Valves updated": "วาล์วที่อัปเดตแล้ว",
+	"Valves updated successfully": "อัปเดตวาล์วเรียบร้อยแล้ว",
+	"variable": "ตัวแปร",
+	"variable to have them replaced with clipboard content.": "ตัวแปรเพื่อให้แทนที่ด้วยเนื้อหาคลิปบอร์ด",
+	"Version": "เวอร์ชัน",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "เสียง",
+	"Voice Input": "",
+	"Warning": "คำเตือน",
+	"Warning:": "คำเตือน:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "คำเตือน: หากคุณอัปเดตหรือเปลี่ยนโมเดลการฝัง คุณจะต้องนำเข้าเอกสารทั้งหมดอีกครั้ง",
+	"Web": "เว็บ",
+	"Web API": "เว็บ API",
+	"Web Loader Settings": "การตั้งค่าเว็บโหลดเดอร์",
+	"Web Search": "การค้นหาเว็บ",
+	"Web Search Engine": "เครื่องมือค้นหาเว็บ",
+	"Webhook URL": "URL ของ Webhook",
+	"WebUI Settings": "การตั้งค่า WebUI",
+	"WebUI will make requests to": "WebUI จะทำการร้องขอไปที่",
+	"What’s New in": "มีอะไรใหม่ใน",
+	"Whisper (Local)": "Whisper (โลคอล)",
+	"Widescreen Mode": "โหมดหน้าจอกว้าง",
+	"Won": "",
+	"Workspace": "พื้นที่ทำงาน",
+	"Write a prompt suggestion (e.g. Who are you?)": "เขียนคำแนะนำพรอมต์ (เช่น คุณคือใคร?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "เขียนสรุปใน 50 คำที่สรุป [หัวข้อหรือคำสำคัญ]",
+	"Write something...": "",
+	"Yesterday": "เมื่อวาน",
+	"You": "คุณ",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "คุณสามารถปรับแต่งการโต้ตอบของคุณกับ LLMs โดยเพิ่มความทรงจำผ่านปุ่ม 'จัดการ' ด้านล่าง ทำให้มันมีประโยชน์และเหมาะกับคุณมากขึ้น",
+	"You cannot clone a base model": "คุณไม่สามารถโคลนโมเดลฐานได้",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "คุณไม่มีการสนทนาที่เก็บถาวร",
+	"You have shared this chat": "คุณได้แชร์แชทนี้แล้ว",
+	"You're a helpful assistant.": "คุณคือผู้ช่วยที่มีประโยชน์",
+	"You're now logged in.": "คุณเข้าสู่ระบบแล้ว",
+	"Your account status is currently pending activation.": "สถานะบัญชีของคุณกำลังรอการเปิดใช้งาน",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "การสนับสนุนทั้งหมดของคุณจะไปยังนักพัฒนาปลั๊กอินโดยตรง; Open WebUI ไม่รับส่วนแบ่งใด ๆ อย่างไรก็ตาม แพลตฟอร์มการระดมทุนที่เลือกอาจมีค่าธรรมเนียมของตัวเอง",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "การตั้งค่าโหลดเดอร์ Youtube"
+}
diff --git a/src/lib/i18n/locales/tk-TM/translation.json b/src/lib/i18n/locales/tk-TM/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..5f0cae683e3c550eb529515cd794840459c4f362
--- /dev/null
+++ b/src/lib/i18n/locales/tk-TM/translation.json
@@ -0,0 +1,710 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' ýa-da '-1' möhlet ýok.",
+	"(Beta)": "(Beta)",
+	"(e.g. `sh webui.sh --api`)": "(meselem, `sh webui.sh --api`)",
+	"(latest)": "(iň soňky)",
+	"{{ models }}": "{{ modeller }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Esasy modeli öçürip bilmersiňiz",
+	"{{modelName}} is thinking...": "{{modelName}} pikirlenýär...",
+	"{{user}}'s Chats": "{{user}}'iň Çatlary",
+	"{{webUIName}} Backend Required": "{{webUIName}} Backend Zerur",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Çatlar we web gözleg soraglary üçin başlyk döretmek ýaly wezipeleri ýerine ýetirýän wagty ulanylýar",
+	"a user": "ulanyjy",
+	"About": "Barada",
+	"Account": "Hasap",
+	"Accurate information": "Takyk maglumat",
+	"Add": "Goş",
+	"Add a model id": "Model ID goş",
+	"Add a short description about what this model does": "Bu modeliň näme edýändigi barada gysgaça düşündiriş goşuň",
+	"Add a short title for this prompt": "Bu düşündiriş üçin gysga başlyk goşuň",
+	"Add a tag": "Bir tag goşuň",
+	"Add custom prompt": "Özboluşly düşündiriş goşuň",
+	"Add Docs": "Resminamalar goş",
+	"Add Files": "Faýllar goş",
+	"Add Memory": "Ýat goş",
+	"Add message": "Habar goş",
+	"Add Model": "Model goş",
+	"Add Tags": "Taglar goş",
+	"Add User": "Ulanyjy goş",
+	"Adjusting these settings will apply changes universally to all users.": "Bu sazlamalary düzetmek ähli ulanyjylara birmeňzeş üýtgeşmeler girizer.",
+	"admin": "admin",
+	"Admin Panel": "Admin Paneli",
+	"Admin Settings": "Admin Sazlamalary",
+	"Advanced Parameters": "Ösen Parametrler",
+	"Advanced Params": "Ösen Parametrler",
+	"all": "ähli",
+	"All Documents": "Ähli Resminamalar",
+	"All Users": "Ähli Ulanyjylar",
+	"Allow": "Rugsat ber",
+	"Allow Chat Deletion": "Çaty öçürmäge rugsat ber",
+	"alphanumeric characters and hyphens": "harply-sanjy belgiler we defisler",
+	"Already have an account?": "Hasabyňyz barmy?",
+	"an assistant": "kömekçi",
+	"An error occurred while processing files.": "",
+	"and": "we",
+	"and create a new shared link.": "we täze paýlaşylan baglanyşyk dörediň.",
+	"API Base URL": "API Esasy URL",
+	"API Key": "API Açar",
+	"API Key created.": "API Açar döredildi.",
+	"API keys": "API açarlary",
+	"April": "Aprel",
+	"Archive": "Arhiw",
+	"Archive All Chats": "Ähli Çatlary Arhiwle",
+	"Archived Chats": "Arhiwlenen Çatlar",
+	"are allowed - Activate this command by typing": "rugsat berilýär - bu buýrugy ýazyň",
+	"Are you sure?": "Kepillikmi?",
+	"Attach file": "Faýl goş",
+	"Attention to detail": "Detala üns",
+	"Audio": "Audio",
+	"August": "Awgust",
+	"Auto-playback response": "Awto-gaýtadan jogap",
+	"Auto-send input after 3 sec.": "3 sekuntdan soň awtomatiki ugrat",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Esasy URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Esasy URL zerur.",
+	"available!": "elýeterli!",
+	"Back": "Yzyna",
+	"Bad Response": "Erbet Jogap",
+	"Banners": "Bannerler",
+	"Base Model (From)": "Esasy Model (Kimden)",
+	"before": "öň",
+	"Being lazy": "Ýaltalyk",
+	"Brave Search API Key": "Brave Gözleg API Açar",
+	"Bypass SSL verification for Websites": "Web sahypalary üçin SSL barlagyny geçmek",
+	"Cancel": "Ýatyrmak",
+	"Capabilities": "Ukyplar",
+	"Change Password": "Paroly Üýtget",
+	"Chat": "Çat",
+	"Chat Bubble UI": "Çat Bubble UI",
+	"Chat direction": "Çat ugrukdyryş",
+	"Chat History": "Çat Taryhy",
+	"Chat History is off for this browser.": "Bu brauzer üçin Çat Taryhy öçürildi.",
+	"Chats": "Çatlar",
+	"Check Again": "Ýene Barla",
+	"Check for updates": "Täzelenmeleri barla",
+	"Checking for updates...": "Täzelenmeleri barlamak...",
+	"Choose a model before saving...": "Saklamazdan ozal model saýlaň...",
+	"Chunk Overlap": "Bölüm Aşyrmasy",
+	"Chunk Params": "Bölüm Parametrleri",
+	"Chunk Size": "Bölüm Ölçegi",
+	"Citation": "Sitata",
+	"Click here for help.": "Kömek üçin şu ýere basyň.",
+	"Click here to": "Şu ýere basyň",
+	"Click here to select": "Saýlamak üçin şu ýere basyň",
+	"Click here to select a csv file.": "CSV faýly saýlamak üçin şu ýere basyň.",
+	"Click here to select documents.": "Resminamalary saýlamak üçin şu ýere basyň.",
+	"click here.": "şu ýere basyň.",
+	"Click on the user role button to change a user's role.": "Ulanyjynyň roluny üýtgetmek üçin ulanyjy roly düwmesine basyň.",
+	"Clone": "Klon",
+	"Close": "Ýap",
+	"Collection": "Kolleksiýa",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Esasy URL",
+	"ComfyUI Base URL is required.": "ComfyUI Esasy URL zerur.",
+	"Command": "Buýruk",
+	"Concurrent Requests": "Meňzeş Haýyşlar",
+	"Confirm Password": "Paroly Tassyklap",
+	"Connections": "Baglanyşyklar",
+	"Content": "Mazmuny",
+	"Context Length": "Kontekst Uzynlygy",
+	"Continue Response": "Jogap Bermegi Dowam et",
+	"Conversation Mode": "Söhbet Reseimi",
+	"Copied shared chat URL to clipboard!": "Paýlaşylan çat URL buferine göçürildi!",
+	"Copy": "Göçür",
+	"Copy last code block": "Soňky kod blokyny göçür",
+	"Copy last response": "Soňky jogaby göçür",
+	"Copy Link": "Baglanyşygy Göçür",
+	"Copying to clipboard was successful!": "Buferine göçürmek üstünlikli boldy!",
+	"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Aşakdaky sorag üçin 3-5 sözden ybarat gysgaça söz düzümi dörediň, 3-5 söz çäklerine berk eýeriň we 'başlyk' sözüni ulanmaň:",
+	"Create a model": "Model döret",
+	"Create Account": "Hasap döret",
+	"Create new key": "Täze açar döret",
+	"Create new secret key": "Täze gizlin açar döret",
+	"Created at": "Döredilen wagty",
+	"Created At": "Döredilen wagty",
+	"Current Model": "Häzirki Model",
+	"Current Password": "Häzirki Parol",
+	"Custom": "Özboluşly",
+	"Customize models for a specific purpose": "Anyk maksat üçin modelleri düzmek",
+	"Dark": "Garaňky",
+	"Database": "Mazada",
+	"December": "Dekabr",
+	"Default": "Nokatlaýyn",
+	"Default (Automatic1111)": "Nokatlaýyn (Automatic1111)",
+	"Default (SentenceTransformers)": "Nokatlaýyn (SentenceTransformers)",
+	"Default (Web API)": "Nokatlaýyn (Web API)",
+	"Default Model": "Nokatlaýyn Model",
+	"Default model set successfully!": "Nokatlaýyn model üstünlikli gurnaldy!",
+	"Default Temperature": "Nokatlaýyn Temperaturasy",
+	"Define what this key is used for...": "Bu açaryň näme üçin ulanylýandygyny kesgitle...",
+	"Delete": "Öçür",
+	"Delete Account": "Hasaby Öçür",
+	"Delete Account Forever": "Hasaby Möhletsyz Öçür",
+	"Delete all": "Ählisini öçür",
+	"Delete All Chats": "Ähli Çatlary Öçür",
+	"Delete this Document": "Bu Resminamany Öçür",
+	"Deleted": "Öçürilen",
+	"Deleted Forever": "Möhletsyz Öçürilen",
+	"Description": "Düşündiriş",
+	"Developers": "Öndürijiler",
+	"Directory": "Katalog",
+	"Directory Type": "Katalog Typy",
+	"Disable": "Ýatyrmak",
+	"Disabled": "Ýatyrylan",
+	"Disconnected": "Baglanşyk kesildi",
+	"Display": "Görkeziş",
+	"Display Text": "Teksti Görkeziş",
+	"Document": "Resminama",
+	"Document File": "Resminama Faýly",
+	"Document Name": "Resminama Ady",
+	"Documents": "Resminamalar",
+	"Done": "Tamam",
+	"Downloading updates...": "Täzelenmeleri ýükläp...",
+	"Drag and drop files here": "Faýllary şu ýere süýräň we goýuň",
+	"Drop your .json, .txt, .csv or .md files here": "JSON, TXT, CSV ýa-da MD faýllaryňyzy şu ýere goýuň",
+	"Duplicate": "Göçür",
+	"Each request will contain a max of n documents": "Her haýyş n sany resminama bilen çäklenýär",
+	"Edit": "Redaktirle",
+	"Edit Labels": "Bellikleri Redaktirle",
+	"Edit message": "Habary Redaktirle",
+	"Edit Parameters": "Parametrleri Redaktirle",
+	"Email": "Email",
+	"Email Sent": "Email Iberildi",
+	"Enable": "Işjeňleşdir",
+	"Enable Character AI": "Häsiýetli AI Işjeňleşdir",
+	"Enable Web Search": "Web Gözlegini Işjeňleşdir",
+	"Enabled": "Işjeň",
+	"Encrypt messages": "Habarlar kodlansyn",
+	"Enter your email": "Email giriziň",
+	"Entries": "Girişler",
+	"Environment": "Daşky Gurşaw",
+	"Error": "Ýalňyşlyk",
+	"Error (400)": "Ýalňyşlyk (400)",
+	"Error (403)": "Ýalňyşlyk (403)",
+	"Error (404)": "Ýalňyşlyk (404)",
+	"Error (500)": "Ýalňyşlyk (500)",
+	"Error occurred": "Ýalňyşlyk ýüze çykdy",
+	"Example": "Mysal",
+	"Example(s)": "Mysal(lar)",
+	"Examples": "Mysallar",
+	"Explain a concept": "Bir konsepsiýany düşündiriň",
+	"Explore": "Barla",
+	"Export": "Eksport",
+	"Export All Data": "Ähli Maglumatlary Eksportla",
+	"Export data": "Maglumatlary Eksportla",
+	"External API": "Daşarky API",
+	"External API Endpoint": "Daşarky API Nokady",
+	"External ID": "Daşarky ID",
+	"Extra Models": "Goşmaça Modeller",
+	"Failed": "Netijesiz",
+	"Fallback": "Düşmek",
+	"False": "Ýalňyş",
+	"Family name": "Maşgala ady",
+	"FAQ": "Sorag-jogaplar",
+	"February": "Fewral",
+	"Field": "Meýdan",
+	"File": "Faýl",
+	"File Name": "Faýl Ady",
+	"File Type": "Faýl Typy",
+	"Files": "Faýllar",
+	"Fill out all required fields.": "Ähli zerur meýdanlary dolduryň.",
+	"Filter": "Süzgüç",
+	"Find your API Key here": "API Açaryňyzy şu ýerden tapyň",
+	"First name": "Ady",
+	"Following parameters are available in the command and are mandatory to specify:": "Buýruga degişli aşakdaky parametrler elýeterlidir we görkezmek hökmandyr:",
+	"For": "Üçin",
+	"Forever": "Möhletsyz",
+	"Forgot Password?": "Paroly unutdyňyzmy?",
+	"Forgot your password?": "Paroly unutdyňyzmy?",
+	"Free and Paid plans available": "Mugt we Tölegli planlar elýeterli",
+	"Friday": "Anna",
+	"From": "Kimden",
+	"Full Access": "Doly Elýeterlilik",
+	"Full Name": "Doly Ady",
+	"Full name": "Doly ady",
+	"Functions": "Funksiýalar",
+	"General": "Umumy",
+	"Generate": "Döret",
+	"Generate email templates": "Email şablonlaryny döret",
+	"Generate from a template": "Şablondan döret",
+	"Generate high-quality images": "Ýokary hilli suratlar döret",
+	"Generate professional profile descriptions": "Professional profil düşündirişleri döret",
+	"Generate prompts for language models": "Dil modelleri üçin düşündirişleri döret",
+	"Generate realistic photos": "Hakyky suratlary döret",
+	"Generate transcripts of audio and video": "Audio we wideo transkriptlerini döret",
+	"Generate translations": "Terjimeler döret",
+	"Generated from API keys": "API açarlaryndan döredildi",
+	"Generating...": "Döredilýär...",
+	"Generator": "Generator",
+	"Get Started": "Başlaň",
+	"Go": "Git",
+	"Go Back": "Yzyna Git",
+	"Go to": "Git",
+	"Go to link": "Baglanyşyga Git",
+	"Group": "Topar",
+	"Guidance": "Gollama",
+	"has been changed successfully": "üstünlikli üýtgedildi",
+	"have been changed successfully": "üstünlikli üýtgedildi",
+	"Hello": "Salam",
+	"Help": "Kömek",
+	"Hide": "Gizle",
+	"Hide all chats": "Ähli çatlary gizle",
+	"Hide Model": "Modeli Gizle",
+	"Hide Sidebar": "Gapdal Paneli Gizle",
+	"Hide Toolbar": "Gurallar Panelini Gizle",
+	"High Quality": "Ýokary Hilli",
+	"Home": "Baş Sahypa",
+	"Hours": "Sagady",
+	"Human-like responses": "Adam görnüşli jogaplar",
+	"Humorous": "Gülkünç",
+	"Identify objects in an image": "Suratda zatlary tanamak",
+	"If you need to quickly translate text from one language to another, use translation prompts.": "Bir dilden beýlekisine tekst terjime etmeli bolsaňyz, terjime düşündirişlerini ulanyň.",
+	"If you want to delete the account and all associated data, select": "Hasaby we degişli ähli maglumatlary öçürmek isleseňiz, saýlaň",
+	"If you're facing issues, try again later.": "Meseleler bar bolsa, soňra täzeden synanyşyň.",
+	"Image": "Surat",
+	"Image Generation": "Surat Döretme",
+	"Import": "Import",
+	"Import Data": "Maglumatlary Importla",
+	"In Progress": "Dowam edýär",
+	"Inactivity Timeout": "Işjeňsiz Töhmet",
+	"Incorrect email or password.": "Nädogry email ýa-da parol.",
+	"Info": "Maglumat",
+	"Information": "Maglumat",
+	"Information updated successfully!": "Maglumat üstünlikli täzelendi!",
+	"Input": "Girdi",
+	"Input Parameters": "Girdi Parametrleri",
+	"Installation Guide": "Gurnama Gollanma",
+	"Integrate custom models": "Özboluşly modelleri integrirle",
+	"Integrate with an external API": "Daşarky API bilen integrirle",
+	"Integration": "Integrasiýa",
+	"Internet Required": "Internet Zerur",
+	"Invalid API key.": "Nädogry API açar.",
+	"Invalid credentials, try again.": "Nädogry maglumatlar, täzeden synanyşyň.",
+	"Invalid or expired API key.": "Nädogry ýa-da möhleti geçen API açar.",
+	"It looks like we encountered an error. Please try again.": "Ýalňyşlyk ýüze çykdy. Täzeden synanyşyň.",
+	"January": "Ýanwar",
+	"Job title": "Iş Ady",
+	"Join": "Goşul",
+	"Join Date": "Goşulma Senesi",
+	"July": "Iýul",
+	"June": "Iýun",
+	"Just now": "Just now",
+	"Key": "Açar",
+	"Key (hidden)": "Açar (gizlin)",
+	"Key Details": "Açar Maglumatlar",
+	"Key Management": "Açar Dolandyryşy",
+	"Language": "Dil",
+	"Language Model": "Dil Modeli",
+	"Last access": "Soňky elýeterlilik",
+	"Last Access": "Soňky elýeterlilik",
+	"Last edited": "Soňky redaktirlenen",
+	"Last modified": "Soňky üýtgedilen",
+	"Last Modified": "Soňky üýtgedilen",
+	"Last name": "Familiýasy",
+	"Last update": "Soňky täzelenme",
+	"Last updated": "Soňky täzelenen",
+	"Later": "Soň",
+	"Launch": "Gur",
+	"Learn": "Öwren",
+	"Learn More": "Has köp öwreniň",
+	"License": "Rugsat",
+	"Light": "Açyk",
+	"Link": "Baglanyşyk",
+	"Link expired": "Baglanyşygyň möhleti geçdi",
+	"Load": "Ýükle",
+	"Loading": "Ýüklenýär",
+	"Local Models": "Ýerli Modeller",
+	"Log out": "Çyk",
+	"Logged out": "Çykdy",
+	"Logged out successfully": "Üstünlikli çykdy",
+	"Login": "Giriş",
+	"Login Required": "Giriş Zerur",
+	"Logs": "Loglar",
+	"Low": "Pes",
+	"Low Quality": "Pes Hilli",
+	"Maintain custom codebase": "Özboluşly kod bazasyny sakla",
+	"Management": "Dolandyryş",
+	"Manual Input": "El bilen Girdi",
+	"March": "Mart",
+	"Max File Count": "",
+	"Max File Size(MB)": "",
+	"Mark as Read": "Okalan hökmünde belläň",
+	"Match": "Gab",
+	"May": "Maý",
+	"Memory": "Ýat",
+	"Memory saved": "Ýat saklanyldy",
+	"Menu": "Menýu",
+	"Message": "Habar",
+	"Message limit reached for today. Please wait until tomorrow.": "Bu günki habar çägi geçdi. Ertir garaşyň.",
+	"Messages": "Habarlar",
+	"Method": "Usul",
+	"Microphone": "Mikrofon",
+	"Minute": "Minut",
+	"Minutes": "Minutlar",
+	"Model": "Model",
+	"Model Details": "Model Maglumatlary",
+	"Model History": "Model Taryhy",
+	"Model Management": "Model Dolandyryşy",
+	"Model name": "Model ady",
+	"Model URL": "Model URL",
+	"Mode": "Reseimi",
+	"Moderate Quality": "Orta Hilli",
+	"Modified": "Üýtgedilen",
+	"Modify User": "Ulanyjyny Üýtget",
+	"Monday": "Duşenbe",
+	"Monetization": "Pul gazanmak",
+	"Month": "Aý",
+	"More": "Has köp",
+	"More Info": "Has köp Maglumat",
+	"More options": "Has köp opsiýalar",
+	"Most Recent": "Iň Täze",
+	"Multiple file import is limited to": "Köp faýl importy çäkli",
+	"Name": "Ady",
+	"Name (hidden)": "Ady (gizlin)",
+	"Name is required": "Ady zerur",
+	"Navigate": "Gez",
+	"Need help?": "Kömek gerekmi?",
+	"New": "Täze",
+	"New Key": "Täze Açar",
+	"New Label": "Täze Bellik",
+	"New Password": "Täze Parol",
+	"New Secret Key": "Täze Gizlin Açar",
+	"New User": "Täze Ulanyjy",
+	"Next": "Indiki",
+	"No": "Ýok",
+	"No access": "Elýeterlilik ýok",
+	"No access.": "Elýeterlilik ýok.",
+	"No admins": "Adminler ýok",
+	"No archived chats": "Arhiwlenen çatlar ýok",
+	"No data found": "Maglumat tapylmady",
+	"No models available": "Modeller elýeterli däl",
+	"No permission to add a model.": "Model goşmak üçin rugsat ýok.",
+	"No permission to archive chat.": "Çaty arhiwlemek üçin rugsat ýok.",
+	"No permission to create chat.": "Çat döretmek üçin rugsat ýok.",
+	"No permission to delete chat.": "Çaty öçürmek üçin rugsat ýok.",
+	"No permission to edit chat.": "Çaty redaktirlemek üçin rugsat ýok.",
+	"No permission to view chat.": "Çaty görmek üçin rugsat ýok.",
+	"No shared chats": "Paýlaşylan çatlar ýok",
+	"No usage": "Ulanyş ýok",
+	"Non-admin users can only view chat details": "Admin däl ulanyjylar diňe çat maglumatlaryny görüp bilerler",
+	"None": "Hiç",
+	"Not Found": "Tapylmady",
+	"Not started": "Başlanmady",
+	"November": "Noýabr",
+	"October": "Oktýabr",
+	"Okay": "Bolýar",
+	"On": "Işjeň",
+	"Once you have added and configured your model, it will appear in the model dropdown list on the chat screen.": "Model goşup we konfigurirleýänden soň, çat ekranynda model aşak düşýän sanawda peýda bolar.",
+	"Only": "Diňe",
+	"Open": "Aç",
+	"OpenAI Base URL": "OpenAI Esasy URL",
+	"OpenAI Key": "OpenAI Açar",
+	"OpenAI Model": "OpenAI Model",
+	"OpenAI Token": "OpenAI Token",
+	"OpenAI URL": "OpenAI URL",
+	"Options": "Opsiýalar",
+	"Other": "Başga",
+	"Other Parameters": "Başga Parametrler",
+	"Owner": "Eýesi",
+	"Owner ID": "Eýesi ID",
+	"Page": "Sahypa",
+	"Parameter": "Parametr",
+	"Parameters": "Parametrler",
+	"Password": "Parol",
+	"Password must be at least 8 characters long": "Parol iň azyndan 8 harp bolmaly",
+	"Password must include at least one number and one letter": "Parol iň azyndan bir san we bir harp bolmaly",
+	"Paste copied text here...": "Göçürilen tekst şu ýere goýuň...",
+	"Paste text here...": "Tekst şu ýere goýuň...",
+	"PDF": "PDF",
+	"PDF Generation": "PDF Döretme",
+	"Pending": "Garaşylýar",
+	"Permission": "Rugsat",
+	"Personal Information": "Şahsy Maglumat",
+	"Photo": "Surat",
+	"Photos": "Suratlar",
+	"Please add a model.": "Model goşuň.",
+	"Please add more content": "Köp mazmun goşuň",
+	"Please enter your email to reset your password": "Parolyňyzy täzeden goýmak üçin email giriziň",
+	"Please try again later": "Soňra täzeden synanyşyň",
+	"Plugin": "Plagin",
+	"Plugin Settings": "Plagin Sazlamalary",
+	"Position": "Ýerleşýän ýeri",
+	"Post": "Post",
+	"Potential Risks": "Mümkin Töwekgelçilikler",
+	"Preparing your data...": "Maglumatlaryňyzy taýýarlaýar...",
+	"Preprocessing...": "Deslapky işlem...",
+	"Preview": "Öň-üşürgi",
+	"Previous": "Öňki",
+	"Print": "Çap et",
+	"Privacy Policy": "Gizlinlik Syýasaty",
+	"Processing": "Işlenýär",
+	"Profile": "Profil",
+	"Prompt": "Düşündiriş",
+	"Prompts": "Düşündirişler",
+	"Public": "Jemgyýetçilik",
+	"Quality": "Hil",
+	"Quantity": "Mukdar",
+	"Quick Start": "Çalt Başla",
+	"Read More": "Has köp oka",
+	"Realistic": "Hakyky",
+	"Recent": "Täze",
+	"Recent Access": "Täze Elýeterlilik",
+	"Recent Chats": "Täze Çatlar",
+	"Recent Documents": "Täze Resminamalar",
+	"Recent Files": "Täze Faýllar",
+	"Recipient": "Alyjy",
+	"Recognize speech and convert it to text": "Gürleýişi tanap tekste öwrüň",
+	"Records": "Ýazgylar",
+	"Reference": "Salgy",
+	"Refresh": "Täzeläň",
+	"Registration Date": "Hasaba alynma Senesi",
+	"Remove": "Aýyr",
+	"Remove Model": "Modeli Aýyr",
+	"Remove user": "Ulanyjyny aýyr",
+	"Rename": "Adyny Üýtget",
+	"Reorder": "Gaýtadan Sargyt Et",
+	"Request": "Haýyş",
+	"Required": "Zerur",
+	"Reset": "Täzeden Guruň",
+	"Reset Password": "Paroly Täzeden Guruň",
+	"Resources": "Resurslar",
+	"Response": "Jogap",
+	"Restored": "Dikeldilen",
+	"Results": "Netijeler",
+	"Review": "Syn",
+	"Review Prompt": "Düşündirişi Synla",
+	"Reviews": "Synlar",
+	"Role": "Roli",
+	"Save": "Sakla",
+	"Save Changes": "Üýtgeşmeleri Sakla",
+	"Saved": "Saklanan",
+	"Saturday": "Şenbe",
+	"Scale": "Şkalasy",
+	"Scan the code": "Kody Skanirle",
+	"Search": "Gözleg",
+	"Search All Chats": "Ähli Çatlary Gözle",
+	"Search for documents": "Resminamalary Gözle",
+	"Search for models": "Modelleri Gözle",
+	"Search for users": "Ulanyjylary Gözle",
+	"Search in chat": "Çatda gözle",
+	"Search query": "Gözleg soragy",
+	"Search...": "Gözleg...",
+	"Secret Key": "Gizlin Açar",
+	"See more": "Has köp gör",
+	"Select": "Saýla",
+	"Select a model": "Bir model saýla",
+	"Select a role": "Roli saýla",
+	"Select Chat": "Çat saýla",
+	"Select File": "Faýl saýla",
+	"Select Label": "Belligi saýla",
+	"Send": "Iber",
+	"Send and receive messages": "Habar iber we kabul et",
+	"Send Message": "Habar Iber",
+	"Sent": "Iberilen",
+	"Separate each entry with a new line": "Her girişi täze setir bilen aýraň",
+	"Separate multiple entries with a comma or new line": "Birnäçe girişi üzgüç ýa-da täze setir bilen aýraň",
+	"September": "Sentýabr",
+	"Server error": "Serwer ýalňyşlygy",
+	"Service Unavailable": "Hyzmat Elýeterli Däl",
+	"Session": "Sessia",
+	"Settings": "Sazlamalar",
+	"Setup": "Gurnama",
+	"Share": "Paýlaş",
+	"Share Chat": "Çaty Paýlaş",
+	"Share this chat with others": "Bu çaty beýlekiler bilen paýlaş",
+	"Shared": "Paýlaşylan",
+	"Shared Chats": "Paýlaşylan Çatlar",
+	"Show": "Görkez",
+	"Show all": "Ählisini görkez",
+	"Show all labels": "Ähli belligi görkez",
+	"Show All Prompts": "Ähli Düşündirişleri Görkez",
+	"Show API Keys": "API Açarlaryny Görkez",
+	"Show Model": "Modeli Görkez",
+	"Show Sidebar": "Gapdal Paneli Görkez",
+	"Show toolbar": "Gurallar Panelini Görkez",
+	"Sign In": "Giriş",
+	"Sign Out": "Çyk",
+	"Sign Up": "Hasaba al",
+	"Sign up": "Hasaba al",
+	"Sign up to get started": "Başlamak üçin hasaba alyň",
+	"Simple and advanced options available": "Ýönekeý we kämilleşdirilen opsiýalar elýeterli",
+	"Simply follow the guide to get started.": "Başlamak üçin görkezmä eýeriň.",
+	"Skip": "Geç",
+	"Smart Completion": "Akyldar Tamamlama",
+	"Software": "Programma üpjünçiligi",
+	"Sorry, an error occurred while processing your request.": "Bagyşlaň, haýyşyňyzy işlemekde ýalňyşlyk ýüze çykdy.",
+	"Sorry, the page you are looking for does not exist.": "Bagyşlaň, gözleýän sahypaňyz ýok.",
+	"Sorry, this link has expired.": "Bagyşlaň, bu baglanyşygyň möhleti geçdi.",
+	"Source": "Çeşme",
+	"Source Language": "Çeşme Dili",
+	"Space": "Ýer",
+	"Special Characters": "Aýratyn Harplar",
+	"Specify the model type": "Model typyny kesgitle",
+	"Standard": "Standart",
+	"Start": "Başla",
+	"Start a New Chat": "Täze Çat Başla",
+	"Start Chat": "Çat Başla",
+	"Start Date": "Başlangyç Sene",
+	"Start Time": "Başlanýan wagt",
+	"Started": "Başlandy",
+	"Status": "Ýagdaýy",
+	"Stop": "Bes et",
+	"Store your data securely": "Maglumatlaryňyzy howpsuz saklaň",
+	"Subject": "Tema",
+	"Submit": "Tabşyr",
+	"Success": "Üstünlik",
+	"Summary": "Jemleýji",
+	"Sunday": "Ýekşenbe",
+	"Support": "Goldaw",
+	"Switch to another model": "Başga modele geçiň",
+	"Switch to another model type": "Başga model typyna geçiň",
+	"System": "Sistema",
+	"System Requirements": "Sistema Talaplary",
+	"Table": "Jadwal",
+	"Tag": "Bellik",
+	"Tag List": "Bellik Sanawy",
+	"Take a tour": "Syýahat et",
+	"Talk to your data": "Maglumatlaryňyz bilen gürleşiň",
+	"Target Language": "Maksat Dili",
+	"Team": "Topar",
+	"Template": "Şablon",
+	"Templates": "Şablonlar",
+	"Temporary": "Wagtylaýyn",
+	"Test": "Synag",
+	"Text": "Tekst",
+	"Text Generation": "Tekst Döretme",
+	"Text to Image": "Tekstden Surat",
+	"Text to Speech": "Tekstden Söz",
+	"The data you need to integrate is currently unavailable.": "Integrirlemeli maglumatlaryňyz häzirki wagtda elýeterli däl.",
+	"The model has been added successfully!": "Model üstünlikli goşuldy!",
+	"The model type you are trying to add already exists.": "Goşmak isleýän model typyňyz eýýäm bar.",
+	"The page you requested could not be found.": "Soranyňyz sahypa tapylmady.",
+	"There are no prompts available at the moment.": "Häzirlikçe düşündirişler elýeterli däl.",
+	"There was an error adding the model.": "Model goşulmakda ýalňyşlyk ýüze çykdy.",
+	"There was an error deleting the model.": "Modeli öçürmekde ýalňyşlyk ýüze çykdy.",
+	"There was an error loading the models.": "Modelleri ýüklemekde ýalňyşlyk ýüze çykdy.",
+	"There was an error updating the model.": "Modeli täzeläp bolmady.",
+	"There was an error while archiving the chat.": "Çaty arhiwlemekde ýalňyşlyk ýüze çykdy.",
+	"There was an error while creating the chat.": "Çat döretmekde ýalňyşlyk ýüze çykdy.",
+	"There was an error while deleting the chat.": "Çaty öçürmekde ýalňyşlyk ýüze çykdy.",
+	"There was an error while editing the chat.": "Çaty redaktirlemekde ýalňyşlyk ýüze çykdy.",
+	"There was an error while fetching the chat.": "Çaty getirmekde ýalňyşlyk ýüze çykdy.",
+	"There was an error while saving the data.": "Maglumatlary saklamakda ýalňyşlyk ýüze çykdy.",
+	"There was an error while sending the message.": "Habary ibermekde ýalňyşlyk ýüze çykdy.",
+	"There was an error while updating the user.": "Ulanyjyny täzeläp bolmady.",
+	"These settings are global and will apply to all users and models.": "Bu sazlamalar umumy we ähli ulanyjylara we modellere degişlidir.",
+	"This action cannot be undone.": "Bu hereket yzyna dolanylyp bilinmez.",
+	"This email is already in use.": "Bu email eýýäm ulanylýar.",
+	"This email is not registered.": "Bu email hasaba alynmady.",
+	"This is the end of the chat": "Bu çatyň soňy",
+	"This link is expired or invalid.": "Bu baglanyşygyň möhleti geçdi ýa-da nädogry.",
+	"This model is already integrated.": "Bu model eýýäm integrirlenen.",
+	"This page does not exist.": "Bu sahypa ýok.",
+	"This will remove the user from the system.": "Bu ulanyjyny sistemadan aýyrar.",
+	"Thursday": "Penşenbe",
+	"Time": "Wagt",
+	"Time Limit Exceeded": "Wagt Limiti Geçdi",
+	"Timezone": "Wagt zolak",
+	"Title": "Ady",
+	"Today": "Şu gün",
+	"Token": "Token",
+	"Token limit exceeded.": "Token çägi geçdi.",
+	"Token or URL is incorrect.": "Token ýa-da URL nädogry.",
+	"Too many requests. Please try again later.": "Örän köp haýyşlar. Soňra täzeden synanyşyň.",
+	"Total Chats": "Jemi Çatlar",
+	"Total Memory": "Jemi Ýat",
+	"Total Storage": "Jemi Sakla",
+	"Total Users": "Jemi Ulanyjylar",
+	"Training": "Okuw",
+	"Tuesday": "Sişenbe",
+	"Type": "Typ",
+	"Unable to archive the chat.": "Çaty arhiwläp bolmady.",
+	"Unable to change the model.": "Modeli üýtgedip bolmady.",
+	"Unable to complete the request.": "Haýyşy tamamlap bolmady.",
+	"Unable to create chat.": "Çat döretmek mümkin däl.",
+	"Unable to delete the model.": "Modeli öçürmek mümkin däl.",
+	"Unable to delete the user.": "Ulanyjyny öçürmek mümkin däl.",
+	"Unable to find the requested resource.": "Soralan resurs tapylmady.",
+	"Unable to import data.": "Maglumatlary import edip bolmady.",
+	"Unable to load the chat.": "Çaty ýüklemek mümkin däl.",
+	"Unable to load the settings.": "Sazlamalary ýüklemek mümkin däl.",
+	"Unable to login.": "Giriş mümkin däl.",
+	"Unable to process the request.": "Haýyşy işläp bolmady.",
+	"Unable to reset password.": "Paroly täzeden gurmak mümkin däl.",
+	"Unable to retrieve data.": "Maglumatlary almak mümkin däl.",
+	"Unable to save": "Saklap bolmady",
+	"Unable to save the data.": "Maglumatlary saklap bolmady.",
+	"Unable to update": "Täzeläp bolmady",
+	"Unable to update the model.": "Modeli täzeläp bolmady.",
+	"Unable to update the user.": "Ulanyjyny täzeläp bolmady.",
+	"Unauthorized": "Rugsatsyz",
+	"Undo": "Yza al",
+	"Unlink": "Baglanyşygy aýyr",
+	"Unread Messages": "Okalmadyk Habarlar",
+	"Unshare": "Paýlaşma",
+	"Unverified": "Tassyklanmadyk",
+	"Update": "Täzeläň",
+	"Update successful": "Üstünlikli täzelenme",
+	"Updated": "Täzelenen",
+	"Updated at": "Täzelendi",
+	"Upload": "Ýükle",
+	"Upload Data": "Maglumat Ýükle",
+	"Upload file": "Faýl ýükle",
+	"Uploading": "Ýüklenýär",
+	"Usage": "Ulanyş",
+	"User": "Ulanyjy",
+	"User added": "Ulanyjy goşuldy",
+	"User deleted": "Ulanyjy öçürildi",
+	"User does not exist": "Ulanyjy ýok",
+	"User Guide": "Ulanyjy Gollanmasy",
+	"User ID": "Ulanyjy ID",
+	"User Management": "Ulanyjy Dolandyryşy",
+	"User Role": "Ulanyjy Roli",
+	"Username": "Ulanyjy Ady",
+	"Username is required": "Ulanyjy ady zerur",
+	"Users": "Ulanyjylar",
+	"Value": "Gymmaty",
+	"Verify": "Tassykla",
+	"Version": "Wersiýasy",
+	"View": "Gör",
+	"View All": "Ählisini gör",
+	"View archived": "Arhiwlenenleri gör",
+	"View Details": "Maglumatlary Gör",
+	"Voice Input": "Ses Girdi",
+	"Voice Recording": "Ses Ýazgysy",
+	"Volume": "Göwrümi",
+	"Warning": "Duýduryş",
+	"Wednesday": "Çarşenbe",
+	"Welcome": "Hoş geldiňiz",
+	"Welcome to ChatGPT! How can I help you today?": "ChatGPT-e hoş geldiňiz! Size nähili kömek edip bilerin?",
+	"Welcome to our service!": "Hyzmatymyza hoş geldiňiz!",
+	"Welcome!": "Hoş geldiňiz!",
+	"Width": "Ini",
+	"Work": "Iş",
+	"Write": "Ýaz",
+	"Write a review": "Syn ýaz",
+	"Write code": "Kod ýazyň",
+	"Write content": "Mazmun ýazyň",
+	"Year": "Ýyl",
+	"Yes": "Hawa",
+	"Yesterday": "Düýn",
+	"You are not authorized to view this content.": "Bu mazmuny görmek üçin rugsadyňyz ýok.",
+	"You can only add a maximum of": "Diňe iň köpüniň",
+	"You can request access from your administrator": "Administratoryňyzdan elýeterlilik haýyş edip bilersiňiz",
+	"You have reached your usage limit for today.": "Bu günki ulanyş çägiňize ýetdiňiz.",
+	"You have to choose a model first": "Ilki bilen model saýlamaly",
+	"You need a valid email": "Dogrudan email gerek",
+	"You will need to log in again to view the updated content": "Täzelenen mazmuny görmek üçin täzeden girmeli bolarsyňyz",
+	"Your data is safe with us": "Maglumatlaryňyz bizde howpsuz",
+	"Your email": "Emailiňiz",
+	"Your email address": "Email adresiňiz",
+	"Your message": "Habaryňyz",
+	"Your name": "Adyňyz",
+	"Your new password": "Täze parolyňyz",
+	"Your password": "Parolyňyz",
+	"Your payment is successful.": "Tölegiňiz üstünlikli boldy.",
+	"Your session has expired. Please log in again.": "Sessiaňyz tamamlandy. Täzeden giriň.",
+	"Your username": "Ulanyjy adyňyz",
+	"You're offline.": "Offline.",
+	"You've reached your token limit for the day.": "Günüňize token çägiňize ýetdiňiz.",
+	"ZIP Code": "Poçta Kody"
+}
diff --git a/src/lib/i18n/locales/tk-TW/translation.json b/src/lib/i18n/locales/tk-TW/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..702b3e9aa46b4ac8a99542d9edf6a76c930e5758
--- /dev/null
+++ b/src/lib/i18n/locales/tk-TW/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "",
+	"(latest)": "",
+	"{{ models }}": "",
+	"{{ owner }}: You cannot delete a base model": "",
+	"{{user}}'s Chats": "",
+	"{{webUIName}} Backend Required": "",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
+	"a user": "",
+	"About": "",
+	"Account": "",
+	"Account Activation Pending": "",
+	"Accurate information": "",
+	"Actions": "",
+	"Active Users": "",
+	"Add": "",
+	"Add a model id": "",
+	"Add a short description about what this model does": "",
+	"Add a short title for this prompt": "",
+	"Add a tag": "",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "",
+	"Add Files": "",
+	"Add Memory": "",
+	"Add Model": "",
+	"Add Tag": "",
+	"Add Tags": "",
+	"Add text content": "",
+	"Add User": "",
+	"Adjusting these settings will apply changes universally to all users.": "",
+	"admin": "",
+	"Admin": "",
+	"Admin Panel": "",
+	"Admin Settings": "",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
+	"Advanced Parameters": "",
+	"Advanced Params": "",
+	"All chats": "",
+	"All Documents": "",
+	"Allow Chat Deletion": "",
+	"Allow Chat Editing": "",
+	"Allow non-local voices": "",
+	"Allow Temporary Chat": "",
+	"Allow User Location": "",
+	"Allow Voice Interruption in Call": "",
+	"alphanumeric characters and hyphens": "",
+	"Already have an account?": "",
+	"an assistant": "",
+	"and": "",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "",
+	"API Base URL": "",
+	"API Key": "",
+	"API Key created.": "",
+	"API keys": "",
+	"April": "",
+	"Archive": "",
+	"Archive All Chats": "",
+	"Archived Chats": "",
+	"are allowed - Activate this command by typing": "",
+	"Are you sure?": "",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "",
+	"Attention to detail": "",
+	"Audio": "",
+	"August": "",
+	"Auto-playback response": "",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "",
+	"AUTOMATIC1111 Base URL is required.": "",
+	"Available list": "",
+	"available!": "",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "",
+	"Bad Response": "",
+	"Banners": "",
+	"Base Model (From)": "",
+	"Batch Size (num_batch)": "",
+	"before": "",
+	"Being lazy": "",
+	"Brave Search API Key": "",
+	"Bypass SSL verification for Websites": "",
+	"Call": "",
+	"Call feature is not supported when using Web STT engine": "",
+	"Camera": "",
+	"Cancel": "",
+	"Capabilities": "",
+	"Change Password": "",
+	"Character": "",
+	"Chat": "",
+	"Chat Background Image": "",
+	"Chat Bubble UI": "",
+	"Chat Controls": "",
+	"Chat direction": "",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "",
+	"Check Again": "",
+	"Check for updates": "",
+	"Checking for updates...": "",
+	"Choose a model before saving...": "",
+	"Chunk Overlap": "",
+	"Chunk Params": "",
+	"Chunk Size": "",
+	"Citation": "",
+	"Clear memory": "",
+	"Click here for help.": "",
+	"Click here to": "",
+	"Click here to download user import template file.": "",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "",
+	"Click here to select a csv file.": "",
+	"Click here to select a py file.": "",
+	"Click here to upload a workflow.json file.": "",
+	"click here.": "",
+	"Click on the user role button to change a user's role.": "",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
+	"Clone": "",
+	"Close": "",
+	"Code execution": "",
+	"Code formatted successfully": "",
+	"Collection": "",
+	"ComfyUI": "",
+	"ComfyUI Base URL": "",
+	"ComfyUI Base URL is required.": "",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "",
+	"Completions": "",
+	"Concurrent Requests": "",
+	"Confirm": "",
+	"Confirm Password": "",
+	"Confirm your action": "",
+	"Connections": "",
+	"Contact Admin for WebUI Access": "",
+	"Content": "",
+	"Content Extraction": "",
+	"Context Length": "",
+	"Continue Response": "",
+	"Continue with {{provider}}": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "",
+	"Copied shared chat URL to clipboard!": "",
+	"Copied to clipboard": "",
+	"Copy": "",
+	"Copy last code block": "",
+	"Copy last response": "",
+	"Copy Link": "",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "",
+	"Create a model": "",
+	"Create Account": "",
+	"Create Knowledge": "",
+	"Create new key": "",
+	"Create new secret key": "",
+	"Created at": "",
+	"Created At": "",
+	"Created by": "",
+	"CSV Import": "",
+	"Current Model": "",
+	"Current Password": "",
+	"Custom": "",
+	"Customize models for a specific purpose": "",
+	"Dark": "",
+	"Dashboard": "",
+	"Database": "",
+	"December": "",
+	"Default": "",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "",
+	"Default Model": "",
+	"Default model updated": "",
+	"Default Prompt Suggestions": "",
+	"Default User Role": "",
+	"Delete": "",
+	"Delete a model": "",
+	"Delete All Chats": "",
+	"Delete chat": "",
+	"Delete Chat": "",
+	"Delete chat?": "",
+	"Delete folder?": "",
+	"Delete function?": "",
+	"Delete prompt?": "",
+	"delete this link": "",
+	"Delete tool?": "",
+	"Delete User": "",
+	"Deleted {{deleteModelTag}}": "",
+	"Deleted {{name}}": "",
+	"Description": "",
+	"Didn't fully follow instructions": "",
+	"Disabled": "",
+	"Discover a function": "",
+	"Discover a model": "",
+	"Discover a prompt": "",
+	"Discover a tool": "",
+	"Discover, download, and explore custom functions": "",
+	"Discover, download, and explore custom prompts": "",
+	"Discover, download, and explore custom tools": "",
+	"Discover, download, and explore model presets": "",
+	"Dismissible": "",
+	"Display Emoji in Call": "",
+	"Display the username instead of You in the Chat": "",
+	"Do not install functions from sources you do not fully trust.": "",
+	"Do not install tools from sources you do not fully trust.": "",
+	"Document": "",
+	"Documentation": "",
+	"Documents": "",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "",
+	"Don't have an account?": "",
+	"don't install random functions from sources you don't trust.": "",
+	"don't install random tools from sources you don't trust.": "",
+	"Don't like the style": "",
+	"Done": "",
+	"Download": "",
+	"Download canceled": "",
+	"Download Database": "",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "",
+	"Edit": "",
+	"Edit Arena Model": "",
+	"Edit Memory": "",
+	"Edit User": "",
+	"ElevenLabs": "",
+	"Email": "",
+	"Embedding Batch Size": "",
+	"Embedding Model": "",
+	"Embedding Model Engine": "",
+	"Embedding model set to \"{{embedding_model}}\"": "",
+	"Enable Community Sharing": "",
+	"Enable Message Rating": "",
+	"Enable New Sign Ups": "",
+	"Enable Web Search": "",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
+	"Enter {{role}} message here": "",
+	"Enter a detail about yourself for your LLMs to recall": "",
+	"Enter api auth string (e.g. username:password)": "",
+	"Enter Brave Search API Key": "",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "",
+	"Enter Chunk Size": "",
+	"Enter description": "",
+	"Enter Github Raw URL": "",
+	"Enter Google PSE API Key": "",
+	"Enter Google PSE Engine Id": "",
+	"Enter Image Size (e.g. 512x512)": "",
+	"Enter language codes": "",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "",
+	"Enter Number of Steps (e.g. 50)": "",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "",
+	"Enter Serper API Key": "",
+	"Enter Serply API Key": "",
+	"Enter Serpstack API Key": "",
+	"Enter stop sequence": "",
+	"Enter system prompt": "",
+	"Enter Tavily API Key": "",
+	"Enter Tika Server URL": "",
+	"Enter Top K": "",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "",
+	"Enter URL (e.g. http://localhost:11434)": "",
+	"Enter Your Email": "",
+	"Enter Your Full Name": "",
+	"Enter your message": "",
+	"Enter Your Password": "",
+	"Enter Your Role": "",
+	"Error": "",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "",
+	"Export": "",
+	"Export All Chats (All Users)": "",
+	"Export chat (.json)": "",
+	"Export Chats": "",
+	"Export Config to JSON File": "",
+	"Export Functions": "",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "",
+	"Export Prompts": "",
+	"Export Tools": "",
+	"External Models": "",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "",
+	"Failed to read clipboard contents": "",
+	"Failed to update settings": "",
+	"Failed to upload file.": "",
+	"February": "",
+	"Feedback History": "",
+	"Feel free to add specific details": "",
+	"File": "",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "",
+	"File not found.": "",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "",
+	"Filter is now globally disabled": "",
+	"Filter is now globally enabled": "",
+	"Filters": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
+	"Fluidly stream large external response chunks": "",
+	"Focus chat input": "",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "",
+	"Function": "",
+	"Function created successfully": "",
+	"Function deleted successfully": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "",
+	"Function ID (e.g. my_filter)": "",
+	"Function is now globally disabled": "",
+	"Function is now globally enabled": "",
+	"Function Name (e.g. My Filter)": "",
+	"Function updated successfully": "",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "",
+	"Functions allow arbitrary code execution.": "",
+	"Functions imported successfully": "",
+	"General": "",
+	"General Settings": "",
+	"Generate Image": "",
+	"Generating search query": "",
+	"Generation Info": "",
+	"Get up and running with": "",
+	"Global": "",
+	"Good Response": "",
+	"Google PSE API Key": "",
+	"Google PSE Engine Id": "",
+	"h:mm a": "",
+	"Haptic Feedback": "",
+	"has no conversations.": "",
+	"Hello, {{name}}": "",
+	"Help": "",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "",
+	"Hide Model": "",
+	"How can I help you today?": "",
+	"Hybrid Search": "",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"ID": "",
+	"Image Generation (Experimental)": "",
+	"Image Generation Engine": "",
+	"Image Settings": "",
+	"Images": "",
+	"Import Chats": "",
+	"Import Config from JSON File": "",
+	"Import Functions": "",
+	"Import Models": "",
+	"Import Prompts": "",
+	"Import Tools": "",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "",
+	"Info": "",
+	"Input commands": "",
+	"Install from Github URL": "",
+	"Instant Auto-Send After Voice Transcription": "",
+	"Interface": "",
+	"Invalid file format.": "",
+	"Invalid Tag": "",
+	"January": "",
+	"join our Discord for help.": "",
+	"JSON": "",
+	"JSON Preview": "",
+	"July": "",
+	"June": "",
+	"JWT Expiration": "",
+	"JWT Token": "",
+	"Keep Alive": "",
+	"Keyboard shortcuts": "",
+	"Knowledge": "",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "",
+	"large language models, locally.": "",
+	"Last Active": "",
+	"Last Modified": "",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "",
+	"Listening...": "",
+	"LLMs can make mistakes. Verify important information.": "",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "",
+	"Made by OpenWebUI Community": "",
+	"Make sure to enclose them with": "",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Manage": "",
+	"Manage Arena Models": "",
+	"Manage Models": "",
+	"Manage Ollama Models": "",
+	"Manage Pipelines": "",
+	"March": "",
+	"Max Tokens (num_predict)": "",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "",
+	"May": "",
+	"Memories accessible by LLMs will be shown here.": "",
+	"Memory": "",
+	"Memory added successfully": "",
+	"Memory cleared successfully": "",
+	"Memory deleted successfully": "",
+	"Memory updated successfully": "",
+	"Merge Responses": "",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "",
+	"Min P": "",
+	"Minimum Score": "",
+	"Mirostat": "",
+	"Mirostat Eta": "",
+	"Mirostat Tau": "",
+	"MMMM DD, YYYY": "",
+	"MMMM DD, YYYY HH:mm": "",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "",
+	"Model '{{modelTag}}' is already in queue for downloading.": "",
+	"Model {{modelId}} not found": "",
+	"Model {{modelName}} is not vision capable": "",
+	"Model {{name}} is now {{status}}": "",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
+	"Model ID": "",
+	"Model Name": "",
+	"Model not selected": "",
+	"Model Params": "",
+	"Model updated successfully": "",
+	"Model Whitelisting": "",
+	"Model(s) Whitelisted": "",
+	"Modelfile Content": "",
+	"Models": "",
+	"more": "",
+	"More": "",
+	"Move to Top": "",
+	"Name": "",
+	"Name your model": "",
+	"New Chat": "",
+	"New folder": "",
+	"New Password": "",
+	"No content found": "",
+	"No content to speak": "",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "",
+	"No search query generated": "",
+	"No source available": "",
+	"No valves to update": "",
+	"None": "",
+	"Not factually correct": "",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
+	"Notes": "",
+	"Notifications": "",
+	"November": "",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "",
+	"OAuth ID": "",
+	"October": "",
+	"Off": "",
+	"Okay, Let's Go!": "",
+	"OLED Dark": "",
+	"Ollama": "",
+	"Ollama API": "",
+	"Ollama API disabled": "",
+	"Ollama API is disabled": "",
+	"Ollama Version": "",
+	"On": "",
+	"Only": "",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"OpenAI": "",
+	"OpenAI API": "",
+	"OpenAI API Config": "",
+	"OpenAI API Key is required.": "",
+	"OpenAI URL/Key required.": "",
+	"or": "",
+	"Other": "",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "",
+	"PDF document (.pdf)": "",
+	"PDF Extract Images (OCR)": "",
+	"pending": "",
+	"Permission denied when accessing media devices": "",
+	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing microphone: {{error}}": "",
+	"Personalization": "",
+	"Pin": "",
+	"Pinned": "",
+	"Pipeline deleted successfully": "",
+	"Pipeline downloaded successfully": "",
+	"Pipelines": "",
+	"Pipelines Not Detected": "",
+	"Pipelines Valves": "",
+	"Plain text (.txt)": "",
+	"Playground": "",
+	"Please carefully review the following warnings:": "",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "",
+	"Previous 30 days": "",
+	"Previous 7 days": "",
+	"Profile Image": "",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
+	"Prompt Content": "",
+	"Prompt suggestions": "",
+	"Prompts": "",
+	"Pull \"{{searchValue}}\" from Ollama.com": "",
+	"Pull a model from Ollama.com": "",
+	"Query Params": "",
+	"RAG Template": "",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "",
+	"Record voice": "",
+	"Redirecting you to OpenWebUI Community": "",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"References from": "",
+	"Refused when it shouldn't have": "",
+	"Regenerate": "",
+	"Release Notes": "",
+	"Relevance": "",
+	"Remove": "",
+	"Remove Model": "",
+	"Rename": "",
+	"Repeat Last N": "",
+	"Request Mode": "",
+	"Reranking Model": "",
+	"Reranking model disabled": "",
+	"Reranking model set to \"{{reranking_model}}\"": "",
+	"Reset": "",
+	"Reset Upload Directory": "",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "",
+	"Rosé Pine": "",
+	"Rosé Pine Dawn": "",
+	"RTL": "",
+	"Run": "",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"Running": "",
+	"Save": "",
+	"Save & Create": "",
+	"Save & Update": "",
+	"Save As Copy": "",
+	"Save Tag": "",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "",
+	"Scroll to bottom when switching between branches": "",
+	"Search": "",
+	"Search a model": "",
+	"Search Chats": "",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "",
+	"Search Knowledge": "",
+	"Search Models": "",
+	"Search Prompts": "",
+	"Search Query Generation Prompt": "",
+	"Search Result Count": "",
+	"Search Tools": "",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_one": "",
+	"Searched {{count}} sites_other": "",
+	"Searching \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "",
+	"See readme.md for instructions": "",
+	"See what's new": "",
+	"Seed": "",
+	"Select a base model": "",
+	"Select a engine": "",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "",
+	"Select a model": "",
+	"Select a pipeline": "",
+	"Select a pipeline url": "",
+	"Select a tool": "",
+	"Select an Ollama instance": "",
+	"Select Engine": "",
+	"Select Knowledge": "",
+	"Select model": "",
+	"Select only one model to call": "",
+	"Selected model(s) do not support image inputs": "",
+	"Semantic distance to query": "",
+	"Send": "",
+	"Send a Message": "",
+	"Send message": "",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "",
+	"Serper API Key": "",
+	"Serply API Key": "",
+	"Serpstack API Key": "",
+	"Server connection verified": "",
+	"Set as default": "",
+	"Set CFG Scale": "",
+	"Set Default Model": "",
+	"Set embedding model (e.g. {{model}})": "",
+	"Set Image Size": "",
+	"Set reranking model (e.g. {{model}})": "",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "",
+	"Set Task Model": "",
+	"Set Voice": "",
+	"Set whisper model": "",
+	"Settings": "",
+	"Settings saved successfully!": "",
+	"Share": "",
+	"Share Chat": "",
+	"Share to OpenWebUI Community": "",
+	"short-summary": "",
+	"Show": "",
+	"Show Admin Details in Account Pending Overlay": "",
+	"Show Model": "",
+	"Show shortcuts": "",
+	"Show your support!": "",
+	"Showcased creativity": "",
+	"Sign in": "",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "",
+	"Sign up": "",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "",
+	"Speech-to-Text Engine": "",
+	"Stop": "",
+	"Stop Sequence": "",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "",
+	"Subtitle (e.g. about the Roman Empire)": "",
+	"Success": "",
+	"Successfully updated.": "",
+	"Suggested": "",
+	"Support": "",
+	"Support this plugin:": "",
+	"Sync directory": "",
+	"System": "",
+	"System Instructions": "",
+	"System Prompt": "",
+	"Tags": "",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "",
+	"Tavily API Key": "",
+	"Tell us more:": "",
+	"Temperature": "",
+	"Template": "",
+	"Temporary Chat": "",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "",
+	"Tfs Z": "",
+	"Thanks for your feedback!": "",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
+	"Theme": "",
+	"Thinking...": "",
+	"This action cannot be undone. Do you wish to continue?": "",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "",
+	"Tika": "",
+	"Tika Server URL required.": "",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "",
+	"Title": "",
+	"Title (e.g. Tell me a fun fact)": "",
+	"Title Auto-Generation": "",
+	"Title cannot be an empty string.": "",
+	"Title Generation Prompt": "",
+	"To access the available model names for downloading,": "",
+	"To access the GGUF models available for downloading,": "",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select filters here, add them to the \"Functions\" workspace first.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
+	"Toast notifications for new updates": "",
+	"Today": "",
+	"Toggle settings": "",
+	"Toggle sidebar": "",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "",
+	"Tool deleted successfully": "",
+	"Tool imported successfully": "",
+	"Tool updated successfully": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution": "",
+	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Top K": "",
+	"Top P": "",
+	"Trouble accessing Ollama?": "",
+	"TTS Model": "",
+	"TTS Settings": "",
+	"TTS Voice": "",
+	"Type": "",
+	"Type Hugging Face Resolve (Download) URL": "",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "",
+	"UI": "",
+	"Unpin": "",
+	"Untagged": "",
+	"Update": "",
+	"Update and Copy Link": "",
+	"Update for the latest features and improvements.": "",
+	"Update password": "",
+	"Updated": "",
+	"Updated at": "",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "",
+	"Upload Pipeline": "",
+	"Upload Progress": "",
+	"URL Mode": "",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "",
+	"Use Initials": "",
+	"use_mlock (Ollama)": "",
+	"use_mmap (Ollama)": "",
+	"user": "",
+	"User": "",
+	"User location successfully retrieved.": "",
+	"User Permissions": "",
+	"Users": "",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "",
+	"Valid time units:": "",
+	"Valves": "",
+	"Valves updated": "",
+	"Valves updated successfully": "",
+	"variable": "",
+	"variable to have them replaced with clipboard content.": "",
+	"Version": "",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "",
+	"Voice Input": "",
+	"Warning": "",
+	"Warning:": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
+	"Web": "",
+	"Web API": "",
+	"Web Loader Settings": "",
+	"Web Search": "",
+	"Web Search Engine": "",
+	"Webhook URL": "",
+	"WebUI Settings": "",
+	"WebUI will make requests to": "",
+	"What’s New in": "",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "",
+	"Won": "",
+	"Workspace": "",
+	"Write a prompt suggestion (e.g. Who are you?)": "",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "",
+	"Write something...": "",
+	"Yesterday": "",
+	"You": "",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You cannot clone a base model": "",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "",
+	"You have shared this chat": "",
+	"You're a helpful assistant.": "",
+	"You're now logged in.": "",
+	"Your account status is currently pending activation.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Youtube": "",
+	"Youtube Loader Settings": ""
+}
diff --git a/src/lib/i18n/locales/tr-TR/translation.json b/src/lib/i18n/locales/tr-TR/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..36c0e81f310603c4b4e123b09cc5a8d5b9722670
--- /dev/null
+++ b/src/lib/i18n/locales/tr-TR/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' veya süresiz için '-1'.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(örn. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(örn. `sh webui.sh --api`)",
+	"(latest)": "(en son)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Temel modeli silemezsiniz",
+	"{{user}}'s Chats": "{{user}}'ın Sohbetleri",
+	"{{webUIName}} Backend Required": "{{webUIName}} Arka-uç Gerekli",
+	"*Prompt node ID(s) are required for image generation": "*Görüntü oluşturma için düğüm kimlikleri gereklidir",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Bir görev modeli, sohbetler ve web arama sorguları için başlık oluşturma gibi görevleri yerine getirirken kullanılır",
+	"a user": "bir kullanıcı",
+	"About": "Hakkında",
+	"Account": "Hesap",
+	"Account Activation Pending": "Hesap Aktivasyonu Bekleniyor",
+	"Accurate information": "Doğru bilgi",
+	"Actions": "Aksiyonlar",
+	"Active Users": "Aktif Kullanıcılar",
+	"Add": "Ekle",
+	"Add a model id": "Bir model id ekle",
+	"Add a short description about what this model does": "Bu modelin ne yaptığı hakkında kısa bir açıklama ekleyin",
+	"Add a short title for this prompt": "Bu prompt için kısa bir başlık ekleyin",
+	"Add a tag": "Bir etiket ekleyin",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Özel prompt ekleyin",
+	"Add Files": "Dosyalar Ekle",
+	"Add Memory": "Bellek Ekle",
+	"Add Model": "Model Ekle",
+	"Add Tag": "Etiket Ekle",
+	"Add Tags": "Etiketler Ekle",
+	"Add text content": "",
+	"Add User": "Kullanıcı Ekle",
+	"Adjusting these settings will apply changes universally to all users.": "Bu ayarların yapılması, değişiklikleri tüm kullanıcılara evrensel olarak uygulayacaktır.",
+	"admin": "yönetici",
+	"Admin": "Yönetici",
+	"Admin Panel": "Yönetici Paneli",
+	"Admin Settings": "Yönetici Ayarları",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Yöneticiler her zaman tüm araçlara erişebilir; kullanıcıların çalışma alanındaki model başına atanmış araçlara ihtiyacı vardır.",
+	"Advanced Parameters": "Gelişmiş Parametreler",
+	"Advanced Params": "Gelişmiş Parametreler",
+	"All chats": "",
+	"All Documents": "Tüm Belgeler",
+	"Allow Chat Deletion": "Sohbet Silmeye İzin Ver",
+	"Allow Chat Editing": "Soğbet Düzenlemeye İzin Ver",
+	"Allow non-local voices": "Yerel olmayan seslere izin verin",
+	"Allow Temporary Chat": "Geçici Sohbetlere İzin Ver",
+	"Allow User Location": "Kullanıcı Konumuna İzin Ver",
+	"Allow Voice Interruption in Call": "Aramada Ses Kesintisine İzin Ver",
+	"alphanumeric characters and hyphens": "alfanumerik karakterler ve tireler",
+	"Already have an account?": "Zaten bir hesabınız mı var?",
+	"an assistant": "bir asistan",
+	"and": "ve",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "ve yeni bir paylaşılan bağlantı oluşturun.",
+	"API Base URL": "API Temel URL",
+	"API Key": "API Anahtarı",
+	"API Key created.": "API Anahtarı oluşturuldu.",
+	"API keys": "API anahtarları",
+	"April": "Nisan",
+	"Archive": "Arşiv",
+	"Archive All Chats": "Tüm Sohbetleri Arşivle",
+	"Archived Chats": "Arşivlenmiş Sohbetler",
+	"are allowed - Activate this command by typing": "izin verilir - Bu komutu yazarak etkinleştirin",
+	"Are you sure?": "Emin misiniz?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Dosya ekle",
+	"Attention to detail": "Ayrıntılara dikkat",
+	"Audio": "Ses",
+	"August": "Ağustos",
+	"Auto-playback response": "Yanıtı otomatik oynatma",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 API Kimlik Doğrulama Dizesi",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Temel URL",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Temel URL gereklidir.",
+	"Available list": "Mevcut liste",
+	"available!": "mevcut!",
+	"Azure AI Speech": "Azure AI Konuşma",
+	"Azure Region": "Azure Bölgesi",
+	"Back": "Geri",
+	"Bad Response": "Kötü Yanıt",
+	"Banners": "Afişler",
+	"Base Model (From)": "Temel Model ('den)",
+	"Batch Size (num_batch)": "Yığın Boyutu (num_batch)",
+	"before": "önce",
+	"Being lazy": "Tembelleşiyor",
+	"Brave Search API Key": "Brave Search API Anahtarı",
+	"Bypass SSL verification for Websites": "Web Siteleri için SSL doğrulamasını atlayın",
+	"Call": "Arama",
+	"Call feature is not supported when using Web STT engine": "Web STT motoru kullanılırken arama özelliği desteklenmiyor",
+	"Camera": "Kamera",
+	"Cancel": "İptal",
+	"Capabilities": "Yetenekler",
+	"Change Password": "Parola Değiştir",
+	"Character": "",
+	"Chat": "Sohbet",
+	"Chat Background Image": "Sohbet Arka Plan Resmi",
+	"Chat Bubble UI": "Sohbet Balonu Arayüzü",
+	"Chat Controls": "Sohbet Kontrolleri",
+	"Chat direction": "Sohbet Yönü",
+	"Chat Overview": "Sohbet Genel Bakış",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Sohbetler",
+	"Check Again": "Tekrar Kontrol Et",
+	"Check for updates": "Güncellemeleri kontrol et",
+	"Checking for updates...": "Güncellemeler kontrol ediliyor...",
+	"Choose a model before saving...": "Kaydetmeden önce bir model seçin...",
+	"Chunk Overlap": "Chunk Çakışması",
+	"Chunk Params": "Chunk Parametreleri",
+	"Chunk Size": "Chunk Boyutu",
+	"Citation": "Alıntı",
+	"Clear memory": "Belleği temizle",
+	"Click here for help.": "Yardım için buraya tıklayın.",
+	"Click here to": "Şunu yapmak için buraya tıklayın:",
+	"Click here to download user import template file.": "Kullanıcı içe aktarma şablon dosyasını indirmek için buraya tıklayın.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Seçmek için buraya tıklayın",
+	"Click here to select a csv file.": "Bir CSV dosyası seçmek için buraya tıklayın.",
+	"Click here to select a py file.": "Bir py dosyası seçmek için buraya tıklayın.",
+	"Click here to upload a workflow.json file.": "Bir workflow.json dosyası yüklemek için buraya tıklayın.",
+	"click here.": "buraya tıklayın.",
+	"Click on the user role button to change a user's role.": "Bir kullanıcının rolünü değiştirmek için kullanıcı rolü düğmesine tıklayın.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Panoya yazma izni reddedildi. Tarayıcı ayarlarını kontrol ederek gerekli izinleri sağlayabilirsiniz.",
+	"Clone": "Klon",
+	"Close": "Kapat",
+	"Code execution": "",
+	"Code formatted successfully": "Kod başarıyla biçimlendirildi",
+	"Collection": "Koleksiyon",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Temel URL",
+	"ComfyUI Base URL is required.": "ComfyUI Temel URL gerekli.",
+	"ComfyUI Workflow": "ComfyUI İş Akışı",
+	"ComfyUI Workflow Nodes": "ComfyUI İş Akışı Düğümleri",
+	"Command": "Komut",
+	"Completions": "",
+	"Concurrent Requests": "Eşzamanlı İstekler",
+	"Confirm": "Onayla",
+	"Confirm Password": "Parolayı Onayla",
+	"Confirm your action": "İşleminizi onaylayın",
+	"Connections": "Bağlantılar",
+	"Contact Admin for WebUI Access": "WebUI Erişimi için Yöneticiyle İletişime Geçin",
+	"Content": "İçerik",
+	"Content Extraction": "İçerik Çıkarma",
+	"Context Length": "Bağlam Uzunluğu",
+	"Continue Response": "Yanıta Devam Et",
+	"Continue with {{provider}}": "{{provider}} ile devam et",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "Mesaj metninin TTS istekleri için nasıl bölüneceğini kontrol edin. 'Noktalama' cümlelere, 'paragraflar' paragraflara böler ve 'hiçbiri' mesajı tek bir dize olarak tutar.",
+	"Controls": "Kontroller",
+	"Copied": "Kopyalandı",
+	"Copied shared chat URL to clipboard!": "Paylaşılan sohbet URL'si panoya kopyalandı!",
+	"Copied to clipboard": "Panoya kopyalandı",
+	"Copy": "Kopyala",
+	"Copy last code block": "Son kod bloğunu kopyala",
+	"Copy last response": "Son yanıtı kopyala",
+	"Copy Link": "Bağlantıyı Kopyala",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Panoya kopyalama başarılı!",
+	"Create a model": "Bir model oluştur",
+	"Create Account": "Hesap Oluştur",
+	"Create Knowledge": "",
+	"Create new key": "Yeni anahtar oluştur",
+	"Create new secret key": "Yeni gizli anahtar oluştur",
+	"Created at": "Oluşturulma tarihi",
+	"Created At": "Şu Tarihte Oluşturuldu:",
+	"Created by": "Şunun tarafından oluşturuldu:",
+	"CSV Import": "CSV İçe Aktarma",
+	"Current Model": "Mevcut Model",
+	"Current Password": "Mevcut Parola",
+	"Custom": "Özel",
+	"Customize models for a specific purpose": "Modelleri belirli amaçlar için özelleştir",
+	"Dark": "Koyu",
+	"Dashboard": "Kontrol Paneli",
+	"Database": "Veritabanı",
+	"December": "Aralık",
+	"Default": "Varsayılan",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Varsayılan (SentenceTransformers)",
+	"Default Model": "Varsayılan Model",
+	"Default model updated": "Varsayılan model güncellendi",
+	"Default Prompt Suggestions": "Varsayılan Prompt Önerileri",
+	"Default User Role": "Varsayılan Kullanıcı Rolü",
+	"Delete": "Sil",
+	"Delete a model": "Bir modeli sil",
+	"Delete All Chats": "Tüm Sohbetleri Sil",
+	"Delete chat": "Sohbeti sil",
+	"Delete Chat": "Sohbeti Sil",
+	"Delete chat?": "Sohbeti sil?",
+	"Delete folder?": "",
+	"Delete function?": "Fonksiyonu sil?",
+	"Delete prompt?": "Promptu sil?",
+	"delete this link": "bu bağlantıyı sil",
+	"Delete tool?": "Aracı sil?",
+	"Delete User": "Kullanıcıyı Sil",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} silindi",
+	"Deleted {{name}}": "{{name}} silindi",
+	"Description": "Açıklama",
+	"Didn't fully follow instructions": "Talimatları tam olarak takip etmedi",
+	"Disabled": "Devre Dışı",
+	"Discover a function": "Bir fonksiyon keşfedin",
+	"Discover a model": "Bir model keşfedin",
+	"Discover a prompt": "Bir prompt keşfedin",
+	"Discover a tool": "Bir araç keşfedin",
+	"Discover, download, and explore custom functions": "Özel fonksiyonları keşfedin, indirin ve inceleyin",
+	"Discover, download, and explore custom prompts": "Özel promptları keşfedin, indirin ve inceleyin",
+	"Discover, download, and explore custom tools": "Özel araçları keşfedin, indirin ve inceleyin",
+	"Discover, download, and explore model presets": "Model ön ayarlarını keşfedin, indirin ve inceleyin",
+	"Dismissible": "Reddedilebilir",
+	"Display Emoji in Call": "Aramada Emoji Göster",
+	"Display the username instead of You in the Chat": "Sohbet'te Siz yerine kullanıcı adını göster",
+	"Do not install functions from sources you do not fully trust.": "Tamamen güvenmediğiniz kaynaklardan fonksiyonlar yüklemeyin.",
+	"Do not install tools from sources you do not fully trust.": "Tamamen güvenmediğiniz kaynaklardan araçlar yüklemeyin.",
+	"Document": "Belge",
+	"Documentation": "Dökümantasyon",
+	"Documents": "Belgeler",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "herhangi bir harici bağlantı yapmaz ve verileriniz güvenli bir şekilde yerel olarak barındırılan sunucunuzda kalır.",
+	"Don't have an account?": "Hesabınız yok mu?",
+	"don't install random functions from sources you don't trust.": "Tanımadığınız kaynaklardan rastgele fonksiyonlar yüklemeyin.",
+	"don't install random tools from sources you don't trust.": "Tanımadığınız kaynaklardan rastgele araçlar yüklemeyin.",
+	"Don't like the style": "Tarzını beğenmedim",
+	"Done": "Tamamlandı",
+	"Download": "İndir",
+	"Download canceled": "İndirme iptal edildi",
+	"Download Database": "Veritabanını İndir",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Sohbete eklemek istediğiniz dosyaları buraya bırakın",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "örn. '30s', '10m'. Geçerli zaman birimleri 's', 'm', 'h'.",
+	"Edit": "Düzenle",
+	"Edit Arena Model": "",
+	"Edit Memory": "Belleği Düzenle",
+	"Edit User": "Kullanıcıyı Düzenle",
+	"ElevenLabs": "",
+	"Email": "E-posta",
+	"Embedding Batch Size": "Gömme Yığın Boyutu",
+	"Embedding Model": "Gömme Modeli",
+	"Embedding Model Engine": "Gömme Modeli Motoru",
+	"Embedding model set to \"{{embedding_model}}\"": "Gömme modeli \"{{embedding_model}}\" olarak ayarlandı",
+	"Enable Community Sharing": "Topluluk Paylaşımını Etkinleştir",
+	"Enable Message Rating": "Mesaj Değerlendirmeyi Etkinleştir",
+	"Enable New Sign Ups": "Yeni Kayıtları Etkinleştir",
+	"Enable Web Search": "Web Aramasını Etkinleştir",
+	"Enable Web Search Query Generation": "Web Arama Sorgusu Oluşturmayı Etkinleştir",
+	"Enabled": "Etkin",
+	"Engine": "Motor",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "CSV dosyanızın şu sırayla 4 sütun içerdiğinden emin olun: İsim, E-posta, Şifre, Rol.",
+	"Enter {{role}} message here": "Buraya {{role}} mesajını girin",
+	"Enter a detail about yourself for your LLMs to recall": "LLM'lerinizin hatırlaması için kendiniz hakkında bir bilgi girin",
+	"Enter api auth string (e.g. username:password)": "Api auth dizesini girin (örn. kullanıcı adı:parola)",
+	"Enter Brave Search API Key": "Brave Search API Anahtarını Girin",
+	"Enter CFG Scale (e.g. 7.0)": "CFG Ölçeğini Girin (örn. 7.0)",
+	"Enter Chunk Overlap": "Chunk Örtüşmesini Girin",
+	"Enter Chunk Size": "Chunk Boyutunu Girin",
+	"Enter description": "",
+	"Enter Github Raw URL": "Github Raw URL'sini girin",
+	"Enter Google PSE API Key": "Google PSE API Anahtarını Girin",
+	"Enter Google PSE Engine Id": "Google PSE Engine Id'sini Girin",
+	"Enter Image Size (e.g. 512x512)": "Görüntü Boyutunu Girin (örn. 512x512)",
+	"Enter language codes": "Dil kodlarını girin",
+	"Enter Model ID": "Model ID'sini Girin",
+	"Enter model tag (e.g. {{modelTag}})": "Model etiketini girin (örn. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Adım Sayısını Girin (örn. 50)",
+	"Enter Sampler (e.g. Euler a)": "Örnekleyiciyi Girin (örn. Euler a)",
+	"Enter Scheduler (e.g. Karras)": "Zamanlayıcıyı Girin (örn. Karras)",
+	"Enter Score": "Skoru Girin",
+	"Enter SearchApi API Key": "Arama-API Anahtarını Girin",
+	"Enter SearchApi Engine": "Arama-API Motorunu Girin",
+	"Enter Searxng Query URL": "Searxng Sorgu URL'sini girin",
+	"Enter Serper API Key": "Serper API Anahtarını Girin",
+	"Enter Serply API Key": "Serply API Anahtarını Girin",
+	"Enter Serpstack API Key": "Serpstack API Anahtarını Girin",
+	"Enter stop sequence": "Durdurma dizisini girin",
+	"Enter system prompt": "Sistem promptunu girin",
+	"Enter Tavily API Key": "Tavily API Anahtarını Girin",
+	"Enter Tika Server URL": "Tika Sunucu URL'sini Girin",
+	"Enter Top K": "Top K'yı girin",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "URL'yi Girin (örn. http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "URL'yi Girin (e.g. http://localhost:11434)",
+	"Enter Your Email": "E-postanızı Girin",
+	"Enter Your Full Name": "Tam Adınızı Girin",
+	"Enter your message": "Mesajınızı girin",
+	"Enter Your Password": "Parolanızı Girin",
+	"Enter Your Role": "Rolünüzü Girin",
+	"Error": "Hata",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Deneysel",
+	"Export": "Dışa Aktar",
+	"Export All Chats (All Users)": "Tüm Sohbetleri Dışa Aktar (Tüm Kullanıcılar)",
+	"Export chat (.json)": "Sohbeti dışa aktar (.json)",
+	"Export Chats": "Sohbetleri Dışa Aktar",
+	"Export Config to JSON File": "",
+	"Export Functions": "Fonksiyonları Dışa Aktar",
+	"Export LiteLLM config.yaml": "LiteLLM config.yaml'ı Dışa Aktar",
+	"Export Models": "Modelleri Dışa Aktar",
+	"Export Prompts": "Promptları Dışa Aktar",
+	"Export Tools": "Araçları Dışa Aktar",
+	"External Models": "Modelleri Dışa Aktar",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "API Anahtarı oluşturulamadı.",
+	"Failed to read clipboard contents": "Pano içeriği okunamadı",
+	"Failed to update settings": "Ayarlar güncellenemedi",
+	"Failed to upload file.": "",
+	"February": "Şubat",
+	"Feedback History": "",
+	"Feel free to add specific details": "Spesifik ayrıntılar eklemekten çekinmeyin",
+	"File": "Dosya",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Dosya Modu",
+	"File not found.": "Dosya bulunamadı.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "Dosya boyutu {{maxSize}} MB'yi aşmamalıdır.",
+	"Files": "Dosyalar",
+	"Filter is now globally disabled": "Filtre artık global olarak devre dışı",
+	"Filter is now globally enabled": "Filtre artık global olarak devrede",
+	"Filters": "Filtreler",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Parmak izi sahteciliği tespit edildi: Avatar olarak baş harfler kullanılamıyor. Varsayılan profil resmine dönülüyor.",
+	"Fluidly stream large external response chunks": "Büyük harici yanıt chunklarını akıcı bir şekilde yayınlayın",
+	"Focus chat input": "Sohbet girişine odaklan",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Talimatları mükemmel şekilde takip etti",
+	"Form": "Form",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Frekans Cezası",
+	"Function": "",
+	"Function created successfully": "Fonksiyon başarıyla oluşturuldu",
+	"Function deleted successfully": "Fonksiyon başarıyla silindi",
+	"Function Description (e.g. A filter to remove profanity from text)": "Fonksiyon Açıklaması (örn. Metinden küfürleri kaldıran bir filtre)",
+	"Function ID (e.g. my_filter)": "Fonksiyon ID'si (örn. my_filter)",
+	"Function is now globally disabled": "Fonksiyon artık global olarak devre dışı",
+	"Function is now globally enabled": "Fonksiyon artık global olarak aktif",
+	"Function Name (e.g. My Filter)": "Fonksiyon Adı (örn. Benim Filtrem)",
+	"Function updated successfully": "Fonksiyon başarıyla güncellendi",
+	"Functions": "Fonksiyonlar",
+	"Functions allow arbitrary code execution": "Fonksiyonlar keyfi kod yürütülmesine izin verir",
+	"Functions allow arbitrary code execution.": "Fonksiyonlar keyfi kod yürütülmesine izin verir.",
+	"Functions imported successfully": "Fonksiyonlar başarıyla içe aktarıldı",
+	"General": "Genel",
+	"General Settings": "Genel Ayarlar",
+	"Generate Image": "Görsel Üret",
+	"Generating search query": "Arama sorgusu oluşturma",
+	"Generation Info": "Üretim Bilgisi",
+	"Get up and running with": "ile çalışmaya başlayın",
+	"Global": "Evrensel",
+	"Good Response": "İyi Yanıt",
+	"Google PSE API Key": "Google PSE API Anahtarı",
+	"Google PSE Engine Id": "Google PSE Engine Id",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "Dokunsal Geri Bildirim",
+	"has no conversations.": "hiç konuşması yok.",
+	"Hello, {{name}}": "Merhaba, {{name}}",
+	"Help": "Yardım",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Gizle",
+	"Hide Model": "Modeli Gizle",
+	"How can I help you today?": "Bugün size nasıl yardımcı olabilirim?",
+	"Hybrid Search": "Karma Arama",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Eylemimin sonuçlarını okuduğumu ve anladığımı kabul ediyorum. Rastgele kod çalıştırmayla ilgili risklerin farkındayım ve kaynağın güvenilirliğini doğruladım.",
+	"ID": "",
+	"Image Generation (Experimental)": "Görüntü Oluşturma (Deneysel)",
+	"Image Generation Engine": "Görüntü Oluşturma Motoru",
+	"Image Settings": "Görüntü Ayarları",
+	"Images": "Görüntüler",
+	"Import Chats": "Sohbetleri İçe Aktar",
+	"Import Config from JSON File": "",
+	"Import Functions": "Fonksiyonları İçe Aktar",
+	"Import Models": "Modelleri İçe Aktar",
+	"Import Prompts": "Promptları İçe Aktar",
+	"Import Tools": "Araçları İçe Aktar",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "stable-diffusion-webui çalıştırılırken `--api-auth` bayrağını dahil edin",
+	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui çalıştırılırken `--api` bayrağını dahil edin",
+	"Info": "Bilgi",
+	"Input commands": "Giriş komutları",
+	"Install from Github URL": "Github URL'sinden yükleyin",
+	"Instant Auto-Send After Voice Transcription": "Ses Transkripsiyonundan Sonra Anında Otomatik Gönder",
+	"Interface": "Arayüz",
+	"Invalid file format.": "",
+	"Invalid Tag": "Geçersiz etiket",
+	"January": "Ocak",
+	"join our Discord for help.": "yardım için Discord'umuza katılın.",
+	"JSON": "JSON",
+	"JSON Preview": "JSON Önizlemesi",
+	"July": "Temmuz",
+	"June": "Haziran",
+	"JWT Expiration": "JWT Bitişi",
+	"JWT Token": "JWT Token",
+	"Keep Alive": "Canlı Tut",
+	"Keyboard shortcuts": "Klavye kısayolları",
+	"Knowledge": "Bilgi",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Dil",
+	"large language models, locally.": "büyük dil modelleri, yerel olarak.",
+	"Last Active": "Son Aktivite",
+	"Last Modified": "Son Düzenleme",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "Varsayılan promptu kullanmak için boş bırakın veya özel bir prompt girin",
+	"Light": "Açık",
+	"Listening...": "Dinleniyor...",
+	"LLMs can make mistakes. Verify important information.": "LLM'ler hata yapabilir. Önemli bilgileri doğrulayın.",
+	"Local Models": "Yerel Modeller",
+	"Lost": "",
+	"LTR": "Soldan Sağa",
+	"Made by OpenWebUI Community": "OpenWebUI Topluluğu tarafından yapılmıştır",
+	"Make sure to enclose them with": "Değişkenlerinizi şu şekilde biçimlendirin:",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "ComfyUI'dan API formatında bir workflow.json dosyası olarak dışa aktardığınızdan emin olun.",
+	"Manage": "Yönet",
+	"Manage Arena Models": "",
+	"Manage Models": "Modelleri Yönet",
+	"Manage Ollama Models": "Ollama Modellerini Yönet",
+	"Manage Pipelines": "Pipelineları Yönet",
+	"March": "Mart",
+	"Max Tokens (num_predict)": "Maksimum Token (num_predict)",
+	"Max Upload Count": "Maksimum Yükleme Sayısı",
+	"Max Upload Size": "Maksimum Yükleme Boyutu",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Aynı anda en fazla 3 model indirilebilir. Lütfen daha sonra tekrar deneyin.",
+	"May": "Mayıs",
+	"Memories accessible by LLMs will be shown here.": "LLM'ler tarafından erişilebilen bellekler burada gösterilecektir.",
+	"Memory": "Bellek",
+	"Memory added successfully": "Bellek başarıyla eklendi",
+	"Memory cleared successfully": "Bellek başarıyle temizlendi",
+	"Memory deleted successfully": "Bellek başarıyla silindi",
+	"Memory updated successfully": "Bellek başarıyla güncellendi",
+	"Merge Responses": "Yanıtları Birleştir",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Bağlantınızı oluşturduktan sonra gönderdiğiniz mesajlar paylaşılmayacaktır. URL'ye sahip kullanıcılar paylaşılan sohbeti görüntüleyebilecektir.",
+	"Min P": "Min P",
+	"Minimum Score": "Minimum Skor",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "DD MMMM YYYY",
+	"MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' başarıyla indirildi.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' zaten indirme sırasında.",
+	"Model {{modelId}} not found": "{{modelId}} bulunamadı",
+	"Model {{modelName}} is not vision capable": "Model {{modelName}} görüntü yeteneğine sahip değil",
+	"Model {{name}} is now {{status}}": "{{name}} modeli artık {{status}}",
+	"Model {{name}} is now at the top": "{{name}} modeli artık en üstte",
+	"Model accepts image inputs": "Model görüntü girdilerini kabul eder",
+	"Model created successfully!": "Model başarıyla oluşturuldu!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model dosya sistemi yolu algılandı. Güncelleme için model kısa adı gerekli, devam edilemiyor.",
+	"Model ID": "Model ID",
+	"Model Name": "",
+	"Model not selected": "Model seçilmedi",
+	"Model Params": "Model Parametreleri",
+	"Model updated successfully": "Model başarıyla güncellendi",
+	"Model Whitelisting": "Model Beyaz Listeye Alma",
+	"Model(s) Whitelisted": "Model(ler) Beyaz Listeye Alındı",
+	"Modelfile Content": "Model Dosyası İçeriği",
+	"Models": "Modeller",
+	"more": "",
+	"More": "Daha Fazla",
+	"Move to Top": "En Üste Taşı",
+	"Name": "Ad",
+	"Name your model": "Modelinizi Adlandırın",
+	"New Chat": "Yeni Sohbet",
+	"New folder": "",
+	"New Password": "Yeni Parola",
+	"No content found": "",
+	"No content to speak": "Konuşacak içerik yok",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Hiçbir dosya seçilmedi",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Sonuç bulunamadı",
+	"No search query generated": "Hiç arama sorgusu oluşturulmadı",
+	"No source available": "Kaynak mevcut değil",
+	"No valves to update": "Güncellenecek valvler yok",
+	"None": "Yok",
+	"Not factually correct": "Gerçeklere göre doğru değil",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Not: Minimum bir skor belirlerseniz, arama yalnızca minimum skora eşit veya daha yüksek bir skora sahip belgeleri getirecektir.",
+	"Notes": "",
+	"Notifications": "Bildirimler",
+	"November": "Kasım",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth ID",
+	"October": "Ekim",
+	"Off": "Kapalı",
+	"Okay, Let's Go!": "Tamam, Hadi Başlayalım!",
+	"OLED Dark": "OLED Koyu",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API'si devre dışı",
+	"Ollama API is disabled": "Ollama API'si devre dışı",
+	"Ollama Version": "Ollama Sürümü",
+	"On": "Açık",
+	"Only": "Yalnızca",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Komut dizisinde yalnızca alfasayısal karakterler ve tireler kabul edilir.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hop! URL geçersiz gibi görünüyor. Lütfen tekrar kontrol edin ve yeniden deneyin.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hop! Desteklenmeyen bir yöntem kullanıyorsunuz (yalnızca önyüz). Lütfen WebUI'yi arkayüzden sunun.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Yeni sohbet aç",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open-WebUI sürümü (v{{OPEN_WEBUI_VERSION}}) gerekli sürümden (v{{REQUIRED_VERSION}}) düşük",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API Konfigürasyonu",
+	"OpenAI API Key is required.": "OpenAI API Anahtarı gereklidir.",
+	"OpenAI URL/Key required.": "OpenAI URL/Anahtar gereklidir.",
+	"or": "veya",
+	"Other": "Diğer",
+	"OUTPUT": "",
+	"Output format": "Çıktı formatı",
+	"Overview": "Genel Bakış",
+	"page": "",
+	"Password": "Parola",
+	"PDF document (.pdf)": "PDF belgesi (.pdf)",
+	"PDF Extract Images (OCR)": "PDF Görüntülerini Çıkart (OCR)",
+	"pending": "beklemede",
+	"Permission denied when accessing media devices": "Medya cihazlarına erişim izni reddedildi",
+	"Permission denied when accessing microphone": "Mikrofona erişim izni reddedildi",
+	"Permission denied when accessing microphone: {{error}}": "Mikrofona erişim izni reddedildi: {{error}}",
+	"Personalization": "Kişiselleştirme",
+	"Pin": "Sabitle",
+	"Pinned": "Sabitlenmiş",
+	"Pipeline deleted successfully": "Pipeline başarıyla silindi",
+	"Pipeline downloaded successfully": "Pipeline başarıyla güncellendi",
+	"Pipelines": "Pipelinelar",
+	"Pipelines Not Detected": "Pipeline Tespit Edilmedi",
+	"Pipelines Valves": "Pipeline Valvleri",
+	"Plain text (.txt)": "Düz metin (.txt)",
+	"Playground": "Oyun Alanı",
+	"Please carefully review the following warnings:": "Lütfen aşağıdaki uyarıları dikkatlice inceleyin:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Olumlu yaklaşım",
+	"Previous 30 days": "Önceki 30 gün",
+	"Previous 7 days": "Önceki 7 gün",
+	"Profile Image": "Profil Fotoğrafı",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (örn. Roma İmparatorluğu hakkında ilginç bir bilgi verin)",
+	"Prompt Content": "Prompt İçeriği",
+	"Prompt suggestions": "Prompt önerileri",
+	"Prompts": "Promptlar",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com'dan \"{{searchValue}}\" çekin",
+	"Pull a model from Ollama.com": "Ollama.com'dan bir model çekin",
+	"Query Params": "Sorgu Parametreleri",
+	"RAG Template": "RAG Şablonu",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Sesli Oku",
+	"Record voice": "Ses kaydı yap",
+	"Redirecting you to OpenWebUI Community": "OpenWebUI Topluluğuna yönlendiriliyorsunuz",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Kendinizden \"User\" olarak bahsedin (örneğin, \"User İspanyolca öğreniyor\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Reddedilmemesi gerekirken reddedildi",
+	"Regenerate": "Tekrar Oluştur",
+	"Release Notes": "Sürüm Notları",
+	"Relevance": "",
+	"Remove": "Kaldır",
+	"Remove Model": "Modeli Kaldır",
+	"Rename": "Yeniden Adlandır",
+	"Repeat Last N": "Son N'yi Tekrar Et",
+	"Request Mode": "İstek Modu",
+	"Reranking Model": "Yeniden Sıralama Modeli",
+	"Reranking model disabled": "Yeniden sıralama modeli devre dışı bırakıldı",
+	"Reranking model set to \"{{reranking_model}}\"": "Yeniden sıralama modeli \"{{reranking_model}}\" olarak ayarlandı",
+	"Reset": "Sıfırla",
+	"Reset Upload Directory": "Yükleme Dizinini Sıfırla",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Yanıtı Panoya Otomatik Kopyala",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Web sitesi izinleri reddedildiğinden yanıt bildirimleri etkinleştirilemiyor. Gerekli erişimi sağlamak için lütfen tarayıcı ayarlarınızı ziyaret edin.",
+	"Response splitting": "Yanıt bölme",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Rol",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "Sağdan Sola",
+	"Run": "Çalıştır",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Llama 2, Code Llama ve diğer modelleri çalıştırın. Kendinize özgü hale getirin ve kendi modelinizi oluşturun.",
+	"Running": "Çalışıyor",
+	"Save": "Kaydet",
+	"Save & Create": "Kaydet ve Oluştur",
+	"Save & Update": "Kaydet ve Güncelle",
+	"Save As Copy": "Kopya Olarak Kaydet",
+	"Save Tag": "Etiketi Kaydet",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Sohbet kayıtlarının doğrudan tarayıcınızın depolama alanına kaydedilmesi artık desteklenmemektedir. Lütfen aşağıdaki butona tıklayarak sohbet kayıtlarınızı indirmek ve silmek için bir dakikanızı ayırın. Endişelenmeyin, sohbet günlüklerinizi arkayüze kolayca yeniden aktarabilirsiniz:",
+	"Scroll to bottom when switching between branches": "Dallar arasında geçiş yaparken en alta kaydır",
+	"Search": "Ara",
+	"Search a model": "Bir model ara",
+	"Search Chats": "Sohbetleri Ara",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Fonksiyonları Ara",
+	"Search Knowledge": "",
+	"Search Models": "Modelleri Ara",
+	"Search Prompts": "Prompt Ara",
+	"Search Query Generation Prompt": "Arama Sorgusu Üretme Promptu",
+	"Search Result Count": "Arama Sonucu Sayısı",
+	"Search Tools": "Arama Araçları",
+	"SearchApi API Key": "Arama-API API Anahtarı",
+	"SearchApi Engine": "Arama-API Motoru",
+	"Searched {{count}} sites_one": "Arandı {{count}} sites_one",
+	"Searched {{count}} sites_other": "Arandı {{count}} sites_other",
+	"Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" aranıyor",
+	"Searching Knowledge for \"{{searchQuery}}\"": "\"{{searchQuery}}\" için Bilgi aranıyor",
+	"Searxng Query URL": "Searxng Sorgu URL'si",
+	"See readme.md for instructions": "Yönergeler için readme.md dosyasına bakın",
+	"See what's new": "Yeniliklere göz atın",
+	"Seed": "Seed",
+	"Select a base model": "Bir temel model seç",
+	"Select a engine": "Bir motor seç",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Bir fonksiyon seç",
+	"Select a model": "Bir model seç",
+	"Select a pipeline": "Bir pipeline seç",
+	"Select a pipeline url": "Bir pipeline URL'si seç",
+	"Select a tool": "Bir araç seç",
+	"Select an Ollama instance": "Bir Ollama örneği seçin",
+	"Select Engine": "Motor Seç",
+	"Select Knowledge": "",
+	"Select model": "Model seç",
+	"Select only one model to call": "Arama için sadece bir model seç",
+	"Selected model(s) do not support image inputs": "Seçilen model(ler) görüntü girişlerini desteklemiyor",
+	"Semantic distance to query": "",
+	"Send": "Gönder",
+	"Send a Message": "Bir Mesaj Gönder",
+	"Send message": "Mesaj gönder",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "İsteğe `stream_options: { include_usage: true }` gönderir.\nDesteklenen sağlayıcılar, ayarlandığında yanıtta token kullanım bilgilerini döndürecektir.",
+	"September": "Eylül",
+	"Serper API Key": "Serper API Anahtarı",
+	"Serply API Key": "Serply API Anahtarı",
+	"Serpstack API Key": "Serpstack API Anahtarı",
+	"Server connection verified": "Sunucu bağlantısı doğrulandı",
+	"Set as default": "Varsayılan olarak ayarla",
+	"Set CFG Scale": "CFG Ölçeğini Ayarla",
+	"Set Default Model": "Varsayılan Modeli Ayarla",
+	"Set embedding model (e.g. {{model}})": "Gömme modelini ayarlayın (örn. {{model}})",
+	"Set Image Size": "Görüntü Boyutunu Ayarla",
+	"Set reranking model (e.g. {{model}})": "Yeniden sıralama modelini ayarlayın (örn. {{model}})",
+	"Set Sampler": "Örnekleyiciyi Ayarla",
+	"Set Scheduler": "Zamanlayıcıyı Ayarla",
+	"Set Steps": "Adımları Ayarla",
+	"Set Task Model": "Görev Modeli Ayarla",
+	"Set Voice": "Ses Ayarla",
+	"Set whisper model": "",
+	"Settings": "Ayarlar",
+	"Settings saved successfully!": "Ayarlar başarıyla kaydedildi!",
+	"Share": "Paylaş",
+	"Share Chat": "Sohbeti Paylaş",
+	"Share to OpenWebUI Community": "OpenWebUI Topluluğu ile Paylaş",
+	"short-summary": "kısa-özet",
+	"Show": "Göster",
+	"Show Admin Details in Account Pending Overlay": "Yönetici Ayrıntılarını Hesap Bekliyor Ekranında Göster",
+	"Show Model": "Modeli Göster",
+	"Show shortcuts": "Kısayolları göster",
+	"Show your support!": "Desteğinizi gösterin!",
+	"Showcased creativity": "Sergilenen yaratıcılık",
+	"Sign in": "Oturum aç",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Çıkış Yap",
+	"Sign up": "Kaydol",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Kaynak",
+	"Speech Playback Speed": "Konuşma Oynatma Hızı",
+	"Speech recognition error: {{error}}": "Konuşma tanıma hatası: {{error}}",
+	"Speech-to-Text Engine": "Konuşmadan Metne Motoru",
+	"Stop": "",
+	"Stop Sequence": "Diziyi Durdur",
+	"Stream Chat Response": "",
+	"STT Model": "STT Modeli",
+	"STT Settings": "STT Ayarları",
+	"Subtitle (e.g. about the Roman Empire)": "Alt başlık (örn. Roma İmparatorluğu hakkında)",
+	"Success": "Başarılı",
+	"Successfully updated.": "Başarıyla güncellendi.",
+	"Suggested": "Önerilen",
+	"Support": "Destek",
+	"Support this plugin:": "Bu eklentiyi destekleyin:",
+	"Sync directory": "",
+	"System": "Sistem",
+	"System Instructions": "",
+	"System Prompt": "Sistem Promptu",
+	"Tags": "Etiketler",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Durdurmak için dokunun",
+	"Tavily API Key": "Tavily API Anahtarı",
+	"Tell us more:": "Bize daha fazlasını anlat:",
+	"Temperature": "Temperature",
+	"Template": "Şablon",
+	"Temporary Chat": "Geçici Sohbet",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Metinden Sese Motoru",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Geri bildiriminiz için teşekkürler!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Bu eklentinin arkasındaki geliştiriciler topluluktan tutkulu gönüllülerdir. Bu eklentinin yararlı olduğunu düşünüyorsanız, gelişimine katkıda bulunmayı düşünün.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "MB cinsinden maksimum dosya boyutu. Dosya boyutu bu sınırı aşarsa, dosya yüklenmeyecektir.",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "Sohbette aynı anda kullanılabilecek maksimum dosya sayısı. Dosya sayısı bu sınırı aşarsa, dosyalar yüklenmeyecektir.",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Puan 0.0 (%0) ile 1.0 (%100) arasında bir değer olmalıdır.",
+	"Theme": "Tema",
+	"Thinking...": "Düşünüyor...",
+	"This action cannot be undone. Do you wish to continue?": "Bu eylem geri alınamaz. Devam etmek istiyor musunuz?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Bu, önemli konuşmalarınızın güvenli bir şekilde arkayüz veritabanınıza kaydedildiğini garantiler. Teşekkür ederiz!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Bu deneysel bir özelliktir, beklendiği gibi çalışmayabilir ve her an değişiklik yapılabilir.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Bu silinecek",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Kapsamlı açıklama",
+	"Tika": "",
+	"Tika Server URL required.": "Tika Sunucu URL'si gereklidir.",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "İpucu: Her değiştirmeden sonra sohbet girişinde tab tuşuna basarak birden fazla değişken yuvasını art arda güncelleyin.",
+	"Title": "Başlık",
+	"Title (e.g. Tell me a fun fact)": "Başlık (e.g. Bana ilginç bir bilgi ver)",
+	"Title Auto-Generation": "Otomatik Başlık Oluşturma",
+	"Title cannot be an empty string.": "Başlık boş bir dize olamaz.",
+	"Title Generation Prompt": "Başlık Oluşturma Promptu",
+	"To access the available model names for downloading,": "İndirilebilir mevcut model adlarına erişmek için,",
+	"To access the GGUF models available for downloading,": "İndirilebilir mevcut GGUF modellerine erişmek için,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "WebUI'ye erişmek için lütfen yöneticiyle iletişime geçin. Yöneticiler kullanıcı durumlarını Yönetici Panelinden yönetebilir.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "sohbet girişine.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Burada eylemleri seçmek için öncelikle bunları \"İşlevler\" çalışma alanına ekleyin.",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Filtreleri burada seçmek için öncelikle bunları \"İşlevler\" çalışma alanına ekleyin.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Araçları burada seçmek için öncelikle bunları \"Araçlar\" çalışma alanına ekleyin.",
+	"Toast notifications for new updates": "",
+	"Today": "Bugün",
+	"Toggle settings": "Ayarları Aç/Kapat",
+	"Toggle sidebar": "Kenar Çubuğunu Aç/Kapat",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "Bağlam Yenilemesinde Korunacak Tokenler (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Araç başarıyla oluşturuldu",
+	"Tool deleted successfully": "Araç başarıyla silindi",
+	"Tool imported successfully": "Araç başarıyla içe aktarıldı",
+	"Tool updated successfully": "Araç başarıyla güncellendi",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Toolkit Açıklaması (örn. Çeşitli işlemleri gerçekleştirmek için bir toolkit)",
+	"Toolkit ID (e.g. my_toolkit)": "Toolkit ID'si (örn. my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Toolkit Adı (örn. Benim Toolkitim)",
+	"Tools": "Araçlar",
+	"Tools are a function calling system with arbitrary code execution": "Araçlar, keyfi kod yürütme ile bir fonksiyon çağırma sistemine sahiptir",
+	"Tools have a function calling system that allows arbitrary code execution": "Araçlar, keyfi kod yürütme izni veren bir fonksiyon çağırma sistemine sahiptir",
+	"Tools have a function calling system that allows arbitrary code execution.": "Araçlar, keyfi kod yürütme izni veren bir fonksiyon çağırma sistemine sahiptir.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Ollama'ya erişmede sorun mu yaşıyorsunuz?",
+	"TTS Model": "TTS Modeli",
+	"TTS Settings": "TTS Ayarları",
+	"TTS Voice": "TTS Sesi",
+	"Type": "Tür",
+	"Type Hugging Face Resolve (Download) URL": "HuggingFace Çözümleme (İndirme) URL'sini Yazın",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Ah! {{provider}}'a bağlanırken bir sorun oluştu.",
+	"UI": "Arayüz",
+	"Unpin": "Sabitlemeyi Kaldır",
+	"Untagged": "",
+	"Update": "Güncelle",
+	"Update and Copy Link": "Güncelle ve Bağlantıyı Kopyala",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Parolayı Güncelle",
+	"Updated": "",
+	"Updated at": "Şu tarihte güncellendi:",
+	"Updated At": "",
+	"Upload": "Yükle",
+	"Upload a GGUF model": "Bir GGUF modeli yükle",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Dosyaları Yükle",
+	"Upload Pipeline": "Pipeline Yükle",
+	"Upload Progress": "Yükleme İlerlemesi",
+	"URL Mode": "URL Modu",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Gravatar Kullan",
+	"Use Initials": "Baş Harfleri Kullan",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "kullanıcı",
+	"User": "",
+	"User location successfully retrieved.": "Kullanıcı konumu başarıyla alındı.",
+	"User Permissions": "Kullanıcı İzinleri",
+	"Users": "Kullanıcılar",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Kullan",
+	"Valid time units:": "Geçerli zaman birimleri:",
+	"Valves": "Valvler",
+	"Valves updated": "Valvler güncellendi",
+	"Valves updated successfully": "Valvler başarıyla güncellendi",
+	"variable": "değişken",
+	"variable to have them replaced with clipboard content.": "panodaki içerikle değiştirilmesi için değişken.",
+	"Version": "Sürüm",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Ses",
+	"Voice Input": "",
+	"Warning": "Uyarı",
+	"Warning:": "Uyarı:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Uyarı: Gömme modelinizi günceller veya değiştirirseniz, tüm belgeleri yeniden içe aktarmanız gerekecektir.",
+	"Web": "Web",
+	"Web API": "Web API",
+	"Web Loader Settings": "Web Yükleyici Ayarları",
+	"Web Search": "Web Araması",
+	"Web Search Engine": "Web Arama Motoru",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "WebUI Ayarları",
+	"WebUI will make requests to": "WebUI, isteklerde bulunacak:",
+	"What’s New in": "Yenilikler:",
+	"Whisper (Local)": "Whisper (Yerel)",
+	"Widescreen Mode": "Geniş Ekran Modu",
+	"Won": "",
+	"Workspace": "Çalışma Alanı",
+	"Write a prompt suggestion (e.g. Who are you?)": "Bir prompt önerisi yazın (örn. Sen kimsin?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "[Konuyu veya anahtar kelimeyi] özetleyen 50 kelimelik bir özet yazın.",
+	"Write something...": "",
+	"Yesterday": "Dün",
+	"You": "Sen",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Aynı anda en fazla {{maxCount}} dosya ile sohbet edebilirsiniz.",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Aşağıdaki 'Yönet' düğmesi aracılığıyla bellekler ekleyerek LLM'lerle etkileşimlerinizi kişiselleştirebilir, onları daha yararlı ve size özel hale getirebilirsiniz.",
+	"You cannot clone a base model": "Bir temel modeli klonlayamazsınız",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Arşivlenmiş sohbetleriniz yok.",
+	"You have shared this chat": "Bu sohbeti paylaştınız",
+	"You're a helpful assistant.": "Sen yardımsever bir asistansın.",
+	"You're now logged in.": "Şimdi giriş yaptınız.",
+	"Your account status is currently pending activation.": "Hesap durumunuz şu anda etkinleştirilmeyi bekliyor.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Tüm katkınız doğrudan eklenti geliştiricisine gidecektir; Open WebUI herhangi bir yüzde almaz. Ancak seçilen finansman platformunun kendi ücretleri olabilir.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube Yükleyici Ayarları"
+}
diff --git a/src/lib/i18n/locales/uk-UA/translation.json b/src/lib/i18n/locales/uk-UA/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..3e1529d175e5f6b716c42880162803ca9fb1704a
--- /dev/null
+++ b/src/lib/i18n/locales/uk-UA/translation.json
@@ -0,0 +1,853 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' or '-1' для відсутності терміну дії.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(e.g. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(e.g. `sh webui.sh --api`)",
+	"(latest)": "(остання)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Ви не можете видалити базову модель.",
+	"{{user}}'s Chats": "Чати {{user}}а",
+	"{{webUIName}} Backend Required": "Необхідно підключення бекенду {{webUIName}}",
+	"*Prompt node ID(s) are required for image generation": "*Для генерації зображення потрібно вказати ідентифікатор(и) вузла(ів)",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "Нова версія (в{{LATEST_VERSION}}) зараз доступна.",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Модель задач використовується при виконанні таких завдань, як генерація заголовків для чатів та пошукових запитів в Інтернеті",
+	"a user": "користувача",
+	"About": "Про програму",
+	"Account": "Обліковий запис",
+	"Account Activation Pending": "Очікування активації облікового запису",
+	"Accurate information": "Точна інформація",
+	"Actions": "Дії",
+	"Active Users": "Активні користувачі",
+	"Add": "Додати",
+	"Add a model id": "Додайте id моделі",
+	"Add a short description about what this model does": "Додайте короткий опис того, що робить ця модель",
+	"Add a short title for this prompt": "Додати коротку назву для цього промту",
+	"Add a tag": "Додайте тег",
+	"Add Arena Model": "Додати модель Arena",
+	"Add Content": "Додати вміст",
+	"Add content here": "Додайте вміст сюди",
+	"Add custom prompt": "Додати користувацьку підказку",
+	"Add Files": "Додати файли",
+	"Add Memory": "Додати пам'ять",
+	"Add Model": "Додати модель",
+	"Add Tag": "Додати тег",
+	"Add Tags": "Додати теги",
+	"Add text content": "Додати текстовий вміст",
+	"Add User": "Додати користувача",
+	"Adjusting these settings will apply changes universally to all users.": "Зміни в цих налаштуваннях будуть застосовані для всіх користувачів.",
+	"admin": "адмін",
+	"Admin": "Адмін",
+	"Admin Panel": "Адмін-панель",
+	"Admin Settings": "Адмін-панель",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Адміністратори мають доступ до всіх інструментів у будь-який час; користувачам потрібні інструменти, призначені для кожної моделі в робочій області.",
+	"Advanced Parameters": "Розширені параметри",
+	"Advanced Params": "Розширені параметри",
+	"All chats": "Усі чати",
+	"All Documents": "Усі документи",
+	"Allow Chat Deletion": "Дозволити видалення чату",
+	"Allow Chat Editing": "Дозволити редагування чату",
+	"Allow non-local voices": "Дозволити не локальні голоси",
+	"Allow Temporary Chat": "Дозволити тимчасовий чат",
+	"Allow User Location": "Доступ до місцезнаходження",
+	"Allow Voice Interruption in Call": "Дозволити переривання голосу під час виклику",
+	"alphanumeric characters and hyphens": "алфавітно-цифрові символи та дефіси",
+	"Already have an account?": "Вже є обліковий запис?",
+	"an assistant": "асистента",
+	"and": "та",
+	"and {{COUNT}} more": "та ще {{COUNT}}",
+	"and create a new shared link.": "і створіть нове спільне посилання.",
+	"API Base URL": "URL-адреса API",
+	"API Key": "Ключ API",
+	"API Key created.": "Ключ API створено.",
+	"API keys": "Ключі API",
+	"April": "Квітень",
+	"Archive": "Архів",
+	"Archive All Chats": "Архівувати всі чати",
+	"Archived Chats": "Архівовані чати",
+	"are allowed - Activate this command by typing": "дозволено - активізуйте цю команду набором",
+	"Are you sure?": "Ви впевнені?",
+	"Arena Models": "Моделі Arena",
+	"Artifacts": "Артефакти",
+	"Ask a question": "Задати питання",
+	"Assistant": "Асистент",
+	"Attach file": "Прикріпити файл",
+	"Attention to detail": "Увага до деталей",
+	"Audio": "Аудіо",
+	"August": "Серпень",
+	"Auto-playback response": "Автоматичне відтворення відповіді",
+	"Automatic1111": "Automatic1111",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Рядок авторизації API",
+	"AUTOMATIC1111 Base URL": "URL-адреса AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "Необхідна URL-адреса AUTOMATIC1111.",
+	"Available list": "Список доступності",
+	"available!": "доступно!",
+	"Azure AI Speech": "Мовлення Azure AI",
+	"Azure Region": "Регіон Azure",
+	"Back": "Назад",
+	"Bad Response": "Неправильна відповідь",
+	"Banners": "Прапори",
+	"Base Model (From)": "Базова модель (від)",
+	"Batch Size (num_batch)": "Розмір партії (num_batch)",
+	"before": "до того, як",
+	"Being lazy": "Не поспішати",
+	"Brave Search API Key": "Ключ API пошуку Brave",
+	"Bypass SSL verification for Websites": "Обхід SSL-перевірки для веб-сайтів",
+	"Call": "Виклик",
+	"Call feature is not supported when using Web STT engine": "Функція виклику не підтримується при використанні Web STT (розпізнавання мовлення) рушія",
+	"Camera": "Камера",
+	"Cancel": "Скасувати",
+	"Capabilities": "Можливості",
+	"Change Password": "Змінити пароль",
+	"Character": "Персонаж",
+	"Chat": "Чат",
+	"Chat Background Image": "Фонове зображення чату",
+	"Chat Bubble UI": "Чат у вигляді бульбашок",
+	"Chat Controls": "Керування чатом",
+	"Chat direction": "Напрям чату",
+	"Chat Overview": "Огляд чату",
+	"Chat Tags Auto-Generation": "Автоматична генерація тегів чату",
+	"Chats": "Чати",
+	"Check Again": "Перевірити ще раз",
+	"Check for updates": "Перевірити оновлення",
+	"Checking for updates...": "Перевірка оновлень...",
+	"Choose a model before saving...": "Оберіть модель перед збереженням...",
+	"Chunk Overlap": "Перекриття фрагментів",
+	"Chunk Params": "Параметри фрагментів",
+	"Chunk Size": "Розмір фрагменту",
+	"Citation": "Цитування",
+	"Clear memory": "Очистити пам'ять",
+	"Click here for help.": "Клацніть тут, щоб отримати допомогу.",
+	"Click here to": "Натисніть тут, щоб",
+	"Click here to download user import template file.": "Натисніть тут, щоб завантажити файл шаблону імпорту користувача.",
+	"Click here to learn more about faster-whisper and see the available models.": "Натисніть тут, щоб дізнатися більше про faster-whisper та переглянути доступні моделі.",
+	"Click here to select": "Натисніть тут, щоб обрати",
+	"Click here to select a csv file.": "Натисніть тут, щоб обрати csv-файл.",
+	"Click here to select a py file.": "Натисніть тут, щоб обрати py-файл.",
+	"Click here to upload a workflow.json file.": "Натисніть тут, щоб завантажити файл workflow.json.",
+	"click here.": "клацніть тут.",
+	"Click on the user role button to change a user's role.": "Натисніть кнопку ролі користувача, щоб змінити роль користувача.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Відмовлено в дозволі на запис до буфера обміну. Будь ласка, перевірте налаштування вашого браузера, щоб надати необхідний доступ.",
+	"Clone": "Клонувати",
+	"Close": "Закрити",
+	"Code execution": "Виконання коду",
+	"Code formatted successfully": "Код успішно відформатовано",
+	"Collection": "Колекція",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL-адреса ComfyUI",
+	"ComfyUI Base URL is required.": "Необхідно вказати URL-адресу ComfyUI.",
+	"ComfyUI Workflow": "ComfyUI Workflow",
+	"ComfyUI Workflow Nodes": "Вузли Workflow в ComfyUI",
+	"Command": "Команда",
+	"Completions": "Завершення",
+	"Concurrent Requests": "Одночасні запити",
+	"Confirm": "Підтвердити",
+	"Confirm Password": "Підтвердіть пароль",
+	"Confirm your action": "Підтвердіть свою дію",
+	"Connections": "З'єднання",
+	"Contact Admin for WebUI Access": "Зверніться до адміна для отримання доступу до WebUI",
+	"Content": "Зміст",
+	"Content Extraction": "Вилучення вмісту",
+	"Context Length": "Довжина контексту",
+	"Continue Response": "Продовжити відповідь",
+	"Continue with {{provider}}": "Продовжити з {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "Керування розбиттям тексту повідомлення для TTS-запитів. 'Punctuation' розбиває на речення, 'paragraphs' розбиває на абзаци, а 'none' залишає повідомлення як один рядок.",
+	"Controls": "Керування",
+	"Copied": "Скопійовано",
+	"Copied shared chat URL to clipboard!": "Скопійовано URL-адресу спільного чату в буфер обміну!",
+	"Copied to clipboard": "Скопійовано в буфер обміну",
+	"Copy": "Копіювати",
+	"Copy last code block": "Копіювати останній блок коду",
+	"Copy last response": "Копіювати останню відповідь",
+	"Copy Link": "Копіювати посилання",
+	"Copy to clipboard": "Копіювати в буфер обміну",
+	"Copying to clipboard was successful!": "Копіювання в буфер обміну виконано успішно!",
+	"Create a model": "Створити модель",
+	"Create Account": "Створити обліковий запис",
+	"Create Knowledge": "Створити знання",
+	"Create new key": "Створити новий ключ",
+	"Create new secret key": "Створити новий секретний ключ",
+	"Created at": "Створено у",
+	"Created At": "Створено у",
+	"Created by": "Створено",
+	"CSV Import": "Імпорт CSV",
+	"Current Model": "Поточна модель",
+	"Current Password": "Поточний пароль",
+	"Custom": "Налаштувати",
+	"Customize models for a specific purpose": "Налаштуйте моделі для конкретних цілей",
+	"Dark": "Темна",
+	"Dashboard": "Панель керування",
+	"Database": "База даних",
+	"December": "Грудень",
+	"Default": "За замовчуванням",
+	"Default (Open AI)": "За замовчуванням (Open AI)",
+	"Default (SentenceTransformers)": "За замовчуванням (SentenceTransformers)",
+	"Default Model": "Модель за замовчуванням",
+	"Default model updated": "Модель за замовчуванням оновлено",
+	"Default Prompt Suggestions": "Пропозиції промтів замовчуванням",
+	"Default User Role": "Роль користувача за замовчуванням",
+	"Delete": "Видалити",
+	"Delete a model": "Видалити модель",
+	"Delete All Chats": "Видалити усі чати",
+	"Delete chat": "Видалити чат",
+	"Delete Chat": "Видалити чат",
+	"Delete chat?": "Видалити чат?",
+	"Delete folder?": "Видалити папку?",
+	"Delete function?": "Видалити функцію?",
+	"Delete prompt?": "Видалити промт?",
+	"delete this link": "видалити це посилання",
+	"Delete tool?": "Видалити інструмент?",
+	"Delete User": "Видалити користувача",
+	"Deleted {{deleteModelTag}}": "Видалено {{deleteModelTag}}",
+	"Deleted {{name}}": "Видалено {{name}}",
+	"Description": "Опис",
+	"Didn't fully follow instructions": "Не повністю дотримувалися інструкцій",
+	"Disabled": "Вимкнено",
+	"Discover a function": "Знайдіть функцію",
+	"Discover a model": "Знайдіть модель",
+	"Discover a prompt": "Знайдіть промт",
+	"Discover a tool": "Знайдіть інструмент",
+	"Discover, download, and explore custom functions": "Знайдіть, завантажте та досліджуйте налаштовані функції",
+	"Discover, download, and explore custom prompts": "Знайдіть, завантажте та досліджуйте налаштовані промти",
+	"Discover, download, and explore custom tools": "Знайдіть, завантажте та досліджуйте налаштовані інструменти",
+	"Discover, download, and explore model presets": "Знайдіть, завантажте та досліджуйте налаштування моделей",
+	"Dismissible": "Неприйнятно",
+	"Display Emoji in Call": "Відображати емодзі у викликах",
+	"Display the username instead of You in the Chat": "Показувати ім'я користувача замість 'Ви' в чаті",
+	"Do not install functions from sources you do not fully trust.": "Не встановлюйте функції з джерел, яким ви не повністю довіряєте.",
+	"Do not install tools from sources you do not fully trust.": "Не встановлюйте інструменти з джерел, яким ви не повністю довіряєте.",
+	"Document": "Документ",
+	"Documentation": "Документація",
+	"Documents": "Документи",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "не встановлює жодних зовнішніх з'єднань, і ваші дані залишаються в безпеці на вашому локальному сервері.",
+	"Don't have an account?": "Немає облікового запису?",
+	"don't install random functions from sources you don't trust.": "не встановлюйте випадкові функції з джерел, яким ви не довіряєте.",
+	"don't install random tools from sources you don't trust.": "не встановлюйте випадкові інструменти з джерел, яким ви не довіряєте.",
+	"Don't like the style": "Не подобається стиль",
+	"Done": "Готово",
+	"Download": "Завантажити",
+	"Download canceled": "Завантаження скасовано",
+	"Download Database": "Завантажити базу даних",
+	"Draw": "Малювати",
+	"Drop any files here to add to the conversation": "Перетягніть сюди файли, щоб додати до розмови",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "напр., '30s','10m'. Дійсні одиниці часу: 'с', 'хв', 'г'.",
+	"Edit": "Редагувати",
+	"Edit Arena Model": "Редагувати модель Arena",
+	"Edit Memory": "Редагувати пам'ять",
+	"Edit User": "Редагувати користувача",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "Ел. пошта",
+	"Embedding Batch Size": "Розмір пакету під час вбудовування",
+	"Embedding Model": "Модель вбудовування",
+	"Embedding Model Engine": "Рушій моделі вбудовування ",
+	"Embedding model set to \"{{embedding_model}}\"": "Встановлена модель вбудовування \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Увімкнути спільний доступ",
+	"Enable Message Rating": "Увімкнути оцінку повідомлень",
+	"Enable New Sign Ups": "Дозволити нові реєстрації",
+	"Enable Web Search": "Увімкнути веб-пошук",
+	"Enable Web Search Query Generation": "Увімкнути генерацію запитів для веб-пошуку",
+	"Enabled": "Увімкнено",
+	"Engine": "Рушій",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Переконайтеся, що ваш CSV-файл містить 4 колонки в такому порядку: Ім'я, Email, Пароль, Роль.",
+	"Enter {{role}} message here": "Введіть повідомлення {{role}} тут",
+	"Enter a detail about yourself for your LLMs to recall": "Введіть відомості про себе для запам'ятовування вашими LLM.",
+	"Enter api auth string (e.g. username:password)": "Введіть рядок авторизації api (напр, ім'я користувача:пароль)",
+	"Enter Brave Search API Key": "Введіть ключ API для пошуку Brave",
+	"Enter CFG Scale (e.g. 7.0)": "Введіть масштаб CFG (напр., 7.0)",
+	"Enter Chunk Overlap": "Введіть перекриття фрагменту",
+	"Enter Chunk Size": "Введіть розмір фрагменту",
+	"Enter description": "Введіть опис",
+	"Enter Github Raw URL": "Введіть Raw URL-адресу Github",
+	"Enter Google PSE API Key": "Введіть ключ API Google PSE",
+	"Enter Google PSE Engine Id": "Введіть Google PSE Engine Id",
+	"Enter Image Size (e.g. 512x512)": "Введіть розмір зображення (напр., 512x512)",
+	"Enter language codes": "Введіть мовні коди",
+	"Enter Model ID": "Введіть ID моделі",
+	"Enter model tag (e.g. {{modelTag}})": "Введіть тег моделі (напр., {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Введіть кількість кроків (напр., 50)",
+	"Enter Sampler (e.g. Euler a)": "Введіть семплер (напр., Euler a)",
+	"Enter Scheduler (e.g. Karras)": "Введіть планувальник (напр., Karras)",
+	"Enter Score": "Введіть бал",
+	"Enter SearchApi API Key": "Введіть ключ API для SearchApi",
+	"Enter SearchApi Engine": "Введіть SearchApi рушія",
+	"Enter Searxng Query URL": "Введіть URL-адресу запиту Searxng",
+	"Enter Serper API Key": "Введіть ключ API Serper",
+	"Enter Serply API Key": "Введіть ключ API Serply",
+	"Enter Serpstack API Key": "Введіть ключ API Serpstack",
+	"Enter stop sequence": "Введіть символ зупинки",
+	"Enter system prompt": "Введіть системний промт",
+	"Enter Tavily API Key": "Введіть ключ API Tavily",
+	"Enter Tika Server URL": "Введіть URL-адресу сервера Tika ",
+	"Enter Top K": "Введіть Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Введіть URL-адресу (напр., http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Введіть URL-адресу (напр., http://localhost:11434)",
+	"Enter Your Email": "Введіть вашу ел. пошту",
+	"Enter Your Full Name": "Введіть ваше ім'я",
+	"Enter your message": "Введіть повідомлення ",
+	"Enter Your Password": "Введіть ваш пароль",
+	"Enter Your Role": "Введіть вашу роль",
+	"Error": "Помилка",
+	"ERROR": "ПОМИЛКА",
+	"Evaluations": "Оцінювання",
+	"Exclude": "",
+	"Experimental": "Експериментальне",
+	"Export": "Експорт",
+	"Export All Chats (All Users)": "Експортувати всі чати (всіх користувачів)",
+	"Export chat (.json)": "Експорт чату (.json)",
+	"Export Chats": "Експортувати чати",
+	"Export Config to JSON File": "Експортувати конфігурацію у файл JSON",
+	"Export Functions": "Експорт функцій ",
+	"Export LiteLLM config.yaml": "Експорт LiteLLM config.yaml",
+	"Export Models": "Експорт моделей",
+	"Export Prompts": "Експортувати промти",
+	"Export Tools": "Експортувати інструменти",
+	"External Models": "Зовнішні моделі",
+	"Failed to add file.": "Не вдалося додати файл.",
+	"Failed to create API Key.": "Не вдалося створити API ключ.",
+	"Failed to read clipboard contents": "Не вдалося прочитати вміст буфера обміну",
+	"Failed to update settings": "Не вдалося оновити налаштування",
+	"Failed to upload file.": "Не вдалося завантажити файл.",
+	"February": "Лютий",
+	"Feedback History": "Історія відгуків",
+	"Feel free to add specific details": "Не соромтеся додавати конкретні деталі",
+	"File": "Файл",
+	"File added successfully.": "Файл успішно додано.",
+	"File content updated successfully.": "Вміст файлу успішно оновлено.",
+	"File Mode": "Файловий режим",
+	"File not found.": "Файл не знайдено.",
+	"File removed successfully.": "Файл успішно видалено.",
+	"File size should not exceed {{maxSize}} MB.": "Розмір файлу не повинен перевищувати {{maxSize}} МБ.",
+	"Files": "Файли",
+	"Filter is now globally disabled": "Фільтр глобально вимкнено",
+	"Filter is now globally enabled": "Фільтр увімкнено глобально",
+	"Filters": "Фільтри",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Виявлено підробку відбитків: Неможливо використовувати ініціали як аватар. Повернення до зображення профілю за замовчуванням.",
+	"Fluidly stream large external response chunks": "Плавно передавати великі фрагменти зовнішніх відповідей",
+	"Focus chat input": "Фокус вводу чату",
+	"Folder deleted successfully": "Папку успішно видалено",
+	"Folder name cannot be empty": "Назва папки не може бути порожньою",
+	"Folder name cannot be empty.": "Назва папки не може бути порожньою.",
+	"Folder name updated successfully": "Назву папки успішно оновлено",
+	"Followed instructions perfectly": "Бездоганно дотримувався інструкцій",
+	"Form": "Форма",
+	"Format your variables using brackets like this:": "Форматуйте свої змінні, використовуючи фігурні дужки таким чином:",
+	"Frequency Penalty": "Штраф за частоту",
+	"Function": "Функція",
+	"Function created successfully": "Функцію успішно створено",
+	"Function deleted successfully": "Функцію успішно видалено",
+	"Function Description (e.g. A filter to remove profanity from text)": "Опис функції (напр., фільтр для видалення ненормативної лексики з тексту)",
+	"Function ID (e.g. my_filter)": "Ідентифікатор функції (напр., my_filter)",
+	"Function is now globally disabled": "Функція зараз глобально вимкнена",
+	"Function is now globally enabled": "Функція зараз глобально увімкнена ",
+	"Function Name (e.g. My Filter)": "Назва функції (напр., My Filter)",
+	"Function updated successfully": "Функцію успішно оновлено",
+	"Functions": "Функції",
+	"Functions allow arbitrary code execution": "Функції дозволяють виконання довільного коду",
+	"Functions allow arbitrary code execution.": "Функції дозволяють виконання довільного коду.",
+	"Functions imported successfully": "Функції успішно імпортовано",
+	"General": "Загальні",
+	"General Settings": "Загальні налаштування",
+	"Generate Image": "Створити зображення",
+	"Generating search query": "Сформувати пошуковий запит",
+	"Generation Info": "Інформація про генерацію",
+	"Get up and running with": "Почніть працювати з",
+	"Global": "Глоб.",
+	"Good Response": "Гарна відповідь",
+	"Google PSE API Key": "Ключ API Google PSE",
+	"Google PSE Engine Id": "Id рушія Google PSE",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "Тактильний зворотній зв'язок",
+	"has no conversations.": "не має розмов.",
+	"Hello, {{name}}": "Привіт, {{name}}",
+	"Help": "Допоможіть",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Приховати",
+	"Hide Model": "Приховати модель",
+	"How can I help you today?": "Чим я можу допомогти вам сьогодні?",
+	"Hybrid Search": "Гібридний пошук",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Я підтверджую, що прочитав і розумію наслідки своїх дій. Я усвідомлюю ризики, пов'язані з виконанням довільного коду, і перевірив надійність джерела.",
+	"ID": "Ідентифікатор",
+	"Image Generation (Experimental)": "Генерування зображень (експериментально)",
+	"Image Generation Engine": "Механізм генерації зображень",
+	"Image Settings": "Налаштування зображення",
+	"Images": "Зображення",
+	"Import Chats": "Імпортувати чати",
+	"Import Config from JSON File": "Імпортувати конфігурацію з файлу JSON",
+	"Import Functions": "Імпорт функцій ",
+	"Import Models": "Імпорт моделей",
+	"Import Prompts": "Імпортувати промти",
+	"Import Tools": "Імпортувати інструменти",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "Включіть прапорець `--api-auth` під час запуску stable-diffusion-webui",
+	"Include `--api` flag when running stable-diffusion-webui": "Включіть прапор `--api` при запуску stable-diffusion-webui",
+	"Info": "Інфо",
+	"Input commands": "Команди вводу",
+	"Install from Github URL": "Встановіть з URL-адреси Github",
+	"Instant Auto-Send After Voice Transcription": "Миттєва автоматична відправка після транскрипції голосу",
+	"Interface": "Інтерфейс",
+	"Invalid file format.": "Неправильний формат файлу.",
+	"Invalid Tag": "Недійсний тег",
+	"January": "Січень",
+	"join our Discord for help.": "приєднуйтеся до нашого Discord для допомоги.",
+	"JSON": "JSON",
+	"JSON Preview": "Перегляд JSON",
+	"July": "Липень",
+	"June": "Червень",
+	"JWT Expiration": "Термін дії JWT",
+	"JWT Token": "Токен JWT",
+	"Keep Alive": "Зберегти активність",
+	"Keyboard shortcuts": "Клавіатурні скорочення",
+	"Knowledge": "Знання",
+	"Knowledge created successfully.": "Знання успішно створено.",
+	"Knowledge deleted successfully.": "Знання успішно видалено.",
+	"Knowledge reset successfully.": "Знання успішно скинуто.",
+	"Knowledge updated successfully": "Знання успішно оновлено",
+	"Landing Page Mode": "Режим головної сторінки",
+	"Language": "Мова",
+	"large language models, locally.": "великими мовними моделями, локально.",
+	"Last Active": "Остання активність",
+	"Last Modified": "Востаннє змінено",
+	"Leaderboard": "Таблиця лідерів",
+	"Leave empty for unlimited": "Залиште порожнім для необмеженого розміру",
+	"Leave empty to include all models or select specific models": "Залиште порожнім, щоб включити всі моделі, або виберіть конкретні моделі.",
+	"Leave empty to use the default prompt, or enter a custom prompt": "Залиште порожнім для використання стандартного запиту, або введіть власний запит",
+	"Light": "Світла",
+	"Listening...": "Слухаю...",
+	"LLMs can make mistakes. Verify important information.": "LLMs можуть помилятися. Перевірте важливу інформацію.",
+	"Local Models": "Локальні моделі",
+	"Lost": "Втрачене",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Зроблено спільнотою OpenWebUI",
+	"Make sure to enclose them with": "Переконайтеся, що вони закриті",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "Обов'язково експортуйте файл workflow.json у форматі API з ComfyUI.",
+	"Manage": "Керувати",
+	"Manage Arena Models": "Керувати моделями Arena",
+	"Manage Models": "Керування моделями",
+	"Manage Ollama Models": "Керування моделями Ollama",
+	"Manage Pipelines": "Керування конвеєрами",
+	"March": "Березень",
+	"Max Tokens (num_predict)": "Макс токенів (num_predict)",
+	"Max Upload Count": "Макс. кількість завантажень",
+	"Max Upload Size": "Макс. розмір завантаження",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 моделі можна завантажити одночасно. Будь ласка, спробуйте пізніше.",
+	"May": "Травень",
+	"Memories accessible by LLMs will be shown here.": "Пам'ять, яка доступна LLM, буде показана тут.",
+	"Memory": "Пам'ять",
+	"Memory added successfully": "Пам'ять додано успішно",
+	"Memory cleared successfully": "Пам'ять успішно очищено",
+	"Memory deleted successfully": "Пам'ять успішно видалено",
+	"Memory updated successfully": "Пам'ять успішно оновлено",
+	"Merge Responses": "Об'єднати відповіді",
+	"Message rating should be enabled to use this feature": "Оцінювання повідомлень має бути увімкнено для використання цієї функції.",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Повідомлення, які ви надішлете після створення посилання, не будуть доступні для інших. Користувачі, які мають URL, зможуть переглядати спільний чат.",
+	"Min P": "Min P",
+	"Minimum Score": "Мінімальний бал",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
+	"Model": "Модель",
+	"Model '{{modelName}}' has been successfully downloaded.": "Модель '{{modelName}}' успішно завантажено.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' вже знаходиться в черзі на завантаження.",
+	"Model {{modelId}} not found": "Модель {{modelId}} не знайдено",
+	"Model {{modelName}} is not vision capable": "Модель {{modelName}} не здатна бачити",
+	"Model {{name}} is now {{status}}": "Модель {{name}} тепер має {{status}}",
+	"Model {{name}} is now at the top": "Модель {{name}} тепер на першому місці",
+	"Model accepts image inputs": "Модель приймає зображеня",
+	"Model created successfully!": "Модель створено успішно!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Виявлено шлях до файлової системи моделі. Для оновлення потрібно вказати коротке ім'я моделі, не вдасться продовжити.",
+	"Model ID": "ID моделі",
+	"Model Name": "Назва моделі",
+	"Model not selected": "Модель не вибрана",
+	"Model Params": "Параметри моделі",
+	"Model updated successfully": "Модель успішно оновлено",
+	"Model Whitelisting": "Модель білого списку",
+	"Model(s) Whitelisted": "Модель(і) білого списку",
+	"Modelfile Content": "Вміст файлу моделі",
+	"Models": "Моделі",
+	"more": "більше",
+	"More": "Більше",
+	"Move to Top": "Перейти до початку",
+	"Name": "Ім'я",
+	"Name your model": "Назвіть свою модель",
+	"New Chat": "Новий чат",
+	"New folder": "Нова папка",
+	"New Password": "Новий пароль",
+	"No content found": "Контент не знайдено.",
+	"No content to speak": "Нема чого говорити",
+	"No distance available": "Відстань недоступна",
+	"No feedbacks found": "Відгуків не знайдено",
+	"No file selected": "Файл не обрано",
+	"No files found.": "Файли не знайдено.",
+	"No HTML, CSS, or JavaScript content found.": "HTML, CSS або JavaScript контент не знайдено.",
+	"No knowledge found": "Знання не знайдено.",
+	"No models found": "Моделей не знайдено",
+	"No results found": "Не знайдено жодного результату",
+	"No search query generated": "Пошуковий запит не сформовано",
+	"No source available": "Джерело не доступне",
+	"No valves to update": "Немає клапанів для оновлення",
+	"None": "Нема",
+	"Not factually correct": "Не відповідає дійсності",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Примітка: Якщо ви встановите мінімальну кількість балів, пошук поверне лише документи з кількістю балів, більшою або рівною мінімальній кількості балів.",
+	"Notes": "",
+	"Notifications": "Сповіщення",
+	"November": "Листопад",
+	"num_gpu (Ollama)": "num_gpu (Ollama)",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth ID",
+	"October": "Жовтень",
+	"Off": "Вимк",
+	"Okay, Let's Go!": "Гаразд, давайте почнемо!",
+	"OLED Dark": "Темний OLED",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API вимкнено",
+	"Ollama API is disabled": "API Ollama вимкнено",
+	"Ollama Version": "Версія Ollama",
+	"On": "Увімк",
+	"Only": "Тільки",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "У рядку команди дозволено використовувати лише алфавітно-цифрові символи та дефіси.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Редагувати можна лише колекції, створіть нову базу знань, щоб редагувати або додавати документи.",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Схоже, що URL-адреса невірна. Будь ласка, перевірте ще раз та спробуйте ще раз.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "Упс! Деякі файли все ще завантажуються. Будь ласка, зачекайте, поки завантаження завершиться.",
+	"Oops! There was an error in the previous response.": "Упс! Сталася помилка в попередній відповіді.",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Ви використовуєте непідтримуваний метод (тільки для фронтенду). Будь ласка, обслуговуйте WebUI з бекенду.",
+	"Open file": "Відкрити файл",
+	"Open in full screen": "Відкрити на весь екран",
+	"Open new chat": "Відкрити новий чат",
+	"Open WebUI uses faster-whisper internally.": "Open WebUI використовує faster-whisper внутрішньо.",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI версія (v{{OPEN_WEBUI_VERSION}}) нижча за необхідну версію (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "Конфігурація OpenAI API",
+	"OpenAI API Key is required.": "Потрібен ключ OpenAI API.",
+	"OpenAI URL/Key required.": "Потрібен OpenAI URL/ключ.",
+	"or": "або",
+	"Other": "Інше",
+	"OUTPUT": "ВИХІД",
+	"Output format": "Формат відповіді",
+	"Overview": "Огляд",
+	"page": "сторінка",
+	"Password": "Пароль",
+	"PDF document (.pdf)": "PDF документ (.pdf)",
+	"PDF Extract Images (OCR)": "Розпізнавання зображень з PDF (OCR)",
+	"pending": "на розгляді",
+	"Permission denied when accessing media devices": "Відмовлено в доступі до медіапристроїв",
+	"Permission denied when accessing microphone": "Відмовлено у доступі до мікрофона",
+	"Permission denied when accessing microphone: {{error}}": "Доступ до мікрофона заборонено: {{error}}",
+	"Personalization": "Персоналізація",
+	"Pin": "Зачепити",
+	"Pinned": "Зачеплено",
+	"Pipeline deleted successfully": "Конвеєр успішно видалено",
+	"Pipeline downloaded successfully": "Конвеєр успішно завантажено",
+	"Pipelines": "Конвеєри",
+	"Pipelines Not Detected": "Конвеєрів не знайдено",
+	"Pipelines Valves": "Клапани конвеєрів",
+	"Plain text (.txt)": "Простий текст (.txt)",
+	"Playground": "Майданчик",
+	"Please carefully review the following warnings:": "Будь ласка, уважно ознайомтеся з наступними попередженнями:",
+	"Please enter a prompt": "Будь ласка, введіть підказку",
+	"Please fill in all fields.": "Будь ласка, заповніть всі поля.",
+	"Please select a reason": "Будь ласка, виберіть причину",
+	"Positive attitude": "Позитивне ставлення",
+	"Previous 30 days": "Попередні 30 днів",
+	"Previous 7 days": "Попередні 7 днів",
+	"Profile Image": "Зображення профілю",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Підказка (напр., розкажіть мені цікавий факт про Римську імперію)",
+	"Prompt Content": "Зміст промту",
+	"Prompt suggestions": "Швидкі промти",
+	"Prompts": "Промти",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Завантажити \"{{searchValue}}\" з Ollama.com",
+	"Pull a model from Ollama.com": "Завантажити модель з Ollama.com",
+	"Query Params": "Параметри запиту",
+	"RAG Template": "Шаблон RAG",
+	"Rating": "Оцінка",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Читати вголос",
+	"Record voice": "Записати голос",
+	"Redirecting you to OpenWebUI Community": "Перенаправляємо вас до спільноти OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Називайте себе \"Користувач\" (наприклад, \"Користувач вивчає іспанську мову\")",
+	"References from": "Посилання з",
+	"Refused when it shouldn't have": "Відмовив, коли не мав би",
+	"Regenerate": "Регенерувати",
+	"Release Notes": "Нотатки до випуску",
+	"Relevance": "Актуальність",
+	"Remove": "Видалити",
+	"Remove Model": "Видалити модель",
+	"Rename": "Перейменувати",
+	"Repeat Last N": "Повторити останні N",
+	"Request Mode": "Режим запиту",
+	"Reranking Model": "Модель переранжування",
+	"Reranking model disabled": "Модель переранжування вимкнена",
+	"Reranking model set to \"{{reranking_model}}\"": "Модель переранжування встановлено на \"{{reranking_model}}\"",
+	"Reset": "Скидання",
+	"Reset Upload Directory": "Скинути каталог завантажень",
+	"Reset Vector Storage/Knowledge": "Скинути векторне сховище/Знання",
+	"Response AutoCopy to Clipboard": "Автокопіювання відповіді в буфер обміну",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Сповіщення про відповіді не можуть бути активовані, оскільки вам було відмовлено в доступі до веб-сайту. Будь ласка, відвідайте налаштування вашого браузера, щоб надати необхідний доступ.",
+	"Response splitting": "Розбиття відповіді",
+	"Result": "Результат",
+	"Rich Text Input for Chat": "",
+	"RK": "RK",
+	"Role": "Роль",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "Запустити",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Запустіть Llama 2, Code Llama та інші моделі. Налаштуйте та створіть власну.",
+	"Running": "Виконується",
+	"Save": "Зберегти",
+	"Save & Create": "Зберегти та створити",
+	"Save & Update": "Зберегти та оновити",
+	"Save As Copy": "Зберегти як копію",
+	"Save Tag": "Зберегти тег",
+	"Saved": "Збережено",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Збереження журналів чату безпосередньо в сховище вашого браузера більше не підтримується. Будь ласка, завантажте та видаліть журнали чату, натиснувши кнопку нижче. Не хвилюйтеся, ви можете легко повторно імпортувати журнали чату до бекенду через",
+	"Scroll to bottom when switching between branches": "Перемотувати до кінця при перемиканні між гілками",
+	"Search": "Пошук",
+	"Search a model": "Шукати модель",
+	"Search Chats": "Пошук в чатах",
+	"Search Collection": "Шукати колекцію",
+	"search for tags": "шукати теги",
+	"Search Functions": "Пошук функцій",
+	"Search Knowledge": "Шукати знання",
+	"Search Models": "Пошук моделей",
+	"Search Prompts": "Пошук промтів",
+	"Search Query Generation Prompt": "Підказка для формування пошукового промту",
+	"Search Result Count": "Кількість результатів пошуку",
+	"Search Tools": "Пошуку інструментів",
+	"SearchApi API Key": "Ключ API для SearchApi",
+	"SearchApi Engine": "Рушій SearchApi",
+	"Searched {{count}} sites_one": "Переглянуто {{count}} сайт",
+	"Searched {{count}} sites_few": "Переглянуто {{count}} сайти",
+	"Searched {{count}} sites_many": "Переглянуто {{count}} сайтів",
+	"Searched {{count}} sites_other": "Переглянуто {{count}} сайтів",
+	"Searching \"{{searchQuery}}\"": "Шукаю \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "Пошук знань для \"{{searchQuery}}\"",
+	"Searxng Query URL": "URL-адреса запиту Searxng",
+	"See readme.md for instructions": "Див. readme.md для інструкцій",
+	"See what's new": "Подивіться, що нового",
+	"Seed": "Сід",
+	"Select a base model": "Обрати базову модель",
+	"Select a engine": "Оберіть рушій",
+	"Select a file to view or drag and drop a file to upload": "Виберіть файл для перегляду або перетягніть і скиньте файл для завантаження.",
+	"Select a function": "Оберіть функцію",
+	"Select a model": "Оберіть модель",
+	"Select a pipeline": "Оберіть конвеєр",
+	"Select a pipeline url": "Оберіть адресу конвеєра",
+	"Select a tool": "Оберіть інструмент",
+	"Select an Ollama instance": "Оберіть екземпляр Ollama",
+	"Select Engine": "Виберіть двигун",
+	"Select Knowledge": "Вибрати знання",
+	"Select model": "Обрати модель",
+	"Select only one model to call": "Оберіть лише одну модель для виклику",
+	"Selected model(s) do not support image inputs": "Вибрані модель(і) не підтримують вхідні зображення",
+	"Semantic distance to query": "Семантична відстань до запиту",
+	"Send": "Надіслати",
+	"Send a Message": "Надіслати повідомлення",
+	"Send message": "Надіслати повідомлення",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "Відправляє `stream_options: { include_usage: true }` у запиті.\nПідтримувані постачальники повернуть інформацію про використання токену у відповіді, якщо вона встановлена.",
+	"September": "Вересень",
+	"Serper API Key": "Ключ API Serper",
+	"Serply API Key": "Ключ API Serply",
+	"Serpstack API Key": "Ключ API Serpstack",
+	"Server connection verified": "З'єднання з сервером підтверджено",
+	"Set as default": "Встановити за замовчуванням",
+	"Set CFG Scale": "Встановити масштаб CFG",
+	"Set Default Model": "Встановити модель за замовчуванням",
+	"Set embedding model (e.g. {{model}})": "Встановити модель вбудовування (напр, {{model}})",
+	"Set Image Size": "Встановити розмір зображення",
+	"Set reranking model (e.g. {{model}})": "Встановити модель переранжування (напр., {{model}})",
+	"Set Sampler": "Встановити семплер",
+	"Set Scheduler": "Встановити планувальник",
+	"Set Steps": "Встановити кроки",
+	"Set Task Model": "Встановити модель задач",
+	"Set Voice": "Встановити голос",
+	"Set whisper model": "Встановити модель whisper",
+	"Settings": "Налаштування",
+	"Settings saved successfully!": "Налаштування успішно збережено!",
+	"Share": "Поділитися",
+	"Share Chat": "Поділитися чатом",
+	"Share to OpenWebUI Community": "Поділитися зі спільнотою OpenWebUI",
+	"short-summary": "короткий зміст",
+	"Show": "Показати",
+	"Show Admin Details in Account Pending Overlay": "Відобразити дані адміна у вікні очікування облікового запису",
+	"Show Model": "Показати модель",
+	"Show shortcuts": "Показати клавіатурні скорочення",
+	"Show your support!": "Підтримайте нас!",
+	"Showcased creativity": "Продемонстрований креатив",
+	"Sign in": "Увійти",
+	"Sign in to {{WEBUI_NAME}}": "Увійти в {{WEBUI_NAME}}",
+	"Sign Out": "Вийти",
+	"Sign up": "Зареєструватися",
+	"Sign up to {{WEBUI_NAME}}": "Зареєструватися в {{WEBUI_NAME}}",
+	"Signing in to {{WEBUI_NAME}}": "Увійти в {{WEBUI_NAME}}",
+	"Source": "Джерело",
+	"Speech Playback Speed": "Швидкість відтворення мовлення",
+	"Speech recognition error: {{error}}": "Помилка розпізнавання мови: {{error}}",
+	"Speech-to-Text Engine": "Система розпізнавання мови",
+	"Stop": "Зупинити",
+	"Stop Sequence": "Символ зупинки",
+	"Stream Chat Response": "Відповідь стрім-чату",
+	"STT Model": "Модель STT ",
+	"STT Settings": "Налаштування STT",
+	"Subtitle (e.g. about the Roman Empire)": "Підзаголовок (напр., про Римську імперію)",
+	"Success": "Успіх",
+	"Successfully updated.": "Успішно оновлено.",
+	"Suggested": "Запропоновано",
+	"Support": "Підтримати",
+	"Support this plugin:": "Підтримайте цей плагін:",
+	"Sync directory": "Синхронізувати каталог",
+	"System": "Система",
+	"System Instructions": "Системні інструкції",
+	"System Prompt": "Системний промт",
+	"Tags": "Теги",
+	"Tags Generation Prompt": "Підказка для генерації тегів",
+	"Tap to interrupt": "Натисніть, щоб перервати",
+	"Tavily API Key": "Ключ API Tavily",
+	"Tell us more:": "Розкажи нам більше:",
+	"Temperature": "Температура",
+	"Template": "Шаблон",
+	"Temporary Chat": "Тимчасовий чат",
+	"Text Splitter": "Роздільник тексту",
+	"Text-to-Speech Engine": "Система синтезу мови",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Дякуємо за ваш відгук!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Розробники цього плагіна - пристрасні волонтери зі спільноти. Якщо ви вважаєте цей плагін корисним, будь ласка, зробіть свій внесок у його розвиток.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "Максимальний розмір файлу в МБ. Якщо розмір файлу перевищує цей ліміт, файл не буде завантажено.",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "Максимальна кількість файлів, які можна використати одночасно в чаті. Якщо кількість файлів перевищує цей ліміт, файли не будуть завантажені.",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Оцінка повинна бути в діапазоні від 0.0 (0%) до 1.0 (100%).",
+	"Theme": "Тема",
+	"Thinking...": "Думаю...",
+	"This action cannot be undone. Do you wish to continue?": "Цю дію не можна скасувати. Ви бажаєте продовжити?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Це забезпечує збереження ваших цінних розмов у безпечному бекенд-сховищі. Дякуємо!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Це експериментальна функція, вона може працювати не так, як очікувалося, і може бути змінена в будь-який час.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Цей варіант видалить усі існуючі файли в колекції та замінить їх новими завантаженими файлами.",
+	"This response was generated by \"{{model}}\"": "Цю відповідь згенеровано за допомогою \"{{model}}\"",
+	"This will delete": "Це призведе до видалення",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "Це видалить <strong>{{NAME}}</strong> та <strong>всі його вмісти</strong>.",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "Це скине базу знань і синхронізує всі файли. Ви бажаєте продовжити?",
+	"Thorough explanation": "Детальне пояснення",
+	"Tika": "Tika",
+	"Tika Server URL required.": "Потрібна URL-адреса сервера Tika.",
+	"Tiktoken": "Tiktoken",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Порада: Оновіть кілька слотів змінних послідовно, натискаючи клавішу табуляції у вікні чату після кожної заміни.",
+	"Title": "Заголовок",
+	"Title (e.g. Tell me a fun fact)": "Заголовок (напр., Розкажіть мені цікавий факт)",
+	"Title Auto-Generation": "Автогенерація заголовків",
+	"Title cannot be an empty string.": "Заголовок не може бути порожнім рядком.",
+	"Title Generation Prompt": "Промт для генерування заголовків",
+	"To access the available model names for downloading,": "Щоб отримати доступ до назв доступних для завантаження моделей,",
+	"To access the GGUF models available for downloading,": "Щоб отримати доступ до моделей GGUF, які можна завантажити,,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Щоб отримати доступ до веб-інтерфейсу, зверніться до адміністратора. Адміністратори можуть керувати статусами користувачів з Панелі адміністратора.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "Щоб прикріпити базу знань тут, спочатку додайте їх до робочого простору \"Знання\".",
+	"to chat input.": "в чаті.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Щоб вибрати дії тут, спочатку додайте їх до робочої області \"Функції\".",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Щоб обрати фільтри тут, спочатку додайте їх до робочої області \"Функції\".",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Щоб обрати тут набори інструментів, спочатку додайте їх до робочої області \"Інструменти\".",
+	"Toast notifications for new updates": "Сповіщення Toast про нові оновлення",
+	"Today": "Сьогодні",
+	"Toggle settings": "Переключити налаштування",
+	"Toggle sidebar": "Переключити бокову панель",
+	"Token": "Токен",
+	"Tokens To Keep On Context Refresh (num_keep)": "Токени для збереження при оновленні контексту (num_keep)",
+	"Too verbose": "",
+	"Tool": "Інструмент",
+	"Tool created successfully": "Інструмент успішно створено",
+	"Tool deleted successfully": "Інструмент успішно видалено",
+	"Tool imported successfully": "Інструмент успішно імпортовано",
+	"Tool updated successfully": "Інструмент успішно оновлено",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Опис інструментарію (напр., набір інструментів для виконання різних операцій)",
+	"Toolkit ID (e.g. my_toolkit)": "Ідентифікатор набору інструментів (напр., my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Назва інструментарію (напр., My ToolKit)",
+	"Tools": "Інструменти",
+	"Tools are a function calling system with arbitrary code execution": "Інструменти - це система виклику функцій з довільним виконанням коду",
+	"Tools have a function calling system that allows arbitrary code execution": "Інструменти мають систему виклику функцій, яка дозволяє виконання довільного коду",
+	"Tools have a function calling system that allows arbitrary code execution.": "Інструменти мають систему виклику функцій, яка дозволяє виконання довільного коду.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Проблеми з доступом до Ollama?",
+	"TTS Model": "Модель TTS",
+	"TTS Settings": "Налаштування TTS",
+	"TTS Voice": "Голос TTS",
+	"Type": "Тип",
+	"Type Hugging Face Resolve (Download) URL": "Введіть URL ресурсу Hugging Face Resolve (завантаження)",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Ой! Виникла проблема при підключенні до {{provider}}.",
+	"UI": "Користувацький інтерфейс",
+	"Unpin": "Відчепити",
+	"Untagged": "Без тегів",
+	"Update": "Оновлення",
+	"Update and Copy Link": "Оновлення та копіювання посилання",
+	"Update for the latest features and improvements.": "Оновіть програми для нових функцій та покращень.",
+	"Update password": "Оновити пароль",
+	"Updated": "Оновлено",
+	"Updated at": "Оновлено на",
+	"Updated At": "Оновлено на",
+	"Upload": "Завантажити",
+	"Upload a GGUF model": "Завантажити GGUF модель",
+	"Upload directory": "Завантажити каталог",
+	"Upload files": "Завантажити файли",
+	"Upload Files": "Завантажити файли",
+	"Upload Pipeline": "Завантажити конвеєр",
+	"Upload Progress": "Прогрес завантаження",
+	"URL Mode": "Режим URL-адреси",
+	"Use '#' in the prompt input to load and include your knowledge.": "Використовуйте '#' у полі введення підказки, щоб завантажити та включити свої знання.",
+	"Use Gravatar": "Змінити аватар",
+	"Use Initials": "Використовувати ініціали",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "користувач",
+	"User": "Користувач",
+	"User location successfully retrieved.": "Місцезнаходження користувача успішно знайдено.",
+	"User Permissions": "Права користувача",
+	"Users": "Користувачі",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "Використовуючи модель арени за замовчуванням з усіма моделями. Натисніть кнопку плюс, щоб додати користувацькі моделі.",
+	"Utilize": "Використовувати",
+	"Valid time units:": "Дійсні одиниці часу:",
+	"Valves": "Клапани",
+	"Valves updated": "Клапани оновлено",
+	"Valves updated successfully": "Клапани успішно оновлено",
+	"variable": "змінна",
+	"variable to have them replaced with clipboard content.": "змінна, щоб замінити їх вмістом буфера обміну.",
+	"Version": "Версія",
+	"Version {{selectedVersion}} of {{totalVersions}}": "Версія {{selectedVersion}} з {{totalVersions}}",
+	"Voice": "Голос",
+	"Voice Input": "Голосове введення",
+	"Warning": "Увага!",
+	"Warning:": "Увага:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Попередження: Якщо ви оновлюєте або змінюєте модель вбудовування, вам потрібно буде повторно імпортувати всі документи.",
+	"Web": "Веб",
+	"Web API": "Веб-API",
+	"Web Loader Settings": "Налаштування веб-завантажувача",
+	"Web Search": "Веб-пошук",
+	"Web Search Engine": "Веб-пошукова система",
+	"Webhook URL": "URL веб-запиту",
+	"WebUI Settings": "Налаштування WebUI",
+	"WebUI will make requests to": "WebUI буде робити запити до",
+	"What’s New in": "Що нового в",
+	"Whisper (Local)": "Whisper (Локально)",
+	"Widescreen Mode": "Широкоекранний режим",
+	"Won": "Переможець",
+	"Workspace": "Робочий простір",
+	"Write a prompt suggestion (e.g. Who are you?)": "Напишіть промт (напр., Хто ти?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишіть стислий зміст у 50 слів, який узагальнює [тема або ключове слово].",
+	"Write something...": "Напишіть щось...",
+	"Yesterday": "Вчора",
+	"You": "Ви",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Ви можете спілкуватися лише з максимальною кількістю {{maxCount}} файлів одночасно.",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Ви можете налаштувати ваші взаємодії з мовними моделями, додавши спогади через кнопку 'Керувати' внизу, що зробить їх більш корисними та персоналізованими для вас.",
+	"You cannot clone a base model": "Базову модель не можна клонувати",
+	"You cannot upload an empty file.": "Ви не можете завантажити порожній файл.",
+	"You have no archived conversations.": "У вас немає архівованих розмов.",
+	"You have shared this chat": "Ви поділилися цим чатом",
+	"You're a helpful assistant.": "Ви корисний асистент.",
+	"You're now logged in.": "Ви увійшли в систему.",
+	"Your account status is currently pending activation.": "Статус вашого облікового запису наразі очікує на активацію.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Весь ваш внесок піде безпосередньо розробнику плагіна; Open WebUI не бере жодних відсотків. Однак, обрана платформа фінансування може мати свої власні збори.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Налаштування завантажувача Youtube"
+}
diff --git a/src/lib/i18n/locales/vi-VN/translation.json b/src/lib/i18n/locales/vi-VN/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..5a8ce3f7326716c344ba24f1dda8af191083a372
--- /dev/null
+++ b/src/lib/i18n/locales/vi-VN/translation.json
@@ -0,0 +1,850 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' hoặc '-1' không hết hạn.",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
+	"(e.g. `sh webui.sh --api`)": "(vd: `sh webui.sh --api`)",
+	"(latest)": "(mới nhất)",
+	"{{ models }}": "{{ mô hình }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Bạn không thể xóa base model",
+	"{{user}}'s Chats": "{{user}}'s Chats",
+	"{{webUIName}} Backend Required": "{{webUIName}} Yêu cầu Backend",
+	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Mô hình tác vụ được sử dụng khi thực hiện các tác vụ như tạo tiêu đề cho cuộc trò chuyện và truy vấn tìm kiếm trên web",
+	"a user": "người sử dụng",
+	"About": "Giới thiệu",
+	"Account": "Tài khoản",
+	"Account Activation Pending": "Tài khoản đang chờ kích hoạt",
+	"Accurate information": "Thông tin chính xác",
+	"Actions": "Tác vụ",
+	"Active Users": "Người dùng đang hoạt động",
+	"Add": "Thêm",
+	"Add a model id": "Thêm model id",
+	"Add a short description about what this model does": "Thêm mô tả ngắn về những khả năng của model",
+	"Add a short title for this prompt": "Thêm tiêu đề ngắn cho prompt này",
+	"Add a tag": "Thêm thẻ (tag)",
+	"Add Arena Model": "",
+	"Add Content": "",
+	"Add content here": "",
+	"Add custom prompt": "Thêm prompt tùy chỉnh",
+	"Add Files": "Thêm tệp",
+	"Add Memory": "Thêm bộ nhớ",
+	"Add Model": "Thêm model",
+	"Add Tag": "Thêm thẻ",
+	"Add Tags": "thêm thẻ",
+	"Add text content": "",
+	"Add User": "Thêm người dùng",
+	"Adjusting these settings will apply changes universally to all users.": "Các thay đổi cài đặt này sẽ áp dụng cho tất cả người sử dụng.",
+	"admin": "quản trị viên",
+	"Admin": "Quản trị",
+	"Admin Panel": "Trang Quản trị",
+	"Admin Settings": "Cài đặt hệ thống",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Quản trị viên luôn có quyền truy cập vào tất cả các tool; người dùng cần các tools được chỉ định cho mỗi mô hình trong workspace.",
+	"Advanced Parameters": "Các tham số Nâng cao",
+	"Advanced Params": "Các tham số Nâng cao",
+	"All chats": "",
+	"All Documents": "Tất cả tài liệu",
+	"Allow Chat Deletion": "Cho phép Xóa nội dung chat",
+	"Allow Chat Editing": "Cho phép Sửa nội dung chat",
+	"Allow non-local voices": "Cho phép giọng nói không bản xứ",
+	"Allow Temporary Chat": "Cho phép Chat nháp",
+	"Allow User Location": "Cho phép sử dụng vị trí người dùng",
+	"Allow Voice Interruption in Call": "Cho phép gián đoạn giọng nói trong cuộc gọi",
+	"alphanumeric characters and hyphens": "ký tự số và gạch nối",
+	"Already have an account?": "Bạn đã có tài khoản?",
+	"an assistant": "trợ lý",
+	"and": "và",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "và tạo một link chia sẻ mới",
+	"API Base URL": "Đường dẫn tới API (API Base URL)",
+	"API Key": "API Key",
+	"API Key created.": "Khóa API đã tạo",
+	"API keys": "API Keys",
+	"April": "Tháng 4",
+	"Archive": "Lưu trữ",
+	"Archive All Chats": "Lưu tất cả các cuộc Chat",
+	"Archived Chats": "Lưu các cuộc Chat",
+	"are allowed - Activate this command by typing": "được phép - Kích hoạt lệnh này bằng cách gõ",
+	"Are you sure?": "Bạn có chắc chắn không?",
+	"Arena Models": "",
+	"Artifacts": "",
+	"Ask a question": "",
+	"Assistant": "",
+	"Attach file": "Đính kèm file",
+	"Attention to detail": "Có sự chú ý đến chi tiết của vấn đề",
+	"Audio": "Âm thanh",
+	"August": "Tháng 8",
+	"Auto-playback response": "Tự động phát lại phản hồi (Auto-playback)",
+	"Automatic1111": "",
+	"AUTOMATIC1111 Api Auth String": "",
+	"AUTOMATIC1111 Base URL": "Đường dẫn kết nối tới AUTOMATIC1111 (Base URL)",
+	"AUTOMATIC1111 Base URL is required.": "Base URL của AUTOMATIC1111 là bắt buộc.",
+	"Available list": "",
+	"available!": "có sẵn!",
+	"Azure AI Speech": "",
+	"Azure Region": "",
+	"Back": "Quay lại",
+	"Bad Response": "Trả lời KHÔNG tốt",
+	"Banners": "Biểu ngữ",
+	"Base Model (From)": "Mô hình cơ sở (từ)",
+	"Batch Size (num_batch)": "",
+	"before": "trước",
+	"Being lazy": "Lười biếng",
+	"Brave Search API Key": "Khóa API tìm kiếm dũng cảm",
+	"Bypass SSL verification for Websites": "Bỏ qua xác thực SSL cho các trang web",
+	"Call": "Gọi",
+	"Call feature is not supported when using Web STT engine": "Tính năng gọi điện không được hỗ trợ khi sử dụng công cụ Web STT",
+	"Camera": "",
+	"Cancel": "Hủy bỏ",
+	"Capabilities": "Năng lực",
+	"Change Password": "Đổi Mật khẩu",
+	"Character": "",
+	"Chat": "Trò chuyện",
+	"Chat Background Image": "Hình nền trò chuyện",
+	"Chat Bubble UI": "Bảng chat",
+	"Chat Controls": "Điều khiển Chats",
+	"Chat direction": "Hướng chat",
+	"Chat Overview": "",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "Chat",
+	"Check Again": "Kiểm tra Lại",
+	"Check for updates": "Kiểm tra cập nhật",
+	"Checking for updates...": "Đang kiểm tra cập nhật...",
+	"Choose a model before saving...": "Chọn mô hình trước khi lưu...",
+	"Chunk Overlap": "Chồng lấn (overlap)",
+	"Chunk Params": "Tham số khối (chunk)",
+	"Chunk Size": "Kích thước khối (size)",
+	"Citation": "Trích dẫn",
+	"Clear memory": "Xóa bộ nhớ",
+	"Click here for help.": "Bấm vào đây để được trợ giúp.",
+	"Click here to": "Nhấn vào đây để",
+	"Click here to download user import template file.": "Bấm vào đây để tải xuống tệp template của người dùng.",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "Bấm vào đây để chọn",
+	"Click here to select a csv file.": "Nhấn vào đây để chọn tệp csv",
+	"Click here to select a py file.": "Nhấn vào đây để chọn tệp py",
+	"Click here to upload a workflow.json file.": "Bấm vào đây để upload file worklow.json",
+	"click here.": "bấm vào đây.",
+	"Click on the user role button to change a user's role.": "Bấm vào nút trong cột VAI TRÒ để thay đổi quyền của người sử dụng.",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Quyền ghi vào clipboard bị từ chối. Vui lòng kiểm tra cài đặt trên trình duyệt của bạn để được cấp quyền truy cập cần thiết.",
+	"Clone": "Nhân bản",
+	"Close": "Đóng",
+	"Code execution": "",
+	"Code formatted successfully": "Mã được định dạng thành công",
+	"Collection": "Tổng hợp mọi tài liệu",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Base URL",
+	"ComfyUI Base URL is required.": "Base URL của ComfyUI là bắt buộc.",
+	"ComfyUI Workflow": "",
+	"ComfyUI Workflow Nodes": "",
+	"Command": "Lệnh",
+	"Completions": "",
+	"Concurrent Requests": "Các truy vấn đồng thời",
+	"Confirm": "Xác nhận",
+	"Confirm Password": "Xác nhận Mật khẩu",
+	"Confirm your action": "Xác nhận hành động của bạn",
+	"Connections": "Kết nối",
+	"Contact Admin for WebUI Access": "Liên hệ với Quản trị viên để được cấp quyền truy cập",
+	"Content": "Nội dung",
+	"Content Extraction": "Trích xuất nội dung",
+	"Context Length": "Độ dài ngữ cảnh (Context Length)",
+	"Continue Response": "Tiếp tục trả lời",
+	"Continue with {{provider}}": "Tiếp tục với {{provider}}",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
+	"Controls": "",
+	"Copied": "Đã sao chép",
+	"Copied shared chat URL to clipboard!": "Đã sao chép link chia sẻ trò chuyện vào clipboard!",
+	"Copied to clipboard": "",
+	"Copy": "Sao chép",
+	"Copy last code block": "Sao chép khối mã cuối cùng",
+	"Copy last response": "Sao chép phản hồi cuối cùng",
+	"Copy Link": "Sao chép link",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "Sao chép vào clipboard thành công!",
+	"Create a model": "Tạo model",
+	"Create Account": "Tạo Tài khoản",
+	"Create Knowledge": "",
+	"Create new key": "Tạo key mới",
+	"Create new secret key": "Tạo key bí mật mới",
+	"Created at": "Được tạo vào lúc",
+	"Created At": "Tạo lúc",
+	"Created by": "Tạo bởi",
+	"CSV Import": "Nạp CSV",
+	"Current Model": "Mô hình hiện tại",
+	"Current Password": "Mật khẩu hiện tại",
+	"Custom": "Tùy chỉnh",
+	"Customize models for a specific purpose": "Tùy chỉnh model cho những mục đích riêng",
+	"Dark": "Tối",
+	"Dashboard": "",
+	"Database": "Cơ sở dữ liệu",
+	"December": "Tháng 12",
+	"Default": "Mặc định",
+	"Default (Open AI)": "",
+	"Default (SentenceTransformers)": "Mặc định (SentenceTransformers)",
+	"Default Model": "Model mặc định",
+	"Default model updated": "Mô hình mặc định đã được cập nhật",
+	"Default Prompt Suggestions": "Đề xuất prompt mặc định",
+	"Default User Role": "Vai trò mặc định",
+	"Delete": "Xóa",
+	"Delete a model": "Xóa mô hình",
+	"Delete All Chats": "Xóa mọi cuộc Chat",
+	"Delete chat": "Xóa nội dung chat",
+	"Delete Chat": "Xóa chat",
+	"Delete chat?": "Xóa chat?",
+	"Delete folder?": "",
+	"Delete function?": "Xóa function?",
+	"Delete prompt?": "Xóa prompt?",
+	"delete this link": "Xóa link này",
+	"Delete tool?": "Xóa tool?",
+	"Delete User": "Xóa người dùng",
+	"Deleted {{deleteModelTag}}": "Đã xóa {{deleteModelTag}}",
+	"Deleted {{name}}": "Đã xóa {{name}}",
+	"Description": "Mô tả",
+	"Didn't fully follow instructions": "Không tuân theo chỉ dẫn một cách đầy đủ",
+	"Disabled": "Đã tắt",
+	"Discover a function": "Khám phá function",
+	"Discover a model": "Khám phá model",
+	"Discover a prompt": "Khám phá thêm prompt mới",
+	"Discover a tool": "Khám phá tool",
+	"Discover, download, and explore custom functions": "Tìm kiếm, tải về và khám phá thêm các function tùy chỉnh",
+	"Discover, download, and explore custom prompts": "Tìm kiếm, tải về và khám phá thêm các prompt tùy chỉnh",
+	"Discover, download, and explore custom tools": "Tìm kiếm, tải về và khám phá thêm các tool tùy chỉnh",
+	"Discover, download, and explore model presets": "Tìm kiếm, tải về và khám phá thêm các model presets",
+	"Dismissible": "Có thể loại bỏ",
+	"Display Emoji in Call": "Hiển thị Emoji trong cuộc gọi",
+	"Display the username instead of You in the Chat": "Hiển thị tên người sử dụng thay vì 'Bạn' trong nội dung chat",
+	"Do not install functions from sources you do not fully trust.": "Không cài đặt các functions từ các nguồn mà bạn không hoàn toàn tin tưởng.",
+	"Do not install tools from sources you do not fully trust.": "Không cài đặt các tools từ những nguồn mà bạn không hoàn toàn tin tưởng.",
+	"Document": "Tài liệu",
+	"Documentation": "Tài liệu",
+	"Documents": "Tài liệu",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "không thực hiện bất kỳ kết nối ngoài nào, và dữ liệu của bạn vẫn được lưu trữ an toàn trên máy chủ lưu trữ cục bộ của bạn.",
+	"Don't have an account?": "Không có tài khoản?",
+	"don't install random functions from sources you don't trust.": "không cài đặt các function từ các nguồn mà bạn không tin tưởng.",
+	"don't install random tools from sources you don't trust.": "không cài đặt các tools từ các nguồn mà bạn không tin tưởng.",
+	"Don't like the style": "Không thích phong cách trả lời",
+	"Done": "Hoàn thành",
+	"Download": "Tải về",
+	"Download canceled": "Đã hủy download",
+	"Download Database": "Tải xuống Cơ sở dữ liệu",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "Thả bất kỳ tệp nào ở đây để thêm vào nội dung chat",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "vd: '30s','10m'. Đơn vị thời gian hợp lệ là 's', 'm', 'h'.",
+	"Edit": "Chỉnh sửa",
+	"Edit Arena Model": "",
+	"Edit Memory": "Sửa Memory",
+	"Edit User": "Thay đổi thông tin người sử dụng",
+	"ElevenLabs": "",
+	"Email": "Email",
+	"Embedding Batch Size": "",
+	"Embedding Model": "Mô hình embedding",
+	"Embedding Model Engine": "Trình xử lý embedding",
+	"Embedding model set to \"{{embedding_model}}\"": "Mô hình embedding đã được thiết lập thành \"{{embedding_model}}\"",
+	"Enable Community Sharing": "Cho phép Chia sẻ Cộng đồng",
+	"Enable Message Rating": "Cho phép phản hồi, đánh giá",
+	"Enable New Sign Ups": "Cho phép đăng ký mới",
+	"Enable Web Search": "Cho phép tìm kiếm Web",
+	"Enable Web Search Query Generation": "",
+	"Enabled": "Đã bật",
+	"Engine": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Đảm bảo tệp CSV của bạn bao gồm 4 cột theo thứ tự sau: Name, Email, Password, Role.",
+	"Enter {{role}} message here": "Nhập yêu cầu của {{role}} ở đây",
+	"Enter a detail about yourself for your LLMs to recall": "Nhập chi tiết về bản thân của bạn để LLMs có thể nhớ",
+	"Enter api auth string (e.g. username:password)": "Nhập chuỗi xác thực api (ví dụ: username: mật khẩu)",
+	"Enter Brave Search API Key": "Nhập API key cho Brave Search",
+	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter Chunk Overlap": "Nhập Chunk chồng lấn (overlap)",
+	"Enter Chunk Size": "Nhập Kích thước Chunk",
+	"Enter description": "",
+	"Enter Github Raw URL": "Nhập URL cho Github Raw",
+	"Enter Google PSE API Key": "Nhập Google PSE API Key",
+	"Enter Google PSE Engine Id": "Nhập Google PSE Engine Id",
+	"Enter Image Size (e.g. 512x512)": "Nhập Kích thước ảnh (vd: 512x512)",
+	"Enter language codes": "Nhập mã ngôn ngữ",
+	"Enter Model ID": "",
+	"Enter model tag (e.g. {{modelTag}})": "Nhập thẻ mô hình (vd: {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Nhập số Steps (vd: 50)",
+	"Enter Sampler (e.g. Euler a)": "",
+	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Score": "Nhập Score",
+	"Enter SearchApi API Key": "",
+	"Enter SearchApi Engine": "",
+	"Enter Searxng Query URL": "Nhập Query URL cho Searxng",
+	"Enter Serper API Key": "Nhập Serper API Key",
+	"Enter Serply API Key": "Nhập Serply API Key",
+	"Enter Serpstack API Key": "Nhập Serpstack API Key",
+	"Enter stop sequence": "Nhập stop sequence",
+	"Enter system prompt": "Nhập system prompt",
+	"Enter Tavily API Key": "Nhập Tavily API Key",
+	"Enter Tika Server URL": "Nhập URL cho  Tika Server",
+	"Enter Top K": "Nhập Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "Nhập URL (vd: http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "Nhập URL (vd: http://localhost:11434)",
+	"Enter Your Email": "Nhập Email của bạn",
+	"Enter Your Full Name": "Nhập Họ và Tên của bạn",
+	"Enter your message": "Nhập tin nhắn của bạn",
+	"Enter Your Password": "Nhập Mật khẩu của bạn",
+	"Enter Your Role": "Nhập vai trò của bạn",
+	"Error": "Lỗi",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "Thử nghiệm",
+	"Export": "Xuất khẩu",
+	"Export All Chats (All Users)": "Tải về tất cả nội dung chat (tất cả mọi người)",
+	"Export chat (.json)": "Tải chat (.json)",
+	"Export Chats": "Tải nội dung chat về máy",
+	"Export Config to JSON File": "",
+	"Export Functions": "Tải Functions về máy",
+	"Export LiteLLM config.yaml": "",
+	"Export Models": "Tải Models về máy",
+	"Export Prompts": "Tải các prompt về máy",
+	"Export Tools": "Tải Tools về máy",
+	"External Models": "Các model ngoài",
+	"Failed to add file.": "",
+	"Failed to create API Key.": "Lỗi khởi tạo API Key",
+	"Failed to read clipboard contents": "Không thể đọc nội dung clipboard",
+	"Failed to update settings": "Lỗi khi cập nhật các cài đặt",
+	"Failed to upload file.": "",
+	"February": "Tháng 2",
+	"Feedback History": "",
+	"Feel free to add specific details": "Mô tả chi tiết về chất lượng của câu hỏi và phương án trả lời",
+	"File": "Tệp",
+	"File added successfully.": "",
+	"File content updated successfully.": "",
+	"File Mode": "Chế độ Tệp văn bản",
+	"File not found.": "Không tìm thấy tệp.",
+	"File removed successfully.": "",
+	"File size should not exceed {{maxSize}} MB.": "",
+	"Files": "Tệp",
+	"Filter is now globally disabled": "Bộ lọc hiện đã bị vô hiệu hóa trên toàn hệ thống",
+	"Filter is now globally enabled": "Bộ lọc hiện được kích hoạt trên toàn hệ thống",
+	"Filters": "Lọc",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Phát hiện giả mạo vân tay: Không thể sử dụng tên viết tắt làm hình đại diện. Mặc định là hình ảnh hồ sơ mặc định.",
+	"Fluidly stream large external response chunks": "Truyền tải các khối phản hồi bên ngoài lớn một cách trôi chảy",
+	"Focus chat input": "Tập trung vào nội dung chat",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "Tuân theo chỉ dẫn một cách hoàn hảo",
+	"Form": "",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "Hình phạt tần số",
+	"Function": "",
+	"Function created successfully": "Function được tạo thành công",
+	"Function deleted successfully": "Function đã bị xóa",
+	"Function Description (e.g. A filter to remove profanity from text)": "Mô tả Function (ví dụ: Bộ lọc để loại bỏ các ngôn từ tục tĩu khỏi văn bản)",
+	"Function ID (e.g. my_filter)": "Mã Function (ví dụ: my_filter)",
+	"Function is now globally disabled": "Function hiện đã bị vô hiệu hóa trên toàn hệ thống",
+	"Function is now globally enabled": "Function đã được kích hoạt trên toàn hệ thống",
+	"Function Name (e.g. My Filter)": "Tên Function (ví dụ: My Filter)",
+	"Function updated successfully": "Function được cập nhật thành công",
+	"Functions": "",
+	"Functions allow arbitrary code execution": "Các Function cho phép thực thi mã tùy ý",
+	"Functions allow arbitrary code execution.": "Các Function cho phép thực thi mã tùy ý.",
+	"Functions imported successfully": "Các function đã được nạp thành công",
+	"General": "Cài đặt chung",
+	"General Settings": "Cấu hình chung",
+	"Generate Image": "Sinh ảnh",
+	"Generating search query": "Tạo truy vấn tìm kiếm",
+	"Generation Info": "Thông tin chung",
+	"Get up and running with": "Khởi động và chạy với",
+	"Global": "Toàn hệ thống",
+	"Good Response": "Trả lời tốt",
+	"Google PSE API Key": "Khóa API Google PSE",
+	"Google PSE Engine Id": "ID công cụ Google PSE",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "Phản hồi xúc giác",
+	"has no conversations.": "không có hội thoại",
+	"Hello, {{name}}": "Xin chào {{name}}",
+	"Help": "Trợ giúp",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "Ẩn",
+	"Hide Model": "Ẩn mô hình",
+	"How can I help you today?": "Tôi có thể giúp gì cho bạn hôm nay?",
+	"Hybrid Search": "Tìm kiếm Hybrid",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Tôi thừa nhận rằng tôi đã đọc và tôi hiểu ý nghĩa của hành động của mình. Tôi nhận thức được những rủi ro liên quan đến việc thực thi mã tùy ý và tôi đã xác minh độ tin cậy của nguồn.",
+	"ID": "",
+	"Image Generation (Experimental)": "Tạo ảnh (thử nghiệm)",
+	"Image Generation Engine": "Công cụ tạo ảnh",
+	"Image Settings": "Cài đặt ảnh",
+	"Images": "Hình ảnh",
+	"Import Chats": "Nạp lại nội dung chat",
+	"Import Config from JSON File": "",
+	"Import Functions": "Nạp Functions",
+	"Import Models": "Nạp model",
+	"Import Prompts": "Nạp các prompt lên hệ thống",
+	"Import Tools": "Nạp Tools",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Bao gồm flag `--api` khi chạy stable-diffusion-webui",
+	"Info": "Thông tin",
+	"Input commands": "Nhập các câu lệnh",
+	"Install from Github URL": "Cài đặt từ Github URL",
+	"Instant Auto-Send After Voice Transcription": "Tự động gửi ngay lập tức sau khi phiên dịch giọng nói",
+	"Interface": "Giao diện",
+	"Invalid file format.": "",
+	"Invalid Tag": "Tag không hợp lệ",
+	"January": "Tháng 1",
+	"join our Discord for help.": "tham gia Discord của chúng tôi để được trợ giúp.",
+	"JSON": "JSON",
+	"JSON Preview": "Xem trước JSON",
+	"July": "Tháng 7",
+	"June": "Tháng 6",
+	"JWT Expiration": "JWT Hết hạn",
+	"JWT Token": "Token JWT",
+	"Keep Alive": "Giữ kết nối",
+	"Keyboard shortcuts": "Phím tắt",
+	"Knowledge": "Kiến thức",
+	"Knowledge created successfully.": "",
+	"Knowledge deleted successfully.": "",
+	"Knowledge reset successfully.": "",
+	"Knowledge updated successfully": "",
+	"Landing Page Mode": "",
+	"Language": "Ngôn ngữ",
+	"large language models, locally.": "các mô hình ngôn ngữ lớn, mang tính địa phương",
+	"Last Active": "Truy cập gần nhất",
+	"Last Modified": "Lần sửa gần nhất",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Light": "Sáng",
+	"Listening...": "Đang nghe...",
+	"LLMs can make mistakes. Verify important information.": "Hệ thống có thể tạo ra nội dung không chính xác hoặc sai. Hãy kiểm chứng kỹ lưỡng thông tin trước khi tiếp nhận và sử dụng.",
+	"Local Models": "",
+	"Lost": "",
+	"LTR": "LTR",
+	"Made by OpenWebUI Community": "Được tạo bởi Cộng đồng OpenWebUI",
+	"Make sure to enclose them with": "Hãy chắc chắn bao quanh chúng bằng",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "Đảm bảo xuất tệp Workflow.json đúng format API của ComfyUI.",
+	"Manage": "Quản lý",
+	"Manage Arena Models": "",
+	"Manage Models": "Quản lý mô hình",
+	"Manage Ollama Models": "Quản lý mô hình với Ollama",
+	"Manage Pipelines": "Quản lý Pipelines",
+	"March": "Tháng 3",
+	"Max Tokens (num_predict)": "Tokens tối đa (num_predict)",
+	"Max Upload Count": "",
+	"Max Upload Size": "",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Tối đa 3 mô hình có thể được tải xuống cùng lúc. Vui lòng thử lại sau.",
+	"May": "Tháng 5",
+	"Memories accessible by LLMs will be shown here.": "Memory có thể truy cập bởi LLMs sẽ hiển thị ở đây.",
+	"Memory": "Memory",
+	"Memory added successfully": "Memory đã được thêm thành công",
+	"Memory cleared successfully": "Memory đã bị xóa",
+	"Memory deleted successfully": "Memory đã bị loại bỏ",
+	"Memory updated successfully": "Memory đã cập nhật thành công",
+	"Merge Responses": "Hợp nhất các phản hồi",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Tin nhắn bạn gửi sau khi tạo liên kết sẽ không được chia sẻ. Người dùng có URL sẽ có thể xem cuộc trò chuyện được chia sẻ.",
+	"Min P": "",
+	"Minimum Score": "Score tối thiểu",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "MMMM DD, YYYY",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "Mô hình '{{modelName}}' đã được tải xuống thành công.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "Mô hình '{{modelTag}}' đã có trong hàng đợi để tải xuống.",
+	"Model {{modelId}} not found": "Không tìm thấy Mô hình {{modelId}}",
+	"Model {{modelName}} is not vision capable": "Model {{modelName}} không có khả năng nhìn",
+	"Model {{name}} is now {{status}}": "Model {{name}} bây giờ là {{status}}",
+	"Model {{name}} is now at the top": "",
+	"Model accepts image inputs": "",
+	"Model created successfully!": "Model đã được tạo thành công",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Đường dẫn hệ thống tệp mô hình được phát hiện. Tên viết tắt mô hình là bắt buộc để cập nhật, không thể tiếp tục.",
+	"Model ID": "ID mẫu",
+	"Model Name": "",
+	"Model not selected": "Chưa chọn Mô hình",
+	"Model Params": "Mô hình Params",
+	"Model updated successfully": "Model đã được cập nhật thành công",
+	"Model Whitelisting": "Whitelist mô hình",
+	"Model(s) Whitelisted": "các mô hình được cho vào danh sách Whitelist",
+	"Modelfile Content": "Nội dung Tệp Mô hình",
+	"Models": "Mô hình",
+	"more": "",
+	"More": "Thêm",
+	"Move to Top": "",
+	"Name": "Tên",
+	"Name your model": "Tên model",
+	"New Chat": "Tạo chat mới",
+	"New folder": "",
+	"New Password": "Mật khẩu mới",
+	"No content found": "",
+	"No content to speak": "Không có nội dung để nói",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "Chưa có tệp nào được chọn",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "",
+	"No knowledge found": "",
+	"No models found": "",
+	"No results found": "Không tìm thấy kết quả",
+	"No search query generated": "Không có truy vấn tìm kiếm nào được tạo ra",
+	"No source available": "Không có nguồn",
+	"No valves to update": "Chưa có valves nào được cập nhật",
+	"None": "Không ai",
+	"Not factually correct": "Không chính xác so với thực tế",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Lưu ý: Nếu bạn đặt điểm (Score) tối thiểu thì tìm kiếm sẽ chỉ trả về những tài liệu có điểm lớn hơn hoặc bằng điểm tối thiểu.",
+	"Notes": "",
+	"Notifications": "Thông báo trên máy tính (Notification)",
+	"November": "Tháng 11",
+	"num_gpu (Ollama)": "",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "",
+	"October": "Tháng 10",
+	"Off": "Tắt",
+	"Okay, Let's Go!": "Được rồi, Bắt đầu thôi!",
+	"OLED Dark": "OLED Dark",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "API Ollama bị vô hiệu hóa",
+	"Ollama API is disabled": "Ollama API đang bị vô hiệu hóa",
+	"Ollama Version": "Phiên bản Ollama",
+	"On": "Bật",
+	"Only": "Only",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "Chỉ ký tự số và gạch nối được phép trong chuỗi lệnh.",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Rất tiếc! URL dường như không hợp lệ. Vui lòng kiểm tra lại và thử lại.",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Rất tiếc! Bạn đang sử dụng một phương thức không được hỗ trợ (chỉ dành cho frontend). Vui lòng cung cấp phương thức cho WebUI từ phía backend.",
+	"Open file": "",
+	"Open in full screen": "",
+	"Open new chat": "Mở nội dung chat mới",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Phiên bản Open WebUI (v{{OPEN_WEBUI_VERSION}}) hiện thấp hơn phiên bản bắt buộc (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "API OpenAI",
+	"OpenAI API Config": "Cấu hình API OpenAI",
+	"OpenAI API Key is required.": "Bắt buộc nhập API OpenAI Key.",
+	"OpenAI URL/Key required.": "Yêu cầu URL/Key API OpenAI.",
+	"or": "hoặc",
+	"Other": "Khác",
+	"OUTPUT": "",
+	"Output format": "",
+	"Overview": "",
+	"page": "",
+	"Password": "Mật khẩu",
+	"PDF document (.pdf)": "Tập tin PDF (.pdf)",
+	"PDF Extract Images (OCR)": "Trích xuất ảnh từ PDF (OCR)",
+	"pending": "đang chờ phê duyệt",
+	"Permission denied when accessing media devices": "Quyền truy cập các thiết bị đa phương tiện bị từ chối",
+	"Permission denied when accessing microphone": "Quyền truy cập micrô bị từ chối",
+	"Permission denied when accessing microphone: {{error}}": "Quyền truy cập micrô bị từ chối: {{error}}",
+	"Personalization": "Cá nhân hóa",
+	"Pin": "Ghim lại",
+	"Pinned": "Đã ghim",
+	"Pipeline deleted successfully": "Pipeline đã bị xóa",
+	"Pipeline downloaded successfully": "Pipeline đã được tải về thành công",
+	"Pipelines": "",
+	"Pipelines Not Detected": "Chưa tìm thấy Pipelines",
+	"Pipelines Valves": "",
+	"Plain text (.txt)": "Văn bản thô (.txt)",
+	"Playground": "Thử nghiệm (Playground)",
+	"Please carefully review the following warnings:": "Vui lòng xem xét cẩn thận các cảnh báo sau:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "",
+	"Please select a reason": "",
+	"Positive attitude": "Thái độ tích cực",
+	"Previous 30 days": "30 ngày trước",
+	"Previous 7 days": "7 ngày trước",
+	"Profile Image": "Ảnh đại diện",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (ví dụ: Hãy kể cho tôi một sự thật thú vị về Đế chế La Mã)",
+	"Prompt Content": "Nội dung prompt",
+	"Prompt suggestions": "Gợi ý prompt",
+	"Prompts": "Prompt",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Tải \"{{searchValue}}\" từ Ollama.com",
+	"Pull a model from Ollama.com": "Tải mô hình từ Ollama.com",
+	"Query Params": "Tham số Truy vấn",
+	"RAG Template": "Mẫu prompt cho RAG",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "Đọc ra loa",
+	"Record voice": "Ghi âm",
+	"Redirecting you to OpenWebUI Community": "Đang chuyển hướng bạn đến Cộng đồng OpenWebUI",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Hãy coi bản thân mình như \"Người dùng\" (ví dụ: \"Người dùng đang học Tiếng Tây Ban Nha\")",
+	"References from": "",
+	"Refused when it shouldn't have": "Từ chối trả lời mà nhẽ không nên làm vậy",
+	"Regenerate": "Tạo sinh lại câu trả lời",
+	"Release Notes": "Mô tả những cập nhật mới",
+	"Relevance": "",
+	"Remove": "Xóa",
+	"Remove Model": "Xóa model",
+	"Rename": "Đổi tên",
+	"Repeat Last N": "Repeat Last N",
+	"Request Mode": "Request Mode",
+	"Reranking Model": "Reranking Model",
+	"Reranking model disabled": "Reranking model disabled",
+	"Reranking model set to \"{{reranking_model}}\"": "Reranking model được đặt thành \"{{reranking_model}}\"",
+	"Reset": "Xóa toàn bộ",
+	"Reset Upload Directory": "Xóa toàn bộ thư mục Upload",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "Tự động Sao chép Phản hồi vào clipboard",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Không thể kích hoạt thông báo vì trang web không cấp quyền. Vui lòng truy cập cài đặt trình duyệt của bạn để cấp quyền cần thiết.",
+	"Response splitting": "",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "Vai trò",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "RTL",
+	"Run": "Thực hiện",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Chạy Llama 2, Code Llama và các mô hình khác. Tùy chỉnh hoặc mô hình riêng của bạn.",
+	"Running": "Đang chạy",
+	"Save": "Lưu",
+	"Save & Create": "Lưu & Tạo",
+	"Save & Update": "Lưu & Cập nhật",
+	"Save As Copy": "",
+	"Save Tag": "Lưu Thẻ",
+	"Saved": "",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Không còn hỗ trợ lưu trữ lịch sử chat trực tiếp vào bộ nhớ trình duyệt của bạn. Vui lòng dành thời gian để tải xuống và xóa lịch sử chat của bạn bằng cách nhấp vào nút bên dưới. Đừng lo lắng, bạn có thể dễ dàng nhập lại lịch sử chat của mình vào backend thông qua",
+	"Scroll to bottom when switching between branches": "Cuộn xuống dưới cùng khi chuyển đổi giữa các nhánh",
+	"Search": "Tìm kiếm",
+	"Search a model": "Tìm model",
+	"Search Chats": "Tìm kiếm các cuộc Chat",
+	"Search Collection": "",
+	"search for tags": "",
+	"Search Functions": "Tìm kiếm Functions",
+	"Search Knowledge": "",
+	"Search Models": "Tìm model",
+	"Search Prompts": "Tìm prompt",
+	"Search Query Generation Prompt": "Prompt tạo câu truy vấn, tìm kiếm",
+	"Search Result Count": "Số kết quả tìm kiếm",
+	"Search Tools": "Tìm kiếm Tools",
+	"SearchApi API Key": "",
+	"SearchApi Engine": "",
+	"Searched {{count}} sites_other": "Đã tìm thấy {{count}} trang web",
+	"Searching \"{{searchQuery}}\"": "Đang tìm \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searxng Query URL": "URL truy vấn Searxng",
+	"See readme.md for instructions": "Xem readme.md để biết hướng dẫn",
+	"See what's new": "Xem những cập nhật mới",
+	"Seed": "Seed",
+	"Select a base model": "Chọn một base model",
+	"Select a engine": "Chọn dịch vụ",
+	"Select a file to view or drag and drop a file to upload": "",
+	"Select a function": "Chọn function",
+	"Select a model": "Chọn mô hình",
+	"Select a pipeline": "Chọn một quy trình",
+	"Select a pipeline url": "Chọn url quy trình",
+	"Select a tool": "Chọn tool",
+	"Select an Ollama instance": "Chọn một thực thể Ollama",
+	"Select Engine": "Chọn Engine",
+	"Select Knowledge": "",
+	"Select model": "Chọn model",
+	"Select only one model to call": "Chọn model để gọi",
+	"Selected model(s) do not support image inputs": "Model được lựa chọn không hỗ trợ đầu vào là hình ảnh",
+	"Semantic distance to query": "",
+	"Send": "Gửi",
+	"Send a Message": "Gửi yêu cầu",
+	"Send message": "Gửi yêu cầu",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"September": "Tháng 9",
+	"Serper API Key": "Khóa API Serper",
+	"Serply API Key": "",
+	"Serpstack API Key": "Khóa API Serpstack",
+	"Server connection verified": "Kết nối máy chủ đã được xác minh",
+	"Set as default": "Đặt làm mặc định",
+	"Set CFG Scale": "",
+	"Set Default Model": "Đặt Mô hình Mặc định",
+	"Set embedding model (e.g. {{model}})": "Thiết lập mô hình embedding (ví dụ: {{model}})",
+	"Set Image Size": "Đặt Kích thước ảnh",
+	"Set reranking model (e.g. {{model}})": "Thiết lập mô hình reranking (ví dụ: {{model}})",
+	"Set Sampler": "",
+	"Set Scheduler": "",
+	"Set Steps": "Đặt Số Bước",
+	"Set Task Model": "Đặt Mô hình Tác vụ",
+	"Set Voice": "Đặt Giọng nói",
+	"Set whisper model": "",
+	"Settings": "Cài đặt",
+	"Settings saved successfully!": "Cài đặt đã được lưu thành công!",
+	"Share": "Chia sẻ",
+	"Share Chat": "Chia sẻ Chat",
+	"Share to OpenWebUI Community": "Chia sẻ đến Cộng đồng OpenWebUI",
+	"short-summary": "tóm tắt ngắn",
+	"Show": "Hiển thị",
+	"Show Admin Details in Account Pending Overlay": "Hiển thị thông tin của Quản trị viên trên màn hình hiển thị Tài khoản đang chờ xử lý",
+	"Show Model": "Hiện mô hình",
+	"Show shortcuts": "Hiển thị phím tắt",
+	"Show your support!": "Thể hiện sự ủng hộ của bạn!",
+	"Showcased creativity": "Thể hiện sự sáng tạo",
+	"Sign in": "Đăng nhập",
+	"Sign in to {{WEBUI_NAME}}": "",
+	"Sign Out": "Đăng xuất",
+	"Sign up": "Đăng ký",
+	"Sign up to {{WEBUI_NAME}}": "",
+	"Signing in to {{WEBUI_NAME}}": "",
+	"Source": "Nguồn",
+	"Speech Playback Speed": "",
+	"Speech recognition error: {{error}}": "Lỗi nhận dạng giọng nói: {{error}}",
+	"Speech-to-Text Engine": "Công cụ Nhận dạng Giọng nói",
+	"Stop": "",
+	"Stop Sequence": "Trình tự Dừng",
+	"Stream Chat Response": "",
+	"STT Model": "",
+	"STT Settings": "Cài đặt Nhận dạng Giọng nói",
+	"Subtitle (e.g. about the Roman Empire)": "Phụ đề (ví dụ: về Đế chế La Mã)",
+	"Success": "Thành công",
+	"Successfully updated.": "Đã cập nhật thành công.",
+	"Suggested": "Gợi ý một số mẫu prompt",
+	"Support": "Hỗ trợ",
+	"Support this plugin:": "Hỗ trợ plugin này:",
+	"Sync directory": "",
+	"System": "Hệ thống",
+	"System Instructions": "",
+	"System Prompt": "Prompt Hệ thống (System Prompt)",
+	"Tags": "Thẻ",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "Chạm để ngừng",
+	"Tavily API Key": "",
+	"Tell us more:": "Hãy cho chúng tôi hiểu thêm về chất lượng của câu trả lời:",
+	"Temperature": "Mức độ sáng tạo",
+	"Template": "Mẫu",
+	"Temporary Chat": "Chat nháp",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "Công cụ Chuyển Văn bản thành Giọng nói",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Cám ơn bạn đã gửi phản hồi!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Các nhà phát triển đằng sau plugin này là những tình nguyện viên nhiệt huyết của cộng đồng. Nếu bạn thấy plugin này hữu ích, vui lòng cân nhắc đóng góp cho sự phát triển của nó.",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Điểm (score) phải có giá trị từ 0,0 (0%) đến 1,0 (100%).",
+	"Theme": "Chủ đề",
+	"Thinking...": "Đang suy luận...",
+	"This action cannot be undone. Do you wish to continue?": "Hành động này không thể được hoàn tác. Bạn có muốn tiếp tục không?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Điều này đảm bảo rằng các nội dung chat có giá trị của bạn được lưu an toàn vào cơ sở dữ liệu backend của bạn. Cảm ơn bạn!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Đây là tính năng thử nghiệm, có thể không hoạt động như mong đợi và có thể thay đổi bất kỳ lúc nào.",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "Chat này sẽ bị xóa",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
+	"Thorough explanation": "Giải thích kỹ lưỡng",
+	"Tika": "",
+	"Tika Server URL required.": "Bắt buộc phải nhập URL cho Tika Server ",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Mẹo: Cập nhật nhiều khe biến liên tiếp bằng cách nhấn phím tab trong đầu vào trò chuyện sau mỗi việc thay thế.",
+	"Title": "Tiêu đề",
+	"Title (e.g. Tell me a fun fact)": "Tiêu đề (ví dụ: Hãy kể cho tôi một sự thật thú vị về...)",
+	"Title Auto-Generation": "Tự động Tạo Tiêu đề",
+	"Title cannot be an empty string.": "Tiêu đề không được phép bỏ trống",
+	"Title Generation Prompt": "Prompt tạo tiêu đề",
+	"To access the available model names for downloading,": "Để truy cập các tên mô hình có sẵn để tải xuống,",
+	"To access the GGUF models available for downloading,": "Để truy cập các mô hình GGUF có sẵn để tải xuống,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Để truy cập vui lòng liên hệ với quản trị viên.",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "",
+	"to chat input.": "đến đầu vào trò chuyện.",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Để chọn các tác vụ, bạn phải thêm chúng vào workspace \"Functions\" trước.",
+	"To select filters here, add them to the \"Functions\" workspace first.": "Để chọn các filters, bạn phải thêm chúng vào workspace \"Functions\" trước.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "Để chọn các tookits, bạn phải thêm chúng vào workspace \"Tools\" trước.",
+	"Toast notifications for new updates": "",
+	"Today": "Hôm nay",
+	"Toggle settings": "Bật/tắt cài đặt",
+	"Toggle sidebar": "Bật/tắt thanh bên",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "Tool đã được tạo thành công",
+	"Tool deleted successfully": "Tool đã bị xóa",
+	"Tool imported successfully": "Tool đã được nạp thành công",
+	"Tool updated successfully": "Tool đã được cập nhật thành công",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Mô tả Toolkit (ví dụ: Toolkit để thực hiện các hoạt động khác nhau)",
+	"Toolkit ID (e.g. my_toolkit)": "",
+	"Toolkit Name (e.g. My ToolKit)": "",
+	"Tools": "",
+	"Tools are a function calling system with arbitrary code execution": "Tools là một hệ thống gọi function với việc thực thi mã tùy ý",
+	"Tools have a function calling system that allows arbitrary code execution": "Các Tools có hệ thống gọi function cho phép thực thi mã tùy ý",
+	"Tools have a function calling system that allows arbitrary code execution.": "Các Tools có hệ thống gọi function cho phép thực thi mã tùy ý.",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Gặp vấn đề khi truy cập Ollama?",
+	"TTS Model": "",
+	"TTS Settings": "Cài đặt Chuyển văn bản thành Giọng nói",
+	"TTS Voice": "",
+	"Type": "Kiểu",
+	"Type Hugging Face Resolve (Download) URL": "Nhập URL Hugging Face Resolve (Tải xuống)",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "Ồ! Đã xảy ra sự cố khi kết nối với {{provider}}.",
+	"UI": "Giao diện",
+	"Unpin": "Bỏ ghim",
+	"Untagged": "",
+	"Update": "Cập nhật",
+	"Update and Copy Link": "Cập nhật và sao chép link",
+	"Update for the latest features and improvements.": "",
+	"Update password": "Cập nhật mật khẩu",
+	"Updated": "",
+	"Updated at": "Cập nhật lúc",
+	"Updated At": "",
+	"Upload": "",
+	"Upload a GGUF model": "Tải lên mô hình GGUF",
+	"Upload directory": "",
+	"Upload files": "",
+	"Upload Files": "Tải tệp lên máy chủ",
+	"Upload Pipeline": "",
+	"Upload Progress": "Tiến trình tải tệp lên hệ thống",
+	"URL Mode": "Chế độ URL",
+	"Use '#' in the prompt input to load and include your knowledge.": "",
+	"Use Gravatar": "Sử dụng Gravatar",
+	"Use Initials": "Sử dụng tên viết tắt",
+	"use_mlock (Ollama)": "use_mlock (Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "Người sử dụng",
+	"User": "",
+	"User location successfully retrieved.": "Đã truy xuất thành công vị trí của người dùng.",
+	"User Permissions": "Phân quyền sử dụng",
+	"Users": "Người sử dụng",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "Sử dụng",
+	"Valid time units:": "Đơn vị thời gian hợp lệ:",
+	"Valves": "",
+	"Valves updated": "Cập nhật Valves",
+	"Valves updated successfully": "Đã cập nhật Valves thành công",
+	"variable": "biến",
+	"variable to have them replaced with clipboard content.": "biến để có chúng được thay thế bằng nội dung clipboard.",
+	"Version": "Version",
+	"Version {{selectedVersion}} of {{totalVersions}}": "",
+	"Voice": "Giọng nói",
+	"Voice Input": "",
+	"Warning": "Cảnh báo",
+	"Warning:": "Cảnh báo:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Cảnh báo: Nếu cập nhật hoặc thay đổi embedding model, bạn sẽ cần cập nhật lại tất cả tài liệu.",
+	"Web": "Web",
+	"Web API": "",
+	"Web Loader Settings": "Cài đặt Web Loader",
+	"Web Search": "Tìm kiếm Web",
+	"Web Search Engine": "Chức năng Tìm kiếm Web",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "Cài đặt WebUI",
+	"WebUI will make requests to": "WebUI sẽ thực hiện các yêu cầu đến",
+	"What’s New in": "Thông tin mới về",
+	"Whisper (Local)": "",
+	"Widescreen Mode": "Chế độ màn hình rộng",
+	"Won": "",
+	"Workspace": "Workspace",
+	"Write a prompt suggestion (e.g. Who are you?)": "Hãy viết một prompt (vd: Bạn là ai?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "Viết một tóm tắt trong vòng 50 từ cho [chủ đề hoặc từ khóa].",
+	"Write something...": "",
+	"Yesterday": "Hôm qua",
+	"You": "Bạn",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Bạn có thể cá nhân hóa các tương tác của mình với LLM bằng cách thêm bộ nhớ thông qua nút 'Quản lý' bên dưới, làm cho chúng hữu ích hơn và phù hợp với bạn hơn.",
+	"You cannot clone a base model": "Bạn không thể nhân bản base model",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "Bạn chưa lưu trữ một nội dung chat nào",
+	"You have shared this chat": "Bạn vừa chia sẻ chat này",
+	"You're a helpful assistant.": "Bạn là một trợ lý hữu ích.",
+	"You're now logged in.": "Bạn đã đăng nhập.",
+	"Your account status is currently pending activation.": "Tài khoản của bạn hiện đang ở trạng thái chờ kích hoạt.",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Toàn bộ đóng góp của bạn sẽ được chuyển trực tiếp đến nhà phát triển plugin; Open WebUI không lấy bất kỳ tỷ lệ phần trăm nào. Tuy nhiên, nền tảng được chọn tài trợ có thể có phí riêng.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Cài đặt Youtube Loader"
+}
diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..845d86a7b046efe4dfea82d7b0a27975f51d3aec
--- /dev/null
+++ b/src/lib/i18n/locales/zh-CN/translation.json
@@ -0,0 +1,850 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' 或 '-1' 表示无过期时间。",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(例如 `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(例如 `sh webui.sh --api`)",
+	"(latest)": "(最新版)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}:您不能删除基础模型",
+	"{{user}}'s Chats": "{{user}} 的对话记录",
+	"{{webUIName}} Backend Required": "{{webUIName}} 需要后端服务",
+	"*Prompt node ID(s) are required for image generation": "*图片生成需要 Prompt node ID",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "新版本(v{{LATEST_VERSION}})现已发布。",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "任务模型用于执行生成对话标题和联网搜索查询等任务",
+	"a user": "用户",
+	"About": "关于",
+	"Account": "账号",
+	"Account Activation Pending": "账号待激活",
+	"Accurate information": "提供的信息很准确",
+	"Actions": "自动化",
+	"Active Users": "当前在线用户",
+	"Add": "添加",
+	"Add a model id": "添加一个模型 ID",
+	"Add a short description about what this model does": "添加有关该模型能力的简短描述",
+	"Add a short title for this prompt": "为此提示词添加一个简短的标题",
+	"Add a tag": "添加标签",
+	"Add Arena Model": "添加竞技场模型",
+	"Add Content": "添加内容",
+	"Add content here": "在此添加内容",
+	"Add custom prompt": "添加自定义提示词",
+	"Add Files": "添加文件",
+	"Add Memory": "添加记忆",
+	"Add Model": "添加模型",
+	"Add Tag": "添加标签",
+	"Add Tags": "添加标签",
+	"Add text content": "添加文本内容",
+	"Add User": "添加用户",
+	"Adjusting these settings will apply changes universally to all users.": "调整这些设置将会对所有用户应用更改。",
+	"admin": "管理员",
+	"Admin": "管理员联系方式",
+	"Admin Panel": "管理员面板",
+	"Admin Settings": "管理员设置",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "管理员拥有所有工具的访问权限;用户则需在工作空间中为每个模型单独分配工具。",
+	"Advanced Parameters": "高级参数",
+	"Advanced Params": "高级参数",
+	"All chats": "所有对话",
+	"All Documents": "所有文档",
+	"Allow Chat Deletion": "允许删除对话记录",
+	"Allow Chat Editing": "允许编辑对话记录",
+	"Allow non-local voices": "允许调用非本地音色",
+	"Allow Temporary Chat": "允许临时对话",
+	"Allow User Location": "允许获取您的位置",
+	"Allow Voice Interruption in Call": "允许通话中的打断语音",
+	"alphanumeric characters and hyphens": "字母数字字符和连字符",
+	"Already have an account?": "已经拥有账号了?",
+	"an assistant": "AI模型",
+	"and": "和",
+	"and {{COUNT}} more": "还有 {{COUNT}} 个",
+	"and create a new shared link.": "并创建一个新的分享链接。",
+	"API Base URL": "API 基础地址",
+	"API Key": "API 密钥",
+	"API Key created.": "API 密钥已创建。",
+	"API keys": "API 密钥",
+	"April": "四月",
+	"Archive": "归档",
+	"Archive All Chats": "归档所有对话记录",
+	"Archived Chats": "已归档对话",
+	"are allowed - Activate this command by typing": "允许 - 通过输入来激活这个命令",
+	"Are you sure?": "是否确定?",
+	"Arena Models": "启用竞技场匿名评价模型",
+	"Artifacts": "Artifacts",
+	"Ask a question": "提问",
+	"Assistant": "AI模型",
+	"Attach file": "添加文件",
+	"Attention to detail": "注重细节",
+	"Audio": "语音",
+	"August": "八月",
+	"Auto-playback response": "自动念出回复内容",
+	"Automatic1111": "Automatic1111",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Api 鉴权字符串",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 基础地址",
+	"AUTOMATIC1111 Base URL is required.": "需要 AUTOMATIC1111 基础地址。",
+	"Available list": "可用列表",
+	"available!": "版本可用!",
+	"Azure AI Speech": "Azure AI Speech",
+	"Azure Region": "Azure Region",
+	"Back": "返回",
+	"Bad Response": "点踩此回答",
+	"Banners": "公告横幅",
+	"Base Model (From)": "基础模型 (来自)",
+	"Batch Size (num_batch)": "批大小 (num_batch)",
+	"before": "对话",
+	"Being lazy": "懒惰",
+	"Brave Search API Key": "Brave Search API 密钥",
+	"Bypass SSL verification for Websites": "绕过网站的 SSL 验证",
+	"Call": "呼叫",
+	"Call feature is not supported when using Web STT engine": "使用 Web 语音转文字引擎时不支持呼叫功能。",
+	"Camera": "摄像头",
+	"Cancel": "取消",
+	"Capabilities": "能力",
+	"Change Password": "更改密码",
+	"Character": "字符",
+	"Chat": "对话",
+	"Chat Background Image": "对话背景图片",
+	"Chat Bubble UI": "气泡样式对话",
+	"Chat Controls": "对话高级设置",
+	"Chat direction": "对话样式方向",
+	"Chat Overview": "对话概述",
+	"Chat Tags Auto-Generation": "自动生成对话标签",
+	"Chats": "对话",
+	"Check Again": "刷新重试",
+	"Check for updates": "检查更新",
+	"Checking for updates...": "正在检查更新...",
+	"Choose a model before saving...": "保存前选择一个模型...",
+	"Chunk Overlap": "块重叠 (Chunk Overlap)",
+	"Chunk Params": "块参数 (Chunk Params)",
+	"Chunk Size": "块大小 (Chunk Size)",
+	"Citation": "引文",
+	"Clear memory": "清除记忆",
+	"Click here for help.": "点击这里获取帮助。",
+	"Click here to": "单击",
+	"Click here to download user import template file.": "单击此处下载用户导入所需的模板文件。",
+	"Click here to learn more about faster-whisper and see the available models.": "点击此处了解更多关于faster-whisper的信息,并查看可用的模型。",
+	"Click here to select": "点击这里选择",
+	"Click here to select a csv file.": "单击此处选择 csv 文件。",
+	"Click here to select a py file.": "单击此处选择 py 文件。",
+	"Click here to upload a workflow.json file.": "单击此处上传 workflow.json 文件。",
+	"click here.": "点击这里。",
+	"Click on the user role button to change a user's role.": "点击角色前方的组别按钮以更改用户所属权限组。",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "写入剪贴板时被拒绝。请检查浏览器设置,授予必要权限。",
+	"Clone": "复制",
+	"Close": "关闭",
+	"Code execution": "代码执行",
+	"Code formatted successfully": "代码格式化成功",
+	"Collection": "文件集",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI 基础地址",
+	"ComfyUI Base URL is required.": "ComfyUI 基础地址为必需填写。",
+	"ComfyUI Workflow": "ComfyUI Workflow",
+	"ComfyUI Workflow Nodes": "ComfyUI Workflow Nodes",
+	"Command": "命令",
+	"Completions": "续写",
+	"Concurrent Requests": "并发请求",
+	"Confirm": "确认",
+	"Confirm Password": "确认密码",
+	"Confirm your action": "确定吗?",
+	"Connections": "外部连接",
+	"Contact Admin for WebUI Access": "请联系管理员以获取访问权限",
+	"Content": "内容",
+	"Content Extraction": "内容提取",
+	"Context Length": "上下文长度",
+	"Continue Response": "继续生成",
+	"Continue with {{provider}}": "使用 {{provider}} 继续",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "控制消息文本如何拆分以用于 TTS 请求。“Punctuation”拆分为句子,“paragraphs”拆分为段落,“none”将消息保留为单个字符串。",
+	"Controls": "对话高级设置",
+	"Copied": "已复制",
+	"Copied shared chat URL to clipboard!": "已复制此对话分享链接至剪贴板!",
+	"Copied to clipboard": "已复制到剪贴板",
+	"Copy": "复制",
+	"Copy last code block": "复制最后一个代码块中的代码",
+	"Copy last response": "复制最后一次回复内容",
+	"Copy Link": "复制链接",
+	"Copy to clipboard": "复制到剪贴板",
+	"Copying to clipboard was successful!": "成功复制到剪贴板!",
+	"Create a model": "创建一个模型",
+	"Create Account": "创建账号",
+	"Create Knowledge": "创建知识",
+	"Create new key": "创建新密钥",
+	"Create new secret key": "创建新安全密钥",
+	"Created at": "创建于",
+	"Created At": "创建于",
+	"Created by": "作者",
+	"CSV Import": "通过 CSV 文件导入",
+	"Current Model": "当前模型",
+	"Current Password": "当前密码",
+	"Custom": "自定义",
+	"Customize models for a specific purpose": "定制专用目的模型",
+	"Dark": "暗色",
+	"Dashboard": "仪表板",
+	"Database": "数据库",
+	"December": "十二月",
+	"Default": "默认",
+	"Default (Open AI)": "默认 (OpenAI)",
+	"Default (SentenceTransformers)": "默认(SentenceTransformers)",
+	"Default Model": "默认模型",
+	"Default model updated": "默认模型已更新",
+	"Default Prompt Suggestions": "默认提示词建议",
+	"Default User Role": "默认用户角色",
+	"Delete": "删除",
+	"Delete a model": "删除一个模型",
+	"Delete All Chats": "删除所有对话记录",
+	"Delete chat": "删除对话记录",
+	"Delete Chat": "删除对话记录",
+	"Delete chat?": "删除对话记录?",
+	"Delete folder?": "删除分组?",
+	"Delete function?": "删除函数?",
+	"Delete prompt?": "删除提示词?",
+	"delete this link": "此处删除这个链接",
+	"Delete tool?": "删除工具?",
+	"Delete User": "删除用户",
+	"Deleted {{deleteModelTag}}": "已删除 {{deleteModelTag}}",
+	"Deleted {{name}}": "已删除 {{name}}",
+	"Description": "描述",
+	"Didn't fully follow instructions": "没有完全遵照指示",
+	"Disabled": "禁用",
+	"Discover a function": "发现更多函数",
+	"Discover a model": "发现更多模型",
+	"Discover a prompt": "发现更多提示词",
+	"Discover a tool": "发现更多工具",
+	"Discover, download, and explore custom functions": "发现、下载并探索更多函数",
+	"Discover, download, and explore custom prompts": "发现、下载并探索更多自定义提示词",
+	"Discover, download, and explore custom tools": "发现、下载并探索更多工具",
+	"Discover, download, and explore model presets": "发现、下载并探索更多模型预设",
+	"Dismissible": "是否可关闭",
+	"Display Emoji in Call": "在通话中显示 Emoji 表情符号",
+	"Display the username instead of You in the Chat": "在对话中显示用户名而不是“你”",
+	"Do not install functions from sources you do not fully trust.": "切勿安装来源不完全可信的函数。",
+	"Do not install tools from sources you do not fully trust.": "切勿安装来源不完全可信的工具。",
+	"Document": "文档",
+	"Documentation": "帮助文档",
+	"Documents": "文档",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "不会与外部建立任何连接,您的数据会安全地存储在本地托管的服务器上。",
+	"Don't have an account?": "没有账号?",
+	"don't install random functions from sources you don't trust.": "切勿随意从不完全可信的来源安装函数。",
+	"don't install random tools from sources you don't trust.": "切勿随意从不完全可信的来源安装工具。",
+	"Don't like the style": "不喜欢这个文风",
+	"Done": "完成",
+	"Download": "下载",
+	"Download canceled": "下载已取消",
+	"Download Database": "下载数据库",
+	"Draw": "平局",
+	"Drop any files here to add to the conversation": "拖动文件到此处以添加到对话中",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "例如 '30s','10m'。有效的时间单位是秒:'s',分:'m',时:'h'。",
+	"Edit": "编辑",
+	"Edit Arena Model": "编辑竞技场模型",
+	"Edit Memory": "编辑记忆",
+	"Edit User": "编辑用户",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "电子邮箱",
+	"Embedding Batch Size": "嵌入层批处理大小 (Embedding Batch Size)",
+	"Embedding Model": "语义向量模型",
+	"Embedding Model Engine": "语义向量模型引擎",
+	"Embedding model set to \"{{embedding_model}}\"": "语义向量模型设置为 \"{{embedding_model}}\"",
+	"Enable Community Sharing": "启用分享至社区",
+	"Enable Message Rating": "启用回复评价",
+	"Enable New Sign Ups": "允许新用户注册",
+	"Enable Web Search": "启用联网搜索",
+	"Enable Web Search Query Generation": "启用生成联网搜索关键词",
+	"Enabled": "启用",
+	"Engine": "引擎",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "确保您的 CSV 文件按以下顺序包含 4 列: 姓名、电子邮箱、密码、角色。",
+	"Enter {{role}} message here": "在此处输入 {{role}} 的对话内容",
+	"Enter a detail about yourself for your LLMs to recall": "输入一个关于你自己的详细信息,方便你的大语言模型记住这些内容",
+	"Enter api auth string (e.g. username:password)": "输入 api 鉴权路径 (例如:username:password)",
+	"Enter Brave Search API Key": "输入 Brave Search API 密钥",
+	"Enter CFG Scale (e.g. 7.0)": "输入 CFG Scale (例如:7.0)",
+	"Enter Chunk Overlap": "输入块重叠 (Chunk Overlap)",
+	"Enter Chunk Size": "输入块大小 (Chunk Size)",
+	"Enter description": "输入简介描述",
+	"Enter Github Raw URL": "输入 Github Raw 地址",
+	"Enter Google PSE API Key": "输入 Google PSE API 密钥",
+	"Enter Google PSE Engine Id": "输入 Google PSE 引擎 ID",
+	"Enter Image Size (e.g. 512x512)": "输入图像分辨率 (例如:512x512)",
+	"Enter language codes": "输入语言代码",
+	"Enter Model ID": "输入模型 ID",
+	"Enter model tag (e.g. {{modelTag}})": "输入模型标签 (例如:{{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "输入步骤数 (Steps) (例如:50)",
+	"Enter Sampler (e.g. Euler a)": "输入 Sampler (例如:Euler a)",
+	"Enter Scheduler (e.g. Karras)": "输入 Scheduler (例如:Karras)",
+	"Enter Score": "输入评分",
+	"Enter SearchApi API Key": "输入 SearchApi API 密钥",
+	"Enter SearchApi Engine": "输入 SearchApi 引擎",
+	"Enter Searxng Query URL": "输入 Searxng 查询地址",
+	"Enter Serper API Key": "输入 Serper API 密钥",
+	"Enter Serply API Key": "输入 Serply API 密钥",
+	"Enter Serpstack API Key": "输入 Serpstack API 密钥",
+	"Enter stop sequence": "输入停止序列 (Stop Sequence)",
+	"Enter system prompt": "输入系统提示词 (Prompt)",
+	"Enter Tavily API Key": "输入 Tavily API 密钥",
+	"Enter Tika Server URL": "输入 Tika 服务器地址",
+	"Enter Top K": "输入 Top K",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "输入地址 (例如:http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "输入地址 (例如:http://localhost:11434)",
+	"Enter Your Email": "输入您的电子邮箱",
+	"Enter Your Full Name": "输入您的名称",
+	"Enter your message": "输入您的消息",
+	"Enter Your Password": "输入您的密码",
+	"Enter Your Role": "输入您的权限组",
+	"Error": "错误",
+	"ERROR": "错误",
+	"Evaluations": "竞技场评估",
+	"Exclude": "",
+	"Experimental": "实验性",
+	"Export": "导出",
+	"Export All Chats (All Users)": "导出所有用户对话",
+	"Export chat (.json)": "JSON 文件 (.json)",
+	"Export Chats": "导出对话",
+	"Export Config to JSON File": "导出配置信息至 JSON 文件中",
+	"Export Functions": "导出函数",
+	"Export LiteLLM config.yaml": "导出 LteLLM config.yaml 文件",
+	"Export Models": "导出模型",
+	"Export Prompts": "导出提示词",
+	"Export Tools": "导出工具",
+	"External Models": "外部模型",
+	"Failed to add file.": "添加文件失败。",
+	"Failed to create API Key.": "无法创建 API 密钥。",
+	"Failed to read clipboard contents": "无法读取剪贴板内容",
+	"Failed to update settings": "无法更新设置",
+	"Failed to upload file.": "上传文件失败",
+	"February": "二月",
+	"Feedback History": "反馈历史",
+	"Feel free to add specific details": "欢迎补充具体细节",
+	"File": "文件",
+	"File added successfully.": "文件成功添加",
+	"File content updated successfully.": "文件内容成功更新",
+	"File Mode": "文件模式",
+	"File not found.": "文件未找到。",
+	"File removed successfully.": "文件成功删除",
+	"File size should not exceed {{maxSize}} MB.": "文件大小不应超过 {{maxSize}} MB。",
+	"Files": "文件",
+	"Filter is now globally disabled": "过滤器已全局禁用",
+	"Filter is now globally enabled": "过滤器已全局启用",
+	"Filters": "过滤器",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "检测到指纹伪造:无法使用姓名缩写作为头像。默认使用默认个人形象。",
+	"Fluidly stream large external response chunks": "流畅地传输外部大型响应块数据",
+	"Focus chat input": "聚焦对话输入",
+	"Folder deleted successfully": "分组删除成功",
+	"Folder name cannot be empty": "分组名称不能为空",
+	"Folder name cannot be empty.": "分组名称不能为空。",
+	"Folder name updated successfully": "分组名称更新成功。",
+	"Followed instructions perfectly": "完全按照指示执行",
+	"Form": "手动创建",
+	"Format your variables using brackets like this:": "使用括号格式化你的变量,如下所示:",
+	"Frequency Penalty": "频率惩罚",
+	"Function": "函数",
+	"Function created successfully": "函数创建成功",
+	"Function deleted successfully": "函数删除成功",
+	"Function Description (e.g. A filter to remove profanity from text)": "函数描述(例如:一个用于从文本中过滤脏话的过滤器)",
+	"Function ID (e.g. my_filter)": "函数 ID (例如:my_filter)",
+	"Function is now globally disabled": "函数全局已禁用",
+	"Function is now globally enabled": "函数全局已启用",
+	"Function Name (e.g. My Filter)": "函数名称(例如:我的过滤器)",
+	"Function updated successfully": "函数更新成功",
+	"Functions": "函数",
+	"Functions allow arbitrary code execution": "注意:函数有权执行任意代码",
+	"Functions allow arbitrary code execution.": "注意:函数有权执行任意代码。",
+	"Functions imported successfully": "函数导入成功",
+	"General": "通用",
+	"General Settings": "通用设置",
+	"Generate Image": "生成图像",
+	"Generating search query": "生成搜索查询",
+	"Generation Info": "生成信息",
+	"Get up and running with": "启动并运行",
+	"Global": "全局",
+	"Good Response": "点赞此回答",
+	"Google PSE API Key": "Google PSE API 密钥",
+	"Google PSE Engine Id": "Google PSE 引擎 ID",
+	"h:mm a": "HH:mm",
+	"Haptic Feedback": "震动反馈",
+	"has no conversations.": "没有对话。",
+	"Hello, {{name}}": "您好,{{name}}",
+	"Help": "帮助",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "隐藏",
+	"Hide Model": "隐藏",
+	"How can I help you today?": "有什么我能帮您的吗?",
+	"Hybrid Search": "混合搜索",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "我已阅读并理解我的行为所带来的影响,明白执行任意代码所涉及的风险。且我已验证代码来源可信度。",
+	"ID": "ID",
+	"Image Generation (Experimental)": "图像生成(实验性)",
+	"Image Generation Engine": "图像生成引擎",
+	"Image Settings": "图像设置",
+	"Images": "图像",
+	"Import Chats": "导入对话记录",
+	"Import Config from JSON File": "导入 JSON 文件中的配置信息",
+	"Import Functions": "导入函数",
+	"Import Models": "导入模型",
+	"Import Prompts": "导入提示词",
+	"Import Tools": "导入工具",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "运行 stable-diffusion-webui 时包含 `--api-auth` 标志",
+	"Include `--api` flag when running stable-diffusion-webui": "运行 stable-diffusion-webui 时包含 `--api` 标志",
+	"Info": "信息",
+	"Input commands": "输入命令",
+	"Install from Github URL": "从 Github URL 安装",
+	"Instant Auto-Send After Voice Transcription": "语音转录文字后即时自动发送",
+	"Interface": "界面",
+	"Invalid file format.": "无效文件格式。",
+	"Invalid Tag": "无效标签",
+	"January": "一月",
+	"join our Discord for help.": "加入我们的 Discord 寻求帮助。",
+	"JSON": "JSON",
+	"JSON Preview": "JSON 预览",
+	"July": "七月",
+	"June": "六月",
+	"JWT Expiration": "JWT 过期",
+	"JWT Token": "JWT 令牌",
+	"Keep Alive": "保持活动",
+	"Keyboard shortcuts": "键盘快捷键",
+	"Knowledge": "知识库",
+	"Knowledge created successfully.": "知识成功创建",
+	"Knowledge deleted successfully.": "知识成功删除",
+	"Knowledge reset successfully.": "知识成功重置",
+	"Knowledge updated successfully": "知识成功更新",
+	"Landing Page Mode": "默认主页样式",
+	"Language": "语言",
+	"large language models, locally.": "本地大语言模型",
+	"Last Active": "最后在线时间",
+	"Last Modified": "最后修改时间",
+	"Leaderboard": "排行榜",
+	"Leave empty for unlimited": "留空表示无限制",
+	"Leave empty to include all models or select specific models": "留空表示包含所有模型或请选择模型",
+	"Leave empty to use the default prompt, or enter a custom prompt": "留空以使用默认提示词,或输入自定义提示词。",
+	"Light": "浅色",
+	"Listening...": "正在倾听...",
+	"LLMs can make mistakes. Verify important information.": "大语言模型可能会生成误导性错误信息,请对关键信息加以验证。",
+	"Local Models": "本地模型",
+	"Lost": "落败",
+	"LTR": "从左至右",
+	"Made by OpenWebUI Community": "由 OpenWebUI 社区制作",
+	"Make sure to enclose them with": "确保将它们包含在内",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "确保从 ComfyUI 导出 API 格式的 workflow.json 文件。",
+	"Manage": "管理",
+	"Manage Arena Models": "管理竞技场模型",
+	"Manage Models": "管理模型",
+	"Manage Ollama Models": "管理 Ollama 模型",
+	"Manage Pipelines": "管理 Pipeline",
+	"March": "三月",
+	"Max Tokens (num_predict)": "最多 Token (num_predict)",
+	"Max Upload Count": "最大上传数量",
+	"Max Upload Size": "最大上传大小",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "最多可以同时下载 3 个模型,请稍后重试。",
+	"May": "五月",
+	"Memories accessible by LLMs will be shown here.": "大语言模型可访问的记忆将在此显示。",
+	"Memory": "记忆",
+	"Memory added successfully": "记忆添加成功",
+	"Memory cleared successfully": "记忆清除成功",
+	"Memory deleted successfully": "记忆删除成功",
+	"Memory updated successfully": "记忆更新成功",
+	"Merge Responses": "合并回复",
+	"Message rating should be enabled to use this feature": "要使用此功能,应先启用回复评价功能",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "创建链接后发送的消息不会被共享。具有 URL 的用户将能够查看共享对话。",
+	"Min P": "Min P",
+	"Minimum Score": "最低分",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "YYYY年 MM月 DD日",
+	"MMMM DD, YYYY HH:mm": "YYYY年 MM月 DD日 HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "YYYY年 MM月 DD日 hh:mm:ss A",
+	"Model": "模型",
+	"Model '{{modelName}}' has been successfully downloaded.": "模型'{{modelName}}'已成功下载。",
+	"Model '{{modelTag}}' is already in queue for downloading.": "模型'{{modelTag}}'已在下载队列中。",
+	"Model {{modelId}} not found": "未找到模型 {{modelId}}",
+	"Model {{modelName}} is not vision capable": "模型 {{modelName}} 不支持视觉能力",
+	"Model {{name}} is now {{status}}": "模型 {{name}} 现在是 {{status}}",
+	"Model {{name}} is now at the top": "模型 {{name}} 已移动到顶部",
+	"Model accepts image inputs": "模型接受图像输入",
+	"Model created successfully!": "模型创建成功!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "检测到模型文件系统路径,无法继续进行。更新操作需要提供模型简称。",
+	"Model ID": "模型 ID",
+	"Model Name": "模型名称",
+	"Model not selected": "未选择模型",
+	"Model Params": "模型参数",
+	"Model updated successfully": "模型更新成功",
+	"Model Whitelisting": "白名单模型",
+	"Model(s) Whitelisted": "模型已加入白名单",
+	"Modelfile Content": "模型文件内容",
+	"Models": "模型",
+	"more": "更多",
+	"More": "更多",
+	"Move to Top": "移动到顶部",
+	"Name": "名称",
+	"Name your model": "为您的模型命名",
+	"New Chat": "新对话",
+	"New folder": "新建分组",
+	"New Password": "新密码",
+	"No content found": "未发现内容",
+	"No content to speak": "没有内容可朗读",
+	"No distance available": "没有可用距离",
+	"No feedbacks found": "暂无任何反馈",
+	"No file selected": "未选中文件",
+	"No files found.": "未找到文件。",
+	"No HTML, CSS, or JavaScript content found.": "未找到 HTML、CSS 或 JavaScript 内容。",
+	"No knowledge found": "未找到知识",
+	"No models found": "未找到任何模型",
+	"No results found": "未找到结果",
+	"No search query generated": "未生成搜索查询",
+	"No source available": "没有可用来源",
+	"No valves to update": "没有需要更新的值",
+	"None": "无",
+	"Not factually correct": "事实并非如此",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "注意:如果设置了最低分数,搜索只会返回分数大于或等于最低分数的文档。",
+	"Notes": "",
+	"Notifications": "桌面通知",
+	"November": "十一月",
+	"num_gpu (Ollama)": "num_gpu (Ollama)",
+	"num_thread (Ollama)": "num_thread(Ollama)",
+	"OAuth ID": "OAuth ID",
+	"October": "十月",
+	"Off": "关闭",
+	"Okay, Let's Go!": "确认,开始使用!",
+	"OLED Dark": "黑色",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API 已禁用",
+	"Ollama API is disabled": "Ollama API 已禁用",
+	"Ollama Version": "Ollama 版本",
+	"On": "开启",
+	"Only": "仅",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "命令字符串中只允许使用英文字母,数字 (0-9) 以及连字符 (-)。",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "只能编辑文件集,创建一个新的知识库来编辑/添加文件。",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "此链接似乎为无效链接。请检查后重试。",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "稍等!还有文件正在上传。请等待上传完成。",
+	"Oops! There was an error in the previous response.": "糟糕!有一个错误出现在了之前的回复中。",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "你正在使用不被支持的方法(仅运行前端服务)。需要后端提供 WebUI 服务。",
+	"Open file": "打开文件",
+	"Open in full screen": "全屏打开",
+	"Open new chat": "打开新对话",
+	"Open WebUI uses faster-whisper internally.": "Open WebUI 使用内置 faster-whisper。",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "当前 Open WebUI 版本 (v{{OPEN_WEBUI_VERSION}}) 低于所需的版本 (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API 配置",
+	"OpenAI API Key is required.": "需要 OpenAI API 密钥。",
+	"OpenAI URL/Key required.": "需要 OpenAI URL/Key",
+	"or": "或",
+	"Other": "其他",
+	"OUTPUT": "输出",
+	"Output format": "输出格式",
+	"Overview": "概述",
+	"page": "页",
+	"Password": "密码",
+	"PDF document (.pdf)": "PDF 文档 (.pdf)",
+	"PDF Extract Images (OCR)": "PDF 图像处理 (使用 OCR)",
+	"pending": "待激活",
+	"Permission denied when accessing media devices": "申请媒体设备权限被拒绝",
+	"Permission denied when accessing microphone": "申请麦克风权限被拒绝",
+	"Permission denied when accessing microphone: {{error}}": "申请麦克风权限被拒绝:{{error}}",
+	"Personalization": "个性化",
+	"Pin": "置顶",
+	"Pinned": "已置顶",
+	"Pipeline deleted successfully": "Pipeline 删除成功",
+	"Pipeline downloaded successfully": "Pipeline 下载成功",
+	"Pipelines": "Pipeline",
+	"Pipelines Not Detected": "未检测到 Pipeline",
+	"Pipelines Valves": "Pipeline 值",
+	"Plain text (.txt)": "TXT 文档 (.txt)",
+	"Playground": "AI 对话游乐场",
+	"Please carefully review the following warnings:": "请仔细阅读以下警告信息:",
+	"Please enter a prompt": "请输出一个 prompt",
+	"Please fill in all fields.": "请填写所有字段。",
+	"Please select a reason": "请选择原因",
+	"Positive attitude": "积极的态度",
+	"Previous 30 days": "过去 30 天",
+	"Previous 7 days": "过去 7 天",
+	"Profile Image": "用户头像",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "提示(例如:给我讲一个关于罗马帝国的趣事。)",
+	"Prompt Content": "提示词内容",
+	"Prompt suggestions": "提示词建议",
+	"Prompts": "提示词",
+	"Pull \"{{searchValue}}\" from Ollama.com": "从 Ollama.com 拉取 \"{{searchValue}}\"",
+	"Pull a model from Ollama.com": "从 Ollama.com 拉取一个模型",
+	"Query Params": "查询参数",
+	"RAG Template": "RAG 提示词模板",
+	"Rating": "评价",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "朗读",
+	"Record voice": "录音",
+	"Redirecting you to OpenWebUI Community": "正在将您重定向到 OpenWebUI 社区",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "使用\"User\" (用户) 来指代自己(例如:“User 正在学习西班牙语”)",
+	"References from": "来自",
+	"Refused when it shouldn't have": "无理拒绝",
+	"Regenerate": "重新生成",
+	"Release Notes": "更新日志",
+	"Relevance": "相关性",
+	"Remove": "移除",
+	"Remove Model": "移除模型",
+	"Rename": "重命名",
+	"Repeat Last N": "重复最后 N 次",
+	"Request Mode": "请求模式",
+	"Reranking Model": "重排模型",
+	"Reranking model disabled": "重排模型已禁用",
+	"Reranking model set to \"{{reranking_model}}\"": "重排模型设置为 \"{{reranking_model}}\"",
+	"Reset": "重置",
+	"Reset Upload Directory": "重置上传目录",
+	"Reset Vector Storage/Knowledge": "重置向量存储/知识",
+	"Response AutoCopy to Clipboard": "自动复制回复到剪贴板",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "无法激活回复时发送通知。请检查浏览器设置,并授予必要的访问权限。",
+	"Response splitting": "拆分回复",
+	"Result": "结果",
+	"Rich Text Input for Chat": "",
+	"RK": "排名",
+	"Role": "权限组",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"RTL": "从右至左",
+	"Run": "运行",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "运行 Llama 2、Code Llama 和其他模型。自定义和创建您自己的模型。",
+	"Running": "运行中",
+	"Save": "保存",
+	"Save & Create": "保存并创建",
+	"Save & Update": "保存并更新",
+	"Save As Copy": "另存为副本",
+	"Save Tag": "保存标签",
+	"Saved": "已保存",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "我们不再支持将聊天记录直接保存到浏览器的存储空间。请点击下面的按钮下载并删除您的聊天记录。别担心,您可以轻松地将聊天记录重新导入到后台。",
+	"Scroll to bottom when switching between branches": "在分支间切换时滚动到底部",
+	"Search": "搜索",
+	"Search a model": "搜索模型",
+	"Search Chats": "搜索对话",
+	"Search Collection": "搜索内容",
+	"search for tags": "搜索标签",
+	"Search Functions": "搜索函数",
+	"Search Knowledge": "搜索知识",
+	"Search Models": "搜索模型",
+	"Search Prompts": "搜索提示词",
+	"Search Query Generation Prompt": "用于联网搜索关键词生成的提示词",
+	"Search Result Count": "搜索结果数量",
+	"Search Tools": "搜索工具",
+	"SearchApi API Key": "SearchApi API 密钥",
+	"SearchApi Engine": "SearchApi 引擎",
+	"Searched {{count}} sites_other": "搜索到 {{count}} 个结果",
+	"Searching \"{{searchQuery}}\"": "搜索 \"{{searchQuery}}\" 中",
+	"Searching Knowledge for \"{{searchQuery}}\"": "检索有关 \"{{searchQuery}}\" 的知识中",
+	"Searxng Query URL": "Searxng 查询 URL",
+	"See readme.md for instructions": "查看 readme.md 以获取说明",
+	"See what's new": "查阅最新更新内容",
+	"Seed": "种子 (Seed)",
+	"Select a base model": "选择一个基础模型",
+	"Select a engine": "选择一个搜索引擎",
+	"Select a file to view or drag and drop a file to upload": "选择文件以查看内容或拖动文件至此以上传",
+	"Select a function": "选择一个函数",
+	"Select a model": "选择一个模型",
+	"Select a pipeline": "选择一个管道",
+	"Select a pipeline url": "选择一个管道 URL",
+	"Select a tool": "选择一个工具",
+	"Select an Ollama instance": "选择一个 Ollama 实例",
+	"Select Engine": "选择引擎",
+	"Select Knowledge": "选择知识",
+	"Select model": "选择模型",
+	"Select only one model to call": "请仅选择一个模型来呼叫",
+	"Selected model(s) do not support image inputs": "已选择的模型不支持发送图像",
+	"Semantic distance to query": "语义距离查询",
+	"Send": "发送",
+	"Send a Message": "输入消息",
+	"Send message": "发送消息",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "在请求中发送 `stream_options: { include_usage: true }`。\n设置后支持的接口将在返回响应中包含令牌使用信息。",
+	"September": "九月",
+	"Serper API Key": "Serper API 密钥",
+	"Serply API Key": "Serply API 密钥",
+	"Serpstack API Key": "Serpstack API 密钥",
+	"Server connection verified": "已验证服务器连接",
+	"Set as default": "设为默认",
+	"Set CFG Scale": "设置 CFG Scale",
+	"Set Default Model": "设置默认模型",
+	"Set embedding model (e.g. {{model}})": "设置语义向量模型 (例如:{{model}})",
+	"Set Image Size": "设置图片分辨率",
+	"Set reranking model (e.g. {{model}})": "设置重排模型 (例如:{{model}})",
+	"Set Sampler": "设置 Sampler",
+	"Set Scheduler": "设置 Scheduler",
+	"Set Steps": "设置步骤",
+	"Set Task Model": "设置任务模型",
+	"Set Voice": "设置音色",
+	"Set whisper model": "设置 whisper 模型",
+	"Settings": "设置",
+	"Settings saved successfully!": "设置已保存",
+	"Share": "分享",
+	"Share Chat": "分享对话",
+	"Share to OpenWebUI Community": "分享到 OpenWebUI 社区",
+	"short-summary": "简短总结",
+	"Show": "显示",
+	"Show Admin Details in Account Pending Overlay": "在用户待激活界面中显示管理员邮箱等详细信息",
+	"Show Model": "显示",
+	"Show shortcuts": "显示快捷方式",
+	"Show your support!": "表达你的支持!",
+	"Showcased creativity": "很有创意",
+	"Sign in": "登录",
+	"Sign in to {{WEBUI_NAME}}": "登录 {{WEBUI_NAME}}",
+	"Sign Out": "登出",
+	"Sign up": "注册",
+	"Sign up to {{WEBUI_NAME}}": "注册 {{WEBUI_NAME}}",
+	"Signing in to {{WEBUI_NAME}}": "正在登录 {{WEBUI_NAME}}",
+	"Source": "来源",
+	"Speech Playback Speed": "语音播放速度",
+	"Speech recognition error: {{error}}": "语音识别错误:{{error}}",
+	"Speech-to-Text Engine": "语音转文本引擎",
+	"Stop": "停止",
+	"Stop Sequence": "停止序列 (Stop Sequence)",
+	"Stream Chat Response": "以流式返回对话响应",
+	"STT Model": "语音转文本模型",
+	"STT Settings": "语音转文本设置",
+	"Subtitle (e.g. about the Roman Empire)": "副标题(例如:关于罗马帝国的副标题)",
+	"Success": "成功",
+	"Successfully updated.": "成功更新。",
+	"Suggested": "建议",
+	"Support": "支持",
+	"Support this plugin:": "支持此插件",
+	"Sync directory": "同步目录",
+	"System": "系统",
+	"System Instructions": "系统指令",
+	"System Prompt": "系统提示词 (System Prompt)",
+	"Tags": "标签",
+	"Tags Generation Prompt": "标签生成提示词",
+	"Tap to interrupt": "点击以中断",
+	"Tavily API Key": "Tavily API 密钥",
+	"Tell us more:": "请告诉我们更多细节",
+	"Temperature": "温度 (Temperature)",
+	"Template": "模板",
+	"Temporary Chat": "临时对话",
+	"Text Splitter": "文本分切器",
+	"Text-to-Speech Engine": "文本转语音引擎",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "感谢您的反馈!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "本插件的背后开发者是社区中热情的志愿者。如果此插件有帮助到您,烦请考虑一下为它的开发做出贡献。",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "最大文件大小(MB)。如果文件大小超过此限制,则无法上传该文件。",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "在单次对话中可以使用的最大文件数。如果文件数超过此限制,则文件不会上传。",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "分值应介于 0.0(0%)和 1.0(100%)之间。",
+	"Theme": "主题",
+	"Thinking...": "正在思考...",
+	"This action cannot be undone. Do you wish to continue?": "此操作无法撤销。是否确认继续?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "这将确保您的宝贵对话被安全地保存到后台数据库中。感谢!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "这是一个实验功能,可能不会如预期那样工作,而且可能随时发生变化。",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "此选项将会删除文件集中所有文件,并用新上传的文件替换。",
+	"This response was generated by \"{{model}}\"": "此回复由 \"{{model}}\" 生成",
+	"This will delete": "这将删除",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "这将删除<strong>{{NAME}}</strong>及其<strong>所有内容</strong>。",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "这将重置知识库并替换所有文件为目录下文件。确认继续?",
+	"Thorough explanation": "解释较为详细",
+	"Tika": "Tika",
+	"Tika Server URL required.": "请输入 Tika 服务器地址。",
+	"Tiktoken": "Tiktoken",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "提示:在每次替换后,在对话输入中按 Tab 键可以连续更新多个变量。",
+	"Title": "标题",
+	"Title (e.g. Tell me a fun fact)": "标题(例如 给我讲一个有趣的事实)",
+	"Title Auto-Generation": "自动生成标题",
+	"Title cannot be an empty string.": "标题不能为空。",
+	"Title Generation Prompt": "用于自动生成标题的提示词",
+	"To access the available model names for downloading,": "要访问可下载的模型名称,",
+	"To access the GGUF models available for downloading,": "要访问可下载的 GGUF 模型,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "请联系管理员以访问。管理员可以在后台管理面板中管理用户状态。",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "要在这里附加知识库,请先将其添加到工作空间中的“知识库”。",
+	"to chat input.": "到对话输入。",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "要在这里选择自动化,请先将其添加到工作空间中的“函数”。",
+	"To select filters here, add them to the \"Functions\" workspace first.": "要在这里选择过滤器,请先将其添加到工作空间中的“函数”。",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "要在这里选择工具包,请先将其添加到工作空间中的“工具”。",
+	"Toast notifications for new updates": "新更新的弹窗提示",
+	"Today": "今天",
+	"Toggle settings": "切换设置",
+	"Toggle sidebar": "切换侧边栏",
+	"Token": "Token",
+	"Tokens To Keep On Context Refresh (num_keep)": "在语境刷新时需保留的 Tokens",
+	"Too verbose": "",
+	"Tool": "工具",
+	"Tool created successfully": "工具创建成功",
+	"Tool deleted successfully": "工具删除成功",
+	"Tool imported successfully": "工具导入成功",
+	"Tool updated successfully": "工具更新成功",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "工具包描述(例如:用于执行各种操作的工具包)",
+	"Toolkit ID (e.g. my_toolkit)": "工具包 ID(例如:my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "工具包名(例如:我的工具包)",
+	"Tools": "工具",
+	"Tools are a function calling system with arbitrary code execution": "工具是一个具有任意代码执行能力的函数调用系统",
+	"Tools have a function calling system that allows arbitrary code execution": "注意:工具有权执行任意代码",
+	"Tools have a function calling system that allows arbitrary code execution.": "注意:工具有权执行任意代码。",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "访问 Ollama 时遇到问题?",
+	"TTS Model": "文本转语音模型",
+	"TTS Settings": "文本转语音设置",
+	"TTS Voice": "文本转语音音色",
+	"Type": "类型",
+	"Type Hugging Face Resolve (Download) URL": "输入 Hugging Face 解析(下载)URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "糟糕!连接到 {{provider}} 时出现问题。",
+	"UI": "界面",
+	"Unpin": "取消置顶",
+	"Untagged": "无标签",
+	"Update": "更新",
+	"Update and Copy Link": "更新和复制链接",
+	"Update for the latest features and improvements.": "更新来获得最新功能与改进。",
+	"Update password": "更新密码",
+	"Updated": "已更新",
+	"Updated at": "更新于",
+	"Updated At": "更新于",
+	"Upload": "上传",
+	"Upload a GGUF model": "上传一个 GGUF 模型",
+	"Upload directory": "上传目录",
+	"Upload files": "上传文件",
+	"Upload Files": "上传文件",
+	"Upload Pipeline": "上传 Pipeline",
+	"Upload Progress": "上传进度",
+	"URL Mode": "URL 模式",
+	"Use '#' in the prompt input to load and include your knowledge.": "在输入框中输入'#'号来加载你需要的知识库内容。",
+	"Use Gravatar": "使用来自 Gravatar 的头像",
+	"Use Initials": "使用首个字符作为头像",
+	"use_mlock (Ollama)": "use_mlock(Ollama)",
+	"use_mmap (Ollama)": "use_mmap (Ollama)",
+	"user": "用户",
+	"User": "用户",
+	"User location successfully retrieved.": "成功检索到用户位置。",
+	"User Permissions": "用户权限",
+	"Users": "用户",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "竞技场模型默认使用所有模型。单击加号按钮添加自定义模型。",
+	"Utilize": "利用",
+	"Valid time units:": "有效时间单位:",
+	"Valves": "值",
+	"Valves updated": "已更新值",
+	"Valves updated successfully": "值更新成功",
+	"variable": "变量",
+	"variable to have them replaced with clipboard content.": "变量将被剪贴板内容替换。",
+	"Version": "版本",
+	"Version {{selectedVersion}} of {{totalVersions}}": "版本 {{selectedVersion}}/{{totalVersions}}",
+	"Voice": "语音",
+	"Voice Input": "语音输入",
+	"Warning": "警告",
+	"Warning:": "警告:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "警告:如果您修改了语义向量模型,则需要重新导入所有文档。",
+	"Web": "网页",
+	"Web API": "网页 API",
+	"Web Loader Settings": "网页爬取设置",
+	"Web Search": "联网搜索",
+	"Web Search Engine": "联网搜索引擎",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "WebUI 设置",
+	"WebUI will make requests to": "WebUI 将请求",
+	"What’s New in": "最近更新内容于",
+	"Whisper (Local)": "Whisper (本地)",
+	"Widescreen Mode": "宽屏模式",
+	"Won": "获胜",
+	"Workspace": "工作空间",
+	"Write a prompt suggestion (e.g. Who are you?)": "写一个提示词建议(例如:你是谁?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "用 50 个字写一个总结 [主题或关键词]。",
+	"Write something...": "单击以键入内容...",
+	"Yesterday": "昨天",
+	"You": "你",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "每次对话最多仅能附上 {{maxCount}} 个文件。",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "通过点击下方的“管理”按钮,你可以添加记忆,以个性化大语言模型的互动,使其更有用,更符合你的需求。",
+	"You cannot clone a base model": "无法复制基础模型",
+	"You cannot upload an empty file.": "请勿上传空文件。",
+	"You have no archived conversations.": "没有已归档的对话。",
+	"You have shared this chat": "此对话已经分享过",
+	"You're a helpful assistant.": "你是一个有帮助的助手。",
+	"You're now logged in.": "已登录。",
+	"Your account status is currently pending activation.": "您的账号当前状态为待激活。",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "您的全部捐款将直接给到插件开发者,Open WebUI 不会收取任何比例。但众筹平台可能会有服务费、抽成。",
+	"Youtube": "YouTube",
+	"Youtube Loader Settings": "YouTube 爬取设置"
+}
diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json
new file mode 100644
index 0000000000000000000000000000000000000000..723103d51b323ce5daa231f51b6a338cbe45dc42
--- /dev/null
+++ b/src/lib/i18n/locales/zh-TW/translation.json
@@ -0,0 +1,851 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s'、'm'、'h'、'd'、'w' 或 '-1' 表示無到期時間。",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(例如 `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api`)": "(例如 `sh webui.sh --api`)",
+	"(latest)": "(最新版)",
+	"{{ models }}": "{{ models }}",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}:您無法刪除基礎模型",
+	"{{user}}'s Chats": "{{user}} 的對話",
+	"{{webUIName}} Backend Required": "需要 {{webUIName}} 後端",
+	"*Prompt node ID(s) are required for image generation": "* 圖片生成需要提示詞節點 ID",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "新版本 (v{{LATEST_VERSION}}) 現已推出。",
+	"A task model is used when performing tasks such as generating titles for chats and web search queries": "執行產生對話標題和網頁搜尋查詢等任務時會使用任務模型",
+	"a user": "一位使用者",
+	"About": "關於",
+	"Account": "帳號",
+	"Account Activation Pending": "帳號待啟用",
+	"Accurate information": "準確資訊",
+	"Actions": "動作",
+	"Active Users": "活躍使用者",
+	"Add": "新增",
+	"Add a model id": "新增模型 ID",
+	"Add a short description about what this model does": "新增這個模型的簡短描述",
+	"Add a short title for this prompt": "為這個提示詞新增一個簡短的標題",
+	"Add a tag": "新增標籤",
+	"Add Arena Model": "",
+	"Add Content": "新增內容",
+	"Add content here": "在此新增內容",
+	"Add custom prompt": "新增自訂提示詞",
+	"Add Files": "新增檔案",
+	"Add Memory": "新增記憶",
+	"Add Model": "新增模型",
+	"Add Tag": "新增標籤",
+	"Add Tags": "新增標籤",
+	"Add text content": "新增文字內容",
+	"Add User": "新增使用者",
+	"Adjusting these settings will apply changes universally to all users.": "調整這些設定將會影響所有使用者。",
+	"admin": "管理員",
+	"Admin": "管理員",
+	"Admin Panel": "管理員控制台",
+	"Admin Settings": "管理員設定",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "管理員可以隨時使用所有工具;使用者則需在工作區中為每個模型分配工具。",
+	"Advanced Parameters": "進階參數",
+	"Advanced Params": "進階參數",
+	"All chats": "",
+	"All Documents": "所有文件",
+	"Allow Chat Deletion": "允許刪除對話紀錄",
+	"Allow Chat Editing": "允許編輯對話",
+	"Allow non-local voices": "允許非本機語音",
+	"Allow Temporary Chat": "允許暫時對話",
+	"Allow User Location": "允許使用者位置",
+	"Allow Voice Interruption in Call": "允許在通話中打斷語音",
+	"alphanumeric characters and hyphens": "英文字母、數字和連字號",
+	"Already have an account?": "已經有帳號了嗎?",
+	"an assistant": "一位助手",
+	"and": "和",
+	"and {{COUNT}} more": "",
+	"and create a new shared link.": "並建立新的共用連結。",
+	"API Base URL": "API 基礎 URL",
+	"API Key": "API 金鑰",
+	"API Key created.": "API 金鑰已建立。",
+	"API keys": "API 金鑰",
+	"April": "4 月",
+	"Archive": "封存",
+	"Archive All Chats": "封存所有對話紀錄",
+	"Archived Chats": "封存的對話紀錄",
+	"are allowed - Activate this command by typing": "已允許 - 輸入此命令以啟用",
+	"Are you sure?": "您確定嗎?",
+	"Arena Models": "",
+	"Artifacts": "成品",
+	"Ask a question": "提出問題",
+	"Assistant": "",
+	"Attach file": "附加檔案",
+	"Attention to detail": "注重細節",
+	"Audio": "音訊",
+	"August": "8 月",
+	"Auto-playback response": "自動播放回應",
+	"Automatic1111": "Automatic1111",
+	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 API 驗證字串",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 基礎 URL",
+	"AUTOMATIC1111 Base URL is required.": "需要 AUTOMATIC1111 基礎 URL。",
+	"Available list": "可用清單",
+	"available!": "可用!",
+	"Azure AI Speech": "Azure AI 語音",
+	"Azure Region": "Azure 區域",
+	"Back": "返回",
+	"Bad Response": "錯誤回應",
+	"Banners": "橫幅",
+	"Base Model (From)": "基礎模型(來自)",
+	"Batch Size (num_batch)": "批次大小(num_batch)",
+	"before": "之前",
+	"Being lazy": "懶惰模式",
+	"Brave Search API Key": "Brave 搜尋 API 金鑰",
+	"Bypass SSL verification for Websites": "略過網站的 SSL 驗證",
+	"Call": "通話",
+	"Call feature is not supported when using Web STT engine": "使用網頁語音辨識 (Web STT) 引擎時不支援通話功能",
+	"Camera": "相機",
+	"Cancel": "取消",
+	"Capabilities": "功能",
+	"Change Password": "修改密碼",
+	"Character": "",
+	"Chat": "對話",
+	"Chat Background Image": "對話背景圖片",
+	"Chat Bubble UI": "對話氣泡介面",
+	"Chat Controls": "對話控制項",
+	"Chat direction": "對話方向",
+	"Chat Overview": "對話概覽",
+	"Chat Tags Auto-Generation": "",
+	"Chats": "對話",
+	"Check Again": "再次檢查",
+	"Check for updates": "檢查更新",
+	"Checking for updates...": "正在檢查更新...",
+	"Choose a model before saving...": "儲存前請選擇一個模型...",
+	"Chunk Overlap": "區塊重疊",
+	"Chunk Params": "區塊參數",
+	"Chunk Size": "區塊大小",
+	"Citation": "引用",
+	"Clear memory": "清除記憶",
+	"Click here for help.": "點選這裡取得協助。",
+	"Click here to": "點選這裡",
+	"Click here to download user import template file.": "點選這裡下載使用者匯入範本檔案。",
+	"Click here to learn more about faster-whisper and see the available models.": "",
+	"Click here to select": "點選這裡選擇",
+	"Click here to select a csv file.": "點選這裡選擇 CSV 檔案。",
+	"Click here to select a py file.": "點選這裡選擇 Python 檔案。",
+	"Click here to upload a workflow.json file.": "點選這裡上傳 workflow.json 檔案。",
+	"click here.": "點選這裡。",
+	"Click on the user role button to change a user's role.": "點選使用者角色按鈕變更使用者的角色。",
+	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "剪貼簿寫入權限遭拒。請檢查您的瀏覽器設定,授予必要的存取權限。",
+	"Clone": "複製",
+	"Close": "關閉",
+	"Code execution": "",
+	"Code formatted successfully": "程式碼格式化成功",
+	"Collection": "收藏",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI 基礎 URL",
+	"ComfyUI Base URL is required.": "需要 ComfyUI 基礎 URL。",
+	"ComfyUI Workflow": "ComfyUI 工作流程",
+	"ComfyUI Workflow Nodes": "ComfyUI 工作流程節點",
+	"Command": "命令",
+	"Completions": "",
+	"Concurrent Requests": "平行請求",
+	"Confirm": "確認",
+	"Confirm Password": "確認密碼",
+	"Confirm your action": "確認您的操作",
+	"Connections": "連線",
+	"Contact Admin for WebUI Access": "請聯絡管理員以取得 WebUI 存取權限",
+	"Content": "內容",
+	"Content Extraction": "內容擷取",
+	"Context Length": "上下文長度",
+	"Continue Response": "繼續回應",
+	"Continue with {{provider}}": "使用 {{provider}} 繼續",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "控制文字轉語音(TTS)請求中如何分割訊息文字。「標點符號」分割為句子,「段落」分割為段落,「無」則保持訊息為單一字串。",
+	"Controls": "控制項",
+	"Copied": "已複製",
+	"Copied shared chat URL to clipboard!": "已複製共用對話 URL 到剪貼簿!",
+	"Copied to clipboard": "已複製到剪貼簿",
+	"Copy": "複製",
+	"Copy last code block": "複製最後一個程式碼區塊",
+	"Copy last response": "複製最後一個回應",
+	"Copy Link": "複製連結",
+	"Copy to clipboard": "",
+	"Copying to clipboard was successful!": "成功複製到剪貼簿!",
+	"Create a model": "建立模型",
+	"Create Account": "建立帳號",
+	"Create Knowledge": "建立知識",
+	"Create new key": "建立新的金鑰",
+	"Create new secret key": "建立新的金鑰",
+	"Created at": "建立於",
+	"Created At": "建立於",
+	"Created by": "建立者",
+	"CSV Import": "CSV 匯入",
+	"Current Model": "目前模型",
+	"Current Password": "目前密碼",
+	"Custom": "自訂",
+	"Customize models for a specific purpose": "自訂模型以用於特定目的",
+	"Dark": "深色",
+	"Dashboard": "儀表板",
+	"Database": "資料庫",
+	"December": "12 月",
+	"Default": "預設",
+	"Default (Open AI)": "預設 (OpenAI)",
+	"Default (SentenceTransformers)": "預設 (SentenceTransformers)",
+	"Default Model": "預設模型",
+	"Default model updated": "預設模型已更新",
+	"Default Prompt Suggestions": "預設提示詞建議",
+	"Default User Role": "預設使用者角色",
+	"Delete": "刪除",
+	"Delete a model": "刪除模型",
+	"Delete All Chats": "刪除所有對話紀錄",
+	"Delete chat": "刪除對話紀錄",
+	"Delete Chat": "刪除對話紀錄",
+	"Delete chat?": "刪除對話紀錄?",
+	"Delete folder?": "",
+	"Delete function?": "刪除函式?",
+	"Delete prompt?": "刪除提示詞?",
+	"delete this link": "刪除此連結",
+	"Delete tool?": "刪除工具?",
+	"Delete User": "刪除使用者",
+	"Deleted {{deleteModelTag}}": "已刪除 {{deleteModelTag}}",
+	"Deleted {{name}}": "已刪除 {{name}}",
+	"Description": "描述",
+	"Didn't fully follow instructions": "未完全遵循指示",
+	"Disabled": "已停用",
+	"Discover a function": "發掘函式",
+	"Discover a model": "發掘模型",
+	"Discover a prompt": "發掘提示詞",
+	"Discover a tool": "發掘工具",
+	"Discover, download, and explore custom functions": "發掘、下載及探索自訂函式",
+	"Discover, download, and explore custom prompts": "發掘、下載及探索自訂提示詞",
+	"Discover, download, and explore custom tools": "發掘、下載及探索自訂工具",
+	"Discover, download, and explore model presets": "發掘、下載及探索模型預設集",
+	"Dismissible": "可忽略",
+	"Display Emoji in Call": "在通話中顯示表情符號",
+	"Display the username instead of You in the Chat": "在對話中顯示使用者名稱,而非「您」",
+	"Do not install functions from sources you do not fully trust.": "請勿從您無法完全信任的來源安裝函式。",
+	"Do not install tools from sources you do not fully trust.": "請勿從您無法完全信任的來源安裝工具。",
+	"Document": "文件",
+	"Documentation": "文件",
+	"Documents": "文件",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "不會建立任何外部連線,而且您的資料會安全地儲存在您本機伺服器上。",
+	"Don't have an account?": "還沒註冊帳號嗎?",
+	"don't install random functions from sources you don't trust.": "請勿從您無法信任的來源安裝隨機函式。",
+	"don't install random tools from sources you don't trust.": "請勿從您無法信任的來源安裝隨機工具。",
+	"Don't like the style": "不喜歡這個樣式",
+	"Done": "完成",
+	"Download": "下載",
+	"Download canceled": "已取消下載",
+	"Download Database": "下載資料庫",
+	"Draw": "",
+	"Drop any files here to add to the conversation": "拖拽任意檔案到此處以新增至對話",
+	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "例如:'30s'、'10m'。有效的時間單位為 's'、'm'、'h'。",
+	"Edit": "編輯",
+	"Edit Arena Model": "",
+	"Edit Memory": "編輯記憶",
+	"Edit User": "編輯使用者",
+	"ElevenLabs": "ElevenLabs",
+	"Email": "電子郵件",
+	"Embedding Batch Size": "嵌入批次大小",
+	"Embedding Model": "嵌入模型",
+	"Embedding Model Engine": "嵌入模型引擎",
+	"Embedding model set to \"{{embedding_model}}\"": "嵌入模型已設定為 \"{{embedding_model}}\"",
+	"Enable Community Sharing": "啟用社群分享",
+	"Enable Message Rating": "啟用訊息評分",
+	"Enable New Sign Ups": "允許新使用者註冊",
+	"Enable Web Search": "啟用網頁搜尋",
+	"Enable Web Search Query Generation": "啟用網頁搜尋查詢生成",
+	"Enabled": "已啟用",
+	"Engine": "引擎",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "請確認您的 CSV 檔案包含以下 4 個欄位,並按照此順序排列:姓名、電子郵件、密碼、角色。",
+	"Enter {{role}} message here": "在此輸入 {{role}} 訊息",
+	"Enter a detail about yourself for your LLMs to recall": "輸入有關您的詳細資訊,讓您的大型語言模型可以回想起來",
+	"Enter api auth string (e.g. username:password)": "輸入 API 驗證字串(例如:username:password)",
+	"Enter Brave Search API Key": "輸入 Brave 搜尋 API 金鑰",
+	"Enter CFG Scale (e.g. 7.0)": "輸入 CFG 比例(例如:7.0)",
+	"Enter Chunk Overlap": "輸入區塊重疊",
+	"Enter Chunk Size": "輸入區塊大小",
+	"Enter description": "",
+	"Enter Github Raw URL": "輸入 GitHub Raw URL",
+	"Enter Google PSE API Key": "輸入 Google PSE API 金鑰",
+	"Enter Google PSE Engine Id": "輸入 Google PSE 引擎 ID",
+	"Enter Image Size (e.g. 512x512)": "輸入圖片大小(例如:512x512)",
+	"Enter language codes": "輸入語言代碼",
+	"Enter Model ID": "輸入模型 ID",
+	"Enter model tag (e.g. {{modelTag}})": "輸入模型標籤(例如:{{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "輸入步驟數(例如:50)",
+	"Enter Sampler (e.g. Euler a)": "輸入取樣器(例如:Euler a)",
+	"Enter Scheduler (e.g. Karras)": "輸入排程器(例如:Karras)",
+	"Enter Score": "輸入分數",
+	"Enter SearchApi API Key": "輸入 SearchApi API 金鑰",
+	"Enter SearchApi Engine": "輸入 SearchApi 引擎",
+	"Enter Searxng Query URL": "輸入 SearXNG 查詢 URL",
+	"Enter Serper API Key": "輸入 Serper API 金鑰",
+	"Enter Serply API Key": "輸入 Serply API 金鑰",
+	"Enter Serpstack API Key": "輸入 Serpstack API 金鑰",
+	"Enter stop sequence": "輸入停止序列",
+	"Enter system prompt": "輸入系統提示詞",
+	"Enter Tavily API Key": "輸入 Tavily API 金鑰",
+	"Enter Tika Server URL": "輸入 Tika 伺服器 URL",
+	"Enter Top K": "輸入 Top K 值",
+	"Enter URL (e.g. http://127.0.0.1:7860/)": "輸入 URL(例如:http://127.0.0.1:7860/)",
+	"Enter URL (e.g. http://localhost:11434)": "輸入 URL(例如:http://localhost:11434)",
+	"Enter Your Email": "輸入您的電子郵件",
+	"Enter Your Full Name": "輸入您的全名",
+	"Enter your message": "輸入您的訊息",
+	"Enter Your Password": "輸入您的密碼",
+	"Enter Your Role": "輸入您的角色",
+	"Error": "錯誤",
+	"ERROR": "",
+	"Evaluations": "",
+	"Exclude": "",
+	"Experimental": "實驗性功能",
+	"Export": "匯出",
+	"Export All Chats (All Users)": "匯出所有對話紀錄(所有使用者)",
+	"Export chat (.json)": "匯出對話紀錄(.json)",
+	"Export Chats": "匯出對話紀錄",
+	"Export Config to JSON File": "將設定匯出為 JSON 檔案",
+	"Export Functions": "匯出函式",
+	"Export LiteLLM config.yaml": "匯出 LiteLLM config.yaml",
+	"Export Models": "匯出模型",
+	"Export Prompts": "匯出提示詞",
+	"Export Tools": "匯出工具",
+	"External Models": "外部模型",
+	"Failed to add file.": "無法新增檔案。",
+	"Failed to create API Key.": "無法建立 API 金鑰。",
+	"Failed to read clipboard contents": "無法讀取剪貼簿內容",
+	"Failed to update settings": "無法更新設定",
+	"Failed to upload file.": "無法上傳檔案。",
+	"February": "2 月",
+	"Feedback History": "",
+	"Feel free to add specific details": "歡迎自由新增特定細節",
+	"File": "檔案",
+	"File added successfully.": "檔案新增成功。",
+	"File content updated successfully.": "檔案內容更新成功。",
+	"File Mode": "檔案模式",
+	"File not found.": "找不到檔案。",
+	"File removed successfully.": "成功移除檔案。",
+	"File size should not exceed {{maxSize}} MB.": "檔案大小不應超過 {{maxSize}} MB。",
+	"Files": "檔案",
+	"Filter is now globally disabled": "篩選器現在已全域停用",
+	"Filter is now globally enabled": "篩選器現在已全域啟用",
+	"Filters": "篩選器",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "偵測到指紋偽造:無法使用姓名縮寫作為大頭貼。將預設為預設個人檔案圖片。",
+	"Fluidly stream large external response chunks": "流暢地串流大型外部回應區塊",
+	"Focus chat input": "聚焦對話輸入",
+	"Folder deleted successfully": "",
+	"Folder name cannot be empty": "",
+	"Folder name cannot be empty.": "",
+	"Folder name updated successfully": "",
+	"Followed instructions perfectly": "完全遵循指示",
+	"Form": "表單",
+	"Format your variables using brackets like this:": "",
+	"Frequency Penalty": "頻率懲罰",
+	"Function": "",
+	"Function created successfully": "成功建立函式",
+	"Function deleted successfully": "成功刪除函式",
+	"Function Description (e.g. A filter to remove profanity from text)": "函式描述(例如:用於從文字中移除不雅詞彙的篩選器)",
+	"Function ID (e.g. my_filter)": "函式 ID(例如:my_filter)",
+	"Function is now globally disabled": "現在已在全域停用函式",
+	"Function is now globally enabled": "現在已在全域啟用函式",
+	"Function Name (e.g. My Filter)": "函式名稱(例如:我的篩選器)",
+	"Function updated successfully": "成功更新函式",
+	"Functions": "函式",
+	"Functions allow arbitrary code execution": "函式允許執行任意程式碼",
+	"Functions allow arbitrary code execution.": "函式允許執行任意程式碼。",
+	"Functions imported successfully": "成功匯入函式",
+	"General": "一般",
+	"General Settings": "一般設定",
+	"Generate Image": "產生圖片",
+	"Generating search query": "正在產生搜尋查詢",
+	"Generation Info": "生成資訊",
+	"Get up and running with": "開始使用",
+	"Global": "全域",
+	"Good Response": "良好回應",
+	"Google PSE API Key": "Google PSE API 金鑰",
+	"Google PSE Engine Id": "Google PSE 引擎 ID",
+	"h:mm a": "h:mm a",
+	"Haptic Feedback": "觸覺回饋",
+	"has no conversations.": "沒有對話。",
+	"Hello, {{name}}": "您好,{{name}}",
+	"Help": "說明",
+	"Help us create the best community leaderboard by sharing your feedback history!": "",
+	"Hide": "隱藏",
+	"Hide Model": "隱藏模型",
+	"How can I help you today?": "今天我能為您做些什麼?",
+	"Hybrid Search": "混合搜尋",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "我確認已閱讀並理解我的操作所帶來的影響。我了解執行任意程式碼的相關風險,並已驗證來源的可信度。",
+	"ID": "",
+	"Image Generation (Experimental)": "圖片生成(實驗性功能)",
+	"Image Generation Engine": "圖片生成引擎",
+	"Image Settings": "圖片設定",
+	"Images": "圖片",
+	"Import Chats": "匯入對話紀錄",
+	"Import Config from JSON File": "從 JSON 檔案匯入設定",
+	"Import Functions": "匯入函式",
+	"Import Models": "匯入模型",
+	"Import Prompts": "匯入提示詞",
+	"Import Tools": "匯入工具",
+	"Include": "",
+	"Include `--api-auth` flag when running stable-diffusion-webui": "執行 stable-diffusion-webui 時包含 `--api-auth` 參數",
+	"Include `--api` flag when running stable-diffusion-webui": "執行 stable-diffusion-webui 時包含 `--api` 參數",
+	"Info": "資訊",
+	"Input commands": "輸入命令",
+	"Install from Github URL": "從 GitHub URL 安裝",
+	"Instant Auto-Send After Voice Transcription": "語音轉錄後立即自動傳送",
+	"Interface": "介面",
+	"Invalid file format.": "",
+	"Invalid Tag": "無效標籤",
+	"January": "1 月",
+	"join our Discord for help.": "加入我們的 Discord 以尋求協助。",
+	"JSON": "JSON",
+	"JSON Preview": "JSON 預覽",
+	"July": "7 月",
+	"June": "6 月",
+	"JWT Expiration": "JWT 過期時間",
+	"JWT Token": "JWT Token",
+	"Keep Alive": "保持連線",
+	"Keyboard shortcuts": "鍵盤快捷鍵",
+	"Knowledge": "知識",
+	"Knowledge created successfully.": "知識建立成功。",
+	"Knowledge deleted successfully.": "知識刪除成功。",
+	"Knowledge reset successfully.": "知識重設成功。",
+	"Knowledge updated successfully": "知識更新成功",
+	"Landing Page Mode": "首頁模式",
+	"Language": "語言",
+	"large language models, locally.": "在本機執行大型語言模型。",
+	"Last Active": "上次活動時間",
+	"Last Modified": "上次修改時間",
+	"Leaderboard": "",
+	"Leave empty for unlimited": "留空表示無限制",
+	"Leave empty to include all models or select specific models": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "留空使用預設提示詞,或輸入自訂提示詞",
+	"Light": "淺色",
+	"Listening...": "正在聆聽...",
+	"LLMs can make mistakes. Verify important information.": "大型語言模型可能會出錯。請驗證重要資訊。",
+	"Local Models": "本機模型",
+	"Lost": "",
+	"LTR": "從左到右",
+	"Made by OpenWebUI Community": "由 OpenWebUI 社群製作",
+	"Make sure to enclose them with": "請務必將它們放在",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "請確保從 ComfyUI 匯出 workflow.json 檔案為 API 格式。",
+	"Manage": "管理",
+	"Manage Arena Models": "",
+	"Manage Models": "管理模型",
+	"Manage Ollama Models": "管理 Ollama 模型",
+	"Manage Pipelines": "管理管線",
+	"March": "3 月",
+	"Max Tokens (num_predict)": "最大 token 數(num_predict)",
+	"Max Upload Count": "最大上傳數量",
+	"Max Upload Size": "最大上傳大小",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "最多可同時下載 3 個模型。請稍後再試。",
+	"May": "5 月",
+	"Memories accessible by LLMs will be shown here.": "可被大型語言模型存取的記憶將顯示在這裡。",
+	"Memory": "記憶",
+	"Memory added successfully": "成功新增記憶",
+	"Memory cleared successfully": "成功清除記憶",
+	"Memory deleted successfully": "成功刪除記憶",
+	"Memory updated successfully": "成功更新記憶",
+	"Merge Responses": "合併回應",
+	"Message rating should be enabled to use this feature": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "建立連結後傳送的訊息不會被分享。擁有網址的使用者將能夠檢視分享的對話內容。",
+	"Min P": "最小 P 值",
+	"Minimum Score": "最低分數",
+	"Mirostat": "Mirostat",
+	"Mirostat Eta": "Mirostat Eta",
+	"Mirostat Tau": "Mirostat Tau",
+	"MMMM DD, YYYY": "YYYY 年 MMMM DD 日",
+	"MMMM DD, YYYY HH:mm": "YYYY 年 MMMM DD 日 HH:mm",
+	"MMMM DD, YYYY hh:mm:ss A": "YYYY 年 MMMM DD 日 hh:mm:ss A",
+	"Model": "",
+	"Model '{{modelName}}' has been successfully downloaded.": "模型「{{modelName}}」已成功下載。",
+	"Model '{{modelTag}}' is already in queue for downloading.": "模型「{{modelTag}}」已在下載佇列中。",
+	"Model {{modelId}} not found": "找不到模型 {{modelId}}",
+	"Model {{modelName}} is not vision capable": "模型 {{modelName}} 不具備視覺能力",
+	"Model {{name}} is now {{status}}": "模型 {{name}} 現在狀態為 {{status}}",
+	"Model {{name}} is now at the top": "模型 {{name}} 現在位於頂端",
+	"Model accepts image inputs": "模型接受影像輸入",
+	"Model created successfully!": "成功建立模型!",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "偵測到模型檔案系統路徑。更新需要模型簡稱,因此無法繼續。",
+	"Model ID": "模型 ID",
+	"Model Name": "",
+	"Model not selected": "未選取模型",
+	"Model Params": "模型參數",
+	"Model updated successfully": "成功更新模型",
+	"Model Whitelisting": "模型白名單",
+	"Model(s) Whitelisted": "模型已加入白名單",
+	"Modelfile Content": "模型檔案內容",
+	"Models": "模型",
+	"more": "",
+	"More": "更多",
+	"Move to Top": "移至頂端",
+	"Name": "名稱",
+	"Name your model": "為您的模型命名",
+	"New Chat": "新增對話",
+	"New folder": "",
+	"New Password": "新密碼",
+	"No content found": "找不到內容",
+	"No content to speak": "沒有要朗讀的內容",
+	"No distance available": "",
+	"No feedbacks found": "",
+	"No file selected": "未選取檔案",
+	"No files found.": "",
+	"No HTML, CSS, or JavaScript content found.": "找不到 HTML、CSS 或 JavaScript 內容。",
+	"No knowledge found": "找不到知識",
+	"No models found": "",
+	"No results found": "找不到任何結果",
+	"No search query generated": "未產生搜尋查詢",
+	"No source available": "沒有可用的來源",
+	"No valves to update": "沒有要更新的閥門",
+	"None": "無",
+	"Not factually correct": "與事實不符",
+	"Not helpful": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "注意:如果您設定了最低分數,則搜尋只會回傳分數大於或等於最低分數的文件。",
+	"Notes": "",
+	"Notifications": "通知",
+	"November": "11 月",
+	"num_gpu (Ollama)": "num_gpu (Ollama)",
+	"num_thread (Ollama)": "num_thread (Ollama)",
+	"OAuth ID": "OAuth ID",
+	"October": "10 月",
+	"Off": "關閉",
+	"Okay, Let's Go!": "好的,我們開始吧!",
+	"OLED Dark": "OLED 深色",
+	"Ollama": "Ollama",
+	"Ollama API": "Ollama API",
+	"Ollama API disabled": "Ollama API 已停用",
+	"Ollama API is disabled": "Ollama API 已停用",
+	"Ollama Version": "Ollama 版本",
+	"On": "開啟",
+	"Only": "僅限",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "命令字串中只允許使用英文字母、數字和連字號。",
+	"Only collections can be edited, create a new knowledge base to edit/add documents.": "只能編輯集合,請建立新的知識以編輯或新增文件。",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "哎呀!這個 URL 似乎無效。請仔細檢查並再試一次。",
+	"Oops! There are files still uploading. Please wait for the upload to complete.": "",
+	"Oops! There was an error in the previous response.": "",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "哎呀!您使用了不支援的方法(僅限前端)。請從後端提供 WebUI。",
+	"Open file": "開啟檔案",
+	"Open in full screen": "全螢幕開啟",
+	"Open new chat": "開啟新的對話",
+	"Open WebUI uses faster-whisper internally.": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI 版本 (v{{OPEN_WEBUI_VERSION}}) 低於所需版本 (v{{REQUIRED_VERSION}})",
+	"OpenAI": "OpenAI",
+	"OpenAI API": "OpenAI API",
+	"OpenAI API Config": "OpenAI API 設定",
+	"OpenAI API Key is required.": "需要 OpenAI API 金鑰。",
+	"OpenAI URL/Key required.": "需要 OpenAI URL/金鑰。",
+	"or": "或",
+	"Other": "其他",
+	"OUTPUT": "",
+	"Output format": "輸出格式",
+	"Overview": "概覽",
+	"page": "頁面",
+	"Password": "密碼",
+	"PDF document (.pdf)": "PDF 文件 (.pdf)",
+	"PDF Extract Images (OCR)": "PDF 影像擷取(OCR 光學文字辨識)",
+	"pending": "待處理",
+	"Permission denied when accessing media devices": "存取媒體裝置時權限遭拒",
+	"Permission denied when accessing microphone": "存取麥克風時權限遭拒",
+	"Permission denied when accessing microphone: {{error}}": "存取麥克風時權限遭拒:{{error}}",
+	"Personalization": "個人化",
+	"Pin": "釘選",
+	"Pinned": "已釘選",
+	"Pipeline deleted successfully": "成功刪除管線",
+	"Pipeline downloaded successfully": "成功下載管線",
+	"Pipelines": "管線",
+	"Pipelines Not Detected": "未偵測到管線",
+	"Pipelines Valves": "管線閥門",
+	"Plain text (.txt)": "純文字 (.txt)",
+	"Playground": "遊樂場",
+	"Please carefully review the following warnings:": "請仔細閱讀以下警告:",
+	"Please enter a prompt": "",
+	"Please fill in all fields.": "請填寫所有欄位。",
+	"Please select a reason": "請選擇原因",
+	"Positive attitude": "積極的態度",
+	"Previous 30 days": "過去 30 天",
+	"Previous 7 days": "過去 7 天",
+	"Profile Image": "個人檔案圖片",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "提示詞(例如:告訴我關於羅馬帝國的一些趣事)",
+	"Prompt Content": "提示詞內容",
+	"Prompt suggestions": "提示詞建議",
+	"Prompts": "提示詞",
+	"Pull \"{{searchValue}}\" from Ollama.com": "從 Ollama.com 下載 \"{{searchValue}}\"",
+	"Pull a model from Ollama.com": "從 Ollama.com 下載模型",
+	"Query Params": "查詢參數",
+	"RAG Template": "RAG 範本",
+	"Rating": "",
+	"Re-rank models by topic similarity": "",
+	"Read Aloud": "朗讀",
+	"Record voice": "錄音",
+	"Redirecting you to OpenWebUI Community": "正在將您重新導向到 OpenWebUI 社群",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "以「使用者」稱呼自己(例如:「使用者正在學習西班牙文」)",
+	"References from": "",
+	"Refused when it shouldn't have": "不應拒絕時拒絕了",
+	"Regenerate": "重新產生",
+	"Release Notes": "發布說明",
+	"Relevance": "",
+	"Remove": "移除",
+	"Remove Model": "移除模型",
+	"Rename": "重新命名",
+	"Repeat Last N": "重複最後 N 個",
+	"Request Mode": "請求模式",
+	"Reranking Model": "重新排序模型",
+	"Reranking model disabled": "已停用重新排序模型",
+	"Reranking model set to \"{{reranking_model}}\"": "重新排序模型已設定為 \"{{reranking_model}}\"",
+	"Reset": "重設",
+	"Reset Upload Directory": "重設上傳目錄",
+	"Reset Vector Storage/Knowledge": "",
+	"Response AutoCopy to Clipboard": "自動將回應複製到剪貼簿",
+	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "無法啟用回應通知,因為網站權限已遭拒。請前往瀏覽器設定以授予必要存取權限。",
+	"Response splitting": "回應分割",
+	"Result": "",
+	"Rich Text Input for Chat": "",
+	"RK": "",
+	"Role": "角色",
+	"Rosé Pine": "玫瑰松",
+	"Rosé Pine Dawn": "黎明玫瑰松",
+	"RTL": "從右到左",
+	"Run": "執行",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "執行 Llama 2、Code Llama 和其他模型。自訂並建立您自己的模型。",
+	"Running": "運作中",
+	"Save": "儲存",
+	"Save & Create": "儲存並建立",
+	"Save & Update": "儲存並更新",
+	"Save As Copy": "另存為副本",
+	"Save Tag": "儲存標籤",
+	"Saved": "已儲存",
+	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "不再支援直接將對話紀錄儲存到您的瀏覽器儲存空間。請點選下方按鈕來下載並刪除您的對話紀錄。別擔心,您可以透過以下方式輕鬆地將對話紀錄重新匯入後端",
+	"Scroll to bottom when switching between branches": "切換分支時捲動到底端",
+	"Search": "搜尋",
+	"Search a model": "搜尋模型",
+	"Search Chats": "搜尋對話",
+	"Search Collection": "搜尋集合",
+	"search for tags": "",
+	"Search Functions": "搜尋函式",
+	"Search Knowledge": "搜尋知識庫",
+	"Search Models": "搜尋模型",
+	"Search Prompts": "搜尋提示詞",
+	"Search Query Generation Prompt": "搜尋查詢生成提示詞",
+	"Search Result Count": "搜尋結果數量",
+	"Search Tools": "搜尋工具",
+	"SearchApi API Key": "SearchApi API 金鑰",
+	"SearchApi Engine": "SearchApi 引擎",
+	"Searched {{count}} sites_one": "已搜尋 {{count}} 個網站",
+	"Searched {{count}} sites_other": "已搜尋 {{count}} 個網站",
+	"Searching \"{{searchQuery}}\"": "正在搜尋 \"{{searchQuery}}\"",
+	"Searching Knowledge for \"{{searchQuery}}\"": "正在搜尋知識庫中的 \"{{searchQuery}}\"",
+	"Searxng Query URL": "Searxng 查詢 URL",
+	"See readme.md for instructions": "檢視 readme.md 以取得說明",
+	"See what's new": "查看新功能",
+	"Seed": "種子值",
+	"Select a base model": "選擇基礎模型",
+	"Select a engine": "選擇引擎",
+	"Select a file to view or drag and drop a file to upload": "選擇檔案以檢視或拖放檔案以上傳",
+	"Select a function": "選擇函式",
+	"Select a model": "選擇模型",
+	"Select a pipeline": "選擇管線",
+	"Select a pipeline url": "選擇管線 URL",
+	"Select a tool": "選擇工具",
+	"Select an Ollama instance": "選擇 Ollama 執行個體",
+	"Select Engine": "選擇引擎",
+	"Select Knowledge": "選擇知識庫",
+	"Select model": "選擇模型",
+	"Select only one model to call": "僅選擇一個模型來呼叫",
+	"Selected model(s) do not support image inputs": "選取的模型不支援圖片輸入",
+	"Semantic distance to query": "",
+	"Send": "傳送",
+	"Send a Message": "傳送訊息",
+	"Send message": "傳送訊息",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "在請求中傳送 `stream_options: { include_usage: true }`。\n設定後,支援的提供者將在回應中回傳權杖使用資訊。",
+	"September": "9 月",
+	"Serper API Key": "Serper API 金鑰",
+	"Serply API Key": "Serply API 金鑰",
+	"Serpstack API Key": "Serpstack API 金鑰",
+	"Server connection verified": "伺服器連線已驗證",
+	"Set as default": "設為預設",
+	"Set CFG Scale": "設定 CFG 比例",
+	"Set Default Model": "設定預設模型",
+	"Set embedding model (e.g. {{model}})": "設定嵌入模型(例如:{{model}})",
+	"Set Image Size": "設定圖片大小",
+	"Set reranking model (e.g. {{model}})": "設定重新排序模型(例如:{{model}})",
+	"Set Sampler": "設定取樣器",
+	"Set Scheduler": "設定排程器",
+	"Set Steps": "設定步數",
+	"Set Task Model": "設定任務模型",
+	"Set Voice": "設定語音",
+	"Set whisper model": "",
+	"Settings": "設定",
+	"Settings saved successfully!": "設定已成功儲存!",
+	"Share": "分享",
+	"Share Chat": "分享對話",
+	"Share to OpenWebUI Community": "分享到 OpenWebUI 社群",
+	"short-summary": "簡短摘要",
+	"Show": "顯示",
+	"Show Admin Details in Account Pending Overlay": "在帳號待審覆蓋層中顯示管理員詳細資訊",
+	"Show Model": "顯示模型",
+	"Show shortcuts": "顯示快捷鍵",
+	"Show your support!": "表達您的支持!",
+	"Showcased creativity": "展現創意",
+	"Sign in": "登入",
+	"Sign in to {{WEBUI_NAME}}": "登入 {{WEBUI_NAME}}",
+	"Sign Out": "登出",
+	"Sign up": "註冊",
+	"Sign up to {{WEBUI_NAME}}": "註冊 {{WEBUI_NAME}}",
+	"Signing in to {{WEBUI_NAME}}": "正在登入 {{WEBUI_NAME}}",
+	"Source": "來源",
+	"Speech Playback Speed": "語音播放速度",
+	"Speech recognition error: {{error}}": "語音辨識錯誤:{{error}}",
+	"Speech-to-Text Engine": "語音轉文字 (STT) 引擎",
+	"Stop": "",
+	"Stop Sequence": "停止序列",
+	"Stream Chat Response": "串流對話回應",
+	"STT Model": "語音轉文字 (STT) 模型",
+	"STT Settings": "語音轉文字 (STT) 設定",
+	"Subtitle (e.g. about the Roman Empire)": "副標題(例如:關於羅馬帝國)",
+	"Success": "成功",
+	"Successfully updated.": "更新成功。",
+	"Suggested": "建議",
+	"Support": "支援",
+	"Support this plugin:": "支持這個外掛:",
+	"Sync directory": "同步目錄",
+	"System": "系統",
+	"System Instructions": "",
+	"System Prompt": "系統提示詞",
+	"Tags": "標籤",
+	"Tags Generation Prompt": "",
+	"Tap to interrupt": "點選以中斷",
+	"Tavily API Key": "Tavily API 金鑰",
+	"Tell us more:": "告訴我們更多:",
+	"Temperature": "溫度",
+	"Template": "範本",
+	"Temporary Chat": "臨時對話",
+	"Text Splitter": "",
+	"Text-to-Speech Engine": "文字轉語音引擎",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "感謝您的回饋!",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "這個外掛背後的開發者是來自社群的熱情志願者。如果您覺得這個外掛很有幫助,請考慮為其開發做出貢獻。",
+	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
+	"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "檔案大小上限(MB)。如果檔案大小超過此限制,檔案將不會被上傳。",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "對話中一次可使用的最大檔案數量。如果檔案數量超過此限制,檔案將不會被上傳。",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "分數應該是介於 0.0(0%)和 1.0(100%)之間的值。",
+	"Theme": "主題",
+	"Thinking...": "正在思考...",
+	"This action cannot be undone. Do you wish to continue?": "此操作無法復原。您確定要繼續進行嗎?",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "這確保您寶貴的對話會安全地儲存到您的後端資料庫。謝謝!",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "這是一個實驗性功能,它可能無法如預期運作,並且可能會隨時變更。",
+	"This option will delete all existing files in the collection and replace them with newly uploaded files.": "此選項將刪除集合中的所有現有檔案,並用新上傳的檔案取代它們。",
+	"This response was generated by \"{{model}}\"": "",
+	"This will delete": "這將會刪除",
+	"This will delete <strong>{{NAME}}</strong> and <strong>all its contents</strong>.": "",
+	"This will reset the knowledge base and sync all files. Do you wish to continue?": "這將重設知識庫並同步所有檔案。您確定要繼續嗎?",
+	"Thorough explanation": "詳細解釋",
+	"Tika": "Tika",
+	"Tika Server URL required.": "需要 Tika 伺服器 URL。",
+	"Tiktoken": "",
+	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "提示:在每次替換後按下對話輸入框中的 Tab 鍵,即可連續更新多個變數欄位。",
+	"Title": "標題",
+	"Title (e.g. Tell me a fun fact)": "標題(例如:告訴我一個有趣的事實)",
+	"Title Auto-Generation": "自動產生標題",
+	"Title cannot be an empty string.": "標題不能是空字串。",
+	"Title Generation Prompt": "自動產生標題的提示詞",
+	"To access the available model names for downloading,": "若要存取可供下載的模型名稱,",
+	"To access the GGUF models available for downloading,": "若要存取可供下載的 GGUF 模型,",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "若要存取 WebUI,請聯絡管理員。管理員可以從管理面板管理使用者狀態。",
+	"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "要在此處附加知識庫,請先將它們新增到「知識」工作區。",
+	"to chat input.": "到對話輸入。",
+	"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "若要在此選擇動作,請先將它們新增到「函式」工作區。",
+	"To select filters here, add them to the \"Functions\" workspace first.": "若要在此選擇篩選器,請先將它們新增到「函式」工作區。",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "若要在此選擇工具包,請先將它們新增到「工具」工作區。",
+	"Toast notifications for new updates": "",
+	"Today": "今天",
+	"Toggle settings": "切換設定",
+	"Toggle sidebar": "切換側邊欄",
+	"Token": "",
+	"Tokens To Keep On Context Refresh (num_keep)": "上下文重新整理時要保留的 token 數 (num_keep)",
+	"Too verbose": "",
+	"Tool": "",
+	"Tool created successfully": "成功建立工具",
+	"Tool deleted successfully": "成功刪除工具",
+	"Tool imported successfully": "成功匯入工具",
+	"Tool updated successfully": "成功更新工具",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "工具包描述(例如:用於執行各種操作的工具包)",
+	"Toolkit ID (e.g. my_toolkit)": "工具包 ID(例如:my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "工具包名稱(例如:我的工具包)",
+	"Tools": "工具",
+	"Tools are a function calling system with arbitrary code execution": "工具是一個具有任意程式碼執行功能的函式呼叫系統",
+	"Tools have a function calling system that allows arbitrary code execution": "工具具有允許執行任意程式碼的函式呼叫系統",
+	"Tools have a function calling system that allows arbitrary code execution.": "工具具有允許執行任意程式碼的函式呼叫系統。",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "存取 Ollama 時遇到問題?",
+	"TTS Model": "文字轉語音 (TTS) 模型",
+	"TTS Settings": "文字轉語音 (TTS) 設定",
+	"TTS Voice": "文字轉語音 (TTS) 聲音",
+	"Type": "類型",
+	"Type Hugging Face Resolve (Download) URL": "輸入 Hugging Face 解析(下載)URL",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "哎呀!連線到 {{provider}} 時出現問題。",
+	"UI": "使用者介面",
+	"Unpin": "取消釘選",
+	"Untagged": "",
+	"Update": "更新",
+	"Update and Copy Link": "更新並複製連結",
+	"Update for the latest features and improvements.": "更新以獲得最新功能和改進。",
+	"Update password": "更新密碼",
+	"Updated": "已更新",
+	"Updated at": "更新於",
+	"Updated At": "",
+	"Upload": "上傳",
+	"Upload a GGUF model": "上傳 GGUF 模型",
+	"Upload directory": "上傳目錄",
+	"Upload files": "上傳檔案",
+	"Upload Files": "上傳檔案",
+	"Upload Pipeline": "上傳管線",
+	"Upload Progress": "上傳進度",
+	"URL Mode": "URL 模式",
+	"Use '#' in the prompt input to load and include your knowledge.": "在提示詞輸入中使用 '#' 來載入並包含您的知識。",
+	"Use Gravatar": "使用 Gravatar",
+	"Use Initials": "使用姓名縮寫",
+	"use_mlock (Ollama)": "使用 mlock (Ollama)",
+	"use_mmap (Ollama)": "使用 mmap (Ollama)",
+	"user": "使用者",
+	"User": "",
+	"User location successfully retrieved.": "成功取得使用者位置。",
+	"User Permissions": "使用者權限",
+	"Users": "使用者",
+	"Using the default arena model with all models. Click the plus button to add custom models.": "",
+	"Utilize": "使用",
+	"Valid time units:": "有效的時間單位:",
+	"Valves": "閥門",
+	"Valves updated": "閥門已更新",
+	"Valves updated successfully": "閥門更新成功",
+	"variable": "變數",
+	"variable to have them replaced with clipboard content.": "變數,以便將其替換為剪貼簿內容。",
+	"Version": "版本",
+	"Version {{selectedVersion}} of {{totalVersions}}": "第 {{selectedVersion}} 版,共 {{totalVersions}} 版",
+	"Voice": "語音",
+	"Voice Input": "",
+	"Warning": "警告",
+	"Warning:": "警告:",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "警告:如果您更新或更改嵌入模型,您將需要重新匯入所有文件。",
+	"Web": "網頁",
+	"Web API": "網頁 API",
+	"Web Loader Settings": "網頁載入器設定",
+	"Web Search": "網頁搜尋",
+	"Web Search Engine": "網頁搜尋引擎",
+	"Webhook URL": "Webhook URL",
+	"WebUI Settings": "WebUI 設定",
+	"WebUI will make requests to": "WebUI 將會向以下位址發出請求",
+	"What’s New in": "新功能",
+	"Whisper (Local)": "Whisper(本機)",
+	"Widescreen Mode": "寬螢幕模式",
+	"Won": "",
+	"Workspace": "工作區",
+	"Write a prompt suggestion (e.g. Who are you?)": "撰寫提示詞建議(例如:你是誰?)",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "用 50 字寫一篇總結 [主題或關鍵字] 的摘要。",
+	"Write something...": "",
+	"Yesterday": "昨天",
+	"You": "您",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "您一次最多只能與 {{maxCount}} 個檔案進行對話。",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "您可以透過下方的「管理」按鈕新增記憶,將您與大型語言模型的互動個人化,讓它們更有幫助並更符合您的需求。",
+	"You cannot clone a base model": "您無法複製基礎模型",
+	"You cannot upload an empty file.": "",
+	"You have no archived conversations.": "您沒有已封存的對話。",
+	"You have shared this chat": "您已分享此對話",
+	"You're a helpful assistant.": "您是一位樂於助人的助手。",
+	"You're now logged in.": "您已登入。",
+	"Your account status is currently pending activation.": "您的帳號目前正在等待啟用。",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "您的所有貢獻將會直接交給外掛開發者;Open WebUI 不會收取任何百分比。然而,所選擇的贊助平臺可能有其自身的費用。",
+	"Youtube": "YouTube",
+	"Youtube Loader Settings": "YouTube 載入器設定"
+}
diff --git a/src/lib/index.ts b/src/lib/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..856f2b6c38aec1085db88189bcf492dbb49a1c45
--- /dev/null
+++ b/src/lib/index.ts
@@ -0,0 +1 @@
+// place files you want to import through the `$lib` alias in this folder.
diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..501ac9b8188ede5bf734bed700031b88e7204255
--- /dev/null
+++ b/src/lib/stores/index.ts
@@ -0,0 +1,201 @@
+import { APP_NAME } from '$lib/constants';
+import { type Writable, writable } from 'svelte/store';
+import type { GlobalModelConfig, ModelConfig } from '$lib/apis';
+import type { Banner } from '$lib/types';
+import type { Socket } from 'socket.io-client';
+
+// Backend
+export const WEBUI_NAME = writable(APP_NAME);
+export const config: Writable<Config | undefined> = writable(undefined);
+export const user: Writable<SessionUser | undefined> = writable(undefined);
+
+// Frontend
+export const MODEL_DOWNLOAD_POOL = writable({});
+
+export const mobile = writable(false);
+
+export const socket: Writable<null | Socket> = writable(null);
+export const activeUserCount: Writable<null | number> = writable(null);
+export const USAGE_POOL: Writable<null | string[]> = writable(null);
+
+export const theme = writable('system');
+
+export const chatId = writable('');
+export const chatTitle = writable('');
+
+export const chats = writable([]);
+export const pinnedChats = writable([]);
+export const tags = writable([]);
+
+export const models: Writable<Model[]> = writable([]);
+export const prompts: Writable<Prompt[]> = writable([]);
+export const knowledge: Writable<Document[]> = writable([]);
+
+export const tools = writable([]);
+export const functions = writable([]);
+
+export const banners: Writable<Banner[]> = writable([]);
+
+export const settings: Writable<Settings> = writable({});
+
+export const showSidebar = writable(false);
+export const showSettings = writable(false);
+export const showArchivedChats = writable(false);
+export const showChangelog = writable(false);
+
+export const showControls = writable(false);
+export const showOverview = writable(false);
+export const showArtifacts = writable(false);
+export const showCallOverlay = writable(false);
+
+export const temporaryChatEnabled = writable(false);
+export const scrollPaginationEnabled = writable(false);
+export const currentChatPage = writable(1);
+
+export type Model = OpenAIModel | OllamaModel;
+
+type BaseModel = {
+	id: string;
+	name: string;
+	info?: ModelConfig;
+	owned_by: 'ollama' | 'openai' | 'arena';
+};
+
+export interface OpenAIModel extends BaseModel {
+	owned_by: 'openai';
+	external: boolean;
+	source?: string;
+}
+
+export interface OllamaModel extends BaseModel {
+	owned_by: 'ollama';
+	details: OllamaModelDetails;
+	size: number;
+	description: string;
+	model: string;
+	modified_at: string;
+	digest: string;
+	ollama?: {
+		name?: string;
+		model?: string;
+		modified_at: string;
+		size?: number;
+		digest?: string;
+		details?: {
+			parent_model?: string;
+			format?: string;
+			family?: string;
+			families?: string[];
+			parameter_size?: string;
+			quantization_level?: string;
+		};
+		urls?: number[];
+	};
+}
+
+type OllamaModelDetails = {
+	parent_model: string;
+	format: string;
+	family: string;
+	families: string[] | null;
+	parameter_size: string;
+	quantization_level: string;
+};
+
+type Settings = {
+	models?: string[];
+	conversationMode?: boolean;
+	speechAutoSend?: boolean;
+	responseAutoPlayback?: boolean;
+	audio?: AudioSettings;
+	showUsername?: boolean;
+	notificationEnabled?: boolean;
+	title?: TitleSettings;
+	splitLargeDeltas?: boolean;
+	chatDirection: 'LTR' | 'RTL';
+
+	system?: string;
+	requestFormat?: string;
+	keepAlive?: string;
+	seed?: number;
+	temperature?: string;
+	repeat_penalty?: string;
+	top_k?: string;
+	top_p?: string;
+	num_ctx?: string;
+	num_batch?: string;
+	num_keep?: string;
+	options?: ModelOptions;
+};
+
+type ModelOptions = {
+	stop?: boolean;
+};
+
+type AudioSettings = {
+	STTEngine?: string;
+	TTSEngine?: string;
+	speaker?: string;
+	model?: string;
+	nonLocalVoices?: boolean;
+};
+
+type TitleSettings = {
+	auto?: boolean;
+	model?: string;
+	modelExternal?: string;
+	prompt?: string;
+};
+
+type Prompt = {
+	command: string;
+	user_id: string;
+	title: string;
+	content: string;
+	timestamp: number;
+};
+
+type Document = {
+	collection_name: string;
+	filename: string;
+	name: string;
+	title: string;
+};
+
+type Config = {
+	status: boolean;
+	name: string;
+	version: string;
+	default_locale: string;
+	default_models: string;
+	default_prompt_suggestions: PromptSuggestion[];
+	features: {
+		auth: boolean;
+		auth_trusted_header: boolean;
+		enable_signup: boolean;
+		enable_login_form: boolean;
+		enable_web_search?: boolean;
+		enable_image_generation: boolean;
+		enable_admin_export: boolean;
+		enable_admin_chat_access: boolean;
+		enable_community_sharing: boolean;
+	};
+	oauth: {
+		providers: {
+			[key: string]: string;
+		};
+	};
+};
+
+type PromptSuggestion = {
+	content: string;
+	title: [string, string];
+};
+
+type SessionUser = {
+	id: string;
+	email: string;
+	name: string;
+	role: string;
+	profile_image_url: string;
+};
diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..475ead8fd4e384b30ef85766cf5223bcae6b1e82
--- /dev/null
+++ b/src/lib/types/index.ts
@@ -0,0 +1,15 @@
+export type Banner = {
+	id: string;
+	type: string;
+	title?: string;
+	content: string;
+	url?: string;
+	dismissible?: boolean;
+	timestamp: number;
+};
+
+export enum TTS_RESPONSE_SPLIT {
+	PUNCTUATION = 'punctuation',
+	PARAGRAPHS = 'paragraphs',
+	NONE = 'none'
+}
diff --git a/src/lib/utils/_template_old.ts b/src/lib/utils/_template_old.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f69ef226011117f94de622616dcf85596731dc44
--- /dev/null
+++ b/src/lib/utils/_template_old.ts
@@ -0,0 +1,66 @@
+import { titleGenerationTemplate } from '$lib/utils/index';
+import { expect, test } from 'vitest';
+
+test('titleGenerationTemplate correctly replaces {{prompt}} placeholder', () => {
+	const template = 'Hello {{prompt}}!';
+	const prompt = 'world';
+	const expected = 'Hello world!';
+	const actual = titleGenerationTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('titleGenerationTemplate correctly replaces {{prompt:start:<length>}} placeholder', () => {
+	const template = 'Hello {{prompt:start:3}}!';
+	const prompt = 'world';
+	const expected = 'Hello wor!';
+	const actual = titleGenerationTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('titleGenerationTemplate correctly replaces {{prompt:end:<length>}} placeholder', () => {
+	const template = 'Hello {{prompt:end:3}}!';
+	const prompt = 'world';
+	const expected = 'Hello rld!';
+	const actual = titleGenerationTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('titleGenerationTemplate correctly replaces {{prompt:middletruncate:<length>}} placeholder when prompt length is greater than length', () => {
+	const template = 'Hello {{prompt:middletruncate:4}}!';
+	const prompt = 'world';
+	const expected = 'Hello wo...ld!';
+	const actual = titleGenerationTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('titleGenerationTemplate correctly replaces {{prompt:middletruncate:<length>}} placeholder when prompt length is less than or equal to length', () => {
+	const template = 'Hello {{prompt:middletruncate:5}}!';
+	const prompt = 'world';
+	const expected = 'Hello world!';
+	const actual = titleGenerationTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('titleGenerationTemplate returns original template when no placeholders are present', () => {
+	const template = 'Hello world!';
+	const prompt = 'world';
+	const expected = 'Hello world!';
+	const actual = titleGenerationTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('titleGenerationTemplate does not replace placeholders inside of replaced placeholders', () => {
+	const template = 'Hello {{prompt}}!';
+	const prompt = 'World, {{prompt}} injection';
+	const expected = 'Hello World, {{prompt}} injection!';
+	const actual = titleGenerationTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('titleGenerationTemplate correctly replaces multiple placeholders', () => {
+	const template = 'Hello {{prompt}}! This is {{prompt:start:3}}!';
+	const prompt = 'world';
+	const expected = 'Hello world! This is wor!';
+	const actual = titleGenerationTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
diff --git a/src/lib/utils/characters/index.ts b/src/lib/utils/characters/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..af3436693a69aa978c4127262894fc5059aeeebd
--- /dev/null
+++ b/src/lib/utils/characters/index.ts
@@ -0,0 +1,197 @@
+import CRC32 from 'crc-32';
+
+export const parseFile = async (file) => {
+	if (file.type === 'application/json') {
+		return await parseJsonFile(file);
+	} else if (file.type === 'image/png') {
+		return await parsePngFile(file);
+	} else {
+		throw new Error('Unsupported file type');
+	}
+};
+
+const parseJsonFile = async (file) => {
+	const text = await file.text();
+	const json = JSON.parse(text);
+
+	const character = extractCharacter(json);
+
+	return {
+		file,
+		json,
+		formats: detectFormats(json),
+		character
+	};
+};
+
+const parsePngFile = async (file) => {
+	const arrayBuffer = await file.arrayBuffer();
+	const text = parsePngText(arrayBuffer);
+	const json = JSON.parse(text);
+
+	const image = URL.createObjectURL(file);
+	const character = extractCharacter(json);
+
+	return {
+		file,
+		json,
+		image,
+		formats: detectFormats(json),
+		character
+	};
+};
+
+const parsePngText = (arrayBuffer) => {
+	const textChunkKeyword = 'chara';
+	const chunks = readPngChunks(new Uint8Array(arrayBuffer));
+
+	const textChunk = chunks
+		.filter((chunk) => chunk.type === 'tEXt')
+		.map((chunk) => decodeTextChunk(chunk.data))
+		.find((entry) => entry.keyword === textChunkKeyword);
+
+	if (!textChunk) {
+		throw new Error(`No PNG text chunk named "${textChunkKeyword}" found`);
+	}
+
+	try {
+		return new TextDecoder().decode(Uint8Array.from(atob(textChunk.text), (c) => c.charCodeAt(0)));
+	} catch (e) {
+		throw new Error('Unable to parse "chara" field as base64', e);
+	}
+};
+
+const readPngChunks = (data) => {
+	const isValidPng =
+		data[0] === 0x89 &&
+		data[1] === 0x50 &&
+		data[2] === 0x4e &&
+		data[3] === 0x47 &&
+		data[4] === 0x0d &&
+		data[5] === 0x0a &&
+		data[6] === 0x1a &&
+		data[7] === 0x0a;
+
+	if (!isValidPng) throw new Error('Invalid PNG file');
+
+	let chunks = [];
+	let offset = 8; // Skip PNG signature
+
+	while (offset < data.length) {
+		let length =
+			(data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3];
+		let type = String.fromCharCode.apply(null, data.slice(offset + 4, offset + 8));
+		let chunkData = data.slice(offset + 8, offset + 8 + length);
+		let crc =
+			(data[offset + 8 + length] << 24) |
+			(data[offset + 8 + length + 1] << 16) |
+			(data[offset + 8 + length + 2] << 8) |
+			data[offset + 8 + length + 3];
+
+		if (CRC32.buf(chunkData, CRC32.str(type)) !== crc) {
+			throw new Error(`Invalid CRC for chunk type "${type}"`);
+		}
+
+		chunks.push({ type, data: chunkData, crc });
+		offset += 12 + length;
+	}
+
+	return chunks;
+};
+
+const decodeTextChunk = (data) => {
+	let i = 0;
+	const keyword = [];
+	const text = [];
+
+	for (; i < data.length && data[i] !== 0; i++) {
+		keyword.push(String.fromCharCode(data[i]));
+	}
+
+	for (i++; i < data.length; i++) {
+		text.push(String.fromCharCode(data[i]));
+	}
+
+	return { keyword: keyword.join(''), text: text.join('') };
+};
+
+const extractCharacter = (json) => {
+	function getTrimmedValue(json, keys) {
+		return keys
+			.map((key) => {
+				const keyParts = key.split('.');
+				let value = json;
+				for (const part of keyParts) {
+					if (value && value[part] != null) {
+						value = value[part];
+					} else {
+						value = null;
+						break;
+					}
+				}
+				return value && value.trim();
+			})
+			.find((value) => value);
+	}
+
+	const name = getTrimmedValue(json, ['char_name', 'name', 'data.name']);
+	const summary = getTrimmedValue(json, ['personality', 'title', 'data.description']);
+	const personality = getTrimmedValue(json, ['char_persona', 'description', 'data.personality']);
+	const scenario = getTrimmedValue(json, ['world_scenario', 'scenario', 'data.scenario']);
+	const greeting = getTrimmedValue(json, [
+		'char_greeting',
+		'greeting',
+		'first_mes',
+		'data.first_mes'
+	]);
+	const examples = getTrimmedValue(json, [
+		'example_dialogue',
+		'mes_example',
+		'definition',
+		'data.mes_example'
+	]);
+
+	return { name, summary, personality, scenario, greeting, examples };
+};
+
+const detectFormats = (json) => {
+	const formats = [];
+
+	if (
+		json.char_name &&
+		json.char_persona &&
+		json.world_scenario &&
+		json.char_greeting &&
+		json.example_dialogue
+	)
+		formats.push('Text Generation Character');
+	if (
+		json.name &&
+		json.personality &&
+		json.description &&
+		json.scenario &&
+		json.first_mes &&
+		json.mes_example
+	)
+		formats.push('TavernAI Character');
+	if (
+		json.character &&
+		json.character.name &&
+		json.character.title &&
+		json.character.description &&
+		json.character.greeting &&
+		json.character.definition
+	)
+		formats.push('CharacterAI Character');
+	if (
+		json.info &&
+		json.info.character &&
+		json.info.character.name &&
+		json.info.character.title &&
+		json.info.character.description &&
+		json.info.character.greeting
+	)
+		formats.push('CharacterAI History');
+
+	return formats;
+};
diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bc47d124d2611a95f8f77f4946b9b1f622486921
--- /dev/null
+++ b/src/lib/utils/index.ts
@@ -0,0 +1,910 @@
+import { v4 as uuidv4 } from 'uuid';
+import sha256 from 'js-sha256';
+
+import { WEBUI_BASE_URL } from '$lib/constants';
+import { TTS_RESPONSE_SPLIT } from '$lib/types';
+
+//////////////////////////
+// Helper functions
+//////////////////////////
+
+export const replaceTokens = (content, char, user) => {
+	const charToken = /{{char}}/gi;
+	const userToken = /{{user}}/gi;
+	const videoIdToken = /{{VIDEO_FILE_ID_([a-f0-9-]+)}}/gi; // Regex to capture the video ID
+	const htmlIdToken = /{{HTML_FILE_ID_([a-f0-9-]+)}}/gi; // Regex to capture the HTML ID
+
+	// Replace {{char}} if char is provided
+	if (char !== undefined && char !== null) {
+		content = content.replace(charToken, char);
+	}
+
+	// Replace {{user}} if user is provided
+	if (user !== undefined && user !== null) {
+		content = content.replace(userToken, user);
+	}
+
+	// Replace video ID tags with corresponding <video> elements
+	content = content.replace(videoIdToken, (match, fileId) => {
+		const videoUrl = `${WEBUI_BASE_URL}/api/v1/files/${fileId}/content`;
+		return `<video src="${videoUrl}" controls></video>`;
+	});
+
+	// Replace HTML ID tags with corresponding HTML content
+	content = content.replace(htmlIdToken, (match, fileId) => {
+		const htmlUrl = `${WEBUI_BASE_URL}/api/v1/files/${fileId}/content/html`;
+		return `<iframe src="${htmlUrl}" width="100%" frameborder="0" onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';"></iframe>`;
+	});
+
+	return content;
+};
+
+export const sanitizeResponseContent = (content: string) => {
+	return content
+		.replace(/<\|[a-z]*$/, '')
+		.replace(/<\|[a-z]+\|$/, '')
+		.replace(/<$/, '')
+		.replaceAll(/<\|[a-z]+\|>/g, ' ')
+		.replaceAll('<', '&lt;')
+		.replaceAll('>', '&gt;')
+		.trim();
+};
+
+export const processResponseContent = (content: string) => {
+	return content.trim();
+};
+
+export const revertSanitizedResponseContent = (content: string) => {
+	return content.replaceAll('&lt;', '<').replaceAll('&gt;', '>');
+};
+
+export function unescapeHtml(html: string) {
+	const doc = new DOMParser().parseFromString(html, 'text/html');
+	return doc.documentElement.textContent;
+}
+
+export const capitalizeFirstLetter = (string) => {
+	return string.charAt(0).toUpperCase() + string.slice(1);
+};
+
+export const splitStream = (splitOn) => {
+	let buffer = '';
+	return new TransformStream({
+		transform(chunk, controller) {
+			buffer += chunk;
+			const parts = buffer.split(splitOn);
+			parts.slice(0, -1).forEach((part) => controller.enqueue(part));
+			buffer = parts[parts.length - 1];
+		},
+		flush(controller) {
+			if (buffer) controller.enqueue(buffer);
+		}
+	});
+};
+
+export const convertMessagesToHistory = (messages) => {
+	const history = {
+		messages: {},
+		currentId: null
+	};
+
+	let parentMessageId = null;
+	let messageId = null;
+
+	for (const message of messages) {
+		messageId = uuidv4();
+
+		if (parentMessageId !== null) {
+			history.messages[parentMessageId].childrenIds = [
+				...history.messages[parentMessageId].childrenIds,
+				messageId
+			];
+		}
+
+		history.messages[messageId] = {
+			...message,
+			id: messageId,
+			parentId: parentMessageId,
+			childrenIds: []
+		};
+
+		parentMessageId = messageId;
+	}
+
+	history.currentId = messageId;
+	return history;
+};
+
+export const getGravatarURL = (email) => {
+	// Trim leading and trailing whitespace from
+	// an email address and force all characters
+	// to lower case
+	const address = String(email).trim().toLowerCase();
+
+	// Create a SHA256 hash of the final string
+	const hash = sha256(address);
+
+	// Grab the actual image URL
+	return `https://www.gravatar.com/avatar/${hash}`;
+};
+
+export const canvasPixelTest = () => {
+	// Test a 1x1 pixel to potentially identify browser/plugin fingerprint blocking or spoofing
+	// Inspiration: https://github.com/kkapsner/CanvasBlocker/blob/master/test/detectionTest.js
+	const canvas = document.createElement('canvas');
+	const ctx = canvas.getContext('2d');
+	canvas.height = 1;
+	canvas.width = 1;
+	const imageData = new ImageData(canvas.width, canvas.height);
+	const pixelValues = imageData.data;
+
+	// Generate RGB test data
+	for (let i = 0; i < imageData.data.length; i += 1) {
+		if (i % 4 !== 3) {
+			pixelValues[i] = Math.floor(256 * Math.random());
+		} else {
+			pixelValues[i] = 255;
+		}
+	}
+
+	ctx.putImageData(imageData, 0, 0);
+	const p = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+
+	// Read RGB data and fail if unmatched
+	for (let i = 0; i < p.length; i += 1) {
+		if (p[i] !== pixelValues[i]) {
+			console.log(
+				'canvasPixelTest: Wrong canvas pixel RGB value detected:',
+				p[i],
+				'at:',
+				i,
+				'expected:',
+				pixelValues[i]
+			);
+			console.log('canvasPixelTest: Canvas blocking or spoofing is likely');
+			return false;
+		}
+	}
+
+	return true;
+};
+
+export const generateInitialsImage = (name) => {
+	const canvas = document.createElement('canvas');
+	const ctx = canvas.getContext('2d');
+	canvas.width = 100;
+	canvas.height = 100;
+
+	if (!canvasPixelTest()) {
+		console.log(
+			'generateInitialsImage: failed pixel test, fingerprint evasion is likely. Using default image.'
+		);
+		return '/user.png';
+	}
+
+	ctx.fillStyle = '#F39C12';
+	ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+	ctx.fillStyle = '#FFFFFF';
+	ctx.font = '40px Helvetica';
+	ctx.textAlign = 'center';
+	ctx.textBaseline = 'middle';
+
+	const sanitizedName = name.trim();
+	const initials =
+		sanitizedName.length > 0
+			? sanitizedName[0] +
+				(sanitizedName.split(' ').length > 1
+					? sanitizedName[sanitizedName.lastIndexOf(' ') + 1]
+					: '')
+			: '';
+
+	ctx.fillText(initials.toUpperCase(), canvas.width / 2, canvas.height / 2);
+
+	return canvas.toDataURL();
+};
+
+export const copyToClipboard = async (text) => {
+	let result = false;
+	if (!navigator.clipboard) {
+		const textArea = document.createElement('textarea');
+		textArea.value = text;
+
+		// Avoid scrolling to bottom
+		textArea.style.top = '0';
+		textArea.style.left = '0';
+		textArea.style.position = 'fixed';
+
+		document.body.appendChild(textArea);
+		textArea.focus();
+		textArea.select();
+
+		try {
+			const successful = document.execCommand('copy');
+			const msg = successful ? 'successful' : 'unsuccessful';
+			console.log('Fallback: Copying text command was ' + msg);
+			result = true;
+		} catch (err) {
+			console.error('Fallback: Oops, unable to copy', err);
+		}
+
+		document.body.removeChild(textArea);
+		return result;
+	}
+
+	result = await navigator.clipboard
+		.writeText(text)
+		.then(() => {
+			console.log('Async: Copying to clipboard was successful!');
+			return true;
+		})
+		.catch((error) => {
+			console.error('Async: Could not copy text: ', error);
+			return false;
+		});
+
+	return result;
+};
+
+export const compareVersion = (latest, current) => {
+	return current === '0.0.0'
+		? false
+		: current.localeCompare(latest, undefined, {
+				numeric: true,
+				sensitivity: 'case',
+				caseFirst: 'upper'
+			}) < 0;
+};
+
+export const findWordIndices = (text) => {
+	const regex = /\[([^\]]+)\]/g;
+	const matches = [];
+	let match;
+
+	while ((match = regex.exec(text)) !== null) {
+		matches.push({
+			word: match[1],
+			startIndex: match.index,
+			endIndex: regex.lastIndex - 1
+		});
+	}
+
+	return matches;
+};
+
+export const removeLastWordFromString = (inputString, wordString) => {
+	console.log('inputString', inputString);
+	// Split the string by newline characters to handle lines separately
+	const lines = inputString.split('\n');
+
+	// Take the last line to operate only on it
+	const lastLine = lines.pop();
+
+	// Split the last line into an array of words
+	const words = lastLine.split(' ');
+
+	// Conditional to check for the last word removal
+	if (words.at(-1) === wordString || (wordString === '' && words.at(-1) === '\\#')) {
+		words.pop(); // Remove last word if condition is satisfied
+	}
+
+	// Join the remaining words back into a string and handle space correctly
+	let updatedLastLine = words.join(' ');
+
+	// Add a trailing space to the updated last line if there are still words
+	if (updatedLastLine !== '') {
+		updatedLastLine += ' ';
+	}
+
+	// Combine the lines together again, placing the updated last line back in
+	const resultString = [...lines, updatedLastLine].join('\n');
+
+	// Return the final string
+	console.log('resultString', resultString);
+
+	return resultString;
+};
+
+export const removeFirstHashWord = (inputString) => {
+	// Split the string into an array of words
+	const words = inputString.split(' ');
+
+	// Find the index of the first word that starts with #
+	const index = words.findIndex((word) => word.startsWith('#'));
+
+	// Remove the first word with #
+	if (index !== -1) {
+		words.splice(index, 1);
+	}
+
+	// Join the remaining words back into a string
+	const resultString = words.join(' ');
+
+	return resultString;
+};
+
+export const transformFileName = (fileName) => {
+	// Convert to lowercase
+	const lowerCaseFileName = fileName.toLowerCase();
+
+	// Remove special characters using regular expression
+	const sanitizedFileName = lowerCaseFileName.replace(/[^\w\s]/g, '');
+
+	// Replace spaces with dashes
+	const finalFileName = sanitizedFileName.replace(/\s+/g, '-');
+
+	return finalFileName;
+};
+
+export const calculateSHA256 = async (file) => {
+	// Create a FileReader to read the file asynchronously
+	const reader = new FileReader();
+
+	// Define a promise to handle the file reading
+	const readFile = new Promise((resolve, reject) => {
+		reader.onload = () => resolve(reader.result);
+		reader.onerror = reject;
+	});
+
+	// Read the file as an ArrayBuffer
+	reader.readAsArrayBuffer(file);
+
+	try {
+		// Wait for the FileReader to finish reading the file
+		const buffer = await readFile;
+
+		// Convert the ArrayBuffer to a Uint8Array
+		const uint8Array = new Uint8Array(buffer);
+
+		// Calculate the SHA-256 hash using Web Crypto API
+		const hashBuffer = await crypto.subtle.digest('SHA-256', uint8Array);
+
+		// Convert the hash to a hexadecimal string
+		const hashArray = Array.from(new Uint8Array(hashBuffer));
+		const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, '0')).join('');
+
+		return `${hashHex}`;
+	} catch (error) {
+		console.error('Error calculating SHA-256 hash:', error);
+		throw error;
+	}
+};
+
+export const getImportOrigin = (_chats) => {
+	// Check what external service chat imports are from
+	if ('mapping' in _chats[0]) {
+		return 'openai';
+	}
+	return 'webui';
+};
+
+export const getUserPosition = async (raw = false) => {
+	// Get the user's location using the Geolocation API
+	const position = await new Promise((resolve, reject) => {
+		navigator.geolocation.getCurrentPosition(resolve, reject);
+	}).catch((error) => {
+		console.error('Error getting user location:', error);
+		throw error;
+	});
+
+	if (!position) {
+		return 'Location not available';
+	}
+
+	// Extract the latitude and longitude from the position
+	const { latitude, longitude } = position.coords;
+
+	if (raw) {
+		return { latitude, longitude };
+	} else {
+		return `${latitude.toFixed(3)}, ${longitude.toFixed(3)} (lat, long)`;
+	}
+};
+
+const convertOpenAIMessages = (convo) => {
+	// Parse OpenAI chat messages and create chat dictionary for creating new chats
+	const mapping = convo['mapping'];
+	const messages = [];
+	let currentId = '';
+	let lastId = null;
+
+	for (const message_id in mapping) {
+		const message = mapping[message_id];
+		currentId = message_id;
+		try {
+			if (
+				messages.length == 0 &&
+				(message['message'] == null ||
+					(message['message']['content']['parts']?.[0] == '' &&
+						message['message']['content']['text'] == null))
+			) {
+				// Skip chat messages with no content
+				continue;
+			} else {
+				const new_chat = {
+					id: message_id,
+					parentId: lastId,
+					childrenIds: message['children'] || [],
+					role: message['message']?.['author']?.['role'] !== 'user' ? 'assistant' : 'user',
+					content:
+						message['message']?.['content']?.['parts']?.[0] ||
+						message['message']?.['content']?.['text'] ||
+						'',
+					model: 'gpt-3.5-turbo',
+					done: true,
+					context: null
+				};
+				messages.push(new_chat);
+				lastId = currentId;
+			}
+		} catch (error) {
+			console.log('Error with', message, '\nError:', error);
+		}
+	}
+
+	const history: Record<PropertyKey, (typeof messages)[number]> = {};
+	messages.forEach((obj) => (history[obj.id] = obj));
+
+	const chat = {
+		history: {
+			currentId: currentId,
+			messages: history // Need to convert this to not a list and instead a json object
+		},
+		models: ['gpt-3.5-turbo'],
+		messages: messages,
+		options: {},
+		timestamp: convo['create_time'],
+		title: convo['title'] ?? 'New Chat'
+	};
+	return chat;
+};
+
+const validateChat = (chat) => {
+	// Because ChatGPT sometimes has features we can't use like DALL-E or might have corrupted messages, need to validate
+	const messages = chat.messages;
+
+	// Check if messages array is empty
+	if (messages.length === 0) {
+		return false;
+	}
+
+	// Last message's children should be an empty array
+	const lastMessage = messages[messages.length - 1];
+	if (lastMessage.childrenIds.length !== 0) {
+		return false;
+	}
+
+	// First message's parent should be null
+	const firstMessage = messages[0];
+	if (firstMessage.parentId !== null) {
+		return false;
+	}
+
+	// Every message's content should be a string
+	for (const message of messages) {
+		if (typeof message.content !== 'string') {
+			return false;
+		}
+	}
+
+	return true;
+};
+
+export const convertOpenAIChats = (_chats) => {
+	// Create a list of dictionaries with each conversation from import
+	const chats = [];
+	let failed = 0;
+	for (const convo of _chats) {
+		const chat = convertOpenAIMessages(convo);
+
+		if (validateChat(chat)) {
+			chats.push({
+				id: convo['id'],
+				user_id: '',
+				title: convo['title'],
+				chat: chat,
+				timestamp: convo['timestamp']
+			});
+		} else {
+			failed++;
+		}
+	}
+	console.log(failed, 'Conversations could not be imported');
+	return chats;
+};
+
+export const isValidHttpUrl = (string: string) => {
+	let url;
+
+	try {
+		url = new URL(string);
+	} catch (_) {
+		return false;
+	}
+
+	return url.protocol === 'http:' || url.protocol === 'https:';
+};
+
+export const removeEmojis = (str: string) => {
+	// Regular expression to match emojis
+	const emojiRegex = /[\uD800-\uDBFF][\uDC00-\uDFFF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g;
+
+	// Replace emojis with an empty string
+	return str.replace(emojiRegex, '');
+};
+
+export const removeFormattings = (str: string) => {
+	return str.replace(/(\*)(.*?)\1/g, '').replace(/(```)(.*?)\1/gs, '');
+};
+
+export const cleanText = (content: string) => {
+	return removeFormattings(removeEmojis(content.trim()));
+};
+
+// This regular expression matches code blocks marked by triple backticks
+const codeBlockRegex = /```[\s\S]*?```/g;
+
+export const extractSentences = (text: string) => {
+	const codeBlocks: string[] = [];
+	let index = 0;
+
+	// Temporarily replace code blocks with placeholders and store the blocks separately
+	text = text.replace(codeBlockRegex, (match) => {
+		const placeholder = `\u0000${index}\u0000`; // Use a unique placeholder
+		codeBlocks[index++] = match;
+		return placeholder;
+	});
+
+	// Split the modified text into sentences based on common punctuation marks, avoiding these blocks
+	let sentences = text.split(/(?<=[.!?])\s+/);
+
+	// Restore code blocks and process sentences
+	sentences = sentences.map((sentence) => {
+		// Check if the sentence includes a placeholder for a code block
+		return sentence.replace(/\u0000(\d+)\u0000/g, (_, idx) => codeBlocks[idx]);
+	});
+
+	return sentences.map(cleanText).filter(Boolean);
+};
+
+export const extractParagraphsForAudio = (text: string) => {
+	const codeBlocks: string[] = [];
+	let index = 0;
+
+	// Temporarily replace code blocks with placeholders and store the blocks separately
+	text = text.replace(codeBlockRegex, (match) => {
+		const placeholder = `\u0000${index}\u0000`; // Use a unique placeholder
+		codeBlocks[index++] = match;
+		return placeholder;
+	});
+
+	// Split the modified text into paragraphs based on newlines, avoiding these blocks
+	let paragraphs = text.split(/\n+/);
+
+	// Restore code blocks and process paragraphs
+	paragraphs = paragraphs.map((paragraph) => {
+		// Check if the paragraph includes a placeholder for a code block
+		return paragraph.replace(/\u0000(\d+)\u0000/g, (_, idx) => codeBlocks[idx]);
+	});
+
+	return paragraphs.map(cleanText).filter(Boolean);
+};
+
+export const extractSentencesForAudio = (text: string) => {
+	return extractSentences(text).reduce((mergedTexts, currentText) => {
+		const lastIndex = mergedTexts.length - 1;
+		if (lastIndex >= 0) {
+			const previousText = mergedTexts[lastIndex];
+			const wordCount = previousText.split(/\s+/).length;
+			const charCount = previousText.length;
+			if (wordCount < 4 || charCount < 50) {
+				mergedTexts[lastIndex] = previousText + ' ' + currentText;
+			} else {
+				mergedTexts.push(currentText);
+			}
+		} else {
+			mergedTexts.push(currentText);
+		}
+		return mergedTexts;
+	}, [] as string[]);
+};
+
+export const getMessageContentParts = (content: string, split_on: string = 'punctuation') => {
+	const messageContentParts: string[] = [];
+
+	switch (split_on) {
+		default:
+		case TTS_RESPONSE_SPLIT.PUNCTUATION:
+			messageContentParts.push(...extractSentencesForAudio(content));
+			break;
+		case TTS_RESPONSE_SPLIT.PARAGRAPHS:
+			messageContentParts.push(...extractParagraphsForAudio(content));
+			break;
+		case TTS_RESPONSE_SPLIT.NONE:
+			messageContentParts.push(cleanText(content));
+			break;
+	}
+
+	return messageContentParts;
+};
+
+export const blobToFile = (blob, fileName) => {
+	// Create a new File object from the Blob
+	const file = new File([blob], fileName, { type: blob.type });
+	return file;
+};
+
+/**
+ * @param {string} template - The template string containing placeholders.
+ * @returns {string} The template string with the placeholders replaced by the prompt.
+ */
+export const promptTemplate = (
+	template: string,
+	user_name?: string,
+	user_location?: string
+): string => {
+	// Get the current date
+	const currentDate = new Date();
+
+	// Format the date to YYYY-MM-DD
+	const formattedDate =
+		currentDate.getFullYear() +
+		'-' +
+		String(currentDate.getMonth() + 1).padStart(2, '0') +
+		'-' +
+		String(currentDate.getDate()).padStart(2, '0');
+
+	// Format the time to HH:MM:SS AM/PM
+	const currentTime = currentDate.toLocaleTimeString('en-US', {
+		hour: 'numeric',
+		minute: 'numeric',
+		second: 'numeric',
+		hour12: true
+	});
+
+	// Get the current weekday
+	const currentWeekday = getWeekday();
+
+	// Get the user's timezone
+	const currentTimezone = getUserTimezone();
+
+	// Get the user's language
+	const userLanguage = localStorage.getItem('locale') || 'en-US';
+
+	// Replace {{CURRENT_DATETIME}} in the template with the formatted datetime
+	template = template.replace('{{CURRENT_DATETIME}}', `${formattedDate} ${currentTime}`);
+
+	// Replace {{CURRENT_DATE}} in the template with the formatted date
+	template = template.replace('{{CURRENT_DATE}}', formattedDate);
+
+	// Replace {{CURRENT_TIME}} in the template with the formatted time
+	template = template.replace('{{CURRENT_TIME}}', currentTime);
+
+	// Replace {{CURRENT_WEEKDAY}} in the template with the current weekday
+	template = template.replace('{{CURRENT_WEEKDAY}}', currentWeekday);
+
+	// Replace {{CURRENT_TIMEZONE}} in the template with the user's timezone
+	template = template.replace('{{CURRENT_TIMEZONE}}', currentTimezone);
+
+	// Replace {{USER_LANGUAGE}} in the template with the user's language
+	template = template.replace('{{USER_LANGUAGE}}', userLanguage);
+
+	if (user_name) {
+		// Replace {{USER_NAME}} in the template with the user's name
+		template = template.replace('{{USER_NAME}}', user_name);
+	}
+
+	if (user_location) {
+		// Replace {{USER_LOCATION}} in the template with the current location
+		template = template.replace('{{USER_LOCATION}}', user_location);
+	}
+
+	return template;
+};
+
+/**
+ * This function is used to replace placeholders in a template string with the provided prompt.
+ * The placeholders can be in the following formats:
+ * - `{{prompt}}`: This will be replaced with the entire prompt.
+ * - `{{prompt:start:<length>}}`: This will be replaced with the first <length> characters of the prompt.
+ * - `{{prompt:end:<length>}}`: This will be replaced with the last <length> characters of the prompt.
+ * - `{{prompt:middletruncate:<length>}}`: This will be replaced with the prompt truncated to <length> characters, with '...' in the middle.
+ *
+ * @param {string} template - The template string containing placeholders.
+ * @param {string} prompt - The string to replace the placeholders with.
+ * @returns {string} The template string with the placeholders replaced by the prompt.
+ */
+export const titleGenerationTemplate = (template: string, prompt: string): string => {
+	template = template.replace(
+		/{{prompt}}|{{prompt:start:(\d+)}}|{{prompt:end:(\d+)}}|{{prompt:middletruncate:(\d+)}}/g,
+		(match, startLength, endLength, middleLength) => {
+			if (match === '{{prompt}}') {
+				return prompt;
+			} else if (match.startsWith('{{prompt:start:')) {
+				return prompt.substring(0, startLength);
+			} else if (match.startsWith('{{prompt:end:')) {
+				return prompt.slice(-endLength);
+			} else if (match.startsWith('{{prompt:middletruncate:')) {
+				if (prompt.length <= middleLength) {
+					return prompt;
+				}
+				const start = prompt.slice(0, Math.ceil(middleLength / 2));
+				const end = prompt.slice(-Math.floor(middleLength / 2));
+				return `${start}...${end}`;
+			}
+			return '';
+		}
+	);
+
+	template = promptTemplate(template);
+
+	return template;
+};
+
+export const approximateToHumanReadable = (nanoseconds: number) => {
+	const seconds = Math.floor((nanoseconds / 1e9) % 60);
+	const minutes = Math.floor((nanoseconds / 6e10) % 60);
+	const hours = Math.floor((nanoseconds / 3.6e12) % 24);
+
+	const results: string[] = [];
+
+	if (seconds >= 0) {
+		results.push(`${seconds}s`);
+	}
+
+	if (minutes > 0) {
+		results.push(`${minutes}m`);
+	}
+
+	if (hours > 0) {
+		results.push(`${hours}h`);
+	}
+
+	return results.reverse().join(' ');
+};
+
+export const getTimeRange = (timestamp) => {
+	const now = new Date();
+	const date = new Date(timestamp * 1000); // Convert Unix timestamp to milliseconds
+
+	// Calculate the difference in milliseconds
+	const diffTime = now.getTime() - date.getTime();
+	const diffDays = diffTime / (1000 * 3600 * 24);
+
+	const nowDate = now.getDate();
+	const nowMonth = now.getMonth();
+	const nowYear = now.getFullYear();
+
+	const dateDate = date.getDate();
+	const dateMonth = date.getMonth();
+	const dateYear = date.getFullYear();
+
+	if (nowYear === dateYear && nowMonth === dateMonth && nowDate === dateDate) {
+		return 'Today';
+	} else if (nowYear === dateYear && nowMonth === dateMonth && nowDate - dateDate === 1) {
+		return 'Yesterday';
+	} else if (diffDays <= 7) {
+		return 'Previous 7 days';
+	} else if (diffDays <= 30) {
+		return 'Previous 30 days';
+	} else if (nowYear === dateYear) {
+		return date.toLocaleString('default', { month: 'long' });
+	} else {
+		return date.getFullYear().toString();
+	}
+};
+
+/**
+ * Extract frontmatter as a dictionary from the specified content string.
+ * @param content {string} - The content string with potential frontmatter.
+ * @returns {Object} - The extracted frontmatter as a dictionary.
+ */
+export const extractFrontmatter = (content) => {
+	const frontmatter = {};
+	let frontmatterStarted = false;
+	let frontmatterEnded = false;
+	const frontmatterPattern = /^\s*([a-z_]+):\s*(.*)\s*$/i;
+
+	// Split content into lines
+	const lines = content.split('\n');
+
+	// Check if the content starts with triple quotes
+	if (lines[0].trim() !== '"""') {
+		return {};
+	}
+
+	frontmatterStarted = true;
+
+	for (let i = 1; i < lines.length; i++) {
+		const line = lines[i];
+
+		if (line.includes('"""')) {
+			if (frontmatterStarted) {
+				frontmatterEnded = true;
+				break;
+			}
+		}
+
+		if (frontmatterStarted && !frontmatterEnded) {
+			const match = frontmatterPattern.exec(line);
+			if (match) {
+				const [, key, value] = match;
+				frontmatter[key.trim()] = value.trim();
+			}
+		}
+	}
+
+	return frontmatter;
+};
+
+// Function to determine the best matching language
+export const bestMatchingLanguage = (supportedLanguages, preferredLanguages, defaultLocale) => {
+	const languages = supportedLanguages.map((lang) => lang.code);
+
+	const match = preferredLanguages
+		.map((prefLang) => languages.find((lang) => lang.startsWith(prefLang)))
+		.find(Boolean);
+
+	return match || defaultLocale;
+};
+
+// Get the date in the format YYYY-MM-DD
+export const getFormattedDate = () => {
+	const date = new Date();
+	return date.toISOString().split('T')[0];
+};
+
+// Get the time in the format HH:MM:SS
+export const getFormattedTime = () => {
+	const date = new Date();
+	return date.toTimeString().split(' ')[0];
+};
+
+// Get the current date and time in the format YYYY-MM-DD HH:MM:SS
+export const getCurrentDateTime = () => {
+	return `${getFormattedDate()} ${getFormattedTime()}`;
+};
+
+// Get the user's timezone
+export const getUserTimezone = () => {
+	return Intl.DateTimeFormat().resolvedOptions().timeZone;
+};
+
+// Get the weekday
+export const getWeekday = () => {
+	const date = new Date();
+	const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+	return weekdays[date.getDay()];
+};
+
+export const createMessagesList = (history, messageId) => {
+	if (messageId === null) {
+		return [];
+	}
+
+	const message = history.messages[messageId];
+	if (message?.parentId) {
+		return [...createMessagesList(history, message.parentId), message];
+	} else {
+		return [message];
+	}
+};
+
+export const formatFileSize = (size) => {
+	if (size == null) return 'Unknown size';
+	if (typeof size !== 'number' || size < 0) return 'Invalid size';
+	if (size === 0) return '0 B';
+	const units = ['B', 'KB', 'MB', 'GB', 'TB'];
+	let unitIndex = 0;
+
+	while (size >= 1024 && unitIndex < units.length - 1) {
+		size /= 1024;
+		unitIndex++;
+	}
+	return `${size.toFixed(1)} ${units[unitIndex]}`;
+};
+
+export const getLineCount = (text) => {
+	console.log(typeof text);
+	return text ? text.split('\n').length : 0;
+};
diff --git a/src/lib/utils/marked/extension.ts b/src/lib/utils/marked/extension.ts
new file mode 100644
index 0000000000000000000000000000000000000000..394b6906a12b041fd5c4f37433f0a169508b9ee9
--- /dev/null
+++ b/src/lib/utils/marked/extension.ts
@@ -0,0 +1,70 @@
+// Helper function to find matching closing tag
+function findMatchingClosingTag(src, openTag, closeTag) {
+	let depth = 1;
+	let index = openTag.length;
+	while (depth > 0 && index < src.length) {
+		if (src.startsWith(openTag, index)) {
+			depth++;
+		} else if (src.startsWith(closeTag, index)) {
+			depth--;
+		}
+		if (depth > 0) {
+			index++;
+		}
+	}
+	return depth === 0 ? index + closeTag.length : -1;
+}
+
+function detailsTokenizer(src) {
+	const detailsRegex = /^<details>\n/;
+	const summaryRegex = /^<summary>(.*?)<\/summary>\n/;
+
+	if (detailsRegex.test(src)) {
+		const endIndex = findMatchingClosingTag(src, '<details>', '</details>');
+		if (endIndex === -1) return;
+
+		const fullMatch = src.slice(0, endIndex);
+		let content = fullMatch.slice(10, -10).trim(); // Remove <details> and </details>
+
+		let summary = '';
+		const summaryMatch = summaryRegex.exec(content);
+		if (summaryMatch) {
+			summary = summaryMatch[1].trim();
+			content = content.slice(summaryMatch[0].length).trim();
+		}
+
+		return {
+			type: 'details',
+			raw: fullMatch,
+			summary: summary,
+			text: content
+		};
+	}
+}
+
+function detailsStart(src) {
+	return src.match(/^<details>/) ? 0 : -1;
+}
+
+function detailsRenderer(token) {
+	return `<details>
+  ${token.summary ? `<summary>${token.summary}</summary>` : ''}
+  ${token.text}
+  </details>`;
+}
+
+function detailsExtension() {
+	return {
+		name: 'details',
+		level: 'block',
+		start: detailsStart,
+		tokenizer: detailsTokenizer,
+		renderer: detailsRenderer
+	};
+}
+
+export default function (options = {}) {
+	return {
+		extensions: [detailsExtension(options)]
+	};
+}
diff --git a/src/lib/utils/marked/katex-extension.ts b/src/lib/utils/marked/katex-extension.ts
new file mode 100644
index 0000000000000000000000000000000000000000..45374f2f340331293abc183a470eb03ca179c230
--- /dev/null
+++ b/src/lib/utils/marked/katex-extension.ts
@@ -0,0 +1,152 @@
+import katex from 'katex';
+
+const DELIMITER_LIST = [
+	{ left: '$$', right: '$$', display: true },
+	{ left: '$', right: '$', display: false },
+	{ left: '\\pu{', right: '}', display: false },
+	{ left: '\\ce{', right: '}', display: false },
+	{ left: '\\(', right: '\\)', display: false },
+	{ left: '\\[', right: '\\]', display: true },
+	{ left: '\\begin{equation}', right: '\\end{equation}', display: true }
+];
+
+// const DELIMITER_LIST = [
+//     { left: '$$', right: '$$', display: false },
+//     { left: '$', right: '$', display: false },
+// ];
+
+// const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1(?=[\s?!\.,:?!。,:]|$)/;
+// const blockRule = /^(\${1,2})\n((?:\\[^]|[^\\])+?)\n\1(?:\n|$)/;
+
+let inlinePatterns = [];
+let blockPatterns = [];
+
+function escapeRegex(string) {
+	return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+}
+
+function generateRegexRules(delimiters) {
+	delimiters.forEach((delimiter) => {
+		const { left, right, display } = delimiter;
+		// Ensure regex-safe delimiters
+		const escapedLeft = escapeRegex(left);
+		const escapedRight = escapeRegex(right);
+
+		if (!display) {
+			// For inline delimiters, we match everything
+			inlinePatterns.push(`${escapedLeft}((?:\\\\[^]|[^\\\\])+?)${escapedRight}`);
+		} else {
+			// Block delimiters doubles as inline delimiters when not followed by a newline
+			inlinePatterns.push(`${escapedLeft}(?!\\n)((?:\\\\[^]|[^\\\\])+?)(?!\\n)${escapedRight}`);
+			blockPatterns.push(`${escapedLeft}\\n((?:\\\\[^]|[^\\\\])+?)\\n${escapedRight}`);
+		}
+	});
+
+	// Math formulas can end in special characters
+	const inlineRule = new RegExp(
+		`^(${inlinePatterns.join('|')})(?=[\\s?。,!-\/:-@[-\`{-~]|$)`,
+		'u'
+	);
+	const blockRule = new RegExp(`^(${blockPatterns.join('|')})(?=[\\s?。,!-\/:-@[-\`{-~]|$)`, 'u');
+
+	return { inlineRule, blockRule };
+}
+
+const { inlineRule, blockRule } = generateRegexRules(DELIMITER_LIST);
+
+export default function (options = {}) {
+	return {
+		extensions: [inlineKatex(options), blockKatex(options)]
+	};
+}
+
+function katexStart(src, displayMode: boolean) {
+	let ruleReg = displayMode ? blockRule : inlineRule;
+
+	let indexSrc = src;
+
+	while (indexSrc) {
+		let index = -1;
+		let startIndex = -1;
+		let startDelimiter = '';
+		let endDelimiter = '';
+		for (let delimiter of DELIMITER_LIST) {
+			if (delimiter.display !== displayMode) {
+				continue;
+			}
+
+			startIndex = indexSrc.indexOf(delimiter.left);
+			if (startIndex === -1) {
+				continue;
+			}
+
+			index = startIndex;
+			startDelimiter = delimiter.left;
+			endDelimiter = delimiter.right;
+		}
+
+		if (index === -1) {
+			return;
+		}
+
+		// Check if the delimiter is preceded by a special character.
+		// If it does, then it's potentially a math formula.
+		const f = index === 0 || indexSrc.charAt(index - 1).match(/[\s?。,!-\/:-@[-`{-~]/);
+		if (f) {
+			const possibleKatex = indexSrc.substring(index);
+
+			if (possibleKatex.match(ruleReg)) {
+				return index;
+			}
+		}
+
+		indexSrc = indexSrc.substring(index + startDelimiter.length).replace(endDelimiter, '');
+	}
+}
+
+function katexTokenizer(src, tokens, displayMode: boolean) {
+	let ruleReg = displayMode ? blockRule : inlineRule;
+	let type = displayMode ? 'blockKatex' : 'inlineKatex';
+
+	const match = src.match(ruleReg);
+
+	if (match) {
+		const text = match
+			.slice(2)
+			.filter((item) => item)
+			.find((item) => item.trim());
+
+		return {
+			type,
+			raw: match[0],
+			text: text,
+			displayMode
+		};
+	}
+}
+
+function inlineKatex(options) {
+	return {
+		name: 'inlineKatex',
+		level: 'inline',
+		start(src) {
+			return katexStart(src, false);
+		},
+		tokenizer(src, tokens) {
+			return katexTokenizer(src, tokens, false);
+		}
+	};
+}
+
+function blockKatex(options) {
+	return {
+		name: 'blockKatex',
+		level: 'block',
+		start(src) {
+			return katexStart(src, true);
+		},
+		tokenizer(src, tokens) {
+			return katexTokenizer(src, tokens, true);
+		}
+	};
+}
diff --git a/src/lib/utils/rag/index.ts b/src/lib/utils/rag/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6523bb7dff16fb98ea4c2a25f8969527d1d4f490
--- /dev/null
+++ b/src/lib/utils/rag/index.ts
@@ -0,0 +1,24 @@
+import { getRAGTemplate } from '$lib/apis/retrieval';
+
+export const RAGTemplate = async (token: string, context: string, query: string) => {
+	let template = await getRAGTemplate(token).catch(() => {
+		return `Use the following context as your learned knowledge, inside <context></context> XML tags.
+		<context>
+		  [context]
+		</context>
+		
+		When answer to user:
+		- If you don't know, just say that you don't know.
+		- If you don't know when you are not sure, ask for clarification.
+		Avoid mentioning that you obtained the information from the context.
+		And answer according to the language of the user's question.
+				
+		Given the context information, answer the query.
+		Query: [query]`;
+	});
+
+	template = template.replace(/\[context\]/g, context);
+	template = template.replace(/\[query\]/g, query);
+
+	return template;
+};
diff --git a/src/lib/utils/transitions/index.ts b/src/lib/utils/transitions/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9d919f561e6483ba6b20f2af29c243e904fdee57
--- /dev/null
+++ b/src/lib/utils/transitions/index.ts
@@ -0,0 +1,48 @@
+import { cubicOut } from 'svelte/easing';
+import type { TransitionConfig } from 'svelte/transition';
+
+type FlyAndScaleParams = {
+	y?: number;
+	start?: number;
+	duration?: number;
+};
+
+const defaultFlyAndScaleParams = { y: -8, start: 0.95, duration: 200 };
+
+export const flyAndScale = (node: Element, params?: FlyAndScaleParams): TransitionConfig => {
+	const style = getComputedStyle(node);
+	const transform = style.transform === 'none' ? '' : style.transform;
+	const withDefaults = { ...defaultFlyAndScaleParams, ...params };
+
+	const scaleConversion = (valueA: number, scaleA: [number, number], scaleB: [number, number]) => {
+		const [minA, maxA] = scaleA;
+		const [minB, maxB] = scaleB;
+
+		const percentage = (valueA - minA) / (maxA - minA);
+		const valueB = percentage * (maxB - minB) + minB;
+
+		return valueB;
+	};
+
+	const styleToString = (style: Record<string, number | string | undefined>): string => {
+		return Object.keys(style).reduce((str, key) => {
+			if (style[key] === undefined) return str;
+			return str + `${key}:${style[key]};`;
+		}, '');
+	};
+
+	return {
+		duration: withDefaults.duration ?? 200,
+		delay: 0,
+		css: (t) => {
+			const y = scaleConversion(t, [0, 1], [withDefaults.y, 0]);
+			const scale = scaleConversion(t, [0, 1], [withDefaults.start, 1]);
+
+			return styleToString({
+				transform: `${transform} translate3d(0, ${y}px, 0) scale(${scale})`,
+				opacity: t
+			});
+		},
+		easing: cubicOut
+	};
+};
diff --git a/src/lib/workers/pyodide.worker.ts b/src/lib/workers/pyodide.worker.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b27c00629524df88b80e467d940975a50e14e4d4
--- /dev/null
+++ b/src/lib/workers/pyodide.worker.ts
@@ -0,0 +1,70 @@
+import { loadPyodide, type PyodideInterface } from 'pyodide';
+
+declare global {
+	interface Window {
+		stdout: string | null;
+		stderr: string | null;
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any
+		result: any;
+		pyodide: PyodideInterface;
+		packages: string[];
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any
+		[key: string]: any;
+	}
+}
+
+async function loadPyodideAndPackages(packages: string[] = []) {
+	self.stdout = null;
+	self.stderr = null;
+	self.result = null;
+
+	self.pyodide = await loadPyodide({
+		indexURL: '/pyodide/',
+		stdout: (text) => {
+			console.log('Python output:', text);
+
+			if (self.stdout) {
+				self.stdout += `${text}\n`;
+			} else {
+				self.stdout = `${text}\n`;
+			}
+		},
+		stderr: (text) => {
+			console.log('An error occurred:', text);
+			if (self.stderr) {
+				self.stderr += `${text}\n`;
+			} else {
+				self.stderr = `${text}\n`;
+			}
+		},
+		packages: ['micropip']
+	});
+
+	const micropip = self.pyodide.pyimport('micropip');
+
+	// await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
+	await micropip.install(packages);
+}
+
+self.onmessage = async (event) => {
+	const { id, code, ...context } = event.data;
+
+	console.log(event.data);
+
+	// The worker copies the context in its own "memory" (an object mapping name to values)
+	for (const key of Object.keys(context)) {
+		self[key] = context[key];
+	}
+
+	// make sure loading is done
+	await loadPyodideAndPackages(self.packages);
+
+	try {
+		self.result = await self.pyodide.runPythonAsync(code);
+	} catch (error) {
+		self.stderr = error.toString();
+	}
+	self.postMessage({ id, result: self.result, stdout: self.stdout, stderr: self.stderr });
+};
+
+export default {};
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..662030f8f9412bd1c7fb84121e993db9b0a3e9ce
--- /dev/null
+++ b/src/routes/(app)/+layout.svelte
@@ -0,0 +1,355 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { onMount, tick, getContext } from 'svelte';
+	import { openDB, deleteDB } from 'idb';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+	import mermaid from 'mermaid';
+
+	import { goto } from '$app/navigation';
+	import { page } from '$app/stores';
+	import { fade } from 'svelte/transition';
+
+	import { getKnowledgeItems } from '$lib/apis/knowledge';
+	import { getFunctions } from '$lib/apis/functions';
+	import { getModels as _getModels, getVersionUpdates } from '$lib/apis';
+	import { getAllTags } from '$lib/apis/chats';
+	import { getPrompts } from '$lib/apis/prompts';
+	import { getTools } from '$lib/apis/tools';
+	import { getBanners } from '$lib/apis/configs';
+	import { getUserSettings } from '$lib/apis/users';
+
+	import { WEBUI_VERSION } from '$lib/constants';
+	import { compareVersion } from '$lib/utils';
+
+	import {
+		config,
+		user,
+		settings,
+		models,
+		prompts,
+		knowledge,
+		tools,
+		functions,
+		tags,
+		banners,
+		showSettings,
+		showChangelog,
+		temporaryChatEnabled
+	} from '$lib/stores';
+
+	import Sidebar from '$lib/components/layout/Sidebar.svelte';
+	import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
+	import ChangelogModal from '$lib/components/ChangelogModal.svelte';
+	import AccountPending from '$lib/components/layout/Overlay/AccountPending.svelte';
+	import UpdateInfoToast from '$lib/components/layout/UpdateInfoToast.svelte';
+
+	const i18n = getContext('i18n');
+
+	let loaded = false;
+	let DB = null;
+	let localDBChats = [];
+
+	let version;
+
+	const getModels = async () => {
+		return _getModels(localStorage.token);
+	};
+
+	onMount(async () => {
+		if ($user === undefined) {
+			await goto('/auth');
+		} else if (['user', 'admin'].includes($user.role)) {
+			try {
+				// Check if IndexedDB exists
+				DB = await openDB('Chats', 1);
+
+				if (DB) {
+					const chats = await DB.getAllFromIndex('chats', 'timestamp');
+					localDBChats = chats.map((item, idx) => chats[chats.length - 1 - idx]);
+
+					if (localDBChats.length === 0) {
+						await deleteDB('Chats');
+					}
+				}
+
+				console.log(DB);
+			} catch (error) {
+				// IndexedDB Not Found
+			}
+
+			const userSettings = await getUserSettings(localStorage.token).catch((error) => {
+				console.error(error);
+				return null;
+			});
+
+			if (userSettings) {
+				settings.set(userSettings.ui);
+			} else {
+				let localStorageSettings = {} as Parameters<(typeof settings)['set']>[0];
+
+				try {
+					localStorageSettings = JSON.parse(localStorage.getItem('settings') ?? '{}');
+				} catch (e: unknown) {
+					console.error('Failed to parse settings from localStorage', e);
+				}
+
+				settings.set(localStorageSettings);
+			}
+
+			await Promise.all([
+				(async () => {
+					models.set(await getModels());
+				})(),
+				(async () => {
+					prompts.set(await getPrompts(localStorage.token));
+				})(),
+				(async () => {
+					knowledge.set(await getKnowledgeItems(localStorage.token));
+				})(),
+				(async () => {
+					tools.set(await getTools(localStorage.token));
+				})(),
+				(async () => {
+					functions.set(await getFunctions(localStorage.token));
+				})(),
+				(async () => {
+					banners.set(await getBanners(localStorage.token));
+				})(),
+				(async () => {
+					tags.set(await getAllTags(localStorage.token));
+				})()
+			]);
+
+			document.addEventListener('keydown', function (event) {
+				const isCtrlPressed = event.ctrlKey || event.metaKey; // metaKey is for Cmd key on Mac
+				// Check if the Shift key is pressed
+				const isShiftPressed = event.shiftKey;
+
+				// Check if Ctrl + Shift + O is pressed
+				if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 'o') {
+					event.preventDefault();
+					console.log('newChat');
+					document.getElementById('sidebar-new-chat-button')?.click();
+				}
+
+				// Check if Shift + Esc is pressed
+				if (isShiftPressed && event.key === 'Escape') {
+					event.preventDefault();
+					console.log('focusInput');
+					document.getElementById('chat-input')?.focus();
+				}
+
+				// Check if Ctrl + Shift + ; is pressed
+				if (isCtrlPressed && isShiftPressed && event.key === ';') {
+					event.preventDefault();
+					console.log('copyLastCodeBlock');
+					const button = [...document.getElementsByClassName('copy-code-button')]?.at(-1);
+					button?.click();
+				}
+
+				// Check if Ctrl + Shift + C is pressed
+				if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 'c') {
+					event.preventDefault();
+					console.log('copyLastResponse');
+					const button = [...document.getElementsByClassName('copy-response-button')]?.at(-1);
+					console.log(button);
+					button?.click();
+				}
+
+				// Check if Ctrl + Shift + S is pressed
+				if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 's') {
+					event.preventDefault();
+					console.log('toggleSidebar');
+					document.getElementById('sidebar-toggle-button')?.click();
+				}
+
+				// Check if Ctrl + Shift + Backspace is pressed
+				if (
+					isCtrlPressed &&
+					isShiftPressed &&
+					(event.key === 'Backspace' || event.key === 'Delete')
+				) {
+					event.preventDefault();
+					console.log('deleteChat');
+					document.getElementById('delete-chat-button')?.click();
+				}
+
+				// Check if Ctrl + . is pressed
+				if (isCtrlPressed && event.key === '.') {
+					event.preventDefault();
+					console.log('openSettings');
+					showSettings.set(!$showSettings);
+				}
+
+				// Check if Ctrl + / is pressed
+				if (isCtrlPressed && event.key === '/') {
+					event.preventDefault();
+					console.log('showShortcuts');
+					document.getElementById('show-shortcuts-button')?.click();
+				}
+			});
+
+			if ($user.role === 'admin') {
+				showChangelog.set($settings?.version !== $config.version);
+			}
+
+			if ($page.url.searchParams.get('temporary-chat') === 'true') {
+				temporaryChatEnabled.set(true);
+			}
+
+			// Check for version updates
+			if ($user.role === 'admin') {
+				// Check if the user has dismissed the update toast in the last 24 hours
+				if (localStorage.dismissedUpdateToast) {
+					const dismissedUpdateToast = new Date(Number(localStorage.dismissedUpdateToast));
+					const now = new Date();
+
+					if (now - dismissedUpdateToast > 24 * 60 * 60 * 1000) {
+						checkForVersionUpdates();
+					}
+				} else {
+					checkForVersionUpdates();
+				}
+			}
+			await tick();
+		}
+
+		loaded = true;
+	});
+
+	const checkForVersionUpdates = async () => {
+		version = await getVersionUpdates(localStorage.token).catch((error) => {
+			return {
+				current: WEBUI_VERSION,
+				latest: WEBUI_VERSION
+			};
+		});
+	};
+</script>
+
+<SettingsModal bind:show={$showSettings} />
+<ChangelogModal bind:show={$showChangelog} />
+
+{#if version && compareVersion(version.latest, version.current) && ($settings?.showUpdateToast ?? true)}
+	<div class=" absolute bottom-8 right-8 z-50" in:fade={{ duration: 100 }}>
+		<UpdateInfoToast
+			{version}
+			on:close={() => {
+				localStorage.setItem('dismissedUpdateToast', Date.now().toString());
+				version = null;
+			}}
+		/>
+	</div>
+{/if}
+
+<div class="app relative">
+	<div
+		class=" text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-900 h-screen max-h-[100dvh] overflow-auto flex flex-row"
+	>
+		{#if loaded}
+			{#if !['user', 'admin'].includes($user.role)}
+				<AccountPending />
+			{:else if localDBChats.length > 0}
+				<div class="fixed w-full h-full flex z-50">
+					<div
+						class="absolute w-full h-full backdrop-blur-md bg-white/20 dark:bg-gray-900/50 flex justify-center"
+					>
+						<div class="m-auto pb-44 flex flex-col justify-center">
+							<div class="max-w-md">
+								<div class="text-center dark:text-white text-2xl font-medium z-50">
+									Important Update<br /> Action Required for Chat Log Storage
+								</div>
+
+								<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
+									{$i18n.t(
+										"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through"
+									)}
+									<span class="font-semibold dark:text-white"
+										>{$i18n.t('Settings')} > {$i18n.t('Chats')} > {$i18n.t('Import Chats')}</span
+									>. {$i18n.t(
+										'This ensures that your valuable conversations are securely saved to your backend database. Thank you!'
+									)}
+								</div>
+
+								<div class=" mt-6 mx-auto relative group w-fit">
+									<button
+										class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm"
+										on:click={async () => {
+											let blob = new Blob([JSON.stringify(localDBChats)], {
+												type: 'application/json'
+											});
+											saveAs(blob, `chat-export-${Date.now()}.json`);
+
+											const tx = DB.transaction('chats', 'readwrite');
+											await Promise.all([tx.store.clear(), tx.done]);
+											await deleteDB('Chats');
+
+											localDBChats = [];
+										}}
+									>
+										Download & Delete
+									</button>
+
+									<button
+										class="text-xs text-center w-full mt-2 text-gray-400 underline"
+										on:click={async () => {
+											localDBChats = [];
+										}}>{$i18n.t('Close')}</button
+									>
+								</div>
+							</div>
+						</div>
+					</div>
+				</div>
+			{/if}
+
+			<Sidebar />
+			<slot />
+		{/if}
+	</div>
+</div>
+
+<style>
+	.loading {
+		display: inline-block;
+		clip-path: inset(0 1ch 0 0);
+		animation: l 1s steps(3) infinite;
+		letter-spacing: -0.5px;
+	}
+
+	@keyframes l {
+		to {
+			clip-path: inset(0 -1ch 0 0);
+		}
+	}
+
+	pre[class*='language-'] {
+		position: relative;
+		overflow: auto;
+
+		/* make space  */
+		margin: 5px 0;
+		padding: 1.75rem 0 1.75rem 1rem;
+		border-radius: 10px;
+	}
+
+	pre[class*='language-'] button {
+		position: absolute;
+		top: 5px;
+		right: 5px;
+
+		font-size: 0.9rem;
+		padding: 0.15rem;
+		background-color: #828282;
+
+		border: ridge 1px #7b7b7c;
+		border-radius: 5px;
+		text-shadow: #c4c4c4 0 0 2px;
+	}
+
+	pre[class*='language-'] button:hover {
+		cursor: pointer;
+		background-color: #bcbabb;
+	}
+</style>
diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..08026e7aa09650afbe47a8c0d0efc8cbfb6a56d0
--- /dev/null
+++ b/src/routes/(app)/+page.svelte
@@ -0,0 +1,7 @@
+<script lang="ts">
+	import Chat from '$lib/components/chat/Chat.svelte';
+	import Help from '$lib/components/layout/Help.svelte';
+</script>
+
+<Help />
+<Chat />
diff --git a/src/routes/(app)/admin/+layout.svelte b/src/routes/(app)/admin/+layout.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d7311c917ae9007c8f4560d495144f6688e7b3dd
--- /dev/null
+++ b/src/routes/(app)/admin/+layout.svelte
@@ -0,0 +1,90 @@
+<script lang="ts">
+	import { onMount, getContext } from 'svelte';
+	import { goto } from '$app/navigation';
+
+	import { WEBUI_NAME, showSidebar, user } from '$lib/stores';
+	import MenuLines from '$lib/components/icons/MenuLines.svelte';
+	import { page } from '$app/stores';
+
+	const i18n = getContext('i18n');
+
+	let loaded = false;
+
+	onMount(async () => {
+		if ($user?.role !== 'admin') {
+			await goto('/');
+		}
+		loaded = true;
+	});
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Admin Panel')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+{#if loaded}
+	<div
+		class=" flex flex-col w-full min-h-screen max-h-screen {$showSidebar
+			? 'md:max-w-[calc(100%-260px)]'
+			: ''}"
+	>
+		<div class=" px-3.5 py-2">
+			<div class=" flex items-center gap-1">
+				<div class="{$showSidebar ? 'md:hidden' : ''} mr-1 flex flex-none items-center">
+					<button
+						id="sidebar-toggle-button"
+						class="cursor-pointer p-1.5 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
+						on:click={() => {
+							showSidebar.set(!$showSidebar);
+						}}
+						aria-label="Toggle Sidebar"
+					>
+						<div class=" m-auto self-center">
+							<MenuLines />
+						</div>
+					</button>
+				</div>
+				<!-- <div class="flex items-center text-xl font-semibold">{$i18n.t('Admin Panel')}</div> -->
+
+				<div class=" flex w-full">
+					<div
+						class="flex scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-full bg-transparent/10 p-1"
+					>
+						<a
+							class="min-w-fit rounded-full p-1.5 px-3 {['/admin', '/admin/'].includes(
+								$page.url.pathname
+							)
+								? 'bg-gray-50 dark:bg-gray-850'
+								: ''} transition"
+							href="/admin">{$i18n.t('Dashboard')}</a
+						>
+
+						<a
+							class="min-w-fit rounded-full p-1.5 px-3 {$page.url.pathname.includes(
+								'/admin/evaluations'
+							)
+								? 'bg-gray-50 dark:bg-gray-850'
+								: ''} transition"
+							href="/admin/evaluations">{$i18n.t('Evaluations')}</a
+						>
+
+						<a
+							class="min-w-fit rounded-full p-1.5 px-3 {$page.url.pathname.includes(
+								'/admin/settings'
+							)
+								? 'bg-gray-50 dark:bg-gray-850'
+								: ''} transition"
+							href="/admin/settings">{$i18n.t('Settings')}</a
+						>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<div class=" pb-1 px-[18px] flex-1 max-h-full overflow-y-auto">
+			<slot />
+		</div>
+	</div>
+{/if}
diff --git a/src/routes/(app)/admin/+page.svelte b/src/routes/(app)/admin/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e9e650f8ca6bdf2ad501d8c4eb38e15201720976
--- /dev/null
+++ b/src/routes/(app)/admin/+page.svelte
@@ -0,0 +1,489 @@
+<script>
+	import { WEBUI_BASE_URL } from '$lib/constants';
+	import { WEBUI_NAME, config, user, showSidebar } from '$lib/stores';
+	import { goto } from '$app/navigation';
+	import { onMount, getContext } from 'svelte';
+
+	import dayjs from 'dayjs';
+	import relativeTime from 'dayjs/plugin/relativeTime';
+	dayjs.extend(relativeTime);
+
+	import { toast } from 'svelte-sonner';
+
+	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
+
+	import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
+	import Pagination from '$lib/components/common/Pagination.svelte';
+	import ChatBubbles from '$lib/components/icons/ChatBubbles.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import UserChatsModal from '$lib/components/admin/UserChatsModal.svelte';
+	import AddUserModal from '$lib/components/admin/AddUserModal.svelte';
+	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import Badge from '$lib/components/common/Badge.svelte';
+	import Plus from '$lib/components/icons/Plus.svelte';
+	import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
+	import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
+	import About from '$lib/components/chat/Settings/About.svelte';
+
+	const i18n = getContext('i18n');
+
+	let loaded = false;
+	let tab = '';
+	let users = [];
+
+	let search = '';
+	let selectedUser = null;
+
+	let page = 1;
+
+	let showDeleteConfirmDialog = false;
+	let showAddUserModal = false;
+
+	let showUserChatsModal = false;
+	let showEditUserModal = false;
+
+	const updateRoleHandler = async (id, role) => {
+		const res = await updateUserRole(localStorage.token, id, role).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			users = await getUsers(localStorage.token);
+		}
+	};
+
+	const editUserPasswordHandler = async (id, password) => {
+		const res = await deleteUserById(localStorage.token, id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+		if (res) {
+			users = await getUsers(localStorage.token);
+			toast.success($i18n.t('Successfully updated.'));
+		}
+	};
+
+	const deleteUserHandler = async (id) => {
+		const res = await deleteUserById(localStorage.token, id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+		if (res) {
+			users = await getUsers(localStorage.token);
+		}
+	};
+
+	onMount(async () => {
+		if ($user?.role !== 'admin') {
+			await goto('/');
+		} else {
+			users = await getUsers(localStorage.token);
+		}
+		loaded = true;
+	});
+	let sortKey = 'created_at'; // default sort key
+	let sortOrder = 'asc'; // default sort order
+
+	function setSortKey(key) {
+		if (sortKey === key) {
+			sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
+		} else {
+			sortKey = key;
+			sortOrder = 'asc';
+		}
+	}
+
+	let filteredUsers;
+
+	$: filteredUsers = users
+		.filter((user) => {
+			if (search === '') {
+				return true;
+			} else {
+				let name = user.name.toLowerCase();
+				const query = search.toLowerCase();
+				return name.includes(query);
+			}
+		})
+		.sort((a, b) => {
+			if (a[sortKey] < b[sortKey]) return sortOrder === 'asc' ? -1 : 1;
+			if (a[sortKey] > b[sortKey]) return sortOrder === 'asc' ? 1 : -1;
+			return 0;
+		})
+		.slice((page - 1) * 20, page * 20);
+</script>
+
+<ConfirmDialog
+	bind:show={showDeleteConfirmDialog}
+	on:confirm={() => {
+		deleteUserHandler(selectedUser.id);
+	}}
+/>
+
+{#key selectedUser}
+	<EditUserModal
+		bind:show={showEditUserModal}
+		{selectedUser}
+		sessionUser={$user}
+		on:save={async () => {
+			users = await getUsers(localStorage.token);
+		}}
+	/>
+{/key}
+
+<AddUserModal
+	bind:show={showAddUserModal}
+	on:save={async () => {
+		users = await getUsers(localStorage.token);
+	}}
+/>
+<UserChatsModal bind:show={showUserChatsModal} user={selectedUser} />
+
+{#if loaded}
+	<div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between">
+		<div class="flex md:self-center text-lg font-medium px-0.5">
+			{$i18n.t('Users')}
+			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
+
+			<span class="text-lg font-medium text-gray-500 dark:text-gray-300">{users.length}</span>
+		</div>
+
+		<div class="flex gap-1">
+			<div class=" flex w-full space-x-2">
+				<div class="flex flex-1">
+					<div class=" self-center ml-1 mr-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 20 20"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<input
+						class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+						bind:value={search}
+						placeholder={$i18n.t('Search')}
+					/>
+				</div>
+
+				<div>
+					<Tooltip content={$i18n.t('Add User')}>
+						<button
+							class=" p-2 rounded-xl hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition font-medium text-sm flex items-center space-x-1"
+							on:click={() => {
+								showAddUserModal = !showAddUserModal;
+							}}
+						>
+							<Plus className="size-3.5" />
+						</button>
+					</Tooltip>
+				</div>
+			</div>
+		</div>
+	</div>
+
+	<div
+		class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded pt-0.5"
+	>
+		<table
+			class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full rounded"
+		>
+			<thead
+				class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"
+			>
+				<tr class="">
+					<th
+						scope="col"
+						class="px-3 py-1.5 cursor-pointer select-none"
+						on:click={() => setSortKey('role')}
+					>
+						<div class="flex gap-1.5 items-center">
+							{$i18n.t('Role')}
+
+							{#if sortKey === 'role'}
+								<span class="font-normal"
+									>{#if sortOrder === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
+					</th>
+					<th
+						scope="col"
+						class="px-3 py-1.5 cursor-pointer select-none"
+						on:click={() => setSortKey('name')}
+					>
+						<div class="flex gap-1.5 items-center">
+							{$i18n.t('Name')}
+
+							{#if sortKey === 'name'}
+								<span class="font-normal"
+									>{#if sortOrder === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
+					</th>
+					<th
+						scope="col"
+						class="px-3 py-1.5 cursor-pointer select-none"
+						on:click={() => setSortKey('email')}
+					>
+						<div class="flex gap-1.5 items-center">
+							{$i18n.t('Email')}
+
+							{#if sortKey === 'email'}
+								<span class="font-normal"
+									>{#if sortOrder === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
+					</th>
+
+					<th
+						scope="col"
+						class="px-3 py-1.5 cursor-pointer select-none"
+						on:click={() => setSortKey('last_active_at')}
+					>
+						<div class="flex gap-1.5 items-center">
+							{$i18n.t('Last Active')}
+
+							{#if sortKey === 'last_active_at'}
+								<span class="font-normal"
+									>{#if sortOrder === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
+					</th>
+					<th
+						scope="col"
+						class="px-3 py-1.5 cursor-pointer select-none"
+						on:click={() => setSortKey('created_at')}
+					>
+						<div class="flex gap-1.5 items-center">
+							{$i18n.t('Created at')}
+							{#if sortKey === 'created_at'}
+								<span class="font-normal"
+									>{#if sortOrder === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
+					</th>
+
+					<th
+						scope="col"
+						class="px-3 py-1.5 cursor-pointer select-none"
+						on:click={() => setSortKey('oauth_sub')}
+					>
+						<div class="flex gap-1.5 items-center">
+							{$i18n.t('OAuth ID')}
+
+							{#if sortKey === 'oauth_sub'}
+								<span class="font-normal"
+									>{#if sortOrder === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
+					</th>
+
+					<th scope="col" class="px-3 py-2 text-right" />
+				</tr>
+			</thead>
+			<tbody class="">
+				{#each filteredUsers as user, userIdx}
+					<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
+						<td class="px-3 py-1 min-w-[7rem] w-28">
+							<button
+								class=" translate-y-0.5"
+								on:click={() => {
+									if (user.role === 'user') {
+										updateRoleHandler(user.id, 'admin');
+									} else if (user.role === 'pending') {
+										updateRoleHandler(user.id, 'user');
+									} else {
+										updateRoleHandler(user.id, 'pending');
+									}
+								}}
+							>
+								<Badge
+									type={user.role === 'admin' ? 'info' : user.role === 'user' ? 'success' : 'muted'}
+									content={$i18n.t(user.role)}
+								/>
+							</button>
+						</td>
+						<td class="px-3 py-1 font-medium text-gray-900 dark:text-white w-max">
+							<div class="flex flex-row w-max">
+								<img
+									class=" rounded-full w-6 h-6 object-cover mr-2.5"
+									src={user.profile_image_url.startsWith(WEBUI_BASE_URL) ||
+									user.profile_image_url.startsWith('https://www.gravatar.com/avatar/') ||
+									user.profile_image_url.startsWith('data:')
+										? user.profile_image_url
+										: `/user.png`}
+									alt="user"
+								/>
+
+								<div class=" font-medium self-center">{user.name}</div>
+							</div>
+						</td>
+						<td class=" px-3 py-1"> {user.email} </td>
+
+						<td class=" px-3 py-1">
+							{dayjs(user.last_active_at * 1000).fromNow()}
+						</td>
+
+						<td class=" px-3 py-1">
+							{dayjs(user.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
+						</td>
+
+						<td class=" px-3 py-1"> {user.oauth_sub ?? ''} </td>
+
+						<td class="px-3 py-1 text-right">
+							<div class="flex justify-end w-full">
+								{#if $config.features.enable_admin_chat_access && user.role !== 'admin'}
+									<Tooltip content={$i18n.t('Chats')}>
+										<button
+											class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+											on:click={async () => {
+												showUserChatsModal = !showUserChatsModal;
+												selectedUser = user;
+											}}
+										>
+											<ChatBubbles />
+										</button>
+									</Tooltip>
+								{/if}
+
+								<Tooltip content={$i18n.t('Edit User')}>
+									<button
+										class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+										on:click={async () => {
+											showEditUserModal = !showEditUserModal;
+											selectedUser = user;
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											fill="none"
+											viewBox="0 0 24 24"
+											stroke-width="1.5"
+											stroke="currentColor"
+											class="w-4 h-4"
+										>
+											<path
+												stroke-linecap="round"
+												stroke-linejoin="round"
+												d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
+											/>
+										</svg>
+									</button>
+								</Tooltip>
+
+								{#if user.role !== 'admin'}
+									<Tooltip content={$i18n.t('Delete User')}>
+										<button
+											class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+											on:click={async () => {
+												showDeleteConfirmDialog = true;
+												selectedUser = user;
+											}}
+										>
+											<svg
+												xmlns="http://www.w3.org/2000/svg"
+												fill="none"
+												viewBox="0 0 24 24"
+												stroke-width="1.5"
+												stroke="currentColor"
+												class="w-4 h-4"
+											>
+												<path
+													stroke-linecap="round"
+													stroke-linejoin="round"
+													d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
+												/>
+											</svg>
+										</button>
+									</Tooltip>
+								{/if}
+							</div>
+						</td>
+					</tr>
+				{/each}
+			</tbody>
+		</table>
+	</div>
+
+	<div class=" text-gray-500 text-xs mt-1.5 text-right">
+		ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
+	</div>
+
+	<Pagination bind:page count={users.length} />
+{/if}
+
+<style>
+	.font-mona {
+		font-family: 'Mona Sans';
+	}
+
+	.scrollbar-hidden::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.scrollbar-hidden {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+</style>
diff --git a/src/routes/(app)/admin/evaluations/+page.svelte b/src/routes/(app)/admin/evaluations/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..186697542ce8116349b6275b3e6ee1b9cbfa533e
--- /dev/null
+++ b/src/routes/(app)/admin/evaluations/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Evaluations from '$lib/components/admin/Evaluations.svelte';
+</script>
+
+<Evaluations />
diff --git a/src/routes/(app)/admin/settings/+page.svelte b/src/routes/(app)/admin/settings/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a0a86f43569c15d30cfd6e56582a897d363fc7ae
--- /dev/null
+++ b/src/routes/(app)/admin/settings/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Settings from '$lib/components/admin/Settings.svelte';
+</script>
+
+<Settings />
diff --git a/src/routes/(app)/c/[id]/+page.svelte b/src/routes/(app)/c/[id]/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2cc68782eb61c109f9a42843aee429693583ae4c
--- /dev/null
+++ b/src/routes/(app)/c/[id]/+page.svelte
@@ -0,0 +1,9 @@
+<script lang="ts">
+	import { page } from '$app/stores';
+
+	import Chat from '$lib/components/chat/Chat.svelte';
+	import Help from '$lib/components/layout/Help.svelte';
+</script>
+
+<Help />
+<Chat chatIdProp={$page.params.id} />
diff --git a/src/routes/(app)/playground/+layout.svelte b/src/routes/(app)/playground/+layout.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..38d2e6f3f4160df42d380822d16fc4b7721213ed
--- /dev/null
+++ b/src/routes/(app)/playground/+layout.svelte
@@ -0,0 +1,79 @@
+<script lang="ts">
+	import { onMount, getContext } from 'svelte';
+	import { WEBUI_NAME, showSidebar, functions } from '$lib/stores';
+	import MenuLines from '$lib/components/icons/MenuLines.svelte';
+	import { page } from '$app/stores';
+
+	const i18n = getContext('i18n');
+
+	onMount(async () => {});
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Playground')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<div
+	class=" flex flex-col w-full min-h-screen max-h-screen {$showSidebar
+		? 'md:max-w-[calc(100%-260px)]'
+		: ''}"
+>
+	<div class=" px-3.5 py-2">
+		<div class=" flex items-center gap-1">
+			<div class="{$showSidebar ? 'md:hidden' : ''} mr-1 flex flex-none items-center">
+				<button
+					id="sidebar-toggle-button"
+					class="cursor-pointer p-1.5 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
+					on:click={() => {
+						showSidebar.set(!$showSidebar);
+					}}
+					aria-label="Toggle Sidebar"
+				>
+					<div class=" m-auto self-center">
+						<MenuLines />
+					</div>
+				</button>
+			</div>
+			<!-- <div class="flex items-center text-xl font-semibold">{$i18n.t('Admin Panel')}</div> -->
+
+			<div class=" flex w-full">
+				<div
+					class="flex scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-full bg-transparent/10 p-1"
+				>
+					<a
+						class="min-w-fit rounded-full p-1.5 px-3 {['/playground', '/playground/'].includes(
+							$page.url.pathname
+						)
+							? 'bg-gray-50 dark:bg-gray-850'
+							: ''} transition"
+						href="/playground">{$i18n.t('Chat')}</a
+					>
+
+					<a
+						class="min-w-fit rounded-full p-1.5 px-3 {$page.url.pathname.includes(
+							'/playground/notes'
+						)
+							? 'bg-gray-50 dark:bg-gray-850'
+							: ''} transition"
+						href="/playground/notes">{$i18n.t('Notes')}</a
+					>
+
+					<a
+						class="min-w-fit rounded-full p-1.5 px-3 {$page.url.pathname.includes(
+							'/playground/completions'
+						)
+							? 'bg-gray-50 dark:bg-gray-850'
+							: ''} transition"
+						href="/playground/completions">{$i18n.t('Completions')}</a
+					>
+				</div>
+			</div>
+		</div>
+	</div>
+
+	<div class=" flex-1 max-h-full overflow-y-auto">
+		<slot />
+	</div>
+</div>
diff --git a/src/routes/(app)/playground/+page.svelte b/src/routes/(app)/playground/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..2a2f7dd0f4cb170265c1e247715362dbc6ba3606
--- /dev/null
+++ b/src/routes/(app)/playground/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Chat from '$lib/components/playground/Chat.svelte';
+</script>
+
+<Chat />
diff --git a/src/routes/(app)/playground/completions/+page.svelte b/src/routes/(app)/playground/completions/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..fd0f404cfb6ca70376e197ae1f8f6b3497598ea7
--- /dev/null
+++ b/src/routes/(app)/playground/completions/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Completions from '$lib/components/playground/Completions.svelte';
+</script>
+
+<Completions />
diff --git a/src/routes/(app)/playground/notes/+page.svelte b/src/routes/(app)/playground/notes/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..51a4bc51734e99c3d09c5af703c1356ae230635b
--- /dev/null
+++ b/src/routes/(app)/playground/notes/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Notes from '$lib/components/playground/Notes.svelte';
+</script>
+
+<Notes />
diff --git a/src/routes/(app)/workspace/+layout.svelte b/src/routes/(app)/workspace/+layout.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..081e986bb7f4e6bd9d6773d2382c33b2f13add98
--- /dev/null
+++ b/src/routes/(app)/workspace/+layout.svelte
@@ -0,0 +1,117 @@
+<script lang="ts">
+	import { onMount, getContext } from 'svelte';
+	import { WEBUI_NAME, showSidebar, functions, user, mobile } from '$lib/stores';
+	import { page } from '$app/stores';
+	import { goto } from '$app/navigation';
+
+	import MenuLines from '$lib/components/icons/MenuLines.svelte';
+
+	const i18n = getContext('i18n');
+
+	let loaded = false;
+
+	onMount(async () => {
+		if ($user?.role !== 'admin') {
+			await goto('/');
+		}
+		loaded = true;
+	});
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Workspace')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+{#if loaded}
+	<div
+		class=" relative flex flex-col w-full min-h-screen max-h-screen {$showSidebar
+			? 'md:max-w-[calc(100%-260px)]'
+			: ''}"
+	>
+		<div class="   px-3.5 my-2 bg-transparent backdrop-blur-xl">
+			<div class=" flex items-center gap-1">
+				<div class="{$showSidebar ? 'md:hidden' : ''} mr-1 self-center flex flex-none items-center">
+					<button
+						id="sidebar-toggle-button"
+						class="cursor-pointer p-1.5 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
+						on:click={() => {
+							showSidebar.set(!$showSidebar);
+						}}
+						aria-label="Toggle Sidebar"
+					>
+						<div class=" m-auto self-center">
+							<MenuLines />
+						</div>
+					</button>
+				</div>
+
+				<!-- <div class="flex items-center text-xl font-semibold mr-3">{$i18n.t('Workspace')}</div> -->
+
+				<div class="">
+					<div
+						class="flex scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-full bg-transparent/10 backdrop-blur-2xl p-1 touch-auto pointer-events-auto"
+					>
+						<a
+							class="min-w-fit rounded-full p-1.5 px-3 {$page.url.pathname.includes(
+								'/workspace/models'
+							)
+								? 'bg-gray-50 dark:bg-gray-850'
+								: ''} transition"
+							href="/workspace/models">{$i18n.t('Models')}</a
+						>
+
+						<a
+							class="min-w-fit rounded-full p-1.5 px-3 {$page.url.pathname.includes(
+								'/workspace/knowledge'
+							)
+								? 'bg-gray-50 dark:bg-gray-850'
+								: ''} transition"
+							href="/workspace/knowledge"
+						>
+							{$i18n.t('Knowledge')}
+						</a>
+
+						<a
+							class="min-w-fit rounded-full p-1.5 px-3 {$page.url.pathname.includes(
+								'/workspace/prompts'
+							)
+								? 'bg-gray-50 dark:bg-gray-850'
+								: ''} transition"
+							href="/workspace/prompts">{$i18n.t('Prompts')}</a
+						>
+
+						<a
+							class="min-w-fit rounded-full p-1.5 px-3 {$page.url.pathname.includes(
+								'/workspace/tools'
+							)
+								? 'bg-gray-50 dark:bg-gray-850'
+								: ''} transition"
+							href="/workspace/tools"
+						>
+							{$i18n.t('Tools')}
+						</a>
+
+						<a
+							class="min-w-fit rounded-full p-1.5 px-3 {$page.url.pathname.includes(
+								'/workspace/functions'
+							)
+								? 'bg-gray-50 dark:bg-gray-850'
+								: ''} transition"
+							href="/workspace/functions"
+						>
+							{$i18n.t('Functions')}
+						</a>
+					</div>
+				</div>
+
+				<!-- <div class="flex items-center text-xl font-semibold">{$i18n.t('Workspace')}</div> -->
+			</div>
+		</div>
+
+		<div class=" pb-1 px-[18px] flex-1 max-h-full overflow-y-auto">
+			<slot />
+		</div>
+	</div>
+{/if}
diff --git a/src/routes/(app)/workspace/+page.svelte b/src/routes/(app)/workspace/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a33774cfab81003a3427b64f336dfe508747480d
--- /dev/null
+++ b/src/routes/(app)/workspace/+page.svelte
@@ -0,0 +1,8 @@
+<script lang="ts">
+	import { goto } from '$app/navigation';
+	import { onMount } from 'svelte';
+
+	onMount(() => {
+		goto('/workspace/models');
+	});
+</script>
diff --git a/src/routes/(app)/workspace/functions/+page.svelte b/src/routes/(app)/workspace/functions/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..8c6c212fab3f704f3953896b6b48f399bb723ad2
--- /dev/null
+++ b/src/routes/(app)/workspace/functions/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Functions from '$lib/components/workspace/Functions.svelte';
+</script>
+
+<Functions />
diff --git a/src/routes/(app)/workspace/functions/create/+page.svelte b/src/routes/(app)/workspace/functions/create/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..deee266e5120a8aabb527f096be2f15686335579
--- /dev/null
+++ b/src/routes/(app)/workspace/functions/create/+page.svelte
@@ -0,0 +1,98 @@
+<script>
+	import { toast } from 'svelte-sonner';
+	import { onMount, getContext } from 'svelte';
+	import { goto } from '$app/navigation';
+
+	import { functions, models } from '$lib/stores';
+	import { createNewFunction, getFunctions } from '$lib/apis/functions';
+	import FunctionEditor from '$lib/components/workspace/Functions/FunctionEditor.svelte';
+	import { getModels } from '$lib/apis';
+	import { compareVersion, extractFrontmatter } from '$lib/utils';
+	import { WEBUI_VERSION } from '$lib/constants';
+
+	const i18n = getContext('i18n');
+
+	let mounted = false;
+	let clone = false;
+	let func = null;
+
+	const saveHandler = async (data) => {
+		console.log(data);
+
+		const manifest = extractFrontmatter(data.content);
+		if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) {
+			console.log('Version is lower than required');
+			toast.error(
+				$i18n.t(
+					'Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})',
+					{
+						OPEN_WEBUI_VERSION: WEBUI_VERSION,
+						REQUIRED_VERSION: manifest?.required_open_webui_version ?? '0.0.0'
+					}
+				)
+			);
+			return;
+		}
+
+		const res = await createNewFunction(localStorage.token, {
+			id: data.id,
+			name: data.name,
+			meta: data.meta,
+			content: data.content
+		}).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Function created successfully'));
+			functions.set(await getFunctions(localStorage.token));
+			models.set(await getModels(localStorage.token));
+
+			await goto('/workspace/functions');
+		}
+	};
+
+	onMount(() => {
+		window.addEventListener('message', async (event) => {
+			if (
+				!['https://openwebui.com', 'https://www.openwebui.com', 'http://localhost:9999'].includes(
+					event.origin
+				)
+			)
+				return;
+
+			func = JSON.parse(event.data);
+			console.log(func);
+		});
+
+		if (window.opener ?? false) {
+			window.opener.postMessage('loaded', '*');
+		}
+
+		if (sessionStorage.function) {
+			func = JSON.parse(sessionStorage.function);
+			sessionStorage.removeItem('function');
+
+			console.log(func);
+			clone = true;
+		}
+
+		mounted = true;
+	});
+</script>
+
+{#if mounted}
+	{#key func?.content}
+		<FunctionEditor
+			id={func?.id ?? ''}
+			name={func?.name ?? ''}
+			meta={func?.meta ?? { description: '' }}
+			content={func?.content ?? ''}
+			{clone}
+			on:save={(e) => {
+				saveHandler(e.detail);
+			}}
+		/>
+	{/key}
+{/if}
diff --git a/src/routes/(app)/workspace/functions/edit/+page.svelte b/src/routes/(app)/workspace/functions/edit/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..366890c04bd55878e283d83ff95bcfc6195d7762
--- /dev/null
+++ b/src/routes/(app)/workspace/functions/edit/+page.svelte
@@ -0,0 +1,88 @@
+<script>
+	import { toast } from 'svelte-sonner';
+	import { onMount, getContext } from 'svelte';
+
+	import { goto } from '$app/navigation';
+	import { page } from '$app/stores';
+	import { functions, models } from '$lib/stores';
+	import { updateFunctionById, getFunctions, getFunctionById } from '$lib/apis/functions';
+
+	import FunctionEditor from '$lib/components/workspace/Functions/FunctionEditor.svelte';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import { getModels } from '$lib/apis';
+	import { compareVersion, extractFrontmatter } from '$lib/utils';
+	import { WEBUI_VERSION } from '$lib/constants';
+
+	const i18n = getContext('i18n');
+
+	let func = null;
+
+	const saveHandler = async (data) => {
+		console.log(data);
+
+		const manifest = extractFrontmatter(data.content);
+		if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) {
+			console.log('Version is lower than required');
+			toast.error(
+				$i18n.t(
+					'Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})',
+					{
+						OPEN_WEBUI_VERSION: WEBUI_VERSION,
+						REQUIRED_VERSION: manifest?.required_open_webui_version ?? '0.0.0'
+					}
+				)
+			);
+			return;
+		}
+
+		const res = await updateFunctionById(localStorage.token, func.id, {
+			id: data.id,
+			name: data.name,
+			meta: data.meta,
+			content: data.content
+		}).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Function updated successfully'));
+			functions.set(await getFunctions(localStorage.token));
+			models.set(await getModels(localStorage.token));
+		}
+	};
+
+	onMount(async () => {
+		console.log('mounted');
+		const id = $page.url.searchParams.get('id');
+
+		if (id) {
+			func = await getFunctionById(localStorage.token, id).catch((error) => {
+				toast.error(error);
+				goto('/workspace/functions');
+				return null;
+			});
+
+			console.log(func);
+		}
+	});
+</script>
+
+{#if func}
+	<FunctionEditor
+		edit={true}
+		id={func.id}
+		name={func.name}
+		meta={func.meta}
+		content={func.content}
+		on:save={(e) => {
+			saveHandler(e.detail);
+		}}
+	/>
+{:else}
+	<div class="flex items-center justify-center h-full">
+		<div class=" pb-16">
+			<Spinner />
+		</div>
+	</div>
+{/if}
diff --git a/src/routes/(app)/workspace/knowledge/+page.svelte b/src/routes/(app)/workspace/knowledge/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..7994ae40dc5da3e31631b1232f40aa70d69fcf94
--- /dev/null
+++ b/src/routes/(app)/workspace/knowledge/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Knowledge from '$lib/components/workspace/Knowledge.svelte';
+</script>
+
+<Knowledge />
diff --git a/src/routes/(app)/workspace/knowledge/[id]/+page.svelte b/src/routes/(app)/workspace/knowledge/[id]/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a18cb72d52e27e1dec74826b539179be410e1826
--- /dev/null
+++ b/src/routes/(app)/workspace/knowledge/[id]/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Collection from '$lib/components/workspace/Knowledge/Collection.svelte';
+</script>
+
+<Collection />
diff --git a/src/routes/(app)/workspace/knowledge/create/+page.svelte b/src/routes/(app)/workspace/knowledge/create/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..0884e340c4435da9315330e53c91227d9e97632e
--- /dev/null
+++ b/src/routes/(app)/workspace/knowledge/create/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import CreateCollection from '$lib/components/workspace/Knowledge/CreateCollection.svelte';
+</script>
+
+<CreateCollection />
diff --git a/src/routes/(app)/workspace/models/+page.svelte b/src/routes/(app)/workspace/models/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f656ad781d36d421abeb0000f1d7020d43efb2e2
--- /dev/null
+++ b/src/routes/(app)/workspace/models/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Models from '$lib/components/workspace/Models.svelte';
+</script>
+
+<Models />
diff --git a/src/routes/(app)/workspace/models/create/+page.svelte b/src/routes/(app)/workspace/models/create/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..42aa275c281456798193c4cd289ed215aa302386
--- /dev/null
+++ b/src/routes/(app)/workspace/models/create/+page.svelte
@@ -0,0 +1,762 @@
+<script>
+	import { v4 as uuidv4 } from 'uuid';
+	import { toast } from 'svelte-sonner';
+	import { goto } from '$app/navigation';
+	import {
+		settings,
+		user,
+		config,
+		models,
+		tools,
+		functions,
+		knowledge as _knowledge
+	} from '$lib/stores';
+
+	import TurndownService from 'turndown';
+
+	import { onMount, tick, getContext } from 'svelte';
+	import { addNewModel, getModelById, getModelInfos } from '$lib/apis/models';
+	import { getModels } from '$lib/apis';
+
+	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+	import Tags from '$lib/components/common/Tags.svelte';
+	import Knowledge from '$lib/components/workspace/Models/Knowledge.svelte';
+	import ToolsSelector from '$lib/components/workspace/Models/ToolsSelector.svelte';
+	import { stringify } from 'postcss';
+	import { parseFile } from '$lib/utils/characters';
+	import FiltersSelector from '$lib/components/workspace/Models/FiltersSelector.svelte';
+	import ActionsSelector from '$lib/components/workspace/Models/ActionsSelector.svelte';
+	import Capabilities from '$lib/components/workspace/Models/Capabilities.svelte';
+	import Textarea from '$lib/components/common/Textarea.svelte';
+
+	const i18n = getContext('i18n');
+
+	let filesInputElement;
+	let inputFiles;
+
+	let showAdvanced = false;
+	let showPreview = false;
+
+	let loading = false;
+	let success = false;
+
+	// ///////////
+	// Model
+	// ///////////
+
+	let id = '';
+	let name = '';
+
+	let info = {
+		id: '',
+		base_model_id: null,
+		name: '',
+		meta: {
+			profile_image_url: null,
+			description: '',
+			suggestion_prompts: [
+				{
+					content: ''
+				}
+			]
+		},
+		params: {
+			system: ''
+		}
+	};
+
+	let params = {};
+	let capabilities = {
+		vision: true,
+		usage: undefined
+	};
+
+	let toolIds = [];
+	let knowledge = [];
+	let filterIds = [];
+	let actionIds = [];
+
+	$: if (name) {
+		id = name
+			.replace(/\s+/g, '-')
+			.replace(/[^a-zA-Z0-9-]/g, '')
+			.toLowerCase();
+	}
+
+	const addUsage = (base_model_id) => {
+		const baseModel = $models.find((m) => m.id === base_model_id);
+
+		if (baseModel) {
+			if (baseModel.owned_by === 'openai') {
+				capabilities.usage = baseModel.info?.meta?.capabilities?.usage ?? false;
+			} else {
+				delete capabilities.usage;
+			}
+			capabilities = capabilities;
+		}
+	};
+
+	const submitHandler = async () => {
+		loading = true;
+
+		info.id = id;
+		info.name = name;
+		info.meta.capabilities = capabilities;
+
+		if (knowledge.length > 0) {
+			info.meta.knowledge = knowledge;
+		} else {
+			if (info.meta.knowledge) {
+				delete info.meta.knowledge;
+			}
+		}
+
+		if (toolIds.length > 0) {
+			info.meta.toolIds = toolIds;
+		} else {
+			if (info.meta.toolIds) {
+				delete info.meta.toolIds;
+			}
+		}
+
+		if (filterIds.length > 0) {
+			info.meta.filterIds = filterIds;
+		} else {
+			if (info.meta.filterIds) {
+				delete info.meta.filterIds;
+			}
+		}
+
+		if (actionIds.length > 0) {
+			info.meta.actionIds = actionIds;
+		} else {
+			if (info.meta.actionIds) {
+				delete info.meta.actionIds;
+			}
+		}
+
+		info.params.stop = params.stop ? params.stop.split(',').filter((s) => s.trim()) : null;
+		Object.keys(info.params).forEach((key) => {
+			if (info.params[key] === '' || info.params[key] === null) {
+				delete info.params[key];
+			}
+		});
+
+		if ($models.find((m) => m.id === info.id)) {
+			toast.error(
+				`Error: A model with the ID '${info.id}' already exists. Please select a different ID to proceed.`
+			);
+			loading = false;
+			success = false;
+			return success;
+		}
+
+		if (info) {
+			const res = await addNewModel(localStorage.token, {
+				...info,
+				meta: {
+					...info.meta,
+					profile_image_url: info.meta.profile_image_url ?? '/static/favicon.png',
+					suggestion_prompts: info.meta.suggestion_prompts
+						? info.meta.suggestion_prompts.filter((prompt) => prompt.content !== '')
+						: null
+				},
+				params: { ...info.params, ...params }
+			});
+
+			if (res) {
+				await models.set(await getModels(localStorage.token));
+				toast.success($i18n.t('Model created successfully!'));
+				await goto('/workspace/models');
+			}
+		}
+
+		loading = false;
+		success = false;
+	};
+
+	const initModel = async (model) => {
+		name = model.name;
+		await tick();
+
+		id = model.id;
+
+		if (model.info.base_model_id) {
+			const base_model = $models
+				.filter((m) => !m?.preset && m?.owned_by !== 'arena')
+				.find((m) =>
+					[model.info.base_model_id, `${model.info.base_model_id}:latest`].includes(m.id)
+				);
+
+			console.log('base_model', base_model);
+
+			if (!base_model) {
+				model.info.base_model_id = null;
+			} else if ($models.find((m) => m.id === `${model.info.base_model_id}:latest`)) {
+				model.info.base_model_id = `${model.info.base_model_id}:latest`;
+			}
+		}
+
+		params = { ...params, ...model?.info?.params };
+		params.stop = params?.stop ? (params?.stop ?? []).join(',') : null;
+
+		capabilities = { ...capabilities, ...(model?.info?.meta?.capabilities ?? {}) };
+		toolIds = model?.info?.meta?.toolIds ?? [];
+
+		if (model?.info?.meta?.filterIds) {
+			filterIds = [...model?.info?.meta?.filterIds];
+		}
+
+		if (model?.info?.meta?.actionIds) {
+			actionIds = [...model?.info?.meta?.actionIds];
+		}
+
+		info = {
+			...info,
+			...model.info
+		};
+
+		console.log(info);
+	};
+
+	onMount(async () => {
+		window.addEventListener('message', async (event) => {
+			if (
+				!['https://openwebui.com', 'https://www.openwebui.com', 'http://localhost:5173'].includes(
+					event.origin
+				)
+			)
+				return;
+
+			const model = JSON.parse(event.data);
+			console.log(model);
+
+			initModel(model);
+		});
+
+		if (window.opener ?? false) {
+			window.opener.postMessage('loaded', '*');
+		}
+
+		if (sessionStorage.model) {
+			const model = JSON.parse(sessionStorage.model);
+			sessionStorage.removeItem('model');
+
+			console.log(model);
+			initModel(model);
+		}
+	});
+</script>
+
+<div class="w-full max-h-full">
+	<input
+		bind:this={filesInputElement}
+		bind:files={inputFiles}
+		type="file"
+		hidden
+		accept="image/*"
+		on:change={() => {
+			let reader = new FileReader();
+			reader.onload = async (event) => {
+				let originalImageUrl = `${event.target.result}`;
+
+				let character = await parseFile(inputFiles[0]).catch((error) => {
+					return null;
+				});
+
+				console.log(character);
+
+				if (character && character.character) {
+					character = character.character;
+					console.log(character);
+
+					name = character.name;
+
+					const pattern = /<\/?[a-z][\s\S]*>/i;
+					if (character.summary.match(pattern)) {
+						const turndownService = new TurndownService();
+						info.meta.description = turndownService.turndown(character.summary);
+					} else {
+						info.meta.description = character.summary;
+					}
+
+					info.params.system = `Personality: ${character.personality}${
+						character?.scenario ? `\nScenario: ${character.scenario}` : ''
+					}${character?.greeting ? `\First Message: ${character.greeting}` : ''}${
+						character?.examples ? `\nExamples: ${character.examples}` : ''
+					}`;
+				}
+
+				const img = new Image();
+				img.src = originalImageUrl;
+
+				img.onload = function () {
+					const canvas = document.createElement('canvas');
+					const ctx = canvas.getContext('2d');
+
+					// Calculate the aspect ratio of the image
+					const aspectRatio = img.width / img.height;
+
+					// Calculate the new width and height to fit within 100x100
+					let newWidth, newHeight;
+					if (aspectRatio > 1) {
+						newWidth = 250 * aspectRatio;
+						newHeight = 250;
+					} else {
+						newWidth = 250;
+						newHeight = 250 / aspectRatio;
+					}
+
+					// Set the canvas size
+					canvas.width = 250;
+					canvas.height = 250;
+
+					// Calculate the position to center the image
+					const offsetX = (250 - newWidth) / 2;
+					const offsetY = (250 - newHeight) / 2;
+
+					// Draw the image on the canvas
+					ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
+
+					// Get the base64 representation of the compressed image
+					const compressedSrc = canvas.toDataURL();
+
+					// Display the compressed image
+					info.meta.profile_image_url = compressedSrc;
+
+					inputFiles = null;
+				};
+			};
+
+			if (
+				inputFiles &&
+				inputFiles.length > 0 &&
+				['image/gif', 'image/webp', 'image/jpeg', 'image/png', 'image/svg+xml'].includes(
+					inputFiles[0]['type']
+				)
+			) {
+				reader.readAsDataURL(inputFiles[0]);
+			} else {
+				console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
+				inputFiles = null;
+			}
+		}}
+	/>
+
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			goto('/workspace/models');
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+	<!-- <hr class="my-3 dark:border-gray-850" /> -->
+
+	<form
+		class="flex flex-col max-w-2xl mx-auto mt-4 mb-10"
+		on:submit|preventDefault={() => {
+			submitHandler();
+		}}
+	>
+		<div class="flex justify-center my-4">
+			<div class="self-center">
+				<button
+					class=" {info.meta.profile_image_url
+						? ''
+						: 'p-4'} rounded-full border border-dashed border-gray-200 flex items-center"
+					type="button"
+					on:click={() => {
+						filesInputElement.click();
+					}}
+				>
+					{#if info.meta.profile_image_url}
+						<img
+							src={info.meta.profile_image_url}
+							alt="modelfile profile"
+							class=" rounded-full size-16 object-cover"
+						/>
+					{:else}
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							class="size-8"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					{/if}
+				</button>
+			</div>
+		</div>
+
+		<div class="my-2 flex space-x-2">
+			<div class="flex-1">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
+
+				<div>
+					<input
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Name your model')}
+						bind:value={name}
+						required
+					/>
+				</div>
+			</div>
+
+			<div class="flex-1">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Model ID')}*</div>
+
+				<div>
+					<input
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Add a model id')}
+						bind:value={id}
+						required
+					/>
+				</div>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Base Model (From)')}</div>
+
+			<div>
+				<select
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+					placeholder="Select a base model (e.g. llama3, gpt-4o)"
+					bind:value={info.base_model_id}
+					on:change={(e) => {
+						addUsage(e.target.value);
+					}}
+					required
+				>
+					<option value={null} class=" text-gray-900">{$i18n.t('Select a base model')}</option>
+					{#each $models.filter((m) => !m?.preset && m?.owned_by !== 'arena') as model}
+						<option value={model.id} class=" text-gray-900">{model.name}</option>
+					{/each}
+				</select>
+			</div>
+		</div>
+
+		<div class="my-1">
+			<div class="flex w-full justify-between items-center mb-1">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Description')}</div>
+
+				<button
+					class="p-1 text-xs flex rounded transition"
+					type="button"
+					on:click={() => {
+						if (info.meta.description === null) {
+							info.meta.description = '';
+						} else {
+							info.meta.description = null;
+						}
+					}}
+				>
+					{#if info.meta.description === null}
+						<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+					{/if}
+				</button>
+			</div>
+
+			{#if info.meta.description !== null}
+				<textarea
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+					placeholder={$i18n.t('Add a short description about what this model does')}
+					bind:value={info.meta.description}
+					row="3"
+				/>
+			{/if}
+		</div>
+
+		<hr class=" dark:border-gray-850 my-1" />
+
+		<div class="my-2">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Model Params')}</div>
+			</div>
+
+			<div class="mt-2">
+				<div class="my-1">
+					<div class=" text-xs font-semibold mb-2">{$i18n.t('System Prompt')}</div>
+					<div>
+						<Textarea
+							className="px-3 py-2 text-sm w-full bg-transparent border dark:border-gray-600 outline-none resize-none overflow-y-hidden rounded-lg "
+							placeholder={`Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.`}
+							rows={4}
+							bind:value={info.params.system}
+						/>
+					</div>
+				</div>
+
+				<div class="flex w-full justify-between">
+					<div class=" self-center text-xs font-semibold">
+						{$i18n.t('Advanced Params')}
+					</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							showAdvanced = !showAdvanced;
+						}}
+					>
+						{#if showAdvanced}
+							<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Show')}</span>
+						{/if}
+					</button>
+				</div>
+
+				{#if showAdvanced}
+					<div class="my-2">
+						<AdvancedParams
+							admin={true}
+							bind:params
+							on:change={(e) => {
+								info.params = { ...info.params, ...params };
+							}}
+						/>
+					</div>
+				{/if}
+			</div>
+		</div>
+
+		<hr class=" dark:border-gray-850 my-1" />
+
+		<div class="my-1">
+			<div class="flex w-full justify-between items-center">
+				<div class="flex w-full justify-between items-center">
+					<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
+
+					<button
+						class="p-1 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							if (info.meta.suggestion_prompts === null) {
+								info.meta.suggestion_prompts = [{ content: '' }];
+							} else {
+								info.meta.suggestion_prompts = null;
+							}
+						}}
+					>
+						{#if info.meta.suggestion_prompts === null}
+							<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+						{/if}
+					</button>
+				</div>
+
+				{#if info.meta.suggestion_prompts !== null}
+					<button
+						class="p-1 px-2 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							if (
+								info.meta.suggestion_prompts.length === 0 ||
+								info.meta.suggestion_prompts.at(-1).content !== ''
+							) {
+								info.meta.suggestion_prompts = [...info.meta.suggestion_prompts, { content: '' }];
+							}
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 20 20"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+							/>
+						</svg>
+					</button>
+				{/if}
+			</div>
+
+			{#if info.meta.suggestion_prompts}
+				<div class="flex flex-col space-y-1 mt-2">
+					{#if info.meta.suggestion_prompts.length > 0}
+						{#each info.meta.suggestion_prompts as prompt, promptIdx}
+							<div class=" flex border dark:border-gray-600 rounded-lg">
+								<input
+									class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
+									placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
+									bind:value={prompt.content}
+								/>
+
+								<button
+									class="px-2"
+									type="button"
+									on:click={() => {
+										info.meta.suggestion_prompts.splice(promptIdx, 1);
+										info.meta.suggestion_prompts = info.meta.suggestion_prompts;
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 20 20"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+										/>
+									</svg>
+								</button>
+							</div>
+						{/each}
+					{:else}
+						<div class="text-xs text-center">No suggestion prompts</div>
+					{/if}
+				</div>
+			{/if}
+		</div>
+
+		<div class="my-2">
+			<Knowledge bind:selectedKnowledge={knowledge} collections={$_knowledge} />
+		</div>
+
+		<div class="my-2">
+			<ToolsSelector bind:selectedToolIds={toolIds} tools={$tools} />
+		</div>
+
+		<div class="my-2">
+			<FiltersSelector
+				bind:selectedFilterIds={filterIds}
+				filters={$functions.filter((func) => func.type === 'filter')}
+			/>
+		</div>
+
+		<div class="my-2">
+			<ActionsSelector
+				bind:selectedActionIds={actionIds}
+				actions={$functions.filter((func) => func.type === 'action')}
+			/>
+		</div>
+
+		<div class="my-1">
+			<Capabilities bind:capabilities />
+		</div>
+
+		<div class="my-1">
+			<div class="flex w-full justify-between items-center">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Tags')}</div>
+			</div>
+
+			<div class="mt-2">
+				<Tags
+					tags={info?.meta?.tags ?? []}
+					on:delete={(e) => {
+						const tagName = e.detail;
+						info.meta.tags = info.meta.tags.filter((tag) => tag.name !== tagName);
+					}}
+					on:add={(e) => {
+						const tagName = e.detail;
+						if (!(info?.meta?.tags ?? null)) {
+							info.meta.tags = [{ name: tagName }];
+						} else {
+							info.meta.tags = [...info.meta.tags, { name: tagName }];
+						}
+					}}
+				/>
+			</div>
+		</div>
+
+		<div class="my-2 text-gray-300 dark:text-gray-700">
+			<div class="flex w-full justify-between mb-2">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('JSON Preview')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					type="button"
+					on:click={() => {
+						showPreview = !showPreview;
+					}}
+				>
+					{#if showPreview}
+						<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Show')}</span>
+					{/if}
+				</button>
+			</div>
+
+			{#if showPreview}
+				<div>
+					<textarea
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						rows="10"
+						value={JSON.stringify(info, null, 2)}
+						disabled
+						readonly
+					/>
+				</div>
+			{/if}
+		</div>
+
+		<div class="my-2 flex justify-end mb-20">
+			<button
+				class=" text-sm px-3 py-2 transition rounded-xl {loading
+					? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+					: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
+				type="submit"
+				disabled={loading}
+			>
+				<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
+
+				{#if loading}
+					<div class="ml-1.5 self-center">
+						<svg
+							class=" w-4 h-4"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							xmlns="http://www.w3.org/2000/svg"
+							><style>
+								.spinner_ajPY {
+									transform-origin: center;
+									animation: spinner_AtaB 0.75s infinite linear;
+								}
+								@keyframes spinner_AtaB {
+									100% {
+										transform: rotate(360deg);
+									}
+								}
+							</style><path
+								d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+								opacity=".25"
+							/><path
+								d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+								class="spinner_ajPY"
+							/></svg
+						>
+					</div>
+				{/if}
+			</button>
+		</div>
+	</form>
+</div>
diff --git a/src/routes/(app)/workspace/models/edit/+page.svelte b/src/routes/(app)/workspace/models/edit/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..27d7a0af3a669e6a3052753d76701d2529745f3b
--- /dev/null
+++ b/src/routes/(app)/workspace/models/edit/+page.svelte
@@ -0,0 +1,710 @@
+<script>
+	import { v4 as uuidv4 } from 'uuid';
+	import { toast } from 'svelte-sonner';
+	import { goto } from '$app/navigation';
+
+	import { onMount, getContext } from 'svelte';
+	import { page } from '$app/stores';
+	import {
+		settings,
+		user,
+		config,
+		models,
+		tools,
+		functions,
+		knowledge as _knowledge
+	} from '$lib/stores';
+	import { splitStream } from '$lib/utils';
+
+	import { getModelInfos, updateModelById } from '$lib/apis/models';
+
+	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
+	import { getModels } from '$lib/apis';
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+	import Tags from '$lib/components/common/Tags.svelte';
+	import Knowledge from '$lib/components/workspace/Models/Knowledge.svelte';
+	import ToolsSelector from '$lib/components/workspace/Models/ToolsSelector.svelte';
+	import FiltersSelector from '$lib/components/workspace/Models/FiltersSelector.svelte';
+	import ActionsSelector from '$lib/components/workspace/Models/ActionsSelector.svelte';
+	import Capabilities from '$lib/components/workspace/Models/Capabilities.svelte';
+	import Textarea from '$lib/components/common/Textarea.svelte';
+
+	const i18n = getContext('i18n');
+
+	let loading = false;
+	let success = false;
+
+	let filesInputElement;
+	let inputFiles;
+
+	let digest = '';
+	let pullProgress = null;
+
+	let showAdvanced = false;
+	let showPreview = false;
+
+	// ///////////
+	// model
+	// ///////////
+
+	let model = null;
+
+	let id = '';
+	let name = '';
+
+	let info = {
+		id: '',
+		base_model_id: null,
+		name: '',
+		meta: {
+			profile_image_url: '/static/favicon.png',
+			description: '',
+			suggestion_prompts: null,
+			tags: []
+		},
+		params: {
+			system: ''
+		}
+	};
+
+	let params = {};
+	let capabilities = {
+		vision: true
+	};
+
+	let knowledge = [];
+	let toolIds = [];
+	let filterIds = [];
+	let actionIds = [];
+
+	const updateHandler = async () => {
+		loading = true;
+
+		info.id = id;
+		info.name = name;
+		info.meta.capabilities = capabilities;
+
+		if (knowledge.length > 0) {
+			info.meta.knowledge = knowledge;
+		} else {
+			if (info.meta.knowledge) {
+				delete info.meta.knowledge;
+			}
+		}
+
+		if (toolIds.length > 0) {
+			info.meta.toolIds = toolIds;
+		} else {
+			if (info.meta.toolIds) {
+				delete info.meta.toolIds;
+			}
+		}
+
+		if (filterIds.length > 0) {
+			info.meta.filterIds = filterIds;
+		} else {
+			if (info.meta.filterIds) {
+				delete info.meta.filterIds;
+			}
+		}
+
+		if (actionIds.length > 0) {
+			info.meta.actionIds = actionIds;
+		} else {
+			if (info.meta.actionIds) {
+				delete info.meta.actionIds;
+			}
+		}
+
+		info.params.stop = params.stop ? params.stop.split(',').filter((s) => s.trim()) : null;
+		Object.keys(info.params).forEach((key) => {
+			if (info.params[key] === '' || info.params[key] === null) {
+				delete info.params[key];
+			}
+		});
+
+		const res = await updateModelById(localStorage.token, info.id, info);
+
+		if (res) {
+			await models.set(await getModels(localStorage.token));
+			toast.success($i18n.t('Model updated successfully'));
+			await goto('/workspace/models');
+		}
+
+		loading = false;
+		success = false;
+	};
+
+	onMount(() => {
+		const _id = $page.url.searchParams.get('id');
+
+		if (_id) {
+			model = $models.find((m) => m.id === _id && m?.owned_by !== 'arena');
+			if (model) {
+				id = model.id;
+				name = model.name;
+
+				info = {
+					...info,
+					...JSON.parse(
+						JSON.stringify(
+							model?.info
+								? model?.info
+								: {
+										id: model.id,
+										name: model.name
+									}
+						)
+					)
+				};
+
+				if (model.preset && model.owned_by === 'ollama' && !info.base_model_id.includes(':')) {
+					info.base_model_id = `${info.base_model_id}:latest`;
+				}
+
+				params = { ...params, ...model?.info?.params };
+				params.stop = params?.stop
+					? (typeof params.stop === 'string' ? params.stop.split(',') : (params?.stop ?? [])).join(
+							','
+						)
+					: null;
+
+				if (model?.info?.meta?.knowledge) {
+					console.log(model?.info?.meta?.knowledge);
+					knowledge = [...model?.info?.meta?.knowledge].map((item) => {
+						if (item?.collection_name) {
+							return {
+								id: item.collection_name,
+								name: item.name,
+								legacy: true
+							};
+						} else if (item?.collection_names) {
+							return {
+								name: item.name,
+								type: 'collection',
+								collection_names: item.collection_names,
+								legacy: true
+							};
+						} else {
+							return item;
+						}
+					});
+				}
+
+				if (model?.info?.meta?.toolIds) {
+					toolIds = [...model?.info?.meta?.toolIds];
+				}
+
+				if (model?.info?.meta?.filterIds) {
+					filterIds = [...model?.info?.meta?.filterIds];
+				}
+
+				if (model?.info?.meta?.actionIds) {
+					actionIds = [...model?.info?.meta?.actionIds];
+				}
+
+				if (model?.owned_by === 'openai') {
+					capabilities.usage = false;
+				}
+
+				if (model?.info?.meta?.capabilities) {
+					capabilities = { ...capabilities, ...model?.info?.meta?.capabilities };
+				}
+
+				console.log(model);
+			} else {
+				goto('/workspace/models');
+			}
+		} else {
+			goto('/workspace/models');
+		}
+	});
+</script>
+
+<div class="w-full max-h-full">
+	<input
+		bind:this={filesInputElement}
+		bind:files={inputFiles}
+		type="file"
+		hidden
+		accept="image/*"
+		on:change={() => {
+			let reader = new FileReader();
+			reader.onload = (event) => {
+				let originalImageUrl = `${event.target.result}`;
+
+				const img = new Image();
+				img.src = originalImageUrl;
+
+				img.onload = function () {
+					const canvas = document.createElement('canvas');
+					const ctx = canvas.getContext('2d');
+
+					// Calculate the aspect ratio of the image
+					const aspectRatio = img.width / img.height;
+
+					// Calculate the new width and height to fit within 100x100
+					let newWidth, newHeight;
+					if (aspectRatio > 1) {
+						newWidth = 250 * aspectRatio;
+						newHeight = 250;
+					} else {
+						newWidth = 250;
+						newHeight = 250 / aspectRatio;
+					}
+
+					// Set the canvas size
+					canvas.width = 250;
+					canvas.height = 250;
+
+					// Calculate the position to center the image
+					const offsetX = (250 - newWidth) / 2;
+					const offsetY = (250 - newHeight) / 2;
+
+					// Draw the image on the canvas
+					ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
+
+					// Get the base64 representation of the compressed image
+					const compressedSrc = canvas.toDataURL();
+
+					// Display the compressed image
+					info.meta.profile_image_url = compressedSrc;
+
+					inputFiles = null;
+				};
+			};
+
+			if (
+				inputFiles &&
+				inputFiles.length > 0 &&
+				['image/gif', 'image/webp', 'image/jpeg', 'image/png', 'image/svg+xml'].includes(
+					inputFiles[0]['type']
+				)
+			) {
+				reader.readAsDataURL(inputFiles[0]);
+			} else {
+				console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
+				inputFiles = null;
+			}
+		}}
+	/>
+
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			goto('/workspace/models');
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+
+	{#if model}
+		<form
+			class="flex flex-col max-w-2xl mx-auto mt-4 mb-10"
+			on:submit|preventDefault={() => {
+				updateHandler();
+			}}
+		>
+			<div class="flex justify-center my-4">
+				<div class="self-center">
+					<button
+						class=" {info.meta.profile_image_url
+							? ''
+							: 'p-4'} rounded-full border border-dashed border-gray-200 flex items-center"
+						type="button"
+						on:click={() => {
+							filesInputElement.click();
+						}}
+					>
+						{#if info.meta.profile_image_url}
+							<img
+								src={info.meta.profile_image_url}
+								alt="modelfile profile"
+								class=" rounded-full size-16 object-cover"
+							/>
+						{:else}
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 24 24"
+								fill="currentColor"
+								class="size-8"
+							>
+								<path
+									fill-rule="evenodd"
+									d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+									clip-rule="evenodd"
+								/>
+							</svg>
+						{/if}
+					</button>
+				</div>
+			</div>
+
+			<div class="mt-2 my-1 flex space-x-2">
+				<div class="flex-1">
+					<div class=" text-sm font-semibold mb-1">{$i18n.t('Name')}*</div>
+
+					<div>
+						<input
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+							placeholder={$i18n.t('Name your model')}
+							bind:value={name}
+							required
+						/>
+					</div>
+				</div>
+
+				<div class="flex-1">
+					<div class=" text-sm font-semibold mb-1">{$i18n.t('Model ID')}*</div>
+
+					<div>
+						<input
+							class="px-3 py-1.5 text-sm w-full bg-transparent disabled:text-gray-500 border dark:border-gray-600 outline-none rounded-lg"
+							placeholder={$i18n.t('Add a model id')}
+							value={id}
+							disabled
+							required
+						/>
+					</div>
+				</div>
+			</div>
+
+			{#if model.preset}
+				<div class="my-1">
+					<div class=" text-sm font-semibold mb-1">{$i18n.t('Base Model (From)')}</div>
+
+					<div>
+						<select
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+							placeholder="Select a base model (e.g. llama3, gpt-4o)"
+							bind:value={info.base_model_id}
+							required
+						>
+							<option value={null} class=" text-gray-900">{$i18n.t('Select a base model')}</option>
+							{#each $models.filter((m) => m.id !== model.id && !m?.preset && m?.owned_by !== 'arena') as model}
+								<option value={model.id} class=" text-gray-900">{model.name}</option>
+							{/each}
+						</select>
+					</div>
+				</div>
+			{/if}
+
+			<div class="my-1">
+				<div class="flex w-full justify-between items-center">
+					<div class=" self-center text-sm font-semibold">{$i18n.t('Description')}</div>
+
+					<button
+						class="p-1 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							if (info.meta.description === null) {
+								info.meta.description = '';
+							} else {
+								info.meta.description = null;
+							}
+						}}
+					>
+						{#if info.meta.description === null}
+							<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+						{/if}
+					</button>
+				</div>
+
+				{#if info.meta.description !== null}
+					<textarea
+						class="mt-1 px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Add a short description about what this model does')}
+						bind:value={info.meta.description}
+						row="3"
+					/>
+				{/if}
+			</div>
+
+			<hr class=" dark:border-gray-850 my-1" />
+
+			<div class="my-2">
+				<div class="flex w-full justify-between">
+					<div class=" self-center text-sm font-semibold">{$i18n.t('Model Params')}</div>
+				</div>
+
+				<!-- <div class=" text-sm font-semibold mb-2"></div> -->
+
+				<div class="mt-2">
+					<div class="my-1">
+						<div class=" text-xs font-semibold mb-2">{$i18n.t('System Prompt')}</div>
+						<div>
+							<Textarea
+								className="px-3 py-2 text-sm w-full bg-transparent border dark:border-gray-600 outline-none resize-none overflow-y-hidden rounded-lg "
+								placeholder={`Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.`}
+								rows={4}
+								bind:value={info.params.system}
+							/>
+						</div>
+					</div>
+
+					<div class="flex w-full justify-between">
+						<div class=" self-center text-xs font-semibold">
+							{$i18n.t('Advanced Params')}
+						</div>
+
+						<button
+							class="p-1 px-3 text-xs flex rounded transition"
+							type="button"
+							on:click={() => {
+								showAdvanced = !showAdvanced;
+							}}
+						>
+							{#if showAdvanced}
+								<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
+							{:else}
+								<span class="ml-2 self-center">{$i18n.t('Show')}</span>
+							{/if}
+						</button>
+					</div>
+
+					{#if showAdvanced}
+						<div class="my-2">
+							<AdvancedParams
+								admin={true}
+								bind:params
+								on:change={(e) => {
+									info.params = { ...info.params, ...params };
+								}}
+							/>
+						</div>
+					{/if}
+				</div>
+			</div>
+
+			<hr class=" dark:border-gray-850 my-1" />
+
+			<div class="my-2">
+				<div class="flex w-full justify-between items-center">
+					<div class="flex w-full justify-between items-center">
+						<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
+
+						<button
+							class="p-1 text-xs flex rounded transition"
+							type="button"
+							on:click={() => {
+								if ((info?.meta?.suggestion_prompts ?? null) === null) {
+									info.meta.suggestion_prompts = [{ content: '' }];
+								} else {
+									info.meta.suggestion_prompts = null;
+								}
+							}}
+						>
+							{#if (info?.meta?.suggestion_prompts ?? null) === null}
+								<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+							{:else}
+								<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+							{/if}
+						</button>
+					</div>
+
+					{#if (info?.meta?.suggestion_prompts ?? null) !== null}
+						<button
+							class="p-1 px-2 text-xs flex rounded transition"
+							type="button"
+							on:click={() => {
+								if (
+									info.meta.suggestion_prompts.length === 0 ||
+									info.meta.suggestion_prompts.at(-1).content !== ''
+								) {
+									info.meta.suggestion_prompts = [...info.meta.suggestion_prompts, { content: '' }];
+								}
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-4 h-4"
+							>
+								<path
+									d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+								/>
+							</svg>
+						</button>
+					{/if}
+				</div>
+
+				{#if info?.meta?.suggestion_prompts}
+					<div class="flex flex-col space-y-1 mt-2">
+						{#if info.meta.suggestion_prompts.length > 0}
+							{#each info.meta.suggestion_prompts as prompt, promptIdx}
+								<div class=" flex border dark:border-gray-600 rounded-lg">
+									<input
+										class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
+										placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
+										bind:value={prompt.content}
+									/>
+
+									<button
+										class="px-2"
+										type="button"
+										on:click={() => {
+											info.meta.suggestion_prompts.splice(promptIdx, 1);
+											info.meta.suggestion_prompts = info.meta.suggestion_prompts;
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											viewBox="0 0 20 20"
+											fill="currentColor"
+											class="w-4 h-4"
+										>
+											<path
+												d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+											/>
+										</svg>
+									</button>
+								</div>
+							{/each}
+						{:else}
+							<div class="text-xs text-center">No suggestion prompts</div>
+						{/if}
+					</div>
+				{/if}
+			</div>
+
+			<div class="my-2">
+				<Knowledge bind:selectedKnowledge={knowledge} collections={$_knowledge} />
+			</div>
+
+			<div class="my-2">
+				<ToolsSelector bind:selectedToolIds={toolIds} tools={$tools} />
+			</div>
+
+			<div class="my-2">
+				<FiltersSelector
+					bind:selectedFilterIds={filterIds}
+					filters={$functions.filter((func) => func.type === 'filter')}
+				/>
+			</div>
+
+			<div class="my-2">
+				<ActionsSelector
+					bind:selectedActionIds={actionIds}
+					actions={$functions.filter((func) => func.type === 'action')}
+				/>
+			</div>
+
+			<div class="my-2">
+				<Capabilities bind:capabilities />
+			</div>
+
+			<div class="my-1">
+				<div class="flex w-full justify-between items-center">
+					<div class=" self-center text-sm font-semibold">{$i18n.t('Tags')}</div>
+				</div>
+
+				<div class="mt-2">
+					<Tags
+						tags={info?.meta?.tags ?? []}
+						on:delete={(e) => {
+							const tagName = e.detail;
+							info.meta.tags = info.meta.tags.filter((tag) => tag.name !== tagName);
+						}}
+						on:add={(e) => {
+							const tagName = e.detail;
+							if (!(info?.meta?.tags ?? null)) {
+								info.meta.tags = [{ name: tagName }];
+							} else {
+								info.meta.tags = [...info.meta.tags, { name: tagName }];
+							}
+						}}
+					/>
+				</div>
+			</div>
+
+			<div class="my-2 text-gray-300 dark:text-gray-700">
+				<div class="flex w-full justify-between mb-2">
+					<div class=" self-center text-sm font-semibold">{$i18n.t('JSON Preview')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							showPreview = !showPreview;
+						}}
+					>
+						{#if showPreview}
+							<span class="ml-2 self-center">{$i18n.t('Hide')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Show')}</span>
+						{/if}
+					</button>
+				</div>
+
+				{#if showPreview}
+					<div>
+						<textarea
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+							rows="10"
+							value={JSON.stringify(info, null, 2)}
+							disabled
+							readonly
+						/>
+					</div>
+				{/if}
+			</div>
+
+			<div class="my-2 flex justify-end mb-20">
+				<button
+					class=" text-sm px-3 py-2 transition rounded-xl {loading
+						? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+						: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
+					type="submit"
+					disabled={loading}
+				>
+					<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
+
+					{#if loading}
+						<div class="ml-1.5 self-center">
+							<svg
+								class=" w-4 h-4"
+								viewBox="0 0 24 24"
+								fill="currentColor"
+								xmlns="http://www.w3.org/2000/svg"
+								><style>
+									.spinner_ajPY {
+										transform-origin: center;
+										animation: spinner_AtaB 0.75s infinite linear;
+									}
+									@keyframes spinner_AtaB {
+										100% {
+											transform: rotate(360deg);
+										}
+									}
+								</style><path
+									d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+									opacity=".25"
+								/><path
+									d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+									class="spinner_ajPY"
+								/></svg
+							>
+						</div>
+					{/if}
+				</button>
+			</div>
+		</form>
+	{/if}
+</div>
diff --git a/src/routes/(app)/workspace/prompts/+page.svelte b/src/routes/(app)/workspace/prompts/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..48c6e65c6eb5b7a326ce0a23f04fd16ab5031a59
--- /dev/null
+++ b/src/routes/(app)/workspace/prompts/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Prompts from '$lib/components/workspace/Prompts.svelte';
+</script>
+
+<Prompts />
diff --git a/src/routes/(app)/workspace/prompts/create/+page.svelte b/src/routes/(app)/workspace/prompts/create/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..5af9b1186b38ccfe04373f3a457d9179f201202e
--- /dev/null
+++ b/src/routes/(app)/workspace/prompts/create/+page.svelte
@@ -0,0 +1,243 @@
+<script>
+	import { toast } from 'svelte-sonner';
+
+	import { goto } from '$app/navigation';
+	import { prompts } from '$lib/stores';
+	import { onMount, tick, getContext } from 'svelte';
+
+	import { createNewPrompt, getPrompts } from '$lib/apis/prompts';
+	import Textarea from '$lib/components/common/Textarea.svelte';
+
+	const i18n = getContext('i18n');
+
+	let loading = false;
+
+	// ///////////
+	// Prompt
+	// ///////////
+
+	let title = '';
+	let command = '';
+	let content = '';
+
+	$: command = title !== '' ? `${title.replace(/\s+/g, '-').toLowerCase()}` : '';
+
+	const submitHandler = async () => {
+		loading = true;
+
+		if (validateCommandString(command)) {
+			const prompt = await createNewPrompt(localStorage.token, command, title, content).catch(
+				(error) => {
+					toast.error(error);
+
+					return null;
+				}
+			);
+
+			if (prompt) {
+				await prompts.set(await getPrompts(localStorage.token));
+				await goto('/workspace/prompts');
+			}
+		} else {
+			toast.error(
+				$i18n.t('Only alphanumeric characters and hyphens are allowed in the command string.')
+			);
+		}
+
+		loading = false;
+	};
+
+	const validateCommandString = (inputString) => {
+		// Regular expression to match only alphanumeric characters and hyphen
+		const regex = /^[a-zA-Z0-9-]+$/;
+
+		// Test the input string against the regular expression
+		return regex.test(inputString);
+	};
+
+	onMount(async () => {
+		window.addEventListener('message', async (event) => {
+			if (
+				!['https://openwebui.com', 'https://www.openwebui.com', 'http://localhost:5173'].includes(
+					event.origin
+				)
+			)
+				return;
+			const prompt = JSON.parse(event.data);
+			console.log(prompt);
+
+			title = prompt.title;
+			await tick();
+			content = prompt.content;
+			command = prompt.command;
+		});
+
+		if (window.opener ?? false) {
+			window.opener.postMessage('loaded', '*');
+		}
+
+		if (sessionStorage.prompt) {
+			const prompt = JSON.parse(sessionStorage.prompt);
+
+			console.log(prompt);
+			title = prompt.title;
+			await tick();
+			content = prompt.content;
+			command = prompt.command.at(0) === '/' ? prompt.command.slice(1) : prompt.command;
+
+			sessionStorage.removeItem('prompt');
+		}
+	});
+</script>
+
+<div class="w-full max-h-full">
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			history.back();
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+
+	<form
+		class="flex flex-col max-w-2xl mx-auto mt-4 mb-10 pb-10"
+		on:submit|preventDefault={() => {
+			submitHandler();
+		}}
+	>
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Title')}*</div>
+
+			<div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+					placeholder={$i18n.t('Add a short title for this prompt')}
+					bind:value={title}
+					required
+				/>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Command')}*</div>
+
+			<div class="flex items-center mb-1">
+				<div
+					class="bg-gray-200 dark:bg-gray-600 font-semibold px-3 py-1 border border-r-0 dark:border-gray-600 rounded-l-lg"
+				>
+					/
+				</div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-r-lg"
+					placeholder={$i18n.t('short-summary')}
+					bind:value={command}
+					required
+				/>
+			</div>
+
+			<div class="text-xs text-gray-400 dark:text-gray-500">
+				{$i18n.t('Only')}
+				<span class=" text-gray-600 dark:text-gray-300 font-medium"
+					>{$i18n.t('alphanumeric characters and hyphens')}</span
+				>
+				{$i18n.t('are allowed - Activate this command by typing')}&nbsp;"<span
+					class=" text-gray-600 dark:text-gray-300 font-medium"
+				>
+					/{command}
+				</span>" &nbsp;
+				{$i18n.t('to chat input.')}
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt Content')}*</div>
+			</div>
+
+			<div class="mt-2">
+				<div>
+					<Textarea
+						className="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none overflow-y-hidden rounded-lg resize-none"
+						placeholder={$i18n.t('Write a summary in 50 words that summarizes [topic or keyword].')}
+						bind:value={content}
+						rows={6}
+						required
+					/>
+				</div>
+
+				<div class="text-xs text-gray-400 dark:text-gray-500">
+					ⓘ {$i18n.t('Format your variables using brackets like this:')}&nbsp;<span
+						class=" text-gray-600 dark:text-gray-300 font-medium"
+						>{'{{'}{$i18n.t('variable')}{'}}'}</span
+					>.
+					{$i18n.t('Make sure to enclose them with')}
+					<span class=" text-gray-600 dark:text-gray-300 font-medium">{'{{'}</span>
+					{$i18n.t('and')}
+					<span class=" text-gray-600 dark:text-gray-300 font-medium">{'}}'}</span>.
+				</div>
+
+				<div class="text-xs text-gray-400 dark:text-gray-500">
+					{$i18n.t('Utilize')}<span class=" text-gray-600 dark:text-gray-300 font-medium">
+						{` {{CLIPBOARD}}`}</span
+					>
+					{$i18n.t('variable to have them replaced with clipboard content.')}
+				</div>
+			</div>
+		</div>
+
+		<div class="my-2 flex justify-end">
+			<button
+				class=" text-sm px-3 py-2 transition rounded-xl {loading
+					? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+					: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
+				type="submit"
+				disabled={loading}
+			>
+				<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
+
+				{#if loading}
+					<div class="ml-1.5 self-center">
+						<svg
+							class=" w-4 h-4"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							xmlns="http://www.w3.org/2000/svg"
+							><style>
+								.spinner_ajPY {
+									transform-origin: center;
+									animation: spinner_AtaB 0.75s infinite linear;
+								}
+								@keyframes spinner_AtaB {
+									100% {
+										transform: rotate(360deg);
+									}
+								}
+							</style><path
+								d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+								opacity=".25"
+							/><path
+								d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+								class="spinner_ajPY"
+							/></svg
+						>
+					</div>
+				{/if}
+			</button>
+		</div>
+	</form>
+</div>
diff --git a/src/routes/(app)/workspace/prompts/edit/+page.svelte b/src/routes/(app)/workspace/prompts/edit/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..812754e0087d036c084b3c184458e3ab84e78739
--- /dev/null
+++ b/src/routes/(app)/workspace/prompts/edit/+page.svelte
@@ -0,0 +1,230 @@
+<script>
+	import { toast } from 'svelte-sonner';
+
+	import { goto } from '$app/navigation';
+	import { prompts } from '$lib/stores';
+	import { onMount, tick, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
+
+	import { getPrompts, updatePromptByCommand } from '$lib/apis/prompts';
+	import { page } from '$app/stores';
+	import Textarea from '$lib/components/common/Textarea.svelte';
+
+	let loading = false;
+
+	// ///////////
+	// Prompt
+	// ///////////
+
+	let title = '';
+	let command = '';
+	let content = '';
+
+	const updateHandler = async () => {
+		loading = true;
+
+		if (validateCommandString(command)) {
+			const prompt = await updatePromptByCommand(localStorage.token, command, title, content).catch(
+				(error) => {
+					toast.error(error);
+					return null;
+				}
+			);
+
+			if (prompt) {
+				await prompts.set(await getPrompts(localStorage.token));
+				await goto('/workspace/prompts');
+			}
+		} else {
+			toast.error(
+				$i18n.t('Only alphanumeric characters and hyphens are allowed in the command string.')
+			);
+		}
+
+		loading = false;
+	};
+
+	const validateCommandString = (inputString) => {
+		// Regular expression to match only alphanumeric characters and hyphen
+		const regex = /^[a-zA-Z0-9-]+$/;
+
+		// Test the input string against the regular expression
+		return regex.test(inputString);
+	};
+
+	onMount(async () => {
+		command = $page.url.searchParams.get('command');
+		if (command) {
+			const prompt = $prompts.filter((prompt) => prompt.command === command).at(0);
+
+			if (prompt) {
+				console.log(prompt);
+
+				console.log(prompt.command);
+
+				title = prompt.title;
+				await tick();
+				command = prompt.command.slice(1);
+				content = prompt.content;
+			} else {
+				goto('/workspace/prompts');
+			}
+		} else {
+			goto('/workspace/prompts');
+		}
+	});
+</script>
+
+<div class="w-full max-h-full">
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			history.back();
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+
+	<form
+		class="flex flex-col max-w-2xl mx-auto mt-4 mb-10 pb-10"
+		on:submit|preventDefault={() => {
+			updateHandler();
+		}}
+	>
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Title')}*</div>
+
+			<div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+					placeholder={$i18n.t('Add a short title for this prompt')}
+					bind:value={title}
+					required
+				/>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Command')}*</div>
+
+			<div class="flex items-center mb-1">
+				<div
+					class="bg-gray-200 dark:bg-gray-600 font-semibold px-3 py-1 border border-r-0 dark:border-gray-600 rounded-l-lg"
+				>
+					/
+				</div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border disabled:text-gray-500 dark:border-gray-600 outline-none rounded-r-lg"
+					placeholder="short-summary"
+					bind:value={command}
+					disabled
+					required
+				/>
+			</div>
+
+			<div class="text-xs text-gray-400 dark:text-gray-500">
+				{$i18n.t('Only')}
+				<span class=" text-gray-600 dark:text-gray-300 font-medium"
+					>{$i18n.t('alphanumeric characters and hyphens')}</span
+				>
+				{$i18n.t('are allowed - Activate this command by typing')}&nbsp;"<span
+					class=" text-gray-600 dark:text-gray-300 font-medium"
+				>
+					/{command}
+				</span>" &nbsp;
+				{$i18n.t('to chat input.')}
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt Content')}*</div>
+			</div>
+
+			<div class="mt-2">
+				<div>
+					<Textarea
+						className="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none overflow-y-hidden rounded-lg resize-none"
+						placeholder={$i18n.t(`Write a summary in 50 words that summarizes [topic or keyword].`)}
+						rows={6}
+						bind:value={content}
+						required
+					/>
+				</div>
+
+				<div class="text-xs text-gray-400 dark:text-gray-500">
+					ⓘ {$i18n.t('Format your variables using brackets like this:')}&nbsp;<span
+						class=" text-gray-600 dark:text-gray-300 font-medium"
+						>{'{{'}{$i18n.t('variable')}{'}}'}</span
+					>.
+					{$i18n.t('Make sure to enclose them with')}
+					<span class=" text-gray-600 dark:text-gray-300 font-medium">{'{{'}</span>
+					{$i18n.t('and')}
+					<span class=" text-gray-600 dark:text-gray-300 font-medium">{'}}'}</span>.
+				</div>
+
+				<div class="text-xs text-gray-400 dark:text-gray-500">
+					{$i18n.t('Utilize')}<span class=" text-gray-600 dark:text-gray-300 font-medium">
+						{` {{CLIPBOARD}}`}</span
+					>
+					{$i18n.t('variable to have them replaced with clipboard content.')}
+				</div>
+			</div>
+		</div>
+
+		<div class="my-2 flex justify-end">
+			<button
+				class=" text-sm px-3 py-2 transition rounded-xl {loading
+					? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+					: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
+				type="submit"
+				disabled={loading}
+			>
+				<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
+
+				{#if loading}
+					<div class="ml-1.5 self-center">
+						<svg
+							class=" w-4 h-4"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							xmlns="http://www.w3.org/2000/svg"
+							><style>
+								.spinner_ajPY {
+									transform-origin: center;
+									animation: spinner_AtaB 0.75s infinite linear;
+								}
+								@keyframes spinner_AtaB {
+									100% {
+										transform: rotate(360deg);
+									}
+								}
+							</style><path
+								d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+								opacity=".25"
+							/><path
+								d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+								class="spinner_ajPY"
+							/></svg
+						>
+					</div>
+				{/if}
+			</button>
+		</div>
+	</form>
+</div>
diff --git a/src/routes/(app)/workspace/tools/+page.svelte b/src/routes/(app)/workspace/tools/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..e745cfa80e984b9816247397b4fb824722a49e9b
--- /dev/null
+++ b/src/routes/(app)/workspace/tools/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import Tools from '$lib/components/workspace/Tools.svelte';
+</script>
+
+<Tools />
diff --git a/src/routes/(app)/workspace/tools/create/+page.svelte b/src/routes/(app)/workspace/tools/create/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..29a285d2269abe02847f0865a6d5c5e0d05ebdb1
--- /dev/null
+++ b/src/routes/(app)/workspace/tools/create/+page.svelte
@@ -0,0 +1,95 @@
+<script>
+	import { goto } from '$app/navigation';
+	import { createNewTool, getTools } from '$lib/apis/tools';
+	import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte';
+	import { WEBUI_VERSION } from '$lib/constants';
+	import { tools } from '$lib/stores';
+	import { compareVersion, extractFrontmatter } from '$lib/utils';
+	import { onMount, getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+
+	const i18n = getContext('i18n');
+
+	let mounted = false;
+	let clone = false;
+	let tool = null;
+
+	const saveHandler = async (data) => {
+		console.log(data);
+
+		const manifest = extractFrontmatter(data.content);
+		if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) {
+			console.log('Version is lower than required');
+			toast.error(
+				$i18n.t(
+					'Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})',
+					{
+						OPEN_WEBUI_VERSION: WEBUI_VERSION,
+						REQUIRED_VERSION: manifest?.required_open_webui_version ?? '0.0.0'
+					}
+				)
+			);
+			return;
+		}
+
+		const res = await createNewTool(localStorage.token, {
+			id: data.id,
+			name: data.name,
+			meta: data.meta,
+			content: data.content
+		}).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Tool created successfully'));
+			tools.set(await getTools(localStorage.token));
+
+			await goto('/workspace/tools');
+		}
+	};
+
+	onMount(() => {
+		window.addEventListener('message', async (event) => {
+			if (
+				!['https://openwebui.com', 'https://www.openwebui.com', 'http://localhost:9999'].includes(
+					event.origin
+				)
+			)
+				return;
+
+			tool = JSON.parse(event.data);
+			console.log(tool);
+		});
+
+		if (window.opener ?? false) {
+			window.opener.postMessage('loaded', '*');
+		}
+
+		if (sessionStorage.tool) {
+			tool = JSON.parse(sessionStorage.tool);
+			sessionStorage.removeItem('tool');
+
+			console.log(tool);
+			clone = true;
+		}
+
+		mounted = true;
+	});
+</script>
+
+{#if mounted}
+	{#key tool?.content}
+		<ToolkitEditor
+			id={tool?.id ?? ''}
+			name={tool?.name ?? ''}
+			meta={tool?.meta ?? { description: '' }}
+			content={tool?.content ?? ''}
+			{clone}
+			on:save={(e) => {
+				saveHandler(e.detail);
+			}}
+		/>
+	{/key}
+{/if}
diff --git a/src/routes/(app)/workspace/tools/edit/+page.svelte b/src/routes/(app)/workspace/tools/edit/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ebd47701e37f119707664022d406c2481aa7fdea
--- /dev/null
+++ b/src/routes/(app)/workspace/tools/edit/+page.svelte
@@ -0,0 +1,86 @@
+<script>
+	import { goto } from '$app/navigation';
+	import { page } from '$app/stores';
+	import { getToolById, getTools, updateToolById } from '$lib/apis/tools';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte';
+	import { WEBUI_VERSION } from '$lib/constants';
+	import { tools } from '$lib/stores';
+	import { compareVersion, extractFrontmatter } from '$lib/utils';
+	import { onMount, getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+
+	const i18n = getContext('i18n');
+
+	let tool = null;
+
+	const saveHandler = async (data) => {
+		console.log(data);
+
+		const manifest = extractFrontmatter(data.content);
+		if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) {
+			console.log('Version is lower than required');
+			toast.error(
+				$i18n.t(
+					'Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})',
+					{
+						OPEN_WEBUI_VERSION: WEBUI_VERSION,
+						REQUIRED_VERSION: manifest?.required_open_webui_version ?? '0.0.0'
+					}
+				)
+			);
+			return;
+		}
+
+		const res = await updateToolById(localStorage.token, tool.id, {
+			id: data.id,
+			name: data.name,
+			meta: data.meta,
+			content: data.content
+		}).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Tool updated successfully'));
+			tools.set(await getTools(localStorage.token));
+
+			// await goto('/workspace/tools');
+		}
+	};
+
+	onMount(async () => {
+		console.log('mounted');
+		const id = $page.url.searchParams.get('id');
+
+		if (id) {
+			tool = await getToolById(localStorage.token, id).catch((error) => {
+				toast.error(error);
+				goto('/workspace/tools');
+				return null;
+			});
+
+			console.log(tool);
+		}
+	});
+</script>
+
+{#if tool}
+	<ToolkitEditor
+		edit={true}
+		id={tool.id}
+		name={tool.name}
+		meta={tool.meta}
+		content={tool.content}
+		on:save={(e) => {
+			saveHandler(e.detail);
+		}}
+	/>
+{:else}
+	<div class="flex items-center justify-center h-full">
+		<div class=" pb-16">
+			<Spinner />
+		</div>
+	</div>
+{/if}
diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..76748a1d3139141c43c101cfa5b74758e78233f2
--- /dev/null
+++ b/src/routes/+error.svelte
@@ -0,0 +1,11 @@
+<script>
+	import { page } from '$app/stores';
+</script>
+
+<div class=" bg-white dark:bg-gray-800 min-h-screen">
+	<div class=" flex h-full">
+		<div class="m-auto my-10 dark:text-gray-300 text-3xl font-semibold">
+			{$page.status}: {$page.error.message}
+		</div>
+	</div>
+</div>
diff --git a/src/routes/+layout.js b/src/routes/+layout.js
new file mode 100644
index 0000000000000000000000000000000000000000..b49c52809497f1c37096bafc5ea6919e0438c2d9
--- /dev/null
+++ b/src/routes/+layout.js
@@ -0,0 +1,16 @@
+// if you want to generate a static html file
+// for your page.
+// Documentation: https://kit.svelte.dev/docs/page-options#prerender
+// export const prerender = true;
+
+// if you want to Generate a SPA
+// you have to set ssr to false.
+// This is not the case (so set as true or comment the line)
+// Documentation: https://kit.svelte.dev/docs/page-options#ssr
+export const ssr = false;
+
+// How to manage the trailing slashes in the URLs
+// the URL for about page witll be /about with 'ignore' (default)
+// the URL for about page witll be /about/ with 'always'
+// https://kit.svelte.dev/docs/page-options#trailingslash
+export const trailingSlash = 'ignore';
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..d1c30a96b6a07087307fb3f9fcc4ddde80cfce4b
--- /dev/null
+++ b/src/routes/+layout.svelte
@@ -0,0 +1,222 @@
+<script>
+	import { io } from 'socket.io-client';
+	import { spring } from 'svelte/motion';
+
+	let loadingProgress = spring(0, {
+		stiffness: 0.05
+	});
+
+	import { onMount, tick, setContext } from 'svelte';
+	import {
+		config,
+		user,
+		theme,
+		WEBUI_NAME,
+		mobile,
+		socket,
+		activeUserCount,
+		USAGE_POOL
+	} from '$lib/stores';
+	import { goto } from '$app/navigation';
+	import { page } from '$app/stores';
+	import { Toaster, toast } from 'svelte-sonner';
+
+	import { getBackendConfig } from '$lib/apis';
+	import { getSessionUser } from '$lib/apis/auths';
+
+	import '../tailwind.css';
+	import '../app.css';
+
+	import 'tippy.js/dist/tippy.css';
+
+	import { WEBUI_BASE_URL, WEBUI_HOSTNAME } from '$lib/constants';
+	import i18n, { initI18n, getLanguages } from '$lib/i18n';
+	import { bestMatchingLanguage } from '$lib/utils';
+
+	setContext('i18n', i18n);
+
+	let loaded = false;
+	const BREAKPOINT = 768;
+
+	const setupSocket = () => {
+		const _socket = io(`${WEBUI_BASE_URL}` || undefined, {
+			reconnection: true,
+			reconnectionDelay: 1000,
+			reconnectionDelayMax: 5000,
+			randomizationFactor: 0.5,
+			path: '/ws/socket.io',
+			auth: { token: localStorage.token }
+		});
+
+		socket.set(_socket);
+
+		_socket.on('connect_error', (err) => {
+			console.log('connect_error', err);
+		});
+
+		_socket.on('connect', () => {
+			console.log('connected', _socket.id);
+		});
+
+		_socket.on('reconnect_attempt', (attempt) => {
+			console.log('reconnect_attempt', attempt);
+		});
+
+		_socket.on('reconnect_failed', () => {
+			console.log('reconnect_failed');
+		});
+
+		_socket.on('disconnect', (reason, details) => {
+			console.log(`Socket ${_socket.id} disconnected due to ${reason}`);
+			if (details) {
+				console.log('Additional details:', details);
+			}
+		});
+
+		_socket.on('user-count', (data) => {
+			console.log('user-count', data);
+			activeUserCount.set(data.count);
+		});
+
+		_socket.on('usage', (data) => {
+			console.log('usage', data);
+			USAGE_POOL.set(data['models']);
+		});
+	};
+
+	onMount(async () => {
+		theme.set(localStorage.theme);
+
+		mobile.set(window.innerWidth < BREAKPOINT);
+		const onResize = () => {
+			if (window.innerWidth < BREAKPOINT) {
+				mobile.set(true);
+			} else {
+				mobile.set(false);
+			}
+		};
+
+		window.addEventListener('resize', onResize);
+
+		let backendConfig = null;
+		try {
+			backendConfig = await getBackendConfig();
+			console.log('Backend config:', backendConfig);
+		} catch (error) {
+			console.error('Error loading backend config:', error);
+		}
+		// Initialize i18n even if we didn't get a backend config,
+		// so `/error` can show something that's not `undefined`.
+
+		initI18n();
+		if (!localStorage.locale) {
+			const languages = await getLanguages();
+			const browserLanguages = navigator.languages
+				? navigator.languages
+				: [navigator.language || navigator.userLanguage];
+			const lang = backendConfig.default_locale
+				? backendConfig.default_locale
+				: bestMatchingLanguage(languages, browserLanguages, 'en-US');
+			$i18n.changeLanguage(lang);
+		}
+
+		if (backendConfig) {
+			// Save Backend Status to Store
+			await config.set(backendConfig);
+			await WEBUI_NAME.set(backendConfig.name);
+
+			if ($config) {
+				setupSocket();
+
+				if (localStorage.token) {
+					// Get Session User Info
+					const sessionUser = await getSessionUser(localStorage.token).catch((error) => {
+						toast.error(error);
+						return null;
+					});
+
+					if (sessionUser) {
+						// Save Session User to Store
+						await user.set(sessionUser);
+						await config.set(await getBackendConfig());
+					} else {
+						// Redirect Invalid Session User to /auth Page
+						localStorage.removeItem('token');
+						await goto('/auth');
+					}
+				} else {
+					// Don't redirect if we're already on the auth page
+					// Needed because we pass in tokens from OAuth logins via URL fragments
+					if ($page.url.pathname !== '/auth') {
+						await goto('/auth');
+					}
+				}
+			}
+		} else {
+			// Redirect to /error when Backend Not Detected
+			await goto(`/error`);
+		}
+
+		await tick();
+
+		if (
+			document.documentElement.classList.contains('her') &&
+			document.getElementById('progress-bar')
+		) {
+			loadingProgress.subscribe((value) => {
+				const progressBar = document.getElementById('progress-bar');
+
+				if (progressBar) {
+					progressBar.style.width = `${value}%`;
+				}
+			});
+
+			await loadingProgress.set(100);
+
+			document.getElementById('splash-screen')?.remove();
+
+			const audio = new Audio(`/audio/greeting.mp3`);
+			const playAudio = () => {
+				audio.play();
+				document.removeEventListener('click', playAudio);
+			};
+
+			document.addEventListener('click', playAudio);
+
+			loaded = true;
+		} else {
+			document.getElementById('splash-screen')?.remove();
+			loaded = true;
+		}
+
+		return () => {
+			window.removeEventListener('resize', onResize);
+		};
+	});
+</script>
+
+<svelte:head>
+	<title>{$WEBUI_NAME}</title>
+	<link crossorigin="anonymous" rel="icon" href="{WEBUI_BASE_URL}/static/favicon.png" />
+
+	<!-- rosepine themes have been disabled as it's not up to date with our latest version. -->
+	<!-- feel free to make a PR to fix if anyone wants to see it return -->
+	<!-- <link rel="stylesheet" type="text/css" href="/themes/rosepine.css" />
+	<link rel="stylesheet" type="text/css" href="/themes/rosepine-dawn.css" /> -->
+</svelte:head>
+
+{#if loaded}
+	<slot />
+{/if}
+
+<Toaster
+	theme={$theme.includes('dark')
+		? 'dark'
+		: $theme === 'system'
+			? window.matchMedia('(prefers-color-scheme: dark)').matches
+				? 'dark'
+				: 'light'
+			: 'light'}
+	richColors
+	position="top-center"
+/>
diff --git a/src/routes/auth/+page.svelte b/src/routes/auth/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f6ba932b9d087828768c16271a931154d7ad677f
--- /dev/null
+++ b/src/routes/auth/+page.svelte
@@ -0,0 +1,376 @@
+<script>
+	import { goto } from '$app/navigation';
+	import { getSessionUser, userSignIn, userSignUp } from '$lib/apis/auths';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
+	import { WEBUI_NAME, config, user, socket } from '$lib/stores';
+	import { onMount, getContext } from 'svelte';
+	import { toast } from 'svelte-sonner';
+	import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
+	import { page } from '$app/stores';
+	import { getBackendConfig } from '$lib/apis';
+
+	const i18n = getContext('i18n');
+
+	let loaded = false;
+	let mode = 'signin';
+
+	let name = '';
+	let email = '';
+	let password = '';
+
+	const setSessionUser = async (sessionUser) => {
+		if (sessionUser) {
+			console.log(sessionUser);
+			toast.success($i18n.t(`You're now logged in.`));
+			if (sessionUser.token) {
+				localStorage.token = sessionUser.token;
+			}
+
+			$socket.emit('user-join', { auth: { token: sessionUser.token } });
+			await user.set(sessionUser);
+			await config.set(await getBackendConfig());
+			goto('/');
+		}
+	};
+
+	const signInHandler = async () => {
+		const sessionUser = await userSignIn(email, password).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		await setSessionUser(sessionUser);
+	};
+
+	const signUpHandler = async () => {
+		const sessionUser = await userSignUp(name, email, password, generateInitialsImage(name)).catch(
+			(error) => {
+				toast.error(error);
+				return null;
+			}
+		);
+
+		await setSessionUser(sessionUser);
+	};
+
+	const submitHandler = async () => {
+		if (mode === 'signin') {
+			await signInHandler();
+		} else {
+			await signUpHandler();
+		}
+	};
+
+	const checkOauthCallback = async () => {
+		if (!$page.url.hash) {
+			return;
+		}
+		const hash = $page.url.hash.substring(1);
+		if (!hash) {
+			return;
+		}
+		const params = new URLSearchParams(hash);
+		const token = params.get('token');
+		if (!token) {
+			return;
+		}
+		const sessionUser = await getSessionUser(token).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+		if (!sessionUser) {
+			return;
+		}
+		localStorage.token = token;
+		await setSessionUser(sessionUser);
+	};
+
+	onMount(async () => {
+		if ($user !== undefined) {
+			await goto('/');
+		}
+		await checkOauthCallback();
+		loaded = true;
+		if (($config?.features.auth_trusted_header ?? false) || $config?.features.auth === false) {
+			await signInHandler();
+		}
+	});
+</script>
+
+<svelte:head>
+	<title>
+		{`${$WEBUI_NAME}`}
+	</title>
+</svelte:head>
+
+{#if loaded}
+	<div class="fixed m-10 z-50">
+		<div class="flex space-x-2">
+			<div class=" self-center">
+				<img
+					crossorigin="anonymous"
+					src="{WEBUI_BASE_URL}/static/favicon.png"
+					class=" w-8 rounded-full"
+					alt="logo"
+				/>
+			</div>
+		</div>
+	</div>
+
+	<div class=" bg-white dark:bg-gray-950 min-h-screen w-full flex justify-center font-primary">
+		<!-- <div class="hidden lg:flex lg:flex-1 px-10 md:px-16 w-full bg-yellow-50 justify-center">
+			<div class=" my-auto pb-16 text-left">
+				<div>
+					<div class=" font-semibold text-yellow-600 text-4xl">
+						{$i18n.t('Get up and running with')} <br /> {$i18n.t('large language models, locally.')}
+					</div>
+
+					<div class="mt-2 text-yellow-600 text-xl">
+						{$i18n.t('Run Llama 2, Code Llama, and other models. Customize and create your own.')}
+					</div>
+				</div>
+			</div>
+		</div> -->
+
+		<div class="w-full sm:max-w-md px-10 min-h-screen flex flex-col text-center">
+			{#if ($config?.features.auth_trusted_header ?? false) || $config?.features.auth === false}
+				<div class=" my-auto pb-10 w-full">
+					<div
+						class="flex items-center justify-center gap-3 text-xl sm:text-2xl text-center font-semibold dark:text-gray-200"
+					>
+						<div>
+							{$i18n.t('Signing in to {{WEBUI_NAME}}', { WEBUI_NAME: $WEBUI_NAME })}
+						</div>
+
+						<div>
+							<Spinner />
+						</div>
+					</div>
+				</div>
+			{:else}
+				<div class="  my-auto pb-10 w-full dark:text-gray-100">
+					<form
+						class=" flex flex-col justify-center"
+						on:submit|preventDefault={() => {
+							submitHandler();
+						}}
+					>
+						<div class="mb-1">
+							<div class=" text-2xl font-medium">
+								{#if mode === 'signin'}
+									{$i18n.t(`Sign in to {{WEBUI_NAME}}`, { WEBUI_NAME: $WEBUI_NAME })}
+								{:else}
+									{$i18n.t(`Sign up to {{WEBUI_NAME}}`, { WEBUI_NAME: $WEBUI_NAME })}
+								{/if}
+							</div>
+
+							{#if mode === 'signup'}
+								<div class=" mt-1 text-xs font-medium text-gray-500">
+									ⓘ {$WEBUI_NAME}
+									{$i18n.t(
+										'does not make any external connections, and your data stays securely on your locally hosted server.'
+									)}
+								</div>
+							{/if}
+						</div>
+
+						{#if $config?.features.enable_login_form}
+							<div class="flex flex-col mt-4">
+								{#if mode === 'signup'}
+									<div>
+										<div class=" text-sm font-medium text-left mb-1">{$i18n.t('Name')}</div>
+										<input
+											bind:value={name}
+											type="text"
+											class=" px-5 py-3 rounded-2xl w-full text-sm outline-none border dark:border-none dark:bg-gray-900"
+											autocomplete="name"
+											placeholder={$i18n.t('Enter Your Full Name')}
+											required
+										/>
+									</div>
+
+									<hr class=" my-3 dark:border-gray-900" />
+								{/if}
+
+								<div class="mb-2">
+									<div class=" text-sm font-medium text-left mb-1">{$i18n.t('Email')}</div>
+									<input
+										bind:value={email}
+										type="email"
+										class=" px-5 py-3 rounded-2xl w-full text-sm outline-none border dark:border-none dark:bg-gray-900"
+										autocomplete="email"
+										placeholder={$i18n.t('Enter Your Email')}
+										required
+									/>
+								</div>
+
+								<div>
+									<div class=" text-sm font-medium text-left mb-1">{$i18n.t('Password')}</div>
+
+									<input
+										bind:value={password}
+										type="password"
+										class=" px-5 py-3 rounded-2xl w-full text-sm outline-none border dark:border-none dark:bg-gray-900"
+										placeholder={$i18n.t('Enter Your Password')}
+										autocomplete="current-password"
+										required
+									/>
+								</div>
+							</div>
+						{/if}
+
+						{#if $config?.features.enable_login_form}
+							<div class="mt-5">
+								<button
+									class=" bg-gray-900 hover:bg-gray-800 w-full rounded-2xl text-white font-medium text-sm py-3 transition"
+									type="submit"
+								>
+									{mode === 'signin' ? $i18n.t('Sign in') : $i18n.t('Create Account')}
+								</button>
+
+								{#if $config?.features.enable_signup}
+									<div class=" mt-4 text-sm text-center">
+										{mode === 'signin'
+											? $i18n.t("Don't have an account?")
+											: $i18n.t('Already have an account?')}
+
+										<button
+											class=" font-medium underline"
+											type="button"
+											on:click={() => {
+												if (mode === 'signin') {
+													mode = 'signup';
+												} else {
+													mode = 'signin';
+												}
+											}}
+										>
+											{mode === 'signin' ? $i18n.t('Sign up') : $i18n.t('Sign in')}
+										</button>
+									</div>
+								{/if}
+							</div>
+						{/if}
+					</form>
+
+					{#if Object.keys($config?.oauth?.providers ?? {}).length > 0}
+						<div class="inline-flex items-center justify-center w-full">
+							<hr class="w-64 h-px my-8 bg-gray-200 border-0 dark:bg-gray-700" />
+							{#if $config?.features.enable_login_form}
+								<span
+									class="absolute px-3 font-medium text-gray-900 -translate-x-1/2 bg-white left-1/2 dark:text-white dark:bg-gray-950"
+									>{$i18n.t('or')}</span
+								>
+							{/if}
+						</div>
+						<div class="flex flex-col space-y-2">
+							{#if $config?.oauth?.providers?.google}
+								<button
+									class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
+									on:click={() => {
+										window.location.href = `${WEBUI_BASE_URL}/oauth/google/login`;
+									}}
+								>
+									<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="size-6 mr-3">
+										<path
+											fill="#EA4335"
+											d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"
+										/><path
+											fill="#4285F4"
+											d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"
+										/><path
+											fill="#FBBC05"
+											d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"
+										/><path
+											fill="#34A853"
+											d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"
+										/><path fill="none" d="M0 0h48v48H0z" />
+									</svg>
+									<span>{$i18n.t('Continue with {{provider}}', { provider: 'Google' })}</span>
+								</button>
+							{/if}
+							{#if $config?.oauth?.providers?.microsoft}
+								<button
+									class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
+									on:click={() => {
+										window.location.href = `${WEBUI_BASE_URL}/oauth/microsoft/login`;
+									}}
+								>
+									<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21" class="size-6 mr-3">
+										<rect x="1" y="1" width="9" height="9" fill="#f25022" /><rect
+											x="1"
+											y="11"
+											width="9"
+											height="9"
+											fill="#00a4ef"
+										/><rect x="11" y="1" width="9" height="9" fill="#7fba00" /><rect
+											x="11"
+											y="11"
+											width="9"
+											height="9"
+											fill="#ffb900"
+										/>
+									</svg>
+									<span>{$i18n.t('Continue with {{provider}}', { provider: 'Microsoft' })}</span>
+								</button>
+							{/if}
+							{#if $config?.oauth?.providers?.oidc}
+								<button
+									class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
+									on:click={() => {
+										window.location.href = `${WEBUI_BASE_URL}/oauth/oidc/login`;
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										fill="none"
+										viewBox="0 0 24 24"
+										stroke-width="1.5"
+										stroke="currentColor"
+										class="size-6 mr-3"
+									>
+										<path
+											stroke-linecap="round"
+											stroke-linejoin="round"
+											d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"
+										/>
+									</svg>
+
+									<span
+										>{$i18n.t('Continue with {{provider}}', {
+											provider: $config?.oauth?.providers?.oidc ?? 'SSO'
+										})}</span
+									>
+								</button>
+							{/if}
+						</div>
+					{/if}
+				</div>
+			{/if}
+		</div>
+	</div>
+{/if}
+
+<style>
+	.font-mona {
+		font-family:
+			'Mona Sans',
+			-apple-system,
+			'Inter',
+			ui-sans-serif,
+			system-ui,
+			'Segoe UI',
+			Roboto,
+			Ubuntu,
+			Cantarell,
+			'Noto Sans',
+			sans-serif,
+			'Helvetica Neue',
+			Arial,
+			'Apple Color Emoji',
+			'Segoe UI Emoji',
+			'Segoe UI Symbol',
+			'Noto Color Emoji';
+	}
+</style>
diff --git a/src/routes/error/+page.svelte b/src/routes/error/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..955186600f72144c1db4666dbe8a6396796e6bfa
--- /dev/null
+++ b/src/routes/error/+page.svelte
@@ -0,0 +1,60 @@
+<script>
+	import { goto } from '$app/navigation';
+	import { WEBUI_NAME, config } from '$lib/stores';
+	import { onMount, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
+
+	let loaded = false;
+
+	onMount(async () => {
+		if ($config) {
+			await goto('/');
+		}
+
+		loaded = true;
+	});
+</script>
+
+{#if loaded}
+	<div class="absolute w-full h-full flex z-50">
+		<div class="absolute rounded-xl w-full h-full backdrop-blur flex justify-center">
+			<div class="m-auto pb-44 flex flex-col justify-center">
+				<div class="max-w-md">
+					<div class="text-center text-2xl font-medium z-50">
+						{$i18n.t('{{webUIName}} Backend Required', { webUIName: $WEBUI_NAME })}
+					</div>
+
+					<div class=" mt-4 text-center text-sm w-full">
+						{$i18n.t(
+							"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend."
+						)}
+
+						<br class=" " />
+						<br class=" " />
+						<a
+							class=" font-semibold underline"
+							href="https://github.com/open-webui/open-webui#how-to-install-"
+							target="_blank">{$i18n.t('See readme.md for instructions')}</a
+						>
+						{$i18n.t('or')}
+						<a class=" font-semibold underline" href="https://discord.gg/5rJgQTnV4s" target="_blank"
+							>{$i18n.t('join our Discord for help.')}</a
+						>
+					</div>
+
+					<div class=" mt-6 mx-auto relative group w-fit">
+						<button
+							class="relative z-20 flex px-5 py-2 rounded-full bg-gray-100 hover:bg-gray-200 transition font-medium text-sm"
+							on:click={() => {
+								location.href = '/';
+							}}
+						>
+							{$i18n.t('Check Again')}
+						</button>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+{/if}
diff --git a/src/routes/s/[id]/+page.svelte b/src/routes/s/[id]/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..8980972727378234ed706f3f02115d0b3b409d4c
--- /dev/null
+++ b/src/routes/s/[id]/+page.svelte
@@ -0,0 +1,166 @@
+<script lang="ts">
+	import { onMount, tick, getContext } from 'svelte';
+	import { goto } from '$app/navigation';
+	import { page } from '$app/stores';
+
+	import dayjs from 'dayjs';
+
+	import { settings, chatId, WEBUI_NAME, models } from '$lib/stores';
+	import { convertMessagesToHistory } from '$lib/utils';
+
+	import { getChatByShareId } from '$lib/apis/chats';
+
+	import Messages from '$lib/components/chat/Messages.svelte';
+	import Navbar from '$lib/components/layout/Navbar.svelte';
+	import { getUserById } from '$lib/apis/users';
+	import { error } from '@sveltejs/kit';
+	import { getModels } from '$lib/apis';
+
+	const i18n = getContext('i18n');
+
+	let loaded = false;
+
+	let autoScroll = true;
+	let processing = '';
+	let messagesContainerElement: HTMLDivElement;
+
+	// let chatId = $page.params.id;
+	let showModelSelector = false;
+	let selectedModels = [''];
+
+	let chat = null;
+	let user = null;
+
+	let title = '';
+	let files = [];
+
+	let messages = [];
+	let history = {
+		messages: {},
+		currentId: null
+	};
+
+	$: if (history.currentId !== null) {
+		let _messages = [];
+
+		let currentMessage = history.messages[history.currentId];
+		while (currentMessage !== null) {
+			_messages.unshift({ ...currentMessage });
+			currentMessage =
+				currentMessage.parentId !== null ? history.messages[currentMessage.parentId] : null;
+		}
+		messages = _messages;
+	} else {
+		messages = [];
+	}
+
+	$: if ($page.params.id) {
+		(async () => {
+			if (await loadSharedChat()) {
+				await tick();
+				loaded = true;
+			} else {
+				await goto('/');
+			}
+		})();
+	}
+
+	//////////////////////////
+	// Web functions
+	//////////////////////////
+
+	const loadSharedChat = async () => {
+		await models.set(await getModels(localStorage.token));
+		await chatId.set($page.params.id);
+		chat = await getChatByShareId(localStorage.token, $chatId).catch(async (error) => {
+			await goto('/');
+			return null;
+		});
+
+		if (chat) {
+			user = await getUserById(localStorage.token, chat.user_id).catch((error) => {
+				console.error(error);
+				return null;
+			});
+
+			const chatContent = chat.chat;
+
+			if (chatContent) {
+				console.log(chatContent);
+
+				selectedModels =
+					(chatContent?.models ?? undefined) !== undefined
+						? chatContent.models
+						: [chatContent.models ?? ''];
+				history =
+					(chatContent?.history ?? undefined) !== undefined
+						? chatContent.history
+						: convertMessagesToHistory(chatContent.messages);
+				title = chatContent.title;
+
+				autoScroll = true;
+				await tick();
+
+				if (messages.length > 0) {
+					history.messages[messages.at(-1).id].done = true;
+				}
+				await tick();
+
+				return true;
+			} else {
+				return null;
+			}
+		}
+	};
+</script>
+
+<svelte:head>
+	<title>
+		{title
+			? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} | ${$WEBUI_NAME}`
+			: `${$WEBUI_NAME}`}
+	</title>
+</svelte:head>
+
+{#if loaded}
+	<div
+		class="min-h-screen max-h-screen w-full flex flex-col text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-900"
+	>
+		<div class="flex flex-col flex-auto justify-center py-8">
+			<div class="px-3 w-full max-w-5xl mx-auto">
+				<div>
+					<div class=" text-3xl font-semibold line-clamp-1">
+						{title}
+					</div>
+
+					<div class=" mt-1 text-gray-400">
+						{dayjs(chat.chat.timestamp).format($i18n.t('MMMM DD, YYYY'))}
+					</div>
+				</div>
+
+				<hr class=" dark:border-gray-800 mt-6 mb-2" />
+			</div>
+
+			<div class=" flex flex-col w-full flex-auto overflow-auto h-0" id="messages-container">
+				<div class=" h-full w-full flex flex-col py-4">
+					<div class="py-2">
+						<Messages
+							{user}
+							chatId={$chatId}
+							readOnly={true}
+							{selectedModels}
+							{processing}
+							bind:history
+							bind:messages
+							bind:autoScroll
+							bottomPadding={files.length > 0}
+							sendPrompt={() => {}}
+							continueResponse={() => {}}
+							regenerateResponse={() => {}}
+						/>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+{/if}
diff --git a/src/routes/watch/+page.svelte b/src/routes/watch/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..78eab5be3196feb63b1e44a51c8ade7710ffa510
--- /dev/null
+++ b/src/routes/watch/+page.svelte
@@ -0,0 +1,22 @@
+<script>
+	import { onMount } from 'svelte';
+	import { goto } from '$app/navigation';
+
+	onMount(() => {
+		// Get the current URL search parameters
+		const params = new URLSearchParams(window.location.search);
+
+		// Check if 'v' parameter exists
+		if (params.has('v')) {
+			// Get the value of 'v' parameter
+			const videoId = params.get('v');
+
+			// Redirect to root with 'youtube' parameter
+
+			goto(`/?youtube=${encodeURIComponent(videoId)}`);
+		} else {
+			// Redirect to root if 'v' parameter doesn't exist
+			goto('/');
+		}
+	});
+</script>
diff --git a/src/tailwind.css b/src/tailwind.css
new file mode 100644
index 0000000000000000000000000000000000000000..998ab8433d9a2253069aafbfecb7ba632c01253c
--- /dev/null
+++ b/src/tailwind.css
@@ -0,0 +1,16 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+	html,
+	pre {
+		font-family: -apple-system, BlinkMacSystemFont, 'Inter', ui-sans-serif, system-ui, 'Segoe UI',
+			Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif, 'Helvetica Neue', Arial,
+			'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+	}
+
+	pre {
+		white-space: pre-wrap;
+	}
+}
diff --git a/static/assets/fonts/Archivo-Variable.ttf b/static/assets/fonts/Archivo-Variable.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..99dc9e5bc7b39e8e989462bef7b624972818da11
--- /dev/null
+++ b/static/assets/fonts/Archivo-Variable.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ed648e6308957e7b2d45b755d8a0461c4d10cd99bc501ee859f91935cb3d8727
+size 652084
diff --git a/static/assets/fonts/Inter-Variable.ttf b/static/assets/fonts/Inter-Variable.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..13713530a909f056dd9fa8cfc3dd0d3acf107a92
--- /dev/null
+++ b/static/assets/fonts/Inter-Variable.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cf3cb43b0366e2dc6df60e1132b1c9a4c15777f0cd8e5a53e0c15124003e9ed4
+size 804612
diff --git a/static/assets/fonts/Mona-Sans.woff2 b/static/assets/fonts/Mona-Sans.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..d88d5ff27bea51dfc9954392acd7933d642415c9
Binary files /dev/null and b/static/assets/fonts/Mona-Sans.woff2 differ
diff --git a/static/audio/greeting.mp3 b/static/audio/greeting.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..8ded2a86a568459c861f280b20064b2c8943a0bf
Binary files /dev/null and b/static/audio/greeting.mp3 differ
diff --git a/static/doge.png b/static/doge.png
new file mode 100644
index 0000000000000000000000000000000000000000..66723c6c1f20b5fbee8fdf52ccbbf91280a763d3
Binary files /dev/null and b/static/doge.png differ
diff --git a/static/favicon.png b/static/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..2b2074780847581edf9cf2ed0d2e9ebd8ff08c56
Binary files /dev/null and b/static/favicon.png differ
diff --git a/static/manifest.json b/static/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93
--- /dev/null
+++ b/static/manifest.json
@@ -0,0 +1 @@
+{}
diff --git a/static/opensearch.xml b/static/opensearch.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ce47e39ae988ff08b4d47eab9f7af325929d4599
--- /dev/null
+++ b/static/opensearch.xml
@@ -0,0 +1,8 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Open WebUI</ShortName>
+<Description>Search Open WebUI</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16" type="image/x-icon">http://localhost:5137/favicon.png</Image>
+<Url type="text/html" method="get" template="http://localhost:5137/?q={searchTerms}"/>
+<moz:SearchForm>http://localhost:5137</moz:SearchForm>
+</OpenSearchDescription>
\ No newline at end of file
diff --git a/static/pyodide/pyodide-lock.json b/static/pyodide/pyodide-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..a651b04459c26ad27ac598e03e5acc802cba867a
--- /dev/null
+++ b/static/pyodide/pyodide-lock.json
@@ -0,0 +1 @@
+{"info": {"abi_version": "2024_0", "arch": "wasm32", "platform": "emscripten_3_1_58", "python": "3.12.1", "version": "0.26.1"}, "packages": {"aiohttp": {"depends": ["aiosignal", "async-timeout", "attrs", "charset-normalizer", "frozenlist", "multidict", "yarl"], "file_name": "aiohttp-3.9.5-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["aiohttp"], "install_dir": "site", "name": "aiohttp", "package_type": "package", "sha256": "c9a381c45ca9d6f16f6ec269c27a82bcbcaa3075f7f8e5de271ef5c256aa4ea4", "shared_library": false, "unvendored_tests": true, "version": "3.9.5"}, "aiohttp-tests": {"depends": ["aiohttp"], "file_name": "aiohttp-tests.tar", "imports": [], "install_dir": "site", "name": "aiohttp-tests", "package_type": "package", "sha256": "dc42d3bea4ede411be4cd43059f224832438b00f076fbe4d9d1ef516e2eab250", "shared_library": false, "unvendored_tests": false, "version": "3.9.5"}, "aiosignal": {"depends": ["frozenlist"], "file_name": "aiosignal-1.3.1-py3-none-any.whl", "imports": ["aiosignal"], "install_dir": "site", "name": "aiosignal", "package_type": "package", "sha256": "e091282e280a5940e759c5c849dfcd3169f8eea21d5838c872ec1265ef825f1e", "shared_library": false, "unvendored_tests": false, "version": "1.3.1"}, "altair": {"depends": ["typing-extensions", "jinja2", "jsonschema", "numpy", "pandas", "toolz", "packaging"], "file_name": "altair-5.3.0-py3-none-any.whl", "imports": ["altair"], "install_dir": "site", "name": "altair", "package_type": "package", "sha256": "1d2f248506ab81f13292d42464faa60c4940c1ef5da9013dc3a97ed398d6f7f7", "shared_library": false, "unvendored_tests": false, "version": "5.3.0"}, "annotated-types": {"depends": [], "file_name": "annotated_types-0.6.0-py3-none-any.whl", "imports": ["annotated_types"], "install_dir": "site", "name": "annotated-types", "package_type": "package", "sha256": "337d2a3e1b65926cd6560d0c9a33f8be55d6b8a21e846091bc7ab8fdb2f421a3", "shared_library": false, "unvendored_tests": true, "version": "0.6.0"}, "annotated-types-tests": {"depends": ["annotated-types"], "file_name": "annotated-types-tests.tar", "imports": [], "install_dir": "site", "name": "annotated-types-tests", "package_type": "package", "sha256": "0eecd674a295f84758689100739cca7d54f73cd6288c41f2269f78a01e465a8d", "shared_library": false, "unvendored_tests": false, "version": "0.6.0"}, "asciitree": {"depends": [], "file_name": "asciitree-0.3.3-py3-none-any.whl", "imports": ["asciitree"], "install_dir": "site", "name": "asciitree", "package_type": "package", "sha256": "15d47009b9cacdddacbc7ef306e64d103ea96d4fdc0fbb2d579e43c8fe8666bf", "shared_library": false, "unvendored_tests": false, "version": "0.3.3"}, "astropy": {"depends": ["packaging", "numpy", "pyerfa", "pyyaml", "astropy_iers_data"], "file_name": "astropy-6.0.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["astropy"], "install_dir": "site", "name": "astropy", "package_type": "package", "sha256": "94be600bfff4973c962112913bf86b5d1c4ba8b162b6ef409a03f70a0cefb9e9", "shared_library": false, "unvendored_tests": false, "version": "6.0.1"}, "astropy-iers-data": {"depends": [], "file_name": "astropy_iers_data-0.2024.4.22.0.29.50-py3-none-any.whl", "imports": ["astropy_iers_data"], "install_dir": "site", "name": "astropy_iers_data", "package_type": "package", "sha256": "b9989c71b05bc8a550d74d7e49d5e041f192f420bdf8b71553adb225e286f478", "shared_library": false, "unvendored_tests": true, "version": "0.2024.4.22.0.29.50"}, "astropy-iers-data-tests": {"depends": ["astropy_iers_data"], "file_name": "astropy_iers_data-tests.tar", "imports": [], "install_dir": "site", "name": "astropy_iers_data-tests", "package_type": "package", "sha256": "3cbaffbea097e4d5f206a7f357b59871a67594cd3f899ad45a28c293c271d4f1", "shared_library": false, "unvendored_tests": false, "version": "0.2024.4.22.0.29.50"}, "asttokens": {"depends": ["six"], "file_name": "asttokens-2.4.1-py2.py3-none-any.whl", "imports": ["asttokens"], "install_dir": "site", "name": "asttokens", "package_type": "package", "sha256": "4f62a79cfd557b35cd1f1e4809c61eef0b6e54e0dea5270653bbde3fc341d05a", "shared_library": false, "unvendored_tests": false, "version": "2.4.1"}, "async-timeout": {"depends": [], "file_name": "async_timeout-4.0.3-py3-none-any.whl", "imports": ["async_timeout"], "install_dir": "site", "name": "async-timeout", "package_type": "package", "sha256": "39d42f0d92a009c9205d74a01ff194d89ea9f1741af36998bc405c615993779e", "shared_library": false, "unvendored_tests": false, "version": "4.0.3"}, "atomicwrites": {"depends": [], "file_name": "atomicwrites-1.4.1-py2.py3-none-any.whl", "imports": ["atomicwrites"], "install_dir": "site", "name": "atomicwrites", "package_type": "package", "sha256": "7ac6f1fb3dde1c23246b08b6d2a380fb9e1b344e57126f00bd9364ad1104bc82", "shared_library": false, "unvendored_tests": false, "version": "1.4.1"}, "attrs": {"depends": ["six"], "file_name": "attrs-23.2.0-py3-none-any.whl", "imports": ["attr", "attrs"], "install_dir": "site", "name": "attrs", "package_type": "package", "sha256": "8e0e26b04b67bd3f09a60f289a0673aadb45daef63c8a6ceca762159d41bdea8", "shared_library": false, "unvendored_tests": false, "version": "23.2.0"}, "autograd": {"depends": ["numpy", "future"], "file_name": "autograd-1.6.2-py3-none-any.whl", "imports": ["autograd"], "install_dir": "site", "name": "autograd", "package_type": "package", "sha256": "4fd6746b144d95de5e8dceae6d2d4fa2a962810b93e420430df74506e915af67", "shared_library": false, "unvendored_tests": true, "version": "1.6.2"}, "autograd-tests": {"depends": ["autograd"], "file_name": "autograd-tests.tar", "imports": [], "install_dir": "site", "name": "autograd-tests", "package_type": "package", "sha256": "4869f8c5b9bbdedfb84ebe2f866934dd9204f62d65b8e2e1c87feb5f769afec3", "shared_library": false, "unvendored_tests": false, "version": "1.6.2"}, "awkward-cpp": {"depends": ["numpy"], "file_name": "awkward_cpp-33-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["awkward_cpp"], "install_dir": "site", "name": "awkward-cpp", "package_type": "package", "sha256": "6dba5ce80f904ade19758b4e0b1e3bc4681aa0bb5e414d6a243251bc3ff9fd10", "shared_library": false, "unvendored_tests": false, "version": "33"}, "b2d": {"depends": ["numpy", "pydantic", "setuptools", "annotated-types"], "file_name": "b2d-0.7.4-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["b2d"], "install_dir": "site", "name": "b2d", "package_type": "package", "sha256": "6a2c8d02641ff9a65b19adfbe0f0aeef1ad1a8a8915a91122d554c3335c82faa", "shared_library": false, "unvendored_tests": false, "version": "0.7.4"}, "bcrypt": {"depends": [], "file_name": "bcrypt-4.1.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["bcrypt"], "install_dir": "site", "name": "bcrypt", "package_type": "package", "sha256": "8981e7922af6f1f6e2ad31ef120c67bfee047916e0b764ed5cd2a1ed8eaadbc7", "shared_library": false, "unvendored_tests": false, "version": "4.1.2"}, "beautifulsoup4": {"depends": ["soupsieve"], "file_name": "beautifulsoup4-4.12.3-py3-none-any.whl", "imports": ["bs4"], "install_dir": "site", "name": "beautifulsoup4", "package_type": "package", "sha256": "94a8052bb54628e76229fe6a1d1d16aa360f265c15abbca97497dad5370f7a7b", "shared_library": false, "unvendored_tests": true, "version": "4.12.3"}, "beautifulsoup4-tests": {"depends": ["beautifulsoup4"], "file_name": "beautifulsoup4-tests.tar", "imports": [], "install_dir": "site", "name": "beautifulsoup4-tests", "package_type": "package", "sha256": "55ec265dec8d21577aad92c4f7f2e5b22f3edecdc68fe003e525946bb088c44d", "shared_library": false, "unvendored_tests": false, "version": "4.12.3"}, "biopython": {"depends": ["numpy"], "file_name": "biopython-1.83-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["Bio", "BioSQL"], "install_dir": "site", "name": "biopython", "package_type": "package", "sha256": "4102d8fa77feca014bb2d49bebbae50cb6b0583737aff4ad02a3efaa6accd55c", "shared_library": false, "unvendored_tests": false, "version": "1.83"}, "bitarray": {"depends": [], "file_name": "bitarray-2.9.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["bitarray"], "install_dir": "site", "name": "bitarray", "package_type": "package", "sha256": "c7cfd44b70d8e6d5e26f81dbd7c7ac96cb562f6dceed02d6af3fdd23e6f74888", "shared_library": false, "unvendored_tests": true, "version": "2.9.2"}, "bitarray-tests": {"depends": ["bitarray"], "file_name": "bitarray-tests.tar", "imports": [], "install_dir": "site", "name": "bitarray-tests", "package_type": "package", "sha256": "3378a1981df0a26a423dde6ab332a4965447e35af539ceabfe4e330bed05f933", "shared_library": false, "unvendored_tests": false, "version": "2.9.2"}, "bitstring": {"depends": ["bitarray"], "file_name": "bitstring-4.1.4-py3-none-any.whl", "imports": ["bitstring"], "install_dir": "site", "name": "bitstring", "package_type": "package", "sha256": "fdea8060e5d10fe019b702aba16c3b0a349abca3230640dd70d57e49e824d127", "shared_library": false, "unvendored_tests": false, "version": "4.1.4"}, "bleach": {"depends": ["webencodings", "packaging", "six"], "file_name": "bleach-6.1.0-py3-none-any.whl", "imports": ["bleach"], "install_dir": "site", "name": "bleach", "package_type": "package", "sha256": "5e12c4669d0caeb23584e9fd5115a7c81c7d8cd10915c64e6f3357e549e3179d", "shared_library": false, "unvendored_tests": false, "version": "6.1.0"}, "bokeh": {"depends": ["contourpy", "numpy", "jinja2", "pandas", "pillow", "python-dateutil", "six", "typing-extensions", "pyyaml", "xyzservices"], "file_name": "bokeh-3.4.1-py3-none-any.whl", "imports": ["bokeh"], "install_dir": "site", "name": "bokeh", "package_type": "package", "sha256": "5617ba021385e030d02822fee0f10d2a58e97951472ddd6eaae4fc8682ff276f", "shared_library": false, "unvendored_tests": false, "version": "3.4.1"}, "boost-histogram": {"depends": ["numpy"], "file_name": "boost_histogram-1.4.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["boost_histogram"], "install_dir": "site", "name": "boost-histogram", "package_type": "package", "sha256": "76cb6e74ed49b53043fa55a0896279c3f83a8eb9b749f7a710360d08f2e324a4", "shared_library": false, "unvendored_tests": false, "version": "1.4.1"}, "brotli": {"depends": [], "file_name": "Brotli-1.1.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["brotli"], "install_dir": "site", "name": "brotli", "package_type": "package", "sha256": "60f96da7ec930a6c71d92f9f4c811ac96748d80c7f83f0664f2b59c08052f173", "shared_library": false, "unvendored_tests": false, "version": "1.1.0"}, "cachetools": {"depends": [], "file_name": "cachetools-5.3.3-py3-none-any.whl", "imports": ["cachetools"], "install_dir": "site", "name": "cachetools", "package_type": "package", "sha256": "89aabb74b24badd4557a1e4b2d6e2ac000089aeb4083b1c62afed95a40c88d90", "shared_library": false, "unvendored_tests": false, "version": "5.3.3"}, "cartopy": {"depends": ["shapely", "pyshp", "pyproj", "geos", "matplotlib", "scipy"], "file_name": "Cartopy-0.23.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cartopy"], "install_dir": "site", "name": "Cartopy", "package_type": "package", "sha256": "d202ba6909b904b13d2013ab791651e24658dae3c7dfb930ae14b2522a6064b9", "shared_library": false, "unvendored_tests": true, "version": "0.23.0"}, "cartopy-tests": {"depends": ["cartopy"], "file_name": "Cartopy-tests.tar", "imports": [], "install_dir": "site", "name": "Cartopy-tests", "package_type": "package", "sha256": "e817f56f9745cbe6a20df6aa41c03967fd82508354e9f5b8ddda9886be3be903", "shared_library": false, "unvendored_tests": false, "version": "0.23.0"}, "cbor-diag": {"depends": [], "file_name": "cbor_diag-1.0.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cbor_diag"], "install_dir": "site", "name": "cbor-diag", "package_type": "package", "sha256": "e4f0f8e870c80e76a50edc2a12883a90b87c8c8a6296444cd32c58157aebef2a", "shared_library": false, "unvendored_tests": false, "version": "1.0.1"}, "certifi": {"depends": [], "file_name": "certifi-2024.2.2-py3-none-any.whl", "imports": ["certifi"], "install_dir": "site", "name": "certifi", "package_type": "package", "sha256": "60307c886a375d40cf3ba444151c347b4271e9dcd7f432517896dcd692dafc62", "shared_library": false, "unvendored_tests": false, "version": "2024.2.2"}, "cffi": {"depends": ["pycparser"], "file_name": "cffi-1.16.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cffi"], "install_dir": "site", "name": "cffi", "package_type": "package", "sha256": "4a993a87e8a955d817656931640ee3a5e2abb887e73397def631613b1ee91273", "shared_library": false, "unvendored_tests": false, "version": "1.16.0"}, "cffi-example": {"depends": ["cffi"], "file_name": "cffi_example-0.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cffi_example"], "install_dir": "site", "name": "cffi_example", "package_type": "package", "sha256": "0419901f15584f393072aabafdaf30b9469fdd515771b15fa0348ad4145303de", "shared_library": false, "unvendored_tests": false, "version": "0.1"}, "cftime": {"depends": ["numpy"], "file_name": "cftime-1.6.3-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cftime"], "install_dir": "site", "name": "cftime", "package_type": "package", "sha256": "af2ffd030f317b437c9d8b51348d5203c06c6d62363b29780d83c0f0b37f7912", "shared_library": false, "unvendored_tests": false, "version": "1.6.3"}, "charset-normalizer": {"depends": [], "file_name": "charset_normalizer-3.3.2-py3-none-any.whl", "imports": ["charset_normalizer"], "install_dir": "site", "name": "charset-normalizer", "package_type": "package", "sha256": "b7159d1583fd0938540590346a637e8f4661d9a31461c1a7bc57893a71089acb", "shared_library": false, "unvendored_tests": false, "version": "3.3.2"}, "clarabel": {"depends": ["numpy", "scipy"], "file_name": "clarabel-0.7.1-cp37-abi3-pyodide_2024_0_wasm32.whl", "imports": ["clarabel"], "install_dir": "site", "name": "clarabel", "package_type": "package", "sha256": "2065e3eae13ee52440cadd32e373bd748536a572698f2e722315948f1bf1800c", "shared_library": false, "unvendored_tests": false, "version": "0.7.1"}, "click": {"depends": [], "file_name": "click-8.1.7-py3-none-any.whl", "imports": ["click"], "install_dir": "site", "name": "click", "package_type": "package", "sha256": "a91efee37121d94b0a1584676a08fba266acf76ca821274cbd7bf6abbb7984df", "shared_library": false, "unvendored_tests": false, "version": "8.1.7"}, "cligj": {"depends": ["click"], "file_name": "cligj-0.7.2-py3-none-any.whl", "imports": ["cligj"], "install_dir": "site", "name": "cligj", "package_type": "package", "sha256": "b938b3aa1e035663db0077f98b889af0f6e3a1fcba727392ef0fdb999c061a76", "shared_library": false, "unvendored_tests": false, "version": "0.7.2"}, "cloudpickle": {"depends": [], "file_name": "cloudpickle-3.0.0-py3-none-any.whl", "imports": ["cloudpickle"], "install_dir": "site", "name": "cloudpickle", "package_type": "package", "sha256": "c658a09163430185fcc8b091cccec3b7809a66a89ce366ddcea721bae39082f6", "shared_library": false, "unvendored_tests": false, "version": "3.0.0"}, "cmyt": {"depends": ["colorspacious", "matplotlib", "more-itertools", "numpy"], "file_name": "cmyt-2.0.0-py3-none-any.whl", "imports": ["cmyt"], "install_dir": "site", "name": "cmyt", "package_type": "package", "sha256": "5e99f7f3922eebd62e1570a254cd6aba140efadc5f5dab743ad16859f84071d3", "shared_library": false, "unvendored_tests": true, "version": "2.0.0"}, "cmyt-tests": {"depends": ["cmyt"], "file_name": "cmyt-tests.tar", "imports": [], "install_dir": "site", "name": "cmyt-tests", "package_type": "package", "sha256": "8cabd34a691476a4c425459e8ca0cf39c34d552a34e23ced302edc3c3257a671", "shared_library": false, "unvendored_tests": false, "version": "2.0.0"}, "colorspacious": {"depends": ["numpy"], "file_name": "colorspacious-1.1.2-py2.py3-none-any.whl", "imports": ["colorspacious"], "install_dir": "site", "name": "colorspacious", "package_type": "package", "sha256": "acee2628cc1eafc4ef7456563bb3dbe0f1578e95dd96a02d8127b09a552dbd33", "shared_library": false, "unvendored_tests": false, "version": "1.1.2"}, "contourpy": {"depends": ["numpy"], "file_name": "contourpy-1.2.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["contourpy"], "install_dir": "site", "name": "contourpy", "package_type": "package", "sha256": "8cc70108fea11bc60feb5d38fa4546155f00407c8ffcd082600c62bf0278b0a5", "shared_library": false, "unvendored_tests": false, "version": "1.2.1"}, "coolprop": {"depends": ["numpy", "matplotlib"], "file_name": "CoolProp-6.6.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["CoolProp"], "install_dir": "site", "name": "coolprop", "package_type": "package", "sha256": "aa0083a1067adc59aa8741e10ea67b677f2693e0fe95574ca4589169cef2d03b", "shared_library": false, "unvendored_tests": true, "version": "6.6.0"}, "coolprop-tests": {"depends": ["coolprop"], "file_name": "coolprop-tests.tar", "imports": [], "install_dir": "site", "name": "coolprop-tests", "package_type": "package", "sha256": "82b2574e6b4ee184156367481db9b9479f7f3d6cf5880940b0e726f866f3677c", "shared_library": false, "unvendored_tests": false, "version": "6.6.0"}, "coverage": {"depends": ["sqlite3"], "file_name": "coverage-7.4.4-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["coverage"], "install_dir": "site", "name": "coverage", "package_type": "package", "sha256": "606a1a385c30766260b5b7d2f7ded768a5f91e29b0c090c5bbac7b2bb2b2c2e7", "shared_library": false, "unvendored_tests": false, "version": "7.4.4"}, "cpp-exceptions-test": {"depends": [], "file_name": "cpp-exceptions-test-0.1.zip", "imports": [], "install_dir": "dynlib", "name": "cpp-exceptions-test", "package_type": "shared_library", "sha256": "23ee6f17609635436bc8cced518a3adec8bdf095f0dda8062166e39077cf8005", "shared_library": true, "unvendored_tests": false, "version": "0.1"}, "cpp-exceptions-test2": {"depends": [], "file_name": "cpp_exceptions_test2-1.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cpp-exceptions-test2"], "install_dir": "site", "name": "cpp-exceptions-test2", "package_type": "package", "sha256": "5d5c8a58219d050452190b4fbe3a0577c9da2ac557822a977d34c08b1ff4cac9", "shared_library": false, "unvendored_tests": false, "version": "1.0"}, "cramjam": {"depends": [], "file_name": "cramjam-2.8.3-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cramjam"], "install_dir": "site", "name": "cramjam", "package_type": "package", "sha256": "42c509a8c042b90f8d39b2b428d7512e40110b6bc8bff417ff5f345fba3860bd", "shared_library": false, "unvendored_tests": false, "version": "2.8.3"}, "crc32c": {"depends": [], "file_name": "crc32c-2.4-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["crc32c"], "install_dir": "site", "name": "crc32c", "package_type": "package", "sha256": "c0703c9b40d31758e7d836a2f0657c343a8a60d2e09bb2826e0d01febe41b6a4", "shared_library": false, "unvendored_tests": false, "version": "2.4"}, "cryptography": {"depends": ["openssl", "six", "cffi"], "file_name": "cryptography-42.0.5-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cryptography"], "install_dir": "site", "name": "cryptography", "package_type": "package", "sha256": "c05ea45f7aa36950b2f7c4216147e51abd6e6899656c512e106c7043bc7772d9", "shared_library": false, "unvendored_tests": false, "version": "42.0.5"}, "cssselect": {"depends": [], "file_name": "cssselect-1.2.0-py2.py3-none-any.whl", "imports": ["cssselect"], "install_dir": "site", "name": "cssselect", "package_type": "package", "sha256": "a835c843cac791f67be9d6ad2eabe0d2690cab0c147902ffe604018271bc557c", "shared_library": false, "unvendored_tests": false, "version": "1.2.0"}, "cvxpy-base": {"depends": ["numpy", "scipy", "clarabel"], "file_name": "cvxpy_base-1.5.1-py3-none-any.whl", "imports": ["cvxpy"], "install_dir": "site", "name": "cvxpy-base", "package_type": "package", "sha256": "43dcbeee1e7875516cdf6d3a227cc9d6720b91dc0765c2f4a2dfe1a297607324", "shared_library": false, "unvendored_tests": true, "version": "1.5.1"}, "cvxpy-base-tests": {"depends": ["cvxpy-base"], "file_name": "cvxpy-base-tests.tar", "imports": [], "install_dir": "site", "name": "cvxpy-base-tests", "package_type": "package", "sha256": "a18ac6cec0d4a36e5297d9920717bfdab1db27b56ae9aae245c04051b8bf6990", "shared_library": false, "unvendored_tests": false, "version": "1.5.1"}, "cycler": {"depends": ["six"], "file_name": "cycler-0.12.1-py3-none-any.whl", "imports": ["cycler"], "install_dir": "site", "name": "cycler", "package_type": "package", "sha256": "62243ceb501f58ad420395814f88d259d7546d115bd652e67d281b476b17c96e", "shared_library": false, "unvendored_tests": false, "version": "0.12.1"}, "cysignals": {"depends": [], "file_name": "cysignals-1.11.4-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cysignals"], "install_dir": "site", "name": "cysignals", "package_type": "package", "sha256": "ff6b27611229d4fffd3ff5f9a8d88854e5181441a2e9c2e337e6bc79ee14de5e", "shared_library": false, "unvendored_tests": false, "version": "1.11.4"}, "cytoolz": {"depends": ["toolz"], "file_name": "cytoolz-0.12.3-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cytoolz"], "install_dir": "site", "name": "cytoolz", "package_type": "package", "sha256": "3db082ceb3c2e49842ad8065d5d9a48cadfa58752d0ee00cf0d9b86cb4b3cf8c", "shared_library": false, "unvendored_tests": true, "version": "0.12.3"}, "cytoolz-tests": {"depends": ["cytoolz"], "file_name": "cytoolz-tests.tar", "imports": [], "install_dir": "site", "name": "cytoolz-tests", "package_type": "package", "sha256": "9b8bae8b847080f2530f764d63b4a87df456f96a63eadb2eefc8884a47beee2b", "shared_library": false, "unvendored_tests": false, "version": "0.12.3"}, "decorator": {"depends": [], "file_name": "decorator-5.1.1-py3-none-any.whl", "imports": ["decorator"], "install_dir": "site", "name": "decorator", "package_type": "package", "sha256": "e7b2576b7ab0f7fab4a8260ce091e610dedeb2f7874c328db3920c5b1d06a57e", "shared_library": false, "unvendored_tests": false, "version": "5.1.1"}, "demes": {"depends": ["attrs", "ruamel.yaml"], "file_name": "demes-0.2.3-py3-none-any.whl", "imports": ["demes"], "install_dir": "site", "name": "demes", "package_type": "package", "sha256": "8be66f0c28a713b8efc6626b4aeb7cdeea70818b2ff3877ebb731a0e6a638fdd", "shared_library": false, "unvendored_tests": false, "version": "0.2.3"}, "deprecation": {"depends": ["packaging"], "file_name": "deprecation-2.1.0-py2.py3-none-any.whl", "imports": ["deprecation"], "install_dir": "site", "name": "deprecation", "package_type": "package", "sha256": "7e29e9de88b1bb7319e512cf74e474e47fe216978df72fe696504c464f564088", "shared_library": false, "unvendored_tests": false, "version": "2.1.0"}, "distlib": {"depends": [], "file_name": "distlib-0.3.8-py2.py3-none-any.whl", "imports": ["distlib"], "install_dir": "site", "name": "distlib", "package_type": "package", "sha256": "c38f21e1668748621aa187e1d77017a072cad344df5b17e855c87d7b29cbeb56", "shared_library": false, "unvendored_tests": false, "version": "0.3.8"}, "docutils": {"depends": [], "file_name": "docutils-0.21.1-py3-none-any.whl", "imports": ["docutils"], "install_dir": "site", "name": "docutils", "package_type": "package", "sha256": "820ce56834fd002d80f0512070f7d71565f076de184287a491cbad6273e5a76c", "shared_library": false, "unvendored_tests": false, "version": "0.21.1"}, "ewah-bool-utils": {"depends": [], "file_name": "ewah_bool_utils-1.2.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["ewah_bool_utils"], "install_dir": "site", "name": "ewah_bool_utils", "package_type": "package", "sha256": "2d64e4cc33e22bc6e54a3cea53f6b7b156f9b5d24c81a012eaab449edb64b2bd", "shared_library": false, "unvendored_tests": true, "version": "1.2.0"}, "ewah-bool-utils-tests": {"depends": ["ewah_bool_utils"], "file_name": "ewah_bool_utils-tests.tar", "imports": [], "install_dir": "site", "name": "ewah_bool_utils-tests", "package_type": "package", "sha256": "1bb3b4036603e5e7a1209236f78105e7036af3b0b615dd38b2bc2c92b60232e8", "shared_library": false, "unvendored_tests": false, "version": "1.2.0"}, "exceptiongroup": {"depends": [], "file_name": "exceptiongroup-1.2.1-py3-none-any.whl", "imports": ["exceptiongroup"], "install_dir": "site", "name": "exceptiongroup", "package_type": "package", "sha256": "92b9f147b8f6e461cf0caf268db6a6e2ee7efb228d60d80f7dd92d521d59e469", "shared_library": false, "unvendored_tests": false, "version": "1.2.1"}, "executing": {"depends": [], "file_name": "executing-2.0.1-py2.py3-none-any.whl", "imports": ["executing"], "install_dir": "site", "name": "executing", "package_type": "package", "sha256": "9d446583b446b5795bc02ae5e92eebd5ffba061287b3940f45462f2612f47b6a", "shared_library": false, "unvendored_tests": false, "version": "2.0.1"}, "fastparquet": {"depends": ["cramjam", "numpy", "pandas", "fsspec", "packaging"], "file_name": "fastparquet-2024.2.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["fastparquet"], "install_dir": "site", "name": "fastparquet", "package_type": "package", "sha256": "ea0392fe11e9fbdea79ad8199d13a38798eb9981413e08539e6bbd33d17b85be", "shared_library": false, "unvendored_tests": false, "version": "2024.2.0"}, "fiona": {"depends": ["attrs", "certifi", "setuptools", "six", "click", "cligj"], "file_name": "fiona-1.9.5-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["fiona"], "install_dir": "site", "name": "fiona", "package_type": "package", "sha256": "0f55d6c5d5b09d420e2b58d010967b5e2e43c1ffdf3aa1d559951ac10d893655", "shared_library": false, "unvendored_tests": true, "version": "1.9.5"}, "fiona-tests": {"depends": ["fiona"], "file_name": "fiona-tests.tar", "imports": [], "install_dir": "site", "name": "fiona-tests", "package_type": "package", "sha256": "6b82496111cea9551fa09174c70c47c12ceb6e5885c8511683e3bbf7b7e29ff5", "shared_library": false, "unvendored_tests": false, "version": "1.9.5"}, "fonttools": {"depends": [], "file_name": "fonttools-4.51.0-py3-none-any.whl", "imports": ["fontTools"], "install_dir": "site", "name": "fonttools", "package_type": "package", "sha256": "e27bd9df5b39f6210c66571b12aa2372310e9e96aee0b605fc5021716b5f3249", "shared_library": false, "unvendored_tests": false, "version": "4.51.0"}, "fpcast-test": {"depends": [], "file_name": "fpcast_test-0.1.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["fpcast_test"], "install_dir": "site", "name": "fpcast-test", "package_type": "package", "sha256": "0f72430fbccb73c6690a931413032f9c1892bd07b7af2ba59f32374ec4970756", "shared_library": false, "unvendored_tests": false, "version": "0.1.1"}, "freesasa": {"depends": [], "file_name": "freesasa-2.2.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["freesasa"], "install_dir": "site", "name": "freesasa", "package_type": "package", "sha256": "d3d478c7aa17ca7b6caf6609cc3152929f4cb43d20fdda7217e648aad96acf0e", "shared_library": false, "unvendored_tests": false, "version": "2.2.1"}, "frozenlist": {"depends": [], "file_name": "frozenlist-1.4.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["frozenlist"], "install_dir": "site", "name": "frozenlist", "package_type": "package", "sha256": "481ab66c7f7b932d8fadfadece1bd9928c0ee471da75cff2baf22845baea2a2c", "shared_library": false, "unvendored_tests": false, "version": "1.4.1"}, "fsspec": {"depends": [], "file_name": "fsspec-2024.3.1-py3-none-any.whl", "imports": ["fsspec"], "install_dir": "site", "name": "fsspec", "package_type": "package", "sha256": "2f1439e76d695b8414d89c7c85cdb0ce0f227be1ef083f07e8c9452e0718d9c8", "shared_library": false, "unvendored_tests": true, "version": "2024.3.1"}, "fsspec-tests": {"depends": ["fsspec"], "file_name": "fsspec-tests.tar", "imports": [], "install_dir": "site", "name": "fsspec-tests", "package_type": "package", "sha256": "0f777d658d32926b150c87c88b5e839545d6ad88320818027908ac83cf7bd8de", "shared_library": false, "unvendored_tests": false, "version": "2024.3.1"}, "future": {"depends": [], "file_name": "future-1.0.0-py3-none-any.whl", "imports": ["future"], "install_dir": "site", "name": "future", "package_type": "package", "sha256": "445e96936c4f8ae76ddb2fa505308eeda42af28db4584037634318c3ebaf9d03", "shared_library": false, "unvendored_tests": true, "version": "1.0.0"}, "future-tests": {"depends": ["future"], "file_name": "future-tests.tar", "imports": [], "install_dir": "site", "name": "future-tests", "package_type": "package", "sha256": "eb8fedb6b9848fa3ab6fa1362f81b7d1c5e8c89773783d923584b95a3b97547f", "shared_library": false, "unvendored_tests": false, "version": "1.0.0"}, "galpy": {"depends": ["numpy", "scipy", "matplotlib", "astropy", "future", "setuptools"], "file_name": "galpy-1.9.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["galpy"], "install_dir": "site", "name": "galpy", "package_type": "package", "sha256": "8f5af7d9d1c7a558fe0ac137ad88f59185bdf30dec8649f291403eceff248b98", "shared_library": false, "unvendored_tests": false, "version": "1.9.2"}, "gdal": {"depends": ["geos"], "file_name": "gdal-3.8.3.zip", "imports": [], "install_dir": "dynlib", "name": "gdal", "package_type": "shared_library", "sha256": "30401f5d6894f73556a95de723c278de64c617dc35a10894b77154c94292b572", "shared_library": true, "unvendored_tests": false, "version": "3.8.3"}, "gensim": {"depends": ["numpy", "scipy", "six", "smart_open", "wrapt"], "file_name": "gensim-4.3.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["gensim"], "install_dir": "site", "name": "gensim", "package_type": "package", "sha256": "3c4b346db7bc0fd0174881be499333eef4aade2aca690beeef4a1a5fd05b6815", "shared_library": false, "unvendored_tests": true, "version": "4.3.2"}, "gensim-tests": {"depends": ["gensim"], "file_name": "gensim-tests.tar", "imports": [], "install_dir": "site", "name": "gensim-tests", "package_type": "package", "sha256": "5ceb546ac41bb973d08be7c44a5732ce29f5720b5f35102149e4f6961223e19d", "shared_library": false, "unvendored_tests": false, "version": "4.3.2"}, "geopandas": {"depends": ["shapely", "fiona", "pyproj", "packaging", "pandas"], "file_name": "geopandas-0.14.3-py3-none-any.whl", "imports": ["geopandas"], "install_dir": "site", "name": "geopandas", "package_type": "package", "sha256": "ad8eade2c6f45ba17c878a302910ebc3d6f7cecd009cdb5d63d140781afb4a21", "shared_library": false, "unvendored_tests": true, "version": "0.14.3"}, "geopandas-tests": {"depends": ["geopandas"], "file_name": "geopandas-tests.tar", "imports": [], "install_dir": "site", "name": "geopandas-tests", "package_type": "package", "sha256": "40703d011365f2763e7dd7514be85e65af8f488671ab4f1aaaacc7ad1a14e23e", "shared_library": false, "unvendored_tests": false, "version": "0.14.3"}, "geos": {"depends": [], "file_name": "geos-3.12.1.zip", "imports": [], "install_dir": "dynlib", "name": "geos", "package_type": "shared_library", "sha256": "150ef1387663f8ddebb9e2b2d49c556f60ccfe3d6a8c3b2334f22bd2e3ad9e52", "shared_library": true, "unvendored_tests": false, "version": "3.12.1"}, "gmpy2": {"depends": [], "file_name": "gmpy2-2.1.5-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["gmpy2"], "install_dir": "site", "name": "gmpy2", "package_type": "package", "sha256": "60b06b4d6ad774cd76ce94dea287cab3d27fe846477ce5f1e5db58e9ebe7596b", "shared_library": false, "unvendored_tests": false, "version": "2.1.5"}, "gsw": {"depends": ["numpy"], "file_name": "gsw-3.6.17-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["gsw"], "install_dir": "site", "name": "gsw", "package_type": "package", "sha256": "abba76d5a7da89cd64646e7856599f31621ae9f48951cad5eac9ed75ab3ee031", "shared_library": false, "unvendored_tests": true, "version": "3.6.17"}, "gsw-tests": {"depends": ["gsw"], "file_name": "gsw-tests.tar", "imports": [], "install_dir": "site", "name": "gsw-tests", "package_type": "package", "sha256": "c1dcfb0baf3f2891f68ed569ed7f08264bca69cc210e1681d9628b64ce42b02d", "shared_library": false, "unvendored_tests": false, "version": "3.6.17"}, "h5py": {"depends": ["numpy", "pkgconfig"], "file_name": "h5py-3.11.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["h5py"], "install_dir": "site", "name": "h5py", "package_type": "package", "sha256": "5d7604354ff24428521620d75352946656375e7a725e7040c976f7be0a908055", "shared_library": false, "unvendored_tests": true, "version": "3.11.0"}, "h5py-tests": {"depends": ["h5py"], "file_name": "h5py-tests.tar", "imports": [], "install_dir": "site", "name": "h5py-tests", "package_type": "package", "sha256": "6f4d260fb05e96386f5091941bb520c804447436983e24901d4e7ba831ed15e7", "shared_library": false, "unvendored_tests": false, "version": "3.11.0"}, "hashlib": {"depends": ["openssl"], "file_name": "hashlib-1.0.0.zip", "imports": ["_hashlib"], "install_dir": "stdlib", "name": "hashlib", "package_type": "cpython_module", "sha256": "2608deda99041715fec1152b2d2d377f591035fcab44a76e270a898b9f70f580", "shared_library": true, "unvendored_tests": false, "version": "1.0.0"}, "html5lib": {"depends": ["webencodings", "six"], "file_name": "html5lib-1.1-py2.py3-none-any.whl", "imports": ["html5lib"], "install_dir": "site", "name": "html5lib", "package_type": "package", "sha256": "58e4002edf04a42f3ec887c5b68c432b21427603b4d1090e379259f6678a3d58", "shared_library": false, "unvendored_tests": false, "version": "1.1"}, "idna": {"depends": [], "file_name": "idna-3.7-py3-none-any.whl", "imports": ["idna"], "install_dir": "site", "name": "idna", "package_type": "package", "sha256": "9fda8aaea4574988fcd20f729a3b6df5b22488245ef347ec63bfe794325d4def", "shared_library": false, "unvendored_tests": false, "version": "3.7"}, "igraph": {"depends": ["texttable"], "file_name": "igraph-0.11.4-cp39-abi3-pyodide_2024_0_wasm32.whl", "imports": ["igraph"], "install_dir": "site", "name": "igraph", "package_type": "package", "sha256": "d6030d268459626e1db94e26ec6f8d372e808e9a9cdf4d684cbe6bb2e6afa866", "shared_library": false, "unvendored_tests": false, "version": "0.11.4"}, "imageio": {"depends": ["numpy", "pillow"], "file_name": "imageio-2.34.1-py3-none-any.whl", "imports": ["imageio"], "install_dir": "site", "name": "imageio", "package_type": "package", "sha256": "052dc45002387ef8b9fd79693c99e67591bcaaf1ca9a82cb67f0de348325122b", "shared_library": false, "unvendored_tests": false, "version": "2.34.1"}, "iniconfig": {"depends": [], "file_name": "iniconfig-2.0.0-py3-none-any.whl", "imports": ["iniconfig"], "install_dir": "site", "name": "iniconfig", "package_type": "package", "sha256": "f9cab05fb8a8d99890626831d140b0f9c26c44ed38a520b89c42ff07afef929d", "shared_library": false, "unvendored_tests": false, "version": "2.0.0"}, "ipython": {"depends": ["asttokens", "decorator", "executing", "matplotlib-inline", "prompt_toolkit", "pure_eval", "pygments", "six", "stack_data", "traitlets", "sqlite3", "wcwidth"], "file_name": "ipython-8.23.0-py3-none-any.whl", "imports": ["IPython"], "install_dir": "site", "name": "ipython", "package_type": "package", "sha256": "cff64d70b073fb2dcf718ee4abd47767373171b864e3d30f890f1573f0654c22", "shared_library": false, "unvendored_tests": true, "version": "8.23.0"}, "ipython-tests": {"depends": ["ipython"], "file_name": "ipython-tests.tar", "imports": [], "install_dir": "site", "name": "ipython-tests", "package_type": "package", "sha256": "b5a49455fa364805cfd9b765a1c68df0dc12cedd79e25ba4096cbe94f04bf10d", "shared_library": false, "unvendored_tests": false, "version": "8.23.0"}, "jedi": {"depends": ["parso"], "file_name": "jedi-0.19.1-py2.py3-none-any.whl", "imports": ["jedi"], "install_dir": "site", "name": "jedi", "package_type": "package", "sha256": "5ee59883b4346dcc0c88adc8973f1dc635b38779f17595a7abec794cc09e8840", "shared_library": false, "unvendored_tests": true, "version": "0.19.1"}, "jedi-tests": {"depends": ["jedi"], "file_name": "jedi-tests.tar", "imports": [], "install_dir": "site", "name": "jedi-tests", "package_type": "package", "sha256": "ca8d52714ffade10e237eed44cb8d359ade5ae6b92d8c7ba0ff66d00161f9dff", "shared_library": false, "unvendored_tests": false, "version": "0.19.1"}, "jinja2": {"depends": ["markupsafe"], "file_name": "Jinja2-3.1.3-py3-none-any.whl", "imports": ["jinja2"], "install_dir": "site", "name": "Jinja2", "package_type": "package", "sha256": "ed3b38b217a040a4dd6dd5f62fca8d0a18ab68f3d5c5a8fef12dcdfeaa9a4373", "shared_library": false, "unvendored_tests": false, "version": "3.1.3"}, "joblib": {"depends": [], "file_name": "joblib-1.4.0-py3-none-any.whl", "imports": ["joblib"], "install_dir": "site", "name": "joblib", "package_type": "package", "sha256": "e913d6bb8a168be5a89e76764eacbb56f8e93f99c8e19e93197da2a4f0331e3f", "shared_library": false, "unvendored_tests": true, "version": "1.4.0"}, "joblib-tests": {"depends": ["joblib"], "file_name": "joblib-tests.tar", "imports": [], "install_dir": "site", "name": "joblib-tests", "package_type": "package", "sha256": "a0e85b342cc9fdb888eef3328a7e99a0740f6a774953276f0a082793a3e10981", "shared_library": false, "unvendored_tests": false, "version": "1.4.0"}, "jsonschema": {"depends": ["attrs", "pyrsistent", "referencing", "jsonschema_specifications"], "file_name": "jsonschema-4.21.1-py3-none-any.whl", "imports": ["jsonschema"], "install_dir": "site", "name": "jsonschema", "package_type": "package", "sha256": "8e103c1dd50ecd40e385b2167ebb00182ed3aa3b455cd88d1edab5243c521503", "shared_library": false, "unvendored_tests": true, "version": "4.21.1"}, "jsonschema-specifications": {"depends": [], "file_name": "jsonschema_specifications-2023.12.1-py3-none-any.whl", "imports": ["jsonschema_specifications"], "install_dir": "site", "name": "jsonschema_specifications", "package_type": "package", "sha256": "40589d9387803ddaf07c51e0a935f6fd0af653826182dfc2eda0e6171320f83b", "shared_library": false, "unvendored_tests": true, "version": "2023.12.1"}, "jsonschema-specifications-tests": {"depends": ["jsonschema_specifications"], "file_name": "jsonschema_specifications-tests.tar", "imports": [], "install_dir": "site", "name": "jsonschema_specifications-tests", "package_type": "package", "sha256": "1aa663daca70e3b7560afb0097d1bcfd7cbea9f5fcd6cb290a020e66e1726f88", "shared_library": false, "unvendored_tests": false, "version": "2023.12.1"}, "jsonschema-tests": {"depends": ["jsonschema"], "file_name": "jsonschema-tests.tar", "imports": [], "install_dir": "site", "name": "jsonschema-tests", "package_type": "package", "sha256": "4f2b82f097f9379d0029c442fa67ed87fc37e3d53045dff293bb9f8491f96857", "shared_library": false, "unvendored_tests": false, "version": "4.21.1"}, "kiwisolver": {"depends": [], "file_name": "kiwisolver-1.4.5-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["kiwisolver"], "install_dir": "site", "name": "kiwisolver", "package_type": "package", "sha256": "93324048b98508b7c87c8827f7fb810803f4c19e1216d3f44ea39ebefce5d977", "shared_library": false, "unvendored_tests": false, "version": "1.4.5"}, "lakers-python": {"depends": [], "file_name": "lakers_python-0.3.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["lakers"], "install_dir": "site", "name": "lakers-python", "package_type": "package", "sha256": "a3ff1dd2c6b808ef58c25b889309a1183780faae47bc30a3d0c3fcf565225c46", "shared_library": false, "unvendored_tests": false, "version": "0.3.0"}, "lazy-loader": {"depends": [], "file_name": "lazy_loader-0.4-py3-none-any.whl", "imports": ["lazy_loader"], "install_dir": "site", "name": "lazy_loader", "package_type": "package", "sha256": "791a725032a6950412e9af99ea11e1c33767616f62fff82038dc5a1e011a2a15", "shared_library": false, "unvendored_tests": true, "version": "0.4"}, "lazy-loader-tests": {"depends": ["lazy_loader"], "file_name": "lazy_loader-tests.tar", "imports": [], "install_dir": "site", "name": "lazy_loader-tests", "package_type": "package", "sha256": "bb75854213b518faf51a33cf774b28c62db019a863e0c8b82336e15118402f07", "shared_library": false, "unvendored_tests": false, "version": "0.4"}, "lazy-object-proxy": {"depends": [], "file_name": "lazy_object_proxy-1.10.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["lazy_object_proxy"], "install_dir": "site", "name": "lazy-object-proxy", "package_type": "package", "sha256": "630987c74e6a8715032001b486625f6b5e319299e447b7ca9730bff8a476b49a", "shared_library": false, "unvendored_tests": false, "version": "1.10.0"}, "libcst": {"depends": ["pyyaml"], "file_name": "libcst-1.3.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["libcst"], "install_dir": "site", "name": "libcst", "package_type": "package", "sha256": "f503a45b3855bb1a008caf8f6ac1a71443bf66369cd6168dccc9f5331400a970", "shared_library": false, "unvendored_tests": true, "version": "1.3.1"}, "libcst-tests": {"depends": ["libcst"], "file_name": "libcst-tests.tar", "imports": [], "install_dir": "site", "name": "libcst-tests", "package_type": "package", "sha256": "61555281e0724435cf9ccf1722dfeb75776795c5d07dc143e06f0d8086e42700", "shared_library": false, "unvendored_tests": false, "version": "1.3.1"}, "libhdf5": {"depends": [], "file_name": "libhdf5-1.12.1.zip", "imports": [], "install_dir": "dynlib", "name": "libhdf5", "package_type": "shared_library", "sha256": "7cd1ee833910ded4bca697b2881f49e26d185bf84d5921baecdf84c6f3e78b8d", "shared_library": true, "unvendored_tests": false, "version": "1.12.1"}, "libheif": {"depends": [], "file_name": "libheif-1.12.0.zip", "imports": [], "install_dir": "dynlib", "name": "libheif", "package_type": "shared_library", "sha256": "ed954f71cd78d1fcf4f9aa93e34f598bbc9a87cc6a288c12f43b5dad800a85b2", "shared_library": true, "unvendored_tests": false, "version": "1.12.0"}, "libmagic": {"depends": [], "file_name": "libmagic-5.42.zip", "imports": [], "install_dir": "dynlib", "name": "libmagic", "package_type": "shared_library", "sha256": "b8c3aee549fcdb7484f24b5dd804192efc75f1de315523f44b626662cc807b67", "shared_library": true, "unvendored_tests": false, "version": "5.42"}, "libnetcdf": {"depends": [], "file_name": "libnetcdf-4.9.2.zip", "imports": [], "install_dir": "dynlib", "name": "libnetcdf", "package_type": "shared_library", "sha256": "25e03271a4ccc228f092d03d6e7001b622d87069f82b255781aaea09f793adaa", "shared_library": true, "unvendored_tests": false, "version": "4.9.2"}, "lightgbm": {"depends": ["numpy", "scipy", "scikit-learn"], "file_name": "lightgbm-4.3.0-py3-none-pyodide_2024_0_wasm32.whl", "imports": ["lightgbm"], "install_dir": "site", "name": "lightgbm", "package_type": "package", "sha256": "3bf6ce78f0341aa9f2ba194f61222bc1e91b1a60e2e2c46bc7bbd6fb5e8534b0", "shared_library": false, "unvendored_tests": false, "version": "4.3.0"}, "logbook": {"depends": ["ssl"], "file_name": "Logbook-1.7.0.post0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["logbook"], "install_dir": "site", "name": "logbook", "package_type": "package", "sha256": "ebdfee44251e45c6a3b7804f8742a477d374c732ec7851552e4f25eb267c1aef", "shared_library": false, "unvendored_tests": false, "version": "1.7.0.post0"}, "lxml": {"depends": [], "file_name": "lxml-5.2.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["lxml"], "install_dir": "site", "name": "lxml", "package_type": "package", "sha256": "71420ee4af4188f59c3e7acdf1d7cab4f4e2d10f9ec1bf4b1742f9075d4b36a2", "shared_library": false, "unvendored_tests": false, "version": "5.2.1"}, "lzma": {"depends": [], "file_name": "lzma-1.0.0.zip", "imports": ["lzma", "_lzma"], "install_dir": "stdlib", "name": "lzma", "package_type": "cpython_module", "sha256": "95f2169b9c57112556c5fc907c3429b572a204cf210048a9d65456ad8e3f4c14", "shared_library": true, "unvendored_tests": false, "version": "1.0.0"}, "markupsafe": {"depends": [], "file_name": "MarkupSafe-2.1.5-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["markupsafe"], "install_dir": "site", "name": "MarkupSafe", "package_type": "package", "sha256": "f2e79b5b66389a43f8381cece4b7dc03df1a28f9d083abe4e964f33bf9e9b72c", "shared_library": false, "unvendored_tests": false, "version": "2.1.5"}, "matplotlib": {"depends": ["cycler", "fonttools", "kiwisolver", "numpy", "packaging", "pillow", "pyparsing", "python-dateutil", "pytz", "matplotlib-pyodide"], "file_name": "matplotlib-3.5.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pylab", "mpl_toolkits", "matplotlib"], "install_dir": "site", "name": "matplotlib", "package_type": "package", "sha256": "670024b6ec06a27fe933dda6cbca5140497ff67d80c70f70b6e6a2d4ccef9b5d", "shared_library": false, "unvendored_tests": true, "version": "3.5.2"}, "matplotlib-inline": {"depends": ["traitlets"], "file_name": "matplotlib_inline-0.1.7-py3-none-any.whl", "imports": ["matplotlib-inline"], "install_dir": "site", "name": "matplotlib-inline", "package_type": "package", "sha256": "01eae0298c3b9d145a69317bbf9622c145130683bd5419b7112f6c2dcaed988a", "shared_library": false, "unvendored_tests": false, "version": "0.1.7"}, "matplotlib-pyodide": {"depends": [], "file_name": "matplotlib_pyodide-0.2.2-py3-none-any.whl", "imports": ["matplotlib_pyodide"], "install_dir": "site", "name": "matplotlib-pyodide", "package_type": "package", "sha256": "07d56729d9625b2b9bb9778a92c88c5c750476abb80f58d71b1386fed434eb8c", "shared_library": false, "unvendored_tests": false, "version": "0.2.2"}, "matplotlib-tests": {"depends": ["matplotlib"], "file_name": "matplotlib-tests.tar", "imports": [], "install_dir": "site", "name": "matplotlib-tests", "package_type": "package", "sha256": "47377f81b9aa221e30824c3edb8cf81268603a578fb6351aab69ec71da799b20", "shared_library": false, "unvendored_tests": false, "version": "3.5.2"}, "memory-allocator": {"depends": [], "file_name": "memory_allocator-0.1.4-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["memory_allocator"], "install_dir": "site", "name": "memory-allocator", "package_type": "package", "sha256": "b1e90dba5ac8b79c66a9677d52dd2c4ba15320151196c7cc9f30aacafb8752f0", "shared_library": false, "unvendored_tests": false, "version": "0.1.4"}, "micropip": {"depends": ["packaging"], "file_name": "micropip-0.6.0-py3-none-any.whl", "imports": ["micropip"], "install_dir": "site", "name": "micropip", "package_type": "package", "sha256": "1a3c889a69e6b2a15456f183e7711bd54175930cbe7ba09e91bec92cac8c418a", "shared_library": false, "unvendored_tests": false, "version": "0.6.0"}, "mmh3": {"depends": [], "file_name": "mmh3-4.1.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["mmh3"], "install_dir": "site", "name": "mmh3", "package_type": "package", "sha256": "ebd23d16d23dd0b2471857f6aa239bd68bfd1fe88a35072dce95ad9c0fb377df", "shared_library": false, "unvendored_tests": false, "version": "4.1.0"}, "mne": {"depends": ["numpy", "scipy", "setuptools", "decorator", "lazy_loader", "packaging"], "file_name": "mne-1.7.0-py3-none-any.whl", "imports": ["mne"], "install_dir": "site", "name": "mne", "package_type": "package", "sha256": "8086f8a7cb973332e29c253c9d60f9807c2384fba15b47b8d875786f6191366f", "shared_library": false, "unvendored_tests": true, "version": "1.7.0"}, "mne-tests": {"depends": ["mne"], "file_name": "mne-tests.tar", "imports": [], "install_dir": "site", "name": "mne-tests", "package_type": "package", "sha256": "7e5c8b54e62641797d2dfed22aed6cf8336135cf65008a3df2b7addb0c1e5015", "shared_library": false, "unvendored_tests": false, "version": "1.7.0"}, "more-itertools": {"depends": [], "file_name": "more_itertools-10.2.0-py3-none-any.whl", "imports": ["more_itertools"], "install_dir": "site", "name": "more-itertools", "package_type": "package", "sha256": "31e7d9b869d4cc1507cbd8ab9883dc52f04cfc60edeb867faa8a43199ee2dfd4", "shared_library": false, "unvendored_tests": false, "version": "10.2.0"}, "mpmath": {"depends": [], "file_name": "mpmath-1.3.0-py3-none-any.whl", "imports": ["mpmath"], "install_dir": "site", "name": "mpmath", "package_type": "package", "sha256": "9fd7b1f3a8a915006c2b48071c89ba620780c714d7e60560d9095f38bb440d9d", "shared_library": false, "unvendored_tests": true, "version": "1.3.0"}, "mpmath-tests": {"depends": ["mpmath"], "file_name": "mpmath-tests.tar", "imports": [], "install_dir": "site", "name": "mpmath-tests", "package_type": "package", "sha256": "c05a226349ebd546b5295ff3a43205e2180cfd552a1fa93d10cbea8763ac8053", "shared_library": false, "unvendored_tests": false, "version": "1.3.0"}, "msgpack": {"depends": [], "file_name": "msgpack-1.0.8-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["msgpack"], "install_dir": "site", "name": "msgpack", "package_type": "package", "sha256": "1e1b95c34b429d9ce044703050035fe1afed561fcbafb5615f9d0ce6d0b243d6", "shared_library": false, "unvendored_tests": false, "version": "1.0.8"}, "msgspec": {"depends": [], "file_name": "msgspec-0.18.6-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["msgspec"], "install_dir": "site", "name": "msgspec", "package_type": "package", "sha256": "8a3ada2ad782421c91e46dfe3bdc88279a9f9640874c774c7eb664f49b4f546e", "shared_library": false, "unvendored_tests": false, "version": "0.18.6"}, "msprime": {"depends": ["numpy", "newick", "tskit", "demes", "rpds-py"], "file_name": "msprime-1.3.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["msprime"], "install_dir": "site", "name": "msprime", "package_type": "package", "sha256": "a5f301b280e9821ae0994fb8dfbc547b41e12a6897e51139c4195de3682f922b", "shared_library": false, "unvendored_tests": false, "version": "1.3.1"}, "multidict": {"depends": [], "file_name": "multidict-6.0.5-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["multidict"], "install_dir": "site", "name": "multidict", "package_type": "package", "sha256": "01a44293972db77a7d74872a37dc32fc57e127ac6c3e92d6c70b039f4656d1f5", "shared_library": false, "unvendored_tests": false, "version": "6.0.5"}, "munch": {"depends": ["setuptools", "six"], "file_name": "munch-4.0.0-py2.py3-none-any.whl", "imports": ["munch"], "install_dir": "site", "name": "munch", "package_type": "package", "sha256": "107c09e48dd9dab92ced1bb625eb714396188b747775ae08f0b289839ac9d08e", "shared_library": false, "unvendored_tests": false, "version": "4.0.0"}, "mypy": {"depends": [], "file_name": "mypy-1.9.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["mypyc", "mypy"], "install_dir": "site", "name": "mypy", "package_type": "package", "sha256": "a0959e7167e1c1d2ca3f015a3b37462c8469a5846960e71db85c6f9700966b09", "shared_library": false, "unvendored_tests": true, "version": "1.9.0"}, "mypy-tests": {"depends": ["mypy"], "file_name": "mypy-tests.tar", "imports": [], "install_dir": "site", "name": "mypy-tests", "package_type": "package", "sha256": "901440be6cfa6a63bd22258515f8997855bf9e358846cbc3424eb630a29f987a", "shared_library": false, "unvendored_tests": false, "version": "1.9.0"}, "netcdf4": {"depends": ["numpy", "packaging", "h5py", "cftime", "certifi"], "file_name": "netCDF4-1.6.5-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["netCDF4"], "install_dir": "site", "name": "netcdf4", "package_type": "package", "sha256": "2b7af5d41dded09350659179b4e5e119cb23652952e08eba560629b38dad52c4", "shared_library": false, "unvendored_tests": false, "version": "1.6.5"}, "networkx": {"depends": ["decorator", "setuptools", "matplotlib", "numpy"], "file_name": "networkx-3.3-py3-none-any.whl", "imports": ["networkx"], "install_dir": "site", "name": "networkx", "package_type": "package", "sha256": "0a39e7e4000d95224d4baf7d9e22c4e837fc67da2e0d39a9bb68bfca50860131", "shared_library": false, "unvendored_tests": true, "version": "3.3"}, "networkx-tests": {"depends": ["networkx"], "file_name": "networkx-tests.tar", "imports": [], "install_dir": "site", "name": "networkx-tests", "package_type": "package", "sha256": "d8bfd615ab6db81a3c8db5afabe78c6c61c80a7994b273aa69d26fdabc2675e8", "shared_library": false, "unvendored_tests": false, "version": "3.3"}, "newick": {"depends": [], "file_name": "newick-1.9.0-py2.py3-none-any.whl", "imports": ["newick"], "install_dir": "site", "name": "newick", "package_type": "package", "sha256": "fd7c551780ac51fbf27e0b1b1efada288a79bfc8b66afc5ea21cd42a7906085e", "shared_library": false, "unvendored_tests": false, "version": "1.9.0"}, "nh3": {"depends": [], "file_name": "nh3-0.2.17-cp37-abi3-pyodide_2024_0_wasm32.whl", "imports": ["nh3"], "install_dir": "site", "name": "nh3", "package_type": "package", "sha256": "ed8c980917c2e5cae0e37fd037fee7115119c27d6eab471662efb1ab6660a284", "shared_library": false, "unvendored_tests": false, "version": "0.2.17"}, "nlopt": {"depends": ["numpy"], "file_name": "nlopt-2.7.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["nlopt"], "install_dir": "site", "name": "nlopt", "package_type": "package", "sha256": "d7b47dd14f8a4d64d7f5a23ebe2d92597fa37492886dc91ce3433cf08d0af1d5", "shared_library": false, "unvendored_tests": false, "version": "2.7.0"}, "nltk": {"depends": ["regex", "sqlite3"], "file_name": "nltk-3.8.1-py3-none-any.whl", "imports": ["nltk"], "install_dir": "site", "name": "nltk", "package_type": "package", "sha256": "063ce4e517cf219b990c6ea452cf46f28b798fd46d7641c74d07a6d49ed8d29c", "shared_library": false, "unvendored_tests": true, "version": "3.8.1"}, "nltk-tests": {"depends": ["nltk"], "file_name": "nltk-tests.tar", "imports": [], "install_dir": "site", "name": "nltk-tests", "package_type": "package", "sha256": "83ea5fe1cc7ff21cd0e7ab455eadd2a69e207955640ac7e7274eddf5df01625e", "shared_library": false, "unvendored_tests": false, "version": "3.8.1"}, "numcodecs": {"depends": ["numpy", "msgpack"], "file_name": "numcodecs-0.11.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["numcodecs"], "install_dir": "site", "name": "numcodecs", "package_type": "package", "sha256": "9c7b7254d00fd62b7effd5b31786bb19f1509530df671a090a5913ff5c3e1e44", "shared_library": false, "unvendored_tests": true, "version": "0.11.0"}, "numcodecs-tests": {"depends": ["numcodecs"], "file_name": "numcodecs-tests.tar", "imports": [], "install_dir": "site", "name": "numcodecs-tests", "package_type": "package", "sha256": "ec0ab47c1ac88f3fb08eb7291e2c8bbd90339809dac5066c744fb0a7c2300859", "shared_library": false, "unvendored_tests": false, "version": "0.11.0"}, "numpy": {"depends": [], "file_name": "numpy-1.26.4-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["numpy"], "install_dir": "site", "name": "numpy", "package_type": "package", "sha256": "4aeba609614f88fbb49d31bdafbc3f8e18e3ae4ba6d2b710c5281e5d22b64f9a", "shared_library": false, "unvendored_tests": true, "version": "1.26.4"}, "numpy-tests": {"depends": ["numpy"], "file_name": "numpy-tests.tar", "imports": [], "install_dir": "site", "name": "numpy-tests", "package_type": "package", "sha256": "95ee5fe4134dc7b045637150374154a2034430e0b48ae522d59496a6c81a16df", "shared_library": false, "unvendored_tests": false, "version": "1.26.4"}, "openblas": {"depends": [], "file_name": "openblas-0.3.26.zip", "imports": [], "install_dir": "dynlib", "name": "openblas", "package_type": "shared_library", "sha256": "bdc0f6c43169c8cd80a6f708d873fd441e6e475f3615476907f8865c3efd5668", "shared_library": true, "unvendored_tests": false, "version": "0.3.26"}, "opencv-python": {"depends": ["numpy"], "file_name": "opencv_python-4.9.0.80-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["cv2"], "install_dir": "site", "name": "opencv-python", "package_type": "package", "sha256": "50b8041c16f52608e17132ca5149bbc88495a030a19d88ce5502a8cf6abbf618", "shared_library": false, "unvendored_tests": false, "version": "4.9.0.80"}, "openssl": {"depends": [], "file_name": "openssl-1.1.1n.zip", "imports": [], "install_dir": "dynlib", "name": "openssl", "package_type": "shared_library", "sha256": "59303d428657ee5466221b3f778fc425149387a89d3330859af53be2796ff95d", "shared_library": true, "unvendored_tests": false, "version": "1.1.1n"}, "optlang": {"depends": ["sympy", "six", "swiglpk"], "file_name": "optlang-1.8.1-py2.py3-none-any.whl", "imports": ["optlang"], "install_dir": "site", "name": "optlang", "package_type": "package", "sha256": "c0f07f5eaf66e64a6c42d0061032f86c670b4e1af96514196f2d2c3f3a71e70a", "shared_library": false, "unvendored_tests": true, "version": "1.8.1"}, "optlang-tests": {"depends": ["optlang"], "file_name": "optlang-tests.tar", "imports": [], "install_dir": "site", "name": "optlang-tests", "package_type": "package", "sha256": "cf7f754efeb1bf3195e16c5cce5eb13c1716db18dda20d4fc88683c9e49427a6", "shared_library": false, "unvendored_tests": false, "version": "1.8.1"}, "orjson": {"depends": [], "file_name": "orjson-3.10.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["orjson"], "install_dir": "site", "name": "orjson", "package_type": "package", "sha256": "b12cded1997656c848609705f31a1d0d91ab4e53000365acec899c7cb9488deb", "shared_library": false, "unvendored_tests": false, "version": "3.10.1"}, "packaging": {"depends": [], "file_name": "packaging-23.2-py3-none-any.whl", "imports": ["packaging"], "install_dir": "site", "name": "packaging", "package_type": "package", "sha256": "34c09d7d17c4b584b10edca9255281c11c4713f77c76945d918d7ffa0455c9fa", "shared_library": false, "unvendored_tests": false, "version": "23.2"}, "pandas": {"depends": ["numpy", "python-dateutil", "pytz"], "file_name": "pandas-2.2.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pandas"], "install_dir": "site", "name": "pandas", "package_type": "package", "sha256": "57257d1fe41081ec6aa88499a6eab6e06b1cee1e54fbc05fc1365c739017e4d6", "shared_library": false, "unvendored_tests": true, "version": "2.2.0"}, "pandas-tests": {"depends": ["pandas"], "file_name": "pandas-tests.tar", "imports": [], "install_dir": "site", "name": "pandas-tests", "package_type": "package", "sha256": "eba78f89a53a4898e0ae87e7571fbfe7a540d65d53fda17935b115e1c767c444", "shared_library": false, "unvendored_tests": false, "version": "2.2.0"}, "parso": {"depends": [], "file_name": "parso-0.8.4-py2.py3-none-any.whl", "imports": ["parso"], "install_dir": "site", "name": "parso", "package_type": "package", "sha256": "36e589cee5c2aaf94489df8f30f4db7d89b42b260a6d739583fa00a3e42d157a", "shared_library": false, "unvendored_tests": false, "version": "0.8.4"}, "patsy": {"depends": ["numpy", "six"], "file_name": "patsy-0.5.6-py2.py3-none-any.whl", "imports": ["patsy"], "install_dir": "site", "name": "patsy", "package_type": "package", "sha256": "760833a100a66baafd1e4b0a726eea87e6c9edcd7c6ba3e7473ca1dfe593e7f8", "shared_library": false, "unvendored_tests": true, "version": "0.5.6"}, "patsy-tests": {"depends": ["patsy"], "file_name": "patsy-tests.tar", "imports": [], "install_dir": "site", "name": "patsy-tests", "package_type": "package", "sha256": "6a3632284ca095eb87cec8a79d06183399f85c21af4ff986cb7d9a37ebd3e13f", "shared_library": false, "unvendored_tests": false, "version": "0.5.6"}, "peewee": {"depends": ["sqlite3", "cffi"], "file_name": "peewee-3.17.3-py3-none-any.whl", "imports": ["peewee"], "install_dir": "site", "name": "peewee", "package_type": "package", "sha256": "b3aac565a0a5e345fa0c069c41a0b696c9c8292e9970405ce9fb7fded0a3cf34", "shared_library": false, "unvendored_tests": true, "version": "3.17.3"}, "peewee-tests": {"depends": ["peewee"], "file_name": "peewee-tests.tar", "imports": [], "install_dir": "site", "name": "peewee-tests", "package_type": "package", "sha256": "bcda11ebbf1e3b0326845039c48106fd56843a1ff1cf33375b075f9b615ae233", "shared_library": false, "unvendored_tests": false, "version": "3.17.3"}, "pillow": {"depends": [], "file_name": "pillow-10.2.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["PIL"], "install_dir": "site", "name": "Pillow", "package_type": "package", "sha256": "b1ef215cdd2abc38fab5d821a86d730bc495e0450d34ed99d90c74f319f90f55", "shared_library": false, "unvendored_tests": false, "version": "10.2.0"}, "pillow-heif": {"depends": ["cffi", "pillow", "libheif"], "file_name": "pillow_heif-0.8.0-cp36-abi3-pyodide_2024_0_wasm32.whl", "imports": ["pillow_heif"], "install_dir": "site", "name": "pillow_heif", "package_type": "package", "sha256": "a2adfb50f218b2609b2be0d48d7fa0e4f03f66b7c8b69a80a83fab2bd62c8472", "shared_library": false, "unvendored_tests": false, "version": "0.8.0"}, "pkgconfig": {"depends": [], "file_name": "pkgconfig-1.5.5-py3-none-any.whl", "imports": ["pkgconfig"], "install_dir": "site", "name": "pkgconfig", "package_type": "package", "sha256": "b1772cd7ef35b74e2fae52ee83f70bc1c51c46e962c3ce8d514be3927b57a37b", "shared_library": false, "unvendored_tests": false, "version": "1.5.5"}, "pluggy": {"depends": [], "file_name": "pluggy-1.5.0-py3-none-any.whl", "imports": ["pluggy"], "install_dir": "site", "name": "pluggy", "package_type": "package", "sha256": "41726a2e85006e36c5d1b584b01674aeaba798f7f27eabc68dc1350ebdebd7e1", "shared_library": false, "unvendored_tests": false, "version": "1.5.0"}, "pplpy": {"depends": ["gmpy2", "cysignals"], "file_name": "pplpy-0.8.10-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["ppl"], "install_dir": "site", "name": "pplpy", "package_type": "package", "sha256": "325d38df6ddd86ba3e0b8fc02e77fcb520033c3211589550b1c0f30638644f22", "shared_library": false, "unvendored_tests": false, "version": "0.8.10"}, "primecountpy": {"depends": ["cysignals"], "file_name": "primecountpy-0.1.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["primecountpy"], "install_dir": "site", "name": "primecountpy", "package_type": "package", "sha256": "dbbd9dd54cdde6f191cb615875f8a8edf6ec4f548def141d99b4ee5e9804d44e", "shared_library": false, "unvendored_tests": false, "version": "0.1.0"}, "prompt-toolkit": {"depends": [], "file_name": "prompt_toolkit-3.0.43-py3-none-any.whl", "imports": ["prompt_toolkit"], "install_dir": "site", "name": "prompt_toolkit", "package_type": "package", "sha256": "954ac31a47ce598daec1757c13f9d4ae5e3081f5762bcaaadbea62cef593ca3e", "shared_library": false, "unvendored_tests": false, "version": "3.0.43"}, "protobuf": {"depends": [], "file_name": "protobuf-4.24.4-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["google"], "install_dir": "site", "name": "protobuf", "package_type": "package", "sha256": "fedaad25244cd92d3ae1cce9dc2bfc30186efde7d979146aa6c2e5c322f3f441", "shared_library": false, "unvendored_tests": false, "version": "4.24.4"}, "pure-eval": {"depends": [], "file_name": "pure_eval-0.2.2-py3-none-any.whl", "imports": ["pure_eval"], "install_dir": "site", "name": "pure_eval", "package_type": "package", "sha256": "2a02f1b49cb9b405f7fa300695be174fc4f8b4da220dcace1daa66ab252ba027", "shared_library": false, "unvendored_tests": false, "version": "0.2.2"}, "py": {"depends": [], "file_name": "py-1.11.0-py2.py3-none-any.whl", "imports": ["py"], "install_dir": "site", "name": "py", "package_type": "package", "sha256": "8027d2e090352a65167983ec14b187b0c281dc31e98cb6e17885f57d3cf2b407", "shared_library": false, "unvendored_tests": false, "version": "1.11.0"}, "pyclipper": {"depends": [], "file_name": "pyclipper-1.3.0.post5-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pyclipper"], "install_dir": "site", "name": "pyclipper", "package_type": "package", "sha256": "c6f8d4b9749600cb8443076258ccf293465fbe0bd62c448316ef3613c9dd2c05", "shared_library": false, "unvendored_tests": false, "version": "1.3.0.post5"}, "pycparser": {"depends": [], "file_name": "pycparser-2.22-py3-none-any.whl", "imports": ["pycparser"], "install_dir": "site", "name": "pycparser", "package_type": "package", "sha256": "2a807b142ffd51086282d4776576ca61b0fd870ea6babc7212d75607357279be", "shared_library": false, "unvendored_tests": false, "version": "2.22"}, "pycryptodome": {"depends": [], "file_name": "pycryptodome-3.20.0-cp35-abi3-pyodide_2024_0_wasm32.whl", "imports": ["Crypto"], "install_dir": "site", "name": "pycryptodome", "package_type": "package", "sha256": "bea845208b260b9081f5ec1cd9833b111b1642af04ec5824e3c83fd0d2c30a4f", "shared_library": false, "unvendored_tests": true, "version": "3.20.0"}, "pycryptodome-tests": {"depends": ["pycryptodome"], "file_name": "pycryptodome-tests.tar", "imports": [], "install_dir": "site", "name": "pycryptodome-tests", "package_type": "package", "sha256": "8b37e84e9df0bae3d6459d5ac64669d88e73015d11918c6c57adeb052f5d0c59", "shared_library": false, "unvendored_tests": false, "version": "3.20.0"}, "pydantic": {"depends": ["typing-extensions", "pydantic_core", "annotated-types"], "file_name": "pydantic-2.7.0-py3-none-any.whl", "imports": ["pydantic"], "install_dir": "site", "name": "pydantic", "package_type": "package", "sha256": "7c866a4a9071ca25279c06c0e4871d6e5a7c611f2e1bcfadc47ba2b3336016ca", "shared_library": false, "unvendored_tests": false, "version": "2.7.0"}, "pydantic-core": {"depends": [], "file_name": "pydantic_core-2.18.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pydantic_core"], "install_dir": "site", "name": "pydantic_core", "package_type": "package", "sha256": "be7f83ad55493a9ffb94777d30fe3e6d3334cb08e2ebdaf0e7a799eef0174866", "shared_library": false, "unvendored_tests": false, "version": "2.18.1"}, "pydecimal": {"depends": [], "file_name": "pydecimal-1.0.0.zip", "imports": ["_pydecimal"], "install_dir": "stdlib", "name": "pydecimal", "package_type": "cpython_module", "sha256": "c0661bafc675196901c67b4f883abdb0dffb44b97aec91bec0a03bfebeb12668", "shared_library": true, "unvendored_tests": false, "version": "1.0.0"}, "pydoc-data": {"depends": [], "file_name": "pydoc_data-1.0.0.zip", "imports": ["pydoc_data"], "install_dir": "stdlib", "name": "pydoc_data", "package_type": "cpython_module", "sha256": "b15bf763b1158e7757c7265a1340c92ce23c753f983e4d487e2bea8cc749300f", "shared_library": true, "unvendored_tests": false, "version": "1.0.0"}, "pyerfa": {"depends": ["numpy"], "file_name": "pyerfa-2.0.1.4-cp39-abi3-pyodide_2024_0_wasm32.whl", "imports": ["erfa"], "install_dir": "site", "name": "pyerfa", "package_type": "package", "sha256": "423bd62931e5244edd860856e47eb6b5122f1ec369fdd797112b6676ab60922a", "shared_library": false, "unvendored_tests": true, "version": "2.0.1.4"}, "pyerfa-tests": {"depends": ["pyerfa"], "file_name": "pyerfa-tests.tar", "imports": [], "install_dir": "site", "name": "pyerfa-tests", "package_type": "package", "sha256": "8bf7d38d8aa679833a5af53c9729bcd28c69a028fefe7f751d06d2e92f93981f", "shared_library": false, "unvendored_tests": false, "version": "2.0.1.4"}, "pygame-ce": {"depends": [], "file_name": "pygame_ce-2.4.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pygame"], "install_dir": "site", "name": "pygame-ce", "package_type": "package", "sha256": "ffeae5c40f989d0c208711b552fa2e2b41b2df6077a4d3941f208fc97d4bf9a1", "shared_library": false, "unvendored_tests": true, "version": "2.4.1"}, "pygame-ce-tests": {"depends": ["pygame-ce"], "file_name": "pygame-ce-tests.tar", "imports": [], "install_dir": "site", "name": "pygame-ce-tests", "package_type": "package", "sha256": "ece1ba607e541cea41d29ad516327e58853cb1908f9fbf338058ee40bdc683c0", "shared_library": false, "unvendored_tests": false, "version": "2.4.1"}, "pygments": {"depends": [], "file_name": "pygments-2.17.2-py3-none-any.whl", "imports": ["pygments"], "install_dir": "site", "name": "Pygments", "package_type": "package", "sha256": "58dfa90582044080f780d8f1b71e9e52c320fb7b0231c7191d32d456650f6cee", "shared_library": false, "unvendored_tests": false, "version": "2.17.2"}, "pyheif": {"depends": ["cffi"], "file_name": "pyheif-0.7.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pyheif"], "install_dir": "site", "name": "pyheif", "package_type": "package", "sha256": "7b1837edd9ae8bb8e1f1d16a65c3f260ca42751db17dd6652a02c92c12201936", "shared_library": false, "unvendored_tests": false, "version": "0.7.1"}, "pyiceberg": {"depends": ["click", "fsspec", "mmh3", "pydantic", "pyparsing", "requests", "rich", "sortedcontainers", "sqlalchemy", "strictyaml"], "file_name": "pyiceberg-0.6.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pyiceberg"], "install_dir": "site", "name": "pyiceberg", "package_type": "package", "sha256": "aa291e29362604cc8acf7ae035b7f044ab5660014087a7464f75b9fbf36a566d", "shared_library": false, "unvendored_tests": false, "version": "0.6.0"}, "pyinstrument": {"depends": [], "file_name": "pyinstrument-4.4.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pyinstrument"], "install_dir": "site", "name": "pyinstrument", "package_type": "package", "sha256": "1ac73667303af9889b8787b46413084b55cb3b2420825cc68bc6afd529fe1996", "shared_library": false, "unvendored_tests": false, "version": "4.4.0"}, "pynacl": {"depends": ["cffi"], "file_name": "PyNaCl-1.5.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["nacl"], "install_dir": "site", "name": "pynacl", "package_type": "package", "sha256": "c3ab7e374410b93609bc53212a8faf8cd617f45bdfd9cb6cdf8ecce60e3ba9e9", "shared_library": false, "unvendored_tests": false, "version": "1.5.0"}, "pyodide-http": {"depends": [], "file_name": "pyodide_http-0.2.1-py3-none-any.whl", "imports": ["pyodide_http"], "install_dir": "site", "name": "pyodide-http", "package_type": "package", "sha256": "aa2cc7e585e98a84208bc042dd36140cb4213ec6112915a1cc86f7101e38d47a", "shared_library": false, "unvendored_tests": false, "version": "0.2.1"}, "pyparsing": {"depends": [], "file_name": "pyparsing-3.1.2-py3-none-any.whl", "imports": ["pyparsing"], "install_dir": "site", "name": "pyparsing", "package_type": "package", "sha256": "8db11035317ae75a36c2b4dea44eeba5630c8bd2f501e74a243dbbe93f511895", "shared_library": false, "unvendored_tests": false, "version": "3.1.2"}, "pyproj": {"depends": ["certifi", "sqlite3"], "file_name": "pyproj-3.6.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pyproj"], "install_dir": "site", "name": "pyproj", "package_type": "package", "sha256": "109d9c5c99442a89c40453571c1337589bcfdeb66d683645973ee8bc5d14ff85", "shared_library": false, "unvendored_tests": false, "version": "3.6.1"}, "pyrsistent": {"depends": [], "file_name": "pyrsistent-0.20.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["_pyrsistent_version", "pyrsistent"], "install_dir": "site", "name": "pyrsistent", "package_type": "package", "sha256": "5d464ec51462568599539a111c431c7612e4acefa8d86a4d18280cfa3bc4d3ec", "shared_library": false, "unvendored_tests": false, "version": "0.20.0"}, "pysam": {"depends": [], "file_name": "pysam-0.22.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pysam"], "install_dir": "site", "name": "pysam", "package_type": "package", "sha256": "c6374ae788806ea721c6588dd8e297c214262e95e42839ef4ade44b649f8a278", "shared_library": false, "unvendored_tests": false, "version": "0.22.0"}, "pyshp": {"depends": [], "file_name": "pyshp-2.3.1-py2.py3-none-any.whl", "imports": ["shapefile"], "install_dir": "site", "name": "pyshp", "package_type": "package", "sha256": "592a39ee27138b454d767f6620c6895f29ae360c4a47ab3fd00ad6875e432566", "shared_library": false, "unvendored_tests": false, "version": "2.3.1"}, "pytest": {"depends": ["atomicwrites", "attrs", "more-itertools", "pluggy", "py", "setuptools", "six", "iniconfig", "exceptiongroup"], "file_name": "pytest-8.1.1-py3-none-any.whl", "imports": ["_pytest", "pytest"], "install_dir": "site", "name": "pytest", "package_type": "package", "sha256": "132f51fe851ed5735e6f6b7729c566e44cb6ac488b7d0040ca247d1cb9638d1a", "shared_library": false, "unvendored_tests": false, "version": "8.1.1"}, "pytest-asyncio": {"depends": ["pytest"], "file_name": "pytest_asyncio-0.23.7-py3-none-any.whl", "imports": ["pytest_asyncio"], "install_dir": "site", "name": "pytest-asyncio", "package_type": "package", "sha256": "80597e5a925462a351645d48af5806fd5ccf95f269c0690eae4df772aa66d424", "shared_library": false, "unvendored_tests": false, "version": "0.23.7"}, "pytest-benchmark": {"depends": [], "file_name": "pytest_benchmark-4.0.0-py3-none-any.whl", "imports": ["pytest_benchmark"], "install_dir": "site", "name": "pytest-benchmark", "package_type": "package", "sha256": "4752b0087dc78a3909a472998646266e834f2af2111ba7cf4a6c9659e737a957", "shared_library": false, "unvendored_tests": false, "version": "4.0.0"}, "python-dateutil": {"depends": ["six"], "file_name": "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", "imports": ["dateutil"], "install_dir": "site", "name": "python-dateutil", "package_type": "package", "sha256": "1adf6847a0ae4a09bef1fc9954089766800ce6d9688a5329d04ad54bcf7d1f2c", "shared_library": false, "unvendored_tests": false, "version": "2.9.0.post0"}, "python-flint": {"depends": [], "file_name": "python_flint-0.6.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["flint"], "install_dir": "site", "name": "python-flint", "package_type": "package", "sha256": "95eac4de7c679cd78071e8d17f4e6ed2f373082f3bddba775d5a017dd9bfdc69", "shared_library": false, "unvendored_tests": false, "version": "0.6.0"}, "python-magic": {"depends": ["libmagic"], "file_name": "python_magic-0.4.27-py2.py3-none-any.whl", "imports": ["magic"], "install_dir": "site", "name": "python-magic", "package_type": "package", "sha256": "7353b45205f8d79530c37367321ae511e84dec5f8a39c441082f3b2f0fff70d3", "shared_library": false, "unvendored_tests": false, "version": "0.4.27"}, "python-sat": {"depends": ["six"], "file_name": "python_sat-1.8.dev13-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pysat"], "install_dir": "site", "name": "python-sat", "package_type": "package", "sha256": "210f18f6d80f9670a9d5d53973b73facfbf2fcc71a87d6583db816862be0ba23", "shared_library": false, "unvendored_tests": false, "version": "1.8.dev13"}, "python-solvespace": {"depends": [], "file_name": "python_solvespace-3.0.8-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["python_solvespace"], "install_dir": "site", "name": "python_solvespace", "package_type": "package", "sha256": "b88c85292a771f365664a9c4d044d87db62c201ee8f7a97bd4fca94ddbee242c", "shared_library": false, "unvendored_tests": false, "version": "3.0.8"}, "pytz": {"depends": [], "file_name": "pytz-2024.1-py2.py3-none-any.whl", "imports": ["pytz"], "install_dir": "site", "name": "pytz", "package_type": "package", "sha256": "62dfa923a4643d5d56371fe08a8aa2ea63854d154cf46c09e7c91c167207eb1a", "shared_library": false, "unvendored_tests": false, "version": "2024.1"}, "pywavelets": {"depends": ["numpy", "matplotlib", "scipy"], "file_name": "pywavelets-1.6.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pywt"], "install_dir": "site", "name": "pywavelets", "package_type": "package", "sha256": "32626f1fd288714f500b961b7358f5dc7b2fabfbd90b458514055dd95f7965d3", "shared_library": false, "unvendored_tests": true, "version": "1.6.0"}, "pywavelets-tests": {"depends": ["pywavelets"], "file_name": "pywavelets-tests.tar", "imports": [], "install_dir": "site", "name": "pywavelets-tests", "package_type": "package", "sha256": "6a6b63b515e6285b7b90bc94dd138a94f1771fa1dfb550816c61c6b1b7bcb4bf", "shared_library": false, "unvendored_tests": false, "version": "1.6.0"}, "pyxel": {"depends": [], "file_name": "pyxel-1.9.10-cp37-abi3-pyodide_2024_0_wasm32.whl", "imports": ["pyxel"], "install_dir": "site", "name": "pyxel", "package_type": "package", "sha256": "46e15678938bb23be6a5e89ee2566bbe6457fa7ede38a0c26e3ea86bdb642b09", "shared_library": false, "unvendored_tests": false, "version": "1.9.10"}, "pyxirr": {"depends": [], "file_name": "pyxirr-0.10.3-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["pyxirr"], "install_dir": "site", "name": "pyxirr", "package_type": "package", "sha256": "ccb91ae101e3ee001aec040e1423c86b25832deddbaf3efbb6ca4557064a7ced", "shared_library": false, "unvendored_tests": false, "version": "0.10.3"}, "pyyaml": {"depends": [], "file_name": "PyYAML-6.0.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["_yaml", "yaml"], "install_dir": "site", "name": "pyyaml", "package_type": "package", "sha256": "f8e7882442554c1648a62c1f47e4ef3c1c4756834c9d42cd3ea89b01e92738c6", "shared_library": false, "unvendored_tests": false, "version": "6.0.1"}, "rebound": {"depends": ["numpy"], "file_name": "rebound-3.24.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["rebound"], "install_dir": "site", "name": "rebound", "package_type": "package", "sha256": "e85c1670d5a67564fa313e55c176a946db7646fb6ad4faa4bc8c32464e7f30de", "shared_library": false, "unvendored_tests": false, "version": "3.24.2"}, "reboundx": {"depends": ["rebound", "numpy"], "file_name": "reboundx-3.10.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["reboundx"], "install_dir": "site", "name": "reboundx", "package_type": "package", "sha256": "2acea849f2e63eaf09ec8ea3e0cc676f3c7930ecdaf372660a72aed514acdb40", "shared_library": false, "unvendored_tests": false, "version": "3.10.1"}, "referencing": {"depends": ["attrs", "rpds-py"], "file_name": "referencing-0.34.0-py3-none-any.whl", "imports": ["referencing"], "install_dir": "site", "name": "referencing", "package_type": "package", "sha256": "6912d699247bc5c38391859c922308465a6a6beb27b4d6a48ade6cf7f66db561", "shared_library": false, "unvendored_tests": true, "version": "0.34.0"}, "referencing-tests": {"depends": ["referencing"], "file_name": "referencing-tests.tar", "imports": [], "install_dir": "site", "name": "referencing-tests", "package_type": "package", "sha256": "5f7bceb247a09327cf0912eca4d6a3c90cb6b4436a864bc82a333ecf3e11159f", "shared_library": false, "unvendored_tests": false, "version": "0.34.0"}, "regex": {"depends": [], "file_name": "regex-2024.4.16-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["regex"], "install_dir": "site", "name": "regex", "package_type": "package", "sha256": "826a6da49b07843236888dd29c8ce0cab1f588315ccf4c55321ecd135fd21093", "shared_library": false, "unvendored_tests": true, "version": "2024.4.16"}, "regex-tests": {"depends": ["regex"], "file_name": "regex-tests.tar", "imports": [], "install_dir": "site", "name": "regex-tests", "package_type": "package", "sha256": "37d33d6e7fb2d1c605bd72d78236ef4e324665c474aea4db737a6e6444159942", "shared_library": false, "unvendored_tests": false, "version": "2024.4.16"}, "requests": {"depends": ["charset-normalizer", "idna", "urllib3", "certifi"], "file_name": "requests-2.31.0-py3-none-any.whl", "imports": ["requests"], "install_dir": "site", "name": "requests", "package_type": "package", "sha256": "001c81f2678aa7dbd3f6884e53997b26c948d0b52603933d953d6f835c397965", "shared_library": false, "unvendored_tests": false, "version": "2.31.0"}, "retrying": {"depends": ["six"], "file_name": "retrying-1.3.4-py3-none-any.whl", "imports": ["retrying"], "install_dir": "site", "name": "retrying", "package_type": "package", "sha256": "c3dba397227a4f7eb4eb95fe627f4d2d03a4c50adf6f25edc1dbc691ee5622c9", "shared_library": false, "unvendored_tests": false, "version": "1.3.4"}, "rich": {"depends": [], "file_name": "rich-13.7.1-py3-none-any.whl", "imports": ["rich"], "install_dir": "site", "name": "rich", "package_type": "package", "sha256": "ea0e850b047833799b03e521dd8eb7d21118056183cf6957c336bf414c265282", "shared_library": false, "unvendored_tests": false, "version": "13.7.1"}, "river": {"depends": ["numpy", "pandas", "pytest", "scipy"], "file_name": "river-0.19.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["river"], "install_dir": "site", "name": "river", "package_type": "package", "sha256": "aaac213ea3f3176ee84654399e6b397f69f0bf2d1632d72c66a3232499dca938", "shared_library": false, "unvendored_tests": true, "version": "0.19.0"}, "river-tests": {"depends": ["river"], "file_name": "river-tests.tar", "imports": [], "install_dir": "site", "name": "river-tests", "package_type": "package", "sha256": "9b2bee4cfa8e9c91c3587ec24657def30e8a569b329b52d1acb64f078f309e22", "shared_library": false, "unvendored_tests": false, "version": "0.19.0"}, "robotraconteur": {"depends": ["numpy"], "file_name": "RobotRaconteur-1.2.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["RobotRaconteur"], "install_dir": "site", "name": "RobotRaconteur", "package_type": "package", "sha256": "77753ca8a261756c7821f7b2cf7be4db90e2a2281d7d89e2c952f68e69eb3711", "shared_library": false, "unvendored_tests": false, "version": "1.2.0"}, "rpds-py": {"depends": [], "file_name": "rpds_py-0.18.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["rpds"], "install_dir": "site", "name": "rpds-py", "package_type": "package", "sha256": "abbcc63b95769dd804106e191eaf0ac462bd81acc45faeb6e83a8d72f8353b2d", "shared_library": false, "unvendored_tests": false, "version": "0.18.0"}, "ruamel-yaml": {"depends": [], "file_name": "ruamel.yaml-0.18.6-py3-none-any.whl", "imports": ["ruamel"], "install_dir": "site", "name": "ruamel.yaml", "package_type": "package", "sha256": "2ebfdf7175e37b832f191a7e11452e32c900618382b6bba4e9b16265d5e56f57", "shared_library": false, "unvendored_tests": false, "version": "0.18.6"}, "rust-panic-test": {"depends": [], "file_name": "rust_panic_test-1.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["rust-panic-test"], "install_dir": "site", "name": "rust-panic-test", "package_type": "package", "sha256": "c97425311fffebd46631b3e0666d4a4a6d4a84b5143e54c58d663b2fe6c122e9", "shared_library": false, "unvendored_tests": false, "version": "1.0"}, "scikit-image": {"depends": ["packaging", "numpy", "scipy", "networkx", "pillow", "imageio", "pywavelets", "lazy_loader"], "file_name": "scikit_image-0.23.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["skimage"], "install_dir": "site", "name": "scikit-image", "package_type": "package", "sha256": "ac530cc5e2cb757079e691415e096fe14b476267ccb0d14be9cd65c714cea96e", "shared_library": false, "unvendored_tests": true, "version": "0.23.2"}, "scikit-image-tests": {"depends": ["scikit-image"], "file_name": "scikit-image-tests.tar", "imports": [], "install_dir": "site", "name": "scikit-image-tests", "package_type": "package", "sha256": "292dda6f83cc184f2332fd05659a55214e9c269ada38472da9338d9e51ee500a", "shared_library": false, "unvendored_tests": false, "version": "0.23.2"}, "scikit-learn": {"depends": ["scipy", "joblib", "threadpoolctl"], "file_name": "scikit_learn-1.4.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["sklearn"], "install_dir": "site", "name": "scikit-learn", "package_type": "package", "sha256": "53302f72d375c6c110e1f50c9d70376884b22ba87eab53e440bd68f2a96016cd", "shared_library": false, "unvendored_tests": true, "version": "1.4.2"}, "scikit-learn-tests": {"depends": ["scikit-learn"], "file_name": "scikit-learn-tests.tar", "imports": [], "install_dir": "site", "name": "scikit-learn-tests", "package_type": "package", "sha256": "e57544bda608c3c48f613197bf1853d6d58d24e56ac70020f7c3c10e905e1b76", "shared_library": false, "unvendored_tests": false, "version": "1.4.2"}, "scipy": {"depends": ["numpy", "openblas"], "file_name": "scipy-1.12.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["scipy"], "install_dir": "site", "name": "scipy", "package_type": "package", "sha256": "3eb24e2939b223bc946db8cd193b33ad2ebbd6c98004cf17f26e2bd4e5bad92c", "shared_library": false, "unvendored_tests": true, "version": "1.12.0"}, "scipy-tests": {"depends": ["scipy"], "file_name": "scipy-tests.tar", "imports": [], "install_dir": "site", "name": "scipy-tests", "package_type": "package", "sha256": "999bf6b9449c64b74a08f4b40840c1f0a9e690cd8714a75554d552fd4cded727", "shared_library": false, "unvendored_tests": false, "version": "1.12.0"}, "screed": {"depends": [], "file_name": "screed-1.1.3-py2.py3-none-any.whl", "imports": ["bigtests", "screed"], "install_dir": "site", "name": "screed", "package_type": "package", "sha256": "1f7bd864f01e97056e0c06a11d0c302726f4d9bcd2cf378961dad6336cedfebf", "shared_library": false, "unvendored_tests": true, "version": "1.1.3"}, "screed-tests": {"depends": ["screed"], "file_name": "screed-tests.tar", "imports": [], "install_dir": "site", "name": "screed-tests", "package_type": "package", "sha256": "8d3ce3a84b6efcd306853e35bffb027b1b21b0cdf8f60be25c7c906193df07bf", "shared_library": false, "unvendored_tests": false, "version": "1.1.3"}, "setuptools": {"depends": ["pyparsing"], "file_name": "setuptools-69.5.1-py3-none-any.whl", "imports": ["_distutils_hack", "pkg_resources", "setuptools"], "install_dir": "site", "name": "setuptools", "package_type": "package", "sha256": "8d109163f2597f5353f0e9089636de4031a310245443e8a1f2af6afe18b8c41c", "shared_library": false, "unvendored_tests": false, "version": "69.5.1"}, "shapely": {"depends": ["numpy"], "file_name": "shapely-2.0.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["shapely"], "install_dir": "site", "name": "shapely", "package_type": "package", "sha256": "2ca8a9a020893e3ce747c006908bd3ff8ca14f5ecf7201bc240d7e2bcdae53a5", "shared_library": false, "unvendored_tests": true, "version": "2.0.2"}, "shapely-tests": {"depends": ["shapely"], "file_name": "shapely-tests.tar", "imports": [], "install_dir": "site", "name": "shapely-tests", "package_type": "package", "sha256": "cd2eca2e5c2aa75fe98a012eebbc52beda9dc13bc9ddfe455955f79d28d1972f", "shared_library": false, "unvendored_tests": false, "version": "2.0.2"}, "sharedlib-test": {"depends": [], "file_name": "sharedlib-test-1.0.zip", "imports": [], "install_dir": "dynlib", "name": "sharedlib-test", "package_type": "shared_library", "sha256": "7478123361942519d05cc921696d597a2ba75806d52ddb5973c0f19b21ee22fd", "shared_library": true, "unvendored_tests": false, "version": "1.0"}, "sharedlib-test-py": {"depends": ["sharedlib-test"], "file_name": "sharedlib_test_py-1.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["sharedlib_test"], "install_dir": "site", "name": "sharedlib-test-py", "package_type": "package", "sha256": "f1e7add98c4d1aebced3e6ccbfe973ff83ec5e8642a00f16975351d0518411cf", "shared_library": false, "unvendored_tests": false, "version": "1.0"}, "simplejson": {"depends": [], "file_name": "simplejson-3.19.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["simplejson"], "install_dir": "site", "name": "simplejson", "package_type": "package", "sha256": "c4f8f531952ab9d402d01ce89106edfbcc243173f968f2e60efdb3618930b3df", "shared_library": false, "unvendored_tests": true, "version": "3.19.2"}, "simplejson-tests": {"depends": ["simplejson"], "file_name": "simplejson-tests.tar", "imports": [], "install_dir": "site", "name": "simplejson-tests", "package_type": "package", "sha256": "4c8ff471020ca4a7d115672f1d2aca34dd7bd7db53b38d45e6b24c3d71f7c2b5", "shared_library": false, "unvendored_tests": false, "version": "3.19.2"}, "sisl": {"depends": ["pyparsing", "numpy", "scipy", "tqdm", "xarray", "pandas", "matplotlib"], "file_name": "sisl-0.14.3-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["sisl_toolbox", "sisl"], "install_dir": "site", "name": "sisl", "package_type": "package", "sha256": "a026d869059702221c0b093650bbd62b96d1c7d87bc42188b5a4a328a2bdb39d", "shared_library": false, "unvendored_tests": true, "version": "0.14.3"}, "sisl-tests": {"depends": ["sisl"], "file_name": "sisl-tests.tar", "imports": [], "install_dir": "site", "name": "sisl-tests", "package_type": "package", "sha256": "35a35535a2ae2ea220c97042753c7c10e3a76932cbb773bb3a8166a833e03d0b", "shared_library": false, "unvendored_tests": false, "version": "0.14.3"}, "six": {"depends": [], "file_name": "six-1.16.0-py2.py3-none-any.whl", "imports": ["six"], "install_dir": "site", "name": "six", "package_type": "package", "sha256": "176a02f2e3c155246b96ff0a8e8a35c35e74f666923a50e47d719f21e2d3b7f2", "shared_library": false, "unvendored_tests": false, "version": "1.16.0"}, "smart-open": {"depends": [], "file_name": "smart_open-7.0.4-py3-none-any.whl", "imports": ["smart_open"], "install_dir": "site", "name": "smart_open", "package_type": "package", "sha256": "8217625b7117f7fe3bd295a6a41c5a5253b2471a0668e3d28453fd5375920690", "shared_library": false, "unvendored_tests": false, "version": "7.0.4"}, "sortedcontainers": {"depends": [], "file_name": "sortedcontainers-2.4.0-py2.py3-none-any.whl", "imports": ["sortedcontainers"], "install_dir": "site", "name": "sortedcontainers", "package_type": "package", "sha256": "56194229f40e8f5d813b68ce87595483b408ead2c4552439265069545b20d753", "shared_library": false, "unvendored_tests": false, "version": "2.4.0"}, "soupsieve": {"depends": [], "file_name": "soupsieve-2.5-py3-none-any.whl", "imports": ["soupsieve"], "install_dir": "site", "name": "soupsieve", "package_type": "package", "sha256": "cc4fbcb87488b840a835be9f568435dba297c6914f1edfa32b3bbf74a8a348cf", "shared_library": false, "unvendored_tests": false, "version": "2.5"}, "sourmash": {"depends": ["screed", "cffi", "deprecation", "cachetools", "numpy", "matplotlib", "scipy", "sqlite3", "bitstring"], "file_name": "sourmash-4.8.8-py3-none-pyodide_2024_0_wasm32.whl", "imports": ["sourmash"], "install_dir": "site", "name": "sourmash", "package_type": "package", "sha256": "2f1a1b6906816867b95e61dea826e7a5cd9344380324a2a90bae8bb33753927d", "shared_library": false, "unvendored_tests": false, "version": "4.8.8"}, "sparseqr": {"depends": ["pycparser", "cffi", "numpy", "scipy", "suitesparse"], "file_name": "sparseqr-1.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["sparseqr"], "install_dir": "site", "name": "sparseqr", "package_type": "package", "sha256": "88acb940cfadf513b4d72c1ccb0adaf7001f0d7da9ca101d450e0321cef08116", "shared_library": false, "unvendored_tests": false, "version": "1.2"}, "sqlalchemy": {"depends": ["sqlite3", "typing-extensions"], "file_name": "SQLAlchemy-2.0.29-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["sqlalchemy"], "install_dir": "site", "name": "sqlalchemy", "package_type": "package", "sha256": "e3e485820b9782d2ce98bf4fdbaa6dee12314d3fd66751fd9ec6edb13065e75b", "shared_library": false, "unvendored_tests": true, "version": "2.0.29"}, "sqlalchemy-tests": {"depends": ["sqlalchemy"], "file_name": "sqlalchemy-tests.tar", "imports": [], "install_dir": "site", "name": "sqlalchemy-tests", "package_type": "package", "sha256": "a4335234473c85da12709a05d651940564acd344f62bf91cbf13cdac888f9604", "shared_library": false, "unvendored_tests": false, "version": "2.0.29"}, "sqlite3": {"depends": [], "file_name": "sqlite3-1.0.0.zip", "imports": ["sqlite3", "_sqlite3"], "install_dir": "stdlib", "name": "sqlite3", "package_type": "cpython_module", "sha256": "29586a9ec94786c6385d3ef46aa65f484bb3f9f61b1bdbd18a0aa396e551cd9c", "shared_library": true, "unvendored_tests": false, "version": "1.0.0"}, "ssl": {"depends": ["openssl"], "file_name": "ssl-1.0.0.zip", "imports": ["ssl", "_ssl"], "install_dir": "stdlib", "name": "ssl", "package_type": "cpython_module", "sha256": "c57eed8de854b8d1ba4911c98390e5a6810d2b88ce84e030598d9a23c98346bf", "shared_library": true, "unvendored_tests": false, "version": "1.0.0"}, "stack-data": {"depends": [], "file_name": "stack_data-0.6.3-py3-none-any.whl", "imports": ["stack_data"], "install_dir": "site", "name": "stack_data", "package_type": "package", "sha256": "2f71f5dd0878678c74d89dfafe6b6d343b4ad5a1036f444df8b0208b8dd2576a", "shared_library": false, "unvendored_tests": false, "version": "0.6.3"}, "statsmodels": {"depends": ["numpy", "scipy", "pandas", "patsy", "packaging"], "file_name": "statsmodels-0.14.2-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["statsmodels"], "install_dir": "site", "name": "statsmodels", "package_type": "package", "sha256": "663c63c8ec52595437705125b2312c334ade7d805278c4484a64db14d6c0e5cf", "shared_library": false, "unvendored_tests": true, "version": "0.14.2"}, "statsmodels-tests": {"depends": ["statsmodels"], "file_name": "statsmodels-tests.tar", "imports": [], "install_dir": "site", "name": "statsmodels-tests", "package_type": "package", "sha256": "6c90ea3d8b162c1557a557145bc8fcdf3437da3d5be74d710b0824b6f5ad49ec", "shared_library": false, "unvendored_tests": false, "version": "0.14.2"}, "strictyaml": {"depends": ["python-dateutil"], "file_name": "strictyaml-1.7.3-py3-none-any.whl", "imports": ["strictyaml"], "install_dir": "site", "name": "strictyaml", "package_type": "package", "sha256": "7e0306417f802210ada75ea0eb506d7a60b150d366f3144b7e8bc18f5ff75d29", "shared_library": false, "unvendored_tests": false, "version": "1.7.3"}, "suitesparse": {"depends": ["openblas"], "file_name": "suitesparse-5.11.0.zip", "imports": [], "install_dir": "dynlib", "name": "suitesparse", "package_type": "shared_library", "sha256": "c14ddfb6b7c3f5fdc86177b9f96cdceacdad3d970bc53a1b22be5c1dd244db8d", "shared_library": true, "unvendored_tests": false, "version": "5.11.0"}, "svgwrite": {"depends": [], "file_name": "svgwrite-1.4.3-py3-none-any.whl", "imports": ["svgwrite"], "install_dir": "site", "name": "svgwrite", "package_type": "package", "sha256": "2f708f13e85580beff1adb8e1b178ab83c3f41161aec6afbdcc418fc6297daa7", "shared_library": false, "unvendored_tests": false, "version": "1.4.3"}, "swiglpk": {"depends": [], "file_name": "swiglpk-5.0.10-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["swiglpk"], "install_dir": "site", "name": "swiglpk", "package_type": "package", "sha256": "47c0fb4a7692d01155b2b15af095ad3505a2ba2c5a5d31bf46197293533515d9", "shared_library": false, "unvendored_tests": false, "version": "5.0.10"}, "sympy": {"depends": ["mpmath"], "file_name": "sympy-1.12-py3-none-any.whl", "imports": ["isympy", "sympy"], "install_dir": "site", "name": "sympy", "package_type": "package", "sha256": "7a4792ab8d537fa71e1a0ee316e106132212e7ef1473fb6a31b325ad868f1802", "shared_library": false, "unvendored_tests": true, "version": "1.12"}, "sympy-tests": {"depends": ["sympy"], "file_name": "sympy-tests.tar", "imports": [], "install_dir": "site", "name": "sympy-tests", "package_type": "package", "sha256": "5a1619b61ee0615c73cafbf492c5a9875b4af5411264c3eca2201d346e86a093", "shared_library": false, "unvendored_tests": false, "version": "1.12"}, "tblib": {"depends": [], "file_name": "tblib-3.0.0-py3-none-any.whl", "imports": ["tblib"], "install_dir": "site", "name": "tblib", "package_type": "package", "sha256": "ad99c66810817b58e92c3be9ceb9ebc3762f51d7ecb6c06a2c8753a170c4e662", "shared_library": false, "unvendored_tests": false, "version": "3.0.0"}, "termcolor": {"depends": [], "file_name": "termcolor-2.4.0-py3-none-any.whl", "imports": ["termcolor"], "install_dir": "site", "name": "termcolor", "package_type": "package", "sha256": "f7eea924f89bf4f79713eb1253d6f3560247cee070e0c5531256e93d7d21e666", "shared_library": false, "unvendored_tests": false, "version": "2.4.0"}, "test": {"depends": [], "file_name": "test-1.0.0.zip", "imports": ["test"], "install_dir": "stdlib", "name": "test", "package_type": "cpython_module", "sha256": "0e1c1c9a3fab52f55d1ee52242962d3e83c4b25aa33aa4c0a9d3e628cedcb67c", "shared_library": true, "unvendored_tests": false, "version": "1.0.0"}, "texttable": {"depends": [], "file_name": "texttable-1.7.0-py2.py3-none-any.whl", "imports": ["texttable"], "install_dir": "site", "name": "texttable", "package_type": "package", "sha256": "c2c44fd89aff617e8211c9e531ee654de3af7dba3cc8edb8415129555e349199", "shared_library": false, "unvendored_tests": false, "version": "1.7.0"}, "threadpoolctl": {"depends": [], "file_name": "threadpoolctl-3.4.0-py3-none-any.whl", "imports": ["threadpoolctl"], "install_dir": "site", "name": "threadpoolctl", "package_type": "package", "sha256": "42744246ef195dcf1a4e1387ef93b52fdbbb1e838c970d8303a970c9cea886c5", "shared_library": false, "unvendored_tests": false, "version": "3.4.0"}, "tomli": {"depends": [], "file_name": "tomli-2.0.1-py3-none-any.whl", "imports": ["tomli"], "install_dir": "site", "name": "tomli", "package_type": "package", "sha256": "96c7e72ef2b9a75c5a5fe81e1e79f8c0dcb2f95cefe5c2b4406083e41bb922be", "shared_library": false, "unvendored_tests": false, "version": "2.0.1"}, "tomli-w": {"depends": [], "file_name": "tomli_w-1.0.0-py3-none-any.whl", "imports": ["tomli_w"], "install_dir": "site", "name": "tomli-w", "package_type": "package", "sha256": "bc217efbfd5909c997ee2ffcbbedce498b235f89d3f8bd97dc91bce38f8222f6", "shared_library": false, "unvendored_tests": false, "version": "1.0.0"}, "toolz": {"depends": [], "file_name": "toolz-0.12.1-py3-none-any.whl", "imports": ["tlz", "toolz"], "install_dir": "site", "name": "toolz", "package_type": "package", "sha256": "7fdec44c6bf2ec5e200f88d34616ffa37a612d59e79a3c7650e1651f43830ad1", "shared_library": false, "unvendored_tests": true, "version": "0.12.1"}, "toolz-tests": {"depends": ["toolz"], "file_name": "toolz-tests.tar", "imports": [], "install_dir": "site", "name": "toolz-tests", "package_type": "package", "sha256": "f8284c76aa221ae218fc32b9c8e79f2002d762d4b29fc418bf661ed1aaaebbbf", "shared_library": false, "unvendored_tests": false, "version": "0.12.1"}, "tqdm": {"depends": [], "file_name": "tqdm-4.66.2-py3-none-any.whl", "imports": ["tqdm"], "install_dir": "site", "name": "tqdm", "package_type": "package", "sha256": "a320470e3ebe4427ba9d5ebc55ba32e115602768a3c3839ffc3b6734cdc5c332", "shared_library": false, "unvendored_tests": false, "version": "4.66.2"}, "traitlets": {"depends": [], "file_name": "traitlets-5.14.3-py3-none-any.whl", "imports": ["traitlets"], "install_dir": "site", "name": "traitlets", "package_type": "package", "sha256": "eb436d3e1a6fc61c765112cb14ef88a97e17af1c1776530d152031d03d616625", "shared_library": false, "unvendored_tests": true, "version": "5.14.3"}, "traitlets-tests": {"depends": ["traitlets"], "file_name": "traitlets-tests.tar", "imports": [], "install_dir": "site", "name": "traitlets-tests", "package_type": "package", "sha256": "ede0f6fe9beada3195925d17172d7bd6007bb32704528658d858b696b204bb66", "shared_library": false, "unvendored_tests": false, "version": "5.14.3"}, "traits": {"depends": [], "file_name": "traits-6.4.3-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["traits"], "install_dir": "site", "name": "traits", "package_type": "package", "sha256": "2c44ca2179d175a1321158821ea4c1950a661ea4d68df0c7c00994ac2b8bb1f5", "shared_library": false, "unvendored_tests": true, "version": "6.4.3"}, "traits-tests": {"depends": ["traits"], "file_name": "traits-tests.tar", "imports": [], "install_dir": "site", "name": "traits-tests", "package_type": "package", "sha256": "281021428f44bc41d9b312740eb2c5081434d14be76ab56dc872011156b0ad01", "shared_library": false, "unvendored_tests": false, "version": "6.4.3"}, "tskit": {"depends": ["numpy", "svgwrite", "jsonschema", "rpds-py"], "file_name": "tskit-0.5.6-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["tskit"], "install_dir": "site", "name": "tskit", "package_type": "package", "sha256": "357a4e1bd6e3221f492300564910dea0745c796d4bba3327244e3bd766a900d7", "shared_library": false, "unvendored_tests": false, "version": "0.5.6"}, "typing-extensions": {"depends": [], "file_name": "typing_extensions-4.11.0-py3-none-any.whl", "imports": ["typing_extensions"], "install_dir": "site", "name": "typing-extensions", "package_type": "package", "sha256": "27413666e546c12f1fd74234fcd3ff44b3b11dd82b4becb2dfd697d199a01373", "shared_library": false, "unvendored_tests": false, "version": "4.11.0"}, "tzdata": {"depends": [], "file_name": "tzdata-2024.1-py2.py3-none-any.whl", "imports": ["tzdata"], "install_dir": "site", "name": "tzdata", "package_type": "package", "sha256": "7bcb4600ba97f6aae14e4bdd9d3f03f16e0b8136f9532a9af3f4c0e9d0403bea", "shared_library": false, "unvendored_tests": false, "version": "2024.1"}, "uncertainties": {"depends": ["future"], "file_name": "uncertainties-3.1.7-py2.py3-none-any.whl", "imports": ["uncertainties"], "install_dir": "site", "name": "uncertainties", "package_type": "package", "sha256": "7d976cd8383c7a5494588ef8c753aa3bc0cf58b85c13d35ef32a28258bc51dca", "shared_library": false, "unvendored_tests": true, "version": "3.1.7"}, "uncertainties-tests": {"depends": ["uncertainties"], "file_name": "uncertainties-tests.tar", "imports": [], "install_dir": "site", "name": "uncertainties-tests", "package_type": "package", "sha256": "31b8ae8b62bfac15cd420ddf7aecb67a11ea6c4911b43ac181763fb0c8c21279", "shared_library": false, "unvendored_tests": false, "version": "3.1.7"}, "unyt": {"depends": ["numpy", "packaging", "sympy"], "file_name": "unyt-3.0.2-py3-none-any.whl", "imports": ["unyt"], "install_dir": "site", "name": "unyt", "package_type": "package", "sha256": "0266261f277eb5ee959c6ddc12b912808ede8f4fc65550474fa52345072adca8", "shared_library": false, "unvendored_tests": true, "version": "3.0.2"}, "unyt-tests": {"depends": ["unyt"], "file_name": "unyt-tests.tar", "imports": [], "install_dir": "site", "name": "unyt-tests", "package_type": "package", "sha256": "dc62293a0d6b7548dea6cffce3fc6560c9efd62fd828038ef9e650a3352bc1fb", "shared_library": false, "unvendored_tests": false, "version": "3.0.2"}, "urllib3": {"depends": [], "file_name": "urllib3-2.2.1-py3-none-any.whl", "imports": ["urllib3"], "install_dir": "site", "name": "urllib3", "package_type": "package", "sha256": "db8c61a24d25a4902704fce3b4dfdd369141859cbeb5b8dd553e22634875f912", "shared_library": false, "unvendored_tests": false, "version": "2.2.1"}, "wcwidth": {"depends": [], "file_name": "wcwidth-0.2.13-py2.py3-none-any.whl", "imports": ["wcwidth"], "install_dir": "site", "name": "wcwidth", "package_type": "package", "sha256": "3384f09b43b9618624cb57500076aa54372a1c6160bf5069f6309c9f9e11a914", "shared_library": false, "unvendored_tests": false, "version": "0.2.13"}, "webencodings": {"depends": [], "file_name": "webencodings-0.5.1-py2.py3-none-any.whl", "imports": ["webencodings"], "install_dir": "site", "name": "webencodings", "package_type": "package", "sha256": "bd558d5be03feae048c02a9b5875f7b7dc5eac1e1c8b167fac3a9575bd13178c", "shared_library": false, "unvendored_tests": false, "version": "0.5.1"}, "wordcloud": {"depends": ["matplotlib"], "file_name": "wordcloud-1.9.3-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["wordcloud"], "install_dir": "site", "name": "wordcloud", "package_type": "package", "sha256": "e35767ded44ad7a2565633e60a011cdf63c9c40ce8755127c80ab12bd71c340b", "shared_library": false, "unvendored_tests": false, "version": "1.9.3"}, "wrapt": {"depends": [], "file_name": "wrapt-1.16.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["wrapt"], "install_dir": "site", "name": "wrapt", "package_type": "package", "sha256": "218ecd2503e989f9ff5e0c44b3270f3faed9e0c1cc989dd30e555840833aba7a", "shared_library": false, "unvendored_tests": false, "version": "1.16.0"}, "xarray": {"depends": ["numpy", "packaging", "pandas"], "file_name": "xarray-2024.3.0-py3-none-any.whl", "imports": ["xarray"], "install_dir": "site", "name": "xarray", "package_type": "package", "sha256": "811da0060896ccf83a10b575e3b73519c2ea6c4f1c53a7cc4b7ec7367f8297b2", "shared_library": false, "unvendored_tests": true, "version": "2024.3.0"}, "xarray-tests": {"depends": ["xarray"], "file_name": "xarray-tests.tar", "imports": [], "install_dir": "site", "name": "xarray-tests", "package_type": "package", "sha256": "ad0d1a70e79d8fd8059283d0e3cc56ddbf0282f64f252b84d9aa18bf183c8f5a", "shared_library": false, "unvendored_tests": false, "version": "2024.3.0"}, "xgboost": {"depends": ["numpy", "scipy", "setuptools"], "file_name": "xgboost-2.1.0.dev0-py3-none-pyodide_2024_0_wasm32.whl", "imports": ["xgboost"], "install_dir": "site", "name": "xgboost", "package_type": "package", "sha256": "4628575b6ceb2e986bbba4356c4864fb32bcbcc60786d3f26d5730012a48712b", "shared_library": false, "unvendored_tests": false, "version": "2.1.0.dev0"}, "xlrd": {"depends": [], "file_name": "xlrd-2.0.1-py2.py3-none-any.whl", "imports": ["xlrd"], "install_dir": "site", "name": "xlrd", "package_type": "package", "sha256": "5dce9ae1baf5f1bca143ae6bc664618ed3cd69d15b7be8d59901296dfd5f316d", "shared_library": false, "unvendored_tests": false, "version": "2.0.1"}, "xxhash": {"depends": [], "file_name": "xxhash-3.4.1-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["xxhash"], "install_dir": "site", "name": "xxhash", "package_type": "package", "sha256": "d7552b7b502fed9a814536ef4192f615c72293c03bc3b4f0abb8a910b025d22b", "shared_library": false, "unvendored_tests": false, "version": "3.4.1"}, "xyzservices": {"depends": [], "file_name": "xyzservices-2024.4.0-py3-none-any.whl", "imports": ["xyzservices"], "install_dir": "site", "name": "xyzservices", "package_type": "package", "sha256": "d83a3998a7ebdaf867608ed25ff2225d735ef165d7d637a53eae60a4019be77c", "shared_library": false, "unvendored_tests": true, "version": "2024.4.0"}, "xyzservices-tests": {"depends": ["xyzservices"], "file_name": "xyzservices-tests.tar", "imports": [], "install_dir": "site", "name": "xyzservices-tests", "package_type": "package", "sha256": "5478c9cfc55e20112bd9786d5170e03498715987db905508288723ce5bd015ba", "shared_library": false, "unvendored_tests": false, "version": "2024.4.0"}, "yarl": {"depends": ["multidict", "idna"], "file_name": "yarl-1.9.4-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["yarl"], "install_dir": "site", "name": "yarl", "package_type": "package", "sha256": "970a80d724db012e4832adbe5f7f25d3070dddec20eac496063ec4c615ba0f5b", "shared_library": false, "unvendored_tests": false, "version": "1.9.4"}, "yt": {"depends": ["ewah_bool_utils", "numpy", "matplotlib", "sympy", "setuptools", "packaging", "unyt", "cmyt", "colorspacious", "tqdm", "tomli", "tomli-w"], "file_name": "yt-4.3.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["yt"], "install_dir": "site", "name": "yt", "package_type": "package", "sha256": "8064f02ce5685790bfa3030a538a5808086ffdd84d4591cb2caaf6d959125564", "shared_library": false, "unvendored_tests": false, "version": "4.3.0"}, "zarr": {"depends": ["numpy", "asciitree", "numcodecs"], "file_name": "zarr-2.16.1-py3-none-any.whl", "imports": ["zarr"], "install_dir": "site", "name": "zarr", "package_type": "package", "sha256": "e8d9f3f619de2b6ab029535822e2ac7d215533c175b10dda90744cd0584edc4b", "shared_library": false, "unvendored_tests": true, "version": "2.16.1"}, "zarr-tests": {"depends": ["zarr"], "file_name": "zarr-tests.tar", "imports": [], "install_dir": "site", "name": "zarr-tests", "package_type": "package", "sha256": "559d33fa87d95d2da09bc0f84fa387ee6e9bf193eecb55b103825659065d804f", "shared_library": false, "unvendored_tests": false, "version": "2.16.1"}, "zengl": {"depends": [], "file_name": "zengl-2.4.1-cp311-abi3-pyodide_2024_0_wasm32.whl", "imports": ["zengl", "_zengl"], "install_dir": "site", "name": "zengl", "package_type": "package", "sha256": "84ca8691ab1b7ac6892fb546f1bb954a9cd97f46b39b25f33e3ee5c68ce44cb5", "shared_library": false, "unvendored_tests": false, "version": "2.4.1"}, "zstandard": {"depends": ["cffi"], "file_name": "zstandard-0.22.0-cp312-cp312-pyodide_2024_0_wasm32.whl", "imports": ["zstandard"], "install_dir": "site", "name": "zstandard", "package_type": "package", "sha256": "2132e35bc2775019e64bb1a88ae0f09c4bb46eb6516681ff7e0f27ef38fade54", "shared_library": false, "unvendored_tests": false, "version": "0.22.0"}}}
\ No newline at end of file
diff --git a/static/robots.txt b/static/robots.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1f53798bb4fe33c86020be7f10c44f29486fd190
--- /dev/null
+++ b/static/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
diff --git a/static/static/favicon.png b/static/static/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..2b2074780847581edf9cf2ed0d2e9ebd8ff08c56
Binary files /dev/null and b/static/static/favicon.png differ
diff --git a/static/static/splash-dark.png b/static/static/splash-dark.png
new file mode 100644
index 0000000000000000000000000000000000000000..202c03f8e46e189025b204b5bedc0552aec4ac82
Binary files /dev/null and b/static/static/splash-dark.png differ
diff --git a/static/static/splash.png b/static/static/splash.png
new file mode 100644
index 0000000000000000000000000000000000000000..389196ca6a364b9e4b7daa0fc13be463b914b251
Binary files /dev/null and b/static/static/splash.png differ
diff --git a/static/themes/rosepine-dawn.css b/static/themes/rosepine-dawn.css
new file mode 100644
index 0000000000000000000000000000000000000000..cc2b5de57d9f5a69d4295513f2dea889310549dd
--- /dev/null
+++ b/static/themes/rosepine-dawn.css
@@ -0,0 +1,140 @@
+.rose-pine-dawn * {
+	color: #575279 !important;
+	stroke: #d7827e !important;
+}
+
+.rose-pine-dawn .app > * {
+	background-color: #faf4ed !important;
+}
+
+.rose-pine-dawn #nav {
+	background-color: #fffaf3;
+}
+
+.rose-pine-dawn .py-2\.5.my-auto.flex.flex-col.justify-between.h-screen {
+	background: #f2e9e1;
+}
+
+.rose-pine-dawn .bg-white.dark\:bg-gray-800 {
+	background: #f2e9e1;
+}
+
+.rose-pine-dawn .w-4.h-4 {
+	fill: #ebbcba;
+}
+
+.rose-pine-dawn #chat-input {
+	background: #cecacd;
+	margin: 0.3rem;
+	padding: 0.5rem;
+}
+
+.rose-pine-dawn .bg-gradient-to-t.from-white.dark\:from-gray-800.from-40\%.pb-2 {
+	background: #f2e9e1 !important;
+	padding-top: 0.6rem;
+}
+
+.rose-pine-dawn
+	.text-white.bg-gray-100.dark\:text-gray-800.dark\:bg-gray-600.disabled.transition.rounded-lg.p-1.mr-0\.5.w-7.h-7.self-center {
+	background-color: #cecacd;
+	transition: background-color 0.2s ease-out linear;
+}
+
+.rose-pine-dawn
+	.bg-black.text-white.hover\:bg-gray-900.dark\:bg-white.dark\:text-black.dark\:hover\:bg-gray-100.transition.rounded-lg.p-1.mr-0\.5.w-7.h-7.self-center {
+	background-color: #286983;
+	transition: background-color 0.2s ease-out linear;
+}
+
+.rose-pine-dawn
+	.bg-black.text-white.hover\:bg-gray-900.dark\:bg-white.dark\:text-black.dark\:hover\:bg-gray-100.transition.rounded-lg.p-1.mr-0\.5.w-7.h-7.self-center
+	> * {
+	fill: #56949f !important;
+	transition: fill 0.2s ease-out linear;
+}
+
+.rose-pine-dawn
+	.w-full.flex.justify-between.rounded-md.px-3.py-2.hover\:bg-gray-900.bg-gray-900.transition.whitespace-nowrap.text-ellipsis {
+	background-color: #56526e;
+	font-weight: bold;
+}
+
+.rose-pine-dawn .hover\:bg-gray-900:hover {
+	--tw-bg-opacity: 1;
+	background-color: rgb(152 147 165 / var(--tw-bg-opacity));
+}
+
+.rose-pine-dawn .text-xs.text-gray-700.uppercase.bg-gray-50.dark\:bg-gray-700.dark\:text-gray-400 {
+	background-color: #403d52;
+}
+
+.rose-pine-dawn .scrollbar-hidden.relative.overflow-x-auto.whitespace-nowrap.svelte-3g4avz {
+	border-radius: 16px 16px 0 0;
+}
+
+.rose-pine-dawn .base.enter.svelte-ug60r4 {
+	background-color: #286983;
+}
+
+.rose-pine-dawn .message.svelte-1nauejd {
+	color: #e0def4 !important;
+}
+
+.rose-pine-dawn #dropdownDots {
+	background-color: #dfdad9;
+}
+
+.rose-pine-dawn .flex.py-2\.5.px-3\.5.w-full.hover\:bg-gray-800.transition:hover {
+	background: #cecacd;
+}
+
+.rose-pine-dawn #dropdownDots {
+	background-color: #dfdad9;
+}
+
+.rose-pine-dawn .flex.py-2\.5.px-3\.5.w-full.hover\:bg-gray-800.transition:hover {
+	background: #cecacd;
+}
+
+.rose-pine-dawn
+	.m-auto.rounded-xl.max-w-full.w-\[40rem\].mx-2.bg-gray-50.dark\:bg-gray-900.shadow-3xl {
+	background-color: #f2e9e1;
+}
+
+.rose-pine-dawn
+	.w-full.rounded.p-4.text-sm.dark\:text-gray-300.dark\:bg-gray-800.outline-none.resize-none {
+	background-color: #cecacd;
+}
+
+.rose-pine-dawn
+	.w-full.rounded.py-2.px-4.text-sm.dark\:text-gray-300.dark\:bg-gray-800.outline-none.svelte-1vx7r9s {
+	background-color: #cecacd;
+}
+
+.rose-pine-dawn
+	.px-2\.5.py-2\.5.min-w-fit.rounded-lg.flex-1.md\:flex-none.flex.text-right.transition.bg-gray-200.dark\:bg-gray-700 {
+	background-color: #dfdad9;
+}
+
+.rose-pine-dawn
+	.px-2\.5.py-2\.5.min-w-fit.rounded-lg.flex-1.md\:flex-none.flex.text-right.transition.hover\:bg-gray-300.dark\:hover\:bg-gray-800:hover {
+	background-color: #cecacd;
+}
+
+.rose-pine-dawn .px-4.py-2.bg-emerald-600.hover\:bg-emerald-700.text-gray-100.transition.rounded {
+	background-color: #56949f;
+}
+
+.rose-pine-dawn #chat-search > * {
+	background-color: #dfdad9 !important;
+}
+
+.rose-pine-dawn .svelte-1ee93ns {
+	--primary: #b4637a !important;
+	--secondary: #fffaf3 !important;
+}
+
+.rose-pine-dawn .svelte-11kvm4p {
+	--primary: #56949f !important;
+	--secondary: #fffaf3 !important;
+}
diff --git a/static/themes/rosepine.css b/static/themes/rosepine.css
new file mode 100644
index 0000000000000000000000000000000000000000..494fd29e06b8e89f322b775a7f6a8d39e8c8c90a
--- /dev/null
+++ b/static/themes/rosepine.css
@@ -0,0 +1,131 @@
+.rose-pine * {
+	color: #e0def4 !important;
+	stroke: #907aa9 !important;
+}
+
+.rose-pine .app > * {
+	background-color: #1f1d2e !important;
+}
+
+.rose-pine #nav {
+	background-color: #191724;
+}
+
+.rose-pine .py-2\.5.my-auto.flex.flex-col.justify-between.h-screen {
+	background: #191724;
+}
+
+.rose-pine .bg-white.dark\:bg-gray-800 {
+	background: #26233a;
+}
+
+.rose-pine .w-4.h-4 {
+	fill: #c4a7e7;
+}
+
+.rose-pine #chat-input {
+	background: #393552;
+	margin: 0.3rem;
+	padding: 0.5rem;
+}
+
+.rose-pine .bg-gradient-to-t.from-white.dark\:from-gray-800.from-40\%.pb-2 {
+	background: #26233a !important;
+	padding-top: 0.6rem;
+}
+
+.rose-pine
+	.text-white.bg-gray-100.dark\:text-gray-800.dark\:bg-gray-600.disabled.transition.rounded-lg.p-1.mr-0\.5.w-7.h-7.self-center {
+	background-color: #6e6a86;
+	transition: background-color 0.2s ease-out linear;
+}
+
+.rose-pine
+	.bg-black.text-white.hover\:bg-gray-900.dark\:bg-white.dark\:text-black.dark\:hover\:bg-gray-100.transition.rounded-lg.p-1.mr-0\.5.w-7.h-7.self-center {
+	background-color: #286983;
+	transition: background-color 0.2s ease-out linear;
+}
+
+.rose-pine
+	.bg-black.text-white.hover\:bg-gray-900.dark\:bg-white.dark\:text-black.dark\:hover\:bg-gray-100.transition.rounded-lg.p-1.mr-0\.5.w-7.h-7.self-center
+	> * {
+	fill: #9ccfd8 !important;
+	transition: fill 0.2s ease-out linear;
+}
+
+.rose-pine
+	.w-full.flex.justify-between.rounded-md.px-3.py-2.hover\:bg-gray-900.bg-gray-900.transition.whitespace-nowrap.text-ellipsis {
+	background-color: #56526e;
+	font-weight: bold;
+}
+
+.rose-pine .hover\:bg-gray-900:hover {
+	--tw-bg-opacity: 1;
+	background-color: rgb(57 53 82 / var(--tw-bg-opacity));
+}
+
+.rose-pine .text-xs.text-gray-700.uppercase.bg-gray-50.dark\:bg-gray-700.dark\:text-gray-400 {
+	background-color: #403d52;
+}
+
+.rose-pine .scrollbar-hidden.relative.overflow-x-auto.whitespace-nowrap.svelte-3g4avz {
+	border-radius: 16px 16px 0 0;
+}
+
+.rose-pine .base.enter.svelte-ug60r4 {
+	background-color: #393552;
+}
+
+.rose-pine .message.svelte-1nauejd {
+	color: #e0def4 !important;
+}
+
+.rose-pine #dropdownDots {
+	background-color: #403d52;
+}
+
+.rose-pine .flex.py-2\.5.px-3\.5.w-full.hover\:bg-gray-800.transition:hover {
+	background: #524f67;
+}
+
+.rose-pine .m-auto.rounded-xl.max-w-full.w-\[40rem\].mx-2.bg-gray-50.dark\:bg-gray-900.shadow-3xl {
+	background-color: #26233a;
+}
+
+.rose-pine
+	.w-full.rounded.p-4.text-sm.dark\:text-gray-300.dark\:bg-gray-800.outline-none.resize-none {
+	background-color: #524f67;
+}
+
+.rose-pine
+	.w-full.rounded.py-2.px-4.text-sm.dark\:text-gray-300.dark\:bg-gray-800.outline-none.svelte-1vx7r9s {
+	background-color: #524f67;
+}
+
+.rose-pine
+	.px-2\.5.py-2\.5.min-w-fit.rounded-lg.flex-1.md\:flex-none.flex.text-right.transition.bg-gray-200.dark\:bg-gray-700 {
+	background-color: #403d52;
+}
+
+.rose-pine
+	.px-2\.5.py-2\.5.min-w-fit.rounded-lg.flex-1.md\:flex-none.flex.text-right.transition.hover\:bg-gray-300.dark\:hover\:bg-gray-800:hover {
+	background-color: #524f67;
+}
+
+.rose-pine .px-4.py-2.bg-emerald-600.hover\:bg-emerald-700.text-gray-100.transition.rounded {
+	background-color: #31748f;
+}
+
+.rose-pine #chat-search > * {
+	background-color: #403d52 !important;
+}
+
+.rose-pine .svelte-1ee93ns {
+	--primary: #eb6f92 !important;
+	--secondary: #e0def4 !important;
+}
+
+.rose-pine .svelte-11kvm4p {
+	--primary: #9ccfd8 !important;
+	--secondary: #1f1d2e !important;
+}
diff --git a/static/user.png b/static/user.png
new file mode 100644
index 0000000000000000000000000000000000000000..2771e7286684e0935f952ef671dfe1f75f422256
Binary files /dev/null and b/static/user.png differ
diff --git a/svelte.config.js b/svelte.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..d756191d3068dbf6c7cf37f9a305357aec4de522
--- /dev/null
+++ b/svelte.config.js
@@ -0,0 +1,27 @@
+import adapter from '@sveltejs/adapter-static';
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+	// Consult https://kit.svelte.dev/docs/integrations#preprocessors
+	// for more information about preprocessors
+	preprocess: vitePreprocess(),
+	kit: {
+		// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
+		// If your environment is not supported or you settled on a specific environment, switch out the adapter.
+		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
+		adapter: adapter({
+			pages: 'build',
+			assets: 'build',
+			fallback: 'index.html'
+		})
+	},
+	onwarn: (warning, handler) => {
+		const { code, _ } = warning;
+		if (code === 'css-unused-selector') return;
+
+		handler(warning);
+	}
+};
+
+export default config;
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..69b84725e2570854d85ada9a321aa7c61cf15272
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,40 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+	darkMode: 'class',
+	content: ['./src/**/*.{html,js,svelte,ts}'],
+	theme: {
+		extend: {
+			colors: {
+				gray: {
+					50: '#f9f9f9',
+					100: '#ececec',
+					200: '#e3e3e3',
+					300: '#cdcdcd',
+					400: '#b4b4b4',
+					500: '#9b9b9b',
+					600: '#676767',
+					700: '#4e4e4e',
+					800: 'var(--color-gray-800, #333)',
+					850: 'var(--color-gray-850, #262626)',
+					900: 'var(--color-gray-900, #171717)',
+					950: 'var(--color-gray-950, #0d0d0d)'
+				}
+			},
+			typography: {
+				DEFAULT: {
+					css: {
+						pre: false,
+						code: false,
+						'pre code': false,
+						'code::before': false,
+						'code::after': false
+					}
+				}
+			},
+			padding: {
+				'safe-bottom': 'env(safe-area-inset-bottom)'
+			}
+		}
+	},
+	plugins: [require('@tailwindcss/typography')]
+};
diff --git a/test/test_files/image_gen/sd-empty.pt b/test/test_files/image_gen/sd-empty.pt
new file mode 100644
index 0000000000000000000000000000000000000000..c6ac59eb01fcb778290a85f12bdb7867de3dfdd1
Binary files /dev/null and b/test/test_files/image_gen/sd-empty.pt differ
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..6ae0c8c44d08a78140c9c62c1b0f745edd05e804
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,17 @@
+{
+	"extends": "./.svelte-kit/tsconfig.json",
+	"compilerOptions": {
+		"allowJs": true,
+		"checkJs": true,
+		"esModuleInterop": true,
+		"forceConsistentCasingInFileNames": true,
+		"resolveJsonModule": true,
+		"skipLibCheck": true,
+		"sourceMap": true,
+		"strict": true
+	}
+	// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
+	//
+	// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
+	// from the referenced tsconfig.json - TypeScript does not merge them in
+}
diff --git a/update_ollama_models.sh b/update_ollama_models.sh
new file mode 100644
index 0000000000000000000000000000000000000000..bde11b4b248694fcb1a224f7c193e75a88ee156f
--- /dev/null
+++ b/update_ollama_models.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+# update_llm.sh
+
+# Retrieves the list of LLMs installed in the Docker container
+llm_list=$(docker exec ollama ollama list | tail -n +2 | awk '{print $1}')
+
+# Loop over each LLM to update it
+for llm in $llm_list; do
+  docker exec ollama ollama pull $llm
+done
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000000000000000000000000000000000000..1b0e7c6ddea7bcc2f19e24771c524684f013d942
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,4659 @@
+version = 1
+requires-python = ">=3.11, <3.12.0"
+resolution-markers = [
+    "python_full_version < '3.12' and platform_system == 'Darwin'",
+    "python_full_version < '3.12' and platform_machine == 'aarch64' and platform_system == 'Linux'",
+    "(python_full_version < '3.12' and platform_machine != 'aarch64' and platform_system != 'Darwin') or (python_full_version < '3.12' and platform_system != 'Darwin' and platform_system != 'Linux')",
+    "python_full_version < '3.12' and platform_system == 'Darwin'",
+    "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_system == 'Darwin'",
+    "python_full_version >= '3.12.4' and python_full_version < '3.13' and platform_system == 'Darwin'",
+    "python_full_version >= '3.13' and platform_system == 'Darwin'",
+    "python_full_version < '3.12' and platform_machine == 'aarch64' and platform_system == 'Linux'",
+    "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux'",
+    "python_full_version >= '3.12.4' and python_full_version < '3.13' and platform_machine == 'aarch64' and platform_system == 'Linux'",
+    "python_full_version >= '3.13' and platform_machine == 'aarch64' and platform_system == 'Linux'",
+    "(python_full_version < '3.12' and platform_machine != 'aarch64' and platform_system != 'Darwin') or (python_full_version < '3.12' and platform_system != 'Darwin' and platform_system != 'Linux')",
+    "(python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and platform_system != 'Darwin') or (python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_system != 'Darwin' and platform_system != 'Linux')",
+    "(python_full_version >= '3.12.4' and python_full_version < '3.13' and platform_machine != 'aarch64' and platform_system != 'Darwin') or (python_full_version >= '3.12.4' and python_full_version < '3.13' and platform_system != 'Darwin' and platform_system != 'Linux')",
+    "(python_full_version >= '3.13' and platform_machine != 'aarch64' and platform_system != 'Darwin') or (python_full_version >= '3.13' and platform_system != 'Darwin' and platform_system != 'Linux')",
+]
+
+[[package]]
+name = "aiohappyeyeballs"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2d/f7/22bba300a16fd1cad99da1a23793fe43963ee326d012fdf852d0b4035955/aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2", size = 16786 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/18/b6/58ea188899950d759a837f9a58b2aee1d1a380ea4d6211ce9b1823748851/aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd", size = 12155 },
+]
+
+[[package]]
+name = "aiohttp"
+version = "3.10.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "aiohappyeyeballs" },
+    { name = "aiosignal" },
+    { name = "attrs" },
+    { name = "frozenlist" },
+    { name = "multidict" },
+    { name = "yarl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ca/28/ca549838018140b92a19001a8628578b0f2a3b38c16826212cc6f706e6d4/aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691", size = 7524360 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f1/90/54ccb1e4eadfb6c95deff695582453f6208584431d69bf572782e9ae542b/aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2", size = 586455 },
+    { url = "https://files.pythonhosted.org/packages/c3/7a/95e88c02756e7e718f054e1bb3ec6ad5d0ee4a2ca2bb1768c5844b3de30a/aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf", size = 397255 },
+    { url = "https://files.pythonhosted.org/packages/07/4f/767387b39990e1ee9aba8ce642abcc286d84d06e068dc167dab983898f18/aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e", size = 388973 },
+    { url = "https://files.pythonhosted.org/packages/61/46/0df41170a4d228c07b661b1ba9d87101d99a79339dc93b8b1183d8b20545/aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77", size = 1326126 },
+    { url = "https://files.pythonhosted.org/packages/af/20/da0d65e07ce49d79173fed41598f487a0a722e87cfbaa8bb7e078a7c1d39/aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061", size = 1364538 },
+    { url = "https://files.pythonhosted.org/packages/aa/20/b59728405114e57541ba9d5b96033e69d004e811ded299537f74237629ca/aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697", size = 1399896 },
+    { url = "https://files.pythonhosted.org/packages/2a/92/006690c31b830acbae09d2618e41308fe4c81c0679b3b33a3af859e0b7bf/aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7", size = 1312914 },
+    { url = "https://files.pythonhosted.org/packages/d4/71/1a253ca215b6c867adbd503f1e142117527ea8775e65962bc09b2fad1d2c/aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0", size = 1271301 },
+    { url = "https://files.pythonhosted.org/packages/0a/ab/5d1d9ff9ce6cce8fa54774d0364e64a0f3cd50e512ff09082ced8e5217a1/aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5", size = 1291652 },
+    { url = "https://files.pythonhosted.org/packages/75/5f/f90510ea954b9ae6e7a53d2995b97a3e5c181110fdcf469bc9238445871d/aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e", size = 1286289 },
+    { url = "https://files.pythonhosted.org/packages/be/9e/1f523414237798660921817c82b9225a363af436458caf584d2fa6a2eb4a/aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1", size = 1341848 },
+    { url = "https://files.pythonhosted.org/packages/f6/36/443472ddaa85d7d80321fda541d9535b23ecefe0bf5792cc3955ea635190/aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277", size = 1361619 },
+    { url = "https://files.pythonhosted.org/packages/19/f6/3ecbac0bc4359c7d7ba9e85c6b10f57e20edaf1f97751ad2f892db231ad0/aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058", size = 1320869 },
+    { url = "https://files.pythonhosted.org/packages/34/7e/ed74ffb36e3a0cdec1b05d8fbaa29cb532371d5a20058b3a8052fc90fe7c/aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072", size = 359271 },
+    { url = "https://files.pythonhosted.org/packages/98/1b/718901f04bc8c886a742be9e83babb7b93facabf7c475cc95e2b3ab80b4d/aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff", size = 379143 },
+    { url = "https://files.pythonhosted.org/packages/d9/1c/74f9dad4a2fc4107e73456896283d915937f48177b99867b63381fadac6e/aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487", size = 583468 },
+    { url = "https://files.pythonhosted.org/packages/12/29/68d090551f2b58ce76c2b436ced8dd2dfd32115d41299bf0b0c308a5483c/aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a", size = 394066 },
+    { url = "https://files.pythonhosted.org/packages/8f/f7/971f88b4cdcaaa4622925ba7d86de47b48ec02a9040a143514b382f78da4/aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d", size = 389098 },
+    { url = "https://files.pythonhosted.org/packages/f1/5a/fe3742efdce551667b2ddf1158b27c5b8eb1edc13d5e14e996e52e301025/aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75", size = 1332742 },
+    { url = "https://files.pythonhosted.org/packages/1a/52/a25c0334a1845eb4967dff279151b67ca32a948145a5812ed660ed900868/aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178", size = 1372134 },
+    { url = "https://files.pythonhosted.org/packages/96/3d/33c1d8efc2d8ec36bff9a8eca2df9fdf8a45269c6e24a88e74f2aa4f16bd/aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e", size = 1414413 },
+    { url = "https://files.pythonhosted.org/packages/64/74/0f1ddaa5f0caba1d946f0dd0c31f5744116e4a029beec454ec3726d3311f/aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f", size = 1328107 },
+    { url = "https://files.pythonhosted.org/packages/0a/32/c10118f0ad50e4093227234f71fd0abec6982c29367f65f32ee74ed652c4/aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73", size = 1280126 },
+    { url = "https://files.pythonhosted.org/packages/c6/c9/77e3d648d97c03a42acfe843d03e97be3c5ef1b4d9de52e5bd2d28eed8e7/aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf", size = 1292660 },
+    { url = "https://files.pythonhosted.org/packages/7e/5d/99c71f8e5c8b64295be421b4c42d472766b263a1fe32e91b64bf77005bf2/aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820", size = 1300988 },
+    { url = "https://files.pythonhosted.org/packages/8f/2c/76d2377dd947f52fbe8afb19b18a3b816d66c7966755c04030f93b1f7b2d/aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca", size = 1339268 },
+    { url = "https://files.pythonhosted.org/packages/fd/e6/3d9d935cc705d57ed524d82ec5d6b678a53ac1552720ae41282caa273584/aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91", size = 1366993 },
+    { url = "https://files.pythonhosted.org/packages/fe/c2/f7eed4d602f3f224600d03ab2e1a7734999b0901b1c49b94dc5891340433/aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6", size = 1329459 },
+    { url = "https://files.pythonhosted.org/packages/ce/8f/27f205b76531fc592abe29e1ad265a16bf934a9f609509c02d765e6a8055/aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12", size = 356968 },
+    { url = "https://files.pythonhosted.org/packages/39/8c/4f6c0b2b3629f6be6c81ab84d9d577590f74f01d4412bfc4067958eaa1e1/aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc", size = 377650 },
+    { url = "https://files.pythonhosted.org/packages/7b/b9/03b4327897a5b5d29338fa9b514f1c2f66a3e4fc88a4e40fad478739314d/aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092", size = 576994 },
+    { url = "https://files.pythonhosted.org/packages/67/1b/20c2e159cd07b8ed6dde71c2258233902fdf415b2fe6174bd2364ba63107/aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77", size = 390684 },
+    { url = "https://files.pythonhosted.org/packages/4d/6b/ff83b34f157e370431d8081c5d1741963f4fb12f9aaddb2cacbf50305225/aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385", size = 386176 },
+    { url = "https://files.pythonhosted.org/packages/4d/a1/6e92817eb657de287560962df4959b7ddd22859c4b23a0309e2d3de12538/aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972", size = 1303310 },
+    { url = "https://files.pythonhosted.org/packages/04/29/200518dc7a39c30ae6d5bc232d7207446536e93d3d9299b8e95db6e79c54/aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16", size = 1340445 },
+    { url = "https://files.pythonhosted.org/packages/8e/20/53f7bba841ba7b5bb5dea580fea01c65524879ba39cb917d08c845524717/aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6", size = 1385121 },
+    { url = "https://files.pythonhosted.org/packages/f1/b4/d99354ad614c48dd38fb1ee880a1a54bd9ab2c3bcad3013048d4a1797d3a/aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa", size = 1299669 },
+    { url = "https://files.pythonhosted.org/packages/51/39/ca1de675f2a5729c71c327e52ac6344e63f036bd37281686ae5c3fb13bfb/aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689", size = 1252638 },
+    { url = "https://files.pythonhosted.org/packages/54/cf/a3ae7ff43138422d477348e309ef8275779701bf305ff6054831ef98b782/aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57", size = 1266889 },
+    { url = "https://files.pythonhosted.org/packages/6e/7a/c6027ad70d9fb23cf254a26144de2723821dade1a624446aa22cd0b6d012/aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f", size = 1266249 },
+    { url = "https://files.pythonhosted.org/packages/64/fd/ed136d46bc2c7e3342fed24662b4827771d55ceb5a7687847aae977bfc17/aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599", size = 1311036 },
+    { url = "https://files.pythonhosted.org/packages/76/9a/43eeb0166f1119256d6f43468f900db1aed7fbe32069d2a71c82f987db4d/aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5", size = 1338756 },
+    { url = "https://files.pythonhosted.org/packages/d5/bc/d01ff0810b3f5e26896f76d44225ed78b088ddd33079b85cd1a23514318b/aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987", size = 1299976 },
+    { url = "https://files.pythonhosted.org/packages/3e/c9/50a297c4f7ab57a949f4add2d3eafe5f3e68bb42f739e933f8b32a092bda/aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04", size = 355609 },
+    { url = "https://files.pythonhosted.org/packages/65/28/aee9d04fb0b3b1f90622c338a08e54af5198e704a910e20947c473298fd0/aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022", size = 375697 },
+]
+
+[[package]]
+name = "aiosignal"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "frozenlist" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ae/67/0952ed97a9793b4958e5736f6d2b346b414a2cd63e82d05940032f45b32f/aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", size = 19422 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/76/ac/a7305707cb852b7e16ff80eaf5692309bde30e2b1100a1fcacdc8f731d97/aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17", size = 7617 },
+]
+
+[[package]]
+name = "alembic"
+version = "1.13.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "mako" },
+    { name = "sqlalchemy" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/e2/efa88e86029cada2da5941ec664d50d9a3b2a91f5066405c6f90e5016c16/alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef", size = 1206463 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/df/ed/c884465c33c25451e4a5cd4acad154c29e5341e3214e220e7f3478aa4b0d/alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953", size = 232990 },
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
+]
+
+[[package]]
+name = "anthropic"
+version = "0.34.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "anyio" },
+    { name = "distro" },
+    { name = "httpx" },
+    { name = "jiter" },
+    { name = "pydantic" },
+    { name = "sniffio" },
+    { name = "tokenizers" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/87/e2/98ff733ff75c1d371c029fb27eb9308f9c8e694749cea70382338a8e7e88/anthropic-0.34.1.tar.gz", hash = "sha256:69e822bd7a31ec11c2edb85f2147e8f0ee0cfd3288fea70b0ca8808b2f9bf91d", size = 901462 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a4/1c/1ce9edec76885badebacb4e31d42acffbdfd30dbaa839d5c378d57ac9aa9/anthropic-0.34.1-py3-none-any.whl", hash = "sha256:2fa26710809d0960d970f26cd0be3686437250a481edb95c33d837aa5fa24158", size = 891537 },
+]
+
+[[package]]
+name = "anyio"
+version = "4.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "idna" },
+    { name = "sniffio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e6/e3/c4c8d473d6780ef1853d630d581f70d655b4f8d7553c6997958c283039a2/anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94", size = 163930 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7b/a2/10639a79341f6c019dedc95bd48a4928eed9f1d1197f4c04f546fc7ae0ff/anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7", size = 86780 },
+]
+
+[[package]]
+name = "apscheduler"
+version = "3.10.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pytz" },
+    { name = "six" },
+    { name = "tzlocal" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5e/34/5dcb368cf89f93132d9a31bd3747962a9dc874480e54333b0c09fa7d56ac/APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a", size = 100832 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/13/b5/7af0cb920a476dccd612fbc9a21a3745fb29b1fcd74636078db8f7ba294c/APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661", size = 59303 },
+]
+
+[[package]]
+name = "argon2-cffi"
+version = "23.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "argon2-cffi-bindings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124 },
+]
+
+[[package]]
+name = "argon2-cffi-bindings"
+version = "21.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "cffi" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658 },
+    { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583 },
+    { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168 },
+    { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709 },
+    { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613 },
+    { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583 },
+    { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475 },
+    { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698 },
+    { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817 },
+    { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104 },
+]
+
+[[package]]
+name = "asgiref"
+version = "3.8.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 },
+]
+
+[[package]]
+name = "async-timeout"
+version = "4.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/87/d6/21b30a550dafea84b1b8eee21b5e23fa16d010ae006011221f33dcd8d7f8/async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", size = 8345 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a7/fa/e01228c2938de91d47b307831c62ab9e4001e747789d0b05baf779a6488c/async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028", size = 5721 },
+]
+
+[[package]]
+name = "attrs"
+version = "24.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 },
+]
+
+[[package]]
+name = "authlib"
+version = "1.3.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "cryptography" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/75/47dbab150ef6f9298e227a40c93c7fed5f3ffb67c9fb62cd49f66285e46e/authlib-1.3.2.tar.gz", hash = "sha256:4b16130117f9eb82aa6eec97f6dd4673c3f960ac0283ccdae2897ee4bc030ba2", size = 147313 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/df/4c/9aa0416a403d5cc80292cb030bcd2c918cce2755e314d8c1aa18656e1e12/Authlib-1.3.2-py2.py3-none-any.whl", hash = "sha256:ede026a95e9f5cdc2d4364a52103f5405e75aa156357e831ef2bfd0bc5094dfc", size = 225111 },
+]
+
+[[package]]
+name = "av"
+version = "12.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/00/f8/5adeeae0c42a7130933d168b8d84a21c98a32cb9fcf9222e2541ed0d9c7b/av-12.3.0.tar.gz", hash = "sha256:04b1892562aff3277efc79f32bd8f1d0cbb64ed011241cb3e96f9ad471816c22", size = 3833953 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5d/20/256fa4fc4ef9bb46fdc4be4662e13a30b0334487c955961f3816d94db04b/av-12.3.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:cc06a806419fddc7102150ffe353c7d96b99b95fd12864280c91c851603fd4cb", size = 24658122 },
+    { url = "https://files.pythonhosted.org/packages/5d/45/a9d0475539b4f49deb34f3da558de31cefc6be867d5c0603d575a8485069/av-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e2130ff622a574d3d5d6e88ac335efcdd98c375bb341f87d9fe540830a746f5", size = 19923068 },
+    { url = "https://files.pythonhosted.org/packages/af/27/1f2b3e46059c6618fd76ba12a96b49dc8515a426cd477032cd33f80505e8/av-12.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e8b9bd99f916ff4d1278654e94658e6ace7ca60f6321f254d09c8cd81d9095b", size = 32555100 },
+    { url = "https://files.pythonhosted.org/packages/28/34/759741d397a8bdbb8a359b8b5d49832a444b26c9a7f79c0f88be76a6b979/av-12.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e375d1d89a5c6edfd9f66701fdb6cc9161cc1ff99d15ff0bda21ee1ad38e9e0", size = 31936355 },
+    { url = "https://files.pythonhosted.org/packages/b4/6e/77426cb92117c941b0f759908bc83f34f259b11b353acb5de95972b452f7/av-12.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef9066fd8d86548e12d587cbfe7b852159e48ff3c732271c3032668d4bd7c599", size = 34416598 },
+    { url = "https://files.pythonhosted.org/packages/ff/d3/4b0fddcd54d0a88ee7e035f239ebb56ce139fac8e02ee0942c43746a66ff/av-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bfaa9864560e43d45d254ed95f70ab1aab24a2fa0cc35ac99eef362f1453bec0", size = 25975217 },
+    { url = "https://files.pythonhosted.org/packages/e4/c1/0636bccf5a1a2c935952614b9d34d8d8aae078c9773a60efb5376702f499/av-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5174e995772ebe33561980dca625f830aea8d39a4338728dedb41ae7dc2605af", size = 24669628 },
+    { url = "https://files.pythonhosted.org/packages/ef/7d/9126abdafe20fa73d2c19fd108450363253cfea283c350618cc1434f473c/av-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:028d8b40308536f740dace3efd0178eb96825b414897c9594fb74136532901cb", size = 19928928 },
+    { url = "https://files.pythonhosted.org/packages/27/75/c1b9e0aa4bd0d8b8311f366b6b38f6c6600d66baddfe2888accc7f76b1f5/av-12.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030791ecc6185776d832d19ce196f61daf3e17e591a9bb6fd181280e1754138", size = 32793461 },
+    { url = "https://files.pythonhosted.org/packages/5a/06/1364c445f8a8ab4870f0f5c4530b496257ae09de7fa01b6108525abea8b9/av-12.3.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3703a35481fda5798a27bf6208c1ec3b61c18931625771fb3c9fd870539c7d7", size = 32217647 },
+    { url = "https://files.pythonhosted.org/packages/27/08/220d5a1ae7e7830d66d041c71e607c1f5df2e3598b12fb406b0d7c2defa7/av-12.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32f3eef56b2df289db6105f9fe2ebc9a8134a8adbd62190daeb8e22c4ff47794", size = 34746451 },
+    { url = "https://files.pythonhosted.org/packages/96/67/9f1c444864d4f3e3773100b9ed20e670f80d5575b7a8fd53cca20de9d681/av-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:62d036ee8321d67190887012c3dbcd1ad83248603cc29ea75fbb75835b8d6e6e", size = 25977611 },
+]
+
+[[package]]
+name = "backoff"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 },
+]
+
+[[package]]
+name = "bcrypt"
+version = "4.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e4/7e/d95e7d96d4828e965891af92e43b52a4cd3395dc1c1ef4ee62748d0471d0/bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221", size = 24294 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a9/81/4e8f5bc0cd947e91fb720e1737371922854da47a94bc9630454e7b2845f8/bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb", size = 471568 },
+    { url = "https://files.pythonhosted.org/packages/05/d2/1be1e16aedec04bcf8d0156e01b987d16a2063d38e64c3f28030a3427d61/bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00", size = 277372 },
+    { url = "https://files.pythonhosted.org/packages/e3/96/7a654027638ad9b7589effb6db77eb63eba64319dfeaf9c0f4ca953e5f76/bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d", size = 273488 },
+    { url = "https://files.pythonhosted.org/packages/46/54/dc7b58abeb4a3d95bab653405935e27ba32f21b812d8ff38f271fb6f7f55/bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291", size = 277759 },
+    { url = "https://files.pythonhosted.org/packages/ac/be/da233c5f11fce3f8adec05e8e532b299b64833cc962f49331cdd0e614fa9/bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328", size = 273796 },
+    { url = "https://files.pythonhosted.org/packages/b0/b8/8b4add88d55a263cf1c6b8cf66c735280954a04223fcd2880120cc767ac3/bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7", size = 311082 },
+    { url = "https://files.pythonhosted.org/packages/7b/76/2aa660679abbdc7f8ee961552e4bb6415a81b303e55e9374533f22770203/bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399", size = 305912 },
+    { url = "https://files.pythonhosted.org/packages/00/03/2af7c45034aba6002d4f2b728c1a385676b4eab7d764410e34fd768009f2/bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060", size = 325185 },
+    { url = "https://files.pythonhosted.org/packages/dc/5d/6843443ce4ab3af40bddb6c7c085ed4a8418b3396f7a17e60e6d9888416c/bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7", size = 335188 },
+    { url = "https://files.pythonhosted.org/packages/cb/4c/ff8ca83d816052fba36def1d24e97d9a85739b9bbf428c0d0ecd296a07c8/bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458", size = 156481 },
+    { url = "https://files.pythonhosted.org/packages/65/f1/e09626c88a56cda488810fb29d5035f1662873777ed337880856b9d204ae/bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5", size = 151336 },
+    { url = "https://files.pythonhosted.org/packages/96/86/8c6a84daed4dd878fbab094400c9174c43d9b838ace077a2f8ee8bc3ae12/bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841", size = 472414 },
+    { url = "https://files.pythonhosted.org/packages/f6/05/e394515f4e23c17662e5aeb4d1859b11dc651be01a3bd03c2e919a155901/bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68", size = 277599 },
+    { url = "https://files.pythonhosted.org/packages/4b/3b/ad784eac415937c53da48983756105d267b91e56aa53ba8a1b2014b8d930/bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe", size = 273491 },
+    { url = "https://files.pythonhosted.org/packages/cc/14/b9ff8e0218bee95e517b70e91130effb4511e8827ac1ab00b4e30943a3f6/bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2", size = 277934 },
+    { url = "https://files.pythonhosted.org/packages/3e/d0/31938bb697600a04864246acde4918c4190a938f891fd11883eaaf41327a/bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c", size = 273804 },
+    { url = "https://files.pythonhosted.org/packages/e7/c3/dae866739989e3f04ae304e1201932571708cb292a28b2f1b93283e2dcd8/bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae", size = 311275 },
+    { url = "https://files.pythonhosted.org/packages/5d/2c/019bc2c63c6125ddf0483ee7d914a405860327767d437913942b476e9c9b/bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d", size = 306355 },
+    { url = "https://files.pythonhosted.org/packages/75/fe/9e137727f122bbe29771d56afbf4e0dbc85968caa8957806f86404a5bfe1/bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e", size = 325381 },
+    { url = "https://files.pythonhosted.org/packages/1a/d4/586b9c18a327561ea4cd336ff4586cca1a7aa0f5ee04e23a8a8bb9ca64f1/bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8", size = 335685 },
+    { url = "https://files.pythonhosted.org/packages/24/55/1a7127faf4576138bb278b91e9c75307490178979d69c8e6e273f74b974f/bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34", size = 155857 },
+    { url = "https://files.pythonhosted.org/packages/1c/2a/c74052e54162ec639266d91539cca7cbf3d1d3b8b36afbfeaee0ea6a1702/bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9", size = 151717 },
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.12.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "soupsieve" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 },
+]
+
+[[package]]
+name = "bidict"
+version = "0.23.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764 },
+]
+
+[[package]]
+name = "bitarray"
+version = "2.9.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/bf/25cf92a83e1fe4948d7935ae3c02f4c9ff9cb9c13e977fba8af11a5f642c/bitarray-2.9.2.tar.gz", hash = "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e", size = 132825 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/32/86/a02960105c0a40e7e4cbc74933f070ab476312d20aa25f6959f4abe5095b/bitarray-2.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fe71fd4b76380c2772f96f1e53a524da7063645d647a4fcd3b651bdd80ca0f2e", size = 177175 },
+    { url = "https://files.pythonhosted.org/packages/8d/fd/ce16db75d5470f9676089428500ef0d473f4e2ff1dcbcf1f856bcd351ea2/bitarray-2.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d527172919cdea1e13994a66d9708a80c3d33dedcf2f0548e4925e600fef3a3a", size = 128273 },
+    { url = "https://files.pythonhosted.org/packages/06/60/c1a419f8abd0c9d2641e3e570fc63ad3a87a63ef88a362900e3254f780bc/bitarray-2.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:052c5073bdcaa9dd10628d99d37a2f33ec09364b86dd1f6281e2d9f8d3db3060", size = 124642 },
+    { url = "https://files.pythonhosted.org/packages/9e/af/bba89e6f9499fb9dba04b701c8106a1dcc94b5913f35ed20f089da8bea99/bitarray-2.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e064caa55a6ed493aca1eda06f8b3f689778bc780a75e6ad7724642ba5dc62f7", size = 296283 },
+    { url = "https://files.pythonhosted.org/packages/8f/44/19e91ffc42a2ded4f1ee73f7923186cf1606cab1119b1d5df24c9cea763e/bitarray-2.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:508069a04f658210fdeee85a7a0ca84db4bcc110cbb1d21f692caa13210f24a7", size = 311309 },
+    { url = "https://files.pythonhosted.org/packages/9f/76/eedaa1fcb60af30536af70f6659e3a86dcfdce3e413b188f7864513e4923/bitarray-2.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4da73ebd537d75fa7bccfc2228fcaedea0803f21dd9d0bf0d3b67fef3c4af294", size = 314220 },
+    { url = "https://files.pythonhosted.org/packages/21/fa/9fb7266b28ce1c8778aaea650c75855640ea1ada91a80c47c90376994a59/bitarray-2.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cb378eaa65cd43098f11ff5d27e48ee3b956d2c00d2d6b5bfc2a09fe183be47", size = 296519 },
+    { url = "https://files.pythonhosted.org/packages/c4/ee/c9a92c123f9b0438498d0a8f9470439a43bdafbf042cbdceab7968e5bb1c/bitarray-2.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d14c790b91f6cbcd9b718f88ed737c78939980c69ac8c7f03dd7e60040c12951", size = 286784 },
+    { url = "https://files.pythonhosted.org/packages/81/7f/0d9c16a7e321f5cb1d6c634acf4f8620513634776ceeee6f8f732b992fae/bitarray-2.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eea9318293bc0ea6447e9ebfba600a62f3428bea7e9c6d42170ae4f481dbab3", size = 328319 },
+    { url = "https://files.pythonhosted.org/packages/2e/98/0730518cf071366633dd027e14e3fba91dc91dd8b4e60d6ae249cde3f53f/bitarray-2.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b76ffec27c7450b8a334f967366a9ebadaea66ee43f5b530c12861b1a991f503", size = 315614 },
+    { url = "https://files.pythonhosted.org/packages/4c/b3/8f198444cd2312d520e0372f933932fff68b5900eb2dbab91725924aa7a1/bitarray-2.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:76b76a07d4ee611405045c6950a1e24c4362b6b44808d4ad6eea75e0dbc59af4", size = 340143 },
+    { url = "https://files.pythonhosted.org/packages/b9/1a/78841fa855ea2dc13d8d61f231bd3a619732738d7d840b4b35d9d8f93e6e/bitarray-2.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c7d16beeaaab15b075990cd26963d6b5b22e8c5becd131781514a00b8bdd04bd", size = 345958 },
+    { url = "https://files.pythonhosted.org/packages/b7/9f/aac87cd45cc4d7b7e50dde590327bde525601088e710c8ba00e8743ddb2b/bitarray-2.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60df43e868a615c7e15117a1e1c2e5e11f48f6457280eba6ddf8fbefbec7da99", size = 327122 },
+    { url = "https://files.pythonhosted.org/packages/78/3e/5df523037f80cf95f99d0155ec921298f4fa2b1f7be10cb0c4daffb632da/bitarray-2.9.2-cp311-cp311-win32.whl", hash = "sha256:e788608ed7767b7b3bbde6d49058bccdf94df0de9ca75d13aa99020cc7e68095", size = 118627 },
+    { url = "https://files.pythonhosted.org/packages/9c/0e/af070131ed7a4fd15cadc84e018d3c6d3b58070513934462b48a5ff9eb1e/bitarray-2.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:a23397da092ef0a8cfe729571da64c2fc30ac18243caa82ac7c4f965087506ff", size = 126013 },
+    { url = "https://files.pythonhosted.org/packages/ef/7d/f489f2136cf5ea1af201be12d55bfc57b45731c4b7e3cc6e001efe62effb/bitarray-2.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436", size = 176636 },
+    { url = "https://files.pythonhosted.org/packages/b8/b6/3e64b19e45b52837e0c4ec1c220bf24dd70dcbcd27e073e07837973f8c16/bitarray-2.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd", size = 127916 },
+    { url = "https://files.pythonhosted.org/packages/23/c7/12b1e5cdd8678a6a47610a013fafdbe80d62226d49b73185619bd7361c53/bitarray-2.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b", size = 124475 },
+    { url = "https://files.pythonhosted.org/packages/77/e1/02dc3f03348808a77b26556a6c299f68dbbf0c4a27f340a69d574d2d2432/bitarray-2.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79", size = 299121 },
+    { url = "https://files.pythonhosted.org/packages/ea/f1/9cdb006c352b47b26532e7ee7798bd2dacf42774eb75e4a353a38a3ff2b3/bitarray-2.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a", size = 313486 },
+    { url = "https://files.pythonhosted.org/packages/bb/79/98bdfea0f390d313fa04546578a3eee3c3a6dbba94973246438ea8c880aa/bitarray-2.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff", size = 317527 },
+    { url = "https://files.pythonhosted.org/packages/58/02/21a2038ee856649f03738828e3bc6c4cd9bfd31125a250c6e30d378067ff/bitarray-2.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081", size = 299885 },
+    { url = "https://files.pythonhosted.org/packages/49/97/82c350256a22689fb50ed86af1a3a5509410332cfe55d644fd7ff5e46dbf/bitarray-2.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60", size = 289796 },
+    { url = "https://files.pythonhosted.org/packages/09/69/ca799419b576d015331b19b42c70086b1208ba363f744c8bb7dc31a0bb6a/bitarray-2.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8", size = 334912 },
+    { url = "https://files.pythonhosted.org/packages/fb/8a/dad9d48c72367f76b117957e3d718de07254667f33838d061856b238b576/bitarray-2.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7", size = 322790 },
+    { url = "https://files.pythonhosted.org/packages/b7/f1/a4f723153e6b4c56a90275a1d6ad04860bd72d9966196eb331cd18b50a12/bitarray-2.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95", size = 346212 },
+    { url = "https://files.pythonhosted.org/packages/7f/4f/55301544e90df8a7b798959bffa94e5694b29c964ed9e9e2a52d6cfac9c7/bitarray-2.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7", size = 353044 },
+    { url = "https://files.pythonhosted.org/packages/16/8b/363fdc21aff37ac99dba4ed41c0d535c37b416cd002351a9848173c738f1/bitarray-2.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d", size = 334380 },
+    { url = "https://files.pythonhosted.org/packages/66/58/f57a6420b363d2f0517d79b9af9fd608360ef174eb5d1d82cc5a26dbdbde/bitarray-2.9.2-cp312-cp312-win32.whl", hash = "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188", size = 118738 },
+    { url = "https://files.pythonhosted.org/packages/93/a9/b9462e2a3b4ee020c6caa1dccece324d6b0c7643b9f0a43d9ac8cd15c9d9/bitarray-2.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498", size = 126202 },
+]
+
+[[package]]
+name = "black"
+version = "24.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click" },
+    { name = "mypy-extensions" },
+    { name = "packaging" },
+    { name = "pathspec" },
+    { name = "platformdirs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/b0/46fb0d4e00372f4a86a6f8efa3cb193c9f64863615e39010b1477e010578/black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f", size = 644810 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/08/a6/0a3aa89de9c283556146dc6dbda20cd63a9c94160a6fbdebaf0918e4a3e1/black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1", size = 1615080 },
+    { url = "https://files.pythonhosted.org/packages/db/94/b803d810e14588bb297e565821a947c108390a079e21dbdcb9ab6956cd7a/black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af", size = 1438143 },
+    { url = "https://files.pythonhosted.org/packages/a5/b5/f485e1bbe31f768e2e5210f52ea3f432256201289fd1a3c0afda693776b0/black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4", size = 1738774 },
+    { url = "https://files.pythonhosted.org/packages/a8/69/a000fc3736f89d1bdc7f4a879f8aaf516fb03613bb51a0154070383d95d9/black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af", size = 1427503 },
+    { url = "https://files.pythonhosted.org/packages/a2/a8/05fb14195cfef32b7c8d4585a44b7499c2a4b205e1662c427b941ed87054/black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368", size = 1646132 },
+    { url = "https://files.pythonhosted.org/packages/41/77/8d9ce42673e5cb9988f6df73c1c5c1d4e9e788053cccd7f5fb14ef100982/black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed", size = 1448665 },
+    { url = "https://files.pythonhosted.org/packages/cc/94/eff1ddad2ce1d3cc26c162b3693043c6b6b575f538f602f26fe846dfdc75/black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018", size = 1762458 },
+    { url = "https://files.pythonhosted.org/packages/28/ea/18b8d86a9ca19a6942e4e16759b2fa5fc02bbc0eb33c1b866fcd387640ab/black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2", size = 1436109 },
+    { url = "https://files.pythonhosted.org/packages/27/1e/83fa8a787180e1632c3d831f7e58994d7aaf23a0961320d21e84f922f919/black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed", size = 206504 },
+]
+
+[[package]]
+name = "blinker"
+version = "1.8.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1e/57/a6a1721eff09598fb01f3c7cda070c1b6a0f12d63c83236edf79a440abcc/blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83", size = 23161 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/bb/2a/10164ed1f31196a2f7f3799368a821765c62851ead0e630ab52b8e14b4d0/blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01", size = 9456 },
+]
+
+[[package]]
+name = "boto3"
+version = "1.35.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "botocore" },
+    { name = "jmespath" },
+    { name = "s3transfer" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fd/a8/e825d84cdcf9136cedc89c1f317f80023179685f83469a6ad04b3b0709f4/boto3-1.35.0.tar.gz", hash = "sha256:bdc242e3ea81decc6ea551b04b2c122f088c29269d8e093b55862946aa0fcfc6", size = 108644 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/0e/da/d7d3a9ad530b6c05548bfabe6e163687a5039fbdfecbf07f7b5532fd077b/boto3-1.35.0-py3-none-any.whl", hash = "sha256:ada32dab854c46a877cf967b8a55ab1a7d356c3c87f1c8bd556d446ff03dfd95", size = 139142 },
+]
+
+[[package]]
+name = "botocore"
+version = "1.35.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "jmespath" },
+    { name = "python-dateutil" },
+    { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d0/2f/592f563d9eadc4ffe846c1f5e0c8747c9eb514a309c19b8362d1e455dd05/botocore-1.35.3.tar.gz", hash = "sha256:ff0c3189c0aa588c3aeda3f3e5e37925d64deaac6748310124307f978933e768", size = 12680057 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9e/5e/9b7d41e3256872159d62e5026519c448acbb94873b1bd950946e82e8e327/botocore-1.35.3-py3-none-any.whl", hash = "sha256:3ff54075e125304a8978e5d3f27ab96485f2deedb561d458b0567a0a921dc243", size = 12468636 },
+]
+
+[[package]]
+name = "build"
+version = "1.2.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "colorama", marker = "os_name == 'nt'" },
+    { name = "packaging" },
+    { name = "pyproject-hooks" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ce/9e/2d725d2f7729c6e79ca62aeb926492abbc06e25910dd30139d60a68bcb19/build-1.2.1.tar.gz", hash = "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d", size = 44781 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", hash = "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", size = 21911 },
+]
+
+[[package]]
+name = "cachetools"
+version = "5.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c3/38/a0f315319737ecf45b4319a8cd1f3a908e29d9277b46942263292115eee7/cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a", size = 27661 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a4/07/14f8ad37f2d12a5ce41206c21820d8cb6561b728e51fad4530dff0552a67/cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", size = 9524 },
+]
+
+[[package]]
+name = "certifi"
+version = "2024.7.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c2/02/a95f2b11e207f68bc64d7aae9666fed2e2b3f307748d5123dffb72a1bbea/certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", size = 164065 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1c/d5/c84e1a17bf61d4df64ca866a1c9a913874b4e9bdc131ec689a0ad013fb36/certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90", size = 162960 },
+]
+
+[[package]]
+name = "cffi"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pycparser" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1e/bf/82c351342972702867359cfeba5693927efe0a8dd568165490144f554b18/cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76", size = 516073 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/53/cc/9298fb6235522e00e47d78d6aa7f395332ef4e5f6fe124f9a03aa60600f7/cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720", size = 181912 },
+    { url = "https://files.pythonhosted.org/packages/e7/79/dc5334fbe60635d0846c56597a8d2af078a543ff22bc48d36551a0de62c2/cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9", size = 178297 },
+    { url = "https://files.pythonhosted.org/packages/39/d7/ef1b6b16b51ccbabaced90ff0d821c6c23567fc4b2e4a445aea25d3ceb92/cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb", size = 444909 },
+    { url = "https://files.pythonhosted.org/packages/29/b8/6e3c61885537d985c78ef7dd779b68109ba256263d74a2f615c40f44548d/cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424", size = 468854 },
+    { url = "https://files.pythonhosted.org/packages/0b/49/adad1228e19b931e523c2731e6984717d5f9e33a2f9971794ab42815b29b/cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d", size = 476890 },
+    { url = "https://files.pythonhosted.org/packages/76/54/c00f075c3e7fd14d9011713bcdb5b4f105ad044c5ad948db7b1a0a7e4e78/cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8", size = 459374 },
+    { url = "https://files.pythonhosted.org/packages/f3/b9/f163bb3fa4fbc636ee1f2a6a4598c096cdef279823ddfaa5734e556dd206/cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6", size = 466891 },
+    { url = "https://files.pythonhosted.org/packages/31/52/72bbc95f6d06ff2e88a6fa13786be4043e542cb24748e1351aba864cb0a7/cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91", size = 477658 },
+    { url = "https://files.pythonhosted.org/packages/67/20/d694811457eeae0c7663fa1a7ca201ce495533b646c1180d4ac25684c69c/cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8", size = 453890 },
+    { url = "https://files.pythonhosted.org/packages/dc/79/40cbf5739eb4f694833db5a27ce7f63e30a9b25b4a836c4f25fb7272aacc/cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb", size = 478254 },
+    { url = "https://files.pythonhosted.org/packages/e9/eb/2c384c385cca5cae67ca10ac4ef685277680b8c552b99aedecf4ea23ff7e/cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9", size = 171285 },
+    { url = "https://files.pythonhosted.org/packages/ca/42/74cb1e0f1b79cb64672f3cb46245b506239c1297a20c0d9c3aeb3929cb0c/cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0", size = 180842 },
+    { url = "https://files.pythonhosted.org/packages/1a/1f/7862231350cc959a3138889d2c8d33da7042b22e923457dfd4cd487d772a/cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc", size = 182826 },
+    { url = "https://files.pythonhosted.org/packages/8b/8c/26119bf8b79e05a1c39812064e1ee7981e1f8a5372205ba5698ea4dd958d/cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59", size = 178494 },
+    { url = "https://files.pythonhosted.org/packages/61/94/4882c47d3ad396d91f0eda6ef16d45be3d752a332663b7361933039ed66a/cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb", size = 454459 },
+    { url = "https://files.pythonhosted.org/packages/0f/7c/a6beb119ad515058c5ee1829742d96b25b2b9204ff920746f6e13bf574eb/cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195", size = 478502 },
+    { url = "https://files.pythonhosted.org/packages/61/8a/2575cd01a90e1eca96a30aec4b1ac101a6fae06c49d490ac2704fa9bc8ba/cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e", size = 485381 },
+    { url = "https://files.pythonhosted.org/packages/cd/66/85899f5a9f152db49646e0c77427173e1b77a1046de0191ab3b0b9a5e6e3/cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828", size = 470907 },
+    { url = "https://files.pythonhosted.org/packages/00/13/150924609bf377140abe6e934ce0a57f3fc48f1fd956ec1f578ce97a4624/cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150", size = 479074 },
+    { url = "https://files.pythonhosted.org/packages/17/fd/7d73d7110155c036303b0a6462c56250e9bc2f4119d7591d27417329b4d1/cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a", size = 484225 },
+    { url = "https://files.pythonhosted.org/packages/fc/83/8353e5c9b01bb46332dac3dfb18e6c597a04ceb085c19c814c2f78a8c0d0/cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885", size = 488388 },
+    { url = "https://files.pythonhosted.org/packages/73/0c/f9d5ca9a095b1fc88ef77d1f8b85d11151c374144e4606da33874e17b65b/cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492", size = 172096 },
+    { url = "https://files.pythonhosted.org/packages/72/21/8c5d285fe20a6e31d29325f1287bb0e55f7d93630a5a44cafdafb5922495/cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2", size = 181478 },
+    { url = "https://files.pythonhosted.org/packages/17/8f/581f2f3c3464d5f7cf87c2f7a5ba9acc6976253e02d73804240964243ec2/cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118", size = 182638 },
+    { url = "https://files.pythonhosted.org/packages/8d/1c/c9afa66684b7039f48018eb11b229b659dfb32b7a16b88251bac106dd1ff/cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7", size = 178453 },
+    { url = "https://files.pythonhosted.org/packages/cc/b6/1a134d479d3a5a1ff2fabbee551d1d3f1dd70f453e081b5f70d604aae4c0/cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377", size = 454441 },
+    { url = "https://files.pythonhosted.org/packages/b1/b4/e1569475d63aad8042b0935dbf62ae2a54d1e9142424e2b0e924d2d4a529/cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb", size = 478543 },
+    { url = "https://files.pythonhosted.org/packages/d2/40/a9ad03fbd64309dec5bb70bc803a9a6772602de0ee164d7b9a6ca5a89249/cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555", size = 485463 },
+    { url = "https://files.pythonhosted.org/packages/a6/1a/f10be60e006dd9242a24bcc2b1cd55c34c578380100f742d8c610f7a5d26/cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204", size = 470854 },
+    { url = "https://files.pythonhosted.org/packages/cc/b3/c035ed21aa3d39432bd749fe331ee90e4bc83ea2dbed1f71c4bc26c41084/cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f", size = 479096 },
+    { url = "https://files.pythonhosted.org/packages/00/cb/6f7edde01131de9382c89430b8e253b8c8754d66b63a62059663ceafeab2/cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0", size = 484013 },
+    { url = "https://files.pythonhosted.org/packages/b9/83/8e4e8c211ea940210d293e951bf06b1bfb90f2eeee590e9778e99b4a8676/cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4", size = 488119 },
+    { url = "https://files.pythonhosted.org/packages/5e/52/3f7cfbc4f444cb4f73ff17b28690d12436dde665f67d68f1e1687908ab6c/cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a", size = 172122 },
+    { url = "https://files.pythonhosted.org/packages/94/19/cf5baa07ee0f0e55eab7382459fbddaba0fdb0ba45973dd92556ae0d02db/cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7", size = 181504 },
+]
+
+[[package]]
+name = "chardet"
+version = "5.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.3.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 },
+    { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 },
+    { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 },
+    { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 },
+    { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 },
+    { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 },
+    { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 },
+    { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 },
+    { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 },
+    { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 },
+    { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 },
+    { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 },
+    { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 },
+    { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 },
+    { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 },
+    { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 },
+    { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 },
+    { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 },
+    { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 },
+    { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 },
+    { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 },
+    { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 },
+    { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 },
+    { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 },
+    { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 },
+    { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 },
+    { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 },
+    { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 },
+    { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 },
+    { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 },
+    { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 },
+]
+
+[[package]]
+name = "chroma-hnswlib"
+version = "0.7.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/73/09/10d57569e399ce9cbc5eee2134996581c957f63a9addfa6ca657daf006b8/chroma_hnswlib-0.7.6.tar.gz", hash = "sha256:4dce282543039681160259d29fcde6151cc9106c6461e0485f57cdccd83059b7", size = 32256 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f5/af/d15fdfed2a204c0f9467ad35084fbac894c755820b203e62f5dcba2d41f1/chroma_hnswlib-0.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81181d54a2b1e4727369486a631f977ffc53c5533d26e3d366dda243fb0998ca", size = 196911 },
+    { url = "https://files.pythonhosted.org/packages/0d/19/aa6f2139f1ff7ad23a690ebf2a511b2594ab359915d7979f76f3213e46c4/chroma_hnswlib-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b4ab4e11f1083dd0a11ee4f0e0b183ca9f0f2ed63ededba1935b13ce2b3606f", size = 185000 },
+    { url = "https://files.pythonhosted.org/packages/79/b1/1b269c750e985ec7d40b9bbe7d66d0a890e420525187786718e7f6b07913/chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53db45cd9173d95b4b0bdccb4dbff4c54a42b51420599c32267f3abbeb795170", size = 2377289 },
+    { url = "https://files.pythonhosted.org/packages/c7/2d/d5663e134436e5933bc63516a20b5edc08b4c1b1588b9680908a5f1afd04/chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c093f07a010b499c00a15bc9376036ee4800d335360570b14f7fe92badcdcf9", size = 2411755 },
+    { url = "https://files.pythonhosted.org/packages/3e/79/1bce519cf186112d6d5ce2985392a89528c6e1e9332d680bf752694a4cdf/chroma_hnswlib-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:0540b0ac96e47d0aa39e88ea4714358ae05d64bbe6bf33c52f316c664190a6a3", size = 151888 },
+    { url = "https://files.pythonhosted.org/packages/93/ac/782b8d72de1c57b64fdf5cb94711540db99a92768d93d973174c62d45eb8/chroma_hnswlib-0.7.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e87e9b616c281bfbe748d01705817c71211613c3b063021f7ed5e47173556cb7", size = 197804 },
+    { url = "https://files.pythonhosted.org/packages/32/4e/fd9ce0764228e9a98f6ff46af05e92804090b5557035968c5b4198bc7af9/chroma_hnswlib-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec5ca25bc7b66d2ecbf14502b5729cde25f70945d22f2aaf523c2d747ea68912", size = 185421 },
+    { url = "https://files.pythonhosted.org/packages/d9/3d/b59a8dedebd82545d873235ef2d06f95be244dfece7ee4a1a6044f080b18/chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305ae491de9d5f3c51e8bd52d84fdf2545a4a2bc7af49765cda286b7bb30b1d4", size = 2389672 },
+    { url = "https://files.pythonhosted.org/packages/74/1e/80a033ea4466338824974a34f418e7b034a7748bf906f56466f5caa434b0/chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:822ede968d25a2c88823ca078a58f92c9b5c4142e38c7c8b4c48178894a0a3c5", size = 2436986 },
+]
+
+[[package]]
+name = "chromadb"
+version = "0.5.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "bcrypt" },
+    { name = "build" },
+    { name = "chroma-hnswlib" },
+    { name = "fastapi" },
+    { name = "grpcio" },
+    { name = "httpx" },
+    { name = "importlib-resources" },
+    { name = "kubernetes" },
+    { name = "mmh3" },
+    { name = "numpy" },
+    { name = "onnxruntime" },
+    { name = "opentelemetry-api" },
+    { name = "opentelemetry-exporter-otlp-proto-grpc" },
+    { name = "opentelemetry-instrumentation-fastapi" },
+    { name = "opentelemetry-sdk" },
+    { name = "orjson" },
+    { name = "overrides" },
+    { name = "posthog" },
+    { name = "pydantic" },
+    { name = "pypika" },
+    { name = "pyyaml" },
+    { name = "tenacity" },
+    { name = "tokenizers" },
+    { name = "tqdm" },
+    { name = "typer" },
+    { name = "typing-extensions" },
+    { name = "uvicorn", extra = ["standard"] },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f9/31/7659067b51ac8b2ec355a100a77fb4d6d823aeb3ff111b6de87dfd18ace1/chromadb-0.5.5.tar.gz", hash = "sha256:84f4bfee320fb4912cbeb4d738f01690891e9894f0ba81f39ee02867102a1c4d", size = 31282293 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/80/4c/ee62b19a8daeed51e3c88c84b7da6047a74b786e598be3592b67a286d419/chromadb-0.5.5-py3-none-any.whl", hash = "sha256:2a5a4b84cb0fc32b380e193be68cdbadf3d9f77dbbf141649be9886e42910ddd", size = 584312 },
+]
+
+[[package]]
+name = "click"
+version = "8.1.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "colorama", marker = "platform_system == 'Windows'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
+]
+
+[[package]]
+name = "colbert-ai"
+version = "0.2.21"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "bitarray" },
+    { name = "datasets" },
+    { name = "flask" },
+    { name = "git-python" },
+    { name = "ninja" },
+    { name = "python-dotenv" },
+    { name = "scipy" },
+    { name = "tqdm" },
+    { name = "transformers" },
+    { name = "ujson" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bc/dc/7edb06e3bb01326610ecfdfc8e396c6867ba7de6e58cda2356a604419899/colbert_ai-0.2.21.tar.gz", hash = "sha256:a8d6fdb4e2272f2b08ed37f8e5096072160d8415d1e40585751898b77e625bab", size = 87978 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8d/9c/5d847be0f05e5266880fb1c183e642a6c34cd6a101c1d6219dfa74887543/colbert_ai-0.2.21-py3-none-any.whl", hash = "sha256:8c17e7be44e7f3989f2067f1176af4f65f4612d62850586657e8afb8314cb2a6", size = 116142 },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
+]
+
+[[package]]
+name = "colorclass"
+version = "2.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/1a/31ff00a33569a3b59d65bbdc445c73e12f92ad28195b7ace299f68b9af70/colorclass-2.2.2.tar.gz", hash = "sha256:6d4fe287766166a98ca7bc6f6312daf04a0481b1eda43e7173484051c0ab4366", size = 16709 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/30/b6/daf3e2976932da4ed3579cff7a30a53d22ea9323ee4f0d8e43be60454897/colorclass-2.2.2-py2.py3-none-any.whl", hash = "sha256:6f10c273a0ef7a1150b1120b6095cbdd68e5cf36dfd5d0fc957a2500bbf99a55", size = 18995 },
+]
+
+[[package]]
+name = "coloredlogs"
+version = "15.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "humanfriendly" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018 },
+]
+
+[[package]]
+name = "compressed-rtf"
+version = "1.0.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8e/ac/abb196bb0b42a239d605fe97c314c3312374749013a07da4e6e0408f223c/compressed_rtf-1.0.6.tar.gz", hash = "sha256:c1c827f1d124d24608981a56e8b8691eb1f2a69a78ccad6440e7d92fde1781dd", size = 5800 }
+
+[[package]]
+name = "cryptography"
+version = "43.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/69/ec/9fb9dcf4f91f0e5e76de597256c43eedefd8423aa59be95c70c4c3db426a/cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e", size = 686873 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d3/46/dcd2eb6840b9452e7fbc52720f3dc54a85eb41e68414733379e8f98e3275/cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74", size = 6239718 },
+    { url = "https://files.pythonhosted.org/packages/e8/23/b0713319edff1d8633775b354f8b34a476e4dd5f4cd4b91e488baec3361a/cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895", size = 3808466 },
+    { url = "https://files.pythonhosted.org/packages/77/9d/0b98c73cebfd41e4fb0439fe9ce08022e8d059f51caa7afc8934fc1edcd9/cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22", size = 3998060 },
+    { url = "https://files.pythonhosted.org/packages/ae/71/e073795d0d1624847f323481f7d84855f699172a632aa37646464b0e1712/cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47", size = 3792596 },
+    { url = "https://files.pythonhosted.org/packages/83/25/439a8ddd8058e7f898b7d27c36f94b66c8c8a2d60e1855d725845f4be0bc/cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf", size = 4008355 },
+    { url = "https://files.pythonhosted.org/packages/c7/a2/1607f1295eb2c30fcf2c07d7fd0c3772d21dcdb827de2b2730b02df0af51/cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55", size = 3899133 },
+    { url = "https://files.pythonhosted.org/packages/5e/64/f41f42ddc9c583737c9df0093affb92c61de7d5b0d299bf644524afe31c1/cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431", size = 4096946 },
+    { url = "https://files.pythonhosted.org/packages/cd/cd/d165adcf3e707d6a049d44ade6ca89973549bed0ab3686fa49efdeefea53/cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc", size = 2616826 },
+    { url = "https://files.pythonhosted.org/packages/f9/b7/38924229e84c41b0e88d7a5eed8a29d05a44364f85fbb9ddb3984b746fd2/cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778", size = 3078700 },
+    { url = "https://files.pythonhosted.org/packages/66/d7/397515233e6a861f921bd0365b162b38e0cc513fcf4f1bdd9cc7bc5a3384/cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66", size = 6242814 },
+    { url = "https://files.pythonhosted.org/packages/58/aa/99b2c00a4f54c60d210d6d1759c720ecf28305aa32d6fb1bb1853f415be6/cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5", size = 3809467 },
+    { url = "https://files.pythonhosted.org/packages/76/eb/ab783b47b3b9b55371b4361c7ec695144bde1a3343ff2b7a8c1d8fe617bb/cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e", size = 3998617 },
+    { url = "https://files.pythonhosted.org/packages/a3/62/62770f34290ebb1b6542bd3f13b3b102875b90aed4804e296f8d2a5ac6d7/cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5", size = 3794003 },
+    { url = "https://files.pythonhosted.org/packages/0f/6c/b42660b3075ff543065b2c1c5a3d9bedaadcff8ebce2ee981be2babc2934/cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f", size = 4008774 },
+    { url = "https://files.pythonhosted.org/packages/f7/74/028cea86db9315ba3f991e307adabf9f0aa15067011137c38b2fb2aa16eb/cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0", size = 3900098 },
+    { url = "https://files.pythonhosted.org/packages/bd/f6/e4387edb55563e2546028ba4c634522fe727693d3cdd9ec0ecacedc75411/cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b", size = 4096867 },
+    { url = "https://files.pythonhosted.org/packages/ce/61/55560405e75432bdd9f6cf72fa516cab623b83a3f6d230791bc8fc4afeee/cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf", size = 2616481 },
+    { url = "https://files.pythonhosted.org/packages/e6/3d/696e7a0f04555c58a2813d47aaa78cb5ba863c1f453c74a4f45ae772b054/cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709", size = 3081462 },
+]
+
+[[package]]
+name = "ctranslate2"
+version = "4.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+    { name = "pyyaml" },
+    { name = "setuptools" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9c/51/e54ea77b4fb549ef627f537d6282604688ac583894b5e1ada6c3c2ba4761/ctranslate2-4.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a49dc5d339e2f4ed016553db0d0e6cbd369742697c87c6cc0cc15a47c7c72d00", size = 14661629 },
+    { url = "https://files.pythonhosted.org/packages/40/c0/b4a5ccb767c2e9a536e22b4fd823dd83d85d4c705db9950eb60dadc6945c/ctranslate2-4.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:def98f6f8900470b2cec9408e5b0402af75f40f771391ebacd2b60666b8d75b9", size = 1287132 },
+    { url = "https://files.pythonhosted.org/packages/41/d7/3319f6bc0bad3adbf50deeb12d13c14bb943693348f53ca06fb2d1ad15f9/ctranslate2-4.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c02fcd5a7be93bf42a8adf81a9ac4f394e23bd639192907b2e11feae589971", size = 16089754 },
+    { url = "https://files.pythonhosted.org/packages/6e/00/46f8aa5d219ca0ff8edcb41255ba56f7e3746b31bc923123ad6d3b24227d/ctranslate2-4.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a06043910a7dee91ea03634be2cff2e1338a9f87bb51e062c03bae69e2c826b6", size = 192505863 },
+    { url = "https://files.pythonhosted.org/packages/ba/f1/f8080272e1969ec39347f0bfb0fca16da766f1c40b5729104c50d856b01c/ctranslate2-4.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:6f49834b63848f17dfdc1b2b8c632c31932ad69e130ce0f7b1e2505aa3923e6c", size = 174907166 },
+    { url = "https://files.pythonhosted.org/packages/dc/a0/e75f10f76b4af815614f4a8f4b26969a8dde56857dd3ab4d69436e40b4d5/ctranslate2-4.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fcf649d976070ddd33cdda00a7a60fde6f1fbe27d65d2c6141dd95153f965f01", size = 14668179 },
+    { url = "https://files.pythonhosted.org/packages/69/c4/18b386794b631f2a21afb5c8b79bc1ee7ab087eb7b854e686ae87ee00f88/ctranslate2-4.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f63f779f1d4518acdc694b1938887d4f28613ac2dfe507ccc2c0d56dd8c95b40", size = 1288547 },
+    { url = "https://files.pythonhosted.org/packages/af/93/a012f82fd3a31304e1cdbb269de2b67592cb29c216eb0dbf87577cf63f69/ctranslate2-4.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68301fbc5fb7daa609eb12ca6c2ed8aa29852c20f962532317762d1889e751d9", size = 16169466 },
+    { url = "https://files.pythonhosted.org/packages/4c/96/3065479817cd1854c51a27d46e771846c12d72b5bc6097d89af24a70b5e2/ctranslate2-4.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45c5b352783bd3806f0c9f5dcbfa49d89c0dde71cb7d1b1c527c525e85af3ded", size = 192678220 },
+    { url = "https://files.pythonhosted.org/packages/be/f4/e7105f5efc72165e964cd3f726ecd94de32ced068f4fb6c0c37ebaec5985/ctranslate2-4.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08626f115d5a39c56a666680735d6eebfc4d8a215288896d4d8afc14cfcdcffe", size = 174910451 },
+]
+
+[[package]]
+name = "dataclasses-json"
+version = "0.6.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "marshmallow" },
+    { name = "typing-inspect" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 },
+]
+
+[[package]]
+name = "datasets"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "aiohttp" },
+    { name = "dill" },
+    { name = "filelock" },
+    { name = "fsspec", extra = ["http"] },
+    { name = "huggingface-hub" },
+    { name = "multiprocess" },
+    { name = "numpy" },
+    { name = "packaging" },
+    { name = "pandas" },
+    { name = "pyarrow" },
+    { name = "pyyaml" },
+    { name = "requests" },
+    { name = "tqdm" },
+    { name = "xxhash" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c8/93/38c0d3c74b5932c4bb7596888c3d3b20adfd563e56552f437f8f57e6377b/datasets-3.0.0.tar.gz", hash = "sha256:592317eb137f0fc5aac068ff283ba13c3c66d10c9c034d44bc8aa584126cf3e2", size = 1876827 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a5/52/45dab187f03d48c765b94db0464f5c10431756e47ae4cc6a8029a7d57a36/datasets-3.0.0-py3-none-any.whl", hash = "sha256:c23fefb6c953dcb1cd5f6deb6c502729c733ef98791e0c3f2d80c7ca2d9a01dd", size = 474265 },
+]
+
+[[package]]
+name = "deepdiff"
+version = "7.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "ordered-set" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/70/10/6f4b0bd0627d542f63a24f38e29d77095dc63d5f45bc1a7b4a6ca8750fa9/deepdiff-7.0.1.tar.gz", hash = "sha256:260c16f052d4badbf60351b4f77e8390bee03a0b516246f6839bc813fb429ddf", size = 421718 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/18/e6/d27d37dc55dbf40cdbd665aa52844b065ac760c9a02a02265f97ea7a4256/deepdiff-7.0.1-py3-none-any.whl", hash = "sha256:447760081918216aa4fd4ca78a4b6a848b81307b2ea94c810255334b759e1dc3", size = 80825 },
+]
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 },
+]
+
+[[package]]
+name = "deprecated"
+version = "1.2.14"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "wrapt" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/92/14/1e41f504a246fc224d2ac264c227975427a85caf37c3979979edb9b1b232/Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3", size = 2974416 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/20/8d/778b7d51b981a96554f29136cd59ca7880bf58094338085bcf2a979a0e6a/Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c", size = 9561 },
+]
+
+[[package]]
+name = "dill"
+version = "0.3.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252 },
+]
+
+[[package]]
+name = "distro"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 },
+]
+
+[[package]]
+name = "dnspython"
+version = "2.6.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/37/7d/c871f55054e403fdfd6b8f65fd6d1c4e147ed100d3e9f9ba1fe695403939/dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc", size = 332727 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/87/a1/8c5287991ddb8d3e4662f71356d9656d91ab3a36618c3dd11b280df0d255/dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50", size = 307696 },
+]
+
+[[package]]
+name = "docker"
+version = "7.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pywin32", marker = "sys_platform == 'win32'" },
+    { name = "requests" },
+    { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774 },
+]
+
+[[package]]
+name = "docx2txt"
+version = "0.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/7d/60ee3f2b16d9bfdfa72e8599470a2c1a5b759cb113c6fe1006be28359327/docx2txt-0.8.tar.gz", hash = "sha256:2c06d98d7cfe2d3947e5760a57d924e3ff07745b379c8737723922e7009236e5", size = 2814 }
+
+[[package]]
+name = "duckduckgo-search"
+version = "6.2.13"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click" },
+    { name = "primp" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/55/2e/fe93c096b2b2bfcbd0a4f3a1de47d9a1c4b0dd4c7573d9f6c2b5c80ce6dc/duckduckgo_search-6.2.13.tar.gz", hash = "sha256:f89a9782f0f47d18c01a761c83453d0aef7a4335d1b6466fc47709652d5ca791", size = 33075 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e2/07/c0a6d0b1192790364ee9cf655c72c47ad13d68971570f19401f220cac50d/duckduckgo_search-6.2.13-py3-none-any.whl", hash = "sha256:a6618fb2744fa1d081b1bf2e47ef8051de993276a15c20f4e879a150726472de", size = 27468 },
+]
+
+[[package]]
+name = "easygui"
+version = "0.98.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cc/ad/e35f7a30272d322be09dc98592d2f55d27cc933a7fde8baccbbeb2bd9409/easygui-0.98.3.tar.gz", hash = "sha256:d653ff79ee1f42f63b5a090f2f98ce02335d86ad8963b3ce2661805cafe99a04", size = 85583 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8e/a7/b276ff776533b423710a285c8168b52551cb2ab0855443131fdc7fd8c16f/easygui-0.98.3-py2.py3-none-any.whl", hash = "sha256:33498710c68b5376b459cd3fc48d1d1f33822139eb3ed01defbc0528326da3ba", size = 92655 },
+]
+
+[[package]]
+name = "ebcdic"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/0d/2f/633031205333bee5f9f93761af8268746aa75f38754823aabb8570eb245b/ebcdic-1.1.1-py2.py3-none-any.whl", hash = "sha256:33b4cb729bc2d0bf46cc1847b0e5946897cb8d3f53520c5b9aa5fa98d7e735f1", size = 128537 },
+]
+
+[[package]]
+name = "ecdsa"
+version = "0.19.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5e/d0/ec8ac1de7accdcf18cfe468653ef00afd2f609faf67c423efbd02491051b/ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8", size = 197791 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/00/e7/ed3243b30d1bec41675b6394a1daae46349dc2b855cb83be846a5a918238/ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a", size = 149266 },
+]
+
+[[package]]
+name = "einops"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/79/ca/9f5dcb8bead39959454c3912266bedc4c315839cee0e0ca9f4328f4588c1/einops-0.8.0.tar.gz", hash = "sha256:63486517fed345712a8385c100cb279108d9d47e6ae59099b07657e983deae85", size = 58861 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/44/5a/f0b9ad6c0a9017e62d4735daaeb11ba3b6c009d69a26141b258cd37b5588/einops-0.8.0-py3-none-any.whl", hash = "sha256:9572fb63046264a862693b0a87088af3bdc8c068fde03de63453cbbde245465f", size = 43223 },
+]
+
+[[package]]
+name = "email-validator"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "dnspython" },
+    { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 },
+]
+
+[[package]]
+name = "emoji"
+version = "2.12.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1b/13/ae307086e7d761fb7fdb2e3439bdd4628b10b7b372639e33fac4e52cfbc2/emoji-2.12.1.tar.gz", hash = "sha256:4aa0488817691aa58d83764b6c209f8a27c0b3ab3f89d1b8dceca1a62e4973eb", size = 442019 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e6/90/20ad30babfa8f2b5ab46281d8e17bdfdbb3ac294cda14d525b9c2d958846/emoji-2.12.1-py3-none-any.whl", hash = "sha256:a00d62173bdadc2510967a381810101624a2f0986145b8da0cffa42e29430235", size = 431357 },
+]
+
+[[package]]
+name = "environs"
+version = "9.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "marshmallow" },
+    { name = "python-dotenv" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d4/e3/c3c6c76f3dbe3e019e9a451b35bf9f44690026a5bb1232f7b77097b72ff5/environs-9.5.0.tar.gz", hash = "sha256:a76307b36fbe856bdca7ee9161e6c466fd7fcffc297109a118c59b54e27e30c9", size = 20795 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ca/5e/f0f217dc393372681bfe05c50f06a212e78d0a3fee907a74ab451ec1dcdb/environs-9.5.0-py2.py3-none-any.whl", hash = "sha256:1e549569a3de49c05f856f40bce86979e7d5ffbbc4398e7f338574c220189124", size = 12548 },
+]
+
+[[package]]
+name = "et-xmlfile"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3d/5d/0413a31d184a20c763ad741cc7852a659bf15094c24840c5bdd1754765cd/et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c", size = 3218 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/96/c2/3dd434b0108730014f1b96fd286040dc3bcb70066346f7e01ec2ac95865f/et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada", size = 4688 },
+]
+
+[[package]]
+name = "extract-msg"
+version = "0.49.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "beautifulsoup4" },
+    { name = "compressed-rtf" },
+    { name = "ebcdic" },
+    { name = "olefile" },
+    { name = "red-black-tree-mod" },
+    { name = "rtfde" },
+    { name = "tzlocal" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8a/9b/0adbb58e3638eeec64a89e8571db2d6566c65504e943d4f1cef0b8732c77/extract_msg-0.49.0.tar.gz", hash = "sha256:cc700cdcc0cb6fcdbfa3b9e477201958cc28c584716d5c45fdd47261c1f2dfcc", size = 326634 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/28/f7/6c87de35c32e74978e223a647d752fab87b04a893f2fb11340be8c961a8c/extract_msg-0.49.0-py3-none-any.whl", hash = "sha256:6a1756164ef2d0c230bce1966d52155da8bd4bec9a6a1c3166cbdff8ffd9e0ba", size = 333646 },
+]
+
+[[package]]
+name = "fake-useragent"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/24/a1/1f662631ab153975fa8dbf09296324ecbaf53370dce922054e8de6b57370/fake-useragent-1.5.1.tar.gz", hash = "sha256:6387269f5a2196b5ba7ed8935852f75486845a1c95c50e72460e6a8e762f5c49", size = 22631 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e4/99/60d8cf1b26938c2e0a57e232f7f15641dfcd6f8deda454d73e4145910ff6/fake_useragent-1.5.1-py3-none-any.whl", hash = "sha256:57415096557c8a4e23b62a375c21c55af5fd4ba30549227f562d2c4f5b60e3b3", size = 17190 },
+]
+
+[[package]]
+name = "fastapi"
+version = "0.111.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "email-validator" },
+    { name = "fastapi-cli" },
+    { name = "httpx" },
+    { name = "jinja2" },
+    { name = "orjson" },
+    { name = "pydantic" },
+    { name = "python-multipart" },
+    { name = "starlette" },
+    { name = "typing-extensions" },
+    { name = "ujson" },
+    { name = "uvicorn", extra = ["standard"] },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0e/1f/f4a99e92c583780787e04b05aa9d8a8db9ec76d091d81545948a006f5b44/fastapi-0.111.0.tar.gz", hash = "sha256:b9db9dd147c91cb8b769f7183535773d8741dd46f9dc6676cd82eab510228cd7", size = 288414 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e6/33/de41e554e5a187d583906e10d53bfae5fd6c07e98cbf4fe5262bd37e739a/fastapi-0.111.0-py3-none-any.whl", hash = "sha256:97ecbf994be0bcbdadedf88c3150252bed7b2087075ac99735403b1b76cc8fc0", size = 91993 },
+]
+
+[[package]]
+name = "fastapi-cli"
+version = "0.0.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "typer" },
+    { name = "uvicorn", extra = ["standard"] },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c5/f8/1ad5ce32d029aeb9117e9a5a9b3e314a8477525d60c12a9b7730a3c186ec/fastapi_cli-0.0.5.tar.gz", hash = "sha256:d30e1239c6f46fcb95e606f02cdda59a1e2fa778a54b64686b3ff27f6211ff9f", size = 15571 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/24/ea/4b5011012ac925fe2f83b19d0e09cee9d324141ec7bf5e78bb2817f96513/fastapi_cli-0.0.5-py3-none-any.whl", hash = "sha256:e94d847524648c748a5350673546bbf9bcaeb086b33c24f2e82e021436866a46", size = 9489 },
+]
+
+[[package]]
+name = "faster-whisper"
+version = "1.0.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "av" },
+    { name = "ctranslate2" },
+    { name = "huggingface-hub" },
+    { name = "onnxruntime" },
+    { name = "tokenizers" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1e/f2/77437ee937233d6e8259e3df511a4662cd7e833dabeaaddbfc929d2a3ed5/faster-whisper-1.0.3.tar.gz", hash = "sha256:1a145db86450b56aaa623c8df7d4ef86e8a1159900f60533e2890e98e8453a17", size = 1980019 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7f/00/4742b1cd3afd23d0ff9b7e72ec40b2c398988332a5578115728fd83415d1/faster_whisper-1.0.3-py3-none-any.whl", hash = "sha256:364d0e378ab232ed26f39656e5c98548b38045224e206b20f7d8c90e2745b9d3", size = 1974982 },
+]
+
+[[package]]
+name = "filelock"
+version = "3.15.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/08/dd/49e06f09b6645156550fb9aee9cc1e59aba7efbc972d665a1bd6ae0435d4/filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb", size = 18007 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7", size = 16159 },
+]
+
+[[package]]
+name = "filetype"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 },
+]
+
+[[package]]
+name = "flask"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "blinker" },
+    { name = "click" },
+    { name = "itsdangerous" },
+    { name = "jinja2" },
+    { name = "werkzeug" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/41/e1/d104c83026f8d35dfd2c261df7d64738341067526406b40190bc063e829a/flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842", size = 676315 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3", size = 101735 },
+]
+
+[[package]]
+name = "flask-cors"
+version = "5.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "flask" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4f/d0/d9e52b154e603b0faccc0b7c2ad36a764d8755ef4036acbf1582a67fb86b/flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef", size = 30954 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/56/07/1afa0514c876282bebc1c9aee83c6bb98fe6415cf57b88d9b06e7e29bf9c/Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc", size = 14463 },
+]
+
+[[package]]
+name = "flatbuffers"
+version = "24.3.25"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a9/74/2df95ef84b214d2bee0886d572775a6f38793f5ca6d7630c3239c91104ac/flatbuffers-24.3.25.tar.gz", hash = "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4", size = 22139 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/41/f0/7e988a019bc54b2dbd0ad4182ef2d53488bb02e58694cd79d61369e85900/flatbuffers-24.3.25-py2.py3-none-any.whl", hash = "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812", size = 26784 },
+]
+
+[[package]]
+name = "fonttools"
+version = "4.53.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c6/cb/cd80a0da995adde8ade6044a8744aee0da5efea01301cadf770f7fbe7dcc/fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4", size = 3452797 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8b/6a/206391c869ab22d1374e2575cad7cab36b93b9e3d37f48f4696eed2c6e9e/fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1", size = 2762654 },
+    { url = "https://files.pythonhosted.org/packages/f5/7e/4060d88dbfaf446e1c9f0fe9cf13dba36ba47c4da85ce5c1df084ce47e7d/fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923", size = 2247865 },
+    { url = "https://files.pythonhosted.org/packages/e1/67/fff766817e17d67208f8a1e72de15066149485acb5e4ff0816b11fd5fca3/fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719", size = 4873046 },
+    { url = "https://files.pythonhosted.org/packages/a4/22/0a0ad59d9367997fd74a00ad2e88d10559122e09f105e94d34c155aecc0a/fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3", size = 4920859 },
+    { url = "https://files.pythonhosted.org/packages/0b/c4/b4e2f1699a5e2244373a6e8175f862f49f377b444adc6c7b1fe1f5b3d04d/fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb", size = 4885904 },
+    { url = "https://files.pythonhosted.org/packages/64/e7/b9a07c386adf8ad0348163fbcaab74daed6ef18ddb3f49b61b5c19900aeb/fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2", size = 5054708 },
+    { url = "https://files.pythonhosted.org/packages/e9/53/2a79462ae38d7943e63290209c04fef89677c67b29cb329cdc549c18d4d5/fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88", size = 2158885 },
+    { url = "https://files.pythonhosted.org/packages/c8/e1/059700c154bd7170d1c37061239836d2e51ff608f47075450f06dd3c292a/fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02", size = 2205133 },
+    { url = "https://files.pythonhosted.org/packages/87/63/8271f50f3e7bff8b78e03914c4c2893f2f21bd4db2975c60d11ecfbdd174/fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58", size = 2756146 },
+    { url = "https://files.pythonhosted.org/packages/dd/bd/cb8fd2dddd68089c112bf42a88afe188b8ace73f94406539857dcc9347a6/fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8", size = 2244990 },
+    { url = "https://files.pythonhosted.org/packages/ae/71/2b9761e25697bdaf3dfe8269541bd4324f3eb0e4cc13f71d7f90cd272394/fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60", size = 4787604 },
+    { url = "https://files.pythonhosted.org/packages/db/2b/5779cfd48625e013c2dfcf0c246474d5b1f5d061a5f1e476037bf9fff3a3/fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f", size = 4871141 },
+    { url = "https://files.pythonhosted.org/packages/b8/3d/ac3cec35a503bf789d03e9d155a220c9e574f4f1573f00a3bea55695d535/fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2", size = 4764714 },
+    { url = "https://files.pythonhosted.org/packages/ac/9f/27135ac0328e22cca1ba23ee6a1a1f971c13e9f0387adc5598d4635c501d/fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f", size = 5023568 },
+    { url = "https://files.pythonhosted.org/packages/04/40/44d6a94e52e91fe104f9ca95944466af34828992cbc66b666f541de137f1/fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670", size = 2147572 },
+    { url = "https://files.pythonhosted.org/packages/6d/9a/b695930e1b4e6929cc60e294489421632a05c105ac8c56ee63ef56a47872/fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab", size = 2193313 },
+    { url = "https://files.pythonhosted.org/packages/e4/b9/0394d67056d4ad36a3807b439571934b318f1df925593a95e9ec0516b1a7/fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d", size = 1090472 },
+]
+
+[[package]]
+name = "fpdf2"
+version = "2.7.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "defusedxml" },
+    { name = "fonttools" },
+    { name = "pillow" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9d/1f/2e23158904cc6783652e78757fa3f9d870729ff4dc2cf1caec51b8085f82/fpdf2-2.7.9.tar.gz", hash = "sha256:f364c0d816a5e364eeeda9761cf5c961bae8c946f080cf87fed7f38ab773b318", size = 231582 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a6/69/57b1a738e5a008f29b228af7f677b64ba8e5a3de831262955c94e036bf0b/fpdf2-2.7.9-py2.py3-none-any.whl", hash = "sha256:1f176aea4a1cc0fa1da5c799fce8f8bcfe2e936b9d2298a75514c9efc7528bfa", size = 206381 },
+]
+
+[[package]]
+name = "frozenlist"
+version = "1.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cf/3d/2102257e7acad73efc4a0c306ad3953f68c504c16982bbdfee3ad75d8085/frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", size = 37820 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/01/bc/8d33f2d84b9368da83e69e42720cff01c5e199b5a868ba4486189a4d8fa9/frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", size = 97060 },
+    { url = "https://files.pythonhosted.org/packages/af/b2/904500d6a162b98a70e510e743e7ea992241b4f9add2c8063bf666ca21df/frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", size = 55347 },
+    { url = "https://files.pythonhosted.org/packages/5b/9c/f12b69997d3891ddc0d7895999a00b0c6a67f66f79498c0e30f27876435d/frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", size = 53374 },
+    { url = "https://files.pythonhosted.org/packages/ac/6e/e0322317b7c600ba21dec224498c0c5959b2bce3865277a7c0badae340a9/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", size = 273288 },
+    { url = "https://files.pythonhosted.org/packages/a7/76/180ee1b021568dad5b35b7678616c24519af130ed3fa1e0f1ed4014e0f93/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", size = 284737 },
+    { url = "https://files.pythonhosted.org/packages/05/08/40159d706a6ed983c8aca51922a93fc69f3c27909e82c537dd4054032674/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", size = 280267 },
+    { url = "https://files.pythonhosted.org/packages/e0/18/9f09f84934c2b2aa37d539a322267939770362d5495f37783440ca9c1b74/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", size = 258778 },
+    { url = "https://files.pythonhosted.org/packages/b3/c9/0bc5ee7e1f5cc7358ab67da0b7dfe60fbd05c254cea5c6108e7d1ae28c63/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", size = 272276 },
+    { url = "https://files.pythonhosted.org/packages/12/5d/147556b73a53ad4df6da8bbb50715a66ac75c491fdedac3eca8b0b915345/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", size = 272424 },
+    { url = "https://files.pythonhosted.org/packages/83/61/2087bbf24070b66090c0af922685f1d0596c24bb3f3b5223625bdeaf03ca/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", size = 260881 },
+    { url = "https://files.pythonhosted.org/packages/a8/be/a235bc937dd803258a370fe21b5aa2dd3e7bfe0287a186a4bec30c6cccd6/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", size = 282327 },
+    { url = "https://files.pythonhosted.org/packages/5d/e7/b2469e71f082948066b9382c7b908c22552cc705b960363c390d2e23f587/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74", size = 281502 },
+    { url = "https://files.pythonhosted.org/packages/db/1b/6a5b970e55dffc1a7d0bb54f57b184b2a2a2ad0b7bca16a97ca26d73c5b5/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", size = 272292 },
+    { url = "https://files.pythonhosted.org/packages/1a/05/ebad68130e6b6eb9b287dacad08ea357c33849c74550c015b355b75cc714/frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", size = 44446 },
+    { url = "https://files.pythonhosted.org/packages/b3/21/c5aaffac47fd305d69df46cfbf118768cdf049a92ee6b0b5cb029d449dcf/frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", size = 50459 },
+    { url = "https://files.pythonhosted.org/packages/b4/db/4cf37556a735bcdb2582f2c3fa286aefde2322f92d3141e087b8aeb27177/frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", size = 93937 },
+    { url = "https://files.pythonhosted.org/packages/46/03/69eb64642ca8c05f30aa5931d6c55e50b43d0cd13256fdd01510a1f85221/frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", size = 53656 },
+    { url = "https://files.pythonhosted.org/packages/3f/ab/c543c13824a615955f57e082c8a5ee122d2d5368e80084f2834e6f4feced/frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", size = 51868 },
+    { url = "https://files.pythonhosted.org/packages/a9/b8/438cfd92be2a124da8259b13409224d9b19ef8f5a5b2507174fc7e7ea18f/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", size = 280652 },
+    { url = "https://files.pythonhosted.org/packages/54/72/716a955521b97a25d48315c6c3653f981041ce7a17ff79f701298195bca3/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", size = 286739 },
+    { url = "https://files.pythonhosted.org/packages/65/d8/934c08103637567084568e4d5b4219c1016c60b4d29353b1a5b3587827d6/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", size = 289447 },
+    { url = "https://files.pythonhosted.org/packages/70/bb/d3b98d83ec6ef88f9bd63d77104a305d68a146fd63a683569ea44c3085f6/frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", size = 265466 },
+    { url = "https://files.pythonhosted.org/packages/0b/f2/b8158a0f06faefec33f4dff6345a575c18095a44e52d4f10c678c137d0e0/frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", size = 281530 },
+    { url = "https://files.pythonhosted.org/packages/ea/a2/20882c251e61be653764038ece62029bfb34bd5b842724fff32a5b7a2894/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", size = 281295 },
+    { url = "https://files.pythonhosted.org/packages/4c/f9/8894c05dc927af2a09663bdf31914d4fb5501653f240a5bbaf1e88cab1d3/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", size = 268054 },
+    { url = "https://files.pythonhosted.org/packages/37/ff/a613e58452b60166507d731812f3be253eb1229808e59980f0405d1eafbf/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", size = 286904 },
+    { url = "https://files.pythonhosted.org/packages/cc/6e/0091d785187f4c2020d5245796d04213f2261ad097e0c1cf35c44317d517/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", size = 290754 },
+    { url = "https://files.pythonhosted.org/packages/a5/c2/e42ad54bae8bcffee22d1e12a8ee6c7717f7d5b5019261a8c861854f4776/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", size = 282602 },
+    { url = "https://files.pythonhosted.org/packages/b6/61/56bad8cb94f0357c4bc134acc30822e90e203b5cb8ff82179947de90c17f/frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", size = 44063 },
+    { url = "https://files.pythonhosted.org/packages/3e/dc/96647994a013bc72f3d453abab18340b7f5e222b7b7291e3697ca1fcfbd5/frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", size = 50452 },
+    { url = "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", size = 11552 },
+]
+
+[[package]]
+name = "fsspec"
+version = "2024.6.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/90/b6/eba5024a9889fcfff396db543a34bef0ab9d002278f163129f9f01005960/fsspec-2024.6.1.tar.gz", hash = "sha256:fad7d7e209dd4c1208e3bbfda706620e0da5142bebbd9c384afb95b07e798e49", size = 284584 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl", hash = "sha256:3cb443f8bcd2efb31295a5b9fdb02aee81d8452c80d28f97a6d0959e6cee101e", size = 177561 },
+]
+
+[package.optional-dependencies]
+http = [
+    { name = "aiohttp" },
+]
+
+[[package]]
+name = "git-python"
+version = "1.0.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "gitpython" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5b/3c/f0726fd577517ff8f09e6fe49f153ec9c595f1964c02c209c757d473780a/git-python-1.0.3.zip", hash = "sha256:a7f51d07c7a0b0a15cb4dfa78601196dd20624211153d07c092b811edb6e86fb", size = 4286 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8a/de/0cc6353a45cdb1e137cffac5383097b300cc578e2e1133eeb847e23a1394/git_python-1.0.3-py2.py3-none-any.whl", hash = "sha256:8820ce93786cd11a76d44c7153708588e8056213e4c512406ea3732871aa9ad6", size = 1888 },
+]
+
+[[package]]
+name = "gitdb"
+version = "4.0.11"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "smmap" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/0d/bbb5b5ee188dec84647a4664f3e11b06ade2bde568dbd489d9d64adef8ed/gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b", size = 394469 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fd/5b/8f0c4a5bb9fd491c277c21eff7ccae71b47d43c4446c9d0c6cff2fe8c2c4/gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4", size = 62721 },
+]
+
+[[package]]
+name = "gitpython"
+version = "3.1.43"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "gitdb" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b6/a1/106fd9fa2dd989b6fb36e5893961f82992cf676381707253e0bf93eb1662/GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c", size = 214149 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e9/bd/cc3a402a6439c15c3d4294333e13042b915bbeab54edc457c723931fed3f/GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff", size = 207337 },
+]
+
+[[package]]
+name = "google-ai-generativelanguage"
+version = "0.6.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "google-api-core", extra = ["grpc"] },
+    { name = "google-auth" },
+    { name = "proto-plus" },
+    { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/28/38/3d717e70a0020cde7bef8ec998ef3c605f208cc77ba93d22450e09f4d4ee/google-ai-generativelanguage-0.6.6.tar.gz", hash = "sha256:1739f035caeeeca5c28f887405eec8690f3372daf79fecf26454a97a4f1733a8", size = 758303 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/81/64/bac34c331e8103a0c32df8298823520787e6ff32ea736785c46b1322d62e/google_ai_generativelanguage-0.6.6-py3-none-any.whl", hash = "sha256:59297737931f073d55ce1268dcc6d95111ee62850349d2b6cde942b16a4fca5c", size = 718256 },
+]
+
+[[package]]
+name = "google-api-core"
+version = "2.19.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "google-auth" },
+    { name = "googleapis-common-protos" },
+    { name = "proto-plus" },
+    { name = "protobuf" },
+    { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c2/41/42a127bf163d9bf1f21540a3bf41c69b231b88707d8d753680b8878201a6/google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd", size = 148925 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/44/99/daa3541e8ecd7d8b7907b714ba92126097a976b5b3dbabdb5febdcf08554/google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125", size = 139384 },
+]
+
+[package.optional-dependencies]
+grpc = [
+    { name = "grpcio" },
+    { name = "grpcio-status" },
+]
+
+[[package]]
+name = "google-api-python-client"
+version = "2.142.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "google-api-core" },
+    { name = "google-auth" },
+    { name = "google-auth-httplib2" },
+    { name = "httplib2" },
+    { name = "uritemplate" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fe/d2/1dc1b95e9fef7bec1df1e04941d9556b6e384691d2ba520777c68429230f/google_api_python_client-2.142.0.tar.gz", hash = "sha256:a1101ac9e24356557ca22f07ff48b7f61fa5d4b4e7feeef3bda16e5dcb86350e", size = 11680160 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f0/41/957e29b392728ba94d1df652e2f3ce59022a6d7bb0164575c016ad204a52/google_api_python_client-2.142.0-py2.py3-none-any.whl", hash = "sha256:266799082bb8301f423ec204dffbffb470b502abbf29efd1f83e644d36eb5a8f", size = 12186205 },
+]
+
+[[package]]
+name = "google-auth"
+version = "2.34.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "cachetools" },
+    { name = "pyasn1-modules" },
+    { name = "rsa" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0f/ae/634dafb151366d91eb848a25846a780dbce4326906ef005d199723fbbca0/google_auth-2.34.0.tar.gz", hash = "sha256:8eb87396435c19b20d32abd2f984e31c191a15284af72eb922f10e5bde9c04cc", size = 257875 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/bb/fb/9af9e3f2996677bdda72734482934fe85a3abde174e5f0783ac2f817ba98/google_auth-2.34.0-py2.py3-none-any.whl", hash = "sha256:72fd4733b80b6d777dcde515628a9eb4a577339437012874ea286bca7261ee65", size = 200870 },
+]
+
+[[package]]
+name = "google-auth-httplib2"
+version = "0.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "google-auth" },
+    { name = "httplib2" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253 },
+]
+
+[[package]]
+name = "google-generativeai"
+version = "0.7.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "google-ai-generativelanguage" },
+    { name = "google-api-core" },
+    { name = "google-api-python-client" },
+    { name = "google-auth" },
+    { name = "protobuf" },
+    { name = "pydantic" },
+    { name = "tqdm" },
+    { name = "typing-extensions" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f7/92/766c19a6ccdc3e5272ecb831e131672e290d1ca4ec5cd6a4040a78454707/google_generativeai-0.7.2-py3-none-any.whl", hash = "sha256:3117d1ebc92ee77710d4bc25ab4763492fddce9b6332eb25d124cf5d8b78b339", size = 164212 },
+]
+
+[[package]]
+name = "googleapis-common-protos"
+version = "1.63.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/41723ae380fa9c561cbe7b61c4eef9091d5fe95486465ccfc84845877331/googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87", size = 112890 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/02/48/87422ff1bddcae677fb6f58c97f5cfc613304a5e8ce2c3662760199c0a84/googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945", size = 220001 },
+]
+
+[[package]]
+name = "greenlet"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/17/14/3bddb1298b9a6786539ac609ba4b7c9c0842e12aa73aaa4d8d73ec8f8185/greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491", size = 182013 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/6e/20/68a278a6f93fa36e21cfc3d7599399a8a831225644eb3b6b18755cd3d6fc/greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61", size = 271666 },
+    { url = "https://files.pythonhosted.org/packages/21/b4/90e06e07c78513ab03855768200bdb35c8e764e805b3f14fb488e56f82dc/greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559", size = 657689 },
+    { url = "https://files.pythonhosted.org/packages/f6/a2/0ed21078039072f9dc738bbf3af12b103a84106b1385ac4723841f846ce7/greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e", size = 673009 },
+    { url = "https://files.pythonhosted.org/packages/42/11/42ad6b1104c357826bbee7d7b9e4f24dbd9fde94899a03efb004aab62963/greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33", size = 667432 },
+    { url = "https://files.pythonhosted.org/packages/bb/6b/384dee7e0121cbd1757bdc1824a5ee28e43d8d4e3f99aa59521f629442fe/greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379", size = 667442 },
+    { url = "https://files.pythonhosted.org/packages/c6/1f/12d5a6cc26e8b483c2e7975f9c22e088ac735c0d8dcb8a8f72d31a4e5f04/greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22", size = 620032 },
+    { url = "https://files.pythonhosted.org/packages/c7/ec/85b647e59e0f137c7792a809156f413e38379cf7f3f2e1353c37f4be4026/greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3", size = 1154218 },
+    { url = "https://files.pythonhosted.org/packages/94/ed/1e5f4bca691a81700e5a88e86d6f0e538acb10188cd2cc17140e523255ef/greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d", size = 1180754 },
+    { url = "https://files.pythonhosted.org/packages/47/79/26d54d7d700ef65b689fc2665a40846d13e834da0486674a8d4f0f371a47/greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728", size = 292822 },
+    { url = "https://files.pythonhosted.org/packages/a2/2f/461615adc53ba81e99471303b15ac6b2a6daa8d2a0f7f77fd15605e16d5b/greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be", size = 273085 },
+    { url = "https://files.pythonhosted.org/packages/e9/55/2c3cfa3cdbb940cf7321fbcf544f0e9c74898eed43bf678abf416812d132/greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e", size = 660514 },
+    { url = "https://files.pythonhosted.org/packages/38/77/efb21ab402651896c74f24a172eb4d7479f9f53898bd5e56b9e20bb24ffd/greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676", size = 674295 },
+    { url = "https://files.pythonhosted.org/packages/74/3a/92f188ace0190f0066dca3636cf1b09481d0854c46e92ec5e29c7cefe5b1/greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc", size = 669395 },
+    { url = "https://files.pythonhosted.org/packages/63/0f/847ed02cdfce10f0e6e3425cd054296bddb11a17ef1b34681fa01a055187/greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230", size = 670455 },
+    { url = "https://files.pythonhosted.org/packages/bd/37/56b0da468a85e7704f3b2bc045015301bdf4be2184a44868c71f6dca6fe2/greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf", size = 625692 },
+    { url = "https://files.pythonhosted.org/packages/7c/68/b5f4084c0a252d7e9c0d95fc1cfc845d08622037adb74e05be3a49831186/greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305", size = 1152597 },
+    { url = "https://files.pythonhosted.org/packages/a4/fa/31e22345518adcd69d1d6ab5087a12c178aa7f3c51103f6d5d702199d243/greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6", size = 1181043 },
+    { url = "https://files.pythonhosted.org/packages/53/80/3d94d5999b4179d91bcc93745d1b0815b073d61be79dd546b840d17adb18/greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2", size = 293635 },
+]
+
+[[package]]
+name = "grpcio"
+version = "1.65.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6c/d8/1d8f1640649808db79b689d65b03556077d5504baad5ea64b167a5adedad/grpcio-1.65.5.tar.gz", hash = "sha256:ec6f219fb5d677a522b0deaf43cea6697b16f338cb68d009e30930c4aa0d2209", size = 12260860 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4a/01/8b8ccc747e0a7572631afe0ad86dafb5e252e045e1bf35d299b8baa80078/grpcio-1.65.5-cp311-cp311-linux_armv7l.whl", hash = "sha256:3207ae60d07e5282c134b6e02f9271a2cb523c6d7a346c6315211fe2bf8d61ed", size = 4886796 },
+    { url = "https://files.pythonhosted.org/packages/a5/8a/773ec9ea43b18b7ba0ed131dcb8ed13958b3a87762d966e3e6275961b968/grpcio-1.65.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2f80510f99f82d4eb825849c486df703f50652cea21c189eacc2b84f2bde764", size = 10443520 },
+    { url = "https://files.pythonhosted.org/packages/2d/5a/2fdd707bf1415ac6b18141893f38bfd0d1603903dbea0ed08cbf595e2ad6/grpcio-1.65.5-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a80e9a5e3f93c54f5eb82a3825ea1fc4965b2fa0026db2abfecb139a5c4ecdf1", size = 5398157 },
+    { url = "https://files.pythonhosted.org/packages/e5/32/e65418aa0e330e019988bf0fac3cb3fa4b93cf2c6668b11067116a3b3a6e/grpcio-1.65.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b2944390a496567de9e70418f3742b477d85d8ca065afa90432edc91b4bb8ad", size = 5983855 },
+    { url = "https://files.pythonhosted.org/packages/99/6a/d9021f91eacf30e6410f4d1809517a950f0e8b9ccd9f1a0afa05b0d1c07c/grpcio-1.65.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3655139d7be213c32c79ef6fb2367cae28e56ef68e39b1961c43214b457f257", size = 5660388 },
+    { url = "https://files.pythonhosted.org/packages/6b/61/f1281d7a3b2e42b3f924773b81e340340fc81fde51af4f9702be97af44af/grpcio-1.65.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05f02d68fc720e085f061b704ee653b181e6d5abfe315daef085719728d3d1fd", size = 6294373 },
+    { url = "https://files.pythonhosted.org/packages/ac/6e/9158f50864a26da29343269d4b8e3c79a934b081f05b62ee296205f6ba85/grpcio-1.65.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1c4caafe71aef4dabf53274bbf4affd6df651e9f80beedd6b8e08ff438ed3260", size = 5911981 },
+    { url = "https://files.pythonhosted.org/packages/ac/90/85e229a7d1ce432d42566f3340a16055c30066debbd31a58c53b971df7d6/grpcio-1.65.5-cp311-cp311-win32.whl", hash = "sha256:84c901cdec16a092099f251ef3360d15e29ef59772150fa261d94573612539b5", size = 3437955 },
+    { url = "https://files.pythonhosted.org/packages/f0/24/d87cfae0e8cc73532221892b414bf0ffc9fe8b84ac6bea5b6be5045963ae/grpcio-1.65.5-cp311-cp311-win_amd64.whl", hash = "sha256:11f8b16121768c1cb99d7dcb84e01510e60e6a206bf9123e134118802486f035", size = 4148265 },
+    { url = "https://files.pythonhosted.org/packages/75/00/079c2c8ca6b92f595937aacd256f8b0e94bb750a4d313b0249a8603aaa67/grpcio-1.65.5-cp312-cp312-linux_armv7l.whl", hash = "sha256:ee6ed64a27588a2c94e8fa84fe8f3b5c89427d4d69c37690903d428ec61ca7e4", size = 4825951 },
+    { url = "https://files.pythonhosted.org/packages/6b/8c/35a8d0f7135dbeb87f522ec743cd06423dba8eaec6c891f9466b0f46284c/grpcio-1.65.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:76991b7a6fb98630a3328839755181ce7c1aa2b1842aa085fd4198f0e5198960", size = 10408025 },
+    { url = "https://files.pythonhosted.org/packages/b8/e4/76fbbc753e8925e7779045e63896cf177f39b2b9419ee64910de4b055376/grpcio-1.65.5-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:89c00a18801b1ed9cc441e29b521c354725d4af38c127981f2c950c796a09b6e", size = 5339884 },
+    { url = "https://files.pythonhosted.org/packages/27/0d/b7be8fffa0c3d721c8ab3553745aa880849c60b2a2a7da0da625f4c4fbaf/grpcio-1.65.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:078038e150a897e5e402ed3d57f1d31ebf604cbed80f595bd281b5da40762a92", size = 5925035 },
+    { url = "https://files.pythonhosted.org/packages/44/3f/0151cd08156be773884c7a0f0e22daecdb71fe009a5decf3f1abe1e35159/grpcio-1.65.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97962720489ef31b5ad8a916e22bc31bba3664e063fb9f6702dce056d4aa61b", size = 5605652 },
+    { url = "https://files.pythonhosted.org/packages/87/a2/b2896199638a94a55d87aaaabe77a04eb59a4a73f420791ae3822ec7a714/grpcio-1.65.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b8270b15b99781461b244f5c81d5c2bc9696ab9189fb5ff86c841417fb3b39fe", size = 6238617 },
+    { url = "https://files.pythonhosted.org/packages/40/ce/340d07e224ac802011f8fcf1e03e73f45927ed7aa8be86ed4ea62ec99f94/grpcio-1.65.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8e5c4c15ac3fe1eb68e46bc51e66ad29be887479f231f8237cf8416058bf0cc1", size = 5855677 },
+    { url = "https://files.pythonhosted.org/packages/8c/22/9c8084ccd67d27f4119f3ca6fd74408a4fceaf73536eca9811594baf5b86/grpcio-1.65.5-cp312-cp312-win32.whl", hash = "sha256:f5b5970341359341d0e4c789da7568264b2a89cd976c05ea476036852b5950cd", size = 3420261 },
+    { url = "https://files.pythonhosted.org/packages/3e/72/bd4c82ac27dd84ab39e33ab445be1157fe9b1d659dda6355079b479053dd/grpcio-1.65.5-cp312-cp312-win_amd64.whl", hash = "sha256:238a625f391a1b9f5f069bdc5930f4fd71b74426bea52196fc7b83f51fa97d34", size = 4134287 },
+]
+
+[[package]]
+name = "grpcio-status"
+version = "1.62.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "googleapis-common-protos" },
+    { name = "grpcio" },
+    { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7c/d7/013ef01c5a1c2fd0932c27c904934162f69f41ca0f28396d3ffe4d386123/grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485", size = 13063 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/90/40/972271de05f9315c0d69f9f7ebbcadd83bc85322f538637d11bb8c67803d/grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8", size = 14448 },
+]
+
+[[package]]
+name = "h11"
+version = "0.14.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "certifi" },
+    { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/17/b0/5e8b8674f8d203335a62fdfcfa0d11ebe09e23613c3391033cbba35f7926/httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61", size = 83234 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/78/d4/e5d7e4f2174f8a4d63c8897d79eb8fe2503f7ecc03282fee1fa2719c2704/httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5", size = 77926 },
+]
+
+[[package]]
+name = "httplib2"
+version = "0.22.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pyparsing" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854 },
+]
+
+[[package]]
+name = "httptools"
+version = "0.6.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/67/1d/d77686502fced061b3ead1c35a2d70f6b281b5f723c4eff7a2277c04e4a2/httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a", size = 191228 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f5/d1/53283b96ed823d5e4d89ee9aa0f29df5a1bdf67f148e061549a595d534e4/httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1", size = 145855 },
+    { url = "https://files.pythonhosted.org/packages/80/dd/cebc9d4b1d4b70e9f3d40d1db0829a28d57ca139d0b04197713816a11996/httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0", size = 75604 },
+    { url = "https://files.pythonhosted.org/packages/76/7a/45c5a9a2e9d21f7381866eb7b6ead5a84d8fe7e54e35208eeb18320a29b4/httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc", size = 324784 },
+    { url = "https://files.pythonhosted.org/packages/59/23/047a89e66045232fb82c50ae57699e40f70e073ae5ccd53f54e532fbd2a2/httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2", size = 318547 },
+    { url = "https://files.pythonhosted.org/packages/82/f5/50708abc7965d7d93c0ee14a148ccc6d078a508f47fe9357c79d5360f252/httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837", size = 330211 },
+    { url = "https://files.pythonhosted.org/packages/e3/1e/9823ca7aab323c0e0e9dd82ce835a6e93b69f69aedffbc94d31e327f4283/httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d", size = 322174 },
+    { url = "https://files.pythonhosted.org/packages/14/e4/20d28dfe7f5b5603b6b04c33bb88662ad749de51f0c539a561f235f42666/httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3", size = 55434 },
+    { url = "https://files.pythonhosted.org/packages/60/13/b62e086b650752adf9094b7e62dab97f4cb7701005664544494b7956a51e/httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0", size = 146354 },
+    { url = "https://files.pythonhosted.org/packages/f8/5d/9ad32b79b6c24524087e78aa3f0a2dfcf58c11c90e090e4593b35def8a86/httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2", size = 75785 },
+    { url = "https://files.pythonhosted.org/packages/d0/a4/b503851c40f20bcbd453db24ed35d961f62abdae0dccc8f672cd5d350d87/httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90", size = 345396 },
+    { url = "https://files.pythonhosted.org/packages/a2/9a/aa406864f3108e06f7320425a528ff8267124dead1fd72a3e9da2067f893/httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503", size = 344741 },
+    { url = "https://files.pythonhosted.org/packages/cf/3a/3fd8dfb987c4247651baf2ac6f28e8e9f889d484ca1a41a9ad0f04dfe300/httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84", size = 345096 },
+    { url = "https://files.pythonhosted.org/packages/80/01/379f6466d8e2edb861c1f44ccac255ed1f8a0d4c5c666a1ceb34caad7555/httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb", size = 343535 },
+    { url = "https://files.pythonhosted.org/packages/d3/97/60860e9ee87a7d4712b98f7e1411730520053b9d69e9e42b0b9751809c17/httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949", size = 55660 },
+]
+
+[[package]]
+name = "httpx"
+version = "0.27.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "anyio" },
+    { name = "certifi" },
+    { name = "httpcore" },
+    { name = "idna" },
+    { name = "sniffio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/3da5bdf4408b8b2800061c339f240c1802f2e82d55e50bd39c5a881f47f0/httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5", size = 126413 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/41/7b/ddacf6dcebb42466abd03f368782142baa82e08fc0c1f8eaa05b4bae87d5/httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5", size = 75590 },
+]
+
+[[package]]
+name = "huggingface-hub"
+version = "0.24.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "filelock" },
+    { name = "fsspec" },
+    { name = "packaging" },
+    { name = "pyyaml" },
+    { name = "requests" },
+    { name = "tqdm" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/65/24/b98fce967b7d63700e5805b915012ba25bb538a81fcf11e97f3cc3f4f012/huggingface_hub-0.24.6.tar.gz", hash = "sha256:cc2579e761d070713eaa9c323e3debe39d5b464ae3a7261c39a9195b27bb8000", size = 349200 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b9/8f/d6718641c14d98a5848c6a24d2376028d292074ffade0702940a4b1dde76/huggingface_hub-0.24.6-py3-none-any.whl", hash = "sha256:a990f3232aa985fe749bc9474060cbad75e8b2f115f6665a9fda5b9c97818970", size = 417509 },
+]
+
+[[package]]
+name = "humanfriendly"
+version = "10.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pyreadline3", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794 },
+]
+
+[[package]]
+name = "idna"
+version = "3.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/21/ed/f86a79a07470cb07819390452f178b3bef1d375f2ec021ecfc709fc7cf07/idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", size = 189575 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0", size = 66836 },
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "zipp" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/20/ff/bd28f70283b9cca0cbf0c2a6082acbecd822d1962ae7b2a904861b9965f8/importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812", size = 52667 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/dc/ef/38766b2edb096260d9b1b6ad35adaa0bce3b0567abb452b21eb074af88c4/importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f", size = 24769 },
+]
+
+[[package]]
+name = "importlib-resources"
+version = "6.4.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0e/6a/3ac38d1458685a04fafa299dce821713a4f65e5ec30466bec07113f2f891/importlib_resources-6.4.4.tar.gz", hash = "sha256:20600c8b7361938dc0bb2d5ec0297802e575df486f5a544fa414da65e13721f7", size = 42721 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/db/2a/728c8ae66011600fac5731a7db030d23c42f1321fd9547654f0c3b2b32d7/importlib_resources-6.4.4-py3-none-any.whl", hash = "sha256:dda242603d1c9cd836c3368b1174ed74cb4049ecd209e7a1a0104620c18c5c11", size = 35608 },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
+]
+
+[[package]]
+name = "itsdangerous"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 },
+]
+
+[[package]]
+name = "jiter"
+version = "0.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/1a/aa64be757afc614484b370a4d9fc1747dc9237b37ce464f7f9d9ca2a3d38/jiter-0.5.0.tar.gz", hash = "sha256:1d916ba875bcab5c5f7d927df998c4cb694d27dceddf3392e58beaf10563368a", size = 158300 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/94/5f/3ac960ed598726aae46edea916e6df4df7ff6fe084bc60774b95cf3154e6/jiter-0.5.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4c8e1ed0ef31ad29cae5ea16b9e41529eb50a7fba70600008e9f8de6376d553", size = 284131 },
+    { url = "https://files.pythonhosted.org/packages/03/eb/2308fa5f5c14c97c4c7720fef9465f1fa0771826cddb4eec9866bdd88846/jiter-0.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6f16e21276074a12d8421692515b3fd6d2ea9c94fd0734c39a12960a20e85f3", size = 299310 },
+    { url = "https://files.pythonhosted.org/packages/3c/f6/dba34ca10b44715fa5302b8e8d2113f72eb00a9297ddf3fa0ae4fd22d1d1/jiter-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5280e68e7740c8c128d3ae5ab63335ce6d1fb6603d3b809637b11713487af9e6", size = 332282 },
+    { url = "https://files.pythonhosted.org/packages/69/f7/64e0a7439790ec47f7681adb3871c9d9c45fff771102490bbee5e92c00b7/jiter-0.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:583c57fc30cc1fec360e66323aadd7fc3edeec01289bfafc35d3b9dcb29495e4", size = 342370 },
+    { url = "https://files.pythonhosted.org/packages/55/31/1efbfff2ae8e4d919144c53db19b828049ad0622a670be3bbea94a86282c/jiter-0.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26351cc14507bdf466b5f99aba3df3143a59da75799bf64a53a3ad3155ecded9", size = 363591 },
+    { url = "https://files.pythonhosted.org/packages/30/c3/7ab2ca2276426a7398c6dfb651e38dbc81954c79a3bfbc36c514d8599499/jiter-0.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829df14d656b3fb87e50ae8b48253a8851c707da9f30d45aacab2aa2ba2d614", size = 378551 },
+    { url = "https://files.pythonhosted.org/packages/47/e7/5d88031cd743c62199b125181a591b1671df3ff2f6e102df85c58d8f7d31/jiter-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42a4bdcf7307b86cb863b2fb9bb55029b422d8f86276a50487982d99eed7c6e", size = 319152 },
+    { url = "https://files.pythonhosted.org/packages/4c/2d/09ea58e1adca9f0359f3d41ef44a1a18e59518d7c43a21f4ece9e72e28c0/jiter-0.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04d461ad0aebf696f8da13c99bc1b3e06f66ecf6cfd56254cc402f6385231c06", size = 357377 },
+    { url = "https://files.pythonhosted.org/packages/7d/2f/83ff1058cb56fc3ff73e0d3c6440703ddc9cdb7f759b00cfbde8228fc435/jiter-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6375923c5f19888c9226582a124b77b622f8fd0018b843c45eeb19d9701c403", size = 511091 },
+    { url = "https://files.pythonhosted.org/packages/ae/c9/4f85f97c9894382ab457382337aea0012711baaa17f2ed55c0ff25f3668a/jiter-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cec323a853c24fd0472517113768c92ae0be8f8c384ef4441d3632da8baa646", size = 492948 },
+    { url = "https://files.pythonhosted.org/packages/4d/f2/2e987e0eb465e064c5f52c2f29c8d955452e3b316746e326269263bfb1b7/jiter-0.5.0-cp311-none-win32.whl", hash = "sha256:aa1db0967130b5cab63dfe4d6ff547c88b2a394c3410db64744d491df7f069bb", size = 195183 },
+    { url = "https://files.pythonhosted.org/packages/ab/59/05d1c3203c349b37c4dd28b02b9b4e5915a7bcbd9319173b4548a67d2e93/jiter-0.5.0-cp311-none-win_amd64.whl", hash = "sha256:aa9d2b85b2ed7dc7697597dcfaac66e63c1b3028652f751c81c65a9f220899ae", size = 191032 },
+    { url = "https://files.pythonhosted.org/packages/aa/bd/c3950e2c478161e131bed8cb67c36aed418190e2a961a1c981e69954e54b/jiter-0.5.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9f664e7351604f91dcdd557603c57fc0d551bc65cc0a732fdacbf73ad335049a", size = 283511 },
+    { url = "https://files.pythonhosted.org/packages/80/1c/8ce58d8c37a589eeaaa5d07d131fd31043886f5e77ab50c00a66d869a361/jiter-0.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:044f2f1148b5248ad2c8c3afb43430dccf676c5a5834d2f5089a4e6c5bbd64df", size = 296974 },
+    { url = "https://files.pythonhosted.org/packages/4d/b8/6faeff9eed8952bed93a77ea1cffae7b946795b88eafd1a60e87a67b09e0/jiter-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:702e3520384c88b6e270c55c772d4bd6d7b150608dcc94dea87ceba1b6391248", size = 331897 },
+    { url = "https://files.pythonhosted.org/packages/4f/54/1d9a2209b46d39ce6f0cef3ad87c462f9c50312ab84585e6bd5541292b35/jiter-0.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:528d742dcde73fad9d63e8242c036ab4a84389a56e04efd854062b660f559544", size = 342962 },
+    { url = "https://files.pythonhosted.org/packages/2a/de/90360be7fc54b2b4c2dfe79eb4ed1f659fce9c96682e6a0be4bbe71371f7/jiter-0.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf80e5fe6ab582c82f0c3331df27a7e1565e2dcf06265afd5173d809cdbf9ba", size = 363844 },
+    { url = "https://files.pythonhosted.org/packages/ba/ad/ef32b173191b7a53ea8a6757b80723cba321f8469834825e8c71c96bde17/jiter-0.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:44dfc9ddfb9b51a5626568ef4e55ada462b7328996294fe4d36de02fce42721f", size = 378709 },
+    { url = "https://files.pythonhosted.org/packages/07/de/353ce53743c0defbbbd652e89c106a97dbbac4eb42c95920b74b5056b93a/jiter-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c451f7922992751a936b96c5f5b9bb9312243d9b754c34b33d0cb72c84669f4e", size = 319038 },
+    { url = "https://files.pythonhosted.org/packages/3f/92/42d47310bf9530b9dece9e2d7c6d51cf419af5586ededaf5e66622d160e2/jiter-0.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:308fce789a2f093dca1ff91ac391f11a9f99c35369117ad5a5c6c4903e1b3e3a", size = 357763 },
+    { url = "https://files.pythonhosted.org/packages/bd/8c/2bb76a9a84474d48fdd133d3445db8a4413da4e87c23879d917e000a9d87/jiter-0.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7f5ad4a7c6b0d90776fdefa294f662e8a86871e601309643de30bf94bb93a64e", size = 511031 },
+    { url = "https://files.pythonhosted.org/packages/33/4f/9f23d79c0795e0a8e56e7988e8785c2dcda27e0ed37977256d50c77c6a19/jiter-0.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea189db75f8eca08807d02ae27929e890c7d47599ce3d0a6a5d41f2419ecf338", size = 493042 },
+    { url = "https://files.pythonhosted.org/packages/df/67/8a4f975aa834b8aecdb6b131422390173928fd47f42f269dcc32034ab432/jiter-0.5.0-cp312-none-win32.whl", hash = "sha256:e3bbe3910c724b877846186c25fe3c802e105a2c1fc2b57d6688b9f8772026e4", size = 195405 },
+    { url = "https://files.pythonhosted.org/packages/15/81/296b1e25c43db67848728cdab34ac3eb5c5cbb4955ceb3f51ae60d4a5e3d/jiter-0.5.0-cp312-none-win_amd64.whl", hash = "sha256:a586832f70c3f1481732919215f36d41c59ca080fa27a65cf23d9490e75b2ef5", size = 189720 },
+]
+
+[[package]]
+name = "jmespath"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256 },
+]
+
+[[package]]
+name = "joblib"
+version = "1.4.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 },
+]
+
+[[package]]
+name = "jsonpatch"
+version = "1.33"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "jsonpointer" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898 },
+]
+
+[[package]]
+name = "jsonpath-python"
+version = "1.0.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b5/49/e582e50b0c54c1b47e714241c4a4767bf28758bf90212248aea8e1ce8516/jsonpath-python-1.0.6.tar.gz", hash = "sha256:dd5be4a72d8a2995c3f583cf82bf3cd1a9544cfdabf2d22595b67aff07349666", size = 18121 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/16/8a/d63959f4eff03893a00e6e63592e3a9f15b9266ed8e0275ab77f8c7dbc94/jsonpath_python-1.0.6-py3-none-any.whl", hash = "sha256:1e3b78df579f5efc23565293612decee04214609208a2335884b3ee3f786b575", size = 7552 },
+]
+
+[[package]]
+name = "jsonpointer"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 },
+]
+
+[[package]]
+name = "kubernetes"
+version = "30.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "certifi" },
+    { name = "google-auth" },
+    { name = "oauthlib" },
+    { name = "python-dateutil" },
+    { name = "pyyaml" },
+    { name = "requests" },
+    { name = "requests-oauthlib" },
+    { name = "six" },
+    { name = "urllib3" },
+    { name = "websocket-client" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/82/3c/9f29f6cab7f35df8e54f019e5719465fa97b877be2454e99f989270b4f34/kubernetes-30.1.0.tar.gz", hash = "sha256:41e4c77af9f28e7a6c314e3bd06a8c6229ddd787cad684e0ab9f69b498e98ebc", size = 887810 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/62/a1/2027ddede72d33be2effc087580aeba07e733a7360780ae87226f1f91bd8/kubernetes-30.1.0-py2.py3-none-any.whl", hash = "sha256:e212e8b7579031dd2e512168b617373bc1e03888d41ac4e04039240a292d478d", size = 1706042 },
+]
+
+[[package]]
+name = "langchain"
+version = "0.2.15"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "aiohttp" },
+    { name = "langchain-core" },
+    { name = "langchain-text-splitters" },
+    { name = "langsmith" },
+    { name = "numpy" },
+    { name = "pydantic" },
+    { name = "pyyaml" },
+    { name = "requests" },
+    { name = "sqlalchemy" },
+    { name = "tenacity" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/f6/9e1044e0b805f5f704435f9b378c26f02dfad3cd361f455e0a1129f8d7ad/langchain-0.2.15.tar.gz", hash = "sha256:f613ce7594be34f9bac687134a56f6e8274951907b798dbd037aefc95df78953", size = 414499 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2a/5f/fec41e34c31265e4dc197ebe24d138c73dfe6a15832fe5db9a83c70e570c/langchain-0.2.15-py3-none-any.whl", hash = "sha256:9e6231441870aaa8523be24a5785ccccfdde759a7e27dd082b6ec80f68e49dec", size = 1001055 },
+]
+
+[[package]]
+name = "langchain-chroma"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "chromadb" },
+    { name = "fastapi" },
+    { name = "langchain-core" },
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/62/06/3ad3efe140eb17fe0c8e1fba03aca0ec6b3fa0451ce7350675c1fefb2a95/langchain_chroma-0.1.2.tar.gz", hash = "sha256:745a53b93e7ae058f9666a48e15ff211122656032ed0e8ffb7291b402f5bf23b", size = 9395 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/10/05/34b30ff33af5ea7e6e5b6d1bf8ea3a0f2c1462c6b7f750f21dd0179fdf1e/langchain_chroma-0.1.2-py3-none-any.whl", hash = "sha256:0948f2975091dfef685a7981c140b8fd8a3b0f0602abba61abbcac7959beee4c", size = 9324 },
+]
+
+[[package]]
+name = "langchain-community"
+version = "0.2.12"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "aiohttp" },
+    { name = "dataclasses-json" },
+    { name = "langchain" },
+    { name = "langchain-core" },
+    { name = "langsmith" },
+    { name = "numpy" },
+    { name = "pyyaml" },
+    { name = "requests" },
+    { name = "sqlalchemy" },
+    { name = "tenacity" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/aa/29/10a27e2a5285decb9cefd4ddd9bf814fb68d6e848cc099a7b3b6b60619aa/langchain_community-0.2.12.tar.gz", hash = "sha256:d671cfc6a4f3b65f49a2e59ab420d0164f109d0a56fc4b4996518205c63b8c7e", size = 1566700 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/83/3a/3becea565ec4232d6d7f6d8774696e2d64e94b388fa3c45bfa264a3787c2/langchain_community-0.2.12-py3-none-any.whl", hash = "sha256:50e74473dd2309bdef561760afbbf0c5ea17ed91fc4dfa0d52279dd16d6d34e0", size = 2311611 },
+]
+
+[[package]]
+name = "langchain-core"
+version = "0.2.38"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "jsonpatch" },
+    { name = "langsmith" },
+    { name = "packaging" },
+    { name = "pydantic" },
+    { name = "pyyaml" },
+    { name = "tenacity" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a0/1c/c1016073a338206358b4a0dba936c109819fb4b070ec2c91456688c1a6dd/langchain_core-0.2.38.tar.gz", hash = "sha256:eb69dbedd344f2ee1f15bcea6c71a05884b867588fadc42d04632e727c1238f3", size = 316363 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1c/e4/501fbe904530dad6ed80f03b188d7602081560dd5cc0bcf0b3c51778c314/langchain_core-0.2.38-py3-none-any.whl", hash = "sha256:8a5729bc7e68b4af089af20eff44fe4e7ca21d0e0c87ec21cef7621981fd1a4a", size = 396442 },
+]
+
+[[package]]
+name = "langchain-text-splitters"
+version = "0.2.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "langchain-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e0/3d/b6cad97223a88465114da8623e9f3da7a74821e76afb3bf1f34f419d93d9/langchain_text_splitters-0.2.2.tar.gz", hash = "sha256:a1e45de10919fa6fb080ef0525deab56557e9552083600455cb9fa4238076140", size = 20188 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/06/76/9e0ca1b8881f64bf927f2205bf6c43a085c04646a71d911b3c05d76e90bb/langchain_text_splitters-0.2.2-py3-none-any.whl", hash = "sha256:1c80d4b11b55e2995f02d2a326c0323ee1eeff24507329bb22924e420c782dff", size = 25239 },
+]
+
+[[package]]
+name = "langdetect"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2569554f7c70f4a3c27712f40e3284d483e88094cc0e/langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0", size = 981474 }
+
+[[package]]
+name = "langfuse"
+version = "2.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "anyio" },
+    { name = "backoff" },
+    { name = "httpx" },
+    { name = "idna" },
+    { name = "packaging" },
+    { name = "pydantic" },
+    { name = "wrapt" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a4/85/5a21a1d3be81e71f11f6d50affbb929eea3d5bbfab649bf8811bba83776f/langfuse-2.44.0.tar.gz", hash = "sha256:dfa5378ff7022ae9fe5b8b842c0365347c98f9ef2b772dcee6a93a45442de28c", size = 106834 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1f/54/03550a879a90ae4242542335b0883bb2d297c75f32cb58604064879a8f3b/langfuse-2.44.0-py3-none-any.whl", hash = "sha256:adb73400a6ad6d597cc95c31381c82f81face3d5fb69391181f224a26f7e8562", size = 195931 },
+]
+
+[[package]]
+name = "langsmith"
+version = "0.1.101"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "httpx" },
+    { name = "orjson" },
+    { name = "pydantic" },
+    { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2c/7d/2adac2d02aa9405f0df823d80ffd10d136bf74c5ae7d62e7bc8e0fc06baa/langsmith-0.1.101.tar.gz", hash = "sha256:caf4d95f314bb6cd3c4e0632eed821fd5cd5d0f18cb824772fce6d7a9113895b", size = 138302 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b5/af/b82590f2830559a0c2438e96d110bfeb002b2687c08a57bf117d9ea6b714/langsmith-0.1.101-py3-none-any.whl", hash = "sha256:572e2c90709cda1ad837ac86cedda7295f69933f2124c658a92a35fb890477cc", size = 148892 },
+]
+
+[[package]]
+name = "lark"
+version = "1.1.9"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2c/e1/804b6196b3fbdd0f8ba785fc62837b034782a891d6f663eea2f30ca23cfa/lark-1.1.9.tar.gz", hash = "sha256:15fa5236490824c2c4aba0e22d2d6d823575dcaf4cdd1848e34b6ad836240fba", size = 255451 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e7/9c/eef7c591e6dc952f3636cfe0df712c0f9916cedf317810a3bb53ccb65cdd/lark-1.1.9-py3-none-any.whl", hash = "sha256:a0dd3a87289f8ccbb325901e4222e723e7d745dbfc1803eaf5f3d2ace19cf2db", size = 111693 },
+]
+
+[[package]]
+name = "lxml"
+version = "5.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5c/a8/449faa2a3cbe6a99f8d38dcd51a3ee8844c17862841a6f769ea7c2a9cd0f/lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b", size = 8141056 },
+    { url = "https://files.pythonhosted.org/packages/ac/8a/ae6325e994e2052de92f894363b038351c50ee38749d30cc6b6d96aaf90f/lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18", size = 4425238 },
+    { url = "https://files.pythonhosted.org/packages/f8/fb/128dddb7f9086236bce0eeae2bfb316d138b49b159f50bc681d56c1bdd19/lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442", size = 5095197 },
+    { url = "https://files.pythonhosted.org/packages/b4/f9/a181a8ef106e41e3086629c8bdb2d21a942f14c84a0e77452c22d6b22091/lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4", size = 4809809 },
+    { url = "https://files.pythonhosted.org/packages/25/2f/b20565e808f7f6868aacea48ddcdd7e9e9fb4c799287f21f1a6c7c2e8b71/lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f", size = 5407593 },
+    { url = "https://files.pythonhosted.org/packages/23/0e/caac672ec246d3189a16c4d364ed4f7d6bf856c080215382c06764058c08/lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e", size = 4866657 },
+    { url = "https://files.pythonhosted.org/packages/67/a4/1f5fbd3f58d4069000522196b0b776a014f3feec1796da03e495cf23532d/lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c", size = 4967017 },
+    { url = "https://files.pythonhosted.org/packages/ee/73/623ecea6ca3c530dd0a4ed0d00d9702e0e85cd5624e2d5b93b005fe00abd/lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16", size = 4810730 },
+    { url = "https://files.pythonhosted.org/packages/1d/ce/fb84fb8e3c298f3a245ae3ea6221c2426f1bbaa82d10a88787412a498145/lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79", size = 5455154 },
+    { url = "https://files.pythonhosted.org/packages/b1/72/4d1ad363748a72c7c0411c28be2b0dc7150d91e823eadad3b91a4514cbea/lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080", size = 4969416 },
+    { url = "https://files.pythonhosted.org/packages/42/07/b29571a58a3a80681722ea8ed0ba569211d9bb8531ad49b5cacf6d409185/lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654", size = 5013672 },
+    { url = "https://files.pythonhosted.org/packages/b9/93/bde740d5a58cf04cbd38e3dd93ad1e36c2f95553bbf7d57807bc6815d926/lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d", size = 4878644 },
+    { url = "https://files.pythonhosted.org/packages/56/b5/645c8c02721d49927c93181de4017164ec0e141413577687c3df8ff0800f/lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763", size = 5511531 },
+    { url = "https://files.pythonhosted.org/packages/85/3f/6a99a12d9438316f4fc86ef88c5d4c8fb674247b17f3173ecadd8346b671/lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec", size = 5402065 },
+    { url = "https://files.pythonhosted.org/packages/80/8a/df47bff6ad5ac57335bf552babfb2408f9eb680c074ec1ba412a1a6af2c5/lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be", size = 5069775 },
+    { url = "https://files.pythonhosted.org/packages/08/ae/e7ad0f0fbe4b6368c5ee1e3ef0c3365098d806d42379c46c1ba2802a52f7/lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9", size = 3474226 },
+    { url = "https://files.pythonhosted.org/packages/c3/b5/91c2249bfac02ee514ab135e9304b89d55967be7e53e94a879b74eec7a5c/lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1", size = 3814971 },
+    { url = "https://files.pythonhosted.org/packages/eb/6d/d1f1c5e40c64bf62afd7a3f9b34ce18a586a1cccbf71e783cd0a6d8e8971/lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859", size = 8171753 },
+    { url = "https://files.pythonhosted.org/packages/bd/83/26b1864921869784355459f374896dcf8b44d4af3b15d7697e9156cb2de9/lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e", size = 4441955 },
+    { url = "https://files.pythonhosted.org/packages/e0/d2/e9bff9fb359226c25cda3538f664f54f2804f4b37b0d7c944639e1a51f69/lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f", size = 5050778 },
+    { url = "https://files.pythonhosted.org/packages/88/69/6972bfafa8cd3ddc8562b126dd607011e218e17be313a8b1b9cc5a0ee876/lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e", size = 4748628 },
+    { url = "https://files.pythonhosted.org/packages/5d/ea/a6523c7c7f6dc755a6eed3d2f6d6646617cad4d3d6d8ce4ed71bfd2362c8/lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179", size = 5322215 },
+    { url = "https://files.pythonhosted.org/packages/99/37/396fbd24a70f62b31d988e4500f2068c7f3fd399d2fd45257d13eab51a6f/lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a", size = 4813963 },
+    { url = "https://files.pythonhosted.org/packages/09/91/e6136f17459a11ce1757df864b213efbeab7adcb2efa63efb1b846ab6723/lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3", size = 4923353 },
+    { url = "https://files.pythonhosted.org/packages/1d/7c/2eeecf87c9a1fca4f84f991067c693e67340f2b7127fc3eca8fa29d75ee3/lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1", size = 4740541 },
+    { url = "https://files.pythonhosted.org/packages/3b/ed/4c38ba58defca84f5f0d0ac2480fdcd99fc7ae4b28fc417c93640a6949ae/lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d", size = 5346504 },
+    { url = "https://files.pythonhosted.org/packages/a5/22/bbd3995437e5745cb4c2b5d89088d70ab19d4feabf8a27a24cecb9745464/lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c", size = 4898077 },
+    { url = "https://files.pythonhosted.org/packages/0a/6e/94537acfb5b8f18235d13186d247bca478fea5e87d224644e0fe907df976/lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99", size = 4946543 },
+    { url = "https://files.pythonhosted.org/packages/8d/e8/4b15df533fe8e8d53363b23a41df9be907330e1fa28c7ca36893fad338ee/lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff", size = 4816841 },
+    { url = "https://files.pythonhosted.org/packages/1a/e7/03f390ea37d1acda50bc538feb5b2bda6745b25731e4e76ab48fae7106bf/lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a", size = 5417341 },
+    { url = "https://files.pythonhosted.org/packages/ea/99/d1133ab4c250da85a883c3b60249d3d3e7c64f24faff494cf0fd23f91e80/lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8", size = 5327539 },
+    { url = "https://files.pythonhosted.org/packages/7d/ed/e6276c8d9668028213df01f598f385b05b55a4e1b4662ee12ef05dab35aa/lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d", size = 5012542 },
+    { url = "https://files.pythonhosted.org/packages/36/88/684d4e800f5aa28df2a991a6a622783fb73cf0e46235cfa690f9776f032e/lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30", size = 3486454 },
+    { url = "https://files.pythonhosted.org/packages/fc/82/ace5a5676051e60355bd8fb945df7b1ba4f4fb8447f2010fb816bfd57724/lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f", size = 3816857 },
+    { url = "https://files.pythonhosted.org/packages/94/6a/42141e4d373903bfea6f8e94b2f554d05506dfda522ada5343c651410dc8/lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a", size = 8156284 },
+    { url = "https://files.pythonhosted.org/packages/91/5e/fa097f0f7d8b3d113fb7312c6308af702f2667f22644441715be961f2c7e/lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd", size = 4432407 },
+    { url = "https://files.pythonhosted.org/packages/2d/a1/b901988aa6d4ff937f2e5cfc114e4ec561901ff00660c3e56713642728da/lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51", size = 5048331 },
+    { url = "https://files.pythonhosted.org/packages/30/0f/b2a54f48e52de578b71bbe2a2f8160672a8a5e103df3a78da53907e8c7ed/lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b", size = 4744835 },
+    { url = "https://files.pythonhosted.org/packages/82/9d/b000c15538b60934589e83826ecbc437a1586488d7c13f8ee5ff1f79a9b8/lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002", size = 5316649 },
+    { url = "https://files.pythonhosted.org/packages/e3/ee/ffbb9eaff5e541922611d2c56b175c45893d1c0b8b11e5a497708a6a3b3b/lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4", size = 4812046 },
+    { url = "https://files.pythonhosted.org/packages/15/ff/7ff89d567485c7b943cdac316087f16b2399a8b997007ed352a1248397e5/lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492", size = 4918597 },
+    { url = "https://files.pythonhosted.org/packages/c6/a3/535b6ed8c048412ff51268bdf4bf1cf052a37aa7e31d2e6518038a883b29/lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3", size = 4738071 },
+    { url = "https://files.pythonhosted.org/packages/7a/8f/cbbfa59cb4d4fd677fe183725a76d8c956495d7a3c7f111ab8f5e13d2e83/lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4", size = 5342213 },
+    { url = "https://files.pythonhosted.org/packages/5c/fb/db4c10dd9958d4b52e34d1d1f7c1f434422aeaf6ae2bbaaff2264351d944/lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367", size = 4893749 },
+    { url = "https://files.pythonhosted.org/packages/f2/38/bb4581c143957c47740de18a3281a0cab7722390a77cc6e610e8ebf2d736/lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832", size = 4945901 },
+    { url = "https://files.pythonhosted.org/packages/fc/d5/18b7de4960c731e98037bd48fa9f8e6e8f2558e6fbca4303d9b14d21ef3b/lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff", size = 4815447 },
+    { url = "https://files.pythonhosted.org/packages/97/a8/cd51ceaad6eb849246559a8ef60ae55065a3df550fc5fcd27014361c1bab/lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd", size = 5411186 },
+    { url = "https://files.pythonhosted.org/packages/89/c3/1e3dabab519481ed7b1fdcba21dcfb8832f57000733ef0e71cf6d09a5e03/lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb", size = 5324481 },
+    { url = "https://files.pythonhosted.org/packages/b6/17/71e9984cf0570cd202ac0a1c9ed5c1b8889b0fc8dc736f5ef0ffb181c284/lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", size = 5011053 },
+    { url = "https://files.pythonhosted.org/packages/69/68/9f7e6d3312a91e30829368c2b3217e750adef12a6f8eb10498249f4e8d72/lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", size = 3485634 },
+    { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 },
+]
+
+[[package]]
+name = "mako"
+version = "1.3.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/67/03/fb5ba97ff65ce64f6d35b582aacffc26b693a98053fa831ab43a437cbddb/Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc", size = 392738 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/03/62/70f5a0c2dd208f9f3f2f9afd103aec42ee4d9ad2401d78342f75e9b8da36/Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a", size = 78565 },
+]
+
+[[package]]
+name = "markdown"
+version = "3.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 },
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "mdurl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
+]
+
+[[package]]
+name = "markupsafe"
+version = "2.1.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219 },
+    { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098 },
+    { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014 },
+    { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220 },
+    { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756 },
+    { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988 },
+    { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718 },
+    { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317 },
+    { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670 },
+    { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224 },
+    { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215 },
+    { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069 },
+    { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452 },
+    { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462 },
+    { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869 },
+    { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906 },
+    { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296 },
+    { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038 },
+    { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572 },
+    { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127 },
+]
+
+[[package]]
+name = "marshmallow"
+version = "3.22.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/70/40/faa10dc4500bca85f41ca9d8cefab282dd23d0fcc7a9b5fab40691e72e76/marshmallow-3.22.0.tar.gz", hash = "sha256:4972f529104a220bb8637d595aa4c9762afbe7f7a77d82dc58c1615d70c5823e", size = 176836 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3c/78/c1de55eb3311f2c200a8b91724414b8d6f5ae78891c15d9d936ea43c3dba/marshmallow-3.22.0-py3-none-any.whl", hash = "sha256:71a2dce49ef901c3f97ed296ae5051135fd3febd2bf43afe0ae9a82143a494d9", size = 49334 },
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
+]
+
+[[package]]
+name = "milvus-lite"
+version = "2.4.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "tqdm" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7c/0b/262997e63e2eaaf5d9f93d73c9d3e75499c6ec058bd1307864be7efb6704/milvus_lite-2.4.10-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:fc4246d3ed7d1910847afce0c9ba18212e93a6e9b8406048436940578dfad5cb", size = 22281832 },
+    { url = "https://files.pythonhosted.org/packages/17/20/9054ace78c61d64a6c24b8e3d6c8a73b23b447028e43c4d1e6c878e8294a/milvus_lite-2.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:74a8e07c5e3b057df17fbb46913388e84df1dc403a200f4e423799a58184c800", size = 19855994 },
+    { url = "https://files.pythonhosted.org/packages/34/8e/7858d12d89bf9e84302c6ffd5faf776d7b2de4372a07461f726ce6f9929e/milvus_lite-2.4.10-py3-none-manylinux2014_aarch64.whl", hash = "sha256:240c7386b747bad696ecb5bd1f58d491e86b9d4b92dccee3315ed7256256eddc", size = 39614866 },
+    { url = "https://files.pythonhosted.org/packages/84/65/639cb552c892ba5fef73301f878b2e7cabb59c918e0c49c9cf3026d49447/milvus_lite-2.4.10-py3-none-manylinux2014_x86_64.whl", hash = "sha256:211d2e334a043f9282bdd9755f76b9b2d93b23bffa7af240919ffce6a8dfe325", size = 49377774 },
+]
+
+[[package]]
+name = "mmh3"
+version = "4.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/63/96/aa247e82878b123468f0079ce2ac77e948315bab91ce45d2934a62e0af95/mmh3-4.1.0.tar.gz", hash = "sha256:a1cf25348b9acd229dda464a094d6170f47d2850a1fcb762a3b6172d2ce6ca4a", size = 26357 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2e/d6/86beea107e7e9700df9522466346c23a2f54faa81337c86fd17002aa95a6/mmh3-4.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3280a463855b0eae64b681cd5b9ddd9464b73f81151e87bb7c91a811d25619e6", size = 39427 },
+    { url = "https://files.pythonhosted.org/packages/1c/08/65fa5489044e2afc304e8540c6c607d5d7b136ddc5cd8315c13de0adc34c/mmh3-4.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:97ac57c6c3301769e757d444fa7c973ceb002cb66534b39cbab5e38de61cd896", size = 29281 },
+    { url = "https://files.pythonhosted.org/packages/b3/aa/98511d3ea3f6ba958136d913be3be3c1009be935a20ecc7b2763f0a605b6/mmh3-4.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7b6502cdb4dbd880244818ab363c8770a48cdccecf6d729ade0241b736b5ec0", size = 30130 },
+    { url = "https://files.pythonhosted.org/packages/3c/b7/1a93f81643435b0e57f1046c4ffe46f0214693eaede0d9b0a1a236776e70/mmh3-4.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ba2da04671a9621580ddabf72f06f0e72c1c9c3b7b608849b58b11080d8f14", size = 69072 },
+    { url = "https://files.pythonhosted.org/packages/45/9e/2ff70246aefd9cf146bc6a420c28ed475a0d1a325f31ee203be02f9215d4/mmh3-4.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a5fef4c4ecc782e6e43fbeab09cff1bac82c998a1773d3a5ee6a3605cde343e", size = 72470 },
+    { url = "https://files.pythonhosted.org/packages/dc/cb/57bc1fdbdbe6837aebfca982494e23e2498ee2a89585c9054713b22e4167/mmh3-4.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5135358a7e00991f73b88cdc8eda5203bf9de22120d10a834c5761dbeb07dd13", size = 71251 },
+    { url = "https://files.pythonhosted.org/packages/4d/c2/46d7d2721b69fbdfd30231309e6395f62ff6744e5c00dd8113b9faa06fba/mmh3-4.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cff9ae76a54f7c6fe0167c9c4028c12c1f6de52d68a31d11b6790bb2ae685560", size = 66035 },
+    { url = "https://files.pythonhosted.org/packages/6f/a4/7ba4bcc838818bcf018e26d118d5ddb605c23c4fad040dc4d811f1cfcb04/mmh3-4.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f02576a4d106d7830ca90278868bf0983554dd69183b7bbe09f2fcd51cf54f", size = 67844 },
+    { url = "https://files.pythonhosted.org/packages/71/ed/8e80d1038e7bb15eaf739711d1fc36f2341acb6b1b95fa77003f2799c91e/mmh3-4.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:073d57425a23721730d3ff5485e2da489dd3c90b04e86243dd7211f889898106", size = 76724 },
+    { url = "https://files.pythonhosted.org/packages/1c/22/a6a70ca81f0ce8fe2f3a68d89c1184c2d2d0fbe0ee305da50e972c5ff9fa/mmh3-4.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:71e32ddec7f573a1a0feb8d2cf2af474c50ec21e7a8263026e8d3b4b629805db", size = 75004 },
+    { url = "https://files.pythonhosted.org/packages/73/20/abe50b605760f1f5b6e0b436c650649e69ca478d0f41b154f300367c09e4/mmh3-4.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7cbb20b29d57e76a58b40fd8b13a9130db495a12d678d651b459bf61c0714cea", size = 82230 },
+    { url = "https://files.pythonhosted.org/packages/45/80/a1fc99d3ee50b573df0bfbb1ad518463af78d2ebca44bfca3b3f9473d651/mmh3-4.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:a42ad267e131d7847076bb7e31050f6c4378cd38e8f1bf7a0edd32f30224d5c9", size = 78679 },
+    { url = "https://files.pythonhosted.org/packages/9e/51/6c9ee2ddf3b386f45ff83b6926a5e826635757d91dab04cbf16eee05f9a7/mmh3-4.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a013979fc9390abadc445ea2527426a0e7a4495c19b74589204f9b71bcaafeb", size = 77382 },
+    { url = "https://files.pythonhosted.org/packages/ee/fa/4b377f244c27fac5f0343cc4dc0d2eb0a08049afc8d5322d07be7461a768/mmh3-4.1.0-cp311-cp311-win32.whl", hash = "sha256:1d3b1cdad7c71b7b88966301789a478af142bddcb3a2bee563f7a7d40519a00f", size = 31232 },
+    { url = "https://files.pythonhosted.org/packages/d1/b0/500ef56c29b276d796bfdb47c16d34fa18a68945e4d730a6fa7d483583ed/mmh3-4.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0dc6dc32eb03727467da8e17deffe004fbb65e8b5ee2b502d36250d7a3f4e2ec", size = 31276 },
+    { url = "https://files.pythonhosted.org/packages/cc/84/94795e6e710c3861f8f355a12be9c9f4b8433a538c983e75bd4c00496a8a/mmh3-4.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9ae3a5c1b32dda121c7dc26f9597ef7b01b4c56a98319a7fe86c35b8bc459ae6", size = 30142 },
+    { url = "https://files.pythonhosted.org/packages/18/45/b4d41e86b00eed8c500adbe0007129861710e181c7f49c507ef6beae9496/mmh3-4.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0033d60c7939168ef65ddc396611077a7268bde024f2c23bdc283a19123f9e9c", size = 39495 },
+    { url = "https://files.pythonhosted.org/packages/a6/d4/f041b8704cb8d1aad3717105daa582e29818b78a540622dfed84cd00d88f/mmh3-4.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d6af3e2287644b2b08b5924ed3a88c97b87b44ad08e79ca9f93d3470a54a41c5", size = 29334 },
+    { url = "https://files.pythonhosted.org/packages/cb/bb/8f75378e1a83b323f9ed06248333c383e7dac614c2f95e1419965cb91693/mmh3-4.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d82eb4defa245e02bb0b0dc4f1e7ee284f8d212633389c91f7fba99ba993f0a2", size = 30144 },
+    { url = "https://files.pythonhosted.org/packages/3e/50/5e36c1945bd83e780a37361fc1999fc4c5a59ecc10a373557fdf0e58eb1f/mmh3-4.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba245e94b8d54765e14c2d7b6214e832557e7856d5183bc522e17884cab2f45d", size = 69094 },
+    { url = "https://files.pythonhosted.org/packages/70/c7/6ae37e7519a938226469476b84bcea2650e2a2cc7a848e6a206ea98ecee3/mmh3-4.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb04e2feeabaad6231e89cd43b3d01a4403579aa792c9ab6fdeef45cc58d4ec0", size = 72611 },
+    { url = "https://files.pythonhosted.org/packages/5e/47/6613f69f57f1e5045e66b22fae9c2fb39ef754c455805d3917f6073e316e/mmh3-4.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e3b1a27def545ce11e36158ba5d5390cdbc300cfe456a942cc89d649cf7e3b2", size = 71462 },
+    { url = "https://files.pythonhosted.org/packages/e0/0a/e423db18ce7b479c4b96381a112b443f0985c611de420f95c58a9f934080/mmh3-4.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce0ab79ff736d7044e5e9b3bfe73958a55f79a4ae672e6213e92492ad5e734d5", size = 66165 },
+    { url = "https://files.pythonhosted.org/packages/4c/7b/bfeb68bee5bddc8baf7ef630b93edc0a533202d84eb076dbb6c77e7e5fd5/mmh3-4.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b02268be6e0a8eeb8a924d7db85f28e47344f35c438c1e149878bb1c47b1cd3", size = 68088 },
+    { url = "https://files.pythonhosted.org/packages/d4/a6/b82e30143997c05776887f5177f724e3b714aa7e7346fbe2ec70f52abcd0/mmh3-4.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:deb887f5fcdaf57cf646b1e062d56b06ef2f23421c80885fce18b37143cba828", size = 76241 },
+    { url = "https://files.pythonhosted.org/packages/6c/60/a3d5872cf7610fcb13e36c472476020c5cf217b23c092bad452eb7784407/mmh3-4.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99dd564e9e2b512eb117bd0cbf0f79a50c45d961c2a02402787d581cec5448d5", size = 74538 },
+    { url = "https://files.pythonhosted.org/packages/f6/d5/742173a94c78f4edab71c04097f6f9150c47f8fd034d592f5f34a9444719/mmh3-4.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:08373082dfaa38fe97aa78753d1efd21a1969e51079056ff552e687764eafdfe", size = 81793 },
+    { url = "https://files.pythonhosted.org/packages/d0/7a/a1db0efe7c67b761d83be3d50e35ef26628ef56b3b8bc776d07412ee8b16/mmh3-4.1.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:54b9c6a2ea571b714e4fe28d3e4e2db37abfd03c787a58074ea21ee9a8fd1740", size = 78217 },
+    { url = "https://files.pythonhosted.org/packages/b3/78/1ff8da7c859cd09704e2f500588d171eda9688fcf6f29e028ef261262a16/mmh3-4.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a7b1edf24c69e3513f879722b97ca85e52f9032f24a52284746877f6a7304086", size = 77052 },
+    { url = "https://files.pythonhosted.org/packages/ed/c7/cf16ace81fc9fbe54a75c914306252af26c6ea485366bb3b579bf6e3dbb8/mmh3-4.1.0-cp312-cp312-win32.whl", hash = "sha256:411da64b951f635e1e2284b71d81a5a83580cea24994b328f8910d40bed67276", size = 31277 },
+    { url = "https://files.pythonhosted.org/packages/d2/0b/b3b1637dca9414451edf287fd91e667e7231d5ffd7498137fe011951fc0a/mmh3-4.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bebc3ecb6ba18292e3d40c8712482b4477abd6981c2ebf0e60869bd90f8ac3a9", size = 31318 },
+    { url = "https://files.pythonhosted.org/packages/dd/6c/c0f06040c58112ccbd0df989055ede98f7c1a1f392dc6a3fc63ec6c124ec/mmh3-4.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:168473dd608ade6a8d2ba069600b35199a9af837d96177d3088ca91f2b3798e3", size = 30147 },
+]
+
+[[package]]
+name = "monotonic"
+version = "1.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ea/ca/8e91948b782ddfbd194f323e7e7d9ba12e5877addf04fb2bf8fca38e86ac/monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7", size = 7615 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9a/67/7e8406a29b6c45be7af7740456f7f37025f0506ae2e05fb9009a53946860/monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c", size = 8154 },
+]
+
+[[package]]
+name = "mpmath"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 },
+]
+
+[[package]]
+name = "msoffcrypto-tool"
+version = "5.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "cryptography" },
+    { name = "olefile" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d2/b7/0fd6573157e0ec60c0c470e732ab3322fba4d2834fd24e1088d670522a01/msoffcrypto_tool-5.4.2.tar.gz", hash = "sha256:44b545adba0407564a0cc3d6dde6ca36b7c0fdf352b85bca51618fa1d4817370", size = 41183 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/03/54/7f6d3d9acad083dae8c22d9ab483b657359a1bf56fee1d7af88794677707/msoffcrypto_tool-5.4.2-py3-none-any.whl", hash = "sha256:274fe2181702d1e5a107ec1b68a4c9fea997a44972ae1cc9ae0cb4f6a50fef0e", size = 48713 },
+]
+
+[[package]]
+name = "multidict"
+version = "6.0.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/79/722ca999a3a09a63b35aac12ec27dfa8e5bb3a38b0f857f7a1a209a88836/multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da", size = 59867 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5f/da/b10ea65b850b54f44a6479177c6987f456bc2d38f8dc73009b78afcf0ede/multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba", size = 50815 },
+    { url = "https://files.pythonhosted.org/packages/21/db/3403263f158b0bc7b0d4653766d71cb39498973f2042eead27b2e9758782/multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e", size = 30269 },
+    { url = "https://files.pythonhosted.org/packages/02/c1/b15ecceb6ffa5081ed2ed450aea58d65b0e0358001f2b426705f9f41f4c2/multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd", size = 30500 },
+    { url = "https://files.pythonhosted.org/packages/3f/e1/7fdd0f39565df3af87d6c2903fb66a7d529fbd0a8a066045d7a5b6ad1145/multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3", size = 130751 },
+    { url = "https://files.pythonhosted.org/packages/76/bc/9f593f9e38c6c09bbf0344b56ad67dd53c69167937c2edadee9719a5e17d/multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf", size = 138185 },
+    { url = "https://files.pythonhosted.org/packages/28/32/d7799a208701d537b92705f46c777ded812a6dc139c18d8ed599908f6b1c/multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29", size = 133585 },
+    { url = "https://files.pythonhosted.org/packages/52/ec/be54a3ad110f386d5bd7a9a42a4ff36b3cd723ebe597f41073a73ffa16b8/multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed", size = 128684 },
+    { url = "https://files.pythonhosted.org/packages/36/e1/a680eabeb71e25d4733276d917658dfa1cd3a99b1223625dbc247d266c98/multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733", size = 120994 },
+    { url = "https://files.pythonhosted.org/packages/ef/08/08f4f44a8a43ea4cee13aa9cdbbf4a639af8db49310a0637ca389c4cf817/multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f", size = 159689 },
+    { url = "https://files.pythonhosted.org/packages/aa/a9/46cdb4cb40bbd4b732169413f56b04a6553460b22bd914f9729c9ba63761/multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4", size = 150611 },
+    { url = "https://files.pythonhosted.org/packages/e9/32/35668bb3e6ab2f12f4e4f7f4000f72f714882a94f904d4c3633fbd036753/multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1", size = 164444 },
+    { url = "https://files.pythonhosted.org/packages/fa/10/f1388a91552af732d8ec48dab928abc209e732767e9e8f92d24c3544353c/multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc", size = 160158 },
+    { url = "https://files.pythonhosted.org/packages/14/c3/f602601f1819983e018156e728e57b3f19726cb424b543667faab82f6939/multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e", size = 156072 },
+    { url = "https://files.pythonhosted.org/packages/82/a6/0290af8487326108c0d03d14f8a0b8b1001d71e4494df5f96ab0c88c0b88/multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c", size = 25731 },
+    { url = "https://files.pythonhosted.org/packages/88/aa/ea217cb18325aa05cb3e3111c19715f1e97c50a4a900cbc20e54648de5f5/multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea", size = 28176 },
+    { url = "https://files.pythonhosted.org/packages/90/9c/7fda9c0defa09538c97b1f195394be82a1f53238536f70b32eb5399dfd4e/multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e", size = 49575 },
+    { url = "https://files.pythonhosted.org/packages/be/21/d6ca80dd1b9b2c5605ff7475699a8ff5dc6ea958cd71fb2ff234afc13d79/multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b", size = 29638 },
+    { url = "https://files.pythonhosted.org/packages/9c/18/9565f32c19d186168731e859692dfbc0e98f66a1dcf9e14d69c02a78b75a/multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5", size = 29874 },
+    { url = "https://files.pythonhosted.org/packages/4e/4e/3815190e73e6ef101b5681c174c541bf972a1b064e926e56eea78d06e858/multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450", size = 129914 },
+    { url = "https://files.pythonhosted.org/packages/0c/08/bb47f886457e2259aefc10044e45c8a1b62f0c27228557e17775869d0341/multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496", size = 134589 },
+    { url = "https://files.pythonhosted.org/packages/d5/2f/952f79b5f0795cf4e34852fc5cf4dfda6166f63c06c798361215b69c131d/multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a", size = 133259 },
+    { url = "https://files.pythonhosted.org/packages/24/1f/af976383b0b772dd351210af5b60ff9927e3abb2f4a103e93da19a957da0/multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226", size = 130779 },
+    { url = "https://files.pythonhosted.org/packages/fc/b1/b0a7744be00b0f5045c7ed4e4a6b8ee6bde4672b2c620474712299df5979/multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271", size = 120125 },
+    { url = "https://files.pythonhosted.org/packages/d0/bf/2a1d667acf11231cdf0b97a6cd9f30e7a5cf847037b5cf6da44884284bd0/multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb", size = 167095 },
+    { url = "https://files.pythonhosted.org/packages/5e/e8/ad6ee74b1a2050d3bc78f566dabcc14c8bf89cbe87eecec866c011479815/multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef", size = 155823 },
+    { url = "https://files.pythonhosted.org/packages/45/7c/06926bb91752c52abca3edbfefac1ea90d9d1bc00c84d0658c137589b920/multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24", size = 170233 },
+    { url = "https://files.pythonhosted.org/packages/3c/29/3dd36cf6b9c5abba8b97bba84eb499a168ba59c3faec8829327b3887d123/multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6", size = 169035 },
+    { url = "https://files.pythonhosted.org/packages/60/47/9a0f43470c70bbf6e148311f78ef5a3d4996b0226b6d295bdd50fdcfe387/multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda", size = 166229 },
+    { url = "https://files.pythonhosted.org/packages/1d/23/c1b7ae7a0b8a3e08225284ef3ecbcf014b292a3ee821bc4ed2185fd4ce7d/multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5", size = 25840 },
+    { url = "https://files.pythonhosted.org/packages/4a/68/66fceb758ad7a88993940dbdf3ac59911ba9dc46d7798bf6c8652f89f853/multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556", size = 27905 },
+    { url = "https://files.pythonhosted.org/packages/fa/a2/17e1e23c6be0a916219c5292f509360c345b5fa6beeb50d743203c27532c/multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7", size = 9729 },
+]
+
+[[package]]
+name = "multiprocess"
+version = "0.70.16"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "dill" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519 },
+    { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741 },
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
+]
+
+[[package]]
+name = "nest-asyncio"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 },
+]
+
+[[package]]
+name = "networkx"
+version = "3.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/04/e6/b164f94c869d6b2c605b5128b7b0cfe912795a87fc90e78533920001f3ec/networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9", size = 2126579 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/38/e9/5f72929373e1a0e8d142a130f3f97e6ff920070f87f91c4e13e40e0fba5a/networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2", size = 1702396 },
+]
+
+[[package]]
+name = "ninja"
+version = "1.11.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/37/2c/d717d13a413d6f7579cdaa1e28e6e2c98de95461549b08d311c8a5bf4c51/ninja-1.11.1.1.tar.gz", hash = "sha256:9d793b08dd857e38d0b6ffe9e6b7145d7c485a42dcfea04905ca0cdb6017cc3c", size = 132392 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3d/6e/04ed11bb244039908f6f212cb5f3e97933e238655248e4ce307c1687ba1f/ninja-1.11.1.1-py2.py3-none-macosx_10_9_universal2.macosx_10_9_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:376889c76d87b95b5719fdd61dd7db193aa7fd4432e5d52d2e44e4c497bdbbee", size = 270611 },
+    { url = "https://files.pythonhosted.org/packages/2c/52/0e5423311eb9939b6f9354059a6d88a6211eb4fa1c7a4ef303ecee1c1fe0/ninja-1.11.1.1-py2.py3-none-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:ecf80cf5afd09f14dcceff28cb3f11dc90fb97c999c89307aea435889cb66877", size = 324256 },
+    { url = "https://files.pythonhosted.org/packages/6d/92/8d7aebd4430ab5ff65df2bfee6d5745f95c004284db2d8ca76dcbfd9de47/ninja-1.11.1.1-py2.py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:84502ec98f02a037a169c4b0d5d86075eaf6afc55e1879003d6cab51ced2ea4b", size = 307194 },
+    { url = "https://files.pythonhosted.org/packages/01/c8/96424839fd127b4492229acf50763ed9940d864ca35d17d151934aef1f6f/ninja-1.11.1.1-py2.py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:73b93c14046447c7c5cc892433d4fae65d6364bec6685411cb97a8bcf815f93a", size = 155643 },
+    { url = "https://files.pythonhosted.org/packages/6b/fa/5ca8e65a98cdb9a71d4f1e38cac7bd757bbb9555a5aef5a4d293aa890e5c/ninja-1.11.1.1-py2.py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18302d96a5467ea98b68e1cae1ae4b4fb2b2a56a82b955193c637557c7273dbd", size = 179538 },
+    { url = "https://files.pythonhosted.org/packages/45/ef/60086f02cbc6882da00a02c81d645cefd8d2d65b01fade41b873d8dd85a2/ninja-1.11.1.1-py2.py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aad34a70ef15b12519946c5633344bc775a7656d789d9ed5fdb0d456383716ef", size = 156217 },
+    { url = "https://files.pythonhosted.org/packages/1c/00/2fd13ac6aafdb566f00d6b541101fca54e58ae58bf96c00f9780df019607/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:d491fc8d89cdcb416107c349ad1e3a735d4c4af5e1cb8f5f727baca6350fdaea", size = 372069 },
+    { url = "https://files.pythonhosted.org/packages/ad/5d/6e97c8a25167d4867694c7fb0b9bdbc9b096d6479c8e56c5bd41b49613f6/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_i686.whl", hash = "sha256:7563ce1d9fe6ed5af0b8dd9ab4a214bf4ff1f2f6fd6dc29f480981f0f8b8b249", size = 418859 },
+    { url = "https://files.pythonhosted.org/packages/43/78/34af88d753389a9412438d16142c77e587e0d69152faf0bbf99701063dd8/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:9df724344202b83018abb45cb1efc22efd337a1496514e7e6b3b59655be85205", size = 419782 },
+    { url = "https://files.pythonhosted.org/packages/3b/74/de0633f8bced3b188942fca64a950e8f2206c60c10c97af465b356ae9b25/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_s390x.whl", hash = "sha256:3e0f9be5bb20d74d58c66cc1c414c3e6aeb45c35b0d0e41e8d739c2c0d57784f", size = 415476 },
+    { url = "https://files.pythonhosted.org/packages/9a/f3/3e4a56ff77739d1582749b93497bdebf11e003fbc7a66363ef6c772ebd0a/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:76482ba746a2618eecf89d5253c0d1e4f1da1270d41e9f54dfbd91831b0f6885", size = 379229 },
+    { url = "https://files.pythonhosted.org/packages/c5/ee/53df34fcc9c0b1db62b2f2e2c848e28d9354e1c7f0dce029ee50b16ca157/ninja-1.11.1.1-py2.py3-none-win32.whl", hash = "sha256:fa2ba9d74acfdfbfbcf06fad1b8282de8a7a8c481d9dee45c859a8c93fcc1082", size = 265049 },
+    { url = "https://files.pythonhosted.org/packages/b6/2f/a3bc50fa63fc4fe9348e15b53dc8c87febfd4e0c660fcf250c4b19a3aa3b/ninja-1.11.1.1-py2.py3-none-win_amd64.whl", hash = "sha256:95da904130bfa02ea74ff9c0116b4ad266174fafb1c707aa50212bc7859aebf1", size = 312958 },
+    { url = "https://files.pythonhosted.org/packages/73/2a/f5b7b3b7ecd5cf4e31375580bf5c6a01a328ed1ebdfff90fab463e3f4bc7/ninja-1.11.1.1-py2.py3-none-win_arm64.whl", hash = "sha256:185e0641bde601e53841525c4196278e9aaf4463758da6dd1e752c0a0f54136a", size = 272686 },
+]
+
+[[package]]
+name = "nltk"
+version = "3.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click" },
+    { name = "joblib" },
+    { name = "regex" },
+    { name = "tqdm" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3c/87/db8be88ad32c2d042420b6fd9ffd4a149f9a0d7f0e86b3f543be2eeeedd2/nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868", size = 2904691 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442 },
+]
+
+[[package]]
+name = "numpy"
+version = "1.26.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554 },
+    { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127 },
+    { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994 },
+    { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005 },
+    { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297 },
+    { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567 },
+    { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812 },
+    { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913 },
+    { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901 },
+    { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868 },
+    { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109 },
+    { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613 },
+    { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172 },
+    { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643 },
+    { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803 },
+    { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754 },
+]
+
+[[package]]
+name = "nvidia-cublas-cu12"
+version = "12.1.3.1"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/37/6d/121efd7382d5b0284239f4ab1fc1590d86d34ed4a4a2fdb13b30ca8e5740/nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728", size = 410594774 },
+    { url = "https://files.pythonhosted.org/packages/c5/ef/32a375b74bea706c93deea5613552f7c9104f961b21df423f5887eca713b/nvidia_cublas_cu12-12.1.3.1-py3-none-win_amd64.whl", hash = "sha256:2b964d60e8cf11b5e1073d179d85fa340c120e99b3067558f3cf98dd69d02906", size = 439918445 },
+]
+
+[[package]]
+name = "nvidia-cuda-cupti-cu12"
+version = "12.1.105"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/00/6b218edd739ecfc60524e585ba8e6b00554dd908de2c9c66c1af3e44e18d/nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e", size = 14109015 },
+    { url = "https://files.pythonhosted.org/packages/d0/56/0021e32ea2848c24242f6b56790bd0ccc8bf99f973ca790569c6ca028107/nvidia_cuda_cupti_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:bea8236d13a0ac7190bd2919c3e8e6ce1e402104276e6f9694479e48bb0eb2a4", size = 10154340 },
+]
+
+[[package]]
+name = "nvidia-cuda-nvrtc-cu12"
+version = "12.1.105"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b6/9f/c64c03f49d6fbc56196664d05dba14e3a561038a81a638eeb47f4d4cfd48/nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2", size = 23671734 },
+    { url = "https://files.pythonhosted.org/packages/ad/1d/f76987c4f454eb86e0b9a0e4f57c3bf1ac1d13ad13cd1a4da4eb0e0c0ce9/nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:0a98a522d9ff138b96c010a65e145dc1b4850e9ecb75a0172371793752fd46ed", size = 19331863 },
+]
+
+[[package]]
+name = "nvidia-cuda-runtime-cu12"
+version = "12.1.105"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/eb/d5/c68b1d2cdfcc59e72e8a5949a37ddb22ae6cade80cd4a57a84d4c8b55472/nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40", size = 823596 },
+    { url = "https://files.pythonhosted.org/packages/9f/e2/7a2b4b5064af56ea8ea2d8b2776c0f2960d95c88716138806121ae52a9c9/nvidia_cuda_runtime_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:dfb46ef84d73fababab44cf03e3b83f80700d27ca300e537f85f636fac474344", size = 821226 },
+]
+
+[[package]]
+name = "nvidia-cudnn-cu12"
+version = "9.1.0.70"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 },
+    { url = "https://files.pythonhosted.org/packages/3f/d0/f90ee6956a628f9f04bf467932c0a25e5a7e706a684b896593c06c82f460/nvidia_cudnn_cu12-9.1.0.70-py3-none-win_amd64.whl", hash = "sha256:6278562929433d68365a07a4a1546c237ba2849852c0d4b2262a486e805b977a", size = 679925892 },
+]
+
+[[package]]
+name = "nvidia-cufft-cu12"
+version = "11.0.2.54"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/86/94/eb540db023ce1d162e7bea9f8f5aa781d57c65aed513c33ee9a5123ead4d/nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56", size = 121635161 },
+    { url = "https://files.pythonhosted.org/packages/f7/57/7927a3aa0e19927dfed30256d1c854caf991655d847a4e7c01fe87e3d4ac/nvidia_cufft_cu12-11.0.2.54-py3-none-win_amd64.whl", hash = "sha256:d9ac353f78ff89951da4af698f80870b1534ed69993f10a4cf1d96f21357e253", size = 121344196 },
+]
+
+[[package]]
+name = "nvidia-curand-cu12"
+version = "10.3.2.106"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/44/31/4890b1c9abc496303412947fc7dcea3d14861720642b49e8ceed89636705/nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0", size = 56467784 },
+    { url = "https://files.pythonhosted.org/packages/5c/97/4c9c7c79efcdf5b70374241d48cf03b94ef6707fd18ea0c0f53684931d0b/nvidia_curand_cu12-10.3.2.106-py3-none-win_amd64.whl", hash = "sha256:75b6b0c574c0037839121317e17fd01f8a69fd2ef8e25853d826fec30bdba74a", size = 55995813 },
+]
+
+[[package]]
+name = "nvidia-cusolver-cu12"
+version = "11.4.5.107"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
+    { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
+    { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928 },
+    { url = "https://files.pythonhosted.org/packages/b8/80/8fca0bf819122a631c3976b6fc517c1b10741b643b94046bd8dd451522c5/nvidia_cusolver_cu12-11.4.5.107-py3-none-win_amd64.whl", hash = "sha256:74e0c3a24c78612192a74fcd90dd117f1cf21dea4822e66d89e8ea80e3cd2da5", size = 121643081 },
+]
+
+[[package]]
+name = "nvidia-cusparse-cu12"
+version = "12.1.0.106"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278 },
+    { url = "https://files.pythonhosted.org/packages/0f/95/48fdbba24c93614d1ecd35bc6bdc6087bd17cbacc3abc4b05a9c2a1ca232/nvidia_cusparse_cu12-12.1.0.106-py3-none-win_amd64.whl", hash = "sha256:b798237e81b9719373e8fae8d4f091b70a0cf09d9d85c95a557e11df2d8e9a5a", size = 195414588 },
+]
+
+[[package]]
+name = "nvidia-nccl-cu12"
+version = "2.20.5"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c1/bb/d09dda47c881f9ff504afd6f9ca4f502ded6d8fc2f572cacc5e39da91c28/nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1fc150d5c3250b170b29410ba682384b14581db722b2531b0d8d33c595f33d01", size = 176238458 },
+    { url = "https://files.pythonhosted.org/packages/4b/2a/0a131f572aa09f741c30ccd45a8e56316e8be8dfc7bc19bf0ab7cfef7b19/nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:057f6bf9685f75215d0c53bf3ac4a10b3e6578351de307abad9e18a99182af56", size = 176249402 },
+]
+
+[[package]]
+name = "nvidia-nvjitlink-cu12"
+version = "12.6.20"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/81/b3/e456a1b2d499bb84bdc6670bfbcf41ff3bac58bd2fae6880d62834641558/nvidia_nvjitlink_cu12-12.6.20-py3-none-manylinux2014_aarch64.whl", hash = "sha256:84fb38465a5bc7c70cbc320cfd0963eb302ee25a5e939e9f512bbba55b6072fb", size = 19252608 },
+    { url = "https://files.pythonhosted.org/packages/59/65/7ff0569494fbaea45ad2814972cc88da843d53cc96eb8554fcd0908941d9/nvidia_nvjitlink_cu12-12.6.20-py3-none-manylinux2014_x86_64.whl", hash = "sha256:562ab97ea2c23164823b2a89cb328d01d45cb99634b8c65fe7cd60d14562bd79", size = 19724950 },
+    { url = "https://files.pythonhosted.org/packages/cb/ef/8f96c82e1cfcf6d5b770f7b043c3cc24841fc247b37629a7cc643dbf72a1/nvidia_nvjitlink_cu12-12.6.20-py3-none-win_amd64.whl", hash = "sha256:ed3c43a17f37b0c922a919203d2d36cbef24d41cc3e6b625182f8b58203644f6", size = 162012830 },
+]
+
+[[package]]
+name = "nvidia-nvtx-cu12"
+version = "12.1.105"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/da/d3/8057f0587683ed2fcd4dbfbdfdfa807b9160b809976099d36b8f60d08f03/nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5", size = 99138 },
+    { url = "https://files.pythonhosted.org/packages/b8/d7/bd7cb2d95ac6ac6e8d05bfa96cdce69619f1ef2808e072919044c2d47a8c/nvidia_nvtx_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:65f4d98982b31b60026e0e6de73fbdfc09d08a96f4656dd3665ca616a11e1e82", size = 66307 },
+]
+
+[[package]]
+name = "oauthlib"
+version = "3.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688 },
+]
+
+[[package]]
+name = "olefile"
+version = "0.47"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565 },
+]
+
+[[package]]
+name = "oletools"
+version = "0.60.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "colorclass" },
+    { name = "easygui" },
+    { name = "msoffcrypto-tool", marker = "platform_python_implementation != 'PyPy' or (platform_system != 'Darwin' and platform_system != 'Windows')" },
+    { name = "olefile" },
+    { name = "pcodedmp" },
+    { name = "pyparsing" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5c/2f/037f40e44706d542b94a2312ccc33ee2701ebfc9a83b46b55263d49ce55a/oletools-0.60.2.zip", hash = "sha256:ad452099f4695ffd8855113f453348200d195ee9fa341a09e197d66ee7e0b2c3", size = 3433750 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ac/ff/05257b7183279b80ecec6333744de23f48f0faeeba46c93e6d13ce835515/oletools-0.60.2-py2.py3-none-any.whl", hash = "sha256:72ad8bd748fd0c4e7b5b4733af770d11543ebb2bf2697455f99f975fcd50cc96", size = 989449 },
+]
+
+[[package]]
+name = "onnxruntime"
+version = "1.19.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "coloredlogs" },
+    { name = "flatbuffers" },
+    { name = "numpy" },
+    { name = "packaging" },
+    { name = "protobuf" },
+    { name = "sympy" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/80/16/fc200316725d04731d8ffc5d2105887a1e400d760b0c7fd464744335cd29/onnxruntime-1.19.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a2b53b3c287cd933e5eb597273926e899082d8c84ab96e1b34035764a1627e17", size = 16778356 },
+    { url = "https://files.pythonhosted.org/packages/cc/3c/ff2ecf2a842822bc5e9758747bdfd4163c53af470421f07afd6cba1ced7d/onnxruntime-1.19.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e94984663963e74fbb468bde9ec6f19dcf890b594b35e249c4dc8789d08993c5", size = 11492628 },
+    { url = "https://files.pythonhosted.org/packages/fa/ca/769da06e76b14a315a1effa5b01d906963379495cd82c00b5023be4c3e61/onnxruntime-1.19.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f379d1f050cfb55ce015d53727b78ee362febc065c38eed81512b22b757da73", size = 13172071 },
+    { url = "https://files.pythonhosted.org/packages/75/7c/5a7e3fd98f9af3c43d6073c38afff8c18d201a72d1eba77c93dd230b8501/onnxruntime-1.19.0-cp311-cp311-win32.whl", hash = "sha256:4ccb48faea02503275ae7e79e351434fc43c294c4cb5c4d8bcb7479061396614", size = 9589924 },
+    { url = "https://files.pythonhosted.org/packages/78/86/fd21288f9e4096d9c27bd0f221cb61719baa97d5e187549a9f0e84e386ae/onnxruntime-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:9cdc8d311289a84e77722de68bd22b8adfb94eea26f4be6f9e017350faac8b18", size = 11083172 },
+    { url = "https://files.pythonhosted.org/packages/d1/3c/7cd126254658f0371fadf8651957387d7f743b1b85545e3b783a7f717215/onnxruntime-1.19.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1b59eaec1be9a8613c5fdeaafe67f73a062edce3ac03bbbdc9e2d98b58a30617", size = 16789643 },
+    { url = "https://files.pythonhosted.org/packages/bf/6e/aae5420a45cbbcacef4c65f70067c11bed7cbb8fda12e0728f37d29746e5/onnxruntime-1.19.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be4144d014a4b25184e63ce7a463a2e7796e2f3df931fccc6a6aefa6f1365dc5", size = 11483896 },
+    { url = "https://files.pythonhosted.org/packages/e6/0f/ad2ec6d490d9cb4ea82dd46382396827cb8ca9a469a56368fc7ef2fb52a4/onnxruntime-1.19.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10d7e7d4ca7021ce7f29a66dbc6071addf2de5839135339bd855c6d9c2bba371", size = 13177713 },
+    { url = "https://files.pythonhosted.org/packages/de/4e/059cae46e48d183ac9b1d0be7ece1c5878711f4a31a206a9dcb34a89e3f5/onnxruntime-1.19.0-cp312-cp312-win32.whl", hash = "sha256:87f2c58b577a1fb31dc5d92b647ecc588fd5f1ea0c3ad4526f5f80a113357c8d", size = 9591661 },
+    { url = "https://files.pythonhosted.org/packages/a0/ed/7ac157855cd2135ba894836ce4d027830b78d71832c9e658046e5b1b3d23/onnxruntime-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:8a1f50d49676d7b69566536ff039d9e4e95fc482a55673719f46528218ecbb94", size = 11084335 },
+]
+
+[[package]]
+name = "open-webui"
+version = "0.3.29"
+source = { editable = "." }
+dependencies = [
+    { name = "aiohttp" },
+    { name = "alembic" },
+    { name = "anthropic" },
+    { name = "apscheduler" },
+    { name = "argon2-cffi" },
+    { name = "authlib" },
+    { name = "bcrypt" },
+    { name = "black" },
+    { name = "boto3" },
+    { name = "chromadb" },
+    { name = "colbert-ai" },
+    { name = "docker" },
+    { name = "docx2txt" },
+    { name = "duckduckgo-search" },
+    { name = "einops" },
+    { name = "extract-msg" },
+    { name = "fake-useragent" },
+    { name = "fastapi" },
+    { name = "faster-whisper" },
+    { name = "flask" },
+    { name = "flask-cors" },
+    { name = "fpdf2" },
+    { name = "google-generativeai" },
+    { name = "langchain" },
+    { name = "langchain-chroma" },
+    { name = "langchain-community" },
+    { name = "langfuse" },
+    { name = "markdown" },
+    { name = "nltk" },
+    { name = "openai" },
+    { name = "opencv-python-headless" },
+    { name = "openpyxl" },
+    { name = "pandas" },
+    { name = "passlib", extra = ["bcrypt"] },
+    { name = "peewee" },
+    { name = "peewee-migrate" },
+    { name = "psutil" },
+    { name = "psycopg2-binary" },
+    { name = "pydantic" },
+    { name = "pydub" },
+    { name = "pyjwt", extra = ["crypto"] },
+    { name = "pymilvus" },
+    { name = "pymongo" },
+    { name = "pymysql" },
+    { name = "pypandoc" },
+    { name = "pypdf" },
+    { name = "pytest" },
+    { name = "pytest-docker" },
+    { name = "python-jose" },
+    { name = "python-multipart" },
+    { name = "python-pptx" },
+    { name = "python-socketio" },
+    { name = "pytube" },
+    { name = "pyxlsb" },
+    { name = "rank-bm25" },
+    { name = "rapidocr-onnxruntime" },
+    { name = "redis" },
+    { name = "requests" },
+    { name = "sentence-transformers" },
+    { name = "sqlalchemy" },
+    { name = "tiktoken" },
+    { name = "unstructured" },
+    { name = "uvicorn", extra = ["standard"] },
+    { name = "validators" },
+    { name = "xlrd" },
+    { name = "youtube-transcript-api" },
+]
+
+[package.metadata]
+requires-dist = [
+    { name = "aiohttp", specifier = "==3.10.5" },
+    { name = "alembic", specifier = "==1.13.2" },
+    { name = "anthropic" },
+    { name = "apscheduler", specifier = "==3.10.4" },
+    { name = "argon2-cffi", specifier = "==23.1.0" },
+    { name = "authlib", specifier = "==1.3.2" },
+    { name = "bcrypt", specifier = "==4.2.0" },
+    { name = "black", specifier = "==24.8.0" },
+    { name = "boto3", specifier = "==1.35.0" },
+    { name = "chromadb", specifier = "==0.5.5" },
+    { name = "colbert-ai", specifier = "==0.2.21" },
+    { name = "docker", specifier = "~=7.1.0" },
+    { name = "docx2txt", specifier = "==0.8" },
+    { name = "duckduckgo-search", specifier = "~=6.2.11" },
+    { name = "einops", specifier = "==0.8.0" },
+    { name = "extract-msg" },
+    { name = "fake-useragent", specifier = "==1.5.1" },
+    { name = "fastapi", specifier = "==0.111.0" },
+    { name = "faster-whisper", specifier = "==1.0.3" },
+    { name = "flask", specifier = "==3.0.3" },
+    { name = "flask-cors", specifier = "==5.0.0" },
+    { name = "fpdf2", specifier = "==2.7.9" },
+    { name = "google-generativeai", specifier = "==0.7.2" },
+    { name = "langchain", specifier = "==0.2.15" },
+    { name = "langchain-chroma", specifier = "==0.1.2" },
+    { name = "langchain-community", specifier = "==0.2.12" },
+    { name = "langfuse", specifier = "==2.44.0" },
+    { name = "markdown", specifier = "==3.7" },
+    { name = "nltk", specifier = "==3.9.1" },
+    { name = "openai" },
+    { name = "opencv-python-headless", specifier = "==4.10.0.84" },
+    { name = "openpyxl", specifier = "==3.1.5" },
+    { name = "pandas", specifier = "==2.2.2" },
+    { name = "passlib", extras = ["bcrypt"], specifier = "==1.7.4" },
+    { name = "peewee", specifier = "==3.17.6" },
+    { name = "peewee-migrate", specifier = "==1.12.2" },
+    { name = "psutil" },
+    { name = "psycopg2-binary", specifier = "==2.9.9" },
+    { name = "pydantic", specifier = "==2.8.2" },
+    { name = "pydub" },
+    { name = "pyjwt", extras = ["crypto"], specifier = "==2.9.0" },
+    { name = "pymilvus", specifier = "==2.4.6" },
+    { name = "pymongo" },
+    { name = "pymysql", specifier = "==1.1.1" },
+    { name = "pypandoc", specifier = "==1.13" },
+    { name = "pypdf", specifier = "==4.3.1" },
+    { name = "pytest", specifier = "~=8.2.2" },
+    { name = "pytest-docker", specifier = "~=3.1.1" },
+    { name = "python-jose", specifier = "==3.3.0" },
+    { name = "python-multipart", specifier = "==0.0.9" },
+    { name = "python-pptx", specifier = "==1.0.0" },
+    { name = "python-socketio", specifier = "==5.11.3" },
+    { name = "pytube", specifier = "==15.0.0" },
+    { name = "pyxlsb", specifier = "==1.0.10" },
+    { name = "rank-bm25", specifier = "==0.2.2" },
+    { name = "rapidocr-onnxruntime", specifier = "==1.3.24" },
+    { name = "redis" },
+    { name = "requests", specifier = "==2.32.3" },
+    { name = "sentence-transformers", specifier = "==3.0.1" },
+    { name = "sqlalchemy", specifier = "==2.0.32" },
+    { name = "tiktoken" },
+    { name = "unstructured", specifier = "==0.15.9" },
+    { name = "uvicorn", extras = ["standard"], specifier = "==0.30.6" },
+    { name = "validators", specifier = "==0.33.0" },
+    { name = "xlrd", specifier = "==2.0.1" },
+    { name = "youtube-transcript-api", specifier = "==0.6.2" },
+]
+
+[[package]]
+name = "openai"
+version = "1.42.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "anyio" },
+    { name = "distro" },
+    { name = "httpx" },
+    { name = "jiter" },
+    { name = "pydantic" },
+    { name = "sniffio" },
+    { name = "tqdm" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8c/1f/310b0b5efb6178ad9f9ca4a80b2ead3cb7cbc16a1b843941bcf1c52dd884/openai-1.42.0.tar.gz", hash = "sha256:c9d31853b4e0bc2dc8bd08003b462a006035655a701471695d0bfdc08529cde3", size = 290549 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/cf/9e/d77569d06e365f093977d94f305a395b7ac5ccd746016a2e8dd34c4e20c1/openai-1.42.0-py3-none-any.whl", hash = "sha256:dc91e0307033a4f94931e5d03cc3b29b9717014ad5e73f9f2051b6cb5eda4d80", size = 362858 },
+]
+
+[[package]]
+name = "opencv-python"
+version = "4.10.0.84"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/b70a2d9ab205110d715906fc8ec83fbb00404aeb3a37a0654fdb68eb0c8c/opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526", size = 95103981 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/66/82/564168a349148298aca281e342551404ef5521f33fba17b388ead0a84dc5/opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251", size = 54835524 },
+    { url = "https://files.pythonhosted.org/packages/64/4a/016cda9ad7cf18c58ba074628a4eaae8aa55f3fd06a266398cef8831a5b9/opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98", size = 56475426 },
+    { url = "https://files.pythonhosted.org/packages/81/e4/7a987ebecfe5ceaf32db413b67ff18eb3092c598408862fff4d7cc3fd19b/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6", size = 41746971 },
+    { url = "https://files.pythonhosted.org/packages/3f/a4/d2537f47fd7fcfba966bd806e3ec18e7ee1681056d4b0a9c8d983983e4d5/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f", size = 62548253 },
+    { url = "https://files.pythonhosted.org/packages/1e/39/bbf57e7b9dab623e8773f6ff36385456b7ae7fa9357a5e53db732c347eac/opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236", size = 28737688 },
+    { url = "https://files.pythonhosted.org/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521 },
+]
+
+[[package]]
+name = "opencv-python-headless"
+version = "4.10.0.84"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2f/7e/d20f68a5f1487adf19d74378d349932a386b1ece3be9be9915e5986db468/opencv-python-headless-4.10.0.84.tar.gz", hash = "sha256:f2017c6101d7c2ef8d7bc3b414c37ff7f54d64413a1847d89970b6b7069b4e1a", size = 95117755 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1c/9b/583c8d9259f6fc19413f83fd18dd8e6cbc8eefb0b4dc6da52dd151fe3272/opencv_python_headless-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a4f4bcb07d8f8a7704d9c8564c224c8b064c63f430e95b61ac0bffaa374d330e", size = 54835657 },
+    { url = "https://files.pythonhosted.org/packages/c0/7b/b4c67f5dad7a9a61c47f7a39e4050e8a4628bd64b3c3daaeb755d759f928/opencv_python_headless-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:5ae454ebac0eb0a0b932e3406370aaf4212e6a3fdb5038cc86c7aea15a6851da", size = 56475470 },
+    { url = "https://files.pythonhosted.org/packages/91/61/f838ce2046f3ec3591ea59ea3549085e399525d3b4558c4ed60b55ed88c0/opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46071015ff9ab40fccd8a163da0ee14ce9846349f06c6c8c0f2870856ffa45db", size = 29329705 },
+    { url = "https://files.pythonhosted.org/packages/d1/09/248f86a404567303cdf120e4a301f389b68e3b18e5c0cc428de327da609c/opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377d08a7e48a1405b5e84afcbe4798464ce7ee17081c1c23619c8b398ff18295", size = 49858781 },
+    { url = "https://files.pythonhosted.org/packages/30/c0/66f88d58500e990a9a0a5c06f98862edf1d0a3a430781218a8c193948438/opencv_python_headless-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:9092404b65458ed87ce932f613ffbb1106ed2c843577501e5768912360fc50ec", size = 28675298 },
+    { url = "https://files.pythonhosted.org/packages/26/d0/22f68eb23eea053a31655960f133c0be9726c6a881547e6e9e7e2a946c4f/opencv_python_headless-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:afcf28bd1209dd58810d33defb622b325d3cbe49dcd7a43a902982c33e5fad05", size = 38754031 },
+]
+
+[[package]]
+name = "openpyxl"
+version = "3.1.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "et-xmlfile" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 },
+]
+
+[[package]]
+name = "opentelemetry-api"
+version = "1.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "deprecated" },
+    { name = "importlib-metadata" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/48/d4/e9a0ddef6eed086c96e8265d864a46da099611b7be153b0cfb63fd47e1b4/opentelemetry_api-1.26.0.tar.gz", hash = "sha256:2bd639e4bed5b18486fef0b5a520aaffde5a18fc225e808a1ac4df363f43a1ce", size = 60904 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e3/a7/6322d1d7a1fb926e8b99208c27730f21217da2f1e0e11dab48a78a0427a4/opentelemetry_api-1.26.0-py3-none-any.whl", hash = "sha256:7d7ea33adf2ceda2dd680b18b1677e4152000b37ca76e679da71ff103b943064", size = 61533 },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-proto-common"
+version = "1.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "opentelemetry-proto" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/84/cd/ed9eaa1d80facb6609d02af6c393b02ce3797a15742361be4859db6fdc17/opentelemetry_exporter_otlp_proto_common-1.26.0.tar.gz", hash = "sha256:bdbe50e2e22a1c71acaa0c8ba6efaadd58882e5a5978737a44a4c4b10d304c92", size = 17815 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/25/2f/0f7e0a73fd901c9abc6ea680d7f19a803dac830c450f21e1123d3a3ec488/opentelemetry_exporter_otlp_proto_common-1.26.0-py3-none-any.whl", hash = "sha256:ee4d8f8891a1b9c372abf8d109409e5b81947cf66423fd998e56880057afbc71", size = 17837 },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-proto-grpc"
+version = "1.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "deprecated" },
+    { name = "googleapis-common-protos" },
+    { name = "grpcio" },
+    { name = "opentelemetry-api" },
+    { name = "opentelemetry-exporter-otlp-proto-common" },
+    { name = "opentelemetry-proto" },
+    { name = "opentelemetry-sdk" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a0/23/cac89aca97ecb8f7498a875dc2ac89224b4f3345bcb8ffff643b59886196/opentelemetry_exporter_otlp_proto_grpc-1.26.0.tar.gz", hash = "sha256:a65b67a9a6b06ba1ec406114568e21afe88c1cdb29c464f2507d529eb906d8ae", size = 25239 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4d/0c/e4473692fec8076008c7926dfcef7223fc6d2785f04ad9d8402347a4eba9/opentelemetry_exporter_otlp_proto_grpc-1.26.0-py3-none-any.whl", hash = "sha256:e2be5eff72ebcb010675b818e8d7c2e7d61ec451755b8de67a140bc49b9b0280", size = 18228 },
+]
+
+[[package]]
+name = "opentelemetry-instrumentation"
+version = "0.47b0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "opentelemetry-api" },
+    { name = "setuptools" },
+    { name = "wrapt" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ab/9d/de2726729dbe5d210683245315ed5a20bf90465d1cc5e7f9cb0bee6673a6/opentelemetry_instrumentation-0.47b0.tar.gz", hash = "sha256:96f9885e450c35e3f16a4f33145f2ebf620aea910c9fd74a392bbc0f807a350f", size = 24516 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1f/6a/be31a84ddd13e9018fcca6885e4710f227eb0fd06eda1896da67287faa2e/opentelemetry_instrumentation-0.47b0-py3-none-any.whl", hash = "sha256:88974ee52b1db08fc298334b51c19d47e53099c33740e48c4f084bd1afd052d5", size = 29218 },
+]
+
+[[package]]
+name = "opentelemetry-instrumentation-asgi"
+version = "0.47b0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "asgiref" },
+    { name = "opentelemetry-api" },
+    { name = "opentelemetry-instrumentation" },
+    { name = "opentelemetry-semantic-conventions" },
+    { name = "opentelemetry-util-http" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/59/a5/895c3810f27cdd3bdb02320df3489d2d33f158970d8447755deb7fc3fef7/opentelemetry_instrumentation_asgi-0.47b0.tar.gz", hash = "sha256:e78b7822c1bca0511e5e9610ec484b8994a81670375e570c76f06f69af7c506a", size = 23398 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ba/d9/c74cb6d69589cc97d856cb3f427dfcef37ec16f9564586290c9c075d9020/opentelemetry_instrumentation_asgi-0.47b0-py3-none-any.whl", hash = "sha256:b798dc4957b3edc9dfecb47a4c05809036a4b762234c5071212fda39ead80ade", size = 15946 },
+]
+
+[[package]]
+name = "opentelemetry-instrumentation-fastapi"
+version = "0.47b0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "opentelemetry-api" },
+    { name = "opentelemetry-instrumentation" },
+    { name = "opentelemetry-instrumentation-asgi" },
+    { name = "opentelemetry-semantic-conventions" },
+    { name = "opentelemetry-util-http" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3f/8f/c68dbef4be5db9330b0e9f492277b0dcdc8870d86de0c749b537406c590a/opentelemetry_instrumentation_fastapi-0.47b0.tar.gz", hash = "sha256:0c7c10b5d971e99a420678ffd16c5b1ea4f0db3b31b62faf305fbb03b4ebee36", size = 17332 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a5/29/a97842d6dfa679bf0f3624ce1ea3458eb185befd536cafe580daa9ab68ae/opentelemetry_instrumentation_fastapi-0.47b0-py3-none-any.whl", hash = "sha256:5ac28dd401160b02e4f544a85a9e4f61a8cbe5b077ea0379d411615376a2bd21", size = 11715 },
+]
+
+[[package]]
+name = "opentelemetry-proto"
+version = "1.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a9/06/9505ef04e527fa711ebffb47f3f56cac6015405953ff688fc349d170fb9c/opentelemetry_proto-1.26.0.tar.gz", hash = "sha256:c5c18796c0cab3751fc3b98dee53855835e90c0422924b484432ac852d93dc1e", size = 34749 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/15/f4/66a3892eea913cded9bac0fdd3fb1a412fa2da8eb50014ec87a52648444a/opentelemetry_proto-1.26.0-py3-none-any.whl", hash = "sha256:6c4d7b4d4d9c88543bcf8c28ae3f8f0448a753dc291c18c5390444c90b76a725", size = 52466 },
+]
+
+[[package]]
+name = "opentelemetry-sdk"
+version = "1.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "opentelemetry-api" },
+    { name = "opentelemetry-semantic-conventions" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d3/85/8ca0d5ebfe708287b091dffcd15553b74bbfe4532f8dd42662b78b2e0cab/opentelemetry_sdk-1.26.0.tar.gz", hash = "sha256:c90d2868f8805619535c05562d699e2f4fb1f00dbd55a86dcefca4da6fa02f85", size = 143139 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/92/f1/a9b550d0f9c049653dd2eab45cecf8fe4baa9795ed143d87834056ffabaf/opentelemetry_sdk-1.26.0-py3-none-any.whl", hash = "sha256:feb5056a84a88670c041ea0ded9921fca559efec03905dddeb3885525e0af897", size = 109475 },
+]
+
+[[package]]
+name = "opentelemetry-semantic-conventions"
+version = "0.47b0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "deprecated" },
+    { name = "opentelemetry-api" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/93/85/edef14d10ad00ddd9fffb20e4d3d938f4c5c1247e11a175066fe2b4a72f8/opentelemetry_semantic_conventions-0.47b0.tar.gz", hash = "sha256:a8d57999bbe3495ffd4d510de26a97dadc1dace53e0275001b2c1b2f67992a7e", size = 83994 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/00/c2/ca5cef8e4cd8eec5a95deed95ec3f6005e499fd9d17ca08731ced03a6921/opentelemetry_semantic_conventions-0.47b0-py3-none-any.whl", hash = "sha256:4ff9d595b85a59c1c1413f02bba320ce7ea6bf9e2ead2b0913c4395c7bbc1063", size = 138027 },
+]
+
+[[package]]
+name = "opentelemetry-util-http"
+version = "0.47b0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8f/b5/fb15aafe7391b6a36f5cd9bcb9f6c3efaeb87a0626e4d2dfef12f66ebf3e/opentelemetry_util_http-0.47b0.tar.gz", hash = "sha256:352a07664c18eef827eb8ddcbd64c64a7284a39dd1655e2f16f577eb046ccb32", size = 7863 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/10/7e/98749e14a4e3f4db8bc016e6b42aba40e4d934baeb8767b8658a99d0dfac/opentelemetry_util_http-0.47b0-py3-none-any.whl", hash = "sha256:3d3215e09c4a723b12da6d0233a31395aeb2bb33a64d7b15a1500690ba250f19", size = 6946 },
+]
+
+[[package]]
+name = "ordered-set"
+version = "4.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634 },
+]
+
+[[package]]
+name = "orjson"
+version = "3.10.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9e/03/821c8197d0515e46ea19439f5c5d5fd9a9889f76800613cfac947b5d7845/orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3", size = 5056450 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/89/c9/dd286c97c2f478d43839bd859ca4d9820e2177d4e07a64c516dc3e018062/orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2", size = 251312 },
+    { url = "https://files.pythonhosted.org/packages/b9/72/d90bd11e83a0e9623b3803b079478a93de8ec4316c98fa66110d594de5fa/orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09", size = 148125 },
+    { url = "https://files.pythonhosted.org/packages/9d/b6/ed61e87f327a4cbb2075ed0716e32ba68cb029aa654a68c3eb27803050d8/orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0", size = 147278 },
+    { url = "https://files.pythonhosted.org/packages/66/9f/e6a11b5d1ad11e9dc869d938707ef93ff5ed20b53d6cda8b5e2ac532a9d2/orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a", size = 152954 },
+    { url = "https://files.pythonhosted.org/packages/92/ee/702d5e8ccd42dc2b9d1043f22daa1ba75165616aa021dc19fb0c5a726ce8/orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e", size = 163953 },
+    { url = "https://files.pythonhosted.org/packages/d3/cb/55205f3f1ee6ba80c0a9a18ca07423003ca8de99192b18be30f1f31b4cdd/orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6", size = 141895 },
+    { url = "https://files.pythonhosted.org/packages/bb/ab/1185e472f15c00d37d09c395e478803ed0eae7a3a3d055a5f3885e1ea136/orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6", size = 170169 },
+    { url = "https://files.pythonhosted.org/packages/53/b9/10abe9089bdb08cd4218cc45eb7abfd787c82cf301cecbfe7f141542d7f4/orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0", size = 167808 },
+    { url = "https://files.pythonhosted.org/packages/8a/ad/26b40ccef119dcb0f4a39745ffd7d2d319152c1a52859b1ebbd114eca19c/orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f", size = 143010 },
+    { url = "https://files.pythonhosted.org/packages/e7/63/5f4101e4895b78ada568f4cf8f870dd594139ca2e75e654e373da78b03b0/orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5", size = 137307 },
+    { url = "https://files.pythonhosted.org/packages/14/7c/b4ecc2069210489696a36e42862ccccef7e49e1454a3422030ef52881b01/orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f", size = 251409 },
+    { url = "https://files.pythonhosted.org/packages/60/84/e495edb919ef0c98d054a9b6d05f2700fdeba3886edd58f1c4dfb25d514a/orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3", size = 147913 },
+    { url = "https://files.pythonhosted.org/packages/c5/27/e40bc7d79c4afb7e9264f22320c285d06d2c9574c9c682ba0f1be3012833/orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93", size = 147390 },
+    { url = "https://files.pythonhosted.org/packages/30/be/fd646fb1a461de4958a6eacf4ecf064b8d5479c023e0e71cc89b28fa91ac/orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313", size = 152973 },
+    { url = "https://files.pythonhosted.org/packages/b1/00/414f8d4bc5ec3447e27b5c26b4e996e4ef08594d599e79b3648f64da060c/orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864", size = 164039 },
+    { url = "https://files.pythonhosted.org/packages/a0/6b/34e6904ac99df811a06e42d8461d47b6e0c9b86e2fe7ee84934df6e35f0d/orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09", size = 142035 },
+    { url = "https://files.pythonhosted.org/packages/17/7e/254189d9b6df89660f65aec878d5eeaa5b1ae371bd2c458f85940445d36f/orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5", size = 169941 },
+    { url = "https://files.pythonhosted.org/packages/02/1a/d11805670c29d3a1b29fc4bd048dc90b094784779690592efe8c9f71249a/orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b", size = 167994 },
+    { url = "https://files.pythonhosted.org/packages/20/5f/03d89b007f9d6733dc11bc35d64812101c85d6c4e9c53af9fa7e7689cb11/orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb", size = 143130 },
+    { url = "https://files.pythonhosted.org/packages/c6/9d/9b9fb6c60b8a0e04031ba85414915e19ecea484ebb625402d968ea45b8d5/orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1", size = 137326 },
+    { url = "https://files.pythonhosted.org/packages/15/05/121af8a87513c56745d01ad7cf215c30d08356da9ad882ebe2ba890824cd/orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149", size = 251331 },
+    { url = "https://files.pythonhosted.org/packages/73/7f/8d6ccd64a6f8bdbfe6c9be7c58aeb8094aa52a01fbbb2cda42ff7e312bd7/orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe", size = 142012 },
+    { url = "https://files.pythonhosted.org/packages/04/65/f2a03fd1d4f0308f01d372e004c049f7eb9bc5676763a15f20f383fa9c01/orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c", size = 169920 },
+    { url = "https://files.pythonhosted.org/packages/e2/1c/3ef8d83d7c6a619ad3d69a4d5318591b4ce5862e6eda7c26bbe8208652ca/orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad", size = 167916 },
+    { url = "https://files.pythonhosted.org/packages/f2/0d/820a640e5a7dfbe525e789c70871ebb82aff73b0c7bf80082653f86b9431/orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2", size = 143089 },
+    { url = "https://files.pythonhosted.org/packages/1a/72/a424db9116c7cad2950a8f9e4aeb655a7b57de988eb015acd0fcd1b4609b/orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024", size = 137081 },
+]
+
+[[package]]
+name = "overrides"
+version = "7.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 },
+]
+
+[[package]]
+name = "packaging"
+version = "23.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fb/2b/9b9c33ffed44ee921d0967086d653047286054117d584f1b1a7c22ceaf7b/packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", size = 146714 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7", size = 53011 },
+]
+
+[[package]]
+name = "pandas"
+version = "2.2.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+    { name = "python-dateutil" },
+    { name = "pytz" },
+    { name = "tzdata" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/88/d9/ecf715f34c73ccb1d8ceb82fc01cd1028a65a5f6dbc57bfa6ea155119058/pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54", size = 4398391 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1b/70/61704497903d43043e288017cb2b82155c0d41e15f5c17807920877b45c2/pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288", size = 12574808 },
+    { url = "https://files.pythonhosted.org/packages/16/c6/75231fd47afd6b3f89011e7077f1a3958441264aca7ae9ff596e3276a5d0/pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151", size = 11304876 },
+    { url = "https://files.pythonhosted.org/packages/97/2d/7b54f80b93379ff94afb3bd9b0cd1d17b48183a0d6f98045bc01ce1e06a7/pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b", size = 15602548 },
+    { url = "https://files.pythonhosted.org/packages/fc/a5/4d82be566f069d7a9a702dcdf6f9106df0e0b042e738043c0cc7ddd7e3f6/pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee", size = 13031332 },
+    { url = "https://files.pythonhosted.org/packages/92/a2/b79c48f530673567805e607712b29814b47dcaf0d167e87145eb4b0118c6/pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db", size = 16286054 },
+    { url = "https://files.pythonhosted.org/packages/40/c7/47e94907f1d8fdb4868d61bd6c93d57b3784a964d52691b77ebfdb062842/pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1", size = 13879507 },
+    { url = "https://files.pythonhosted.org/packages/ab/63/966db1321a0ad55df1d1fe51505d2cdae191b84c907974873817b0a6e849/pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24", size = 11634249 },
+    { url = "https://files.pythonhosted.org/packages/dd/49/de869130028fb8d90e25da3b7d8fb13e40f5afa4c4af1781583eb1ff3839/pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef", size = 12500886 },
+    { url = "https://files.pythonhosted.org/packages/db/7c/9a60add21b96140e22465d9adf09832feade45235cd22f4cb1668a25e443/pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce", size = 11340320 },
+    { url = "https://files.pythonhosted.org/packages/b0/85/f95b5f322e1ae13b7ed7e97bd999160fa003424711ab4dc8344b8772c270/pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad", size = 15204346 },
+    { url = "https://files.pythonhosted.org/packages/40/10/79e52ef01dfeb1c1ca47a109a01a248754ebe990e159a844ece12914de83/pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad", size = 12733396 },
+    { url = "https://files.pythonhosted.org/packages/35/9d/208febf8c4eb5c1d9ea3314d52d8bd415fd0ef0dd66bb24cc5bdbc8fa71a/pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76", size = 15858913 },
+    { url = "https://files.pythonhosted.org/packages/99/d1/2d9bd05def7a9e08a92ec929b5a4c8d5556ec76fae22b0fa486cbf33ea63/pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32", size = 13417786 },
+    { url = "https://files.pythonhosted.org/packages/22/a5/a0b255295406ed54269814bc93723cfd1a0da63fb9aaf99e1364f07923e5/pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23", size = 11498828 },
+]
+
+[[package]]
+name = "passlib"
+version = "1.7.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554 },
+]
+
+[package.optional-dependencies]
+bcrypt = [
+    { name = "bcrypt" },
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
+]
+
+[[package]]
+name = "pcodedmp"
+version = "1.2.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "oletools" },
+    { name = "win-unicode-console", marker = "platform_python_implementation != 'PyPy' and platform_system == 'Windows'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3d/20/6d461e29135f474408d0d7f95b2456a9ba245560768ee51b788af10f7429/pcodedmp-1.2.6.tar.gz", hash = "sha256:025f8c809a126f45a082ffa820893e6a8d990d9d7ddb68694b5a9f0a6dbcd955", size = 35549 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ba/72/b380fb5c89d89c3afafac8cf02a71a45f4f4a4f35531ca949a34683962d1/pcodedmp-1.2.6-py2.py3-none-any.whl", hash = "sha256:4441f7c0ab4cbda27bd4668db3b14f36261d86e5059ce06c0828602cbe1c4278", size = 30939 },
+]
+
+[[package]]
+name = "peewee"
+version = "3.17.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bd/be/e9c886b4601a19f4c34a1b75c5fe8b98a2115dd964251a76b24c977c369d/peewee-3.17.6.tar.gz", hash = "sha256:cea5592c6f4da1592b7cff8eaf655be6648a1f5857469e30037bf920c03fb8fb", size = 2954075 }
+
+[[package]]
+name = "peewee-migrate"
+version = "1.12.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click" },
+    { name = "peewee" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/23/52/a2faf82c9872d2948935f9e9070ca1cb9e6bcf36d87fde01067ef2c88500/peewee_migrate-1.12.2.tar.gz", hash = "sha256:c8187c97b756909ea57e77cce06ae66395219e86764ef0b286a7bc72ff7405ad", size = 16406 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8c/32/de329eb77c16ebe2d52971d55954e4c34c7302ab285df8897b8d8dfd705e/peewee_migrate-1.12.2-py3-none-any.whl", hash = "sha256:2930bf83ef802cdb5fb123116c5eb87cbf3756cb27674f674923be6bb27dabee", size = 18580 },
+]
+
+[[package]]
+name = "pillow"
+version = "10.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265 },
+    { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655 },
+    { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304 },
+    { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804 },
+    { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126 },
+    { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541 },
+    { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616 },
+    { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802 },
+    { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213 },
+    { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498 },
+    { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219 },
+    { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350 },
+    { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980 },
+    { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799 },
+    { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973 },
+    { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054 },
+    { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484 },
+    { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375 },
+    { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773 },
+    { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690 },
+    { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951 },
+    { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427 },
+    { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685 },
+    { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883 },
+    { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837 },
+    { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562 },
+    { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761 },
+    { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767 },
+    { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989 },
+    { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255 },
+    { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603 },
+    { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972 },
+    { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375 },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f5/52/0763d1d976d5c262df53ddda8d8d4719eedf9594d046f117c25a27261a19/platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3", size = 20916 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/68/13/2aa1f0e1364feb2c9ef45302f387ac0bd81484e9c9a4c5688a322fbdfd08/platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", size = 18146 },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
+]
+
+[[package]]
+name = "posthog"
+version = "3.5.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "backoff" },
+    { name = "monotonic" },
+    { name = "python-dateutil" },
+    { name = "requests" },
+    { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/a6/a260008b95152d31ef19845ae3100411b481279ec98d22e8c87606abe78e/posthog-3.5.2.tar.gz", hash = "sha256:a383a80c1f47e0243f5ce359e81e06e2e7b37eb39d1d6f8d01c3e64ed29df2ee", size = 38380 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/53/62/2e7f75beedf9b5411f133a5af558cc7d76e20bbf6778a51ade15e6d3152b/posthog-3.5.2-py2.py3-none-any.whl", hash = "sha256:605b3d92369971cc99290b1fcc8534cbddac3726ef7972caa993454a5ecfb644", size = 41545 },
+]
+
+[[package]]
+name = "primp"
+version = "0.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/63/9c/10d2c7b734228021cf17d92b2872ed53535103d29a38a6ad4eee89a8ae1b/primp-0.6.3.tar.gz", hash = "sha256:17d30ebe26864defad5232dbbe1372e80483940012356e1f68846bb182282039", size = 78662 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/61/09/96e8327fd8c1224226d9a170daf1dcba7d3f6578edeb9f811803f2a61aba/primp-0.6.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bdbe6a7cdaaf5c9ed863432a941f4a75bd4c6ff626cbc8d32fc232793c70ba06", size = 2719331 },
+    { url = "https://files.pythonhosted.org/packages/b3/a1/b58ac752b0500208df1be3b762eeaff1a117ec3108e26b38821ae9ac31e0/primp-0.6.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:eeb53eb987bdcbcd85740633470255cab887d921df713ffa12a36a13366c9cdb", size = 2517811 },
+    { url = "https://files.pythonhosted.org/packages/f7/85/ca027d7ec6121346d5927205777ad74bf918f291a538d710b5f9e0957333/primp-0.6.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78da53d3c92a8e3f05bd3286ac76c291f1b6fe5e08ea63b7ba92b0f9141800bb", size = 2826729 },
+    { url = "https://files.pythonhosted.org/packages/3a/45/ff51ccc5dbc82afa6d2dda15bf07b21b6b44708d02934cd7562007d1f719/primp-0.6.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:86337b44deecdac752bd8112909987fc9fa9b894f30191c80a164dc8f895da53", size = 2739692 },
+    { url = "https://files.pythonhosted.org/packages/74/a1/a626fb8d2f6499d3e05971b2dcd33d770244722ee52f7d2c4ab636c1157f/primp-0.6.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d3cd9a22b97f3eae42b2a5fb99f00480daf4cd6d9b139e05b0ffb03f7cc037f3", size = 2900573 },
+    { url = "https://files.pythonhosted.org/packages/09/55/c96cb510c9f7881fa01bc7b269e446d32635ab9f0adbd36918c69b45a140/primp-0.6.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7732bec917e2d3c48a31cdb92e1250f4ad6203a1aa4f802bd9abd84f2286a1e0", size = 3058818 },
+    { url = "https://files.pythonhosted.org/packages/98/de/5c1dab24c1bff7933d0e4e8f4d0f46b66fc23531258d8433789c4468cac3/primp-0.6.3-cp38-abi3-win_amd64.whl", hash = "sha256:1e4113c34b86c676ae321af185f03a372caef3ee009f1682c2d62e30ec87348c", size = 2757631 },
+]
+
+[[package]]
+name = "proto-plus"
+version = "1.24.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3e/fc/e9a65cd52c1330d8d23af6013651a0bc50b6d76bcbdf91fae7cd19c68f29/proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445", size = 55942 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7c/6f/db31f0711c0402aa477257205ce7d29e86a75cb52cd19f7afb585f75cda0/proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12", size = 50080 },
+]
+
+[[package]]
+name = "protobuf"
+version = "4.25.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/ab/cb61a4b87b2e7e6c312dce33602bd5884797fd054e0e53205f1c27cf0f66/protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d", size = 380283 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c8/43/27b48d9040763b78177d3083e16c70dba6e3c3ee2af64b659f6332c2b06e/protobuf-4.25.4-cp310-abi3-win32.whl", hash = "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4", size = 392409 },
+    { url = "https://files.pythonhosted.org/packages/0c/d4/589d673ada9c4c62d5f155218d7ff7ac796efb9c6af95b0bd29d438ae16e/protobuf-4.25.4-cp310-abi3-win_amd64.whl", hash = "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d", size = 413398 },
+    { url = "https://files.pythonhosted.org/packages/34/ca/bf85ffe3dd16f1f2aaa6c006da8118800209af3da160ae4d4f47500eabd9/protobuf-4.25.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b", size = 394160 },
+    { url = "https://files.pythonhosted.org/packages/68/1d/e8961af9a8e534d66672318d6b70ea8e3391a6b13e16a29b039e4a99c214/protobuf-4.25.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835", size = 293700 },
+    { url = "https://files.pythonhosted.org/packages/ca/6c/cc7ab2fb3a4a7f07f211d8a7bbb76bba633eb09b148296dbd4281e217f95/protobuf-4.25.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040", size = 294612 },
+    { url = "https://files.pythonhosted.org/packages/b5/95/0ba7f66934a0a798006f06fc3d74816da2b7a2bcfd9b98c53d26f684c89e/protobuf-4.25.4-py3-none-any.whl", hash = "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978", size = 156464 },
+]
+
+[[package]]
+name = "psutil"
+version = "6.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/c7/8c6872f7372eb6a6b2e4708b88419fb46b857f7a2e1892966b851cc79fc9/psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2", size = 508067 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c5/66/78c9c3020f573c58101dc43a44f6855d01bbbd747e24da2f0c4491200ea3/psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35", size = 249766 },
+    { url = "https://files.pythonhosted.org/packages/e1/3f/2403aa9558bea4d3854b0e5e567bc3dd8e9fbc1fc4453c0aa9aafeb75467/psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1", size = 253024 },
+    { url = "https://files.pythonhosted.org/packages/0b/37/f8da2fbd29690b3557cca414c1949f92162981920699cd62095a984983bf/psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0", size = 250961 },
+    { url = "https://files.pythonhosted.org/packages/35/56/72f86175e81c656a01c4401cd3b1c923f891b31fbcebe98985894176d7c9/psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0", size = 287478 },
+    { url = "https://files.pythonhosted.org/packages/19/74/f59e7e0d392bc1070e9a70e2f9190d652487ac115bb16e2eff6b22ad1d24/psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd", size = 290455 },
+    { url = "https://files.pythonhosted.org/packages/cd/5f/60038e277ff0a9cc8f0c9ea3d0c5eb6ee1d2470ea3f9389d776432888e47/psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132", size = 292046 },
+    { url = "https://files.pythonhosted.org/packages/8b/20/2ff69ad9c35c3df1858ac4e094f20bd2374d33c8643cf41da8fd7cdcb78b/psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d", size = 253560 },
+    { url = "https://files.pythonhosted.org/packages/73/44/561092313ae925f3acfaace6f9ddc4f6a9c748704317bad9c8c8f8a36a79/psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3", size = 257399 },
+    { url = "https://files.pythonhosted.org/packages/7c/06/63872a64c312a24fb9b4af123ee7007a306617da63ff13bcc1432386ead7/psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0", size = 251988 },
+]
+
+[[package]]
+name = "psycopg2-binary"
+version = "2.9.9"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/07/e720e53bfab016ebcc34241695ccc06a9e3d91ba19b40ca81317afbdc440/psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c", size = 384973 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a5/ac/702d300f3df169b9d0cbef0340d9f34a78bc18dc2dbafbcb39ff0f165cf8/psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26", size = 2822581 },
+    { url = "https://files.pythonhosted.org/packages/7a/1f/a6cf0cdf944253f7c45d90fbc876cc8bed5cc9942349306245715c0d88d6/psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f", size = 2552633 },
+    { url = "https://files.pythonhosted.org/packages/81/0b/3adf561107c865928455891156d1dde5325253f7f4316fe56cd2c3f73570/psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2", size = 2851075 },
+    { url = "https://files.pythonhosted.org/packages/f7/98/c2fedcbf0a9607519a010dcf88571138b2251062dbde3610cdba5ba1eee1/psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0", size = 3080509 },
+    { url = "https://files.pythonhosted.org/packages/c2/05/81e8bc7fca95574c9323e487d9ce1b58a4cfcc17f89b8fe843af46361211/psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53", size = 3264303 },
+    { url = "https://files.pythonhosted.org/packages/ce/85/62825cabc6aad53104b7b6d12eb2ad74737d268630032d07b74d4444cb72/psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be", size = 3019515 },
+    { url = "https://files.pythonhosted.org/packages/e9/b0/9ca2b8e01a0912c9a14234fd5df7a241a1e44778c5797bf4b8eaa8dc3d3a/psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27", size = 2355892 },
+    { url = "https://files.pythonhosted.org/packages/73/17/ba28bb0022db5e2015a82d2df1c4b0d419c37fa07a588b3aff3adc4939f6/psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359", size = 2534903 },
+    { url = "https://files.pythonhosted.org/packages/3b/92/b463556409cdc12791cd8b1dae0072bf8efe817ef68b7ea3d9cf7d0e5656/psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2", size = 2486597 },
+    { url = "https://files.pythonhosted.org/packages/92/57/96576e07132d7f7a1ac1df939575e6fdd8951aea337ee152b586bb51a971/psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc", size = 2454908 },
+    { url = "https://files.pythonhosted.org/packages/7c/ae/cedd56e1f4a2b0e37213283caf3733a875c4c76f3372241e19c0d2a87355/psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d", size = 1024240 },
+    { url = "https://files.pythonhosted.org/packages/25/1f/7ae31759142999a8d06b3e250c1346c4abcdcada8fa884376775dc1de686/psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417", size = 1163655 },
+    { url = "https://files.pythonhosted.org/packages/a7/d0/5f2db14e7b53552276ab613399a83f83f85b173a862d3f20580bc7231139/psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf", size = 2823784 },
+    { url = "https://files.pythonhosted.org/packages/18/ca/da384fd47233e300e3e485c90e7aab5d7def896d1281239f75901faf87d4/psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d", size = 2553308 },
+    { url = "https://files.pythonhosted.org/packages/50/66/fa53d2d3d92f6e1ef469d92afc6a4fe3f6e8a9a04b687aa28fb1f1d954ee/psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212", size = 2851283 },
+    { url = "https://files.pythonhosted.org/packages/04/37/2429360ac5547378202db14eec0dde76edbe1f6627df5a43c7e164922859/psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493", size = 3081839 },
+    { url = "https://files.pythonhosted.org/packages/62/2a/c0530b59d7e0d09824bc2102ecdcec0456b8ca4d47c0caa82e86fce3ed4c/psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996", size = 3264488 },
+    { url = "https://files.pythonhosted.org/packages/19/57/9f172b900795ea37246c78b5f52e00f4779984370855b3e161600156906d/psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119", size = 3020700 },
+    { url = "https://files.pythonhosted.org/packages/94/68/1176fc14ea76861b7b8360be5176e87fb20d5091b137c76570eb4e237324/psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba", size = 2355968 },
+    { url = "https://files.pythonhosted.org/packages/70/bb/aec2646a705a09079d008ce88073401cd61fc9b04f92af3eb282caa3a2ec/psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07", size = 2536101 },
+    { url = "https://files.pythonhosted.org/packages/14/33/12818c157e333cb9d9e6753d1b2463b6f60dbc1fade115f8e4dc5c52cac4/psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb", size = 2487064 },
+    { url = "https://files.pythonhosted.org/packages/56/a2/7851c68fe8768f3c9c246198b6356ee3e4a8a7f6820cc798443faada3400/psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe", size = 2456257 },
+    { url = "https://files.pythonhosted.org/packages/6f/ee/3ba07c6dc7c3294e717e94720da1597aedc82a10b1b180203ce183d4631a/psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93", size = 1024709 },
+    { url = "https://files.pythonhosted.org/packages/7b/08/9c66c269b0d417a0af9fb969535f0371b8c538633535a7a6a5ca3f9231e2/psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab", size = 1163864 },
+]
+
+[[package]]
+name = "pyarrow"
+version = "17.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/27/4e/ea6d43f324169f8aec0e57569443a38bab4b398d09769ca64f7b4d467de3/pyarrow-17.0.0.tar.gz", hash = "sha256:4beca9521ed2c0921c1023e68d097d0299b62c362639ea315572a58f3f50fd28", size = 1112479 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f9/46/ce89f87c2936f5bb9d879473b9663ce7a4b1f4359acc2f0eb39865eaa1af/pyarrow-17.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:1c8856e2ef09eb87ecf937104aacfa0708f22dfeb039c363ec99735190ffb977", size = 29028748 },
+    { url = "https://files.pythonhosted.org/packages/8d/8e/ce2e9b2146de422f6638333c01903140e9ada244a2a477918a368306c64c/pyarrow-17.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e19f569567efcbbd42084e87f948778eb371d308e137a0f97afe19bb860ccb3", size = 27190965 },
+    { url = "https://files.pythonhosted.org/packages/3b/c8/5675719570eb1acd809481c6d64e2136ffb340bc387f4ca62dce79516cea/pyarrow-17.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b244dc8e08a23b3e352899a006a26ae7b4d0da7bb636872fa8f5884e70acf15", size = 39269081 },
+    { url = "https://files.pythonhosted.org/packages/5e/78/3931194f16ab681ebb87ad252e7b8d2c8b23dad49706cadc865dff4a1dd3/pyarrow-17.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b72e87fe3e1db343995562f7fff8aee354b55ee83d13afba65400c178ab2597", size = 39864921 },
+    { url = "https://files.pythonhosted.org/packages/d8/81/69b6606093363f55a2a574c018901c40952d4e902e670656d18213c71ad7/pyarrow-17.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dc5c31c37409dfbc5d014047817cb4ccd8c1ea25d19576acf1a001fe07f5b420", size = 38740798 },
+    { url = "https://files.pythonhosted.org/packages/4c/21/9ca93b84b92ef927814cb7ba37f0774a484c849d58f0b692b16af8eebcfb/pyarrow-17.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e3343cb1e88bc2ea605986d4b94948716edc7a8d14afd4e2c097232f729758b4", size = 39871877 },
+    { url = "https://files.pythonhosted.org/packages/30/d1/63a7c248432c71c7d3ee803e706590a0b81ce1a8d2b2ae49677774b813bb/pyarrow-17.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a27532c38f3de9eb3e90ecab63dfda948a8ca859a66e3a47f5f42d1e403c4d03", size = 25151089 },
+    { url = "https://files.pythonhosted.org/packages/d4/62/ce6ac1275a432b4a27c55fe96c58147f111d8ba1ad800a112d31859fae2f/pyarrow-17.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9b8a823cea605221e61f34859dcc03207e52e409ccf6354634143e23af7c8d22", size = 29019418 },
+    { url = "https://files.pythonhosted.org/packages/8e/0a/dbd0c134e7a0c30bea439675cc120012337202e5fac7163ba839aa3691d2/pyarrow-17.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1e70de6cb5790a50b01d2b686d54aaf73da01266850b05e3af2a1bc89e16053", size = 27152197 },
+    { url = "https://files.pythonhosted.org/packages/cb/05/3f4a16498349db79090767620d6dc23c1ec0c658a668d61d76b87706c65d/pyarrow-17.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0071ce35788c6f9077ff9ecba4858108eebe2ea5a3f7cf2cf55ebc1dbc6ee24a", size = 39263026 },
+    { url = "https://files.pythonhosted.org/packages/c2/0c/ea2107236740be8fa0e0d4a293a095c9f43546a2465bb7df34eee9126b09/pyarrow-17.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:757074882f844411fcca735e39aae74248a1531367a7c80799b4266390ae51cc", size = 39880798 },
+    { url = "https://files.pythonhosted.org/packages/f6/b0/b9164a8bc495083c10c281cc65064553ec87b7537d6f742a89d5953a2a3e/pyarrow-17.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ba11c4f16976e89146781a83833df7f82077cdab7dc6232c897789343f7891a", size = 38715172 },
+    { url = "https://files.pythonhosted.org/packages/f1/c4/9625418a1413005e486c006e56675334929fad864347c5ae7c1b2e7fe639/pyarrow-17.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b0c6ac301093b42d34410b187bba560b17c0330f64907bfa4f7f7f2444b0cf9b", size = 39874508 },
+    { url = "https://files.pythonhosted.org/packages/ae/49/baafe2a964f663413be3bd1cf5c45ed98c5e42e804e2328e18f4570027c1/pyarrow-17.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:392bc9feabc647338e6c89267635e111d71edad5fcffba204425a7c8d13610d7", size = 25099235 },
+]
+
+[[package]]
+name = "pyasn1"
+version = "0.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4a/a3/d2157f333900747f20984553aca98008b6dc843eb62f3a36030140ccec0d/pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", size = 148088 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/23/7e/5f50d07d5e70a2addbccd90ac2950f81d1edd0783630651d9268d7f1db49/pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473", size = 85313 },
+]
+
+[[package]]
+name = "pyasn1-modules"
+version = "0.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pyasn1" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f7/00/e7bd1dec10667e3f2be602686537969a7ac92b0a7c5165be2e5875dc3971/pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6", size = 307859 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/13/68/8906226b15ef38e71dc926c321d2fe99de8048e9098b5dfd38343011c886/pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b", size = 181220 },
+]
+
+[[package]]
+name = "pyclipper"
+version = "1.3.0.post5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1b/3d/e5b5ff36b24f3fc9b962a68ce4f6932ab698b8ba860261f402be37b85d17/pyclipper-1.3.0.post5.tar.gz", hash = "sha256:c0239f928e0bf78a3efc2f2f615a10bfcdb9f33012d46d64c8d1225b4bde7096", size = 164719 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/21/47/9c6a9d2523735d7a5ec2991e6a05370b96e19db26c5628fedd1143dc6e4f/pyclipper-1.3.0.post5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b7a983ae019932bfa0a1971a2dc8c856704add5f3d567bed8fac02dbc0e7f0bf", size = 279155 },
+    { url = "https://files.pythonhosted.org/packages/05/f0/3e4ca96c1adb32f254ba0ba3a5a4cf4bd6794c285177f10357f3574d11d5/pyclipper-1.3.0.post5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8760075c395b924f894aa16ee06e8c040c6f9b63e0903e49de3cc8d82d9e637", size = 146859 },
+    { url = "https://files.pythonhosted.org/packages/ed/79/64cfb4bf0338c3dcd4ef4b819f0fb48a65bc9a9b5b2644cf21a665d08ae8/pyclipper-1.3.0.post5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4ea61ca5899d3346c614951342c506f119601ed0a1f4889a9cc236558afec6b", size = 952640 },
+    { url = "https://files.pythonhosted.org/packages/43/64/9c8e0c7d96d32c63e38f92da92e4e38685e30773644d9dcb73d2325beb47/pyclipper-1.3.0.post5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46499b361ae067662b22578401d83d57716f3cc0071d592feb07d504b439fea7", size = 971470 },
+    { url = "https://files.pythonhosted.org/packages/da/ff/29f1fa1473d6c8abaa2f41f60dfeb23e92819ee67f7d9387715e53e2a414/pyclipper-1.3.0.post5-cp311-cp311-win32.whl", hash = "sha256:d5c77e39ab05a6cf277c819639968b21e6959e996ea1a074afc24236541708ff", size = 99360 },
+    { url = "https://files.pythonhosted.org/packages/f3/ec/56da9f2d5d846f144530d5313a05078afb7cfc26ec179be5af35f057d064/pyclipper-1.3.0.post5-cp311-cp311-win_amd64.whl", hash = "sha256:0f78a1c18ff4f9276f78d9353d6ed4309c3886a9d0172437e48328aef499165e", size = 108311 },
+    { url = "https://files.pythonhosted.org/packages/87/f0/2a9dbd3359bd834b24691ba65b1011c1a7a7cafd92691165506ece1eeb3b/pyclipper-1.3.0.post5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5237282f906049c307e6c90333c7d56f6b8712bf087ef97b141830c40b09ca0a", size = 278102 },
+    { url = "https://files.pythonhosted.org/packages/28/30/1b532eff31728e0233684eada4153773af11a81992bb9791160ed27760af/pyclipper-1.3.0.post5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aca8635573646b65c054399433fb3493637f1445db942de8a52fca9ef493ba3d", size = 145946 },
+    { url = "https://files.pythonhosted.org/packages/b0/95/e0d4c036c1c936b6f7e6265d15f56b5b3ceb4a4d7dcb491cdd2604882f93/pyclipper-1.3.0.post5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1158a2b13d59bdfab33d1d928f7b72c8c7fb8a76e7d2283839cb45d7c0ff2140", size = 947205 },
+    { url = "https://files.pythonhosted.org/packages/22/f3/c5b39f3515d7af0c96b67f6eb13b62d0cd471f348ebafa106d6fcb8d9d33/pyclipper-1.3.0.post5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a041f1a7982b17cf92fd3be349ec41ff1901792149c166bf283f469567b52d6", size = 966618 },
+    { url = "https://files.pythonhosted.org/packages/0a/67/1fe463403bbd2ea7ca79f328a118b12fff495d0e83c98bdf5afd187ccccc/pyclipper-1.3.0.post5-cp312-cp312-win32.whl", hash = "sha256:bf3a2ccd6e4e078250b0a31a12c519b0be6d1bc160acfceee62407dbd68558f6", size = 98762 },
+    { url = "https://files.pythonhosted.org/packages/a1/f0/760e614b84dd4d8f03dd5432dda100d699e23074c263b38b6c117adc8395/pyclipper-1.3.0.post5-cp312-cp312-win_amd64.whl", hash = "sha256:2ce6e0a6ab32182c26537965cf521822cd11a28a7ffcef48635a94c6ca8559ef", size = 108190 },
+]
+
+[[package]]
+name = "pycparser"
+version = "2.22"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 },
+]
+
+[[package]]
+name = "pydantic"
+version = "2.8.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "annotated-types" },
+    { name = "pydantic-core" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8c/99/d0a5dca411e0a017762258013ba9905cd6e7baa9a3fd1fe8b6529472902e/pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a", size = 739834 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1f/fa/b7f815b8c9ad021c07f88875b601222ef5e70619391ade4a49234d12d278/pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8", size = 423875 },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.20.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/12/e3/0d5ad91211dba310f7ded335f4dad871172b9cc9ce204f5a56d76ccd6247/pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4", size = 388371 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/61/db/f6a724db226d990a329910727cfac43539ff6969edc217286dd05cda3ef6/pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312", size = 1834507 },
+    { url = "https://files.pythonhosted.org/packages/9b/83/6f2bfe75209d557ae1c3550c1252684fc1827b8b12fbed84c3b4439e135d/pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88", size = 1773527 },
+    { url = "https://files.pythonhosted.org/packages/93/ef/513ea76d7ca81f2354bb9c8d7839fc1157673e652613f7e1aff17d8ce05d/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc", size = 1787879 },
+    { url = "https://files.pythonhosted.org/packages/31/0a/ac294caecf235f0cc651de6232f1642bb793af448d1cfc541b0dc1fd72b8/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43", size = 1774694 },
+    { url = "https://files.pythonhosted.org/packages/46/a4/08f12b5512f095963550a7cb49ae010e3f8f3f22b45e508c2cb4d7744fce/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6", size = 1976369 },
+    { url = "https://files.pythonhosted.org/packages/15/59/b2495be4410462aedb399071c71884042a2c6443319cbf62d00b4a7ed7a5/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121", size = 2691250 },
+    { url = "https://files.pythonhosted.org/packages/3c/ae/fc99ce1ba791c9e9d1dee04ce80eef1dae5b25b27e3fc8e19f4e3f1348bf/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1", size = 2061462 },
+    { url = "https://files.pythonhosted.org/packages/44/bb/eb07cbe47cfd638603ce3cb8c220f1a054b821e666509e535f27ba07ca5f/pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b", size = 1893923 },
+    { url = "https://files.pythonhosted.org/packages/ce/ef/5a52400553b8faa0e7f11fd7a2ba11e8d2feb50b540f9e7973c49b97eac0/pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27", size = 1966779 },
+    { url = "https://files.pythonhosted.org/packages/4c/5b/fb37fe341344d9651f5c5f579639cd97d50a457dc53901aa8f7e9f28beb9/pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b", size = 2109044 },
+    { url = "https://files.pythonhosted.org/packages/70/1a/6f7278802dbc66716661618807ab0dfa4fc32b09d1235923bbbe8b3a5757/pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a", size = 1708265 },
+    { url = "https://files.pythonhosted.org/packages/35/7f/58758c42c61b0bdd585158586fecea295523d49933cb33664ea888162daf/pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2", size = 1901750 },
+    { url = "https://files.pythonhosted.org/packages/6f/47/ef0d60ae23c41aced42921728650460dc831a0adf604bfa66b76028cb4d0/pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231", size = 1839225 },
+    { url = "https://files.pythonhosted.org/packages/6a/23/430f2878c9cd977a61bb39f71751d9310ec55cee36b3d5bf1752c6341fd0/pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9", size = 1768604 },
+    { url = "https://files.pythonhosted.org/packages/9e/2b/ec4e7225dee79e0dc80ccc3c35ab33cc2c4bbb8a1a7ecf060e5e453651ec/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f", size = 1789767 },
+    { url = "https://files.pythonhosted.org/packages/64/b0/38b24a1fa6d2f96af3148362e10737ec073768cd44d3ec21dca3be40a519/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52", size = 1772061 },
+    { url = "https://files.pythonhosted.org/packages/5e/da/bb73274c42cb60decfa61e9eb0c9029da78b3b9af0a9de0309dbc8ff87b6/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237", size = 1974573 },
+    { url = "https://files.pythonhosted.org/packages/c8/65/41693110fb3552556180460daffdb8bbeefb87fc026fd9aa4b849374015c/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe", size = 2625596 },
+    { url = "https://files.pythonhosted.org/packages/09/b3/a5a54b47cccd1ab661ed5775235c5e06924753c2d4817737c5667bfa19a8/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e", size = 2099064 },
+    { url = "https://files.pythonhosted.org/packages/52/fa/443a7a6ea54beaba45ff3a59f3d3e6e3004b7460bcfb0be77bcf98719d3b/pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24", size = 1900345 },
+    { url = "https://files.pythonhosted.org/packages/8e/e6/9aca9ffae60f9cdf0183069de3e271889b628d0fb175913fcb3db5618fb1/pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1", size = 1968252 },
+    { url = "https://files.pythonhosted.org/packages/46/5e/6c716810ea20a6419188992973a73c2fb4eb99cd382368d0637ddb6d3c99/pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd", size = 2119191 },
+    { url = "https://files.pythonhosted.org/packages/06/fc/6123b00a9240fbb9ae0babad7a005d51103d9a5d39c957a986f5cdd0c271/pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688", size = 1717788 },
+    { url = "https://files.pythonhosted.org/packages/d5/36/e61ad5a46607a469e2786f398cd671ebafcd9fb17f09a2359985c7228df5/pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d", size = 1898188 },
+    { url = "https://files.pythonhosted.org/packages/49/75/40b0e98b658fdba02a693b3bacb4c875a28bba87796c7b13975976597d8c/pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686", size = 1838688 },
+    { url = "https://files.pythonhosted.org/packages/75/02/d8ba2d4a266591a6a623c68b331b96523d4b62ab82a951794e3ed8907390/pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a", size = 1768409 },
+    { url = "https://files.pythonhosted.org/packages/91/ae/25ecd9bc4ce4993e99a1a3c9ab111c082630c914260e129572fafed4ecc2/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b", size = 1789317 },
+    { url = "https://files.pythonhosted.org/packages/7a/80/72057580681cdbe55699c367963d9c661b569a1d39338b4f6239faf36cdc/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19", size = 1771949 },
+    { url = "https://files.pythonhosted.org/packages/a2/be/d9bbabc55b05019013180f141fcaf3b14dbe15ca7da550e95b60c321009a/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac", size = 1974392 },
+    { url = "https://files.pythonhosted.org/packages/79/2d/7bcd938c6afb0f40293283f5f09988b61fb0a4f1d180abe7c23a2f665f8e/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703", size = 2625565 },
+    { url = "https://files.pythonhosted.org/packages/ac/88/ca758e979457096008a4b16a064509028e3e092a1e85a5ed6c18ced8da88/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c", size = 2098784 },
+    { url = "https://files.pythonhosted.org/packages/eb/de/2fad6d63c3c42e472e985acb12ec45b7f56e42e6f4cd6dfbc5e87ee8678c/pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83", size = 1900198 },
+    { url = "https://files.pythonhosted.org/packages/fe/50/077c7f35b6488dc369a6d22993af3a37901e198630f38ac43391ca730f5b/pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203", size = 1968005 },
+    { url = "https://files.pythonhosted.org/packages/5d/1f/f378631574ead46d636b9a04a80ff878b9365d4b361b1905ef1667d4182a/pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0", size = 2118920 },
+    { url = "https://files.pythonhosted.org/packages/7a/ea/e4943f17df7a3031d709481fe4363d4624ae875a6409aec34c28c9e6cf59/pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e", size = 1717397 },
+    { url = "https://files.pythonhosted.org/packages/13/63/b95781763e8d84207025071c0cec16d921c0163c7a9033ae4b9a0e020dc7/pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20", size = 1898013 },
+]
+
+[[package]]
+name = "pydub"
+version = "0.25.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327 },
+]
+
+[[package]]
+name = "pygments"
+version = "2.18.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
+]
+
+[[package]]
+name = "pyjwt"
+version = "2.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 },
+]
+
+[package.optional-dependencies]
+crypto = [
+    { name = "cryptography" },
+]
+
+[[package]]
+name = "pymilvus"
+version = "2.4.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "environs" },
+    { name = "grpcio" },
+    { name = "milvus-lite", marker = "sys_platform != 'win32'" },
+    { name = "pandas" },
+    { name = "protobuf" },
+    { name = "setuptools" },
+    { name = "ujson" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/40/20/203f77e8c35cac3e462307ba216c00525fd895618921d918fe814d426155/pymilvus-2.4.6.tar.gz", hash = "sha256:6ac3eb91c92cc01bbe444fe83f895f02d7b2546d96ac67998630bf31ac074d66", size = 1214006 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1b/41/af144715fc246ee2232e609bf93ab7cfd4b417e14087606fd6d93cce7336/pymilvus-2.4.6-py3-none-any.whl", hash = "sha256:b4c43472edc313b845d313be50610e19054e6954b2c5c3b515565c596c2d3d97", size = 197837 },
+]
+
+[[package]]
+name = "pymongo"
+version = "4.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "dnspython" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/05/2c/ad0896cb94668c3cad1eb702ab60ae17036b051f54cfe547f11a0322f1d3/pymongo-4.8.0.tar.gz", hash = "sha256:454f2295875744dc70f1881e4b2eb99cdad008a33574bc8aaf120530f66c0cde", size = 1506091 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/0a/3d/bba2845c76dddcd8c34d5014da80346851df048eefa826acb13265affba2/pymongo-4.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b50040d9767197b77ed420ada29b3bf18a638f9552d80f2da817b7c4a4c9c68", size = 645578 },
+    { url = "https://files.pythonhosted.org/packages/c2/ca/d177c3ad846bad631b548b27c261821d25a08d608dca134aedb1b00b98fe/pymongo-4.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:417369ce39af2b7c2a9c7152c1ed2393edfd1cbaf2a356ba31eb8bcbd5c98dd7", size = 645731 },
+    { url = "https://files.pythonhosted.org/packages/be/1a/3d9b9fb3f9de9da46919fef900fe88090f5865a09ae9e0e19496a603a819/pymongo-4.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf821bd3befb993a6db17229a2c60c1550e957de02a6ff4dd0af9476637b2e4d", size = 1399930 },
+    { url = "https://files.pythonhosted.org/packages/57/64/281c9c8efb98ab6c6fcf44bf7cc33e17bcb163cb9c9260c9d78d2318d013/pymongo-4.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9365166aa801c63dff1a3cb96e650be270da06e3464ab106727223123405510f", size = 1451584 },
+    { url = "https://files.pythonhosted.org/packages/37/ed/5258d22a91ea6e0b9d72e0aa7674f5a9951fea0c036d1063f29bc45a35d2/pymongo-4.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc8b8582f4209c2459b04b049ac03c72c618e011d3caa5391ff86d1bda0cc486", size = 1423899 },
+    { url = "https://files.pythonhosted.org/packages/f3/7f/6d231046d9caf43395f9406dbef885f122edbee172ec6a3a6ea330e07848/pymongo-4.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e5019f75f6827bb5354b6fef8dfc9d6c7446894a27346e03134d290eb9e758", size = 1397112 },
+    { url = "https://files.pythonhosted.org/packages/af/81/4074148396415ac19074a1a144e1cd6b2ff000f5ef253ed24a4e3e9ff340/pymongo-4.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b5802151fc2b51cd45492c80ed22b441d20090fb76d1fd53cd7760b340ff554", size = 1357689 },
+    { url = "https://files.pythonhosted.org/packages/bc/26/799fe943573b2d86970698a0667d8d8636790e86242d979f4b3d870d269f/pymongo-4.8.0-cp311-cp311-win32.whl", hash = "sha256:4bf58e6825b93da63e499d1a58de7de563c31e575908d4e24876234ccb910eba", size = 611133 },
+    { url = "https://files.pythonhosted.org/packages/51/28/577224211f43e2079126bfec53080efba46e59218f47808098f125139558/pymongo-4.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:b747c0e257b9d3e6495a018309b9e0c93b7f0d65271d1d62e572747f4ffafc88", size = 630990 },
+    { url = "https://files.pythonhosted.org/packages/9e/8d/b082d026f96215a76553032620549f931679da7f941018e2c358fd549faa/pymongo-4.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e6a720a3d22b54183352dc65f08cd1547204d263e0651b213a0a2e577e838526", size = 699090 },
+    { url = "https://files.pythonhosted.org/packages/eb/da/fa51bb7d8d5c8b4672b72c05a9357b5f9300f48128574c746fa4825f607a/pymongo-4.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31e4d21201bdf15064cf47ce7b74722d3e1aea2597c6785882244a3bb58c7eab", size = 698800 },
+    { url = "https://files.pythonhosted.org/packages/7b/dc/78f0c931d38bece6ae1dc49035961c82f3eb42952c745391ebdd3a910222/pymongo-4.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b804bb4f2d9dc389cc9e827d579fa327272cdb0629a99bfe5b83cb3e269ebf", size = 1655527 },
+    { url = "https://files.pythonhosted.org/packages/74/36/92f0eeeb5111c332072e37efb1d5a668c5e4b75be53cbd06a77f6b4192d2/pymongo-4.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fbdb87fe5075c8beb17a5c16348a1ea3c8b282a5cb72d173330be2fecf22f5", size = 1718203 },
+    { url = "https://files.pythonhosted.org/packages/98/40/757579f837dadaddf167cd36ae85a7ab29c035bc0ae8d90bdc8a5fbdfc33/pymongo-4.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd39455b7ee70aabee46f7399b32ab38b86b236c069ae559e22be6b46b2bbfc4", size = 1685776 },
+    { url = "https://files.pythonhosted.org/packages/24/bb/13d23966ad01511610a471eae480bcb6a94b832c40f2bdbc706f7a757b76/pymongo-4.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:940d456774b17814bac5ea7fc28188c7a1338d4a233efbb6ba01de957bded2e8", size = 1650569 },
+    { url = "https://files.pythonhosted.org/packages/b5/80/1f405ce80cb6a3867709147e24a2f69e342ff71fb1b9ba663d0237f0c5ed/pymongo-4.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:236bbd7d0aef62e64caf4b24ca200f8c8670d1a6f5ea828c39eccdae423bc2b2", size = 1601592 },
+    { url = "https://files.pythonhosted.org/packages/30/19/cd66230b6407c6b8cf45c1ae073659a88af5699c792c46fd4eaf317bd11e/pymongo-4.8.0-cp312-cp312-win32.whl", hash = "sha256:47ec8c3f0a7b2212dbc9be08d3bf17bc89abd211901093e3ef3f2adea7de7a69", size = 656042 },
+    { url = "https://files.pythonhosted.org/packages/99/1c/f5108dc39450077556844abfd92b768c57775f85270fc0b1dc834ad18113/pymongo-4.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e84bc7707492f06fbc37a9f215374d2977d21b72e10a67f1b31893ec5a140ad8", size = 680400 },
+]
+
+[[package]]
+name = "pymysql"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/ce59b5e5ed4ce8512f879ff1fa5ab699d211ae2495f1adaa5fbba2a1eada/pymysql-1.1.1.tar.gz", hash = "sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0", size = 47678 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/0c/94/e4181a1f6286f545507528c78016e00065ea913276888db2262507693ce5/PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c", size = 44972 },
+]
+
+[[package]]
+name = "pypandoc"
+version = "1.13"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/95/ac/40008af3ae4af9a5cc796803fb4d2ef82385632113f2c74f1ca1e2b5e3ed/pypandoc-1.13.tar.gz", hash = "sha256:31652073c7960c2b03570bd1e94f602ca9bc3e70099df5ead4cea98ff5151c1e", size = 32657 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fc/09/91ab02feebc195a39ce0a39edcafbe866e69ff700a59790e605b3d5f69b1/pypandoc-1.13-py3-none-any.whl", hash = "sha256:4c7d71bf2f1ed122aac287113b5c4d537a33bbc3c1df5aed11a7d4a7ac074681", size = 21236 },
+]
+
+[[package]]
+name = "pyparsing"
+version = "3.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/46/3a/31fd28064d016a2182584d579e033ec95b809d8e220e74c4af6f0f2e8842/pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", size = 889571 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9d/ea/6d76df31432a0e6fdf81681a895f009a4bb47b3c39036db3e1b528191d52/pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742", size = 103245 },
+]
+
+[[package]]
+name = "pypdf"
+version = "4.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f0/65/2ed7c9e1d31d860f096061b3dd2d665f501e09faaa0409a3f0d719d2a16d/pypdf-4.3.1.tar.gz", hash = "sha256:b2f37fe9a3030aa97ca86067a56ba3f9d3565f9a791b305c7355d8392c30d91b", size = 293266 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3c/60/eccdd92dd4af3e4bea6d6a342f7588c618a15b9bec4b968af581e498bcc4/pypdf-4.3.1-py3-none-any.whl", hash = "sha256:64b31da97eda0771ef22edb1bfecd5deee4b72c3d1736b7df2689805076d6418", size = 295825 },
+]
+
+[[package]]
+name = "pypika"
+version = "0.48.9"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/2c/94ed7b91db81d61d7096ac8f2d325ec562fc75e35f3baea8749c85b28784/PyPika-0.48.9.tar.gz", hash = "sha256:838836a61747e7c8380cd1b7ff638694b7a7335345d0f559b04b2cd832ad5378", size = 67259 }
+
+[[package]]
+name = "pyproject-hooks"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/07/6f63dda440d4abb191b91dc383b472dae3dd9f37e4c1e4a5c3db150531c6/pyproject_hooks-1.1.0.tar.gz", hash = "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965", size = 7838 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", hash = "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", size = 9184 },
+]
+
+[[package]]
+name = "pyreadline3"
+version = "3.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/86/3d61a61f36a0067874a00cb4dceb9028d34b6060e47828f7fc86fb9f7ee9/pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae", size = 86465 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/56/fc/a3c13ded7b3057680c8ae95a9b6cc83e63657c38e0005c400a5d018a33a7/pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb", size = 95203 },
+]
+
+[[package]]
+name = "pytest"
+version = "8.2.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "colorama", marker = "sys_platform == 'win32'" },
+    { name = "iniconfig" },
+    { name = "packaging" },
+    { name = "pluggy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a6/58/e993ca5357553c966b9e73cb3475d9c935fe9488746e13ebdf9b80fae508/pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977", size = 1427980 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4e/e7/81ebdd666d3bff6670d27349b5053605d83d55548e6bd5711f3b0ae7dd23/pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343", size = 339873 },
+]
+
+[[package]]
+name = "pytest-docker"
+version = "3.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "attrs" },
+    { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e7/a6/543f2fb157ad228fcc04a8974aa16c989058834d8e539608814cf722f1b3/pytest-docker-3.1.1.tar.gz", hash = "sha256:2371524804a752aaa766c79b9eee8e634534afddb82597f3b573da7c5d6ffb5f", size = 12918 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1a/a4/69defc13bf77ee5aeb3e7b7c45393d6c7312e9c4d8b55d280a094ff76ff3/pytest_docker-3.1.1-py3-none-any.whl", hash = "sha256:fd0d48d6feac41f62acbc758319215ec9bb805c2309622afb07c27fa5c5ae362", size = 8243 },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
+]
+
+[[package]]
+name = "python-dotenv"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
+]
+
+[[package]]
+name = "python-engineio"
+version = "4.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "simple-websocket" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/01/94faf505820f1fb94133a456dad87a76df589f6999de563229a342e412fa/python_engineio-4.9.1.tar.gz", hash = "sha256:7631cf5563086076611e494c643b3fa93dd3a854634b5488be0bba0ef9b99709", size = 89549 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ad/38/4642c75241686c9cf05d23c50b9ffbd760507292f12fdfb04adc2ab5d34a/python_engineio-4.9.1-py3-none-any.whl", hash = "sha256:f995e702b21f6b9ebde4e2000cd2ad0112ba0e5116ec8d22fe3515e76ba9dddd", size = 57686 },
+]
+
+[[package]]
+name = "python-iso639"
+version = "2024.4.27"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ff/d9/fbab4ccc1a712b989b28c1c964c94096aa4b44770245a49439532e787416/python_iso639-2024.4.27.tar.gz", hash = "sha256:97e63b5603e085c6a56a12a95740010e75d9134e0aab767e0978b53fd8824f13", size = 279163 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/01/08/5e649cf18dec750d498c53c6c8eb1d9790752ebd50fa7f7e69cc0c277cfe/python_iso639-2024.4.27-py3-none-any.whl", hash = "sha256:27526a84cebc4c4d53fea9d1ebbc7209c8d279bebaa343e6765a1fc8780565ab", size = 274742 },
+]
+
+[[package]]
+name = "python-jose"
+version = "3.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "ecdsa" },
+    { name = "pyasn1" },
+    { name = "rsa" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e4/19/b2c86504116dc5f0635d29f802da858404d77d930a25633d2e86a64a35b3/python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a", size = 129068 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/bd/2d/e94b2f7bab6773c70efc70a61d66e312e1febccd9e0db6b9e0adf58cbad1/python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a", size = 33530 },
+]
+
+[[package]]
+name = "python-magic"
+version = "0.4.27"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840 },
+]
+
+[[package]]
+name = "python-multipart"
+version = "0.0.9"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5c/0f/9c55ac6c84c0336e22a26fa84ca6c51d58d7ac3a2d78b0dfa8748826c883/python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026", size = 31516 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3d/47/444768600d9e0ebc82f8e347775d24aef8f6348cf00e9fa0e81910814e6d/python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215", size = 22299 },
+]
+
+[[package]]
+name = "python-oxmsg"
+version = "0.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click" },
+    { name = "olefile" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4f/d4/4ec721fd433453fe05344f41f17458775d111e9f6c668ce1a0fccec0fecd/python_oxmsg-0.0.1.tar.gz", hash = "sha256:b65c1f93d688b85a9410afa824192a1ddc39da359b04a0bd2cbd3874e84d4994", size = 34541 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d4/c8/fb23e1e7723ba9200b75bc121f22f67498ae098a202f1646acc4f6a54f5c/python_oxmsg-0.0.1-py3-none-any.whl", hash = "sha256:8ea7d5dda1bc161a413213da9e18ed152927c1fda2feaf5d1f02192d8ad45eea", size = 31426 },
+]
+
+[[package]]
+name = "python-pptx"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "lxml" },
+    { name = "pillow" },
+    { name = "typing-extensions" },
+    { name = "xlsxwriter" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5d/b5/b5f64158c9230429bbe9b87a372ccda6ce9b0a64fa48c43a377be284e144/python_pptx-1.0.0.tar.gz", hash = "sha256:5c0f9fbf564fccf825c03c8bb75af9245fbdfad75e2857d115f47fd94b65eae8", size = 10109490 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9b/06/62d0069a8b6ece6dded497593538a97339121e3096eff77ba6f3734a84a7/python_pptx-1.0.0-py3-none-any.whl", hash = "sha256:e099cbcb370e97ae1cca2186ac757774a2c7bf1b5cb7e6ac3cb77061466713c5", size = 472287 },
+]
+
+[[package]]
+name = "python-socketio"
+version = "5.11.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "bidict" },
+    { name = "python-engineio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1e/74/b1e8787cea757e1f533a7878e94f929679ef7e07a2aaf44de6b71065b1f2/python_socketio-5.11.3.tar.gz", hash = "sha256:194af8cdbb7b0768c2e807ba76c7abc288eb5bb85559b7cddee51a6bc7a65737", size = 117702 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e9/59/5ee858d5736594d75385b9a8c0f65af6eca5da2b359ed3fb6a7486526399/python_socketio-5.11.3-py3-none-any.whl", hash = "sha256:2a923a831ff70664b7c502df093c423eb6aa93c1ce68b8319e840227a26d8b69", size = 76180 },
+]
+
+[[package]]
+name = "pytube"
+version = "15.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/e7/16fec46c8d255c4bbc4b185d89c91dc92cdb802836570d8004d0db169c91/pytube-15.0.0.tar.gz", hash = "sha256:076052efe76f390dfa24b1194ff821d4e86c17d41cb5562f3a276a8bcbfc9d1d", size = 67229 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/51/64/bcf8632ed2b7a36bbf84a0544885ffa1d0b4bcf25cc0903dba66ec5fdad9/pytube-15.0.0-py3-none-any.whl", hash = "sha256:07b9904749e213485780d7eb606e5e5b8e4341aa4dccf699160876da00e12d78", size = 57594 },
+]
+
+[[package]]
+name = "pytz"
+version = "2024.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/90/26/9f1f00a5d021fff16dee3de13d43e5e978f3d58928e129c3a62cf7eb9738/pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", size = 316214 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9c/3d/a121f284241f08268b21359bd425f7d4825cffc5ac5cd0e1b3d82ffd2b10/pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319", size = 505474 },
+]
+
+[[package]]
+name = "pywin32"
+version = "306"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8b/1e/fc18ad83ca553e01b97aa8393ff10e33c1fb57801db05488b83282ee9913/pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", size = 8507689 },
+    { url = "https://files.pythonhosted.org/packages/7e/9e/ad6b1ae2a5ad1066dc509350e0fbf74d8d50251a51e420a2a8feaa0cecbd/pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", size = 9227547 },
+    { url = "https://files.pythonhosted.org/packages/91/20/f744bff1da8f43388498503634378dbbefbe493e65675f2cc52f7185c2c2/pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", size = 10388324 },
+    { url = "https://files.pythonhosted.org/packages/14/91/17e016d5923e178346aabda3dfec6629d1a26efe587d19667542105cf0a6/pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", size = 8507705 },
+    { url = "https://files.pythonhosted.org/packages/83/1c/25b79fc3ec99b19b0a0730cc47356f7e2959863bf9f3cd314332bddb4f68/pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", size = 9227429 },
+    { url = "https://files.pythonhosted.org/packages/1c/43/e3444dc9a12f8365d9603c2145d16bf0a2f8180f343cf87be47f5579e547/pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", size = 10388145 },
+]
+
+[[package]]
+name = "pyxlsb"
+version = "1.0.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3f/13/eebaeb7a40b062d1c6f7f91d09e73d30a69e33e4baa7cbe4b7658548b1cd/pyxlsb-1.0.10.tar.gz", hash = "sha256:8062d1ea8626d3f1980e8b1cfe91a4483747449242ecb61013bc2df85435f685", size = 22424 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/92/345823838ae367c59b63e03aef9c331f485370f9df6d049256a61a28f06d/pyxlsb-1.0.10-py2.py3-none-any.whl", hash = "sha256:87c122a9a622e35ca5e741d2e541201d28af00fb46bec492cfa9586890b120b4", size = 23849 },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 },
+    { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 },
+    { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 },
+    { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 },
+    { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 },
+    { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 },
+    { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 },
+    { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 },
+    { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 },
+    { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 },
+    { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 },
+    { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 },
+    { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 },
+    { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 },
+    { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 },
+    { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 },
+    { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 },
+    { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 },
+    { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
+    { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
+    { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
+    { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 },
+    { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 },
+    { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 },
+    { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 },
+    { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 },
+    { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
+]
+
+[[package]]
+name = "rank-bm25"
+version = "0.2.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fc/0a/f9579384aa017d8b4c15613f86954b92a95a93d641cc849182467cf0bb3b/rank_bm25-0.2.2.tar.gz", hash = "sha256:096ccef76f8188563419aaf384a02f0ea459503fdf77901378d4fd9d87e5e51d", size = 8347 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2a/21/f691fb2613100a62b3fa91e9988c991e9ca5b89ea31c0d3152a3210344f9/rank_bm25-0.2.2-py3-none-any.whl", hash = "sha256:7bd4a95571adadfc271746fa146a4bcfd89c0cf731e49c3d1ad863290adbe8ae", size = 8584 },
+]
+
+[[package]]
+name = "rapidfuzz"
+version = "3.9.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/b0/e0756b5efe826c1bdf6442777cc924b41258685dcf372ee77399cc10408e/rapidfuzz-3.9.6.tar.gz", hash = "sha256:5cf2a7d621e4515fee84722e93563bf77ff2cbe832a77a48b81f88f9e23b9e8d", size = 1596107 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5e/55/5ba0016fe8fba98d8ff55832dd7d79f2d6b93fe27be7863ccf3f79366d76/rapidfuzz-3.9.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52e4675f642fbc85632f691b67115a243cd4d2a47bdcc4a3d9a79e784518ff97", size = 2055435 },
+    { url = "https://files.pythonhosted.org/packages/55/23/1d0c51c01fbff028ff5746a388edd610a591e76153fca72f6f7e68b5fc14/rapidfuzz-3.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1f93a2f13038700bd245b927c46a2017db3dcd4d4ff94687d74b5123689b873b", size = 1510617 },
+    { url = "https://files.pythonhosted.org/packages/28/d6/8dd267f4377d8bf1698d891a28c24d417499724355f6a3541c0b5ab1c35d/rapidfuzz-3.9.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b70500bca460264b8141d8040caee22e9cf0418c5388104ff0c73fb69ee28f", size = 1559930 },
+    { url = "https://files.pythonhosted.org/packages/6a/3e/254fd9e2ce895480bc43a5a11a35d4825b11918d694c959cc0e214d842a9/rapidfuzz-3.9.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1e037fb89f714a220f68f902fc6300ab7a33349f3ce8ffae668c3b3a40b0b06", size = 5964374 },
+    { url = "https://files.pythonhosted.org/packages/d2/cd/3e555024d9168dbd732e919fb0f7d05c3f6809d6ed86042383efeb6f98a0/rapidfuzz-3.9.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6792f66d59b86ccfad5e247f2912e255c85c575789acdbad8e7f561412ffed8a", size = 1825493 },
+    { url = "https://files.pythonhosted.org/packages/32/5f/c47c511e2b174e80ea1091722359b4db7900e4987a4bcacffae6f27237c0/rapidfuzz-3.9.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68d9cffe710b67f1969cf996983608cee4490521d96ea91d16bd7ea5dc80ea98", size = 1830053 },
+    { url = "https://files.pythonhosted.org/packages/e9/85/88f1fd986714887ad4448c7b321c85b9aa5842df9deb606bcdfdfc35fae6/rapidfuzz-3.9.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63daaeeea76da17fa0bbe7fb05cba8ed8064bb1a0edf8360636557f8b6511961", size = 3384091 },
+    { url = "https://files.pythonhosted.org/packages/cc/3f/9a941793dbc419a9e9aad742d8057c8440f191ab3758d1ce2c12e9f27ccd/rapidfuzz-3.9.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d214e063bffa13e3b771520b74f674b22d309b5720d4df9918ff3e0c0f037720", size = 2458230 },
+    { url = "https://files.pythonhosted.org/packages/9d/40/40b75226e0b45ba0212b8ce19996b8970fd3de4748341f6bdd0302b54856/rapidfuzz-3.9.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ed443a2062460f44c0346cb9d269b586496b808c2419bbd6057f54061c9b9c75", size = 7239493 },
+    { url = "https://files.pythonhosted.org/packages/df/62/47401ac22299f70a8231f7e7421b3f804cd716f6a24521ef172dec7afe01/rapidfuzz-3.9.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5b0c9b227ee0076fb2d58301c505bb837a290ae99ee628beacdb719f0626d749", size = 2837398 },
+    { url = "https://files.pythonhosted.org/packages/59/f2/a3db1b31dc80d906528e03d213a9f9bd8c44547cb1cbf9a3c59649058c2e/rapidfuzz-3.9.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:82c9722b7dfaa71e8b61f8c89fed0482567fb69178e139fe4151fc71ed7df782", size = 3386617 },
+    { url = "https://files.pythonhosted.org/packages/5f/41/2d285edb31f718c81e3433a56142d5e606ba20d0997fcf57774042f79850/rapidfuzz-3.9.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c18897c95c0a288347e29537b63608a8f63a5c3cb6da258ac46fcf89155e723e", size = 4392081 },
+    { url = "https://files.pythonhosted.org/packages/37/7b/d355309d5aa606dd91d91501a64c87db32e3d75b775950a19df8ec0288f0/rapidfuzz-3.9.6-cp311-cp311-win32.whl", hash = "sha256:3e910cf08944da381159587709daaad9e59d8ff7bca1f788d15928f3c3d49c2a", size = 1854906 },
+    { url = "https://files.pythonhosted.org/packages/aa/bb/cdd512d40f8ea67692deee6b0da4f7235c6a0f9e126fdded32b62c5d91fe/rapidfuzz-3.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:59c4a61fab676d37329fc3a671618a461bfeef53a4d0b8b12e3bc24a14e166f8", size = 1654104 },
+    { url = "https://files.pythonhosted.org/packages/53/92/5014563b1e8f901f983f96606c6982ff4ed286a8ae4335b67012a4a50cf9/rapidfuzz-3.9.6-cp311-cp311-win_arm64.whl", hash = "sha256:8b4afea244102332973377fddbe54ce844d0916e1c67a5123432291717f32ffa", size = 855134 },
+    { url = "https://files.pythonhosted.org/packages/df/f4/e8175a4ad862ede4caa8dd287d187d12db2f4eb426b00b030b1cb7f4d2dd/rapidfuzz-3.9.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:70591b28b218fff351b88cdd7f2359a01a71f9f7f5a2e465ce3715ed4b3c422b", size = 2053429 },
+    { url = "https://files.pythonhosted.org/packages/22/a5/8c14e41bcdea3be343764de6ad464ff87300352f8e2d01064bf0f1809e9d/rapidfuzz-3.9.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee2d8355c7343c631a03e57540ea06e8717c19ecf5ff64ea07e0498f7f161457", size = 1506224 },
+    { url = "https://files.pythonhosted.org/packages/75/92/51d74bdf539475d8c71df76da73d4609231254ac2499beb24efa78667b14/rapidfuzz-3.9.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:708fb675de0f47b9635d1cc6fbbf80d52cb710d0a1abbfae5c84c46e3abbddc3", size = 1542825 },
+    { url = "https://files.pythonhosted.org/packages/b9/7a/29bf00754308ba43eb6f95988445b86953b29bc780164f8961d0c3d67b89/rapidfuzz-3.9.6-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d66c247c2d3bb7a9b60567c395a15a929d0ebcc5f4ceedb55bfa202c38c6e0c", size = 5858827 },
+    { url = "https://files.pythonhosted.org/packages/0a/a3/1b5a2bb95e5b532fe571b6b0c8c4cf35893e748eac5cac2ae7d3c0f41d47/rapidfuzz-3.9.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15146301b32e6e3d2b7e8146db1a26747919d8b13690c7f83a4cb5dc111b3a08", size = 1794607 },
+    { url = "https://files.pythonhosted.org/packages/0e/42/7ee9c15087d6b7146e73e4650bfb8ddbbe2161a9edbe2b44be9ffb68a2f1/rapidfuzz-3.9.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7a03da59b6c7c97e657dd5cd4bcaab5fe4a2affd8193958d6f4d938bee36679", size = 1818934 },
+    { url = "https://files.pythonhosted.org/packages/15/30/0a4bc8b641e2374475c03d6c6ec6568303ac2070698c067c690daefd9b8f/rapidfuzz-3.9.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d2c2fe19e392dbc22695b6c3b2510527e2b774647e79936bbde49db7742d6f1", size = 3381282 },
+    { url = "https://files.pythonhosted.org/packages/e1/45/6c9bbba66a5ada5679c0705375068337c2573a940443868cbcba5c909c07/rapidfuzz-3.9.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:91aaee4c94cb45930684f583ffc4e7c01a52b46610971cede33586cf8a04a12e", size = 2425764 },
+    { url = "https://files.pythonhosted.org/packages/74/b3/b02d002e643ec5f97278b6f04c2293b9b678249220f253b7e649a4e0df3a/rapidfuzz-3.9.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3f5702828c10768f9281180a7ff8597da1e5002803e1304e9519dd0f06d79a85", size = 7176966 },
+    { url = "https://files.pythonhosted.org/packages/3f/0d/52cd49cafb91fc0cc4e75e3beab7b16fffe62a6c66d5fb5d336b2deacda5/rapidfuzz-3.9.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ccd1763b608fb4629a0b08f00b3c099d6395e67c14e619f6341b2c8429c2f310", size = 2800253 },
+    { url = "https://files.pythonhosted.org/packages/4f/ee/82004bf9274566711b134bb2628bf878046a9212c798bd10f0138ad6cca9/rapidfuzz-3.9.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc7a0d4b2cb166bc46d02c8c9f7551cde8e2f3c9789df3827309433ee9771163", size = 3345106 },
+    { url = "https://files.pythonhosted.org/packages/d8/78/7008dcd6701cc84eb74fad6c743f03c33c7f41516d15a313fcd759b2d614/rapidfuzz-3.9.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7496f53d40560a58964207b52586783633f371683834a8f719d6d965d223a2eb", size = 4360019 },
+    { url = "https://files.pythonhosted.org/packages/07/25/5de7013daac2eb7b18bc0123fa5d83ddbe5508673b14b9ca7e32f2be3cbe/rapidfuzz-3.9.6-cp312-cp312-win32.whl", hash = "sha256:5eb1a9272ca71bc72be5415c2fa8448a6302ea4578e181bb7da9db855b367df0", size = 1842659 },
+    { url = "https://files.pythonhosted.org/packages/7b/81/bfd28ed4a5638b594985988921d19fd716f119dde93703d8545c1bcb8e7e/rapidfuzz-3.9.6-cp312-cp312-win_amd64.whl", hash = "sha256:0d21fc3c0ca507a1180152a6dbd129ebaef48facde3f943db5c1055b6e6be56a", size = 1648213 },
+    { url = "https://files.pythonhosted.org/packages/8a/81/249ed13ce5cee9c9aa9c69656e2d78ed240534d478ebaada04930513820a/rapidfuzz-3.9.6-cp312-cp312-win_arm64.whl", hash = "sha256:43bb27a57c29dc5fa754496ba6a1a508480d21ae99ac0d19597646c16407e9f3", size = 849878 },
+    { url = "https://files.pythonhosted.org/packages/d1/de/83b660bb054a3bd85f033a2521ba99ae65e49b95f3b18745cfcf68a94ae7/rapidfuzz-3.9.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:83a5ac6547a9d6eedaa212975cb8f2ce2aa07e6e30833b40e54a52b9f9999aa4", size = 2026087 },
+    { url = "https://files.pythonhosted.org/packages/6f/f3/7b7f8ddb80562722c6cb86a3396ad6945c0003f36addbca610782fe7e113/rapidfuzz-3.9.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:10f06139142ecde67078ebc9a745965446132b998f9feebffd71acdf218acfcc", size = 1499413 },
+    { url = "https://files.pythonhosted.org/packages/bd/e9/a37db9a3674b210d6ef21b15c66eb1585125d553ed5b73f6481dadf932c3/rapidfuzz-3.9.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74720c3f24597f76c7c3e2c4abdff55f1664f4766ff5b28aeaa689f8ffba5fab", size = 1537241 },
+    { url = "https://files.pythonhosted.org/packages/75/67/e111c7a11eafd70677d6688c36d93b54a43783119b16ee5591589a110ff9/rapidfuzz-3.9.6-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2bce52b5c150878e558a0418c2b637fb3dbb6eb38e4eb27d24aa839920483e", size = 5877164 },
+    { url = "https://files.pythonhosted.org/packages/80/58/ce20051fbbac74bc369d4ab3f450683ebf70e456474d6172ba9b6f6164f0/rapidfuzz-3.9.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1611199f178793ca9a060c99b284e11f6d7d124998191f1cace9a0245334d219", size = 1766888 },
+    { url = "https://files.pythonhosted.org/packages/8f/b4/afde433a48fb18f5d7ddf144ffb2b0b674f3f176a2aca89f2ea06fe54d4f/rapidfuzz-3.9.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0308b2ad161daf502908a6e21a57c78ded0258eba9a8f5e2545e2dafca312507", size = 1819868 },
+    { url = "https://files.pythonhosted.org/packages/53/47/089d2d8a27c4e90dd4dfb532d31bc873537fcc479874e9909a9018156855/rapidfuzz-3.9.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eda91832201b86e3b70835f91522587725bec329ec68f2f7faf5124091e5ca7", size = 3361094 },
+    { url = "https://files.pythonhosted.org/packages/95/a5/f5e1fc00485f45ed17803f91f7877fa07b0fb4006ff42cfebd7355ad1bdc/rapidfuzz-3.9.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ece873c093aedd87fc07c2a7e333d52e458dc177016afa1edaf157e82b6914d8", size = 2421352 },
+    { url = "https://files.pythonhosted.org/packages/57/fa/0d6573980251fb63147a35be8f4fae962d79ebbe4b675fdaf0cf16726aa0/rapidfuzz-3.9.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d97d3c9d209d5c30172baea5966f2129e8a198fec4a1aeb2f92abb6e82a2edb1", size = 7190683 },
+    { url = "https://files.pythonhosted.org/packages/71/50/2f866735f788d565a2ed6d113a54a410b4a412b46cb399e3ae466e72730f/rapidfuzz-3.9.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6c4550d0db4931f5ebe9f0678916d1b06f06f5a99ba0b8a48b9457fd8959a7d4", size = 2791255 },
+    { url = "https://files.pythonhosted.org/packages/a2/90/7c36906695b216bf489cd234d95e5e00f06f976054ce3c441085a5ae650f/rapidfuzz-3.9.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b6b8dd4af6324fc325d9483bec75ecf9be33e590928c9202d408e4eafff6a0a6", size = 3341004 },
+    { url = "https://files.pythonhosted.org/packages/16/2b/15f6df84ef774457fd77a80436154e8fc6c903bfc997490a275fca84adda/rapidfuzz-3.9.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16122ae448bc89e2bea9d81ce6cb0f751e4e07da39bd1e70b95cae2493857853", size = 4355422 },
+    { url = "https://files.pythonhosted.org/packages/7f/3b/e593d8f0b7bb2c1cb00514552056a4b61000d2d8f4fd24e016fdd2e1afe1/rapidfuzz-3.9.6-cp313-cp313-win32.whl", hash = "sha256:71cc168c305a4445109cd0d4925406f6e66bcb48fde99a1835387c58af4ecfe9", size = 1840420 },
+    { url = "https://files.pythonhosted.org/packages/fe/09/d25939dfe7eea5e4a474dfebd60073acd792621d42a9ffe7534627c8ad26/rapidfuzz-3.9.6-cp313-cp313-win_amd64.whl", hash = "sha256:59ee78f2ecd53fef8454909cda7400fe2cfcd820f62b8a5d4dfe930102268054", size = 1645331 },
+    { url = "https://files.pythonhosted.org/packages/d8/26/6eda9e43dd5b0833feb4cf9ea0ad4e46b07c4811d8b8815a8517b379c640/rapidfuzz-3.9.6-cp313-cp313-win_arm64.whl", hash = "sha256:58b4ce83f223605c358ae37e7a2d19a41b96aa65b1fede99cc664c9053af89ac", size = 848759 },
+]
+
+[[package]]
+name = "rapidocr-onnxruntime"
+version = "1.3.24"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+    { name = "onnxruntime" },
+    { name = "opencv-python" },
+    { name = "pillow" },
+    { name = "pyclipper" },
+    { name = "pyyaml" },
+    { name = "shapely" },
+    { name = "six" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/54/08/6b3c1762c9800faa4346d6141b88af9f94f3680c9d83774e700b1b75c256/rapidocr_onnxruntime-1.3.24-py3-none-any.whl", hash = "sha256:4282ff0b8db05ad2a53afc8d0ef2e7d879c53022fc61a4f8e84c58d737822cd2", size = 14910742 },
+]
+
+[[package]]
+name = "red-black-tree-mod"
+version = "1.20"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/34/12/944f61bc67a1e918953741c0b3b75a28f96d8060d08fd3614233309ced3b/red-black-tree-mod-1.20.tar.gz", hash = "sha256:2448e6fc9cbf1be204c753f352c6ee49aa8156dbf1faa57dfc26bd7705077e0a", size = 28589 }
+
+[[package]]
+name = "redis"
+version = "5.0.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "async-timeout", marker = "python_full_version < '3.11.3'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/48/10/defc227d65ea9c2ff5244645870859865cba34da7373477c8376629746ec/redis-5.0.8.tar.gz", hash = "sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870", size = 4595651 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c5/d1/19a9c76811757684a0f74adc25765c8a901d67f9f6472ac9d57c844a23c8/redis-5.0.8-py3-none-any.whl", hash = "sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4", size = 255608 },
+]
+
+[[package]]
+name = "regex"
+version = "2024.7.24"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3f/51/64256d0dc72816a4fe3779449627c69ec8fee5a5625fd60ba048f53b3478/regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506", size = 393485 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/cb/ec/261f8434a47685d61e59a4ef3d9ce7902af521219f3ebd2194c7adb171a6/regex-2024.7.24-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281", size = 470810 },
+    { url = "https://files.pythonhosted.org/packages/f0/47/f33b1cac88841f95fff862476a9e875d9a10dae6912a675c6f13c128e5d9/regex-2024.7.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b", size = 282126 },
+    { url = "https://files.pythonhosted.org/packages/fc/1b/256ca4e2d5041c0aa2f1dc222f04412b796346ab9ce2aa5147405a9457b4/regex-2024.7.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a", size = 278920 },
+    { url = "https://files.pythonhosted.org/packages/91/03/4603ec057c0bafd2f6f50b0bdda4b12a0ff81022decf1de007b485c356a6/regex-2024.7.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73", size = 785420 },
+    { url = "https://files.pythonhosted.org/packages/75/f8/13b111fab93e6273e26de2926345e5ecf6ddad1e44c4d419d7b0924f9c52/regex-2024.7.24-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2", size = 828164 },
+    { url = "https://files.pythonhosted.org/packages/4a/80/bc3b9d31bd47ff578758af929af0ac1d6169b247e26fa6e87764007f3d93/regex-2024.7.24-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e", size = 812621 },
+    { url = "https://files.pythonhosted.org/packages/8b/77/92d4a14530900d46dddc57b728eea65d723cc9fcfd07b96c2c141dabba84/regex-2024.7.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51", size = 786609 },
+    { url = "https://files.pythonhosted.org/packages/35/58/06695fd8afad4c8ed0a53ec5e222156398b9fe5afd58887ab94ea68e4d16/regex-2024.7.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364", size = 775290 },
+    { url = "https://files.pythonhosted.org/packages/1b/0f/50b97ee1fc6965744b9e943b5c0f3740792ab54792df73d984510964ef29/regex-2024.7.24-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee", size = 772849 },
+    { url = "https://files.pythonhosted.org/packages/8f/64/565ff6cf241586ab7ae76bb4138c4d29bc1d1780973b457c2db30b21809a/regex-2024.7.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c", size = 778428 },
+    { url = "https://files.pythonhosted.org/packages/e5/fe/4ceabf4382e44e1e096ac46fd5e3bca490738b24157116a48270fd542e88/regex-2024.7.24-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce", size = 849436 },
+    { url = "https://files.pythonhosted.org/packages/68/23/1868e40d6b594843fd1a3498ffe75d58674edfc90d95e18dd87865b93bf2/regex-2024.7.24-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1", size = 849484 },
+    { url = "https://files.pythonhosted.org/packages/f3/52/bff76de2f6e2bc05edce3abeb7e98e6309aa022fc06071100a0216fbeb50/regex-2024.7.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e", size = 776712 },
+    { url = "https://files.pythonhosted.org/packages/f2/72/70ade7b0b5fe5c6df38fdfa2a5a8273e3ea6a10b772aa671b7e889e78bae/regex-2024.7.24-cp311-cp311-win32.whl", hash = "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c", size = 257716 },
+    { url = "https://files.pythonhosted.org/packages/04/4d/80e04f4e27ab0cbc9096e2d10696da6d9c26a39b60db52670fd57614fea5/regex-2024.7.24-cp311-cp311-win_amd64.whl", hash = "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52", size = 269662 },
+    { url = "https://files.pythonhosted.org/packages/0f/26/f505782f386ac0399a9237571833f187414882ab6902e2e71a1ecb506835/regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86", size = 471748 },
+    { url = "https://files.pythonhosted.org/packages/bb/1d/ea9a21beeb433dbfca31ab82867d69cb67ff8674af9fab6ebd55fa9d3387/regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad", size = 282841 },
+    { url = "https://files.pythonhosted.org/packages/9b/f2/c6182095baf0a10169c34e87133a8e73b2e816a80035669b1278e927685e/regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9", size = 279114 },
+    { url = "https://files.pythonhosted.org/packages/72/58/b5161bf890b6ca575a25685f19a4a3e3b6f4a072238814f8658123177d84/regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289", size = 789749 },
+    { url = "https://files.pythonhosted.org/packages/09/fb/5381b19b62f3a3494266be462f6a015a869cf4bfd8e14d6e7db67e2c8069/regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9", size = 831666 },
+    { url = "https://files.pythonhosted.org/packages/3d/6d/2a21c85f970f9be79357d12cf4b97f4fc6bf3bf6b843c39dabbc4e5f1181/regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c", size = 817544 },
+    { url = "https://files.pythonhosted.org/packages/f9/ae/5f23e64f6cf170614237c654f3501a912dfb8549143d4b91d1cd13dba319/regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440", size = 790854 },
+    { url = "https://files.pythonhosted.org/packages/29/0a/d04baad1bbc49cdfb4aef90c4fc875a60aaf96d35a1616f1dfe8149716bc/regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610", size = 779242 },
+    { url = "https://files.pythonhosted.org/packages/3a/27/b242a962f650c3213da4596d70e24c7c1c46e3aa0f79f2a81164291085f8/regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5", size = 776932 },
+    { url = "https://files.pythonhosted.org/packages/9c/ae/de659bdfff80ad2c0b577a43dd89dbc43870a4fc4bbf604e452196758e83/regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799", size = 784521 },
+    { url = "https://files.pythonhosted.org/packages/d4/ac/eb6a796da0bdefbf09644a7868309423b18d344cf49963a9d36c13502d46/regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05", size = 854548 },
+    { url = "https://files.pythonhosted.org/packages/56/77/fde8d825dec69e70256e0925af6c81eea9acf0a634d3d80f619d8dcd6888/regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94", size = 853345 },
+    { url = "https://files.pythonhosted.org/packages/ff/04/2b79ad0bb9bc05ab4386caa2c19aa047a66afcbdfc2640618ffc729841e4/regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38", size = 781414 },
+    { url = "https://files.pythonhosted.org/packages/bf/71/d0af58199283ada7d25b20e416f5b155f50aad99b0e791c0966ff5a1cd00/regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc", size = 258125 },
+    { url = "https://files.pythonhosted.org/packages/95/b3/10e875c45c60b010b66fc109b899c6fc4f05d485fe1d54abff98ce791124/regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908", size = 269162 },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "certifi" },
+    { name = "charset-normalizer" },
+    { name = "idna" },
+    { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
+]
+
+[[package]]
+name = "requests-oauthlib"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "oauthlib" },
+    { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179 },
+]
+
+[[package]]
+name = "requests-toolbelt"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 },
+]
+
+[[package]]
+name = "rich"
+version = "13.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "markdown-it-py" },
+    { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/01/c954e134dc440ab5f96952fe52b4fdc64225530320a910473c1fe270d9aa/rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432", size = 221248 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", size = 240681 },
+]
+
+[[package]]
+name = "rsa"
+version = "4.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "pyasn1" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", size = 29711 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315 },
+]
+
+[[package]]
+name = "rtfde"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "lark" },
+    { name = "oletools" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c9/32/1ad82739351117c0711767b828e8f2567a5ffb783741a87120d955564a19/RTFDE-0.1.2-py3-none-any.whl", hash = "sha256:f6d1450c99b04e930da130e8b419aa33b1f953623e1b94ad5c0f67f0362eb737", size = 36142 },
+]
+
+[[package]]
+name = "s3transfer"
+version = "0.10.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "botocore" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cb/67/94c6730ee4c34505b14d94040e2f31edf144c230b6b49e971b4f25ff8fab/s3transfer-0.10.2.tar.gz", hash = "sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6", size = 144095 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3c/4a/b221409913760d26cf4498b7b1741d510c82d3ad38381984a3ddc135ec66/s3transfer-0.10.2-py3-none-any.whl", hash = "sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69", size = 82716 },
+]
+
+[[package]]
+name = "safetensors"
+version = "0.4.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/41/5b/0e63bf736e171463481c5ea3406650dc25aa044083062d321820e7a1ef9f/safetensors-0.4.4.tar.gz", hash = "sha256:5fe3e9b705250d0172ed4e100a811543108653fb2b66b9e702a088ad03772a07", size = 69522 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/0f/1b/27cea7a581019d0d674284048ff76e3a6e048bc3ae3c31cb0bfc93641180/safetensors-0.4.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bbaa31f2cb49013818bde319232ccd72da62ee40f7d2aa532083eda5664e85ff", size = 392373 },
+    { url = "https://files.pythonhosted.org/packages/36/46/93c39c96188a88ca15d12759bb51f52ce7365f6fd19ef09580bc096e8860/safetensors-0.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fdcb80f4e9fbb33b58e9bf95e7dbbedff505d1bcd1c05f7c7ce883632710006", size = 381488 },
+    { url = "https://files.pythonhosted.org/packages/37/a2/93cab60b8e2c8ea6343a04cdd2c09c860c9640eaaffbf8b771a0e8f98e7d/safetensors-0.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55c14c20be247b8a1aeaf3ab4476265e3ca83096bb8e09bb1a7aa806088def4f", size = 441025 },
+    { url = "https://files.pythonhosted.org/packages/19/37/2a5220dce5eff841328bfc3071f4a7063f3eb12341893b2688669fc67115/safetensors-0.4.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:949aaa1118660f992dbf0968487b3e3cfdad67f948658ab08c6b5762e90cc8b6", size = 439791 },
+    { url = "https://files.pythonhosted.org/packages/f8/93/1d894ff44df26baf4c2471a5874388361390d3cb1cc4811cff40fc01373e/safetensors-0.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c11a4ab7debc456326a2bac67f35ee0ac792bcf812c7562a4a28559a5c795e27", size = 477752 },
+    { url = "https://files.pythonhosted.org/packages/a5/17/b697f517c7ffb8d62d1ef17c6224c00edbb96b931e565d887476a51ac803/safetensors-0.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0cea44bba5c5601b297bc8307e4075535b95163402e4906b2e9b82788a2a6df", size = 496019 },
+    { url = "https://files.pythonhosted.org/packages/af/b9/c33f69f4dad9c65209efb76c2be6968af5219e31ccfd344a0025d972252f/safetensors-0.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9d752c97f6bbe327352f76e5b86442d776abc789249fc5e72eacb49e6916482", size = 435416 },
+    { url = "https://files.pythonhosted.org/packages/71/59/f6480a68df2f4fb5aefae45a800d9bc043c0549210075275fef190a896ce/safetensors-0.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03f2bb92e61b055ef6cc22883ad1ae898010a95730fa988c60a23800eb742c2c", size = 456771 },
+    { url = "https://files.pythonhosted.org/packages/09/01/2a7507cdf7318fb68596e6537ef81e83cfc171c483b4a786b9c947368e19/safetensors-0.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:87bf3f91a9328a941acc44eceffd4e1f5f89b030985b2966637e582157173b98", size = 619456 },
+    { url = "https://files.pythonhosted.org/packages/80/b3/4bb5b1fb025cb8c81fe8a76371334860a9c276fade616f83fd53feef2740/safetensors-0.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:20d218ec2b6899d29d6895419a58b6e44cc5ff8f0cc29fac8d236a8978ab702e", size = 605125 },
+    { url = "https://files.pythonhosted.org/packages/09/93/0d6d54b84eff8361dc257fa306ae0ef1899025a2d9657efe8384ac8b7267/safetensors-0.4.4-cp311-none-win32.whl", hash = "sha256:8079486118919f600c603536e2490ca37b3dbd3280e3ad6eaacfe6264605ac8a", size = 272273 },
+    { url = "https://files.pythonhosted.org/packages/21/4f/5ee44681c7ea827f9d3c104ca429865b41c05a4163eff7f0599152c2e682/safetensors-0.4.4-cp311-none-win_amd64.whl", hash = "sha256:2f8c2eb0615e2e64ee27d478c7c13f51e5329d7972d9e15528d3e4cfc4a08f0d", size = 285982 },
+    { url = "https://files.pythonhosted.org/packages/e2/41/a491dbe3fc1c195ce648939a87d3b4b3800eaade2f05278a6dc02b575c51/safetensors-0.4.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:baec5675944b4a47749c93c01c73d826ef7d42d36ba8d0dba36336fa80c76426", size = 391372 },
+    { url = "https://files.pythonhosted.org/packages/3a/a1/d99aa8d10fa8d82276ee2aaa87afd0a6b96e69c128eaa9f93524b52c5276/safetensors-0.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f15117b96866401825f3e94543145028a2947d19974429246ce59403f49e77c6", size = 381800 },
+    { url = "https://files.pythonhosted.org/packages/c8/1c/4fa05b79afdd4688a357a42433565b5b09137af6b4f6cd0c9e371466e2f1/safetensors-0.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a13a9caea485df164c51be4eb0c87f97f790b7c3213d635eba2314d959fe929", size = 440817 },
+    { url = "https://files.pythonhosted.org/packages/65/c0/152b059debd3cee4f44b7df972e915a38f776379ea99ce4a3cbea3f78dbd/safetensors-0.4.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b54bc4ca5f9b9bba8cd4fb91c24b2446a86b5ae7f8975cf3b7a277353c3127c", size = 439483 },
+    { url = "https://files.pythonhosted.org/packages/9c/93/20c05daeecf6fa93b9403c3660df1d983d7ddd5cdb3e3710ff41b72754dd/safetensors-0.4.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:08332c22e03b651c8eb7bf5fc2de90044f3672f43403b3d9ac7e7e0f4f76495e", size = 476631 },
+    { url = "https://files.pythonhosted.org/packages/84/2f/bfe3e54b7dbcaef3f10b8f3c71146790ab18b0bd79ad9ca2bc2c950b68df/safetensors-0.4.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb62841e839ee992c37bb75e75891c7f4904e772db3691c59daaca5b4ab960e1", size = 493575 },
+    { url = "https://files.pythonhosted.org/packages/1b/0b/2a1b405131f26b95acdb3ed6c8e3a8c84de72d364fd26202d43e68ec4bad/safetensors-0.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e5b927acc5f2f59547270b0309a46d983edc44be64e1ca27a7fcb0474d6cd67", size = 434891 },
+    { url = "https://files.pythonhosted.org/packages/31/ce/cad390a08128ebcb74be79a1e03c496a4773059b2541c6a97a52fd1705fb/safetensors-0.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a69c71b1ae98a8021a09a0b43363b0143b0ce74e7c0e83cacba691b62655fb8", size = 457631 },
+    { url = "https://files.pythonhosted.org/packages/9f/83/d9d6e6a45d624c27155f4336af8e7b2bcde346137f6460dcd5e1bcdc2e3f/safetensors-0.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23654ad162c02a5636f0cd520a0310902c4421aab1d91a0b667722a4937cc445", size = 619367 },
+    { url = "https://files.pythonhosted.org/packages/9f/20/b37e1ae87cb83a1c2fe5cf0710bab12d6f186474cbbdda4fda2d7d57d225/safetensors-0.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0677c109d949cf53756859160b955b2e75b0eefe952189c184d7be30ecf7e858", size = 605302 },
+    { url = "https://files.pythonhosted.org/packages/99/5a/9237f1d0adba5eec3711d7c1911b3111631a86779d692fe8ad2cd709d6a4/safetensors-0.4.4-cp312-none-win32.whl", hash = "sha256:a51d0ddd4deb8871c6de15a772ef40b3dbd26a3c0451bb9e66bc76fc5a784e5b", size = 273434 },
+    { url = "https://files.pythonhosted.org/packages/b9/dd/b11f3a33fe7b6c94fde08b3de094b93d3438d67922ef90bcb5002e306e0b/safetensors-0.4.4-cp312-none-win_amd64.whl", hash = "sha256:2d065059e75a798bc1933c293b68d04d79b586bb7f8c921e0ca1e82759d0dbb1", size = 286347 },
+    { url = "https://files.pythonhosted.org/packages/b3/d6/7a4db869a295b57066e1399eb467c38df86439d3766c850ca8eb75b5e3a3/safetensors-0.4.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9d625692578dd40a112df30c02a1adf068027566abd8e6a74893bb13d441c150", size = 391373 },
+    { url = "https://files.pythonhosted.org/packages/1e/97/de856ad42ef65822ff982e7af7fc889cd717240672b45c647af7ea05c631/safetensors-0.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7cabcf39c81e5b988d0adefdaea2eb9b4fd9bd62d5ed6559988c62f36bfa9a89", size = 382523 },
+    { url = "https://files.pythonhosted.org/packages/07/d2/d9316af4c15b4ca0362cb4498abe47be6e04f7119f3ccf697e38ee04d33b/safetensors-0.4.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8359bef65f49d51476e9811d59c015f0ddae618ee0e44144f5595278c9f8268c", size = 441039 },
+    { url = "https://files.pythonhosted.org/packages/e8/ac/478e910c891feadb693316b31447f14929b7047a612df9b628589b89be3c/safetensors-0.4.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1a32c662e7df9226fd850f054a3ead0e4213a96a70b5ce37b2d26ba27004e013", size = 439516 },
+    { url = "https://files.pythonhosted.org/packages/81/43/f9929e854c4fcca98459f03de003d9619dd5f7d10d74e03df7af9907b119/safetensors-0.4.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c329a4dcc395364a1c0d2d1574d725fe81a840783dda64c31c5a60fc7d41472c", size = 477242 },
+    { url = "https://files.pythonhosted.org/packages/0a/4d/b754f59fe395ea5bd8531c090c557e161fffed1753eeb3d87c0f8eaa62c4/safetensors-0.4.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:239ee093b1db877c9f8fe2d71331a97f3b9c7c0d3ab9f09c4851004a11f44b65", size = 494615 },
+    { url = "https://files.pythonhosted.org/packages/54/7d/b26801dab2ecb499eb1ebdb46be65600b49bb062fe12b298150695a6e23c/safetensors-0.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd574145d930cf9405a64f9923600879a5ce51d9f315443a5f706374841327b6", size = 434933 },
+    { url = "https://files.pythonhosted.org/packages/e2/40/0f6627ad98e21e620a6835f02729f6b701804d3c452f8773648cbd0b9c2c/safetensors-0.4.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f6784eed29f9e036acb0b7769d9e78a0dc2c72c2d8ba7903005350d817e287a4", size = 457646 },
+    { url = "https://files.pythonhosted.org/packages/30/1e/7f7819d1be7c36fbedcb7099a461b79e0ed19631b3ca5595e0f81501bb2c/safetensors-0.4.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:65a4a6072436bf0a4825b1c295d248cc17e5f4651e60ee62427a5bcaa8622a7a", size = 619204 },
+    { url = "https://files.pythonhosted.org/packages/b1/58/e91e8c9888303919ce56f038fcad4147431fd95630890799bf8c928d1d34/safetensors-0.4.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:df81e3407630de060ae8313da49509c3caa33b1a9415562284eaf3d0c7705f9f", size = 605400 },
+]
+
+[[package]]
+name = "scikit-learn"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "joblib" },
+    { name = "numpy" },
+    { name = "scipy" },
+    { name = "threadpoolctl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/92/72/2961b9874a9ddf2b0f95f329d4e67f67c3301c1d88ba5e239ff25661bb85/scikit_learn-1.5.1.tar.gz", hash = "sha256:0ea5d40c0e3951df445721927448755d3fe1d80833b0b7308ebff5d2a45e6414", size = 6958368 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/03/86/ab9f95e338c5ef5b4e79463ee91e55aae553213835e59bf038bc0cc21bf8/scikit_learn-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:154297ee43c0b83af12464adeab378dee2d0a700ccd03979e2b821e7dd7cc1c2", size = 12087598 },
+    { url = "https://files.pythonhosted.org/packages/7d/d7/fb80c63062b60b1fa5dcb2d4dd3a4e83bd8c68cdc83cf6ff8c016228f184/scikit_learn-1.5.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b5e865e9bd59396220de49cb4a57b17016256637c61b4c5cc81aaf16bc123bbe", size = 10979067 },
+    { url = "https://files.pythonhosted.org/packages/c1/f8/fd3fa610cac686952d8c78b8b44cf5263c6c03885bd8e5d5819c684b44e8/scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909144d50f367a513cee6090873ae582dba019cb3fca063b38054fa42704c3a4", size = 12485469 },
+    { url = "https://files.pythonhosted.org/packages/32/63/ed228892adad313aab0d0f9261241e7bf1efe36730a2788ad424bcad00ca/scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b6f74b2c880276e365fe84fe4f1befd6a774f016339c65655eaff12e10cbf", size = 13335048 },
+    { url = "https://files.pythonhosted.org/packages/5d/55/0403bf2031250ac982c8053397889fbc5a3a2b3798b913dae4f51c3af6a4/scikit_learn-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a07f90846313a7639af6a019d849ff72baadfa4c74c778821ae0fad07b7275b", size = 10988436 },
+    { url = "https://files.pythonhosted.org/packages/b1/8d/cf392a56e24627093a467642c8b9263052372131359b570df29aaf4811ab/scikit_learn-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5944ce1faada31c55fb2ba20a5346b88e36811aab504ccafb9f0339e9f780395", size = 12102404 },
+    { url = "https://files.pythonhosted.org/packages/d5/2c/734fc9269bdb6768905ac41b82d75264b26925b1e462f4ebf45fe4f17646/scikit_learn-1.5.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0828673c5b520e879f2af6a9e99eee0eefea69a2188be1ca68a6121b809055c1", size = 11037398 },
+    { url = "https://files.pythonhosted.org/packages/d3/a9/15774b178bcd1cde1c470adbdb554e1504dce7c302e02ff736c90d65e014/scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508907e5f81390e16d754e8815f7497e52139162fd69c4fdbd2dfa5d6cc88915", size = 12089887 },
+    { url = "https://files.pythonhosted.org/packages/8a/5d/047cde25131eef3a38d03317fa7d25d6f60ce6e8ccfd24ac88b3e309fc00/scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97625f217c5c0c5d0505fa2af28ae424bd37949bb2f16ace3ff5f2f81fb4498b", size = 13079093 },
+    { url = "https://files.pythonhosted.org/packages/cb/be/dec2a8d31d133034a8ec51ae68ac564ec9bde1c78a64551f1438c3690b9e/scikit_learn-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:da3f404e9e284d2b0a157e1b56b6566a34eb2798205cba35a211df3296ab7a74", size = 10945350 },
+]
+
+[[package]]
+name = "scipy"
+version = "1.14.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b2/ab/070ccfabe870d9f105b04aee1e2860520460ef7ca0213172abfe871463b9/scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675", size = 39076999 },
+    { url = "https://files.pythonhosted.org/packages/a7/c5/02ac82f9bb8f70818099df7e86c3ad28dae64e1347b421d8e3adf26acab6/scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2", size = 29894570 },
+    { url = "https://files.pythonhosted.org/packages/ed/05/7f03e680cc5249c4f96c9e4e845acde08eb1aee5bc216eff8a089baa4ddb/scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617", size = 23103567 },
+    { url = "https://files.pythonhosted.org/packages/5e/fc/9f1413bef53171f379d786aabc104d4abeea48ee84c553a3e3d8c9f96a9c/scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8", size = 25499102 },
+    { url = "https://files.pythonhosted.org/packages/c2/4b/b44bee3c2ddc316b0159b3d87a3d467ef8d7edfd525e6f7364a62cd87d90/scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37", size = 35586346 },
+    { url = "https://files.pythonhosted.org/packages/93/6b/701776d4bd6bdd9b629c387b5140f006185bd8ddea16788a44434376b98f/scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2", size = 41165244 },
+    { url = "https://files.pythonhosted.org/packages/06/57/e6aa6f55729a8f245d8a6984f2855696c5992113a5dc789065020f8be753/scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2", size = 42817917 },
+    { url = "https://files.pythonhosted.org/packages/ea/c2/5ecadc5fcccefaece775feadcd795060adf5c3b29a883bff0e678cfe89af/scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94", size = 44781033 },
+    { url = "https://files.pythonhosted.org/packages/c0/04/2bdacc8ac6387b15db6faa40295f8bd25eccf33f1f13e68a72dc3c60a99e/scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d", size = 39128781 },
+    { url = "https://files.pythonhosted.org/packages/c8/53/35b4d41f5fd42f5781dbd0dd6c05d35ba8aa75c84ecddc7d44756cd8da2e/scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07", size = 29939542 },
+    { url = "https://files.pythonhosted.org/packages/66/67/6ef192e0e4d77b20cc33a01e743b00bc9e68fb83b88e06e636d2619a8767/scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5", size = 23148375 },
+    { url = "https://files.pythonhosted.org/packages/f6/32/3a6dedd51d68eb7b8e7dc7947d5d841bcb699f1bf4463639554986f4d782/scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc", size = 25578573 },
+    { url = "https://files.pythonhosted.org/packages/f0/5a/efa92a58dc3a2898705f1dc9dbaf390ca7d4fba26d6ab8cfffb0c72f656f/scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310", size = 35319299 },
+    { url = "https://files.pythonhosted.org/packages/8e/ee/8a26858ca517e9c64f84b4c7734b89bda8e63bec85c3d2f432d225bb1886/scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066", size = 40849331 },
+    { url = "https://files.pythonhosted.org/packages/a5/cd/06f72bc9187840f1c99e1a8750aad4216fc7dfdd7df46e6280add14b4822/scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1", size = 42544049 },
+    { url = "https://files.pythonhosted.org/packages/aa/7d/43ab67228ef98c6b5dd42ab386eae2d7877036970a0d7e3dd3eb47a0d530/scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f", size = 44521212 },
+    { url = "https://files.pythonhosted.org/packages/50/ef/ac98346db016ff18a6ad7626a35808f37074d25796fd0234c2bb0ed1e054/scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79", size = 39091068 },
+    { url = "https://files.pythonhosted.org/packages/b9/cc/70948fe9f393b911b4251e96b55bbdeaa8cca41f37c26fd1df0232933b9e/scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e", size = 29875417 },
+    { url = "https://files.pythonhosted.org/packages/3b/2e/35f549b7d231c1c9f9639f9ef49b815d816bf54dd050da5da1c11517a218/scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73", size = 23084508 },
+    { url = "https://files.pythonhosted.org/packages/3f/d6/b028e3f3e59fae61fb8c0f450db732c43dd1d836223a589a8be9f6377203/scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e", size = 25503364 },
+    { url = "https://files.pythonhosted.org/packages/a7/2f/6c142b352ac15967744d62b165537a965e95d557085db4beab2a11f7943b/scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d", size = 35292639 },
+    { url = "https://files.pythonhosted.org/packages/56/46/2449e6e51e0d7c3575f289f6acb7f828938eaab8874dbccfeb0cd2b71a27/scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e", size = 40798288 },
+    { url = "https://files.pythonhosted.org/packages/32/cd/9d86f7ed7f4497c9fd3e39f8918dd93d9f647ba80d7e34e4946c0c2d1a7c/scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06", size = 42524647 },
+    { url = "https://files.pythonhosted.org/packages/f5/1b/6ee032251bf4cdb0cc50059374e86a9f076308c1512b61c4e003e241efb7/scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84", size = 44469524 },
+]
+
+[[package]]
+name = "sentence-transformers"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "huggingface-hub" },
+    { name = "numpy" },
+    { name = "pillow" },
+    { name = "scikit-learn" },
+    { name = "scipy" },
+    { name = "torch" },
+    { name = "tqdm" },
+    { name = "transformers" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/41/fb/2368f84127920d86330b533792e66b26264e92b729b5c1998aaa33d2e22f/sentence_transformers-3.0.1.tar.gz", hash = "sha256:8a3d2c537cc4d1014ccc20ac92be3d6135420a3bc60ae29a3a8a9b4bb35fbff6", size = 177258 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/58/4b/922436953394e1bfda05e4bf1fe0e80f609770f256c59a9df7a9254f3e0d/sentence_transformers-3.0.1-py3-none-any.whl", hash = "sha256:01050cc4053c49b9f5b78f6980b5a72db3fd3a0abb9169b1792ac83875505ee6", size = 227071 },
+]
+
+[[package]]
+name = "setuptools"
+version = "73.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8d/37/f4d4ce9bc15e61edba3179f9b0f763fc6d439474d28511b11f0d95bab7a2/setuptools-73.0.1.tar.gz", hash = "sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193", size = 2526506 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/07/6a/0270e295bf30c37567736b7fca10167640898214ff911273af37ddb95770/setuptools-73.0.1-py3-none-any.whl", hash = "sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e", size = 2346588 },
+]
+
+[[package]]
+name = "shapely"
+version = "2.0.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4a/89/0d20bac88016be35ff7d3c0c2ae64b477908f1b1dfa540c5d69ac7af07fe/shapely-2.0.6.tar.gz", hash = "sha256:997f6159b1484059ec239cacaa53467fd8b5564dabe186cd84ac2944663b0bf6", size = 282361 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/37/15/269d8e1f7f658a37e61f7028683c546f520e4e7cedba1e32c77ff9d3a3c7/shapely-2.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aeb0f51a9db176da9a30cb2f4329b6fbd1e26d359012bb0ac3d3c7781667a9e", size = 1449578 },
+    { url = "https://files.pythonhosted.org/packages/37/63/e182e43081fffa0a2d970c480f2ef91647a6ab94098f61748c23c2a485f2/shapely-2.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a7a78b0d51257a367ee115f4d41ca4d46edbd0dd280f697a8092dd3989867b2", size = 1296792 },
+    { url = "https://files.pythonhosted.org/packages/6e/5a/d019f69449329dcd517355444fdb9ddd58bec5e080b8bdba007e8e4c546d/shapely-2.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32c23d2f43d54029f986479f7c1f6e09c6b3a19353a3833c2ffb226fb63a855", size = 2443997 },
+    { url = "https://files.pythonhosted.org/packages/25/aa/53f145e5a610a49af9ac49f2f1be1ec8659ebd5c393d66ac94e57c83b00e/shapely-2.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3dc9fb0eb56498912025f5eb352b5126f04801ed0e8bdbd867d21bdbfd7cbd0", size = 2528334 },
+    { url = "https://files.pythonhosted.org/packages/64/64/0c7b0a22b416d36f6296b92bb4219d82b53d0a7c47e16fd0a4c85f2f117c/shapely-2.0.6-cp311-cp311-win32.whl", hash = "sha256:d93b7e0e71c9f095e09454bf18dad5ea716fb6ced5df3cb044564a00723f339d", size = 1294669 },
+    { url = "https://files.pythonhosted.org/packages/b1/5a/6a67d929c467a1973b6bb9f0b00159cc343b02bf9a8d26db1abd2f87aa23/shapely-2.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:c02eb6bf4cfb9fe6568502e85bb2647921ee49171bcd2d4116c7b3109724ef9b", size = 1442032 },
+    { url = "https://files.pythonhosted.org/packages/46/77/efd9f9d4b6a762f976f8b082f54c9be16f63050389500fb52e4f6cc07c1a/shapely-2.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cec9193519940e9d1b86a3b4f5af9eb6910197d24af02f247afbfb47bcb3fab0", size = 1450326 },
+    { url = "https://files.pythonhosted.org/packages/68/53/5efa6e7a4036a94fe6276cf7bbb298afded51ca3396b03981ad680c8cc7d/shapely-2.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83b94a44ab04a90e88be69e7ddcc6f332da7c0a0ebb1156e1c4f568bbec983c3", size = 1298480 },
+    { url = "https://files.pythonhosted.org/packages/88/a2/1be1db4fc262e536465a52d4f19d85834724fedf2299a1b9836bc82fe8fa/shapely-2.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:537c4b2716d22c92036d00b34aac9d3775e3691f80c7aa517c2c290351f42cd8", size = 2439311 },
+    { url = "https://files.pythonhosted.org/packages/d5/7d/9a57e187cbf2fbbbdfd4044a4f9ce141c8d221f9963750d3b001f0ec080d/shapely-2.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726", size = 2524835 },
+    { url = "https://files.pythonhosted.org/packages/6d/0a/f407509ab56825f39bf8cfce1fb410238da96cf096809c3e404e5bc71ea1/shapely-2.0.6-cp312-cp312-win32.whl", hash = "sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f", size = 1295613 },
+    { url = "https://files.pythonhosted.org/packages/7b/b3/857afd9dfbfc554f10d683ac412eac6fa260d1f4cd2967ecb655c57e831a/shapely-2.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48", size = 1442539 },
+    { url = "https://files.pythonhosted.org/packages/34/e8/d164ef5b0eab86088cde06dee8415519ffd5bb0dd1bd9d021e640e64237c/shapely-2.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013", size = 1445344 },
+    { url = "https://files.pythonhosted.org/packages/ce/e2/9fba7ac142f7831757a10852bfa465683724eadbc93d2d46f74a16f9af04/shapely-2.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7", size = 1296182 },
+    { url = "https://files.pythonhosted.org/packages/cf/dc/790d4bda27d196cd56ec66975eaae3351c65614cafd0e16ddde39ec9fb92/shapely-2.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381", size = 2423426 },
+    { url = "https://files.pythonhosted.org/packages/af/b0/f8169f77eac7392d41e231911e0095eb1148b4d40c50ea9e34d999c89a7e/shapely-2.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805", size = 2513249 },
+    { url = "https://files.pythonhosted.org/packages/f6/1d/a8c0e9ab49ff2f8e4dedd71b0122eafb22a18ad7e9d256025e1f10c84704/shapely-2.0.6-cp313-cp313-win32.whl", hash = "sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a", size = 1294848 },
+    { url = "https://files.pythonhosted.org/packages/23/38/2bc32dd1e7e67a471d4c60971e66df0bdace88656c47a9a728ace0091075/shapely-2.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2", size = 1441371 },
+]
+
+[[package]]
+name = "shellingham"
+version = "1.5.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
+]
+
+[[package]]
+name = "simple-websocket"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "wsproto" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d3/82/3cf87d317911864a2f2a8daf1779fc7f82d5d55e6a8aaa0315f8209047a7/simple-websocket-1.0.0.tar.gz", hash = "sha256:17d2c72f4a2bd85174a97e3e4c88b01c40c3f81b7b648b0cc3ce1305968928c8", size = 13071 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/6d/ea/288a8ac1d9551354488ff60c0ac6a76acc3b6b60f0460ac1944c75e240da/simple_websocket-1.0.0-py3-none-any.whl", hash = "sha256:1d5bf585e415eaa2083e2bcf02a3ecf91f9712e7b3e6b9fa0b461ad04e0837bc", size = 13712 },
+]
+
+[[package]]
+name = "six"
+version = "1.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
+]
+
+[[package]]
+name = "smmap"
+version = "5.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/88/04/b5bf6d21dc4041000ccba7eb17dd3055feb237e7ffc2c20d3fae3af62baa/smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62", size = 22291 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a7/a5/10f97f73544edcdef54409f1d839f6049a0d79df68adbc1ceb24d1aaca42/smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da", size = 24282 },
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 },
+]
+
+[[package]]
+name = "sqlalchemy"
+version = "2.0.32"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "greenlet", marker = "(python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/af/6f/967e987683908af816aa3072c1a6997ac9933cf38d66b0474fb03f253323/SQLAlchemy-2.0.32.tar.gz", hash = "sha256:c1b88cc8b02b6a5f0efb0345a03672d4c897dc7d92585176f88c67346f565ea8", size = 9546691 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fc/a9/e3bd92004095ed6796ea4ac5fdd9606b1e53117ef5b90ae79ac3fc6e225e/SQLAlchemy-2.0.32-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21b053be28a8a414f2ddd401f1be8361e41032d2ef5884b2f31d31cb723e559f", size = 2088752 },
+    { url = "https://files.pythonhosted.org/packages/a9/34/b97f4458eefbdead7ee5ce69cbf3591574c5ba44162dbe52c4386818623f/SQLAlchemy-2.0.32-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b178e875a7a25b5938b53b006598ee7645172fccafe1c291a706e93f48499ff5", size = 2079150 },
+    { url = "https://files.pythonhosted.org/packages/6b/b5/95ff12f5d4eb7813dd5a59ccc8e3c68d4683fedf59801b40704593c3b757/SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723a40ee2cc7ea653645bd4cf024326dea2076673fc9d3d33f20f6c81db83e1d", size = 3197551 },
+    { url = "https://files.pythonhosted.org/packages/ca/af/379f8695ab751acf61868b0098c8d66e2b2ad8b11d9939d5144c82d05bc5/SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295ff8689544f7ee7e819529633d058bd458c1fd7f7e3eebd0f9268ebc56c2a0", size = 3197551 },
+    { url = "https://files.pythonhosted.org/packages/ff/0c/5feaea51f23b5f008f16f9dbf7eec18ee5b9b8eb2875d6e367f52daf633e/SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49496b68cd190a147118af585173ee624114dfb2e0297558c460ad7495f9dfe2", size = 3134583 },
+    { url = "https://files.pythonhosted.org/packages/cc/83/4eca3604f9049a2b92a9ffb818ea1cc8186f722e539a6feee58f931bad34/SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:acd9b73c5c15f0ec5ce18128b1fe9157ddd0044abc373e6ecd5ba376a7e5d961", size = 3154911 },
+    { url = "https://files.pythonhosted.org/packages/3d/56/485ad322f148a8b70060e03b5f130e714f95d839b5e50315e5c5efd1fc05/SQLAlchemy-2.0.32-cp311-cp311-win32.whl", hash = "sha256:9365a3da32dabd3e69e06b972b1ffb0c89668994c7e8e75ce21d3e5e69ddef28", size = 2059047 },
+    { url = "https://files.pythonhosted.org/packages/bb/8c/4548ae42b4ab7f3fe9f1aeb4b1f28ea795485ca44840cb0f3f57aa8ecfcc/SQLAlchemy-2.0.32-cp311-cp311-win_amd64.whl", hash = "sha256:8bd63d051f4f313b102a2af1cbc8b80f061bf78f3d5bd0843ff70b5859e27924", size = 2084480 },
+    { url = "https://files.pythonhosted.org/packages/06/95/88beb07aa61c611829c9ce950f349adcf00065c1bb313090c20d80a520ca/SQLAlchemy-2.0.32-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bab3db192a0c35e3c9d1560eb8332463e29e5507dbd822e29a0a3c48c0a8d92", size = 2087267 },
+    { url = "https://files.pythonhosted.org/packages/11/93/0b28f9d261af927eef3df472e5bbf144fb33e062de770b2c312bb516702b/SQLAlchemy-2.0.32-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:19d98f4f58b13900d8dec4ed09dd09ef292208ee44cc9c2fe01c1f0a2fe440e9", size = 2077732 },
+    { url = "https://files.pythonhosted.org/packages/84/50/1ce1dec4b1cce8f1163c2c58bb1588ac5076c3dbc4bb1d3eab70e798fdd4/SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd33c61513cb1b7371fd40cf221256456d26a56284e7d19d1f0b9f1eb7dd7e8", size = 3227230 },
+    { url = "https://files.pythonhosted.org/packages/9d/b8/aa822988d390cf06afa3c69d86a3a38bba79b51385207cd7cd99d0be17bb/SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6ba0497c1d066dd004e0f02a92426ca2df20fac08728d03f67f6960271feec", size = 3238118 },
+    { url = "https://files.pythonhosted.org/packages/c3/d7/7a65172ed2713acf0262a65392dfcf05ca2b7a67c988ebad425eba9b3843/SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b6be53e4fde0065524f1a0a7929b10e9280987b320716c1509478b712a7688c", size = 3173610 },
+    { url = "https://files.pythonhosted.org/packages/a9/0f/8da0613e3f0b095ef423802943ed4b98242370736034ed5043a43c46c3d4/SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:916a798f62f410c0b80b63683c8061f5ebe237b0f4ad778739304253353bc1cb", size = 3200224 },
+    { url = "https://files.pythonhosted.org/packages/50/ef/973e0bbf2be5c12e34dca92139ca100f51ba078e36c3c06fd1dc8480c209/SQLAlchemy-2.0.32-cp312-cp312-win32.whl", hash = "sha256:31983018b74908ebc6c996a16ad3690301a23befb643093fcfe85efd292e384d", size = 2057626 },
+    { url = "https://files.pythonhosted.org/packages/db/5f/440c324aae82a2ce892ac0fe1d114b9dc9f04e934e8f0762574876a168b5/SQLAlchemy-2.0.32-cp312-cp312-win_amd64.whl", hash = "sha256:4363ed245a6231f2e2957cccdda3c776265a75851f4753c60f3004b90e69bfeb", size = 2083167 },
+    { url = "https://files.pythonhosted.org/packages/99/1b/045185a9f6481d926a451aafaa0d07c98f19ac7abe730dff9630c9ead4fa/SQLAlchemy-2.0.32-py3-none-any.whl", hash = "sha256:e567a8793a692451f706b363ccf3c45e056b67d90ead58c3bc9471af5d212202", size = 1878765 },
+]
+
+[[package]]
+name = "starlette"
+version = "0.37.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "anyio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/61/b5/6bceb93ff20bd7ca36e6f7c540581abb18f53130fabb30ba526e26fd819b/starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823", size = 2843736 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fd/18/31fa32ed6c68ba66220204ef0be798c349d0a20c1901f9d4a794e08c76d8/starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee", size = 71908 },
+]
+
+[[package]]
+name = "sympy"
+version = "1.13.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "mpmath" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/94/15/4a041424c7187f41cce678f5a02189b244e9aac61a18b45cd415a3a470f3/sympy-1.13.2.tar.gz", hash = "sha256:401449d84d07be9d0c7a46a64bd54fe097667d5e7181bfe67ec777be9e01cb13", size = 7532926 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c1/f9/6845bf8fca0eaf847da21c5d5bc6cd92797364662824a11d3f836423a1a5/sympy-1.13.2-py3-none-any.whl", hash = "sha256:c51d75517712f1aed280d4ce58506a4a88d635d6b5dd48b39102a7ae1f3fcfe9", size = 6189289 },
+]
+
+[[package]]
+name = "tabulate"
+version = "0.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 },
+]
+
+[[package]]
+name = "tenacity"
+version = "8.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165 },
+]
+
+[[package]]
+name = "threadpoolctl"
+version = "3.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 },
+]
+
+[[package]]
+name = "tiktoken"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "regex" },
+    { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c4/4a/abaec53e93e3ef37224a4dd9e2fc6bb871e7a538c2b6b9d2a6397271daf4/tiktoken-0.7.0.tar.gz", hash = "sha256:1077266e949c24e0291f6c350433c6f0971365ece2b173a23bc3b9f9defef6b6", size = 33437 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/22/eb/57492b2568eea1d546da5cc1ae7559d924275280db80ba07e6f9b89a914b/tiktoken-0.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:10c7674f81e6e350fcbed7c09a65bca9356eaab27fb2dac65a1e440f2bcfe30f", size = 961468 },
+    { url = "https://files.pythonhosted.org/packages/30/ef/e07dbfcb2f85c84abaa1b035a9279575a8da0236305491dc22ae099327f7/tiktoken-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:084cec29713bc9d4189a937f8a35dbdfa785bd1235a34c1124fe2323821ee93f", size = 907005 },
+    { url = "https://files.pythonhosted.org/packages/ea/9b/f36db825b1e9904c3a2646439cb9923fc1e09208e2e071c6d9dd64ead131/tiktoken-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:811229fde1652fedcca7c6dfe76724d0908775b353556d8a71ed74d866f73f7b", size = 1049183 },
+    { url = "https://files.pythonhosted.org/packages/61/b4/b80d1fe33015e782074e96bbbf4108ccd283b8deea86fb43c15d18b7c351/tiktoken-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b6e7dc2e7ad1b3757e8a24597415bafcfb454cebf9a33a01f2e6ba2e663992", size = 1080830 },
+    { url = "https://files.pythonhosted.org/packages/2a/40/c66ff3a21af6d62a7e0ff428d12002c4e0389f776d3ff96dcaa0bb354eee/tiktoken-0.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1063c5748be36344c7e18c7913c53e2cca116764c2080177e57d62c7ad4576d1", size = 1092967 },
+    { url = "https://files.pythonhosted.org/packages/2e/80/f4c9e255ff236e6a69ce44b927629cefc1b63d3a00e2d1c9ed540c9492d2/tiktoken-0.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20295d21419bfcca092644f7e2f2138ff947a6eb8cfc732c09cc7d76988d4a89", size = 1142682 },
+    { url = "https://files.pythonhosted.org/packages/b1/10/c04b4ff592a5f46b28ebf4c2353f735c02ae7f0ce1b165d00748ced6467e/tiktoken-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:959d993749b083acc57a317cbc643fb85c014d055b2119b739487288f4e5d1cb", size = 799009 },
+    { url = "https://files.pythonhosted.org/packages/1d/46/4cdda4186ce900608f522da34acf442363346688c71b938a90a52d7b84cc/tiktoken-0.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:71c55d066388c55a9c00f61d2c456a6086673ab7dec22dd739c23f77195b1908", size = 960446 },
+    { url = "https://files.pythonhosted.org/packages/b6/30/09ced367d280072d7a3e21f34263dfbbf6378661e7a0f6414e7c18971083/tiktoken-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09ed925bccaa8043e34c519fbb2f99110bd07c6fd67714793c21ac298e449410", size = 906652 },
+    { url = "https://files.pythonhosted.org/packages/e6/7b/c949e4954441a879a67626963dff69096e3c774758b9f2bb0853f7b4e1e7/tiktoken-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c6c40ff1db0f48a7b4d2dafeae73a5607aacb472fa11f125e7baf9dce73704", size = 1047904 },
+    { url = "https://files.pythonhosted.org/packages/50/81/1842a22f15586072280364c2ab1e40835adaf64e42fe80e52aff921ee021/tiktoken-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20b5c6af30e621b4aca094ee61777a44118f52d886dbe4f02b70dfe05c15350", size = 1079836 },
+    { url = "https://files.pythonhosted.org/packages/6d/87/51a133a3d5307cf7ae3754249b0faaa91d3414b85c3d36f80b54d6817aa6/tiktoken-0.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d427614c3e074004efa2f2411e16c826f9df427d3c70a54725cae860f09e4bf4", size = 1092472 },
+    { url = "https://files.pythonhosted.org/packages/a5/1f/c93517dc6d3b2c9e988b8e24f87a8b2d4a4ab28920a3a3f3ea338397ae0c/tiktoken-0.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c46d7af7b8c6987fac9b9f61041b452afe92eb087d29c9ce54951280f899a97", size = 1141881 },
+    { url = "https://files.pythonhosted.org/packages/bf/4b/48ca098cb580c099b5058bf62c4cb5e90ca6130fa43ef4df27088536245b/tiktoken-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0bc603c30b9e371e7c4c7935aba02af5994a909fc3c0fe66e7004070858d3f8f", size = 799281 },
+]
+
+[[package]]
+name = "tokenizers"
+version = "0.19.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "huggingface-hub" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/48/04/2071c150f374aab6d5e92aaec38d0f3c368d227dd9e0469a1f0966ac68d1/tokenizers-0.19.1.tar.gz", hash = "sha256:ee59e6680ed0fdbe6b724cf38bd70400a0c1dd623b07ac729087270caeac88e3", size = 321039 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c8/d6/6e1d728d765eb4102767f071bf7f6439ab10d7f4a975c9217db65715207a/tokenizers-0.19.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5c88d1481f1882c2e53e6bb06491e474e420d9ac7bdff172610c4f9ad3898059", size = 2533448 },
+    { url = "https://files.pythonhosted.org/packages/90/79/d17a0f491d10817cd30f1121a07aa09c8e97a81114b116e473baf1577f09/tokenizers-0.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddf672ed719b4ed82b51499100f5417d7d9f6fb05a65e232249268f35de5ed14", size = 2440254 },
+    { url = "https://files.pythonhosted.org/packages/c7/28/2d11c3ff94f9d42eceb2ea549a06e3f166fe391c5a025e5d96fac898a3ac/tokenizers-0.19.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dadc509cc8a9fe460bd274c0e16ac4184d0958117cf026e0ea8b32b438171594", size = 3684971 },
+    { url = "https://files.pythonhosted.org/packages/36/c6/537f22b57e6003904d35d07962dbde2f2e9bdd791d0241da976a4c7f8194/tokenizers-0.19.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfedf31824ca4915b511b03441784ff640378191918264268e6923da48104acc", size = 3568894 },
+    { url = "https://files.pythonhosted.org/packages/af/ef/3c1deed14ec59b2c8e7e2fa27b2a53f7d101181277a43b89ab17d891ef2e/tokenizers-0.19.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac11016d0a04aa6487b1513a3a36e7bee7eec0e5d30057c9c0408067345c48d2", size = 3426873 },
+    { url = "https://files.pythonhosted.org/packages/06/db/c0320c4798ac6bd12d2ef895bec9d10d216a3b4d6fff10e9d68883ea7edc/tokenizers-0.19.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76951121890fea8330d3a0df9a954b3f2a37e3ec20e5b0530e9a0044ca2e11fe", size = 3965050 },
+    { url = "https://files.pythonhosted.org/packages/4c/8a/a166888d6cb14db55f5eb7ce0b1d4777d145aa27cbf4f945712cf6c29935/tokenizers-0.19.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b342d2ce8fc8d00f376af068e3274e2e8649562e3bc6ae4a67784ded6b99428d", size = 4047855 },
+    { url = "https://files.pythonhosted.org/packages/a7/03/fb50fc03f86016b227a967c8d474f90230c885c0d18f78acdfda7a96ce56/tokenizers-0.19.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d16ff18907f4909dca9b076b9c2d899114dd6abceeb074eca0c93e2353f943aa", size = 3608228 },
+    { url = "https://files.pythonhosted.org/packages/5b/cd/0385e1026e1e03732fd398e964792a3a8433918b166748c82507e014d748/tokenizers-0.19.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:706a37cc5332f85f26efbe2bdc9ef8a9b372b77e4645331a405073e4b3a8c1c6", size = 9633115 },
+    { url = "https://files.pythonhosted.org/packages/25/50/8f8ad0bbdaf09d04b15e6502d1fa1c653754ed7e016e4ae009726aa1a4e4/tokenizers-0.19.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16baac68651701364b0289979ecec728546133e8e8fe38f66fe48ad07996b88b", size = 9949062 },
+    { url = "https://files.pythonhosted.org/packages/db/11/31be66710f1d14526f3588a441efadeb184e1e68458067007b20ead03c59/tokenizers-0.19.1-cp311-none-win32.whl", hash = "sha256:9ed240c56b4403e22b9584ee37d87b8bfa14865134e3e1c3fb4b2c42fafd3256", size = 2041039 },
+    { url = "https://files.pythonhosted.org/packages/65/8e/6d7d72b28f22c422cff8beae10ac3c2e4376b9be721ef8167b7eecd1da62/tokenizers-0.19.1-cp311-none-win_amd64.whl", hash = "sha256:ad57d59341710b94a7d9dbea13f5c1e7d76fd8d9bcd944a7a6ab0b0da6e0cc66", size = 2220386 },
+    { url = "https://files.pythonhosted.org/packages/63/90/2890cd096898dcdb596ee172cde40c0f54a9cf43b0736aa260a5501252af/tokenizers-0.19.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:621d670e1b1c281a1c9698ed89451395d318802ff88d1fc1accff0867a06f153", size = 2530580 },
+    { url = "https://files.pythonhosted.org/packages/74/d1/f4e1e950adb36675dfd8f9d0f4be644f3f3aaf22a5677a4f5c81282b662e/tokenizers-0.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d924204a3dbe50b75630bd16f821ebda6a5f729928df30f582fb5aade90c818a", size = 2436682 },
+    { url = "https://files.pythonhosted.org/packages/ed/30/89b321a16c58d233e301ec15072c0d3ed5014825e72da98604cd3ab2fba1/tokenizers-0.19.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4f3fefdc0446b1a1e6d81cd4c07088ac015665d2e812f6dbba4a06267d1a2c95", size = 3693494 },
+    { url = "https://files.pythonhosted.org/packages/05/40/fa899f32de483500fbc78befd378fd7afba4270f17db707d1a78c0a4ddc3/tokenizers-0.19.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9620b78e0b2d52ef07b0d428323fb34e8ea1219c5eac98c2596311f20f1f9266", size = 3566541 },
+    { url = "https://files.pythonhosted.org/packages/67/14/e7da32ae5fb4971830f1ef335932fae3fa57e76b537e852f146c850aefdf/tokenizers-0.19.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04ce49e82d100594715ac1b2ce87d1a36e61891a91de774755f743babcd0dd52", size = 3430792 },
+    { url = "https://files.pythonhosted.org/packages/f2/4b/aae61bdb6ab584d2612170801703982ee0e35f8b6adacbeefe5a3b277621/tokenizers-0.19.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5c2ff13d157afe413bf7e25789879dd463e5a4abfb529a2d8f8473d8042e28f", size = 3962812 },
+    { url = "https://files.pythonhosted.org/packages/0a/b6/f7b7ef89c4da7b20256e6eab23d3835f05d1ca8f451d31c16cbfe3cd9eb6/tokenizers-0.19.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3174c76efd9d08f836bfccaca7cfec3f4d1c0a4cf3acbc7236ad577cc423c840", size = 4024688 },
+    { url = "https://files.pythonhosted.org/packages/80/54/12047a69f5b382d7ee72044dc89151a2dd0d13b2c9bdcc22654883704d31/tokenizers-0.19.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9d5b6c0e7a1e979bec10ff960fae925e947aab95619a6fdb4c1d8ff3708ce3", size = 3610961 },
+    { url = "https://files.pythonhosted.org/packages/52/b7/1e8a913d18ac28feeda42d4d2d51781874398fb59cd1c1e2653a4b5742ed/tokenizers-0.19.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a179856d1caee06577220ebcfa332af046d576fb73454b8f4d4b0ba8324423ea", size = 9631367 },
+    { url = "https://files.pythonhosted.org/packages/ac/3d/2284f6d99f8f21d09352b88b8cfefa24ab88468d962aeb0aa15c20d76b32/tokenizers-0.19.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:952b80dac1a6492170f8c2429bd11fcaa14377e097d12a1dbe0ef2fb2241e16c", size = 9950121 },
+    { url = "https://files.pythonhosted.org/packages/2a/94/ec3369dbc9b7200c14c8c7a1a04c78b7a7398d0c001e1b7d1ffe30eb93a0/tokenizers-0.19.1-cp312-none-win32.whl", hash = "sha256:01d62812454c188306755c94755465505836fd616f75067abcae529c35edeb57", size = 2044069 },
+    { url = "https://files.pythonhosted.org/packages/0c/97/80bff6937e0c67d30c0facacd4f0bcf4254e581aa4995c73cef8c8640e56/tokenizers-0.19.1-cp312-none-win_amd64.whl", hash = "sha256:b70bfbe3a82d3e3fb2a5e9b22a39f8d1740c96c68b6ace0086b39074f08ab89a", size = 2214527 },
+]
+
+[[package]]
+name = "torch"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "filelock" },
+    { name = "fsspec" },
+    { name = "jinja2" },
+    { name = "networkx" },
+    { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "sympy" },
+    { name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and platform_system == 'Linux'" },
+    { name = "typing-extensions" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/80/83/9b7681e41e59adb6c2b042f7e8eb716515665a6eed3dda4215c6b3385b90/torch-2.4.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:e743adadd8c8152bb8373543964551a7cb7cc20ba898dc8f9c0cdbe47c283de0", size = 797262052 },
+    { url = "https://files.pythonhosted.org/packages/84/fa/2b510a02809ddd70aed821bc2328c4effd206503df38a1328c9f1f957813/torch-2.4.0-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:7334325c0292cbd5c2eac085f449bf57d3690932eac37027e193ba775703c9e6", size = 89850473 },
+    { url = "https://files.pythonhosted.org/packages/18/cf/f69dff972a748e08e1bf602ef94ea5c6d4dd2f41cea22c8ad67a607d8b41/torch-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:97730014da4c57ffacb3c09298c6ce05400606e890bd7a05008d13dd086e46b1", size = 197860580 },
+    { url = "https://files.pythonhosted.org/packages/b7/d0/5e8f96d83889e77b478b90e7d8d24a5fc14c5c9350c6b93d071f45f39096/torch-2.4.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:f169b4ea6dc93b3a33319611fcc47dc1406e4dd539844dcbd2dec4c1b96e166d", size = 62144370 },
+    { url = "https://files.pythonhosted.org/packages/bf/55/b6c74df4695f94a9c3505021bc2bd662e271d028d055b3b2529f3442a3bd/torch-2.4.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:997084a0f9784d2a89095a6dc67c7925e21bf25dea0b3d069b41195016ccfcbb", size = 797168571 },
+    { url = "https://files.pythonhosted.org/packages/9a/5d/327fb72044c22d68a826643abf2e220db3d7f6005a41a6b167af1ffbc708/torch-2.4.0-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:bc3988e8b36d1e8b998d143255d9408d8c75da4ab6dd0dcfd23b623dfb0f0f57", size = 89746726 },
+    { url = "https://files.pythonhosted.org/packages/dc/95/a14dd84ce65e5ce176176393a80b2f74864ee134a31f590140456a4c0959/torch-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:3374128bbf7e62cdaed6c237bfd39809fbcfaa576bee91e904706840c3f2195c", size = 197807123 },
+    { url = "https://files.pythonhosted.org/packages/c7/87/489ebb234e75760e06fa4789fa6d4e13c125beefa1483ce35c9e43dcd395/torch-2.4.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:91aaf00bfe1ffa44dc5b52809d9a95129fca10212eca3ac26420eb11727c6288", size = 62123112 },
+]
+
+[[package]]
+name = "tqdm"
+version = "4.66.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "colorama", marker = "platform_system == 'Windows'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351 },
+]
+
+[[package]]
+name = "transformers"
+version = "4.44.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "filelock" },
+    { name = "huggingface-hub" },
+    { name = "numpy" },
+    { name = "packaging" },
+    { name = "pyyaml" },
+    { name = "regex" },
+    { name = "requests" },
+    { name = "safetensors" },
+    { name = "tokenizers" },
+    { name = "tqdm" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/71/46/62e914365ab463addb0357a88f8d2614aae02f1a2b2b5c24c7ee005ff157/transformers-4.44.1.tar.gz", hash = "sha256:3b9a1a07ca65c665c7bf6109b7da76182184d10bb58d9ab14e6892e7b9e073a2", size = 8110315 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/47/ab/c42556ba7c5aed687256466d472abb9a1b9cbff5730aa42a884d892e061a/transformers-4.44.1-py3-none-any.whl", hash = "sha256:bd2642da18b4e6d29b135c17650cd7ca8e874f2d092d2eddd3ed6b71a93a155c", size = 9465379 },
+]
+
+[[package]]
+name = "triton"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "filelock", marker = "(python_full_version < '3.13' and platform_machine != 'aarch64' and platform_system != 'Darwin') or (python_full_version < '3.13' and platform_system != 'Darwin' and platform_system != 'Linux')" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/33/3e/a2f59384587eff6aeb7d37b6780de7fedd2214935e27520430ca9f5b7975/triton-3.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ce8520437c602fb633f1324cc3871c47bee3b67acf9756c1a66309b60e3216c", size = 209438883 },
+    { url = "https://files.pythonhosted.org/packages/fe/7b/7757205dee3628f75e7991021d15cd1bd0c9b044ca9affe99b50879fc0e1/triton-3.0.0-1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e509deb77f1c067d8640725ef00c5cbfcb2052a1a3cb6a6d343841f92624eb", size = 209464695 },
+]
+
+[[package]]
+name = "typer"
+version = "0.12.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click" },
+    { name = "rich" },
+    { name = "shellingham" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d4/f7/f174a1cae84848ae8b27170a96187b91937b743f0580ff968078fe16930a/typer-0.12.4.tar.gz", hash = "sha256:c9c1613ed6a166162705b3347b8d10b661ccc5d95692654d0fb628118f2c34e6", size = 97945 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ae/cc/15083dcde1252a663398b1b2a173637a3ec65adadfb95137dc95df1e6adc/typer-0.12.4-py3-none-any.whl", hash = "sha256:819aa03699f438397e876aa12b0d63766864ecba1b579092cc9fe35d886e34b6", size = 47402 },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.12.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
+]
+
+[[package]]
+name = "typing-inspect"
+version = "0.9.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "mypy-extensions" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 },
+]
+
+[[package]]
+name = "tzdata"
+version = "2024.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/74/5b/e025d02cb3b66b7b76093404392d4b44343c69101cc85f4d180dd5784717/tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", size = 190559 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/65/58/f9c9e6be752e9fcb8b6a0ee9fb87e6e7a1f6bcab2cdc73f02bb7ba91ada0/tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252", size = 345370 },
+]
+
+[[package]]
+name = "tzlocal"
+version = "5.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "tzdata", marker = "platform_system == 'Windows'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/d3/c19d65ae67636fe63953b20c2e4a8ced4497ea232c43ff8d01db16de8dc0/tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e", size = 30201 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/97/3f/c4c51c55ff8487f2e6d0e618dba917e3c3ee2caae6cf0fbb59c9b1876f2e/tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8", size = 17859 },
+]
+
+[[package]]
+name = "ujson"
+version = "5.10.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f0/00/3110fd566786bfa542adb7932d62035e0c0ef662a8ff6544b6643b3d6fd7/ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1", size = 7154885 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/23/ec/3c551ecfe048bcb3948725251fb0214b5844a12aa60bee08d78315bb1c39/ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00", size = 55353 },
+    { url = "https://files.pythonhosted.org/packages/8d/9f/4731ef0671a0653e9f5ba18db7c4596d8ecbf80c7922dd5fe4150f1aea76/ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126", size = 51813 },
+    { url = "https://files.pythonhosted.org/packages/1f/2b/44d6b9c1688330bf011f9abfdb08911a9dc74f76926dde74e718d87600da/ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8", size = 51988 },
+    { url = "https://files.pythonhosted.org/packages/29/45/f5f5667427c1ec3383478092a414063ddd0dfbebbcc533538fe37068a0a3/ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b", size = 53561 },
+    { url = "https://files.pythonhosted.org/packages/26/21/a0c265cda4dd225ec1be595f844661732c13560ad06378760036fc622587/ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9", size = 58497 },
+    { url = "https://files.pythonhosted.org/packages/28/36/8fde862094fd2342ccc427a6a8584fed294055fdee341661c78660f7aef3/ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f", size = 997877 },
+    { url = "https://files.pythonhosted.org/packages/90/37/9208e40d53baa6da9b6a1c719e0670c3f474c8fc7cc2f1e939ec21c1bc93/ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4", size = 1140632 },
+    { url = "https://files.pythonhosted.org/packages/89/d5/2626c87c59802863d44d19e35ad16b7e658e4ac190b0dead17ff25460b4c/ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1", size = 1043513 },
+    { url = "https://files.pythonhosted.org/packages/2f/ee/03662ce9b3f16855770f0d70f10f0978ba6210805aa310c4eebe66d36476/ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f", size = 38616 },
+    { url = "https://files.pythonhosted.org/packages/3e/20/952dbed5895835ea0b82e81a7be4ebb83f93b079d4d1ead93fcddb3075af/ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720", size = 42071 },
+    { url = "https://files.pythonhosted.org/packages/e8/a6/fd3f8bbd80842267e2d06c3583279555e8354c5986c952385199d57a5b6c/ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5", size = 55642 },
+    { url = "https://files.pythonhosted.org/packages/a8/47/dd03fd2b5ae727e16d5d18919b383959c6d269c7b948a380fdd879518640/ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e", size = 51807 },
+    { url = "https://files.pythonhosted.org/packages/25/23/079a4cc6fd7e2655a473ed9e776ddbb7144e27f04e8fc484a0fb45fe6f71/ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043", size = 51972 },
+    { url = "https://files.pythonhosted.org/packages/04/81/668707e5f2177791869b624be4c06fb2473bf97ee33296b18d1cf3092af7/ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1", size = 53686 },
+    { url = "https://files.pythonhosted.org/packages/bd/50/056d518a386d80aaf4505ccf3cee1c40d312a46901ed494d5711dd939bc3/ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3", size = 58591 },
+    { url = "https://files.pythonhosted.org/packages/fc/d6/aeaf3e2d6fb1f4cfb6bf25f454d60490ed8146ddc0600fae44bfe7eb5a72/ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21", size = 997853 },
+    { url = "https://files.pythonhosted.org/packages/f8/d5/1f2a5d2699f447f7d990334ca96e90065ea7f99b142ce96e85f26d7e78e2/ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2", size = 1140689 },
+    { url = "https://files.pythonhosted.org/packages/f2/2c/6990f4ccb41ed93744aaaa3786394bca0875503f97690622f3cafc0adfde/ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e", size = 1043576 },
+    { url = "https://files.pythonhosted.org/packages/14/f5/a2368463dbb09fbdbf6a696062d0c0f62e4ae6fa65f38f829611da2e8fdd/ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e", size = 38764 },
+    { url = "https://files.pythonhosted.org/packages/59/2d/691f741ffd72b6c84438a93749ac57bf1a3f217ac4b0ea4fd0e96119e118/ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc", size = 42211 },
+    { url = "https://files.pythonhosted.org/packages/0d/69/b3e3f924bb0e8820bb46671979770c5be6a7d51c77a66324cdb09f1acddb/ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287", size = 55646 },
+    { url = "https://files.pythonhosted.org/packages/32/8a/9b748eb543c6cabc54ebeaa1f28035b1bd09c0800235b08e85990734c41e/ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e", size = 51806 },
+    { url = "https://files.pythonhosted.org/packages/39/50/4b53ea234413b710a18b305f465b328e306ba9592e13a791a6a6b378869b/ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557", size = 51975 },
+    { url = "https://files.pythonhosted.org/packages/b4/9d/8061934f960cdb6dd55f0b3ceeff207fcc48c64f58b43403777ad5623d9e/ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988", size = 53693 },
+    { url = "https://files.pythonhosted.org/packages/f5/be/7bfa84b28519ddbb67efc8410765ca7da55e6b93aba84d97764cd5794dbc/ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816", size = 58594 },
+    { url = "https://files.pythonhosted.org/packages/48/eb/85d465abafb2c69d9699cfa5520e6e96561db787d36c677370e066c7e2e7/ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20", size = 997853 },
+    { url = "https://files.pythonhosted.org/packages/9f/76/2a63409fc05d34dd7d929357b7a45e3a2c96f22b4225cd74becd2ba6c4cb/ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0", size = 1140694 },
+    { url = "https://files.pythonhosted.org/packages/45/ed/582c4daba0f3e1688d923b5cb914ada1f9defa702df38a1916c899f7c4d1/ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f", size = 1043580 },
+    { url = "https://files.pythonhosted.org/packages/d7/0c/9837fece153051e19c7bade9f88f9b409e026b9525927824cdf16293b43b/ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165", size = 38766 },
+    { url = "https://files.pythonhosted.org/packages/d7/72/6cb6728e2738c05bbe9bd522d6fc79f86b9a28402f38663e85a28fddd4a0/ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539", size = 42212 },
+]
+
+[[package]]
+name = "unstructured"
+version = "0.15.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "backoff" },
+    { name = "beautifulsoup4" },
+    { name = "chardet" },
+    { name = "dataclasses-json" },
+    { name = "emoji" },
+    { name = "filetype" },
+    { name = "langdetect" },
+    { name = "lxml" },
+    { name = "nltk" },
+    { name = "numpy" },
+    { name = "psutil" },
+    { name = "python-iso639" },
+    { name = "python-magic" },
+    { name = "python-oxmsg" },
+    { name = "rapidfuzz" },
+    { name = "requests" },
+    { name = "tabulate" },
+    { name = "tqdm" },
+    { name = "typing-extensions" },
+    { name = "unstructured-client" },
+    { name = "wrapt" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/74/db/be587e728e2edf684a6c2ead46d05e02951f78b2949c571fed78266941eb/unstructured-0.15.9.tar.gz", hash = "sha256:de26d0e38bac4aa3ae2950f175d0c53a5ccae5c45806b67f55a4af8dea4c407a", size = 1858477 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/58/7b/93126eed91753d65d0c07e9f4c80bd715b6b6003f139483024ae00749aa2/unstructured-0.15.9-py3-none-any.whl", hash = "sha256:ddbb043461cfb9efa1d48a18e62e3b43ff4e0cec25fbf0f28bf345589c1af4d2", size = 2120717 },
+]
+
+[[package]]
+name = "unstructured-client"
+version = "0.25.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "certifi" },
+    { name = "charset-normalizer" },
+    { name = "dataclasses-json" },
+    { name = "deepdiff" },
+    { name = "httpx" },
+    { name = "idna" },
+    { name = "jsonpath-python" },
+    { name = "marshmallow" },
+    { name = "mypy-extensions" },
+    { name = "nest-asyncio" },
+    { name = "packaging" },
+    { name = "pypdf" },
+    { name = "python-dateutil" },
+    { name = "requests" },
+    { name = "requests-toolbelt" },
+    { name = "six" },
+    { name = "typing-extensions" },
+    { name = "typing-inspect" },
+    { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/57/83/6a37f3737acb1593f6e9f2f206af8986de964c7df05493e543e9f444976b/unstructured-client-0.25.5.tar.gz", hash = "sha256:adb97ea56ce65f8b277d5b05f093e9d13a3320ac8dea7265ffa71f5e13ed5f84", size = 38191 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/58/c8/4dadb117b2f43fc2fc3f44d8718e7cff897ad8d4e8c86fc3f27457fbc236/unstructured_client-0.25.5-py3-none-any.whl", hash = "sha256:23537fee984e43d06a75f986a73e420a9659cc92010afb8324fbf67c85962eaf", size = 43888 },
+]
+
+[[package]]
+name = "uritemplate"
+version = "4.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d2/5a/4742fdba39cd02a56226815abfa72fe0aa81c33bed16ed045647d6000eba/uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0", size = 273898 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/81/c0/7461b49cd25aeece13766f02ee576d1db528f1c37ce69aee300e075b485b/uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e", size = 10356 },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/6d/fa469ae21497ddc8bc93e5877702dca7cb8f911e337aca7452b5724f1bb6/urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168", size = 292266 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ca/1c/89ffc63a9605b583d5df2be791a27bc1a42b7c32bab68d3c8f2f73a98cd4/urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", size = 121444 },
+]
+
+[[package]]
+name = "uvicorn"
+version = "0.30.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "click" },
+    { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5a/01/5e637e7aa9dd031be5376b9fb749ec20b86f5a5b6a49b87fabd374d5fa9f/uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788", size = 42825 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f5/8e/cdc7d6263db313030e4c257dd5ba3909ebc4e4fb53ad62d5f09b1a2f5458/uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5", size = 62835 },
+]
+
+[package.optional-dependencies]
+standard = [
+    { name = "colorama", marker = "sys_platform == 'win32'" },
+    { name = "httptools" },
+    { name = "python-dotenv" },
+    { name = "pyyaml" },
+    { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
+    { name = "watchfiles" },
+    { name = "websockets" },
+]
+
+[[package]]
+name = "uvloop"
+version = "0.20.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bc/f1/dc9577455e011ad43d9379e836ee73f40b4f99c02946849a44f7ae64835e/uvloop-0.20.0.tar.gz", hash = "sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469", size = 2329938 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/64/bf/45828beccf685b7ed9638d9b77ef382b470c6ca3b5bff78067e02ffd5663/uvloop-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037", size = 1320593 },
+    { url = "https://files.pythonhosted.org/packages/27/c0/3c24e50bee7802a2add96ca9f0d5eb0ebab07e0a5615539d38aeb89499b9/uvloop-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9", size = 736676 },
+    { url = "https://files.pythonhosted.org/packages/83/ce/ffa3c72954eae36825acfafd2b6a9221d79abd2670c0d25e04d6ef4a2007/uvloop-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e", size = 3494573 },
+    { url = "https://files.pythonhosted.org/packages/46/6d/4caab3a36199ba52b98d519feccfcf48921d7a6649daf14a93c7e77497e9/uvloop-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756", size = 3489932 },
+    { url = "https://files.pythonhosted.org/packages/e4/4f/49c51595bd794945c88613df88922c38076eae2d7653f4624aa6f4980b07/uvloop-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0", size = 4185596 },
+    { url = "https://files.pythonhosted.org/packages/b8/94/7e256731260d313f5049717d1c4582d52a3b132424c95e16954a50ab95d3/uvloop-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf", size = 4185746 },
+    { url = "https://files.pythonhosted.org/packages/2d/64/31cbd379d6e260ac8de3f672f904e924f09715c3f192b09f26cc8e9f574c/uvloop-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d", size = 1324302 },
+    { url = "https://files.pythonhosted.org/packages/1e/6b/9207e7177ff30f78299401f2e1163ea41130d4fd29bcdc6d12572c06b728/uvloop-0.20.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e", size = 738105 },
+    { url = "https://files.pythonhosted.org/packages/c1/ba/b64b10f577519d875992dc07e2365899a1a4c0d28327059ce1e1bdfb6854/uvloop-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9", size = 4090658 },
+    { url = "https://files.pythonhosted.org/packages/0a/f8/5ceea6876154d926604f10c1dd896adf9bce6d55a55911364337b8a5ed8d/uvloop-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab", size = 4173357 },
+    { url = "https://files.pythonhosted.org/packages/18/b2/117ab6bfb18274753fbc319607bf06e216bd7eea8be81d5bac22c912d6a7/uvloop-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5", size = 4029868 },
+    { url = "https://files.pythonhosted.org/packages/6f/52/deb4be09060637ef4752adaa0b75bf770c20c823e8108705792f99cd4a6f/uvloop-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00", size = 4115980 },
+]
+
+[[package]]
+name = "validators"
+version = "0.33.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5d/af/5ad4fed95276e3eb7628d858c88cd205799bcad847e46223760a3129cbb1/validators-0.33.0.tar.gz", hash = "sha256:535867e9617f0100e676a1257ba1e206b9bfd847ddc171e4d44811f07ff0bfbf", size = 70741 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/04/22/91b4bd36df27e651daedd93d03d5d3bb6029fdb0b55494e45ee46c36c570/validators-0.33.0-py3-none-any.whl", hash = "sha256:134b586a98894f8139865953899fc2daeb3d0c35569552c5518f089ae43ed075", size = 43298 },
+]
+
+[[package]]
+name = "watchfiles"
+version = "0.23.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "anyio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9e/1a/b06613ef620d7f5ca712a3d4928ec1c07182159a64277fcdf7738edb0b32/watchfiles-0.23.0.tar.gz", hash = "sha256:9338ade39ff24f8086bb005d16c29f8e9f19e55b18dcb04dfa26fcbc09da497b", size = 37384 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/14/5f/787386438d895145099e1415d1fbd3ff047a4f5e329134fd30677fe83f1f/watchfiles-0.23.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e397b64f7aaf26915bf2ad0f1190f75c855d11eb111cc00f12f97430153c2eab", size = 374801 },
+    { url = "https://files.pythonhosted.org/packages/76/6f/3075cd9c69fdce2544fb13cb9e3c8ad51424cb2c552b019514799a14966e/watchfiles-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4ac73b02ca1824ec0a7351588241fd3953748d3774694aa7ddb5e8e46aef3e3", size = 368210 },
+    { url = "https://files.pythonhosted.org/packages/ab/6b/cd4faa27088a8b612ffdfa25e3d413e676a6173b8b02a33e7fec152d75ca/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130a896d53b48a1cecccfa903f37a1d87dbb74295305f865a3e816452f6e49e4", size = 441356 },
+    { url = "https://files.pythonhosted.org/packages/39/ba/d361135dac6cd0fb4449f4f058c053eb9b42f70ff4d9a13767808e18851c/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5e7803a65eb2d563c73230e9d693c6539e3c975ccfe62526cadde69f3fda0cf", size = 437615 },
+    { url = "https://files.pythonhosted.org/packages/34/2c/c279de01628f467d16b444bdcedf9c4ce3bc5242cb23f9bfb8fbff8522ee/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1aa4cc85202956d1a65c88d18c7b687b8319dbe6b1aec8969784ef7a10e7d1a", size = 456227 },
+    { url = "https://files.pythonhosted.org/packages/a4/9f/a3c9f1fbcd1099554e4f707e14473ff23f0e05013d553755b98c2d86716d/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87f889f6e58849ddb7c5d2cb19e2e074917ed1c6e3ceca50405775166492cca8", size = 472219 },
+    { url = "https://files.pythonhosted.org/packages/22/ee/06a0a6cbde8ac6fff57c33da9e428f42dd0989e60a6ad72ca6534f650a47/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37fd826dac84c6441615aa3f04077adcc5cac7194a021c9f0d69af20fb9fa788", size = 479948 },
+    { url = "https://files.pythonhosted.org/packages/b9/f0/76ad5227da9461b1190de2f9dd21fece09660a9a44607de9c728f3d3e93f/watchfiles-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee7db6e36e7a2c15923072e41ea24d9a0cf39658cb0637ecc9307b09d28827e1", size = 427559 },
+    { url = "https://files.pythonhosted.org/packages/e1/15/daf4361e0a6e6b27f516aaaacbb16baa8d1a266657b2314862fc73f2deaf/watchfiles-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2368c5371c17fdcb5a2ea71c5c9d49f9b128821bfee69503cc38eae00feb3220", size = 616447 },
+    { url = "https://files.pythonhosted.org/packages/b3/e4/2647ca9aaa072e139a4cc6c83c8a15d2f8fa6740913903ab998917a5ed97/watchfiles-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:857af85d445b9ba9178db95658c219dbd77b71b8264e66836a6eba4fbf49c320", size = 598031 },
+    { url = "https://files.pythonhosted.org/packages/3d/02/f223537cd0e3c22df45629710b27b7f89fdf4114be2f3399b83faedf1446/watchfiles-0.23.0-cp311-none-win32.whl", hash = "sha256:1d636c8aeb28cdd04a4aa89030c4b48f8b2954d8483e5f989774fa441c0ed57b", size = 264354 },
+    { url = "https://files.pythonhosted.org/packages/03/31/c1b5ea92100d9774f5a8a89115a43ef1c4fb169b643b6cc930e0cd2c5728/watchfiles-0.23.0-cp311-none-win_amd64.whl", hash = "sha256:46f1d8069a95885ca529645cdbb05aea5837d799965676e1b2b1f95a4206313e", size = 275821 },
+    { url = "https://files.pythonhosted.org/packages/23/9c/810ede8d4dff7e65393b50cbb1a3ef10b6cdb1312a97d8106712175355c8/watchfiles-0.23.0-cp311-none-win_arm64.whl", hash = "sha256:e495ed2a7943503766c5d1ff05ae9212dc2ce1c0e30a80d4f0d84889298fa304", size = 266906 },
+    { url = "https://files.pythonhosted.org/packages/61/52/85cdf326a53f1ae3fbe5dcab13f5729ca91ec2d61140e095a2a4cdf6a9ca/watchfiles-0.23.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1db691bad0243aed27c8354b12d60e8e266b75216ae99d33e927ff5238d270b5", size = 373314 },
+    { url = "https://files.pythonhosted.org/packages/20/5e/a97417a6544615b21c7960a45aeea13e3b42779e0ed3ebdd2d76ad62ab50/watchfiles-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62d2b18cb1edaba311fbbfe83fb5e53a858ba37cacb01e69bc20553bb70911b8", size = 368915 },
+    { url = "https://files.pythonhosted.org/packages/bc/82/537945ed624af6248c9820a99cbfd5902bb5e6a71a01a5b3de0c00f1872e/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e087e8fdf1270d000913c12e6eca44edd02aad3559b3e6b8ef00f0ce76e0636f", size = 441495 },
+    { url = "https://files.pythonhosted.org/packages/28/24/060b064f28083866d916052fcced5c3547c5081a8e27b0702434666aa9a0/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd41d5c72417b87c00b1b635738f3c283e737d75c5fa5c3e1c60cd03eac3af77", size = 437357 },
+    { url = "https://files.pythonhosted.org/packages/b6/00/ac760f3fa8d8975dbeaef9af99b21077e7c38898ac5051c8601649d86d99/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e5f3ca0ff47940ce0a389457b35d6df601c317c1e1a9615981c474452f98de1", size = 456584 },
+    { url = "https://files.pythonhosted.org/packages/f7/52/2f7bbedc5f524d2ba0e9d792dab01ef4418d0f5045a9f5f4e5aca142a30d/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6991e3a78f642368b8b1b669327eb6751439f9f7eaaa625fae67dd6070ecfa0b", size = 471863 },
+    { url = "https://files.pythonhosted.org/packages/b1/64/a80f51cb55c967629930682bf120d5ca9d1c65077c38328be635ed0d567c/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f7252f52a09f8fa5435dc82b6af79483118ce6bd51eb74e6269f05ee22a7b9f", size = 478307 },
+    { url = "https://files.pythonhosted.org/packages/03/f1/fdacfdbffb0635a7d0140ecca6ef7b5bce6566a085f76a65eb796ee54ddd/watchfiles-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e01bcb8d767c58865207a6c2f2792ad763a0fe1119fb0a430f444f5b02a5ea0", size = 427117 },
+    { url = "https://files.pythonhosted.org/packages/d1/23/89b2bef692c350de8a4c2bde501fdf6087889a55f52a3201f0c53b616087/watchfiles-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8e56fbcdd27fce061854ddec99e015dd779cae186eb36b14471fc9ae713b118c", size = 616352 },
+    { url = "https://files.pythonhosted.org/packages/2c/35/a683945181a527083a1146620997b5d6ffe06d716c4497d388bfea813f0c/watchfiles-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bd3e2d64500a6cad28bcd710ee6269fbeb2e5320525acd0cfab5f269ade68581", size = 597165 },
+    { url = "https://files.pythonhosted.org/packages/9e/9b/ec2eabc996e5332fc89c633fbe762e08a58a7df6b5e595dd458c5f7778a4/watchfiles-0.23.0-cp312-none-win32.whl", hash = "sha256:eb99c954291b2fad0eff98b490aa641e128fbc4a03b11c8a0086de8b7077fb75", size = 264293 },
+    { url = "https://files.pythonhosted.org/packages/e0/3a/62add8d90070f4b17f8bbfd66c9eaa9e08af3bc4020c07a9400d1b959aaf/watchfiles-0.23.0-cp312-none-win_amd64.whl", hash = "sha256:dccc858372a56080332ea89b78cfb18efb945da858fabeb67f5a44fa0bcb4ebb", size = 275514 },
+    { url = "https://files.pythonhosted.org/packages/e8/9a/2792d4c24105104bfaf959bffefb09e02d14050913a83242ce4eb1e3f2ff/watchfiles-0.23.0-cp312-none-win_arm64.whl", hash = "sha256:6c21a5467f35c61eafb4e394303720893066897fca937bade5b4f5877d350ff8", size = 266607 },
+    { url = "https://files.pythonhosted.org/packages/f6/5b/1a1d9bca4eae8cf191e74b62cd970f4a010f56f897c11dd2e6caef3ce7e3/watchfiles-0.23.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ba31c32f6b4dceeb2be04f717811565159617e28d61a60bb616b6442027fd4b9", size = 372999 },
+    { url = "https://files.pythonhosted.org/packages/98/e1/76ad010c0a2bb6efbb80383c0bba56db065238f12b0da6e6026b4e69f6aa/watchfiles-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85042ab91814fca99cec4678fc063fb46df4cbb57b4835a1cc2cb7a51e10250e", size = 368511 },
+    { url = "https://files.pythonhosted.org/packages/a1/13/d2d59d545b84fd3cf4f08b69da358209b4276c2c932d060d94a421015074/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24655e8c1c9c114005c3868a3d432c8aa595a786b8493500071e6a52f3d09217", size = 441063 },
+    { url = "https://files.pythonhosted.org/packages/4b/d1/dab28bed3bc9172d44100e5fae8107bd01ef85fc6bddb80d223d0d9f709f/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b1a950ab299a4a78fd6369a97b8763732bfb154fdb433356ec55a5bce9515c1", size = 436805 },
+    { url = "https://files.pythonhosted.org/packages/06/9c/46e0d17853b62b5d4bf8095e7b9bb0b0ad4babb6c6133138929473f161f3/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8d3c5cd327dd6ce0edfc94374fb5883d254fe78a5e9d9dfc237a1897dc73cd1", size = 456411 },
+    { url = "https://files.pythonhosted.org/packages/2c/ff/e891b230bcf3a648352a00b920d4a1142a938f0b97c9e8e27c2eaaeda221/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ff785af8bacdf0be863ec0c428e3288b817e82f3d0c1d652cd9c6d509020dd0", size = 471563 },
+    { url = "https://files.pythonhosted.org/packages/0b/07/f5b54afa8b7c33386c5778d92e681562939900f4ee1c6de9bffc49e7221f/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02b7ba9d4557149410747353e7325010d48edcfe9d609a85cb450f17fd50dc3d", size = 478385 },
+    { url = "https://files.pythonhosted.org/packages/a3/b6/243c1dd351ac9b8258a3ea99c33d04ecdc9766e6c7f13a43452883e92a7a/watchfiles-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a1b05c0afb2cd2f48c1ed2ae5487b116e34b93b13074ed3c22ad5c743109f0", size = 427485 },
+    { url = "https://files.pythonhosted.org/packages/28/8a/6d00aa4aa9a9938de645c1d411e3af82e74db8d25a0c05427b7a88b4d8d3/watchfiles-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:109a61763e7318d9f821b878589e71229f97366fa6a5c7720687d367f3ab9eef", size = 615839 },
+    { url = "https://files.pythonhosted.org/packages/5a/d9/120d212d2952342e2c9673096f5c17cd48e90a7c9ff203ab1ad2f974befe/watchfiles-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9f8e6bb5ac007d4a4027b25f09827ed78cbbd5b9700fd6c54429278dacce05d1", size = 596603 },
+    { url = "https://files.pythonhosted.org/packages/3b/25/ec3676b140a93ac256d058a6f82810cf5e0e42fd444b948c62bc56f57f52/watchfiles-0.23.0-cp313-none-win32.whl", hash = "sha256:f46c6f0aec8d02a52d97a583782d9af38c19a29900747eb048af358a9c1d8e5b", size = 263898 },
+    { url = "https://files.pythonhosted.org/packages/1a/c6/bf3b8cbe6944499fbe0d400175560a200cdecadccbacc8ace74486565d74/watchfiles-0.23.0-cp313-none-win_amd64.whl", hash = "sha256:f449afbb971df5c6faeb0a27bca0427d7b600dd8f4a068492faec18023f0dcff", size = 275220 },
+]
+
+[[package]]
+name = "websocket-client"
+version = "1.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 },
+]
+
+[[package]]
+name = "websockets"
+version = "13.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/b0/e53bdd53d86447d211694f3cf66f163d077c5d68e6bcaa726bf64e88ae3a/websockets-13.0.tar.gz", hash = "sha256:b7bf950234a482b7461afdb2ec99eee3548ec4d53f418c7990bb79c620476602", size = 147622 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/12/29/9fdf8a7f1ced2bac55d36e0b879991498c9858f1e524763434025948d254/websockets-13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06df8306c241c235075d2ae77367038e701e53bc8c1bb4f6644f4f53aa6dedd0", size = 150915 },
+    { url = "https://files.pythonhosted.org/packages/b9/27/723276e7fcb41a3e0859e347014e3e24637982a29222132746b98095ec02/websockets-13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85a1f92a02f0b8c1bf02699731a70a8a74402bb3f82bee36e7768b19a8ed9709", size = 148575 },
+    { url = "https://files.pythonhosted.org/packages/04/54/39b1f809e34f78ebb1dcb9cf57465db9705bbf59f30bd1b3b381272dff2b/websockets-13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9ed02c604349068d46d87ef4c2012c112c791f2bec08671903a6bb2bd9c06784", size = 148825 },
+    { url = "https://files.pythonhosted.org/packages/fe/df/0a8a90162c32ceb9f28415291c1d689310b503288d29169302964105a351/websockets-13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b89849171b590107f6724a7b0790736daead40926ddf47eadf998b4ff51d6414", size = 158482 },
+    { url = "https://files.pythonhosted.org/packages/20/05/227dbb1861cd1e2eb04ac79b136da841dbf6f196e4dc0bd1e67edb4ee69d/websockets-13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:939a16849d71203628157a5e4a495da63967c744e1e32018e9b9e2689aca64d4", size = 157478 },
+    { url = "https://files.pythonhosted.org/packages/fe/dd/3384d3eb26022703895d6ed65aec2d3af6976c3d9aed06200a322e7192cb/websockets-13.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad818cdac37c0ad4c58e51cb4964eae4f18b43c4a83cb37170b0d90c31bd80cf", size = 157855 },
+    { url = "https://files.pythonhosted.org/packages/93/ad/0320a24cd8309e1a257d43d762a732162f2956b769c1ad950b70d4d4d15a/websockets-13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cbfe82a07596a044de78bb7a62519e71690c5812c26c5f1d4b877e64e4f46309", size = 158160 },
+    { url = "https://files.pythonhosted.org/packages/d0/33/acc24e576228301d1dc23ce9d3f7d20f51dfe6c16d1b241e6ba4b2904d3e/websockets-13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e07e76c49f39c5b45cbd7362b94f001ae209a3ea4905ae9a09cfd53b3c76373d", size = 157598 },
+    { url = "https://files.pythonhosted.org/packages/83/47/01645a0ea041e32a9d8946a324845beb8daba2e2f00ee4fd2d04d3ceb598/websockets-13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:372f46a0096cfda23c88f7e42349a33f8375e10912f712e6b496d3a9a557290f", size = 157548 },
+    { url = "https://files.pythonhosted.org/packages/73/89/ea73bc41934eb3ea3f0c04fa7b16455ec5925b8b72aa5e016bd22df5feb5/websockets-13.0-cp311-cp311-win32.whl", hash = "sha256:376a43a4fd96725f13450d3d2e98f4f36c3525c562ab53d9a98dd2950dca9a8a", size = 151756 },
+    { url = "https://files.pythonhosted.org/packages/9b/b1/81f655476532b31c39814d55a1dc1e97ecedc5a1b4f9517ee665aec398f6/websockets-13.0-cp311-cp311-win_amd64.whl", hash = "sha256:2be1382a4daa61e2f3e2be3b3c86932a8db9d1f85297feb6e9df22f391f94452", size = 152200 },
+    { url = "https://files.pythonhosted.org/packages/ad/0a/baeea2931827e73ebe3d958fad9df74ec66d08341d0cf701ced0381adc91/websockets-13.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5407c34776b9b77bd89a5f95eb0a34aaf91889e3f911c63f13035220eb50107", size = 150928 },
+    { url = "https://files.pythonhosted.org/packages/6d/f7/306e2940829db34c5866e869eb5b1a08dd04d1c6d25c71327a028d124871/websockets-13.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4782ec789f059f888c1e8fdf94383d0e64b531cffebbf26dd55afd53ab487ca4", size = 148585 },
+    { url = "https://files.pythonhosted.org/packages/2b/3c/183a4f79e0ce6be8733f824e0a48db3771a373a7206aef900bc1ae4c176e/websockets-13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c8feb8e19ef65c9994e652c5b0324abd657bedd0abeb946fb4f5163012c1e730", size = 148821 },
+    { url = "https://files.pythonhosted.org/packages/03/32/37e1c9dd9aa1e7fa6fb3147d6992d61a20ba63ffee2adc88a392e1ae7376/websockets-13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f3d2e20c442b58dbac593cb1e02bc02d149a86056cc4126d977ad902472e3b", size = 158746 },
+    { url = "https://files.pythonhosted.org/packages/6c/da/0cace6358289c7de1ee02ed0d572dfe92e5cb97270bda60f04a4e49ac5c5/websockets-13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e39d393e0ab5b8bd01717cc26f2922026050188947ff54fe6a49dc489f7750b7", size = 157699 },
+    { url = "https://files.pythonhosted.org/packages/c7/ab/b763b0e8598c4251ec6e17d18f46cbced157772b991200fb0d32550844c5/websockets-13.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f661a4205741bdc88ac9c2b2ec003c72cee97e4acd156eb733662ff004ba429", size = 158124 },
+    { url = "https://files.pythonhosted.org/packages/d0/2d/40b8c3ba08792c2ecdb81613671a4b9bd33b83c50519b235e8eeb0ae21a0/websockets-13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:384129ad0490e06bab2b98c1da9b488acb35bb11e2464c728376c6f55f0d45f3", size = 158415 },
+    { url = "https://files.pythonhosted.org/packages/4c/5e/9a42db20f6c38d247a900bfb8633953df93d8873a99ed9432645a4d5e185/websockets-13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:df5c0eff91f61b8205a6c9f7b255ff390cdb77b61c7b41f79ca10afcbb22b6cb", size = 157795 },
+    { url = "https://files.pythonhosted.org/packages/87/52/7fb5f052eefaa5d2b42da06b314c2af0467fadbd7f360716a1a4d4f7ab67/websockets-13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02cc9bb1a887dac0e08bf657c5d00aa3fac0d03215d35a599130c2034ae6663a", size = 157791 },
+    { url = "https://files.pythonhosted.org/packages/9c/8b/4b7064d1a40fcb85f64bc051d8bdc8a9e388572eb5bec5cb85ffb2c43e01/websockets-13.0-cp312-cp312-win32.whl", hash = "sha256:d9726d2c9bd6aed8cb994d89b3910ca0079406edce3670886ec828a73e7bdd53", size = 151765 },
+    { url = "https://files.pythonhosted.org/packages/8b/a3/297207726b292e85b9a8ce24ef6ab16a056c457100e915a67b6928a58fa9/websockets-13.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0839f35322f7b038d8adcf679e2698c3a483688cc92e3bd15ee4fb06669e9a", size = 152202 },
+    { url = "https://files.pythonhosted.org/packages/03/b6/778678e1ff104df3a869dacb0bc845df34d74f2ff7451f99babccd212203/websockets-13.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:da7e501e59857e8e3e9d10586139dc196b80445a591451ca9998aafba1af5278", size = 150936 },
+    { url = "https://files.pythonhosted.org/packages/fa/25/28609b2555f11e4913a4021147b7a7c5117b5c41da5d26a604a91bae85b9/websockets-13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a00e1e587c655749afb5b135d8d3edcfe84ec6db864201e40a882e64168610b3", size = 148590 },
+    { url = "https://files.pythonhosted.org/packages/cb/1f/e06fb15fde90683fd98e6ca44fb54fe579161ce553d54fdbb578014ae1a7/websockets-13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a7fbf2a8fe7556a8f4e68cb3e736884af7bf93653e79f6219f17ebb75e97d8f0", size = 148826 },
+    { url = "https://files.pythonhosted.org/packages/22/00/9892eee346f44cd814c18888bc1a05880e3f8091e4eb999e6b34634cd278/websockets-13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ea9c9c7443a97ea4d84d3e4d42d0e8c4235834edae652993abcd2aff94affd7", size = 158717 },
+    { url = "https://files.pythonhosted.org/packages/dc/ad/2bdc3a5dd60b639e0f8e76ee4a57fda27abaf05f604708c61c6fd7f8ad88/websockets-13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35c2221b539b360203f3f9ad168e527bf16d903e385068ae842c186efb13d0ea", size = 157660 },
+    { url = "https://files.pythonhosted.org/packages/0c/14/5585de16939608b77a37f8b88e1bd1d430d95ec19d3a8c26ec42a91f2815/websockets-13.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:358d37c5c431dd050ffb06b4b075505aae3f4f795d7fff9794e5ed96ce99b998", size = 158104 },
+    { url = "https://files.pythonhosted.org/packages/7b/1e/6cd9063fd34fe7f649ed9a56d3c91e80dea95cf3ab3344203ee774d51a56/websockets-13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:038e7a0f1bfafc7bf52915ab3506b7a03d1e06381e9f60440c856e8918138151", size = 158463 },
+    { url = "https://files.pythonhosted.org/packages/d9/4d/c3282f8e54103f3d38b5e56851d00911dafd0c37c8d03a9ecc7a25f2a9da/websockets-13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fd038bc9e2c134847f1e0ce3191797fad110756e690c2fdd9702ed34e7a43abb", size = 157850 },
+    { url = "https://files.pythonhosted.org/packages/a1/08/af4f67b74cc6891ee1c34a77b47a3cb77081b824c3df92c1196980df9a4f/websockets-13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93b8c2008f372379fb6e5d2b3f7c9ec32f7b80316543fd3a5ace6610c5cde1b0", size = 157843 },
+    { url = "https://files.pythonhosted.org/packages/b4/b7/2c991e51d48b1b98847d0a0b608508a3b687f215a2390f99cf0ee7dd2777/websockets-13.0-cp313-cp313-win32.whl", hash = "sha256:851fd0afb3bc0b73f7c5b5858975d42769a5fdde5314f4ef2c106aec63100687", size = 151763 },
+    { url = "https://files.pythonhosted.org/packages/bc/0f/f06ed6485cf9cdea7d89c2f6e9d19f1be963ba5d26fb79760bfd17dd4aa5/websockets-13.0-cp313-cp313-win_amd64.whl", hash = "sha256:7d14901fdcf212804970c30ab9ee8f3f0212e620c7ea93079d6534863444fb4e", size = 152197 },
+    { url = "https://files.pythonhosted.org/packages/b2/89/c0be9f09eea478659e9d936210ff03e6a2a3a8d4b8dfac6b1143ff646ded/websockets-13.0-py3-none-any.whl", hash = "sha256:dbbac01e80aee253d44c4f098ab3cc17c822518519e869b284cfbb8cd16cc9de", size = 142957 },
+]
+
+[[package]]
+name = "werkzeug"
+version = "3.0.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0f/e2/6dbcaab07560909ff8f654d3a2e5a60552d937c909455211b1b36d7101dc/werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306", size = 803966 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4b/84/997bbf7c2bf2dc3f09565c6d0b4959fefe5355c18c4096cfd26d83e0785b/werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", size = 227554 },
+]
+
+[[package]]
+name = "win-unicode-console"
+version = "0.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/89/8d/7aad74930380c8972ab282304a2ff45f3d4927108bb6693cabcc9fc6a099/win_unicode_console-0.5.zip", hash = "sha256:d4142d4d56d46f449d6f00536a73625a871cba040f0bc1a2e305a04578f07d1e", size = 31420 }
+
+[[package]]
+name = "wrapt"
+version = "1.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/95/4c/063a912e20bcef7124e0df97282a8af3ff3e4b603ce84c481d6d7346be0a/wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d", size = 53972 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fd/03/c188ac517f402775b90d6f312955a5e53b866c964b32119f2ed76315697e/wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09", size = 37313 },
+    { url = "https://files.pythonhosted.org/packages/0f/16/ea627d7817394db04518f62934a5de59874b587b792300991b3c347ff5e0/wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d", size = 38164 },
+    { url = "https://files.pythonhosted.org/packages/7f/a7/f1212ba098f3de0fd244e2de0f8791ad2539c03bef6c05a9fcb03e45b089/wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389", size = 80890 },
+    { url = "https://files.pythonhosted.org/packages/b7/96/bb5e08b3d6db003c9ab219c487714c13a237ee7dcc572a555eaf1ce7dc82/wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060", size = 73118 },
+    { url = "https://files.pythonhosted.org/packages/6e/52/2da48b35193e39ac53cfb141467d9f259851522d0e8c87153f0ba4205fb1/wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1", size = 80746 },
+    { url = "https://files.pythonhosted.org/packages/11/fb/18ec40265ab81c0e82a934de04596b6ce972c27ba2592c8b53d5585e6bcd/wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3", size = 85668 },
+    { url = "https://files.pythonhosted.org/packages/0f/ef/0ecb1fa23145560431b970418dce575cfaec555ab08617d82eb92afc7ccf/wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956", size = 78556 },
+    { url = "https://files.pythonhosted.org/packages/25/62/cd284b2b747f175b5a96cbd8092b32e7369edab0644c45784871528eb852/wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d", size = 85712 },
+    { url = "https://files.pythonhosted.org/packages/e5/a7/47b7ff74fbadf81b696872d5ba504966591a3468f1bc86bca2f407baef68/wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362", size = 35327 },
+    { url = "https://files.pythonhosted.org/packages/cf/c3/0084351951d9579ae83a3d9e38c140371e4c6b038136909235079f2e6e78/wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89", size = 37523 },
+    { url = "https://files.pythonhosted.org/packages/92/17/224132494c1e23521868cdd57cd1e903f3b6a7ba6996b7b8f077ff8ac7fe/wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b", size = 37614 },
+    { url = "https://files.pythonhosted.org/packages/6a/d7/cfcd73e8f4858079ac59d9db1ec5a1349bc486ae8e9ba55698cc1f4a1dff/wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36", size = 38316 },
+    { url = "https://files.pythonhosted.org/packages/7e/79/5ff0a5c54bda5aec75b36453d06be4f83d5cd4932cc84b7cb2b52cee23e2/wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73", size = 86322 },
+    { url = "https://files.pythonhosted.org/packages/c4/81/e799bf5d419f422d8712108837c1d9bf6ebe3cb2a81ad94413449543a923/wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809", size = 79055 },
+    { url = "https://files.pythonhosted.org/packages/62/62/30ca2405de6a20448ee557ab2cd61ab9c5900be7cbd18a2639db595f0b98/wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b", size = 87291 },
+    { url = "https://files.pythonhosted.org/packages/49/4e/5d2f6d7b57fc9956bf06e944eb00463551f7d52fc73ca35cfc4c2cdb7aed/wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81", size = 90374 },
+    { url = "https://files.pythonhosted.org/packages/a6/9b/c2c21b44ff5b9bf14a83252a8b973fb84923764ff63db3e6dfc3895cf2e0/wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9", size = 83896 },
+    { url = "https://files.pythonhosted.org/packages/14/26/93a9fa02c6f257df54d7570dfe8011995138118d11939a4ecd82cb849613/wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c", size = 91738 },
+    { url = "https://files.pythonhosted.org/packages/a2/5b/4660897233eb2c8c4de3dc7cefed114c61bacb3c28327e64150dc44ee2f6/wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc", size = 35568 },
+    { url = "https://files.pythonhosted.org/packages/5c/cc/8297f9658506b224aa4bd71906447dea6bb0ba629861a758c28f67428b91/wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8", size = 37653 },
+    { url = "https://files.pythonhosted.org/packages/ff/21/abdedb4cdf6ff41ebf01a74087740a709e2edb146490e4d9beea054b0b7a/wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1", size = 23362 },
+]
+
+[[package]]
+name = "wsproto"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226 },
+]
+
+[[package]]
+name = "xlrd"
+version = "2.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/b3/19a2540d21dea5f908304375bd43f5ed7a4c28a370dc9122c565423e6b44/xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88", size = 100259 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a6/0c/c2a72d51fe56e08a08acc85d13013558a2d793028ae7385448a6ccdfae64/xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", size = 96531 },
+]
+
+[[package]]
+name = "xlsxwriter"
+version = "3.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/c3/b36fa44a0610a0f65d2e65ba6a262cbe2554b819f1449731971f7c16ea3c/XlsxWriter-3.2.0.tar.gz", hash = "sha256:9977d0c661a72866a61f9f7a809e25ebbb0fb7036baa3b9fe74afcfca6b3cb8c", size = 198732 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a7/ea/53d1fe468e63e092cf16e2c18d16f50c29851242f9dd12d6a66e0d7f0d02/XlsxWriter-3.2.0-py3-none-any.whl", hash = "sha256:ecfd5405b3e0e228219bcaf24c2ca0915e012ca9464a14048021d21a995d490e", size = 159925 },
+]
+
+[[package]]
+name = "xxhash"
+version = "3.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b8/c7/afed0f131fbda960ff15eee7f304fa0eeb2d58770fade99897984852ef23/xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1", size = 31969 },
+    { url = "https://files.pythonhosted.org/packages/8c/0c/7c3bc6d87e5235672fcc2fb42fd5ad79fe1033925f71bf549ee068c7d1ca/xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8", size = 30800 },
+    { url = "https://files.pythonhosted.org/packages/04/9e/01067981d98069eec1c20201f8c145367698e9056f8bc295346e4ea32dd1/xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166", size = 221566 },
+    { url = "https://files.pythonhosted.org/packages/d4/09/d4996de4059c3ce5342b6e1e6a77c9d6c91acce31f6ed979891872dd162b/xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7", size = 201214 },
+    { url = "https://files.pythonhosted.org/packages/62/f5/6d2dc9f8d55a7ce0f5e7bfef916e67536f01b85d32a9fbf137d4cadbee38/xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623", size = 429433 },
+    { url = "https://files.pythonhosted.org/packages/d9/72/9256303f10e41ab004799a4aa74b80b3c5977d6383ae4550548b24bd1971/xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a", size = 194822 },
+    { url = "https://files.pythonhosted.org/packages/34/92/1a3a29acd08248a34b0e6a94f4e0ed9b8379a4ff471f1668e4dce7bdbaa8/xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88", size = 208538 },
+    { url = "https://files.pythonhosted.org/packages/53/ad/7fa1a109663366de42f724a1cdb8e796a260dbac45047bce153bc1e18abf/xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c", size = 216953 },
+    { url = "https://files.pythonhosted.org/packages/35/02/137300e24203bf2b2a49b48ce898ecce6fd01789c0fcd9c686c0a002d129/xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2", size = 203594 },
+    { url = "https://files.pythonhosted.org/packages/23/03/aeceb273933d7eee248c4322b98b8e971f06cc3880e5f7602c94e5578af5/xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084", size = 210971 },
+    { url = "https://files.pythonhosted.org/packages/e3/64/ed82ec09489474cbb35c716b189ddc1521d8b3de12b1b5ab41ce7f70253c/xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d", size = 415050 },
+    { url = "https://files.pythonhosted.org/packages/71/43/6db4c02dcb488ad4e03bc86d70506c3d40a384ee73c9b5c93338eb1f3c23/xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839", size = 192216 },
+    { url = "https://files.pythonhosted.org/packages/22/6d/db4abec29e7a567455344433d095fdb39c97db6955bb4a2c432e486b4d28/xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da", size = 30120 },
+    { url = "https://files.pythonhosted.org/packages/52/1c/fa3b61c0cf03e1da4767213672efe186b1dfa4fc901a4a694fb184a513d1/xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58", size = 30003 },
+    { url = "https://files.pythonhosted.org/packages/6b/8e/9e6fc572acf6e1cc7ccb01973c213f895cb8668a9d4c2b58a99350da14b7/xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3", size = 26777 },
+    { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969 },
+    { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787 },
+    { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959 },
+    { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006 },
+    { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326 },
+    { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380 },
+    { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934 },
+    { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301 },
+    { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351 },
+    { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294 },
+    { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674 },
+    { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022 },
+    { url = "https://files.pythonhosted.org/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170 },
+    { url = "https://files.pythonhosted.org/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040 },
+    { url = "https://files.pythonhosted.org/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796 },
+    { url = "https://files.pythonhosted.org/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795 },
+    { url = "https://files.pythonhosted.org/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792 },
+    { url = "https://files.pythonhosted.org/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950 },
+    { url = "https://files.pythonhosted.org/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980 },
+    { url = "https://files.pythonhosted.org/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324 },
+    { url = "https://files.pythonhosted.org/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370 },
+    { url = "https://files.pythonhosted.org/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911 },
+    { url = "https://files.pythonhosted.org/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352 },
+    { url = "https://files.pythonhosted.org/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410 },
+    { url = "https://files.pythonhosted.org/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322 },
+    { url = "https://files.pythonhosted.org/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725 },
+    { url = "https://files.pythonhosted.org/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070 },
+    { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172 },
+    { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041 },
+    { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801 },
+]
+
+[[package]]
+name = "yarl"
+version = "1.9.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "idna" },
+    { name = "multidict" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e0/ad/bedcdccbcbf91363fd425a948994f3340924145c2bc8ccb296f4a1e52c28/yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf", size = 141869 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/12/65/4c7f3676209a569405c9f0f492df2bc3a387c253f5d906e36944fdd12277/yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099", size = 132836 },
+    { url = "https://files.pythonhosted.org/packages/3b/c5/81e3dbf5271ab1510860d2ae7a704ef43f93f7cb9326bf7ebb1949a7260b/yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c", size = 83215 },
+    { url = "https://files.pythonhosted.org/packages/20/3d/7dabf580dfc0b588e48830486b488858122b10a61f33325e0d7cf1d6180b/yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0", size = 81237 },
+    { url = "https://files.pythonhosted.org/packages/38/45/7c669999f5d350f4f8f74369b94e0f6705918eee18e38610bfe44af93d4f/yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525", size = 324181 },
+    { url = "https://files.pythonhosted.org/packages/50/49/aa04effe2876cced8867bf9d89b620acf02b733c62adfe22a8218c35d70b/yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8", size = 339412 },
+    { url = "https://files.pythonhosted.org/packages/7d/95/4310771fb9c71599d8466f43347ac18fafd501621e65b93f4f4f16899b1d/yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9", size = 337973 },
+    { url = "https://files.pythonhosted.org/packages/9f/ea/94ad7d8299df89844e666e4aa8a0e9b88e02416cd6a7dd97969e9eae5212/yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42", size = 328126 },
+    { url = "https://files.pythonhosted.org/packages/6d/be/9d4885e2725f5860833547c9e4934b6e0f44a355b24ffc37957264761e3e/yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe", size = 316677 },
+    { url = "https://files.pythonhosted.org/packages/4a/70/5c744d67cad3d093e233cb02f37f2830cb89abfcbb7ad5b5af00ff21d14d/yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce", size = 324243 },
+    { url = "https://files.pythonhosted.org/packages/c2/80/8b38d8fed958ac37afb8b81a54bf4f767b107e2c2004dab165edb58fc51b/yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9", size = 318099 },
+    { url = "https://files.pythonhosted.org/packages/59/50/715bbc7bda65291f9295e757f67854206f4d8be9746d39187724919ac14d/yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572", size = 334924 },
+    { url = "https://files.pythonhosted.org/packages/a8/af/ca9962488027576d7162878a1864cbb1275d298af986ce96bdfd4807d7b2/yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958", size = 335060 },
+    { url = "https://files.pythonhosted.org/packages/28/c7/249a3a903d500ca7369eb542e2847a14f12f249638dcc10371db50cd17ff/yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98", size = 326689 },
+    { url = "https://files.pythonhosted.org/packages/ec/0c/f02dd0b875a7a460f95dc7cf18983ed43c693283d6ab92e0ad71b9e0de8f/yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31", size = 70407 },
+    { url = "https://files.pythonhosted.org/packages/27/41/945ae9a80590e4fb0be166863c6e63d75e4b35789fa3a61ff1dbdcdc220f/yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1", size = 76719 },
+    { url = "https://files.pythonhosted.org/packages/7b/cd/a921122610dedfed94e494af18e85aae23e93274c00ca464cfc591c8f4fb/yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81", size = 129561 },
+    { url = "https://files.pythonhosted.org/packages/7c/a0/887c93020c788f249c24eaab288c46e5fed4d2846080eaf28ed3afc36e8d/yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142", size = 81595 },
+    { url = "https://files.pythonhosted.org/packages/54/99/ed3c92c38f421ba6e36caf6aa91c34118771d252dce800118fa2f44d7962/yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074", size = 79400 },
+    { url = "https://files.pythonhosted.org/packages/ea/45/65801be625ef939acc8b714cf86d4a198c0646e80dc8970359d080c47204/yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129", size = 317397 },
+    { url = "https://files.pythonhosted.org/packages/06/91/9696601a8ba674c8f0c15035cc9e94ca31f541330364adcfd5a399f598bf/yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2", size = 327246 },
+    { url = "https://files.pythonhosted.org/packages/da/3e/bf25177b3618889bf067aacf01ef54e910cd569d14e2f84f5e7bec23bb82/yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78", size = 327321 },
+    { url = "https://files.pythonhosted.org/packages/28/1c/bdb3411467b805737dd2720b85fd082e49f59bf0cc12dc1dfcc80ab3d274/yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4", size = 322424 },
+    { url = "https://files.pythonhosted.org/packages/41/e9/53bc89f039df2824a524a2aa03ee0bfb8f0585b08949e7521f5eab607085/yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0", size = 310868 },
+    { url = "https://files.pythonhosted.org/packages/79/cd/a78c3b0304a4a970b5ae3993f4f5f649443bc8bfa5622f244aed44c810ed/yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51", size = 323452 },
+    { url = "https://files.pythonhosted.org/packages/2e/5e/1c78eb05ae0efae08498fd7ab939435a29f12c7f161732e7fe327e5b8ca1/yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff", size = 313554 },
+    { url = "https://files.pythonhosted.org/packages/04/e0/0029563a8434472697aebb269fdd2ffc8a19e3840add1d5fa169ec7c56e3/yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7", size = 331029 },
+    { url = "https://files.pythonhosted.org/packages/de/1b/7e6b1ad42ccc0ed059066a7ae2b6fd4bce67795d109a99ccce52e9824e96/yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc", size = 333839 },
+    { url = "https://files.pythonhosted.org/packages/85/8a/c364d6e2eeb4e128a5ee9a346fc3a09aa76739c0c4e2a7305989b54f174b/yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10", size = 328251 },
+    { url = "https://files.pythonhosted.org/packages/ec/9d/0da94b33b9fb89041e10f95a14a55b0fef36c60b6a1d5ff85a0c2ecb1a97/yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7", size = 70195 },
+    { url = "https://files.pythonhosted.org/packages/c5/f4/2fdc5a11503bc61818243653d836061c9ce0370e2dd9ac5917258a007675/yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984", size = 76397 },
+    { url = "https://files.pythonhosted.org/packages/4d/05/4d79198ae568a92159de0f89e710a8d19e3fa267b719a236582eee921f4a/yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad", size = 31638 },
+]
+
+[[package]]
+name = "youtube-transcript-api"
+version = "0.6.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a6/e9/82d16b9639bb9fedade810f87ecb18f705591160b5768a79001ac5b99a82/youtube_transcript_api-0.6.2.tar.gz", hash = "sha256:cad223d7620633cec44f657646bffc8bbc5598bd8e70b1ad2fa8277dec305eb7", size = 24565 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/52/42/5f57d37d56bdb09722f226ed81cc1bec63942da745aa27266b16b0e16a5d/youtube_transcript_api-0.6.2-py3-none-any.whl", hash = "sha256:019dbf265c6a68a0591c513fff25ed5a116ce6525832aefdfb34d4df5567121c", size = 24207 },
+]
+
+[[package]]
+name = "zipp"
+version = "3.20.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0e/af/9f2de5bd32549a1b705af7a7c054af3878816a1267cb389c03cc4f342a51/zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31", size = 23244 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/da/cc/b9958af9f9c86b51f846d8487440af495ecf19b16e426fce1ed0b0796175/zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d", size = 9432 },
+]
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c6af4bba559bece5432370df7712b7ea69a0aa66
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,30 @@
+import { sveltekit } from '@sveltejs/kit/vite';
+import { defineConfig } from 'vite';
+
+// /** @type {import('vite').Plugin} */
+// const viteServerConfig = {
+// 	name: 'log-request-middleware',
+// 	configureServer(server) {
+// 		server.middlewares.use((req, res, next) => {
+// 			res.setHeader('Access-Control-Allow-Origin', '*');
+// 			res.setHeader('Access-Control-Allow-Methods', 'GET');
+// 			res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
+// 			res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
+// 			next();
+// 		});
+// 	}
+// };
+
+export default defineConfig({
+	plugins: [sveltekit()],
+	define: {
+		APP_VERSION: JSON.stringify(process.env.npm_package_version),
+		APP_BUILD_HASH: JSON.stringify(process.env.APP_BUILD_HASH || 'dev-build')
+	},
+	build: {
+		sourcemap: true
+	},
+	worker: {
+		format: 'es'
+	}
+});