浏览代码

Merge branch 'dev' into feat/model-config

Jun Siang Cheah 11 月之前
父节点
当前提交
0665703401
共有 100 个文件被更改,包括 6584 次插入2077 次删除
  1. 32 19
      .github/pull_request_template.md
  2. 18 0
      .github/workflows/format-build-frontend.yaml
  3. 302 6
      .prettierignore
  4. 30 27
      Dockerfile
  5. 20 19
      backend/apps/audio/main.py
  6. 89 68
      backend/apps/images/main.py
  7. 11 8
      backend/apps/litellm/main.py
  8. 29 25
      backend/apps/ollama/main.py
  9. 28 19
      backend/apps/openai/main.py
  10. 126 107
      backend/apps/rag/main.py
  11. 13 9
      backend/apps/web/main.py
  12. 16 16
      backend/apps/web/routers/auths.py
  13. 2 2
      backend/apps/web/routers/chats.py
  14. 4 4
      backend/apps/web/routers/configs.py
  15. 3 3
      backend/apps/web/routers/users.py
  16. 278 73
      backend/config.py
  17. 47 39
      backend/main.py
  18. 32 0
      cypress/e2e/chat.cy.ts
  19. 2 6
      cypress/e2e/settings.cy.ts
  20. 0 2
      docker-compose.api.yaml
  21. 0 2
      docker-compose.data.yaml
  22. 0 2
      docker-compose.gpu.yaml
  23. 0 2
      docker-compose.yaml
  24. 2 2
      docs/CONTRIBUTING.md
  25. 990 6
      package-lock.json
  26. 6 4
      package.json
  27. 24 4
      src/app.css
  28. 7 0
      src/lib/apis/streaming/index.ts
  29. 1 1
      src/lib/components/ChangelogModal.svelte
  30. 5 2
      src/lib/components/admin/AddUserModal.svelte
  31. 2 2
      src/lib/components/admin/Settings/General.svelte
  32. 11 11
      src/lib/components/chat/MessageInput.svelte
  33. 27 25
      src/lib/components/chat/Messages.svelte
  34. 1 1
      src/lib/components/chat/Messages/CitationsModal.svelte
  35. 1 1
      src/lib/components/chat/Messages/Name.svelte
  36. 2 2
      src/lib/components/chat/Messages/ProfileImage.svelte
  37. 2 2
      src/lib/components/chat/Messages/RateComment.svelte
  38. 128 118
      src/lib/components/chat/Messages/ResponseMessage.svelte
  39. 169 90
      src/lib/components/chat/Messages/UserMessage.svelte
  40. 3 3
      src/lib/components/chat/ModelSelector.svelte
  41. 12 11
      src/lib/components/chat/ModelSelector/Selector.svelte
  42. 1 1
      src/lib/components/chat/Settings/Account.svelte
  43. 1 1
      src/lib/components/chat/Settings/Advanced.svelte
  44. 2 2
      src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
  45. 2 2
      src/lib/components/chat/Settings/Audio.svelte
  46. 1 1
      src/lib/components/chat/Settings/Connections.svelte
  47. 2 2
      src/lib/components/chat/Settings/General.svelte
  48. 29 2
      src/lib/components/chat/Settings/Interface.svelte
  49. 81 0
      src/lib/components/chat/Settings/Personalization.svelte
  50. 152 0
      src/lib/components/chat/Settings/Personalization/ManageModal.svelte
  51. 25 1
      src/lib/components/chat/SettingsModal.svelte
  52. 15 1
      src/lib/components/chat/ShareChatModal.svelte
  53. 2 2
      src/lib/components/common/Modal.svelte
  54. 22 0
      src/lib/components/common/Switch.svelte
  55. 2 2
      src/lib/components/documents/Settings/General.svelte
  56. 1 1
      src/lib/components/documents/Settings/QueryParams.svelte
  57. 19 0
      src/lib/components/icons/MenuLines.svelte
  58. 11 0
      src/lib/components/icons/User.svelte
  59. 64 37
      src/lib/components/layout/Navbar.svelte
  60. 62 88
      src/lib/components/layout/Navbar/Menu.svelte
  61. 132 280
      src/lib/components/layout/Sidebar.svelte
  62. 16 4
      src/lib/components/layout/Sidebar/ChatMenu.svelte
  63. 147 0
      src/lib/components/layout/Sidebar/UserMenu.svelte
  64. 611 0
      src/lib/components/workspace/Documents.svelte
  65. 409 0
      src/lib/components/workspace/Modelfiles.svelte
  66. 114 116
      src/lib/components/workspace/Playground.svelte
  67. 331 0
      src/lib/components/workspace/Prompts.svelte
  68. 2 2
      src/lib/constants.ts
  69. 1 1
      src/lib/i18n/index.ts
  70. 101 99
      src/lib/i18n/locales/ar-BH/translation.json
  71. 11 9
      src/lib/i18n/locales/bg-BG/translation.json
  72. 11 9
      src/lib/i18n/locales/bn-BD/translation.json
  73. 11 9
      src/lib/i18n/locales/ca-ES/translation.json
  74. 8 6
      src/lib/i18n/locales/de-DE/translation.json
  75. 10 8
      src/lib/i18n/locales/dg-DG/translation.json
  76. 7 5
      src/lib/i18n/locales/en-GB/translation.json
  77. 7 5
      src/lib/i18n/locales/en-US/translation.json
  78. 11 9
      src/lib/i18n/locales/es-ES/translation.json
  79. 11 9
      src/lib/i18n/locales/fa-IR/translation.json
  80. 8 6
      src/lib/i18n/locales/fi-FI/translation.json
  81. 11 9
      src/lib/i18n/locales/fr-CA/translation.json
  82. 11 9
      src/lib/i18n/locales/fr-FR/translation.json
  83. 509 0
      src/lib/i18n/locales/he-IL/translation.json
  84. 11 9
      src/lib/i18n/locales/hi-IN/translation.json
  85. 148 146
      src/lib/i18n/locales/it-IT/translation.json
  86. 11 9
      src/lib/i18n/locales/ja-JP/translation.json
  87. 11 9
      src/lib/i18n/locales/ka-GE/translation.json
  88. 11 9
      src/lib/i18n/locales/ko-KR/translation.json
  89. 4 0
      src/lib/i18n/locales/languages.json
  90. 495 0
      src/lib/i18n/locales/lt-LT/translation.json
  91. 12 10
      src/lib/i18n/locales/nl-NL/translation.json
  92. 148 146
      src/lib/i18n/locales/pl-PL/translation.json
  93. 11 9
      src/lib/i18n/locales/pt-BR/translation.json
  94. 11 9
      src/lib/i18n/locales/pt-PT/translation.json
  95. 12 10
      src/lib/i18n/locales/ru-RU/translation.json
  96. 11 9
      src/lib/i18n/locales/sv-SE/translation.json
  97. 65 63
      src/lib/i18n/locales/tr-TR/translation.json
  98. 8 6
      src/lib/i18n/locales/uk-UA/translation.json
  99. 11 9
      src/lib/i18n/locales/vi-VN/translation.json
  100. 145 143
      src/lib/i18n/locales/zh-CN/translation.json

+ 32 - 19
.github/pull_request_template.md

@@ -1,4 +1,4 @@
-## Pull Request Checklist
+# Pull Request Checklist
 
 - [ ] **Target branch:** Pull requests should target the `dev` branch.
 - [ ] **Description:** Briefly describe the changes in this pull request.
@@ -7,32 +7,46 @@
 - [ ] **Dependencies:** Are there any new dependencies? Have you updated the dependency versions in the documentation?
 - [ ] **Testing:** Have you written and run sufficient tests for the changes?
 - [ ] **Code Review:** Have you self-reviewed your code and addressed any coding standard issues?
-
----
-
-## Description
-
-[Insert a brief description of the changes made in this pull request, including any relevant motivation and impact.]
-
----
-
-### Changelog Entry
+- [ ] **Label title:** Ensure the pull request title is labeled properly 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
+
+- [Briefly describe the changes made in this pull request, including any relevant motivation and impact.]
 
 ### Added
 
 - [List any new features, functionalities, or additions]
 
-### Fixed
-
-- [List any fixes, corrections, or bug fixes]
-
 ### 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 deprecated functionalities]
+- [List any removed features, files, or functionalities]
+
+### Fixed
+
+- [List any fixes, corrections, or bug fixes]
 
 ### Security
 
@@ -40,12 +54,11 @@
 
 ### Breaking Changes
 
-- [List any breaking changes affecting compatibility or functionality]
+- **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]
+  - [Reference any related issues, commits, or other relevant information]

+ 18 - 0
.github/workflows/format-build-frontend.yaml

@@ -37,3 +37,21 @@ jobs:
 
       - 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: '20'
+
+      - name: Install Dependencies
+        run: npm ci
+
+      - name: Run vitest
+        run: npm run test:frontend

+ 302 - 6
.prettierignore

@@ -1,3 +1,11 @@
+# Ignore files for PNPM, NPM and YARN
+pnpm-lock.yaml
+package-lock.json
+yarn.lock
+
+kubernetes/
+
+# Copy of .gitignore
 .DS_Store
 node_modules
 /build
@@ -6,11 +14,299 @@ node_modules
 .env
 .env.*
 !.env.example
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
 
-# Ignore files for PNPM, NPM and YARN
-pnpm-lock.yaml
-package-lock.json
-yarn.lock
+# 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.*
 
-# Ignore kubernetes files
-kubernetes
+# cypress artifacts
+cypress/videos
+cypress/screenshots

+ 30 - 27
Dockerfile

@@ -80,25 +80,25 @@ RUN mkdir -p $HOME/.cache/chroma
 RUN echo -n 00000000-0000-0000-0000-000000000000 > $HOME/.cache/chroma/telemetry_user_id
 
 RUN if [ "$USE_OLLAMA" = "true" ]; then \
-        apt-get update && \
-        # Install pandoc and netcat
-        apt-get install -y --no-install-recommends pandoc netcat-openbsd && \
-        # for RAG OCR
-        apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
-        # install helper tools
-        apt-get install -y --no-install-recommends curl && \
-        # install ollama
-        curl -fsSL https://ollama.com/install.sh | sh && \
-        # cleanup
-        rm -rf /var/lib/apt/lists/*; \
+    apt-get update && \
+    # Install pandoc and netcat
+    apt-get install -y --no-install-recommends pandoc netcat-openbsd curl && \
+    # 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 and netcat
-        apt-get install -y --no-install-recommends pandoc netcat-openbsd && \
-        # for RAG OCR
-        apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
-        # cleanup
-        rm -rf /var/lib/apt/lists/*; \
+    apt-get update && \
+    # Install pandoc and netcat
+    apt-get install -y --no-install-recommends pandoc netcat-openbsd curl jq && \
+    # for RAG OCR
+    apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
+    # cleanup
+    rm -rf /var/lib/apt/lists/*; \
     fi
 
 # install python dependencies
@@ -106,16 +106,16 @@ COPY ./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'])"; \
+    # 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'])"; \
     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'])"; \
+    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'])"; \
     fi
 
 
@@ -134,4 +134,7 @@ COPY ./backend .
 
 EXPOSE 8080
 
+HEALTHCHECK CMD curl --silent --fail http://localhost:8080/health | jq -e '.status == true' || exit 1
+
+
 CMD [ "bash", "start.sh"]

+ 20 - 19
backend/apps/audio/main.py

@@ -45,6 +45,7 @@ from config import (
     AUDIO_OPENAI_API_KEY,
     AUDIO_OPENAI_API_MODEL,
     AUDIO_OPENAI_API_VOICE,
+    AppConfig,
 )
 
 log = logging.getLogger(__name__)
@@ -59,11 +60,11 @@ app.add_middleware(
     allow_headers=["*"],
 )
 
-
-app.state.OPENAI_API_BASE_URL = AUDIO_OPENAI_API_BASE_URL
-app.state.OPENAI_API_KEY = AUDIO_OPENAI_API_KEY
-app.state.OPENAI_API_MODEL = AUDIO_OPENAI_API_MODEL
-app.state.OPENAI_API_VOICE = AUDIO_OPENAI_API_VOICE
+app.state.config = AppConfig()
+app.state.config.OPENAI_API_BASE_URL = AUDIO_OPENAI_API_BASE_URL
+app.state.config.OPENAI_API_KEY = AUDIO_OPENAI_API_KEY
+app.state.config.OPENAI_API_MODEL = AUDIO_OPENAI_API_MODEL
+app.state.config.OPENAI_API_VOICE = AUDIO_OPENAI_API_VOICE
 
 # setting device type for whisper model
 whisper_device_type = DEVICE_TYPE if DEVICE_TYPE and DEVICE_TYPE == "cuda" else "cpu"
@@ -83,10 +84,10 @@ class OpenAIConfigUpdateForm(BaseModel):
 @app.get("/config")
 async def get_openai_config(user=Depends(get_admin_user)):
     return {
-        "OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL,
-        "OPENAI_API_KEY": app.state.OPENAI_API_KEY,
-        "OPENAI_API_MODEL": app.state.OPENAI_API_MODEL,
-        "OPENAI_API_VOICE": app.state.OPENAI_API_VOICE,
+        "OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
+        "OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
+        "OPENAI_API_MODEL": app.state.config.OPENAI_API_MODEL,
+        "OPENAI_API_VOICE": app.state.config.OPENAI_API_VOICE,
     }
 
 
@@ -97,17 +98,17 @@ async def update_openai_config(
     if form_data.key == "":
         raise HTTPException(status_code=400, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)
 
-    app.state.OPENAI_API_BASE_URL = form_data.url
-    app.state.OPENAI_API_KEY = form_data.key
-    app.state.OPENAI_API_MODEL = form_data.model
-    app.state.OPENAI_API_VOICE = form_data.speaker
+    app.state.config.OPENAI_API_BASE_URL = form_data.url
+    app.state.config.OPENAI_API_KEY = form_data.key
+    app.state.config.OPENAI_API_MODEL = form_data.model
+    app.state.config.OPENAI_API_VOICE = form_data.speaker
 
     return {
         "status": True,
-        "OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL,
-        "OPENAI_API_KEY": app.state.OPENAI_API_KEY,
-        "OPENAI_API_MODEL": app.state.OPENAI_API_MODEL,
-        "OPENAI_API_VOICE": app.state.OPENAI_API_VOICE,
+        "OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
+        "OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
+        "OPENAI_API_MODEL": app.state.config.OPENAI_API_MODEL,
+        "OPENAI_API_VOICE": app.state.config.OPENAI_API_VOICE,
     }
 
 
@@ -124,13 +125,13 @@ async def speech(request: Request, user=Depends(get_verified_user)):
         return FileResponse(file_path)
 
     headers = {}
-    headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEY}"
+    headers["Authorization"] = f"Bearer {app.state.config.OPENAI_API_KEY}"
     headers["Content-Type"] = "application/json"
 
     r = None
     try:
         r = requests.post(
-            url=f"{app.state.OPENAI_API_BASE_URL}/audio/speech",
+            url=f"{app.state.config.OPENAI_API_BASE_URL}/audio/speech",
             data=body,
             headers=headers,
             stream=True,

+ 89 - 68
backend/apps/images/main.py

@@ -42,6 +42,7 @@ from config import (
     IMAGE_GENERATION_MODEL,
     IMAGE_SIZE,
     IMAGE_STEPS,
+    AppConfig,
 )
 
 
@@ -60,26 +61,31 @@ app.add_middleware(
     allow_headers=["*"],
 )
 
-app.state.ENGINE = IMAGE_GENERATION_ENGINE
-app.state.ENABLED = ENABLE_IMAGE_GENERATION
+app.state.config = AppConfig()
 
-app.state.OPENAI_API_BASE_URL = IMAGES_OPENAI_API_BASE_URL
-app.state.OPENAI_API_KEY = IMAGES_OPENAI_API_KEY
+app.state.config.ENGINE = IMAGE_GENERATION_ENGINE
+app.state.config.ENABLED = ENABLE_IMAGE_GENERATION
 
-app.state.MODEL = IMAGE_GENERATION_MODEL
+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.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
-app.state.COMFYUI_BASE_URL = COMFYUI_BASE_URL
 
+app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
+app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL
 
-app.state.IMAGE_SIZE = IMAGE_SIZE
-app.state.IMAGE_STEPS = IMAGE_STEPS
+
+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 {"engine": app.state.ENGINE, "enabled": app.state.ENABLED}
+    return {
+        "engine": app.state.config.ENGINE,
+        "enabled": app.state.config.ENABLED,
+    }
 
 
 class ConfigUpdateForm(BaseModel):
@@ -89,9 +95,12 @@ class ConfigUpdateForm(BaseModel):
 
 @app.post("/config/update")
 async def update_config(form_data: ConfigUpdateForm, user=Depends(get_admin_user)):
-    app.state.ENGINE = form_data.engine
-    app.state.ENABLED = form_data.enabled
-    return {"engine": app.state.ENGINE, "enabled": app.state.ENABLED}
+    app.state.config.ENGINE = form_data.engine
+    app.state.config.ENABLED = form_data.enabled
+    return {
+        "engine": app.state.config.ENGINE,
+        "enabled": app.state.config.ENABLED,
+    }
 
 
 class EngineUrlUpdateForm(BaseModel):
@@ -102,8 +111,8 @@ class EngineUrlUpdateForm(BaseModel):
 @app.get("/url")
 async def get_engine_url(user=Depends(get_admin_user)):
     return {
-        "AUTOMATIC1111_BASE_URL": app.state.AUTOMATIC1111_BASE_URL,
-        "COMFYUI_BASE_URL": app.state.COMFYUI_BASE_URL,
+        "AUTOMATIC1111_BASE_URL": app.state.config.AUTOMATIC1111_BASE_URL,
+        "COMFYUI_BASE_URL": app.state.config.COMFYUI_BASE_URL,
     }
 
 
@@ -113,29 +122,29 @@ async def update_engine_url(
 ):
 
     if form_data.AUTOMATIC1111_BASE_URL == None:
-        app.state.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
+        app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
     else:
         url = form_data.AUTOMATIC1111_BASE_URL.strip("/")
         try:
             r = requests.head(url)
-            app.state.AUTOMATIC1111_BASE_URL = url
+            app.state.config.AUTOMATIC1111_BASE_URL = url
         except Exception as e:
             raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
 
     if form_data.COMFYUI_BASE_URL == None:
-        app.state.COMFYUI_BASE_URL = COMFYUI_BASE_URL
+        app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL
     else:
         url = form_data.COMFYUI_BASE_URL.strip("/")
 
         try:
             r = requests.head(url)
-            app.state.COMFYUI_BASE_URL = url
+            app.state.config.COMFYUI_BASE_URL = url
         except Exception as e:
             raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
 
     return {
-        "AUTOMATIC1111_BASE_URL": app.state.AUTOMATIC1111_BASE_URL,
-        "COMFYUI_BASE_URL": app.state.COMFYUI_BASE_URL,
+        "AUTOMATIC1111_BASE_URL": app.state.config.AUTOMATIC1111_BASE_URL,
+        "COMFYUI_BASE_URL": app.state.config.COMFYUI_BASE_URL,
         "status": True,
     }
 
@@ -148,8 +157,8 @@ class OpenAIConfigUpdateForm(BaseModel):
 @app.get("/openai/config")
 async def get_openai_config(user=Depends(get_admin_user)):
     return {
-        "OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL,
-        "OPENAI_API_KEY": app.state.OPENAI_API_KEY,
+        "OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
+        "OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
     }
 
 
@@ -160,13 +169,13 @@ async def update_openai_config(
     if form_data.key == "":
         raise HTTPException(status_code=400, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)
 
-    app.state.OPENAI_API_BASE_URL = form_data.url
-    app.state.OPENAI_API_KEY = form_data.key
+    app.state.config.OPENAI_API_BASE_URL = form_data.url
+    app.state.config.OPENAI_API_KEY = form_data.key
 
     return {
         "status": True,
-        "OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL,
-        "OPENAI_API_KEY": app.state.OPENAI_API_KEY,
+        "OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
+        "OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
     }
 
 
@@ -176,7 +185,7 @@ class ImageSizeUpdateForm(BaseModel):
 
 @app.get("/size")
 async def get_image_size(user=Depends(get_admin_user)):
-    return {"IMAGE_SIZE": app.state.IMAGE_SIZE}
+    return {"IMAGE_SIZE": app.state.config.IMAGE_SIZE}
 
 
 @app.post("/size/update")
@@ -185,9 +194,9 @@ async def update_image_size(
 ):
     pattern = r"^\d+x\d+$"  # Regular expression pattern
     if re.match(pattern, form_data.size):
-        app.state.IMAGE_SIZE = form_data.size
+        app.state.config.IMAGE_SIZE = form_data.size
         return {
-            "IMAGE_SIZE": app.state.IMAGE_SIZE,
+            "IMAGE_SIZE": app.state.config.IMAGE_SIZE,
             "status": True,
         }
     else:
@@ -203,7 +212,7 @@ class ImageStepsUpdateForm(BaseModel):
 
 @app.get("/steps")
 async def get_image_size(user=Depends(get_admin_user)):
-    return {"IMAGE_STEPS": app.state.IMAGE_STEPS}
+    return {"IMAGE_STEPS": app.state.config.IMAGE_STEPS}
 
 
 @app.post("/steps/update")
@@ -211,9 +220,9 @@ async def update_image_size(
     form_data: ImageStepsUpdateForm, user=Depends(get_admin_user)
 ):
     if form_data.steps >= 0:
-        app.state.IMAGE_STEPS = form_data.steps
+        app.state.config.IMAGE_STEPS = form_data.steps
         return {
-            "IMAGE_STEPS": app.state.IMAGE_STEPS,
+            "IMAGE_STEPS": app.state.config.IMAGE_STEPS,
             "status": True,
         }
     else:
@@ -226,14 +235,14 @@ async def update_image_size(
 @app.get("/models")
 def get_models(user=Depends(get_current_user)):
     try:
-        if app.state.ENGINE == "openai":
+        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.ENGINE == "comfyui":
+        elif app.state.config.ENGINE == "comfyui":
 
-            r = requests.get(url=f"{app.state.COMFYUI_BASE_URL}/object_info")
+            r = requests.get(url=f"{app.state.config.COMFYUI_BASE_URL}/object_info")
             info = r.json()
 
             return list(
@@ -245,7 +254,7 @@ def get_models(user=Depends(get_current_user)):
 
         else:
             r = requests.get(
-                url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/sd-models"
+                url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/sd-models"
             )
             models = r.json()
             return list(
@@ -255,23 +264,29 @@ def get_models(user=Depends(get_current_user)):
                 )
             )
     except Exception as e:
-        app.state.ENABLED = False
+        app.state.config.ENABLED = False
         raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
 
 
 @app.get("/models/default")
 async def get_default_model(user=Depends(get_admin_user)):
     try:
-        if app.state.ENGINE == "openai":
-            return {"model": app.state.MODEL if app.state.MODEL else "dall-e-2"}
-        elif app.state.ENGINE == "comfyui":
-            return {"model": app.state.MODEL if app.state.MODEL else ""}
+        if app.state.config.ENGINE == "openai":
+            return {
+                "model": (
+                    app.state.config.MODEL if app.state.config.MODEL else "dall-e-2"
+                )
+            }
+        elif app.state.config.ENGINE == "comfyui":
+            return {"model": (app.state.config.MODEL if app.state.config.MODEL else "")}
         else:
-            r = requests.get(url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/options")
+            r = requests.get(
+                url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options"
+            )
             options = r.json()
             return {"model": options["sd_model_checkpoint"]}
     except Exception as e:
-        app.state.ENABLED = False
+        app.state.config.ENABLED = False
         raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
 
 
@@ -280,20 +295,20 @@ class UpdateModelForm(BaseModel):
 
 
 def set_model_handler(model: str):
-    if app.state.ENGINE == "openai":
-        app.state.MODEL = model
-        return app.state.MODEL
-    if app.state.ENGINE == "comfyui":
-        app.state.MODEL = model
-        return app.state.MODEL
+    if app.state.config.ENGINE in ["openai", "comfyui"]:
+        app.state.config.MODEL = model
+        return app.state.config.MODEL
     else:
-        r = requests.get(url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/options")
+        r = requests.get(
+            url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options"
+        )
         options = r.json()
 
         if model != options["sd_model_checkpoint"]:
             options["sd_model_checkpoint"] = model
             r = requests.post(
-                url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/options", json=options
+                url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
+                json=options,
             )
 
         return options
@@ -382,26 +397,32 @@ def generate_image(
     user=Depends(get_current_user),
 ):
 
-    width, height = tuple(map(int, app.state.IMAGE_SIZE.split("x")))
+    width, height = tuple(map(int, app.state.config.IMAGE_SIZE).split("x"))
 
     r = None
     try:
-        if app.state.ENGINE == "openai":
+        if app.state.config.ENGINE == "openai":
 
             headers = {}
-            headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEY}"
+            headers["Authorization"] = f"Bearer {app.state.config.OPENAI_API_KEY}"
             headers["Content-Type"] = "application/json"
 
             data = {
-                "model": app.state.MODEL if app.state.MODEL != "" else "dall-e-2",
+                "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.IMAGE_SIZE,
+                "size": (
+                    form_data.size if form_data.size else app.state.config.IMAGE_SIZE
+                ),
                 "response_format": "b64_json",
             }
 
             r = requests.post(
-                url=f"{app.state.OPENAI_API_BASE_URL}/images/generations",
+                url=f"{app.state.config.OPENAI_API_BASE_URL}/images/generations",
                 json=data,
                 headers=headers,
             )
@@ -421,7 +442,7 @@ def generate_image(
 
             return images
 
-        elif app.state.ENGINE == "comfyui":
+        elif app.state.config.ENGINE == "comfyui":
 
             data = {
                 "prompt": form_data.prompt,
@@ -430,19 +451,19 @@ def generate_image(
                 "n": form_data.n,
             }
 
-            if app.state.IMAGE_STEPS != None:
-                data["steps"] = app.state.IMAGE_STEPS
+            if app.state.config.IMAGE_STEPS is not None:
+                data["steps"] = app.state.config.IMAGE_STEPS
 
-            if form_data.negative_prompt != None:
+            if form_data.negative_prompt is not None:
                 data["negative_prompt"] = form_data.negative_prompt
 
             data = ImageGenerationPayload(**data)
 
             res = comfyui_generate_image(
-                app.state.MODEL,
+                app.state.config.MODEL,
                 data,
                 user.id,
-                app.state.COMFYUI_BASE_URL,
+                app.state.config.COMFYUI_BASE_URL,
             )
             log.debug(f"res: {res}")
 
@@ -469,14 +490,14 @@ def generate_image(
                 "height": height,
             }
 
-            if app.state.IMAGE_STEPS != None:
-                data["steps"] = app.state.IMAGE_STEPS
+            if app.state.config.IMAGE_STEPS is not None:
+                data["steps"] = app.state.config.IMAGE_STEPS
 
-            if form_data.negative_prompt != None:
+            if form_data.negative_prompt is not None:
                 data["negative_prompt"] = form_data.negative_prompt
 
             r = requests.post(
-                url=f"{app.state.AUTOMATIC1111_BASE_URL}/sdapi/v1/txt2img",
+                url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/txt2img",
                 json=data,
             )
 

+ 11 - 8
backend/apps/litellm/main.py

@@ -1,4 +1,5 @@
 import sys
+from contextlib import asynccontextmanager
 
 from fastapi import FastAPI, Depends, HTTPException
 from fastapi.routing import APIRoute
@@ -46,7 +47,16 @@ import asyncio
 import subprocess
 import yaml
 
-app = FastAPI()
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+    log.info("startup_event")
+    # TODO: Check config.yaml file and create one
+    asyncio.create_task(start_litellm_background())
+    yield
+
+
+app = FastAPI(lifespan=lifespan)
 
 origins = ["*"]
 
@@ -142,13 +152,6 @@ async def shutdown_litellm_background():
         background_process = None
 
 
-@app.on_event("startup")
-async def startup_event():
-    log.info("startup_event")
-    # TODO: Check config.yaml file and create one
-    asyncio.create_task(start_litellm_background())
-
-
 app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
 app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 

+ 29 - 25
backend/apps/ollama/main.py

@@ -47,6 +47,7 @@ from config import (
     MODEL_FILTER_LIST,
     UPLOAD_DIR,
     MODEL_CONFIG,
+    AppConfig,
 )
 from utils.misc import calculate_sha256
 
@@ -62,12 +63,13 @@ app.add_middleware(
     allow_headers=["*"],
 )
 
+app.state.config = AppConfig()
 
 app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
 app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 app.state.MODEL_CONFIG = MODEL_CONFIG.get("ollama", [])
 
-app.state.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
+app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
 app.state.MODELS = {}
 
 
@@ -98,7 +100,7 @@ async def get_status():
 
 @app.get("/urls")
 async def get_ollama_api_urls(user=Depends(get_admin_user)):
-    return {"OLLAMA_BASE_URLS": app.state.OLLAMA_BASE_URLS}
+    return {"OLLAMA_BASE_URLS": app.state.config.OLLAMA_BASE_URLS}
 
 
 class UrlUpdateForm(BaseModel):
@@ -107,10 +109,10 @@ class UrlUpdateForm(BaseModel):
 
 @app.post("/urls/update")
 async def update_ollama_api_url(form_data: UrlUpdateForm, user=Depends(get_admin_user)):
-    app.state.OLLAMA_BASE_URLS = form_data.urls
+    app.state.config.OLLAMA_BASE_URLS = form_data.urls
 
-    log.info(f"app.state.OLLAMA_BASE_URLS: {app.state.OLLAMA_BASE_URLS}")
-    return {"OLLAMA_BASE_URLS": app.state.OLLAMA_BASE_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}
 
 
 @app.get("/cancel/{request_id}")
@@ -155,7 +157,7 @@ def merge_models_lists(model_lists):
 
 async def get_all_models():
     log.info("get_all_models()")
-    tasks = [fetch_url(f"{url}/api/tags") for url in app.state.OLLAMA_BASE_URLS]
+    tasks = [fetch_url(f"{url}/api/tags") for url in app.state.config.OLLAMA_BASE_URLS]
     responses = await asyncio.gather(*tasks)
 
     models = {
@@ -199,7 +201,7 @@ async def get_ollama_tags(
                 return models
         return models
     else:
-        url = app.state.OLLAMA_BASE_URLS[url_idx]
+        url = app.state.config.OLLAMA_BASE_URLS[url_idx]
         try:
             r = requests.request(method="GET", url=f"{url}/api/tags")
             r.raise_for_status()
@@ -229,7 +231,9 @@ async def get_ollama_versions(url_idx: Optional[int] = None):
     if url_idx == None:
 
         # returns lowest version
-        tasks = [fetch_url(f"{url}/api/version") for url in app.state.OLLAMA_BASE_URLS]
+        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))
 
@@ -248,7 +252,7 @@ async def get_ollama_versions(url_idx: Optional[int] = None):
                 detail=ERROR_MESSAGES.OLLAMA_NOT_FOUND,
             )
     else:
-        url = app.state.OLLAMA_BASE_URLS[url_idx]
+        url = app.state.config.OLLAMA_BASE_URLS[url_idx]
         try:
             r = requests.request(method="GET", url=f"{url}/api/version")
             r.raise_for_status()
@@ -280,7 +284,7 @@ class ModelNameForm(BaseModel):
 async def pull_model(
     form_data: ModelNameForm, url_idx: int = 0, user=Depends(get_admin_user)
 ):
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
     r = None
@@ -368,7 +372,7 @@ async def push_model(
                 detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.name),
             )
 
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.debug(f"url: {url}")
 
     r = None
@@ -430,7 +434,7 @@ 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.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
     r = None
@@ -503,7 +507,7 @@ async def copy_model(
                 detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.source),
             )
 
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
     try:
@@ -550,7 +554,7 @@ async def delete_model(
                 detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.name),
             )
 
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
     try:
@@ -590,7 +594,7 @@ async def show_model_info(form_data: ModelNameForm, user=Depends(get_verified_us
         )
 
     url_idx = random.choice(app.state.MODELS[form_data.name]["urls"])
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
     try:
@@ -647,7 +651,7 @@ async def generate_embeddings(
                 detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
             )
 
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
     try:
@@ -697,7 +701,7 @@ def generate_ollama_embeddings(
                 detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
             )
 
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
     try:
@@ -766,7 +770,7 @@ async def generate_completion(
                 detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
             )
 
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
     r = None
@@ -869,7 +873,7 @@ async def generate_chat_completion(
                 detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
             )
 
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
     r = None
@@ -978,7 +982,7 @@ async def generate_openai_chat_completion(
                 detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
             )
 
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
     log.info(f"url: {url}")
 
     r = None
@@ -1077,7 +1081,7 @@ async def get_openai_models(
         }
 
     else:
-        url = app.state.OLLAMA_BASE_URLS[url_idx]
+        url = app.state.config.OLLAMA_BASE_URLS[url_idx]
         try:
             r = requests.request(method="GET", url=f"{url}/api/tags")
             r.raise_for_status()
@@ -1211,7 +1215,7 @@ async def download_model(
 
     if url_idx == None:
         url_idx = 0
-    url = app.state.OLLAMA_BASE_URLS[url_idx]
+    url = app.state.config.OLLAMA_BASE_URLS[url_idx]
 
     file_name = parse_huggingface_url(form_data.url)
 
@@ -1230,7 +1234,7 @@ async def download_model(
 def upload_model(file: UploadFile = File(...), url_idx: Optional[int] = None):
     if url_idx == None:
         url_idx = 0
-    ollama_url = app.state.OLLAMA_BASE_URLS[url_idx]
+    ollama_url = app.state.config.OLLAMA_BASE_URLS[url_idx]
 
     file_path = f"{UPLOAD_DIR}/{file.filename}"
 
@@ -1295,7 +1299,7 @@ def upload_model(file: UploadFile = File(...), url_idx: Optional[int] = None):
 # async def upload_model(file: UploadFile = File(), url_idx: Optional[int] = None):
 #     if url_idx == None:
 #         url_idx = 0
-#     url = app.state.OLLAMA_BASE_URLS[url_idx]
+#     url = app.state.config.OLLAMA_BASE_URLS[url_idx]
 
 #     file_location = os.path.join(UPLOAD_DIR, file.filename)
 #     total_size = file.size
@@ -1332,7 +1336,7 @@ def upload_model(file: UploadFile = File(...), url_idx: Optional[int] = None):
 async def deprecated_proxy(
     path: str, request: Request, user=Depends(get_verified_user)
 ):
-    url = app.state.OLLAMA_BASE_URLS[0]
+    url = app.state.config.OLLAMA_BASE_URLS[0]
     target_url = f"{url}/{path}"
 
     body = await request.body()

+ 28 - 19
backend/apps/openai/main.py

@@ -27,6 +27,7 @@ from config import (
     ENABLE_MODEL_FILTER,
     MODEL_FILTER_LIST,
     MODEL_CONFIG,
+    AppConfig,
 )
 from typing import List, Optional
 
@@ -46,12 +47,14 @@ app.add_middleware(
     allow_headers=["*"],
 )
 
+app.state.config = AppConfig()
+
 app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
 app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 app.state.MODEL_CONFIG = MODEL_CONFIG.get("openai", [])
 
-app.state.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
-app.state.OPENAI_API_KEYS = OPENAI_API_KEYS
+app.state.config.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
+app.state.config.OPENAI_API_KEYS = OPENAI_API_KEYS
 
 app.state.MODELS = {}
 
@@ -77,32 +80,32 @@ class KeysUpdateForm(BaseModel):
 
 @app.get("/urls")
 async def get_openai_urls(user=Depends(get_admin_user)):
-    return {"OPENAI_API_BASE_URLS": app.state.OPENAI_API_BASE_URLS}
+    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.OPENAI_API_BASE_URLS = form_data.urls
-    return {"OPENAI_API_BASE_URLS": app.state.OPENAI_API_BASE_URLS}
+    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.OPENAI_API_KEYS}
+    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.OPENAI_API_KEYS = form_data.keys
-    return {"OPENAI_API_KEYS": app.state.OPENAI_API_KEYS}
+    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.OPENAI_API_BASE_URLS.index("https://api.openai.com/v1")
+        idx = app.state.config.OPENAI_API_BASE_URLS.index("https://api.openai.com/v1")
         body = await request.body()
         name = hashlib.sha256(body).hexdigest()
 
@@ -116,13 +119,15 @@ async def speech(request: Request, user=Depends(get_verified_user)):
             return FileResponse(file_path)
 
         headers = {}
-        headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEYS[idx]}"
+        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.OPENAI_API_BASE_URLS[idx]}/audio/speech",
+                url=f"{app.state.config.OPENAI_API_BASE_URLS[idx]}/audio/speech",
                 data=body,
                 headers=headers,
                 stream=True,
@@ -182,7 +187,8 @@ def merge_models_lists(model_lists):
                 [
                     {**model, "urlIdx": idx}
                     for model in models
-                    if "api.openai.com" not in app.state.OPENAI_API_BASE_URLS[idx]
+                    if "api.openai.com"
+                    not in app.state.config.OPENAI_API_BASE_URLS[idx]
                     or "gpt" in model["id"]
                 ]
             )
@@ -193,12 +199,15 @@ def merge_models_lists(model_lists):
 async def get_all_models():
     log.info("get_all_models()")
 
-    if len(app.state.OPENAI_API_KEYS) == 1 and app.state.OPENAI_API_KEYS[0] == "":
+    if (
+        len(app.state.config.OPENAI_API_KEYS) == 1
+        and app.state.config.OPENAI_API_KEYS[0] == ""
+    ):
         models = {"data": []}
     else:
         tasks = [
-            fetch_url(f"{url}/models", app.state.OPENAI_API_KEYS[idx])
-            for idx, url in enumerate(app.state.OPENAI_API_BASE_URLS)
+            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)
@@ -250,7 +259,7 @@ async def get_models(url_idx: Optional[int] = None, user=Depends(get_current_use
                 return models
         return models
     else:
-        url = app.state.OPENAI_API_BASE_URLS[url_idx]
+        url = app.state.config.OPENAI_API_BASE_URLS[url_idx]
 
         r = None
 
@@ -314,8 +323,8 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
     except json.JSONDecodeError as e:
         log.error("Error loading request body into a dictionary:", e)
 
-    url = app.state.OPENAI_API_BASE_URLS[idx]
-    key = app.state.OPENAI_API_KEYS[idx]
+    url = app.state.config.OPENAI_API_BASE_URLS[idx]
+    key = app.state.config.OPENAI_API_KEYS[idx]
 
     target_url = f"{url}/{path}"
 

+ 126 - 107
backend/apps/rag/main.py

@@ -93,6 +93,7 @@ from config import (
     RAG_TEMPLATE,
     ENABLE_RAG_LOCAL_WEB_FETCH,
     YOUTUBE_LOADER_LANGUAGE,
+    AppConfig,
 )
 
 from constants import ERROR_MESSAGES
@@ -102,30 +103,32 @@ log.setLevel(SRC_LOG_LEVELS["RAG"])
 
 app = FastAPI()
 
-app.state.TOP_K = RAG_TOP_K
-app.state.RELEVANCE_THRESHOLD = RAG_RELEVANCE_THRESHOLD
+app.state.config = AppConfig()
 
-app.state.ENABLE_RAG_HYBRID_SEARCH = ENABLE_RAG_HYBRID_SEARCH
-app.state.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = (
+app.state.config.TOP_K = RAG_TOP_K
+app.state.config.RELEVANCE_THRESHOLD = RAG_RELEVANCE_THRESHOLD
+
+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.CHUNK_SIZE = CHUNK_SIZE
-app.state.CHUNK_OVERLAP = CHUNK_OVERLAP
+app.state.config.CHUNK_SIZE = CHUNK_SIZE
+app.state.config.CHUNK_OVERLAP = CHUNK_OVERLAP
 
-app.state.RAG_EMBEDDING_ENGINE = RAG_EMBEDDING_ENGINE
-app.state.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL
-app.state.RAG_RERANKING_MODEL = RAG_RERANKING_MODEL
-app.state.RAG_TEMPLATE = RAG_TEMPLATE
+app.state.config.RAG_EMBEDDING_ENGINE = RAG_EMBEDDING_ENGINE
+app.state.config.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL
+app.state.config.RAG_RERANKING_MODEL = RAG_RERANKING_MODEL
+app.state.config.RAG_TEMPLATE = RAG_TEMPLATE
 
 
-app.state.OPENAI_API_BASE_URL = RAG_OPENAI_API_BASE_URL
-app.state.OPENAI_API_KEY = RAG_OPENAI_API_KEY
+app.state.config.OPENAI_API_BASE_URL = RAG_OPENAI_API_BASE_URL
+app.state.config.OPENAI_API_KEY = RAG_OPENAI_API_KEY
 
-app.state.PDF_EXTRACT_IMAGES = PDF_EXTRACT_IMAGES
+app.state.config.PDF_EXTRACT_IMAGES = PDF_EXTRACT_IMAGES
 
 
-app.state.YOUTUBE_LOADER_LANGUAGE = YOUTUBE_LOADER_LANGUAGE
+app.state.config.YOUTUBE_LOADER_LANGUAGE = YOUTUBE_LOADER_LANGUAGE
 app.state.YOUTUBE_LOADER_TRANSLATION = None
 
 
@@ -133,7 +136,7 @@ def update_embedding_model(
     embedding_model: str,
     update_model: bool = False,
 ):
-    if embedding_model and app.state.RAG_EMBEDDING_ENGINE == "":
+    if embedding_model and app.state.config.RAG_EMBEDDING_ENGINE == "":
         app.state.sentence_transformer_ef = sentence_transformers.SentenceTransformer(
             get_model_path(embedding_model, update_model),
             device=DEVICE_TYPE,
@@ -158,22 +161,22 @@ def update_reranking_model(
 
 
 update_embedding_model(
-    app.state.RAG_EMBEDDING_MODEL,
+    app.state.config.RAG_EMBEDDING_MODEL,
     RAG_EMBEDDING_MODEL_AUTO_UPDATE,
 )
 
 update_reranking_model(
-    app.state.RAG_RERANKING_MODEL,
+    app.state.config.RAG_RERANKING_MODEL,
     RAG_RERANKING_MODEL_AUTO_UPDATE,
 )
 
 
 app.state.EMBEDDING_FUNCTION = get_embedding_function(
-    app.state.RAG_EMBEDDING_ENGINE,
-    app.state.RAG_EMBEDDING_MODEL,
+    app.state.config.RAG_EMBEDDING_ENGINE,
+    app.state.config.RAG_EMBEDDING_MODEL,
     app.state.sentence_transformer_ef,
-    app.state.OPENAI_API_KEY,
-    app.state.OPENAI_API_BASE_URL,
+    app.state.config.OPENAI_API_KEY,
+    app.state.config.OPENAI_API_BASE_URL,
 )
 
 origins = ["*"]
@@ -200,12 +203,12 @@ class UrlForm(CollectionNameForm):
 async def get_status():
     return {
         "status": True,
-        "chunk_size": app.state.CHUNK_SIZE,
-        "chunk_overlap": app.state.CHUNK_OVERLAP,
-        "template": app.state.RAG_TEMPLATE,
-        "embedding_engine": app.state.RAG_EMBEDDING_ENGINE,
-        "embedding_model": app.state.RAG_EMBEDDING_MODEL,
-        "reranking_model": app.state.RAG_RERANKING_MODEL,
+        "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,
     }
 
 
@@ -213,18 +216,21 @@ async def get_status():
 async def get_embedding_config(user=Depends(get_admin_user)):
     return {
         "status": True,
-        "embedding_engine": app.state.RAG_EMBEDDING_ENGINE,
-        "embedding_model": app.state.RAG_EMBEDDING_MODEL,
+        "embedding_engine": app.state.config.RAG_EMBEDDING_ENGINE,
+        "embedding_model": app.state.config.RAG_EMBEDDING_MODEL,
         "openai_config": {
-            "url": app.state.OPENAI_API_BASE_URL,
-            "key": app.state.OPENAI_API_KEY,
+            "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.RAG_RERANKING_MODEL}
+    return {
+        "status": True,
+        "reranking_model": app.state.config.RAG_RERANKING_MODEL,
+    }
 
 
 class OpenAIConfigForm(BaseModel):
@@ -243,34 +249,34 @@ async def update_embedding_config(
     form_data: EmbeddingModelUpdateForm, user=Depends(get_admin_user)
 ):
     log.info(
-        f"Updating embedding model: {app.state.RAG_EMBEDDING_MODEL} to {form_data.embedding_model}"
+        f"Updating embedding model: {app.state.config.RAG_EMBEDDING_MODEL} to {form_data.embedding_model}"
     )
     try:
-        app.state.RAG_EMBEDDING_ENGINE = form_data.embedding_engine
-        app.state.RAG_EMBEDDING_MODEL = form_data.embedding_model
+        app.state.config.RAG_EMBEDDING_ENGINE = form_data.embedding_engine
+        app.state.config.RAG_EMBEDDING_MODEL = form_data.embedding_model
 
-        if app.state.RAG_EMBEDDING_ENGINE in ["ollama", "openai"]:
+        if app.state.config.RAG_EMBEDDING_ENGINE in ["ollama", "openai"]:
             if form_data.openai_config != None:
-                app.state.OPENAI_API_BASE_URL = form_data.openai_config.url
-                app.state.OPENAI_API_KEY = form_data.openai_config.key
+                app.state.config.OPENAI_API_BASE_URL = form_data.openai_config.url
+                app.state.config.OPENAI_API_KEY = form_data.openai_config.key
 
-        update_embedding_model(app.state.RAG_EMBEDDING_MODEL, True)
+        update_embedding_model(app.state.config.RAG_EMBEDDING_MODEL), True
 
         app.state.EMBEDDING_FUNCTION = get_embedding_function(
-            app.state.RAG_EMBEDDING_ENGINE,
-            app.state.RAG_EMBEDDING_MODEL,
+            app.state.config.RAG_EMBEDDING_ENGINE,
+            app.state.config.RAG_EMBEDDING_MODEL,
             app.state.sentence_transformer_ef,
-            app.state.OPENAI_API_KEY,
-            app.state.OPENAI_API_BASE_URL,
+            app.state.config.OPENAI_API_KEY,
+            app.state.config.OPENAI_API_BASE_URL,
         )
 
         return {
             "status": True,
-            "embedding_engine": app.state.RAG_EMBEDDING_ENGINE,
-            "embedding_model": app.state.RAG_EMBEDDING_MODEL,
+            "embedding_engine": app.state.config.RAG_EMBEDDING_ENGINE,
+            "embedding_model": app.state.config.RAG_EMBEDDING_MODEL,
             "openai_config": {
-                "url": app.state.OPENAI_API_BASE_URL,
-                "key": app.state.OPENAI_API_KEY,
+                "url": app.state.config.OPENAI_API_BASE_URL,
+                "key": app.state.config.OPENAI_API_KEY,
             },
         }
     except Exception as e:
@@ -290,16 +296,16 @@ async def update_reranking_config(
     form_data: RerankingModelUpdateForm, user=Depends(get_admin_user)
 ):
     log.info(
-        f"Updating reranking model: {app.state.RAG_RERANKING_MODEL} to {form_data.reranking_model}"
+        f"Updating reranking model: {app.state.config.RAG_RERANKING_MODEL} to {form_data.reranking_model}"
     )
     try:
-        app.state.RAG_RERANKING_MODEL = form_data.reranking_model
+        app.state.config.RAG_RERANKING_MODEL = form_data.reranking_model
 
-        update_reranking_model(app.state.RAG_RERANKING_MODEL, True)
+        update_reranking_model(app.state.config.RAG_RERANKING_MODEL), True
 
         return {
             "status": True,
-            "reranking_model": app.state.RAG_RERANKING_MODEL,
+            "reranking_model": app.state.config.RAG_RERANKING_MODEL,
         }
     except Exception as e:
         log.exception(f"Problem updating reranking model: {e}")
@@ -313,14 +319,14 @@ async def update_reranking_config(
 async def get_rag_config(user=Depends(get_admin_user)):
     return {
         "status": True,
-        "pdf_extract_images": app.state.PDF_EXTRACT_IMAGES,
+        "pdf_extract_images": app.state.config.PDF_EXTRACT_IMAGES,
         "chunk": {
-            "chunk_size": app.state.CHUNK_SIZE,
-            "chunk_overlap": app.state.CHUNK_OVERLAP,
+            "chunk_size": app.state.config.CHUNK_SIZE,
+            "chunk_overlap": app.state.config.CHUNK_OVERLAP,
         },
-        "web_loader_ssl_verification": app.state.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
+        "web_loader_ssl_verification": app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
         "youtube": {
-            "language": app.state.YOUTUBE_LOADER_LANGUAGE,
+            "language": app.state.config.YOUTUBE_LOADER_LANGUAGE,
             "translation": app.state.YOUTUBE_LOADER_TRANSLATION,
         },
     }
@@ -345,50 +351,52 @@ class ConfigUpdateForm(BaseModel):
 
 @app.post("/config/update")
 async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_user)):
-    app.state.PDF_EXTRACT_IMAGES = (
+    app.state.config.PDF_EXTRACT_IMAGES = (
         form_data.pdf_extract_images
-        if form_data.pdf_extract_images != None
-        else app.state.PDF_EXTRACT_IMAGES
+        if form_data.pdf_extract_images is not None
+        else app.state.config.PDF_EXTRACT_IMAGES
     )
 
-    app.state.CHUNK_SIZE = (
-        form_data.chunk.chunk_size if form_data.chunk != None else app.state.CHUNK_SIZE
+    app.state.config.CHUNK_SIZE = (
+        form_data.chunk.chunk_size
+        if form_data.chunk is not None
+        else app.state.config.CHUNK_SIZE
     )
 
-    app.state.CHUNK_OVERLAP = (
+    app.state.config.CHUNK_OVERLAP = (
         form_data.chunk.chunk_overlap
-        if form_data.chunk != None
-        else app.state.CHUNK_OVERLAP
+        if form_data.chunk is not None
+        else app.state.config.CHUNK_OVERLAP
     )
 
-    app.state.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = (
+    app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = (
         form_data.web_loader_ssl_verification
         if form_data.web_loader_ssl_verification != None
-        else app.state.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION
+        else app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION
     )
 
-    app.state.YOUTUBE_LOADER_LANGUAGE = (
+    app.state.config.YOUTUBE_LOADER_LANGUAGE = (
         form_data.youtube.language
-        if form_data.youtube != None
-        else app.state.YOUTUBE_LOADER_LANGUAGE
+        if form_data.youtube is not None
+        else app.state.config.YOUTUBE_LOADER_LANGUAGE
     )
 
     app.state.YOUTUBE_LOADER_TRANSLATION = (
         form_data.youtube.translation
-        if form_data.youtube != None
+        if form_data.youtube is not None
         else app.state.YOUTUBE_LOADER_TRANSLATION
     )
 
     return {
         "status": True,
-        "pdf_extract_images": app.state.PDF_EXTRACT_IMAGES,
+        "pdf_extract_images": app.state.config.PDF_EXTRACT_IMAGES,
         "chunk": {
-            "chunk_size": app.state.CHUNK_SIZE,
-            "chunk_overlap": app.state.CHUNK_OVERLAP,
+            "chunk_size": app.state.config.CHUNK_SIZE,
+            "chunk_overlap": app.state.config.CHUNK_OVERLAP,
         },
-        "web_loader_ssl_verification": app.state.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
+        "web_loader_ssl_verification": app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
         "youtube": {
-            "language": app.state.YOUTUBE_LOADER_LANGUAGE,
+            "language": app.state.config.YOUTUBE_LOADER_LANGUAGE,
             "translation": app.state.YOUTUBE_LOADER_TRANSLATION,
         },
     }
@@ -398,7 +406,7 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_
 async def get_rag_template(user=Depends(get_current_user)):
     return {
         "status": True,
-        "template": app.state.RAG_TEMPLATE,
+        "template": app.state.config.RAG_TEMPLATE,
     }
 
 
@@ -406,10 +414,10 @@ async def get_rag_template(user=Depends(get_current_user)):
 async def get_query_settings(user=Depends(get_admin_user)):
     return {
         "status": True,
-        "template": app.state.RAG_TEMPLATE,
-        "k": app.state.TOP_K,
-        "r": app.state.RELEVANCE_THRESHOLD,
-        "hybrid": app.state.ENABLE_RAG_HYBRID_SEARCH,
+        "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,
     }
 
 
@@ -424,16 +432,20 @@ class QuerySettingsForm(BaseModel):
 async def update_query_settings(
     form_data: QuerySettingsForm, user=Depends(get_admin_user)
 ):
-    app.state.RAG_TEMPLATE = form_data.template if form_data.template else RAG_TEMPLATE
-    app.state.TOP_K = form_data.k if form_data.k else 4
-    app.state.RELEVANCE_THRESHOLD = form_data.r if form_data.r else 0.0
-    app.state.ENABLE_RAG_HYBRID_SEARCH = form_data.hybrid if form_data.hybrid else False
+    app.state.config.RAG_TEMPLATE = (
+        form_data.template if form_data.template else RAG_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.RAG_TEMPLATE,
-        "k": app.state.TOP_K,
-        "r": app.state.RELEVANCE_THRESHOLD,
-        "hybrid": app.state.ENABLE_RAG_HYBRID_SEARCH,
+        "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,
     }
 
 
@@ -451,21 +463,23 @@ def query_doc_handler(
     user=Depends(get_current_user),
 ):
     try:
-        if app.state.ENABLE_RAG_HYBRID_SEARCH:
+        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.TOP_K,
+                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.RELEVANCE_THRESHOLD,
+                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.TOP_K,
+                k=form_data.k if form_data.k else app.state.config.TOP_K,
             )
     except Exception as e:
         log.exception(e)
@@ -489,21 +503,23 @@ def query_collection_handler(
     user=Depends(get_current_user),
 ):
     try:
-        if app.state.ENABLE_RAG_HYBRID_SEARCH:
+        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.TOP_K,
+                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.RELEVANCE_THRESHOLD,
+                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.TOP_K,
+                k=form_data.k if form_data.k else app.state.config.TOP_K,
             )
 
     except Exception as e:
@@ -520,7 +536,7 @@ def store_youtube_video(form_data: UrlForm, user=Depends(get_current_user)):
         loader = YoutubeLoader.from_youtube_url(
             form_data.url,
             add_video_info=True,
-            language=app.state.YOUTUBE_LOADER_LANGUAGE,
+            language=app.state.config.YOUTUBE_LOADER_LANGUAGE,
             translation=app.state.YOUTUBE_LOADER_TRANSLATION,
         )
         data = loader.load()
@@ -548,7 +564,8 @@ def store_web(form_data: UrlForm, user=Depends(get_current_user)):
     # "https://www.gutenberg.org/files/1727/1727-h/1727-h.htm"
     try:
         loader = get_web_loader(
-            form_data.url, verify_ssl=app.state.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION
+            form_data.url,
+            verify_ssl=app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
         )
         data = loader.load()
 
@@ -604,8 +621,8 @@ def resolve_hostname(hostname):
 def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> bool:
 
     text_splitter = RecursiveCharacterTextSplitter(
-        chunk_size=app.state.CHUNK_SIZE,
-        chunk_overlap=app.state.CHUNK_OVERLAP,
+        chunk_size=app.state.config.CHUNK_SIZE,
+        chunk_overlap=app.state.config.CHUNK_OVERLAP,
         add_start_index=True,
     )
 
@@ -622,8 +639,8 @@ def store_text_in_vector_db(
     text, metadata, collection_name, overwrite: bool = False
 ) -> bool:
     text_splitter = RecursiveCharacterTextSplitter(
-        chunk_size=app.state.CHUNK_SIZE,
-        chunk_overlap=app.state.CHUNK_OVERLAP,
+        chunk_size=app.state.config.CHUNK_SIZE,
+        chunk_overlap=app.state.config.CHUNK_OVERLAP,
         add_start_index=True,
     )
     docs = text_splitter.create_documents([text], metadatas=[metadata])
@@ -646,11 +663,11 @@ def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> b
         collection = CHROMA_CLIENT.create_collection(name=collection_name)
 
         embedding_func = get_embedding_function(
-            app.state.RAG_EMBEDDING_ENGINE,
-            app.state.RAG_EMBEDDING_MODEL,
+            app.state.config.RAG_EMBEDDING_ENGINE,
+            app.state.config.RAG_EMBEDDING_MODEL,
             app.state.sentence_transformer_ef,
-            app.state.OPENAI_API_KEY,
-            app.state.OPENAI_API_BASE_URL,
+            app.state.config.OPENAI_API_KEY,
+            app.state.config.OPENAI_API_BASE_URL,
         )
 
         embedding_texts = list(map(lambda x: x.replace("\n", " "), texts))
@@ -724,7 +741,9 @@ def get_loader(filename: str, file_content_type: str, file_path: str):
     ]
 
     if file_ext == "pdf":
-        loader = PyPDFLoader(file_path, extract_images=app.state.PDF_EXTRACT_IMAGES)
+        loader = PyPDFLoader(
+            file_path, extract_images=app.state.config.PDF_EXTRACT_IMAGES
+        )
     elif file_ext == "csv":
         loader = CSVLoader(file_path)
     elif file_ext == "rst":

+ 13 - 9
backend/apps/web/main.py

@@ -21,20 +21,24 @@ from config import (
     USER_PERMISSIONS,
     WEBHOOK_URL,
     WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
+    JWT_EXPIRES_IN,
+    AppConfig,
 )
 
 app = FastAPI()
 
 origins = ["*"]
 
-app.state.ENABLE_SIGNUP = ENABLE_SIGNUP
-app.state.JWT_EXPIRES_IN = "-1"
+app.state.config = AppConfig()
 
-app.state.DEFAULT_MODELS = DEFAULT_MODELS
-app.state.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
-app.state.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
-app.state.USER_PERMISSIONS = USER_PERMISSIONS
-app.state.WEBHOOK_URL = WEBHOOK_URL
+app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP
+app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN
+
+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.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
 
 app.add_middleware(
@@ -61,6 +65,6 @@ async def get_status():
     return {
         "status": True,
         "auth": WEBUI_AUTH,
-        "default_models": app.state.DEFAULT_MODELS,
-        "default_prompt_suggestions": app.state.DEFAULT_PROMPT_SUGGESTIONS,
+        "default_models": app.state.config.DEFAULT_MODELS,
+        "default_prompt_suggestions": app.state.config.DEFAULT_PROMPT_SUGGESTIONS,
     }

+ 16 - 16
backend/apps/web/routers/auths.py

@@ -140,7 +140,7 @@ async def signin(request: Request, form_data: SigninForm):
     if user:
         token = create_token(
             data={"id": user.id},
-            expires_delta=parse_duration(request.app.state.JWT_EXPIRES_IN),
+            expires_delta=parse_duration(request.app.state.config.JWT_EXPIRES_IN),
         )
 
         return {
@@ -163,7 +163,7 @@ async def signin(request: Request, form_data: SigninForm):
 
 @router.post("/signup", response_model=SigninResponse)
 async def signup(request: Request, form_data: SignupForm):
-    if not request.app.state.ENABLE_SIGNUP and WEBUI_AUTH:
+    if not request.app.state.config.ENABLE_SIGNUP and WEBUI_AUTH:
         raise HTTPException(
             status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
         )
@@ -180,7 +180,7 @@ async def signup(request: Request, form_data: SignupForm):
         role = (
             "admin"
             if Users.get_num_users() == 0
-            else request.app.state.DEFAULT_USER_ROLE
+            else request.app.state.config.DEFAULT_USER_ROLE
         )
         hashed = get_password_hash(form_data.password)
         user = Auths.insert_new_auth(
@@ -194,13 +194,13 @@ async def signup(request: Request, form_data: SignupForm):
         if user:
             token = create_token(
                 data={"id": user.id},
-                expires_delta=parse_duration(request.app.state.JWT_EXPIRES_IN),
+                expires_delta=parse_duration(request.app.state.config.JWT_EXPIRES_IN),
             )
             # response.set_cookie(key='token', value=token, httponly=True)
 
-            if request.app.state.WEBHOOK_URL:
+            if request.app.state.config.WEBHOOK_URL:
                 post_webhook(
-                    request.app.state.WEBHOOK_URL,
+                    request.app.state.config.WEBHOOK_URL,
                     WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
                     {
                         "action": "signup",
@@ -276,13 +276,13 @@ async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)):
 
 @router.get("/signup/enabled", response_model=bool)
 async def get_sign_up_status(request: Request, user=Depends(get_admin_user)):
-    return request.app.state.ENABLE_SIGNUP
+    return request.app.state.config.ENABLE_SIGNUP
 
 
 @router.get("/signup/enabled/toggle", response_model=bool)
 async def toggle_sign_up(request: Request, user=Depends(get_admin_user)):
-    request.app.state.ENABLE_SIGNUP = not request.app.state.ENABLE_SIGNUP
-    return request.app.state.ENABLE_SIGNUP
+    request.app.state.config.ENABLE_SIGNUP = not request.app.state.config.ENABLE_SIGNUP
+    return request.app.state.config.ENABLE_SIGNUP
 
 
 ############################
@@ -292,7 +292,7 @@ async def toggle_sign_up(request: Request, user=Depends(get_admin_user)):
 
 @router.get("/signup/user/role")
 async def get_default_user_role(request: Request, user=Depends(get_admin_user)):
-    return request.app.state.DEFAULT_USER_ROLE
+    return request.app.state.config.DEFAULT_USER_ROLE
 
 
 class UpdateRoleForm(BaseModel):
@@ -304,8 +304,8 @@ async def update_default_user_role(
     request: Request, form_data: UpdateRoleForm, user=Depends(get_admin_user)
 ):
     if form_data.role in ["pending", "user", "admin"]:
-        request.app.state.DEFAULT_USER_ROLE = form_data.role
-    return request.app.state.DEFAULT_USER_ROLE
+        request.app.state.config.DEFAULT_USER_ROLE = form_data.role
+    return request.app.state.config.DEFAULT_USER_ROLE
 
 
 ############################
@@ -315,7 +315,7 @@ async def update_default_user_role(
 
 @router.get("/token/expires")
 async def get_token_expires_duration(request: Request, user=Depends(get_admin_user)):
-    return request.app.state.JWT_EXPIRES_IN
+    return request.app.state.config.JWT_EXPIRES_IN
 
 
 class UpdateJWTExpiresDurationForm(BaseModel):
@@ -332,10 +332,10 @@ async def update_token_expires_duration(
 
     # Check if the input string matches the pattern
     if re.match(pattern, form_data.duration):
-        request.app.state.JWT_EXPIRES_IN = form_data.duration
-        return request.app.state.JWT_EXPIRES_IN
+        request.app.state.config.JWT_EXPIRES_IN = form_data.duration
+        return request.app.state.config.JWT_EXPIRES_IN
     else:
-        return request.app.state.JWT_EXPIRES_IN
+        return request.app.state.config.JWT_EXPIRES_IN
 
 
 ############################

+ 2 - 2
backend/apps/web/routers/chats.py

@@ -58,7 +58,7 @@ async def delete_all_user_chats(request: Request, user=Depends(get_current_user)
 
     if (
         user.role == "user"
-        and not request.app.state.USER_PERMISSIONS["chat"]["deletion"]
+        and not request.app.state.config.USER_PERMISSIONS["chat"]["deletion"]
     ):
         raise HTTPException(
             status_code=status.HTTP_401_UNAUTHORIZED,
@@ -266,7 +266,7 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_
         result = Chats.delete_chat_by_id(id)
         return result
     else:
-        if not request.app.state.USER_PERMISSIONS["chat"]["deletion"]:
+        if not request.app.state.config.USER_PERMISSIONS["chat"]["deletion"]:
             raise HTTPException(
                 status_code=status.HTTP_401_UNAUTHORIZED,
                 detail=ERROR_MESSAGES.ACCESS_PROHIBITED,

+ 4 - 4
backend/apps/web/routers/configs.py

@@ -44,8 +44,8 @@ class SetDefaultSuggestionsForm(BaseModel):
 async def set_global_default_models(
     request: Request, form_data: SetDefaultModelsForm, user=Depends(get_admin_user)
 ):
-    request.app.state.DEFAULT_MODELS = form_data.models
-    return request.app.state.DEFAULT_MODELS
+    request.app.state.config.DEFAULT_MODELS = form_data.models
+    return request.app.state.config.DEFAULT_MODELS
 
 
 @router.post("/default/suggestions", response_model=List[PromptSuggestion])
@@ -55,5 +55,5 @@ async def set_global_default_suggestions(
     user=Depends(get_admin_user),
 ):
     data = form_data.model_dump()
-    request.app.state.DEFAULT_PROMPT_SUGGESTIONS = data["suggestions"]
-    return request.app.state.DEFAULT_PROMPT_SUGGESTIONS
+    request.app.state.config.DEFAULT_PROMPT_SUGGESTIONS = data["suggestions"]
+    return request.app.state.config.DEFAULT_PROMPT_SUGGESTIONS

+ 3 - 3
backend/apps/web/routers/users.py

@@ -39,15 +39,15 @@ async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_admin_user)
 
 @router.get("/permissions/user")
 async def get_user_permissions(request: Request, user=Depends(get_admin_user)):
-    return request.app.state.USER_PERMISSIONS
+    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.USER_PERMISSIONS = form_data
-    return request.app.state.USER_PERMISSIONS
+    request.app.state.config.USER_PERMISSIONS = form_data
+    return request.app.state.config.USER_PERMISSIONS
 
 
 ############################

+ 278 - 73
backend/config.py

@@ -5,6 +5,7 @@ import chromadb
 from chromadb import Settings
 from base64 import b64encode
 from bs4 import BeautifulSoup
+from typing import TypeVar, Generic, Union
 
 from pathlib import Path
 import json
@@ -17,7 +18,6 @@ import shutil
 from secrets import token_bytes
 from constants import ERROR_MESSAGES
 
-
 ####################################
 # Load .env file
 ####################################
@@ -71,7 +71,6 @@ for source in log_sources:
 
 log.setLevel(SRC_LOG_LEVELS["CONFIG"])
 
-
 WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
 if WEBUI_NAME != "Open WebUI":
     WEBUI_NAME += " (Open WebUI)"
@@ -161,16 +160,6 @@ CHANGELOG = changelog_json
 
 WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.100")
 
-####################################
-# 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
-)
-
-
 ####################################
 # DATA/FRONTEND BUILD DIR
 ####################################
@@ -184,6 +173,108 @@ try:
 except:
     CONFIG_DATA = {}
 
+
+####################################
+# Config helpers
+####################################
+
+
+def save_config():
+    try:
+        with open(f"{DATA_DIR}/config.json", "w") as f:
+            json.dump(CONFIG_DATA, f, indent="\t")
+    except Exception as e:
+        log.exception(e)
+
+
+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
+
+
+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 config.json")
+            self.value = self.config_value
+        else:
+            self.value = env_value
+
+    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 save(self):
+        # Don't save if the value is the same as the env value and the config value
+        if self.env_value == self.value:
+            if self.config_value == self.value:
+                return
+        log.info(f"Saving '{self.env_name}' to config.json")
+        path_parts = self.config_path.split(".")
+        config = CONFIG_DATA
+        for key in path_parts[:-1]:
+            if key not in config:
+                config[key] = {}
+            config = config[key]
+        config[path_parts[-1]] = self.value
+        save_config()
+        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)
+####################################
+
+WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
+WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
+    "WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
+)
+JWT_EXPIRES_IN = PersistentConfig(
+    "JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1")
+)
+
 ####################################
 # Static DIR
 ####################################
@@ -318,7 +409,9 @@ 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
@@ -335,7 +428,9 @@ 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 = (
@@ -346,37 +441,42 @@ 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[
-        OPENAI_API_BASE_URLS.index("https://api.openai.com/v1")
+    OPENAI_API_KEY = OPENAI_API_KEYS.value[
+        OPENAI_API_BASE_URLS.value.index("https://api.openai.com/v1")
     ]
 except:
     pass
 
 OPENAI_API_BASE_URL = "https://api.openai.com/v1"
 
-
 ####################################
 # WEBUI
 ####################################
 
-ENABLE_SIGNUP = (
-    False
-    if WEBUI_AUTH == False
-    else os.environ.get("ENABLE_SIGNUP", "True").lower() == "true"
+ENABLE_SIGNUP = PersistentConfig(
+    "ENABLE_SIGNUP",
+    "ui.enable_signup",
+    (
+        False
+        if not WEBUI_AUTH
+        else os.environ.get("ENABLE_SIGNUP", "True").lower() == "true"
+    ),
+)
+DEFAULT_MODELS = PersistentConfig(
+    "DEFAULT_MODELS", "ui.default_models", os.environ.get("DEFAULT_MODELS", None)
 )
-DEFAULT_MODELS = os.environ.get("DEFAULT_MODELS", None)
-
 
-DEFAULT_PROMPT_SUGGESTIONS = (
-    CONFIG_DATA["ui"]["prompt_suggestions"]
-    if "ui" in CONFIG_DATA
-    and "prompt_suggestions" in CONFIG_DATA["ui"]
-    and type(CONFIG_DATA["ui"]["prompt_suggestions"]) is list
-    else [
+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.",
@@ -404,23 +504,40 @@ DEFAULT_PROMPT_SUGGESTIONS = (
             "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 = os.getenv("DEFAULT_USER_ROLE", "pending")
+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": {"deletion": USER_PERMISSIONS_CHAT_DELETION}}
+USER_PERMISSIONS = PersistentConfig(
+    "USER_PERMISSIONS",
+    "ui.user_permissions",
+    {"chat": {"deletion": USER_PERMISSIONS_CHAT_DELETION}},
+)
 
-ENABLE_MODEL_FILTER = os.environ.get("ENABLE_MODEL_FILTER", "False").lower() == "true"
+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 = [model.strip() for model in MODEL_FILTER_LIST.split(";")]
+MODEL_FILTER_LIST = PersistentConfig(
+    "MODEL_FILTER_LIST",
+    "model_filter.list",
+    [model.strip() for model in MODEL_FILTER_LIST.split(";")],
+)
 
-WEBHOOK_URL = os.environ.get("WEBHOOK_URL", "")
+WEBHOOK_URL = PersistentConfig(
+    "WEBHOOK_URL", "webhook_url", os.environ.get("WEBHOOK_URL", "")
+)
 
 ENABLE_ADMIN_EXPORT = os.environ.get("ENABLE_ADMIN_EXPORT", "True").lower() == "true"
 
@@ -460,26 +577,45 @@ else:
 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)
 
-RAG_TOP_K = int(os.environ.get("RAG_TOP_K", "5"))
-RAG_RELEVANCE_THRESHOLD = float(os.environ.get("RAG_RELEVANCE_THRESHOLD", "0.0"))
-
-ENABLE_RAG_HYBRID_SEARCH = (
-    os.environ.get("ENABLE_RAG_HYBRID_SEARCH", "").lower() == "true"
+RAG_TOP_K = PersistentConfig(
+    "RAG_TOP_K", "rag.top_k", int(os.environ.get("RAG_TOP_K", "5"))
+)
+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",
+)
 
-ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = (
-    os.environ.get("ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION", "True").lower() == "true"
+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 = os.environ.get("RAG_EMBEDDING_ENGINE", "")
+RAG_EMBEDDING_ENGINE = PersistentConfig(
+    "RAG_EMBEDDING_ENGINE",
+    "rag.embedding_engine",
+    os.environ.get("RAG_EMBEDDING_ENGINE", ""),
+)
 
-PDF_EXTRACT_IMAGES = os.environ.get("PDF_EXTRACT_IMAGES", "False").lower() == "true"
+PDF_EXTRACT_IMAGES = PersistentConfig(
+    "PDF_EXTRACT_IMAGES",
+    "rag.pdf_extract_images",
+    os.environ.get("PDF_EXTRACT_IMAGES", "False").lower() == "true",
+)
 
-RAG_EMBEDDING_MODEL = os.environ.get(
-    "RAG_EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2"
+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}"),
+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"
@@ -489,9 +625,13 @@ RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE = (
     os.environ.get("RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE", "").lower() == "true"
 )
 
-RAG_RERANKING_MODEL = os.environ.get("RAG_RERANKING_MODEL", "")
-if not RAG_RERANKING_MODEL == "":
-    log.info(f"Reranking model set: {RAG_RERANKING_MODEL}"),
+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"
@@ -529,9 +669,14 @@ if USE_CUDA.lower() == "true":
 else:
     DEVICE_TYPE = "cpu"
 
-
-CHUNK_SIZE = int(os.environ.get("CHUNK_SIZE", "1500"))
-CHUNK_OVERLAP = int(os.environ.get("CHUNK_OVERLAP", "100"))
+CHUNK_SIZE = PersistentConfig(
+    "CHUNK_SIZE", "rag.chunk_size", int(os.environ.get("CHUNK_SIZE", "1500"))
+)
+CHUNK_OVERLAP = PersistentConfig(
+    "CHUNK_OVERLAP",
+    "rag.chunk_overlap",
+    int(os.environ.get("CHUNK_OVERLAP", "100")),
+)
 
 DEFAULT_RAG_TEMPLATE = """Use the following context as your learned knowledge, inside <context></context> XML tags.
 <context>
@@ -547,16 +692,32 @@ And answer according to the language of the user's question.
 Given the context information, answer the query.
 Query: [query]"""
 
-RAG_TEMPLATE = os.environ.get("RAG_TEMPLATE", DEFAULT_RAG_TEMPLATE)
+RAG_TEMPLATE = PersistentConfig(
+    "RAG_TEMPLATE",
+    "rag.template",
+    os.environ.get("RAG_TEMPLATE", DEFAULT_RAG_TEMPLATE),
+)
 
-RAG_OPENAI_API_BASE_URL = os.getenv("RAG_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL)
-RAG_OPENAI_API_KEY = os.getenv("RAG_OPENAI_API_KEY", OPENAI_API_KEY)
+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 = os.getenv("YOUTUBE_LOADER_LANGUAGE", "en").split(",")
+YOUTUBE_LOADER_LANGUAGE = PersistentConfig(
+    "YOUTUBE_LOADER_LANGUAGE",
+    "rag.youtube_loader_language",
+    os.getenv("YOUTUBE_LOADER_LANGUAGE", "en").split(","),
+)
 
 ####################################
 # Transcribe
@@ -573,34 +734,78 @@ WHISPER_MODEL_AUTO_UPDATE = (
 # Images
 ####################################
 
-IMAGE_GENERATION_ENGINE = os.getenv("IMAGE_GENERATION_ENGINE", "")
+IMAGE_GENERATION_ENGINE = PersistentConfig(
+    "IMAGE_GENERATION_ENGINE",
+    "image_generation.engine",
+    os.getenv("IMAGE_GENERATION_ENGINE", ""),
+)
 
-ENABLE_IMAGE_GENERATION = (
-    os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true"
+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_BASE_URL = os.getenv("AUTOMATIC1111_BASE_URL", "")
 
-COMFYUI_BASE_URL = os.getenv("COMFYUI_BASE_URL", "")
+COMFYUI_BASE_URL = PersistentConfig(
+    "COMFYUI_BASE_URL",
+    "image_generation.comfyui.base_url",
+    os.getenv("COMFYUI_BASE_URL", ""),
+)
 
-IMAGES_OPENAI_API_BASE_URL = os.getenv(
-    "IMAGES_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL
+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),
 )
-IMAGES_OPENAI_API_KEY = os.getenv("IMAGES_OPENAI_API_KEY", OPENAI_API_KEY)
 
-IMAGE_SIZE = os.getenv("IMAGE_SIZE", "512x512")
+IMAGE_SIZE = PersistentConfig(
+    "IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
+)
 
-IMAGE_STEPS = int(os.getenv("IMAGE_STEPS", 50))
+IMAGE_STEPS = PersistentConfig(
+    "IMAGE_STEPS", "image_generation.steps", int(os.getenv("IMAGE_STEPS", 50))
+)
 
-IMAGE_GENERATION_MODEL = os.getenv("IMAGE_GENERATION_MODEL", "")
+IMAGE_GENERATION_MODEL = PersistentConfig(
+    "IMAGE_GENERATION_MODEL",
+    "image_generation.model",
+    os.getenv("IMAGE_GENERATION_MODEL", ""),
+)
 
 ####################################
 # Audio
 ####################################
 
-AUDIO_OPENAI_API_BASE_URL = os.getenv("AUDIO_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL)
-AUDIO_OPENAI_API_KEY = os.getenv("AUDIO_OPENAI_API_KEY", OPENAI_API_KEY)
-AUDIO_OPENAI_API_MODEL = os.getenv("AUDIO_OPENAI_API_MODEL", "tts-1")
-AUDIO_OPENAI_API_VOICE = os.getenv("AUDIO_OPENAI_API_VOICE", "alloy")
+AUDIO_OPENAI_API_BASE_URL = PersistentConfig(
+    "AUDIO_OPENAI_API_BASE_URL",
+    "audio.openai.api_base_url",
+    os.getenv("AUDIO_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
+)
+AUDIO_OPENAI_API_KEY = PersistentConfig(
+    "AUDIO_OPENAI_API_KEY",
+    "audio.openai.api_key",
+    os.getenv("AUDIO_OPENAI_API_KEY", OPENAI_API_KEY),
+)
+AUDIO_OPENAI_API_MODEL = PersistentConfig(
+    "AUDIO_OPENAI_API_MODEL",
+    "audio.openai.api_model",
+    os.getenv("AUDIO_OPENAI_API_MODEL", "tts-1"),
+)
+AUDIO_OPENAI_API_VOICE = PersistentConfig(
+    "AUDIO_OPENAI_API_VOICE",
+    "audio.openai.api_voice",
+    os.getenv("AUDIO_OPENAI_API_VOICE", "alloy"),
+)
 
 ####################################
 # LiteLLM

+ 47 - 39
backend/main.py

@@ -1,3 +1,4 @@
+from contextlib import asynccontextmanager
 from bs4 import BeautifulSoup
 import json
 import markdown
@@ -59,6 +60,7 @@ from config import (
     WEBHOOK_URL,
     ENABLE_ADMIN_EXPORT,
     MODEL_CONFIG,
+    AppConfig,
 )
 from constants import ERROR_MESSAGES
 
@@ -93,14 +95,27 @@ https://github.com/open-webui/open-webui
 """
 )
 
-app = FastAPI(docs_url="/docs" if ENV == "dev" else None, redoc_url=None)
 
-app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
-app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+    if ENABLE_LITELLM:
+        asyncio.create_task(start_litellm_background())
+    yield
+    if ENABLE_LITELLM:
+        await shutdown_litellm_background()
+
+
+app = FastAPI(
+    docs_url="/docs" if ENV == "dev" else None, redoc_url=None, lifespan=lifespan
+)
+
+app.state.config = AppConfig()
+app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
+app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 
 app.state.MODEL_CONFIG = MODEL_CONFIG
 
-app.state.WEBHOOK_URL = WEBHOOK_URL
+app.state.config.WEBHOOK_URL = WEBHOOK_URL
 
 origins = ["*"]
 
@@ -132,12 +147,12 @@ class RAGMiddleware(BaseHTTPMiddleware):
                 data["messages"], citations = rag_messages(
                     docs=data["docs"],
                     messages=data["messages"],
-                    template=rag_app.state.RAG_TEMPLATE,
+                    template=rag_app.state.config.RAG_TEMPLATE,
                     embedding_function=rag_app.state.EMBEDDING_FUNCTION,
-                    k=rag_app.state.TOP_K,
+                    k=rag_app.state.config.TOP_K,
                     reranking_function=rag_app.state.sentence_transformer_rf,
-                    r=rag_app.state.RELEVANCE_THRESHOLD,
-                    hybrid_search=rag_app.state.ENABLE_RAG_HYBRID_SEARCH,
+                    r=rag_app.state.config.RELEVANCE_THRESHOLD,
+                    hybrid_search=rag_app.state.config.ENABLE_RAG_HYBRID_SEARCH,
                 )
                 del data["docs"]
 
@@ -214,12 +229,6 @@ async def check_url(request: Request, call_next):
     return response
 
 
-@app.on_event("startup")
-async def on_startup():
-    if ENABLE_LITELLM:
-        asyncio.create_task(start_litellm_background())
-
-
 app.mount("/api/v1", webui_app)
 app.mount("/litellm/api", litellm_app)
 
@@ -246,9 +255,9 @@ async def get_app_config():
         "version": VERSION,
         "auth": WEBUI_AUTH,
         "default_locale": default_locale,
-        "images": images_app.state.ENABLED,
-        "default_models": webui_app.state.DEFAULT_MODELS,
-        "default_prompt_suggestions": webui_app.state.DEFAULT_PROMPT_SUGGESTIONS,
+        "images": images_app.state.config.ENABLED,
+        "default_models": webui_app.state.config.DEFAULT_MODELS,
+        "default_prompt_suggestions": webui_app.state.config.DEFAULT_PROMPT_SUGGESTIONS,
         "trusted_header_auth": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER),
         "admin_export_enabled": ENABLE_ADMIN_EXPORT,
     }
@@ -257,8 +266,8 @@ async def get_app_config():
 @app.get("/api/config/model/filter")
 async def get_model_filter_config(user=Depends(get_admin_user)):
     return {
-        "enabled": app.state.ENABLE_MODEL_FILTER,
-        "models": app.state.MODEL_FILTER_LIST,
+        "enabled": app.state.config.ENABLE_MODEL_FILTER,
+        "models": app.state.config.MODEL_FILTER_LIST,
     }
 
 
@@ -271,21 +280,21 @@ class ModelFilterConfigForm(BaseModel):
 async def update_model_filter_config(
     form_data: ModelFilterConfigForm, user=Depends(get_admin_user)
 ):
-    app.state.ENABLE_MODEL_FILTER = form_data.enabled
-    app.state.MODEL_FILTER_LIST = form_data.models
+    app.state.config.ENABLE_MODEL_FILTER, form_data.enabled
+    app.state.config.MODEL_FILTER_LIST, form_data.models
 
-    ollama_app.state.ENABLE_MODEL_FILTER = app.state.ENABLE_MODEL_FILTER
-    ollama_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST
+    ollama_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
+    ollama_app.state.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
 
-    openai_app.state.ENABLE_MODEL_FILTER = app.state.ENABLE_MODEL_FILTER
-    openai_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST
+    openai_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
+    openai_app.state.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
 
-    litellm_app.state.ENABLE_MODEL_FILTER = app.state.ENABLE_MODEL_FILTER
-    litellm_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST
+    litellm_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
+    litellm_app.state.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
 
     return {
-        "enabled": app.state.ENABLE_MODEL_FILTER,
-        "models": app.state.MODEL_FILTER_LIST,
+        "enabled": app.state.config.ENABLE_MODEL_FILTER,
+        "models": app.state.config.MODEL_FILTER_LIST,
     }
 
 
@@ -331,7 +340,7 @@ async def get_model_config(user=Depends(get_admin_user)):
 @app.get("/api/webhook")
 async def get_webhook_url(user=Depends(get_admin_user)):
     return {
-        "url": app.state.WEBHOOK_URL,
+        "url": app.state.config.WEBHOOK_URL,
     }
 
 
@@ -341,12 +350,12 @@ class UrlForm(BaseModel):
 
 @app.post("/api/webhook")
 async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)):
-    app.state.WEBHOOK_URL = form_data.url
+    app.state.config.WEBHOOK_URL = form_data.url
 
-    webui_app.state.WEBHOOK_URL = app.state.WEBHOOK_URL
+    webui_app.state.WEBHOOK_URL = app.state.config.WEBHOOK_URL
 
     return {
-        "url": app.state.WEBHOOK_URL,
+        "url": app.state.config.WEBHOOK_URL,
     }
 
 
@@ -410,6 +419,11 @@ async def get_opensearch_xml():
     return Response(content=xml_content, media_type="application/xml")
 
 
+@app.get("/health")
+async def healthcheck():
+    return {"status": True}
+
+
 app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
 app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache")
 
@@ -423,9 +437,3 @@ else:
     log.warning(
         f"Frontend build directory not found at '{FRONTEND_BUILD_DIR}'. Serving API only."
     )
-
-
-@app.on_event("shutdown")
-async def shutdown_event():
-    if ENABLE_LITELLM:
-        await shutdown_litellm_background()

+ 32 - 0
cypress/e2e/chat.cy.ts

@@ -42,5 +42,37 @@ describe('Settings', () => {
 				.find('div[aria-label="Generation Info"]', { timeout: 120_000 }) // Generation Info is created after the stop token is received
 				.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-textarea').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
+			cy.get('.chat-assistant', { timeout: 120_000 }) // .chat-assistant is created after the first token is received
+				.find('div[aria-label="Generation Info"]', { timeout: 120_000 }) // Generation Info is created after the stop token is received
+				.should('exist');
+			// spy on requests
+			const spy = cy.spy();
+			cy.intercept('GET', '/api/v1/chats/*', 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');
+			cy.wrap({}, { timeout: 5000 }).should(() => {
+				// Check if the request was made twice (once for to replace chat object and once more due to change event)
+				expect(spy).to.be.callCount(2);
+			});
+		});
 	});
 });

+ 2 - 6
cypress/e2e/settings.cy.ts

@@ -15,12 +15,8 @@ describe('Settings', () => {
 		cy.loginAdmin();
 		// Visit the home page
 		cy.visit('/');
-		// Open the sidebar if it is not already open
-		cy.get('[aria-label="Open sidebar"]').then(() => {
-			cy.get('button[id="sidebar-toggle-button"]').click();
-		});
-		// Click on the profile link
-		cy.get('button').contains(adminUser.name).click();
+		// Click on the user menu
+		cy.get('button[aria-label="User Menu"]').click();
 		// Click on the settings link
 		cy.get('button').contains('Settings').click();
 	});

+ 0 - 2
docker-compose.api.yaml

@@ -1,5 +1,3 @@
-version: '3.8'
-
 services:
   ollama:
     # Expose Ollama API outside the container stack

+ 0 - 2
docker-compose.data.yaml

@@ -1,5 +1,3 @@
-version: '3.8'
-
 services:
   ollama:
     volumes:

+ 0 - 2
docker-compose.gpu.yaml

@@ -1,5 +1,3 @@
-version: '3.8'
-
 services:
   ollama:
     # GPU support

+ 0 - 2
docker-compose.yaml

@@ -1,5 +1,3 @@
-version: '3.8'
-
 services:
   ollama:
     volumes:

+ 2 - 2
docs/CONTRIBUTING.md

@@ -17,7 +17,7 @@ If your issue or contribution pertains directly to the core Ollama technology, p
 
 ### 🚨 Reporting Issues
 
-Noticed something off? Have an idea? Check our [Issues tab](https://github.com/open-webui/oopen-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.
+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]
 >
@@ -54,7 +54,7 @@ Help us make Open WebUI more accessible by improving documentation, writing tuto
 
 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.
+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:
 

文件差异内容过多而无法显示
+ 990 - 6
package-lock.json


+ 6 - 4
package.json

@@ -12,10 +12,11 @@
 		"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": "prettier --plugin-search-dir --write \"**/*.{js,ts,svelte,css,md,html,json}\"",
 		"format:backend": "black . --exclude \"/venv/\"",
-		"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write 'src/lib/i18n/**/*.{js,json}'",
-		"cy:open": "cypress open"
+		"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write \"src/lib/i18n/**/*.{js,json}\"",
+		"cy:open": "cypress open",
+		"test:frontend": "vitest"
 	},
 	"devDependencies": {
 		"@sveltejs/adapter-auto": "^2.0.0",
@@ -41,7 +42,8 @@
 		"tailwindcss": "^3.3.3",
 		"tslib": "^2.4.1",
 		"typescript": "^5.0.0",
-		"vite": "^4.4.2"
+		"vite": "^4.4.2",
+		"vitest": "^1.6.0"
 	},
 	"type": "module",
 	"dependencies": {

+ 24 - 4
src/app.css

@@ -83,11 +83,31 @@ select {
 	display: none;
 }
 
-.scrollbar-none:active::-webkit-scrollbar-thumb,
-.scrollbar-none:focus::-webkit-scrollbar-thumb,
-.scrollbar-none:hover::-webkit-scrollbar-thumb {
+.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
 	visibility: visible;
 }
-.scrollbar-none::-webkit-scrollbar-thumb {
+.scrollbar-hidden::-webkit-scrollbar-thumb {
 	visibility: hidden;
 }
+
+.scrollbar-none::-webkit-scrollbar {
+	display: none; /* for Chrome, Safari and Opera */
+}
+
+.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 */
+}

+ 7 - 0
src/lib/apis/streaming/index.ts

@@ -6,6 +6,8 @@ type TextStreamUpdate = {
 	value: string;
 	// eslint-disable-next-line @typescript-eslint/no-explicit-any
 	citations?: any;
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	error?: any;
 };
 
 // createOpenAITextStream takes a responseBody with a SSE response,
@@ -47,6 +49,11 @@ async function* openAIStreamToIterator(
 			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;

+ 1 - 1
src/lib/components/ChangelogModal.svelte

@@ -58,7 +58,7 @@
 	</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-none">
+		<div class=" overflow-y-scroll max-h-80 scrollbar-hidden">
 			<div class="mb-3">
 				{#if changelog}
 					{#each Object.keys(changelog) as version}

+ 5 - 2
src/lib/components/admin/AddUserModal.svelte

@@ -73,13 +73,16 @@
 						console.log(idx, columns);
 
 						if (idx > 0) {
-							if (columns.length === 4 && ['admin', 'user', 'pending'].includes(columns[3])) {
+							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]
+									columns[3].toLowerCase()
 								).catch((error) => {
 									toast.error(`Row ${idx + 1}: ${error}`);
 									return null;

+ 2 - 2
src/lib/components/admin/Settings/General.svelte

@@ -123,7 +123,7 @@
 
 				<div class="flex mt-2 space-x-2">
 					<input
-						class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 						type="text"
 						placeholder={`https://example.com/webhook`}
 						bind:value={webhookUrl}
@@ -140,7 +140,7 @@
 
 				<div class="flex mt-2 space-x-2">
 					<input
-						class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 						type="text"
 						placeholder={`e.g.) "30m","1h", "10d". `}
 						bind:value={JWTExpiresIn}

+ 11 - 11
src/lib/components/chat/MessageInput.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 	import { toast } from 'svelte-sonner';
 	import { onMount, tick, getContext } from 'svelte';
-	import { type Model, modelfiles, settings, showSidebar } from '$lib/stores';
+	import { type Model, mobile, modelfiles, settings, showSidebar } from '$lib/stores';
 	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
 
 	import {
@@ -418,7 +418,7 @@
 {#if dragged}
 	<div
 		class="fixed {$showSidebar
-			? 'left-0 lg:left-[260px] lg:w-[calc(100%-260px)]'
+			? '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"
@@ -434,9 +434,9 @@
 	</div>
 {/if}
 
-<div class="fixed bottom-0 {$showSidebar ? 'left-0 lg:left-[260px]' : 'left-0'} right-0">
+<div class="fixed bottom-0 {$showSidebar ? 'left-0 md:left-[260px]' : 'left-0'} right-0">
 	<div class="w-full">
-		<div class="px-2.5 lg:px-16 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
+		<div class="px-2.5 md:px-16 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
 			<div class="flex flex-col max-w-5xl w-full">
 				<div class="relative">
 					{#if autoScroll === false && messages.length > 0}
@@ -543,7 +543,7 @@
 		</div>
 
 		<div class="bg-white dark:bg-gray-900">
-			<div class="max-w-6xl px-2.5 lg:px-16 mx-auto inset-x-0">
+			<div class="max-w-6xl px-2.5 md:px-16 mx-auto inset-x-0">
 				<div class=" pb-2">
 					<input
 						bind:this={filesInputElement}
@@ -770,7 +770,7 @@
 							<textarea
 								id="chat-textarea"
 								bind:this={chatTextAreaElement}
-								class="scrollbar-none dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
+								class="scrollbar-hidden dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
 									? ''
 									: ' pl-4'} rounded-xl resize-none h-[48px]"
 								placeholder={chatInputPlaceholder !== ''
@@ -781,7 +781,7 @@
 								bind:value={prompt}
 								on:keypress={(e) => {
 									if (
-										window.innerWidth > 1024 ||
+										!$mobile ||
 										!(
 											'ontouchstart' in window ||
 											navigator.maxTouchPoints > 0 ||
@@ -1062,12 +1062,12 @@
 </div>
 
 <style>
-	.scrollbar-none:active::-webkit-scrollbar-thumb,
-	.scrollbar-none:focus::-webkit-scrollbar-thumb,
-	.scrollbar-none:hover::-webkit-scrollbar-thumb {
+	.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
 		visibility: visible;
 	}
-	.scrollbar-none::-webkit-scrollbar-thumb {
+	.scrollbar-hidden::-webkit-scrollbar-thumb {
 		visibility: hidden;
 	}
 </style>

+ 27 - 25
src/lib/components/chat/Messages.svelte

@@ -309,31 +309,33 @@
 									copyToClipboard={copyToClipboardWithToast}
 								/>
 							{:else}
-								<ResponseMessage
-									{message}
-									modelfiles={selectedModelfiles}
-									siblings={history.messages[message.parentId]?.childrenIds ?? []}
-									isLastMessage={messageIdx + 1 === messages.length}
-									{readOnly}
-									{updateChatMessages}
-									{confirmEditResponseMessage}
-									{showPreviousMessage}
-									{showNextMessage}
-									{rateMessage}
-									copyToClipboard={copyToClipboardWithToast}
-									{continueGeneration}
-									{regenerateResponse}
-									on:save={async (e) => {
-										console.log('save', e);
-
-										const message = e.detail;
-										history.messages[message.id] = message;
-										await updateChatById(localStorage.token, chatId, {
-											messages: messages,
-											history: history
-										});
-									}}
-								/>
+								{#key message.id}
+									<ResponseMessage
+										{message}
+										modelfiles={selectedModelfiles}
+										siblings={history.messages[message.parentId]?.childrenIds ?? []}
+										isLastMessage={messageIdx + 1 === messages.length}
+										{readOnly}
+										{updateChatMessages}
+										{confirmEditResponseMessage}
+										{showPreviousMessage}
+										{showNextMessage}
+										{rateMessage}
+										copyToClipboard={copyToClipboardWithToast}
+										{continueGeneration}
+										{regenerateResponse}
+										on:save={async (e) => {
+											console.log('save', e);
+
+											const message = e.detail;
+											history.messages[message.id] = message;
+											await updateChatById(localStorage.token, chatId, {
+												messages: messages,
+												history: history
+											});
+										}}
+									/>
+								{/key}
 							{/if}
 						</div>
 					</div>

+ 1 - 1
src/lib/components/chat/Messages/CitationsModal.svelte

@@ -47,7 +47,7 @@
 
 		<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-none"
+				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">

+ 1 - 1
src/lib/components/chat/Messages/Name.svelte

@@ -1,3 +1,3 @@
-<div class=" self-center font-bold mb-0.5 capitalize line-clamp-1">
+<div class=" self-center font-bold mb-0.5 line-clamp-1">
 	<slot />
 </div>

+ 2 - 2
src/lib/components/chat/Messages/ProfileImage.svelte

@@ -2,6 +2,6 @@
 	export let src = '/user.png';
 </script>
 
-<div class=" mr-4">
-	<img {src} class=" max-w-[28px] object-cover rounded-full" alt="profile" draggable="false" />
+<div class=" mr-3">
+	<img {src} class=" w-8 object-cover rounded-full" alt="profile" draggable="false" />
 </div>

+ 2 - 2
src/lib/components/chat/Messages/RateComment.svelte

@@ -39,9 +39,9 @@
 	let selectedReason = null;
 	let comment = '';
 
-	$: if (message.annotation.rating === 1) {
+	$: if (message?.annotation?.rating === 1) {
 		reasons = LIKE_REASONS;
-	} else if (message.annotation.rating === -1) {
+	} else if (message?.annotation?.rating === -1) {
 		reasons = DISLIKE_REASONS;
 	}
 

+ 128 - 118
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -65,8 +65,8 @@
 	let generatingImage = false;
 
 	let showRateComment = false;
-
 	let showCitationModal = false;
+
 	let selectedCitation = null;
 
 	$: tokens = marked.lexer(sanitizeResponseContent(message.content));
@@ -338,7 +338,7 @@
 				($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
 		/>
 
-		<div class="w-full overflow-hidden">
+		<div class="w-full overflow-hidden pl-1">
 			<Name>
 				{#if message.model in modelfiles}
 					{modelfiles[message.model]?.title}
@@ -347,8 +347,10 @@
 				{/if}
 
 				{#if message.timestamp}
-					<span class=" invisible group-hover:visible text-gray-400 text-xs font-medium">
-						{dayjs(message.timestamp * 1000).format($i18n.t('DD/MM/YYYY HH:mm'))}
+					<span
+						class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase"
+					>
+						{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
 					</span>
 				{/if}
 			</Name>
@@ -370,7 +372,7 @@
 			>
 				<div>
 					{#if edit === true}
-						<div class=" w-full">
+						<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}
@@ -382,23 +384,25 @@
 								}}
 							/>
 
-							<div class=" mt-2 mb-1 flex justify-center space-x-2 text-sm font-medium">
+							<div class=" mt-2 mb-1 flex justify-end space-x-1.5 text-sm font-medium">
 								<button
-									class="px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
+									id="close-edit-message-button"
+									class=" px-4 py-2 bg-gray-900 hover:bg-gray-850 text-gray-100 transition rounded-3xl"
 									on:click={() => {
-										editMessageConfirmHandler();
+										cancelEditMessage();
 									}}
 								>
-									{$i18n.t('Save')}
+									{$i18n.t('Cancel')}
 								</button>
 
 								<button
-									class=" px-4 py-2 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-100 transition outline outline-1 outline-gray-200 dark:outline-gray-600 rounded-lg"
+									id="save-edit-message-button"
+									class="px-4 py-2 bg-white hover:bg-gray-100 text-gray-800 transition rounded-3xl"
 									on:click={() => {
-										cancelEditMessage();
+										editMessageConfirmHandler();
 									}}
 								>
-									{$i18n.t('Cancel')}
+									{$i18n.t('Save')}
 								</button>
 							</div>
 						</div>
@@ -476,7 +480,7 @@
 												<div class="bg-white dark:bg-gray-700 rounded-full size-4">
 													{idx + 1}
 												</div>
-												<div class=" mx-2">
+												<div class="flex-1 mx-2 line-clamp-1">
 													{citation.source.name}
 												</div>
 											</button>
@@ -487,50 +491,56 @@
 
 							{#if message.done || siblings.length > 1}
 								<div
-									class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
+									class=" flex justify-start overflow-x-auto buttons text-gray-600 dark:text-gray-500"
 								>
 									{#if siblings.length > 1}
-										<div class="flex self-center min-w-fit">
+										<div class="flex self-center">
 											<button
-												class="self-center dark:hover:text-white hover:text-black transition"
+												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"
-													viewBox="0 0 20 20"
-													fill="currentColor"
-													class="w-4 h-4"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke="currentColor"
+													stroke-width="2.5"
+													class="size-3.5"
 												>
 													<path
-														fill-rule="evenodd"
-														d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
-														clip-rule="evenodd"
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="M15.75 19.5 8.25 12l7.5-7.5"
 													/>
 												</svg>
 											</button>
 
-											<div class="text-xs font-bold self-center min-w-fit dark:text-gray-100">
-												{siblings.indexOf(message.id) + 1} / {siblings.length}
+											<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 dark:hover:text-white hover:text-black transition"
+												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"
-													viewBox="0 0 20 20"
-													fill="currentColor"
-													class="w-4 h-4"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke="currentColor"
+													stroke-width="2.5"
+													class="size-3.5"
 												>
 													<path
-														fill-rule="evenodd"
-														d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
-														clip-rule="evenodd"
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="m8.25 4.5 7.5 7.5-7.5 7.5"
 													/>
 												</svg>
 											</button>
@@ -543,7 +553,7 @@
 												<button
 													class="{isLastMessage
 														? 'visible'
-														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
+														: '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();
 													}}
@@ -552,7 +562,7 @@
 														xmlns="http://www.w3.org/2000/svg"
 														fill="none"
 														viewBox="0 0 24 24"
-														stroke-width="2"
+														stroke-width="2.3"
 														stroke="currentColor"
 														class="w-4 h-4"
 													>
@@ -570,7 +580,7 @@
 											<button
 												class="{isLastMessage
 													? 'visible'
-													: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition copy-response-button"
+													: '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);
 												}}
@@ -579,7 +589,7 @@
 													xmlns="http://www.w3.org/2000/svg"
 													fill="none"
 													viewBox="0 0 24 24"
-													stroke-width="2"
+													stroke-width="2.3"
 													stroke="currentColor"
 													class="w-4 h-4"
 												>
@@ -592,83 +602,12 @@
 											</button>
 										</Tooltip>
 
-										{#if !readOnly}
-											<Tooltip content={$i18n.t('Good Response')} placement="bottom">
-												<button
-													class="{isLastMessage
-														? 'visible'
-														: 'invisible group-hover:visible'} p-1 rounded {message?.annotation
-														?.rating === 1
-														? 'bg-gray-100 dark:bg-gray-800'
-														: ''} dark:hover:text-white hover:text-black transition"
-													on:click={() => {
-														rateMessage(message.id, 1);
-														showRateComment = true;
-
-														window.setTimeout(() => {
-															document
-																.getElementById(`message-feedback-${message.id}`)
-																?.scrollIntoView();
-														}, 0);
-													}}
-												>
-													<svg
-														stroke="currentColor"
-														fill="none"
-														stroke-width="2"
-														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 rounded {message?.annotation
-														?.rating === -1
-														? 'bg-gray-100 dark:bg-gray-800'
-														: ''} dark:hover:text-white hover:text-black transition"
-													on:click={() => {
-														rateMessage(message.id, -1);
-														showRateComment = true;
-														window.setTimeout(() => {
-															document
-																.getElementById(`message-feedback-${message.id}`)
-																?.scrollIntoView();
-														}, 0);
-													}}
-												>
-													<svg
-														stroke="currentColor"
-														fill="none"
-														stroke-width="2"
-														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}
-
 										<Tooltip content={$i18n.t('Read Aloud')} placement="bottom">
 											<button
 												id="speak-button-{message.id}"
 												class="{isLastMessage
 													? 'visible'
-													: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
+													: '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(message);
@@ -715,7 +654,7 @@
 														xmlns="http://www.w3.org/2000/svg"
 														fill="none"
 														viewBox="0 0 24 24"
-														stroke-width="2"
+														stroke-width="2.3"
 														stroke="currentColor"
 														class="w-4 h-4"
 													>
@@ -730,7 +669,7 @@
 														xmlns="http://www.w3.org/2000/svg"
 														fill="none"
 														viewBox="0 0 24 24"
-														stroke-width="2"
+														stroke-width="2.3"
 														stroke="currentColor"
 														class="w-4 h-4"
 													>
@@ -749,7 +688,7 @@
 												<button
 													class="{isLastMessage
 														? 'visible'
-														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
+														: '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);
@@ -796,7 +735,7 @@
 															xmlns="http://www.w3.org/2000/svg"
 															fill="none"
 															viewBox="0 0 24 24"
-															stroke-width="2"
+															stroke-width="2.3"
 															stroke="currentColor"
 															class="w-4 h-4"
 														>
@@ -816,7 +755,7 @@
 												<button
 													class=" {isLastMessage
 														? 'visible'
-														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition whitespace-pre-wrap"
+														: '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);
 													}}
@@ -826,7 +765,7 @@
 														xmlns="http://www.w3.org/2000/svg"
 														fill="none"
 														viewBox="0 0 24 24"
-														stroke-width="2"
+														stroke-width="2.3"
 														stroke="currentColor"
 														class="w-4 h-4"
 													>
@@ -840,13 +779,84 @@
 											</Tooltip>
 										{/if}
 
+										{#if !readOnly}
+											<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 === 1
+														? 'bg-gray-100 dark:bg-gray-800'
+														: ''} dark:hover:text-white hover:text-black transition"
+													on:click={() => {
+														rateMessage(message.id, 1);
+														showRateComment = true;
+
+														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 === -1
+														? 'bg-gray-100 dark:bg-gray-800'
+														: ''} dark:hover:text-white hover:text-black transition"
+													on:click={() => {
+														rateMessage(message.id, -1);
+														showRateComment = true;
+														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 && !readOnly}
 											<Tooltip content={$i18n.t('Continue Response')} placement="bottom">
 												<button
 													type="button"
 													class="{isLastMessage
 														? 'visible'
-														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button"
+														: '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={() => {
 														continueGeneration();
 													}}
@@ -855,7 +865,7 @@
 														xmlns="http://www.w3.org/2000/svg"
 														fill="none"
 														viewBox="0 0 24 24"
-														stroke-width="2"
+														stroke-width="2.3"
 														stroke="currentColor"
 														class="w-4 h-4"
 													>
@@ -878,14 +888,14 @@
 													type="button"
 													class="{isLastMessage
 														? 'visible'
-														: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button"
+														: '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={regenerateResponse}
 												>
 													<svg
 														xmlns="http://www.w3.org/2000/svg"
 														fill="none"
 														viewBox="0 0 24 24"
-														stroke-width="2"
+														stroke-width="2.3"
 														stroke="currentColor"
 														class="w-4 h-4"
 													>
@@ -902,7 +912,7 @@
 								</div>
 							{/if}
 
-							{#if showRateComment}
+							{#if message.done && showRateComment}
 								<RateComment
 									messageId={message.id}
 									bind:show={showRateComment}

+ 169 - 90
src/lib/components/chat/Messages/UserMessage.svelte

@@ -54,49 +54,55 @@
 	};
 </script>
 
-<div class=" flex w-full">
-	<ProfileImage
-		src={message.user
-			? $modelfiles.find((modelfile) => modelfile.tagName === message.user)?.imageUrl ?? '/user.png'
-			: user?.profile_image_url ?? '/user.png'}
-	/>
-
+<div class=" flex w-full user-message">
+	{#if !($settings?.chatBubble ?? true)}
+		<ProfileImage
+			src={message.user
+				? $modelfiles.find((modelfile) => modelfile.tagName === message.user)?.imageUrl ??
+				  '/user.png'
+				: user?.profile_image_url ?? '/user.png'}
+		/>
+	{/if}
 	<div class="w-full overflow-hidden">
-		<div class="user-message">
-			<Name>
-				{#if message.user}
-					{#if $modelfiles.map((modelfile) => modelfile.tagName).includes(message.user)}
-						{$modelfiles.find((modelfile) => modelfile.tagName === message.user)?.title}
+		{#if !($settings?.chatBubble ?? true)}
+			<div>
+				<Name>
+					{#if message.user}
+						{#if $modelfiles.map((modelfile) => modelfile.tagName).includes(message.user)}
+							{$modelfiles.find((modelfile) => modelfile.tagName === message.user)?.title}
+						{:else}
+							{$i18n.t('You')}
+							<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
+						{/if}
+					{:else if $settings.showUsername}
+						{user.name}
 					{:else}
 						{$i18n.t('You')}
-						<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
 					{/if}
-				{:else if $settings.showUsername}
-					{user.name}
-				{:else}
-					{$i18n.t('You')}
-				{/if}
-
-				{#if message.timestamp}
-					<span class=" invisible group-hover:visible text-gray-400 text-xs font-medium">
-						{dayjs(message.timestamp * 1000).format($i18n.t('DD/MM/YYYY HH:mm'))}
-					</span>
-				{/if}
-			</Name>
-		</div>
+
+					{#if message.timestamp}
+						<span
+							class=" invisible group-hover:visible text-gray-400 text-xs font-medium uppercase"
+						>
+							{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
+						</span>
+					{/if}
+				</Name>
+			</div>
+		{/if}
 
 		<div
-			class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-headings:my-0 prose-p:my-0 prose-p:-mb-4 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-6 prose-li:-mb-4 whitespace-pre-line"
+			class="prose chat-{message.role} w-full max-w-full flex flex-col justify-end dark:prose-invert prose-headings:my-0 prose-p:my-0 prose-p:-mb-4 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-6 prose-li:-mb-4 whitespace-pre-line"
 		>
 			{#if message.files}
-				<div class="my-2.5 w-full flex overflow-x-auto gap-2 flex-wrap">
+				<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>
+						<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 if file.type === 'doc'}
 								<button
-									class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none text-left"
+									class="h-16 w-72 flex items-center space-x-3 px-2.5 dark:bg-gray-850 rounded-xl border border-gray-200 dark:border-none text-left"
 									type="button"
 									on:click={() => {
 										if (file?.url) {
@@ -132,7 +138,7 @@
 								</button>
 							{:else if file.type === 'collection'}
 								<button
-									class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none text-left"
+									class="h-16 w-72 flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none text-left"
 									type="button"
 								>
 									<div class="p-2.5 bg-red-400 text-white rounded-lg">
@@ -166,7 +172,7 @@
 			{/if}
 
 			{#if edit === true}
-				<div class=" w-full">
+				<div class=" w-full bg-gray-50 dark:bg-gray-800 rounded-3xl px-5 py-3 mb-2">
 					<textarea
 						id="message-edit-{message.id}"
 						bind:this={messageEditTextAreaElement}
@@ -190,85 +196,104 @@
 						}}
 					/>
 
-					<div class=" mt-2 mb-1 flex justify-center space-x-2 text-sm font-medium">
+					<div class=" mt-2 mb-1 flex justify-end space-x-1.5 text-sm font-medium">
 						<button
-							id="save-edit-message-button"
-							class="px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
+							id="close-edit-message-button"
+							class=" px-4 py-2 bg-gray-900 hover:bg-gray-850 text-gray-100 transition rounded-3xl"
 							on:click={() => {
-								editMessageConfirmHandler();
+								cancelEditMessage();
 							}}
 						>
-							{$i18n.t('Save & Submit')}
+							{$i18n.t('Cancel')}
 						</button>
 
 						<button
-							id="close-edit-message-button"
-							class=" px-4 py-2 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-100 transition outline outline-1 outline-gray-200 dark:outline-gray-600 rounded-lg"
+							id="save-edit-message-button"
+							class="px-4 py-2 bg-white hover:bg-gray-100 text-gray-800 transition rounded-3xl"
 							on:click={() => {
-								cancelEditMessage();
+								editMessageConfirmHandler();
 							}}
 						>
-							{$i18n.t('Cancel')}
+							{$i18n.t('Send')}
 						</button>
 					</div>
 				</div>
 			{:else}
 				<div class="w-full">
-					<pre id="user-message">{message.content}</pre>
+					<div class="flex {$settings?.chatBubble ?? true ? 'justify-end' : ''} mb-2">
+						<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' : ''
+								  }`
+								: ''}  "
+						>
+							<pre id="user-message">{message.content}</pre>
+						</div>
+					</div>
 
-					<div class=" flex justify-start space-x-1 text-gray-700 dark:text-gray-500">
-						{#if siblings.length > 1}
-							<div class="flex self-center">
-								<button
-									class="self-center dark:hover:text-white hover:text-black transition"
-									on:click={() => {
-										showPreviousMessage(message);
-									}}
-								>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 20 20"
-										fill="currentColor"
-										class="w-4 h-4"
+					<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">
+									<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);
+										}}
 									>
-										<path
-											fill-rule="evenodd"
-											d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-								</button>
+										<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 font-bold self-center dark:text-gray-100">
-									{siblings.indexOf(message.id) + 1} / {siblings.length}
-								</div>
+									<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 dark:hover:text-white hover:text-black transition"
-									on:click={() => {
-										showNextMessage(message);
-									}}
-								>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 20 20"
-										fill="currentColor"
-										class="w-4 h-4"
+									<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);
+										}}
 									>
-										<path
-											fill-rule="evenodd"
-											d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-								</button>
-							</div>
+										<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 rounded dark:hover:text-white hover:text-black transition edit-user-message-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();
 									}}
@@ -277,7 +302,7 @@
 										xmlns="http://www.w3.org/2000/svg"
 										fill="none"
 										viewBox="0 0 24 24"
-										stroke-width="2"
+										stroke-width="2.3"
 										stroke="currentColor"
 										class="w-4 h-4"
 									>
@@ -293,7 +318,7 @@
 
 						<Tooltip content={$i18n.t('Copy')} placement="bottom">
 							<button
-								class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
+								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);
 								}}
@@ -302,7 +327,7 @@
 									xmlns="http://www.w3.org/2000/svg"
 									fill="none"
 									viewBox="0 0 24 24"
-									stroke-width="2"
+									stroke-width="2.3"
 									stroke="currentColor"
 									class="w-4 h-4"
 								>
@@ -340,6 +365,60 @@
 								</button>
 							</Tooltip>
 						{/if}
+
+						{#if $settings?.chatBubble ?? true}
+							{#if siblings.length > 1}
+								<div class="flex self-center">
+									<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}

+ 3 - 3
src/lib/components/chat/ModelSelector.svelte

@@ -2,7 +2,7 @@
 	import { Collapsible } from 'bits-ui';
 
 	import { setDefaultModels } from '$lib/apis/configs';
-	import { models, showSettings, settings, user } from '$lib/stores';
+	import { models, showSettings, settings, user, mobile } from '$lib/stores';
 	import { onMount, tick, getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 	import Selector from './ModelSelector/Selector.svelte';
@@ -38,7 +38,7 @@
 	}
 </script>
 
-<div class="flex flex-col mt-0.5 w-full">
+<div class="flex flex-col w-full items-center md:items-start">
 	{#each selectedModels as selectedModel, selectedModelIdx}
 		<div class="flex w-full max-w-fit">
 			<div class="overflow-hidden w-full">
@@ -108,7 +108,7 @@
 	{/each}
 </div>
 
-{#if showSetDefault}
+{#if showSetDefault && !$mobile}
 	<div class="text-left mt-0.5 ml-1 text-[0.7rem] text-gray-500">
 		<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
 	</div>

+ 12 - 11
src/lib/components/chat/ModelSelector/Selector.svelte

@@ -10,7 +10,7 @@
 
 	import { cancelOllamaRequest, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
 
-	import { user, MODEL_DOWNLOAD_POOL, models } from '$lib/stores';
+	import { user, MODEL_DOWNLOAD_POOL, models, mobile } from '$lib/stores';
 	import { toast } from 'svelte-sonner';
 	import { capitalizeFirstLetter, getModels, splitStream } from '$lib/utils';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
@@ -25,7 +25,7 @@
 
 	export let items = [{ value: 'mango', label: 'Mango' }];
 
-	export let className = ' w-[32rem]';
+	export let className = ' w-[30rem]';
 
 	let show = false;
 
@@ -201,10 +201,11 @@
 			<ChevronDown className=" self-center ml-2 size-3" strokeWidth="2.5" />
 		</div>
 	</DropdownMenu.Trigger>
+
 	<DropdownMenu.Content
-		class=" z-40 {className} max-w-[calc(100vw-1rem)] justify-start rounded-lg  bg-white dark:bg-gray-900 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-700/50  outline-none "
+		class=" z-40 {className} max-w-[calc(100vw-1rem)] justify-start rounded-xl  bg-white dark:bg-gray-850 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-700/50  outline-none "
 		transition={flyAndScale}
-		side={'bottom-start'}
+		side={$mobile ? 'bottom' : 'bottom-start'}
 		sideOffset={4}
 	>
 		<slot>
@@ -224,11 +225,11 @@
 				<hr class="border-gray-100 dark:border-gray-800" />
 			{/if}
 
-			<div class="px-3 my-2 max-h-72 overflow-y-auto scrollbar-none">
+			<div class="px-3 my-2 max-h-64 overflow-y-auto scrollbar-hidden">
 				{#each filteredItems as item}
 					<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-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+						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"
 						on:click={() => {
 							value = item.value;
 
@@ -318,7 +319,7 @@
 
 				{#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-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+						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();
 						}}
@@ -412,12 +413,12 @@
 </DropdownMenu.Root>
 
 <style>
-	.scrollbar-none:active::-webkit-scrollbar-thumb,
-	.scrollbar-none:focus::-webkit-scrollbar-thumb,
-	.scrollbar-none:hover::-webkit-scrollbar-thumb {
+	.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
 		visibility: visible;
 	}
-	.scrollbar-none::-webkit-scrollbar-thumb {
+	.scrollbar-hidden::-webkit-scrollbar-thumb {
 		visibility: hidden;
 	}
 </style>

+ 1 - 1
src/lib/components/chat/Settings/Account.svelte

@@ -71,7 +71,7 @@
 </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-[22rem]">
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
 		<input
 			id="profile-image-input"
 			bind:this={profileImageInputElement}

+ 1 - 1
src/lib/components/chat/Settings/Advanced.svelte

@@ -84,7 +84,7 @@
 			{#if keepAlive !== null}
 				<div class="flex mt-1 space-x-2">
 					<input
-						class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+						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}

+ 2 - 2
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte

@@ -27,7 +27,7 @@
 			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Seed')}</div>
 			<div class=" flex-1 self-center">
 				<input
-					class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+					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={options.seed}
@@ -43,7 +43,7 @@
 			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Stop Sequence')}</div>
 			<div class=" flex-1 self-center">
 				<input
-					class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+					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={options.stop}

+ 2 - 2
src/lib/components/chat/Settings/Audio.svelte

@@ -147,7 +147,7 @@
 		dispatch('save');
 	}}
 >
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<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>
 
@@ -345,7 +345,7 @@
 		{/if}
 	</div>
 
-	<div class="flex justify-end pt-3 text-sm font-medium">
+	<div class="flex justify-end 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"

+ 1 - 1
src/lib/components/chat/Settings/Connections.svelte

@@ -65,7 +65,7 @@
 		dispatch('save');
 	}}
 >
-	<div class="  pr-1.5 overflow-y-scroll max-h-[22rem] space-y-3">
+	<div class="  pr-1.5 overflow-y-scroll max-h-[25rem] space-y-3">
 		<div class=" space-y-3">
 			<div class="mt-2 space-y-2 pr-1.5">
 				<div class="flex justify-between items-center text-sm">

+ 2 - 2
src/lib/components/chat/Settings/General.svelte

@@ -130,7 +130,7 @@
 </script>
 
 <div class="flex flex-col h-full justify-between text-sm">
-	<div class="  pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<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>
 
@@ -253,7 +253,7 @@
 					{#if keepAlive !== null}
 						<div class="flex mt-1 space-x-2">
 							<input
-								class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+								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}

+ 29 - 2
src/lib/components/chat/Settings/Interface.svelte

@@ -22,6 +22,7 @@
 	// Interface
 	let promptSuggestions = [];
 	let showUsername = false;
+	let chatBubble = true;
 
 	const toggleSplitLargeChunks = async () => {
 		splitLargeChunks = !splitLargeChunks;
@@ -33,6 +34,11 @@
 		saveSettings({ fullScreenMode: fullScreenMode });
 	};
 
+	const toggleChatBubble = async () => {
+		chatBubble = !chatBubble;
+		saveSettings({ chatBubble: chatBubble });
+	};
+
 	const toggleShowUsername = async () => {
 		showUsername = !showUsername;
 		saveSettings({ showUsername: showUsername });
@@ -105,6 +111,7 @@
 
 		responseAutoCopy = settings.responseAutoCopy ?? false;
 		showUsername = settings.showUsername ?? false;
+		chatBubble = settings.chatBubble ?? true;
 		fullScreenMode = settings.fullScreenMode ?? false;
 		splitLargeChunks = settings.splitLargeChunks ?? false;
 	});
@@ -117,10 +124,30 @@
 		dispatch('save');
 	}}
 >
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<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('WebUI Add-ons')}</div>
 
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">{$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>
+
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
 					<div class=" self-center text-xs font-medium">{$i18n.t('Title Auto-Generation')}</div>
@@ -286,7 +313,7 @@
 		{#if $user.role === 'admin'}
 			<hr class=" dark:border-gray-700" />
 
-			<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
+			<div class=" space-y-3 pr-1.5">
 				<div class="flex w-full justify-between mb-2">
 					<div class=" self-center text-sm font-semibold">
 						{$i18n.t('Default Prompt Suggestions')}

+ 81 - 0
src/lib/components/chat/Settings/Personalization.svelte

@@ -0,0 +1,81 @@
+<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';
+	const dispatch = createEventDispatcher();
+
+	const i18n = getContext('i18n');
+
+	export let saveSettings: Function;
+
+	// Addons
+	let enableMemory = true;
+
+	onMount(async () => {
+		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
+		enableMemory = settings?.memory ?? true;
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={() => {
+		dispatch('save');
+	}}
+>
+	<div class="  pr-1.5 overflow-y-scroll max-h-[25rem]">
+		<div>
+			<div class="flex items-center justify-between mb-1">
+				<div class="text-sm font-medium">
+					{$i18n.t('Memory')} <span class=" text-xs text-gray-500">({$i18n.t('Beta')})</span>
+				</div>
+
+				<div class="mt-1">
+					<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>
+				LLMs will become more helpful as you chat, picking up on details and preferences to tailor
+				its responses 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-gray-300 dark:outline-gray-800 rounded-3xl"
+			>
+				Manage
+			</button>
+		</div>
+	</div>
+
+	<div class="flex justify-end 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>

+ 152 - 0
src/lib/components/chat/Settings/Personalization/ManageModal.svelte

@@ -0,0 +1,152 @@
+<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 Tooltip from '$lib/components/common/Tooltip.svelte';
+
+	const i18n = getContext('i18n');
+
+	export let show = false;
+
+	let memories = [];
+
+	$: 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('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">
+				{#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"> {$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 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>
+						<!-- {#each chats as chat}
+							<div>
+								{JSON.stringify(chat)}
+							</div>
+						{/each} -->
+					</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>

+ 25 - 1
src/lib/components/chat/SettingsModal.svelte

@@ -15,6 +15,8 @@
 	import Chats from './Settings/Chats.svelte';
 	import Connections from './Settings/Connections.svelte';
 	import Images from './Settings/Images.svelte';
+	import User from '../icons/User.svelte';
+	import Personalization from './Settings/Personalization.svelte';
 
 	const i18n = getContext('i18n');
 
@@ -165,6 +167,21 @@
 					<div class=" self-center">{$i18n.t('Interface')}</div>
 				</button>
 
+				<button
+					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+					'personalization'
+						? 'bg-gray-200 dark:bg-gray-700'
+						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
+					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.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
 					'audio'
@@ -298,7 +315,7 @@
 					<div class=" self-center">{$i18n.t('About')}</div>
 				</button>
 			</div>
-			<div class="flex-1 md:min-h-[25rem]">
+			<div class="flex-1 md:min-h-[28rem]">
 				{#if selectedTab === 'general'}
 					<General
 						{getModels}
@@ -323,6 +340,13 @@
 							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}

+ 15 - 1
src/lib/components/chat/ShareChatModal.svelte

@@ -57,10 +57,23 @@
 
 	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) {
-				chat = await getChatById(localStorage.token, chatId);
+				const _chat = await getChatById(localStorage.token, chatId);
+				if (isDifferentChat(_chat)) {
+					chat = _chat;
+				}
 			} else {
 				chat = null;
 				console.log(chat);
@@ -137,6 +150,7 @@
 							<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);
 

+ 2 - 2
src/lib/components/common/Modal.svelte

@@ -16,9 +16,9 @@
 		} else if (size === 'sm') {
 			return 'w-[30rem]';
 		} else if (size === 'md') {
-			return 'w-[44rem]';
-		} else {
 			return 'w-[48rem]';
+		} else {
+			return 'w-[50rem]';
 		}
 	};
 

+ 22 - 0
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] transition  {state
+		? ' bg-emerald-600'
+		: 'bg-gray-200 dark:bg-transparent'}  outline 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>

+ 2 - 2
src/lib/components/documents/Settings/General.svelte

@@ -190,13 +190,13 @@
 		saveHandler();
 	}}
 >
-	<div class=" space-y-2.5 pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<div class=" space-y-2.5 pr-1.5 overflow-y-scroll max-h-[28rem]">
 		<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('Scan for documents from {{path}}', { path: '/data/docs' })}
+					{$i18n.t('Scan for documents from {{path}}', { path: 'DOCS_DIR (/data/docs)' })}
 				</div>
 
 				<button

+ 1 - 1
src/lib/components/documents/Settings/QueryParams.svelte

@@ -46,7 +46,7 @@
 		saveHandler();
 	}}
 >
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
 		<div class=" ">
 			<div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>
 

+ 19 - 0
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>

+ 11 - 0
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>

+ 64 - 37
src/lib/components/layout/Navbar.svelte

@@ -2,7 +2,17 @@
 	import { getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 
-	import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores';
+	import {
+		WEBUI_NAME,
+		chatId,
+		mobile,
+		modelfiles,
+		settings,
+		showArchivedChats,
+		showSettings,
+		showSidebar,
+		user
+	} from '$lib/stores';
 
 	import { slide } from 'svelte/transition';
 	import ShareChatModal from '../chat/ShareChatModal.svelte';
@@ -10,6 +20,8 @@
 	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';
 
 	const i18n = getContext('i18n');
 
@@ -28,48 +40,35 @@
 
 <ShareChatModal bind:show={showShareChatModal} chatId={$chatId} />
 <nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30">
-	<div class=" flex max-w-full w-full mx-auto px-5 pt-0.5 md:px-[1.3rem]">
+	<div class=" flex max-w-full w-full mx-auto px-5 pt-0.5 md:px-[1rem]">
 		<div class="flex items-center w-full max-w-full">
+			<div
+				class="{$showSidebar
+					? 'md:hidden'
+					: ''} mr-3 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-100 dark:hover:bg-gray-850 transition"
+					on:click={() => {
+						showSidebar.set(!$showSidebar);
+					}}
+				>
+					<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">
-				<div class="md:hidden flex self-center w-[1px] h-5 mx-2 bg-gray-300 dark:bg-stone-700" />
+			<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}
-					<Tooltip content={$i18n.t('Settings')}>
-						<button
-							class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
-							id="open-settings-button"
-							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="w-5 h-5"
-							>
-								<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>
-				{:else}
+				{#if shareEnabled}
 					<Menu
 						{chat}
 						{shareEnabled}
@@ -81,7 +80,8 @@
 						}}
 					>
 						<button
-							class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
+							class="hidden md:flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
+							id="chat-context-menu-button"
 						>
 							<div class=" m-auto self-center">
 								<svg
@@ -105,7 +105,9 @@
 				<Tooltip content={$i18n.t('New Chat')}>
 					<button
 						id="new-chat-button"
-						class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
+						class=" flex {$showSidebar
+							? 'md:hidden'
+							: ''} cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
 						on:click={() => {
 							initNewChat();
 						}}
@@ -127,6 +129,31 @@
 						</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=" flex rounded-xl p-1.5 w-full hover:bg-gray-100 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"
+								/>
+							</div>
+						</button>
+					</UserMenu>
+				{/if}
 			</div>
 		</div>
 	</div>

+ 62 - 88
src/lib/components/layout/Navbar/Menu.svelte

@@ -76,14 +76,14 @@
 
 	<div slot="content">
 		<DropdownMenu.Content
-			class="w-full max-w-[200px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow-lg"
+			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={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-850 rounded-md"
+			<!-- <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);
 				}}
@@ -108,113 +108,87 @@
 					/>
 				</svg>
 				<div class="flex items-center">{$i18n.t('Settings')}</div>
-			</DropdownMenu.Item>
+			</DropdownMenu.Item> -->
 
-			{#if shareEnabled}
-				<DropdownMenu.Item
-					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 rounded-md"
-					on:click={() => {
-						shareHandler();
-					}}
+			<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"
 				>
-					<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>
+					<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>
 
-				<!-- <DropdownMenu.Item
+			<!-- <DropdownMenu.Item
 					class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer"
 					on:click={() => {
 						downloadHandler();
 					}}
 				/> -->
-				<DropdownMenu.Sub>
-					<DropdownMenu.SubTrigger
-						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 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-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 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 dark:hover:bg-gray-850 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 dark:hover:bg-gray-850 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>
-
-				<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
-
-				<div class="flex p-1">
-					<Tags chatId={chat.id} />
-				</div>
-
-				<!-- <DropdownMenu.Item
-					class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer"
-					on:click={() => {
-						tagHandler();
-					}}
+			<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="2"
+						stroke-width="1.5"
 						stroke="currentColor"
 						class="size-4"
 					>
 						<path
 							stroke-linecap="round"
 							stroke-linejoin="round"
-							d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z"
+							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"
 						/>
-						<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z" />
 					</svg>
 
-					<div class="flex items-center">Tag</div>
-				</DropdownMenu.Item> -->
-			{/if}
+					<div class="flex items-center">{$i18n.t('Download')}</div>
+				</DropdownMenu.SubTrigger>
+				<DropdownMenu.SubContent
+					class="w-full 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"
+					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={() => {
+							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>
+
+			<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
+
+			<div class="flex p-1">
+				<Tags chatId={chat.id} />
+			</div>
 		</DropdownMenu.Content>
 	</div>
 </Dropdown>

+ 132 - 280
src/lib/components/layout/Sidebar.svelte

@@ -1,6 +1,16 @@
 <script lang="ts">
 	import { goto } from '$app/navigation';
-	import { user, chats, settings, showSettings, chatId, tags, showSidebar } from '$lib/stores';
+	import {
+		user,
+		chats,
+		settings,
+		showSettings,
+		chatId,
+		tags,
+		showSidebar,
+		mobile,
+		showArchivedChats
+	} from '$lib/stores';
 	import { onMount, getContext } from 'svelte';
 
 	const i18n = getContext('i18n');
@@ -22,8 +32,9 @@
 	import ShareChatModal from '../chat/ShareChatModal.svelte';
 	import ArchiveBox from '../icons/ArchiveBox.svelte';
 	import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
+	import UserMenu from './Sidebar/UserMenu.svelte';
 
-	const BREAKPOINT = 1024;
+	const BREAKPOINT = 768;
 
 	let show = false;
 	let navElement;
@@ -39,7 +50,6 @@
 	let chatTitleEditId = null;
 	let chatTitle = '';
 
-	let showArchivedChatsModal = false;
 	let showShareChatModal = false;
 	let showDropdown = false;
 	let isEditing = false;
@@ -66,7 +76,24 @@
 		}
 	});
 
+	mobile;
+	const onResize = () => {
+		if ($showSidebar && window.innerWidth < BREAKPOINT) {
+			showSidebar.set(false);
+		}
+	};
+
 	onMount(async () => {
+		mobile.subscribe((e) => {
+			if ($showSidebar && e) {
+				showSidebar.set(false);
+			}
+
+			if (!$showSidebar && !e) {
+				showSidebar.set(true);
+			}
+		});
+
 		showSidebar.set(window.innerWidth > BREAKPOINT);
 		await chats.set(await getChatList(localStorage.token));
 
@@ -96,20 +123,12 @@
 			checkDirection();
 		};
 
-		const onResize = () => {
-			if ($showSidebar && window.innerWidth < BREAKPOINT) {
-				showSidebar.set(false);
-			}
-		};
-
 		window.addEventListener('touchstart', onTouchStart);
 		window.addEventListener('touchend', onTouchEnd);
-		window.addEventListener('resize', onResize);
 
 		return () => {
 			window.removeEventListener('touchstart', onTouchStart);
 			window.removeEventListener('touchend', onTouchEnd);
-			window.removeEventListener('resize', onResize);
 		};
 	});
 
@@ -176,31 +195,43 @@
 
 <ShareChatModal bind:show={showShareChatModal} chatId={shareChatId} />
 <ArchivedChatsModal
-	bind:show={showArchivedChatsModal}
+	bind:show={$showArchivedChats}
 	on:change={async () => {
 		await chats.set(await getChatList(localStorage.token));
 	}}
 />
 
+<!-- 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 {$showSidebar
-		? 'lg:relative w-[260px]'
+	class="h-screen max-h-[100dvh] min-h-screen select-none {$showSidebar
+		? 'md:relative 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 rounded-r-2xl
         "
 	data-state={$showSidebar}
 >
 	<div
-		class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] {$showSidebar
+		class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] z-50 {$showSidebar
 			? ''
 			: 'invisible'}"
 	>
-		<div class="px-2 flex justify-center space-x-2">
+		<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-grow flex justify-between rounded-xl px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+				class="flex flex-1 justify-between rounded-xl px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-850 transition"
 				href="/"
+				draggable="false"
 				on:click={async () => {
 					selectedChatId = null;
 
@@ -208,27 +239,29 @@
 					const newChatButton = document.getElementById('new-chat-button');
 					setTimeout(() => {
 						newChatButton?.click();
+
+						if ($mobile) {
+							showSidebar.set(false);
+						}
 					}, 0);
 				}}
 			>
-				<div class="flex self-center">
-					<div class="self-center mr-1.5">
-						<img
-							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">{$i18n.t('New Chat')}</div>
+				<div class="self-center mx-1.5">
+					<img
+						src="{WEBUI_BASE_URL}/static/favicon.png"
+						class=" size-6 -translate-x-1.5 rounded-full"
+						alt="logo"
+					/>
 				</div>
-
-				<div class="self-center">
+				<div class=" self-center font-medium text-sm text-gray-850 dark:text-white">
+					{$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="w-4 h-4"
+						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"
@@ -239,17 +272,42 @@
 					</svg>
 				</div>
 			</a>
+
+			<button
+				class=" cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 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 flex justify-center mt-0.5">
+			<div class="px-2.5 flex justify-center text-gray-800 dark:text-gray-200">
 				<a
-					class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
-					href="/modelfiles"
+					class="flex-grow flex space-x-3 rounded-xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+					href="/workspace"
 					on:click={() => {
 						selectedChatId = null;
 						chatId.set('');
 					}}
+					draggable="false"
 				>
 					<div class="self-center">
 						<svg
@@ -258,7 +316,7 @@
 							viewBox="0 0 24 24"
 							stroke-width="2"
 							stroke="currentColor"
-							class="w-4 h-4"
+							class="size-[1.1rem]"
 						>
 							<path
 								stroke-linecap="round"
@@ -269,71 +327,7 @@
 					</div>
 
 					<div class="flex self-center">
-						<div class=" self-center font-medium text-sm">{$i18n.t('Modelfiles')}</div>
-					</div>
-				</a>
-			</div>
-
-			<div class="px-2 flex justify-center">
-				<a
-					class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
-					href="/prompts"
-					on:click={() => {
-						selectedChatId = null;
-						chatId.set('');
-					}}
-				>
-					<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="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>
-					</div>
-
-					<div class="flex self-center">
-						<div class=" self-center font-medium text-sm">{$i18n.t('Prompts')}</div>
-					</div>
-				</a>
-			</div>
-
-			<div class="px-2 flex justify-center mb-1">
-				<a
-					class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
-					href="/documents"
-					on:click={() => {
-						selectedChatId = null;
-						chatId.set('');
-					}}
-				>
-					<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="w-4 h-4"
-						>
-							<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>
-					</div>
-
-					<div class="flex self-center">
-						<div class=" self-center font-medium text-sm">{$i18n.t('Documents')}</div>
+						<div class=" self-center font-medium text-sm">{$i18n.t('Workspace')}</div>
 					</div>
 				</a>
 			</div>
@@ -383,9 +377,9 @@
 				</div>
 			{/if}
 
-			<div class="px-2 mt-1 mb-2 flex justify-center space-x-2">
-				<div class="flex w-full" id="chat-search">
-					<div class="self-center pl-3 py-2 rounded-l-xl bg-white dark:bg-gray-950">
+			<div class="px-2 mt-0.5 mb-2 flex justify-center space-x-2">
+				<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"
@@ -401,7 +395,7 @@
 					</div>
 
 					<input
-						class="w-full rounded-r-xl py-1.5 pl-2.5 pr-4 text-sm dark:text-gray-300 dark:bg-gray-950 outline-none"
+						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={$i18n.t('Search')}
 						bind:value={search}
 						on:focus={() => {
@@ -412,9 +406,9 @@
 			</div>
 
 			{#if $tags.length > 0}
-				<div class="px-2.5 mt-0.5 mb-2 flex gap-1 flex-wrap">
+				<div class="px-2.5 mb-2 flex gap-1 flex-wrap">
 					<button
-						class="px-2.5 text-xs font-medium bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
+						class="px-2.5 text-xs font-medium bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
 						on:click={async () => {
 							await chats.set(await getChatList(localStorage.token));
 						}}
@@ -423,7 +417,7 @@
 					</button>
 					{#each $tags as tag}
 						<button
-							class="px-2.5 text-xs font-medium bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
+							class="px-2.5 text-xs font-medium bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
 							on:click={async () => {
 								let chatIds = await getChatListByTagName(localStorage.token, tag.name);
 								if (chatIds.length === 0) {
@@ -439,7 +433,7 @@
 				</div>
 			{/if}
 
-			<div class="pl-2 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto scrollbar-none">
+			<div class="pl-2 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto scrollbar-hidden">
 				{#each filteredChatList as chat, idx}
 					{#if idx === 0 || (idx > 0 && chat.time_range !== filteredChatList[idx - 1].time_range)}
 						<div
@@ -494,7 +488,7 @@
 								href="/c/{chat.id}"
 								on:click={() => {
 									selectedChatId = chat.id;
-									if (window.innerWidth < 1024) {
+									if ($mobile) {
 										showSidebar.set(false);
 									}
 								}}
@@ -609,6 +603,9 @@
 											shareChatId = selectedChatId;
 											showShareChatModal = true;
 										}}
+										archiveChatHandler={() => {
+											archiveChatHandler(chat.id);
+										}}
 										renameHandler={() => {
 											chatTitle = chat.title;
 											chatTitleEditId = chat.id;
@@ -640,18 +637,6 @@
 										</button>
 									</ChatMenu>
 
-									<Tooltip content={$i18n.t('Archive')}>
-										<button
-											aria-label="Archive"
-											class=" self-center dark:hover:text-white transition"
-											on:click={() => {
-												archiveChatHandler(chat.id);
-											}}
-										>
-											<ArchiveBox />
-										</button>
-									</Tooltip>
-
 									{#if chat.id === $chatId}
 										<button
 											id="delete-chat-button"
@@ -685,171 +670,38 @@
 
 			<div class="flex flex-col">
 				{#if $user !== undefined}
-					<button
-						class=" flex rounded-xl py-3 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
-						on:click={() => {
-							showDropdown = !showDropdown;
+					<UserMenu
+						role={$user.role}
+						on:show={(e) => {
+							if (e.detail === 'archived-chat') {
+								showArchivedChats.set(true);
+							}
 						}}
 					>
-						<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-semibold">{$user.name}</div>
-					</button>
-
-					{#if showDropdown}
-						<div
-							id="dropdownDots"
-							class="absolute z-40 bottom-[70px] rounded-lg shadow w-[240px] bg-white dark:bg-gray-900"
-							transition:fade|slide={{ duration: 100 }}
+						<button
+							class=" flex rounded-xl py-3 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+							on:click={() => {
+								showDropdown = !showDropdown;
+							}}
 						>
-							<div class="p-1 py-2 w-full">
-								{#if $user.role === 'admin'}
-									<button
-										class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
-										on:click={() => {
-											goto('/admin');
-											showDropdown = 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 font-medium">{$i18n.t('Admin Panel')}</div>
-									</button>
-
-									<button
-										class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
-										on:click={() => {
-											goto('/playground');
-											showDropdown = 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="m6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z"
-												/>
-											</svg>
-										</div>
-										<div class=" self-center font-medium">{$i18n.t('Playground')}</div>
-									</button>
-								{/if}
-
-								<button
-									class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
-									on:click={() => {
-										showArchivedChatsModal = true;
-										showDropdown = false;
-									}}
-								>
-									<div class=" self-center mr-3">
-										<ArchiveBox className="size-5" strokeWidth="1.5" />
-									</div>
-									<div class=" self-center font-medium">{$i18n.t('Archived Chats')}</div>
-								</button>
-
-								<button
-									class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
-									on:click={async () => {
-										await showSettings.set(true);
-										showDropdown = 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 font-medium">{$i18n.t('Settings')}</div>
-								</button>
-							</div>
-
-							<hr class=" dark:border-gray-800 m-0 p-0" />
-
-							<div class="p-1 py-2 w-full">
-								<button
-									class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
-									on:click={() => {
-										localStorage.removeItem('token');
-										location.href = '/auth';
-										showDropdown = 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 font-medium">{$i18n.t('Sign Out')}</div>
-								</button>
+							<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>
-					{/if}
+							<div class=" self-center font-semibold">{$user.name}</div>
+						</button>
+					</UserMenu>
 				{/if}
 			</div>
 		</div>
 	</div>
 
-	<div
+	<!-- <div
 		id="sidebar-handle"
-		class="fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
+		class=" hidden md:fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
 	>
 		<Tooltip
 			placement="right"
@@ -882,16 +734,16 @@
 				</span>
 			</button>
 		</Tooltip>
-	</div>
+	</div> -->
 </div>
 
 <style>
-	.scrollbar-none:active::-webkit-scrollbar-thumb,
-	.scrollbar-none:focus::-webkit-scrollbar-thumb,
-	.scrollbar-none:hover::-webkit-scrollbar-thumb {
+	.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
 		visibility: visible;
 	}
-	.scrollbar-none::-webkit-scrollbar-thumb {
+	.scrollbar-hidden::-webkit-scrollbar-thumb {
 		visibility: hidden;
 	}
 </style>

+ 16 - 4
src/lib/components/layout/Sidebar/ChatMenu.svelte

@@ -9,10 +9,12 @@
 	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';
 
 	const i18n = getContext('i18n');
 
 	export let shareHandler: Function;
+	export let archiveChatHandler: Function;
 	export let renameHandler: Function;
 	export let deleteHandler: Function;
 	export let onClose: Function;
@@ -36,14 +38,14 @@
 
 	<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-50 bg-white dark:bg-gray-900 dark:text-white shadow"
+			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 dark:hover:bg-gray-850 rounded-md"
+				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();
 				}}
@@ -53,7 +55,7 @@
 			</DropdownMenu.Item>
 
 			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
+				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={() => {
 					renameHandler();
 				}}
@@ -63,7 +65,17 @@
 			</DropdownMenu.Item>
 
 			<DropdownMenu.Item
-				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
+				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={() => {
+					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-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 				on:click={() => {
 					deleteHandler();
 				}}

+ 147 - 0
src/lib/components/layout/Sidebar/UserMenu.svelte

@@ -0,0 +1,147 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { createEventDispatcher, getContext } from 'svelte';
+
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { goto } from '$app/navigation';
+	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
+	import { showSettings } from '$lib/stores';
+	import { fade, slide } from 'svelte/transition';
+
+	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 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			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;
+				}}
+			>
+				<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 font-medium">{$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;
+				}}
+			>
+				<div class=" self-center mr-3">
+					<ArchiveBox className="size-5" strokeWidth="1.5" />
+				</div>
+				<div class=" self-center font-medium">{$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('/admin');
+						show = 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 font-medium">{$i18n.t('Admin Panel')}</div>
+				</button>
+			{/if}
+
+			<hr class=" dark:border-gray-800 my-2 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={() => {
+					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 font-medium">{$i18n.t('Sign Out')}</div>
+			</button>
+
+			<!-- <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>

+ 611 - 0
src/lib/components/workspace/Documents.svelte

@@ -0,0 +1,611 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext } from 'svelte';
+	import { WEBUI_NAME, documents } from '$lib/stores';
+	import { createNewDoc, deleteDocByName, getDocs } from '$lib/apis/documents';
+
+	import { SUPPORTED_FILE_TYPE, SUPPORTED_FILE_EXTENSIONS } from '$lib/constants';
+	import { uploadDocToVectorDB } from '$lib/apis/rag';
+	import { transformFileName } from '$lib/utils';
+
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+
+	import EditDocModal from '$lib/components/documents/EditDocModal.svelte';
+	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
+	import SettingsModal from '$lib/components/documents/SettingsModal.svelte';
+	import AddDocModal from '$lib/components/documents/AddDocModal.svelte';
+
+	const i18n = getContext('i18n');
+
+	let importFiles = '';
+
+	let inputFiles = '';
+	let query = '';
+	let documentsImportInputElement: HTMLInputElement;
+	let tags = [];
+
+	let showSettingsModal = false;
+	let showAddDocModal = false;
+	let showEditDocModal = false;
+	let selectedDoc;
+	let selectedTag = '';
+
+	let dragged = false;
+
+	const deleteDoc = async (name) => {
+		await deleteDocByName(localStorage.token, name);
+		await documents.set(await getDocs(localStorage.token));
+	};
+
+	const deleteDocs = async (docs) => {
+		const res = await Promise.all(
+			docs.map(async (doc) => {
+				return await deleteDocByName(localStorage.token, doc.name);
+			})
+		);
+
+		await documents.set(await getDocs(localStorage.token));
+	};
+
+	const uploadDoc = async (file) => {
+		const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			await createNewDoc(
+				localStorage.token,
+				res.collection_name,
+				res.filename,
+				transformFileName(res.filename),
+				res.filename
+			).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+			await documents.set(await getDocs(localStorage.token));
+		}
+	};
+
+	onMount(() => {
+		documents.subscribe((docs) => {
+			tags = docs.reduce((a, e, i, arr) => {
+				return [...new Set([...a, ...(e?.content?.tags ?? []).map((tag) => tag.name)])];
+			}, []);
+		});
+		const dropZone = document.querySelector('body');
+
+		const onDragOver = (e) => {
+			e.preventDefault();
+			dragged = true;
+		};
+
+		const onDragLeave = () => {
+			dragged = false;
+		};
+
+		const onDrop = async (e) => {
+			e.preventDefault();
+
+			if (e.dataTransfer?.files) {
+				let reader = new FileReader();
+
+				reader.onload = (event) => {
+					files = [
+						...files,
+						{
+							type: 'image',
+							url: `${event.target.result}`
+						}
+					];
+				};
+
+				const inputFiles = e.dataTransfer?.files;
+
+				if (inputFiles && inputFiles.length > 0) {
+					for (const file of inputFiles) {
+						console.log(file, file.name.split('.').at(-1));
+						if (
+							SUPPORTED_FILE_TYPE.includes(file['type']) ||
+							SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
+						) {
+							uploadDoc(file);
+						} else {
+							toast.error(
+								`Unknown File Type '${file['type']}', but accepting and treating as plain text`
+							);
+							uploadDoc(file);
+						}
+					}
+				} else {
+					toast.error($i18n.t(`File not found.`));
+				}
+			}
+
+			dragged = false;
+		};
+
+		dropZone?.addEventListener('dragover', onDragOver);
+		dropZone?.addEventListener('drop', onDrop);
+		dropZone?.addEventListener('dragleave', onDragLeave);
+
+		return () => {
+			dropZone?.removeEventListener('dragover', onDragOver);
+			dropZone?.removeEventListener('drop', onDrop);
+			dropZone?.removeEventListener('dragleave', onDragLeave);
+		};
+	});
+
+	let filteredDocs;
+
+	$: filteredDocs = $documents.filter(
+		(doc) =>
+			(selectedTag === '' ||
+				(doc?.content?.tags ?? []).map((tag) => tag.name).includes(selectedTag)) &&
+			(query === '' || doc.name.includes(query))
+	);
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Documents')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+{#if dragged}
+	<div
+		class="fixed 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 rounded-xl 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}
+
+{#key selectedDoc}
+	<EditDocModal bind:show={showEditDocModal} {selectedDoc} />
+{/key}
+
+<AddDocModal bind:show={showAddDocModal} />
+
+<SettingsModal bind:show={showSettingsModal} />
+
+<div class="mb-3">
+	<div class="flex justify-between items-center">
+		<div class=" text-lg font-semibold self-center">{$i18n.t('Documents')}</div>
+
+		<div>
+			<button
+				class="flex 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 transition"
+				type="button"
+				on:click={() => {
+					showSettingsModal = !showSettingsModal;
+				}}
+			>
+				<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 class=" text-xs">{$i18n.t('Document Settings')}</div>
+			</button>
+		</div>
+	</div>
+	<div class=" text-gray-500 text-xs mt-1">
+		ⓘ {$i18n.t("Use '#' in the prompt input to load and select your documents.")}
+	</div>
+</div>
+
+<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={query}
+			placeholder={$i18n.t('Search Documents')}
+		/>
+	</div>
+
+	<div>
+		<button
+			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"
+			on:click={() => {
+				showAddDocModal = 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>
+	</div>
+</div>
+
+<!-- <div>
+    <div
+        class="my-3 py-16 rounded-lg border-2 border-dashed dark:border-gray-600 {dragged &&
+            ' dark:bg-gray-700'} "
+        role="region"
+        on:drop={onDrop}
+        on:dragover={onDragOver}
+        on:dragleave={onDragLeave}
+    >
+        <div class="  pointer-events-none">
+            <div class="text-center dark:text-white text-2xl font-semibold z-50">{$i18n.t('Add Files')}</div>
+
+            <div class=" mt-2 text-center text-sm dark:text-gray-200 w-full">
+                Drop any files here to add to my documents
+            </div>
+        </div>
+    </div>
+</div> -->
+
+<hr class=" dark:border-gray-850 my-2.5" />
+
+{#if tags.length > 0}
+	<div class="px-2.5 pt-1 flex gap-1 flex-wrap">
+		<div class="ml-0.5 pr-3 my-auto flex items-center">
+			<Checkbox
+				state={filteredDocs.filter((doc) => doc?.selected === 'checked').length ===
+				filteredDocs.length
+					? 'checked'
+					: 'unchecked'}
+				indeterminate={filteredDocs.filter((doc) => doc?.selected === 'checked').length > 0 &&
+					filteredDocs.filter((doc) => doc?.selected === 'checked').length !== filteredDocs.length}
+				on:change={(e) => {
+					if (e.detail === 'checked') {
+						filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'checked' }));
+					} else if (e.detail === 'unchecked') {
+						filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'unchecked' }));
+					}
+				}}
+			/>
+		</div>
+
+		{#if filteredDocs.filter((doc) => doc?.selected === 'checked').length === 0}
+			<button
+				class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
+				on:click={async () => {
+					selectedTag = '';
+					// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
+				}}
+			>
+				<div class=" text-xs font-medium self-center line-clamp-1">{$i18n.t('all')}</div>
+			</button>
+
+			{#each tags as tag}
+				<button
+					class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
+					on:click={async () => {
+						selectedTag = tag;
+						// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
+					}}
+				>
+					<div class=" text-xs font-medium self-center line-clamp-1">
+						#{tag}
+					</div>
+				</button>
+			{/each}
+		{:else}
+			<div class="flex-1 flex w-full justify-between items-center">
+				<div class="text-xs font-medium py-0.5 self-center mr-1">
+					{filteredDocs.filter((doc) => doc?.selected === 'checked').length} Selected
+				</div>
+
+				<div class="flex gap-1">
+					<!-- <button
+                        class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
+                        on:click={async () => {
+                            selectedTag = '';
+                            // await chats.set(await getChatListByTagName(localStorage.token, tag.name));
+                        }}
+                    >
+                        <div class=" text-xs font-medium self-center line-clamp-1">add tags</div>
+                    </button> -->
+
+					<button
+						class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
+						on:click={async () => {
+							deleteDocs(filteredDocs.filter((doc) => doc.selected === 'checked'));
+							// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
+						}}
+					>
+						<div class=" text-xs font-medium self-center line-clamp-1">
+							{$i18n.t('delete')}
+						</div>
+					</button>
+				</div>
+			</div>
+		{/if}
+	</div>
+{/if}
+
+<div class="my-3 mb-5">
+	{#each filteredDocs as doc}
+		<button
+			class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+			on:click={() => {
+				if (doc?.selected === 'checked') {
+					doc.selected = 'unchecked';
+				} else {
+					doc.selected = 'checked';
+				}
+			}}
+		>
+			<div class="my-auto flex items-center">
+				<Checkbox state={doc?.selected ?? 'unchecked'} />
+			</div>
+			<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
+				<div class=" flex items-center space-x-3">
+					<div class="p-2.5 bg-red-400 text-white rounded-lg">
+						{#if doc}
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 24 24"
+								fill="currentColor"
+								class="w-6 h-6"
+							>
+								<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}
+							<svg
+								class=" w-6 h-6 translate-y-[0.5px]"
+								fill="currentColor"
+								viewBox="0 0 24 24"
+								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="2.5" /><circle
+									class="spinner_qM83 spinner_oXPr"
+									cx="12"
+									cy="12"
+									r="2.5"
+								/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
+							>
+						{/if}
+					</div>
+					<div class=" self-center flex-1">
+						<div class=" font-bold line-clamp-1">#{doc.name} ({doc.filename})</div>
+						<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
+							{doc.title}
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="flex flex-row space-x-1 self-center">
+				<button
+					class="self-center w-fit text-sm z-20 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={async (e) => {
+						e.stopPropagation();
+						showEditDocModal = !showEditDocModal;
+						selectedDoc = doc;
+					}}
+				>
+					<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>
+				</button>
+
+				<!-- <button
+            class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
+            type="button"
+            on:click={() => {
+                console.log('download file');
+            }}
+        >
+            <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>
+        </button> -->
+
+				<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={(e) => {
+						e.stopPropagation();
+
+						deleteDoc(doc.name);
+					}}
+				>
+					<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>
+				</button>
+			</div>
+		</button>
+	{/each}
+</div>
+
+<div class=" flex justify-end w-full mb-2">
+	<div class="flex space-x-2">
+		<input
+			id="documents-import-input"
+			bind:this={documentsImportInputElement}
+			bind:files={importFiles}
+			type="file"
+			accept=".json"
+			hidden
+			on:change={() => {
+				console.log(importFiles);
+
+				const reader = new FileReader();
+				reader.onload = async (event) => {
+					const savedDocs = JSON.parse(event.target.result);
+					console.log(savedDocs);
+
+					for (const doc of savedDocs) {
+						await createNewDoc(
+							localStorage.token,
+							doc.collection_name,
+							doc.filename,
+							doc.name,
+							doc.title
+						).catch((error) => {
+							toast.error(error);
+							return null;
+						});
+					}
+
+					await documents.set(await getDocs(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={() => {
+				documentsImportInputElement.click();
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Import Documents Mapping')}</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 () => {
+				let blob = new Blob([JSON.stringify($documents)], {
+					type: 'application/json'
+				});
+				saveAs(blob, `documents-mapping-export-${Date.now()}.json`);
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Export Documents Mapping')}</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>

+ 409 - 0
src/lib/components/workspace/Modelfiles.svelte

@@ -0,0 +1,409 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext } from 'svelte';
+
+	import { WEBUI_NAME, modelfiles, settings, user } from '$lib/stores';
+	import { createModel, deleteModel } from '$lib/apis/ollama';
+	import {
+		createNewModelfile,
+		deleteModelfileByTagName,
+		getModelfiles
+	} from '$lib/apis/modelfiles';
+	import { goto } from '$app/navigation';
+
+	const i18n = getContext('i18n');
+
+	let localModelfiles = [];
+	let importFiles;
+	let modelfilesImportInputElement: HTMLInputElement;
+	const deleteModelHandler = async (tagName) => {
+		let success = null;
+
+		success = await deleteModel(localStorage.token, tagName).catch((err) => {
+			toast.error(err);
+			return null;
+		});
+
+		if (success) {
+			toast.success($i18n.t(`Deleted {{tagName}}`, { tagName }));
+		}
+
+		return success;
+	};
+
+	const deleteModelfile = async (tagName) => {
+		await deleteModelHandler(tagName);
+		await deleteModelfileByTagName(localStorage.token, tagName);
+		await modelfiles.set(await getModelfiles(localStorage.token));
+	};
+
+	const shareModelfile = async (modelfile) => {
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
+
+		const url = 'https://openwebui.com';
+
+		const tab = await window.open(`${url}/modelfiles/create`, '_blank');
+		window.addEventListener(
+			'message',
+			(event) => {
+				if (event.origin !== url) return;
+				if (event.data === 'loaded') {
+					tab.postMessage(JSON.stringify(modelfile), '*');
+				}
+			},
+			false
+		);
+	};
+
+	const saveModelfiles = async (modelfiles) => {
+		let blob = new Blob([JSON.stringify(modelfiles)], {
+			type: 'application/json'
+		});
+		saveAs(blob, `modelfiles-export-${Date.now()}.json`);
+	};
+
+	onMount(() => {
+		localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
+
+		if (localModelfiles) {
+			console.log(localModelfiles);
+		}
+	});
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Modelfiles')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<div class=" text-lg font-semibold mb-3">{$i18n.t('Modelfiles')}</div>
+
+<a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2" href="/workspace/modelfiles/create">
+	<div class=" self-center w-10">
+		<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-bold">{$i18n.t('Create a modelfile')}</div>
+		<div class=" text-sm">{$i18n.t('Customize Ollama models for a specific purpose')}</div>
+	</div>
+</a>
+
+<hr class=" dark:border-gray-850" />
+
+<div class=" my-2 mb-5">
+	{#each $modelfiles as modelfile}
+		<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-4 cursor-pointer w-full"
+				href={`/?models=${encodeURIComponent(modelfile.tagName)}`}
+			>
+				<div class=" self-center w-10">
+					<div class=" rounded-full bg-stone-700">
+						<img
+							src={modelfile.imageUrl ?? '/user.png'}
+							alt="modelfile profile"
+							class=" rounded-full w-full h-auto object-cover"
+						/>
+					</div>
+				</div>
+
+				<div class=" flex-1 self-center">
+					<div class=" font-bold capitalize">{modelfile.title}</div>
+					<div class=" text-sm overflow-hidden text-ellipsis line-clamp-1">
+						{modelfile.desc}
+					</div>
+				</div>
+			</a>
+			<div class="flex flex-row space-x-1 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/modelfiles/edit?tag=${encodeURIComponent(modelfile.tagName)}`}
+				>
+					<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>
+
+				<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={() => {
+						// console.log(modelfile);
+						sessionStorage.modelfile = JSON.stringify(modelfile);
+						goto('/workspace/modelfiles/create');
+					}}
+				>
+					<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="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>
+				</button>
+
+				<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={() => {
+						shareModelfile(modelfile);
+					}}
+				>
+					<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="M7.217 10.907a2.25 2.25 0 1 0 0 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186 9.566-5.314m-9.566 7.5 9.566 5.314m0 0a2.25 2.25 0 1 0 3.935 2.186 2.25 2.25 0 0 0-3.935-2.186Zm0-12.814a2.25 2.25 0 1 0 3.933-2.185 2.25 2.25 0 0 0-3.933 2.185Z"
+						/>
+					</svg>
+				</button>
+
+				<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={() => {
+						deleteModelfile(modelfile.tagName);
+					}}
+				>
+					<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>
+			</div>
+		</div>
+	{/each}
+</div>
+
+<div class=" flex justify-end w-full mb-3">
+	<div class="flex space-x-1">
+		<input
+			id="modelfiles-import-input"
+			bind:this={modelfilesImportInputElement}
+			bind:files={importFiles}
+			type="file"
+			accept=".json"
+			hidden
+			on:change={() => {
+				console.log(importFiles);
+
+				let reader = new FileReader();
+				reader.onload = async (event) => {
+					let savedModelfiles = JSON.parse(event.target.result);
+					console.log(savedModelfiles);
+
+					for (const modelfile of savedModelfiles) {
+						await createNewModelfile(localStorage.token, modelfile).catch((error) => {
+							return null;
+						});
+					}
+
+					await modelfiles.set(await getModelfiles(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={() => {
+				modelfilesImportInputElement.click();
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Import Modelfiles')}</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 () => {
+				saveModelfiles($modelfiles);
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Export Modelfiles')}</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 px-3 py-1 border dark:border-gray-600 rounded-xl flex"
+					on:click={async () => {
+						for (const modelfile of localModelfiles) {
+							await createNewModelfile(localStorage.token, modelfile).catch((error) => {
+								return null;
+							});
+						}
+
+						saveModelfiles(localModelfiles);
+						localStorage.removeItem('modelfiles');
+						localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
+						await modelfiles.set(await getModelfiles(localStorage.token));
+					}}
+				>
+					<div class=" self-center mr-2 font-medium">{$i18n.t('Sync All')}</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="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+				</button>
+
+				<button
+					class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
+					on:click={async () => {
+						saveModelfiles(localModelfiles);
+
+						localStorage.removeItem('modelfiles');
+						localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
+						await modelfiles.set(await getModelfiles(localStorage.token));
+					}}
+				>
+					<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>
+
+<div class=" my-16">
+	<div class=" text-lg font-semibold mb-3">{$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/"
+		target="_blank"
+	>
+		<div class=" self-center w-10">
+			<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-bold">{$i18n.t('Discover a modelfile')}</div>
+			<div class=" text-sm">{$i18n.t('Discover, download, and explore model presets')}</div>
+		</div>
+	</a>
+</div>

+ 114 - 116
src/routes/(app)/playground/+page.svelte → src/lib/components/workspace/Playground.svelte

@@ -268,75 +268,74 @@
 	</title>
 </svelte:head>
 
-<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
-	<div class=" flex flex-col justify-between w-full overflow-y-auto h-[100dvh]">
-		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10 h-full">
-			<div class=" flex flex-col h-full">
-				<div class="flex flex-col justify-between mb-2.5 gap-1">
-					<div class="flex justify-between items-center gap-2">
-						<div class=" text-2xl font-semibold self-center flex">
-							{$i18n.t('Playground')}
-							<span class=" text-xs text-gray-500 self-center ml-1">{$i18n.t('(Beta)')}</span>
-						</div>
-
-						<div>
-							<button
-								class=" flex items-center gap-0.5 text-xs px-2.5 py-0.5 rounded-lg {mode ===
-									'chat' && 'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {mode === 'complete' &&
-									'text-green-600 dark:text-green-200 bg-green-200/30'} "
-								on:click={() => {
-									if (mode === 'complete') {
-										mode = 'chat';
-									} else {
-										mode = 'complete';
-									}
-								}}
-							>
-								{#if mode === 'complete'}
-									{$i18n.t('Text Completion')}
-								{:else if mode === 'chat'}
-									{$i18n.t('Chat')}
-								{/if}
-
-								<div>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 16 16"
-										fill="currentColor"
-										class="w-3 h-3"
-									>
-										<path
-											fill-rule="evenodd"
-											d="M5.22 10.22a.75.75 0 0 1 1.06 0L8 11.94l1.72-1.72a.75.75 0 1 1 1.06 1.06l-2.25 2.25a.75.75 0 0 1-1.06 0l-2.25-2.25a.75.75 0 0 1 0-1.06ZM10.78 5.78a.75.75 0 0 1-1.06 0L8 4.06 6.28 5.78a.75.75 0 0 1-1.06-1.06l2.25-2.25a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1 0 1.06Z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-								</div>
-							</button>
-						</div>
+<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">
+			<div class="flex flex-col justify-between mb-2.5 gap-1">
+				<div class="flex justify-between items-center gap-2">
+					<div class=" text-lg font-semibold self-center flex">
+						{$i18n.t('Playground')}
+						<span class=" text-xs text-gray-500 self-center ml-1">{$i18n.t('(Beta)')}</span>
 					</div>
 
-					<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
-											.filter((model) => model.name !== 'hr')
-											.map((model) => ({
-												value: model.id,
-												label: model.custom_info?.name ?? model.name,
-												info: model
-											}))}
-										bind:value={selectedModelId}
-										className="w-[42rem]"
+					<div>
+						<button
+							class=" flex items-center gap-0.5 text-xs px-2.5 py-0.5 rounded-lg {mode === 'chat' &&
+								'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {mode === 'complete' &&
+								'text-green-600 dark:text-green-200 bg-green-200/30'} "
+							on:click={() => {
+								if (mode === 'complete') {
+									mode = 'chat';
+								} else {
+									mode = 'complete';
+								}
+							}}
+						>
+							{#if mode === 'complete'}
+								{$i18n.t('Text Completion')}
+							{:else if mode === 'chat'}
+								{$i18n.t('Chat')}
+							{/if}
+
+							<div>
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="w-3 h-3"
+								>
+									<path
+										fill-rule="evenodd"
+										d="M5.22 10.22a.75.75 0 0 1 1.06 0L8 11.94l1.72-1.72a.75.75 0 1 1 1.06 1.06l-2.25 2.25a.75.75 0 0 1-1.06 0l-2.25-2.25a.75.75 0 0 1 0-1.06ZM10.78 5.78a.75.75 0 0 1-1.06 0L8 4.06 6.28 5.78a.75.75 0 0 1-1.06-1.06l2.25-2.25a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1 0 1.06Z"
+										clip-rule="evenodd"
 									/>
-								</div>
+								</svg>
+							</div>
+						</button>
+					</div>
+				</div>
+
+				<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
+										.filter((model) => model.name !== 'hr')
+										.map((model) => ({
+											value: model.id,
+											label: model.name,
+											info: model
+										}))}
+									bind:value={selectedModelId}
+									className="w-[42rem]"
+								/>
 							</div>
 						</div>
+					</div>
 
-						<!-- <button
+					<!-- <button
 							class=" self-center dark:hover:text-gray-300"
 							id="open-settings-button"
 							on:click={async () => {}}
@@ -361,67 +360,66 @@
 								/>
 							</svg>
 						</button> -->
-					</div>
 				</div>
+			</div>
 
-				{#if mode === 'chat'}
-					<div class="p-1">
-						<div class="p-3 outline outline-1 outline-gray-200 dark:outline-gray-800 rounded-lg">
-							<div class=" text-sm font-medium">{$i18n.t('System')}</div>
+			{#if mode === 'chat'}
+				<div class="p-1">
+					<div class="p-3 outline outline-1 outline-gray-200 dark:outline-gray-800 rounded-lg">
+						<div class=" text-sm font-medium">{$i18n.t('System')}</div>
+						<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>
+			{/if}
+
+			<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">
+						{#if mode === 'complete'}
 							<textarea
-								id="system-textarea"
-								class="w-full h-full bg-transparent resize-none outline-none text-sm"
-								bind:value={system}
+								id="text-completion-textarea"
+								bind:this={textCompletionAreaElement}
+								class="w-full h-full p-3 bg-transparent outline outline-1 outline-gray-200 dark:outline-gray-800 resize-none rounded-lg text-sm"
+								bind:value={text}
 								placeholder={$i18n.t("You're a helpful assistant.")}
-								rows="4"
 							/>
-						</div>
-					</div>
-				{/if}
-
-				<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">
-							{#if mode === 'complete'}
-								<textarea
-									id="text-completion-textarea"
-									bind:this={textCompletionAreaElement}
-									class="w-full h-full p-3 bg-transparent outline outline-1 outline-gray-200 dark:outline-gray-800 resize-none rounded-lg text-sm"
-									bind:value={text}
-									placeholder={$i18n.t("You're a helpful assistant.")}
-								/>
-							{:else if mode === 'chat'}
-								<ChatCompletion bind:messages />
-							{/if}
-						</div>
+						{:else if mode === 'chat'}
+							<ChatCompletion bind:messages />
+						{/if}
 					</div>
 				</div>
+			</div>
 
-				<div class="pb-2">
-					{#if !loading}
-						<button
-							class="px-3 py-1.5 text-sm font-medium bg-emerald-600 hover:bg-emerald-700 text-gray-50 transition rounded-lg"
-							on:click={() => {
-								submitHandler();
-							}}
-						>
-							{$i18n.t('Submit')}
-						</button>
-					{:else}
-						<button
-							class="px-3 py-1.5 text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-900 transition rounded-lg"
-							on:click={() => {
-								stopResponse();
-							}}
-						>
-							{$i18n.t('Cancel')}
-						</button>
-					{/if}
-				</div>
+			<div class="pb-3">
+				{#if !loading}
+					<button
+						class="px-3 py-1.5 text-sm font-medium bg-emerald-600 hover:bg-emerald-700 text-gray-50 transition rounded-lg"
+						on:click={() => {
+							submitHandler();
+						}}
+					>
+						{$i18n.t('Submit')}
+					</button>
+				{:else}
+					<button
+						class="px-3 py-1.5 text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-900 transition rounded-lg"
+						on:click={() => {
+							stopResponse();
+						}}
+					>
+						{$i18n.t('Cancel')}
+					</button>
+				{/if}
 			</div>
 		</div>
 	</div>

+ 331 - 0
src/lib/components/workspace/Prompts.svelte

@@ -0,0 +1,331 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext } from 'svelte';
+	import { WEBUI_NAME, prompts } from '$lib/stores';
+	import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
+	import { error } from '@sveltejs/kit';
+	import { goto } from '$app/navigation';
+
+	const i18n = getContext('i18n');
+
+	let importFiles = '';
+	let query = '';
+	let promptsImportInputElement: HTMLInputElement;
+	const sharePrompt = 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 deletePrompt = async (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>
+
+<div class="mb-3 flex justify-between items-center">
+	<div class=" text-lg font-semibold self-center">{$i18n.t('Prompts')}</div>
+</div>
+
+<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={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>
+<hr class=" dark:border-gray-850 my-2.5" />
+
+<div class="my-3 mb-5">
+	{#each $prompts.filter((p) => query === '' || p.command.includes(query)) 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-5">
+						<div class=" font-bold">{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 space-x-1 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>
+
+				<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={() => {
+						// console.log(modelfile);
+						sessionStorage.prompt = JSON.stringify(prompt);
+						goto('/workspace/prompts/create');
+					}}
+				>
+					<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="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>
+				</button>
+
+				<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={() => {
+						sharePrompt(prompt);
+					}}
+				>
+					<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="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z"
+						/>
+					</svg>
+				</button>
+
+				<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={() => {
+						deletePrompt(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="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>
+				</button>
+			</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">{$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">{$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>
+
+<div class=" my-16">
+	<div class=" text-lg font-semibold mb-3">{$i18n.t('Made by OpenWebUI Community')}</div>
+
+	<a
+		class=" flex space-x-4 cursor-pointer w-full mb-3 px-3 py-2"
+		href="https://openwebui.com/?type=prompts"
+		target="_blank"
+	>
+		<div class=" self-center w-10">
+			<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-bold">{$i18n.t('Discover a prompt')}</div>
+			<div class=" text-sm">{$i18n.t('Discover, download, and explore custom prompts')}</div>
+		</div>
+	</a>
+</div>

+ 2 - 2
src/lib/constants.ts

@@ -1,8 +1,8 @@
-import { dev } from '$app/environment';
+import { browser, dev } from '$app/environment';
 // import { version } from '../../package.json';
 
 export const APP_NAME = 'Open WebUI';
-export const WEBUI_BASE_URL = dev ? `http://${location.hostname}:8080` : ``;
+export const WEBUI_BASE_URL = browser ? (dev ? `http://${location.hostname}:8080` : ``) : ``;
 
 export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`;
 

+ 1 - 1
src/lib/i18n/index.ts

@@ -37,7 +37,7 @@ const createIsLoadingStore = (i18n: i18nType) => {
 	return isLoading;
 };
 
-export const initI18n = (defaultLocale: string) => {
+export const initI18n = (defaultLocale: string | undefined) => {
 	let detectionOrder = defaultLocale
 		? ['querystring', 'localStorage']
 		: ['querystring', 'localStorage', 'navigator'];

+ 101 - 99
src/lib/i18n/locales/ar-BH/translation.json

@@ -1,11 +1,11 @@
 {
 	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "",
 	"(Beta)": "(تجريبي)",
-	"(e.g. `sh webui.sh --api`)": "(مثال `sh webui.sh --api`)",
+	"(e.g. `sh webui.sh --api`)": "( `sh webui.sh --api`مثال)",
 	"(latest)": "(الأخير)",
 	"{{modelName}} is thinking...": "{{modelName}} ...يفكر",
 	"{{user}}'s Chats": "{{user}}' الدردشات",
-	"{{webUIName}} Backend Required": "",
+	"{{webUIName}} Backend Required": "{{webUIName}} مطلوب",
 	"a user": "المستخدم",
 	"About": "عن",
 	"Account": "الحساب",
@@ -15,20 +15,20 @@
 	"Add a short description about what this modelfile does": "أضف وصفًا قصيرًا حول ما يفعله ملف الموديل هذا",
 	"Add a short title for this prompt": "أضف عنوانًا قصيرًا لبداء المحادثة",
 	"Add a tag": "أضافة تاق",
-	"Add custom prompt": "",
+	"Add custom prompt": "أضافة مطالبة مخصصه",
 	"Add Docs": "إضافة المستندات",
 	"Add Files": "إضافة ملفات",
 	"Add message": "اضافة رسالة",
 	"Add Model": "اضافة موديل",
 	"Add Tags": "اضافة تاق",
 	"Add User": "اضافة مستخدم",
-	"Adjusting these settings will apply changes universally to all users.": "سيؤدي ضبط هذه الإعدادات إلى تطبيق التغييرات بشكل عام على كافة المستخدمين.",
+	"Adjusting these settings will apply changes universally to all users.": "سيؤدي ضبط هذه الإعدادات إلى تطبيق التغييرات بشكل عام على كافة المستخدمين",
 	"admin": "المشرف",
 	"Admin Panel": "لوحة التحكم",
 	"Admin Settings": "اعدادات المشرف",
 	"Advanced Parameters": "التعليمات المتقدمة",
 	"all": "الكل",
-	"All Documents": "",
+	"All Documents": "جميع الملفات",
 	"All Users": "جميع المستخدمين",
 	"Allow": "يسمح",
 	"Allow Chat Deletion": "يستطيع حذف المحادثات",
@@ -42,32 +42,34 @@
 	"API Key created.": "API تم أنشاء المفتاح",
 	"API keys": "API المفاتيح",
 	"API RPM": "API RPM",
-	"April": "",
+	"April": "أبريل",
 	"Archive": "الأرشيف",
 	"Archived Chats": "الأرشيف المحادثات",
 	"are allowed - Activate this command by typing": "مسموح - قم بتنشيط هذا الأمر عن طريق الكتابة",
 	"Are you sure?": "هل أنت متأكد ؟",
-	"Attach file": "",
+	"Attach file": "أرفق ملف",
 	"Attention to detail": "انتبه للتفاصيل",
 	"Audio": "صوتي",
-	"August": "",
+	"August": "أغسطس",
 	"Auto-playback response": "استجابة التشغيل التلقائي",
-	"Auto-send input after 3 sec.": "إرسال تلقائي للإدخال بعد 3 ثوانٍ.",
+	"Auto-send input after 3 sec.": "إرسال تلقائي للإدخال بعد 3 ثواني.",
 	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 الرابط الرئيسي",
 	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 الرابط مطلوب",
 	"available!": "متاح",
 	"Back": "خلف",
 	"Bad Response": "استجابة خطاء",
-	"before": "",
+	"before": "قبل",
 	"Being lazy": "كون كسول",
+	"Beta": "",
 	"Builder Mode": "بناء الموديل",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "اللغاء",
 	"Categories": "التصنيفات",
 	"Change Password": "تغير الباسورد",
 	"Chat": "المحادثة",
+	"Chat Bubble UI": "",
 	"Chat History": "تاريخ المحادثة",
-	"Chat History is off for this browser.": "سجل الدردشة معطل لهذا المتصفح.",
+	"Chat History is off for this browser.": "سجل الدردشة معطل لهذا المتصفح",
 	"Chats": "المحادثات",
 	"Check Again": "تحقق مرة اخرى",
 	"Check for updates": "تحقق من التحديثات",
@@ -76,13 +78,13 @@
 	"Chunk Overlap": "Chunk تداخل",
 	"Chunk Params": "Chunk المتغيرات",
 	"Chunk Size": "Chunk حجم",
-	"Citation": "",
+	"Citation": "اقتباس",
 	"Click here for help.": "أضغط هنا للمساعدة",
-	"Click here to": "",
-	"Click here to check other modelfiles.": "انقر هنا للتحقق من ملفات الموديلات الأخرى.",
+	"Click here to": "أضغط هنا الانتقال",
+	"Click here to check other modelfiles.": "انقر هنا للتحقق من ملفات الموديلات الأخرى",
 	"Click here to select": "أضغط هنا للاختيار",
 	"Click here to select a csv file.": "أضغط هنا للاختيار ملف csv",
-	"Click here to select documents.": "انقر هنا لاختيار المستندات.",
+	"Click here to select documents.": "انقر هنا لاختيار المستندات",
 	"click here.": "أضغط هنا",
 	"Click on the user role button to change a user's role.": "أضغط على أسم الصلاحيات لتغيرها للمستخدم",
 	"Close": "أغلق",
@@ -97,17 +99,17 @@
 	"Context Length": "طول السياق",
 	"Continue Response": "متابعة الرد",
 	"Conversation Mode": "وضع المحادثة",
-	"Copied shared chat URL to clipboard!": "تم نسخ عنوان URL للدردشة المشتركة إلى الحافظة!",
+	"Copied shared chat URL to clipboard!": "تم نسخ عنوان URL للدردشة المشتركة إلى الحافظة",
 	"Copy": "نسخ",
 	"Copy last code block": "انسخ كتلة التعليمات البرمجية الأخيرة",
 	"Copy last response": "انسخ الرد الأخير",
 	"Copy Link": "أنسخ الرابط",
-	"Copying to clipboard was successful!": "تم النسخ إلى الحافظة بنجاح!",
+	"Copying to clipboard was successful!": "تم النسخ إلى الحافظة بنجاح",
 	"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':": "قم بإنشاء عبارة موجزة مكونة من 3-5 كلمات كرأس للاستعلام التالي، مع الالتزام الصارم بالحد الأقصى لعدد الكلمات الذي يتراوح بين 3-5 كلمات وتجنب استخدام الكلمة 'عنوان':",
 	"Create a modelfile": "إنشاء ملف نموذجي",
 	"Create Account": "إنشاء حساب",
-	"Create new key": "",
-	"Create new secret key": "",
+	"Create new key": "عمل مفتاح جديد",
+	"Create new secret key": "عمل سر جديد",
 	"Created at": "أنشئت في",
 	"Created At": "أنشئت من",
 	"Current Model": "الموديل المختار",
@@ -116,27 +118,26 @@
 	"Custom": "مخصص",
 	"Customize Ollama models for a specific purpose": "تخصيص الموديل Ollama لغرض محدد",
 	"Dark": "مظلم",
-	"Dashboard": "",
+	"Dashboard": "لوحة التحكم",
 	"Database": "قاعدة البيانات",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
-	"December": "",
+	"December": "ديسمبر",
 	"Default": "الإفتراضي",
-	"Default (Automatic1111)": "الإفتراضي (Automatic1111)",
-	"Default (SentenceTransformers)": "الإفتراضي (SentenceTransformers)",
-	"Default (Web API)": "الإفتراضي (Web API)",
+	"Default (Automatic1111)": "(Automatic1111) الإفتراضي",
+	"Default (SentenceTransformers)": "(SentenceTransformers) الإفتراضي",
+	"Default (Web API)": "(Web API) الإفتراضي",
 	"Default model updated": "الإفتراضي تحديث الموديل",
 	"Default Prompt Suggestions": "الإفتراضي Prompt الاقتراحات",
 	"Default User Role": "الإفتراضي صلاحيات المستخدم",
 	"delete": "حذف",
-	"Delete": "حذف.",
+	"Delete": "حذف",
 	"Delete a model": "حذف الموديل",
 	"Delete chat": "حذف المحادثه",
 	"Delete Chat": "حذف المحادثه.",
-	"Delete Chats": "حذ المحادثات",
-	"delete this link": "",
+	"Delete Chats": "حذف المحادثات",
+	"delete this link": "أحذف هذا الرابط",
 	"Delete User": "حذف المستخدم",
-	"Deleted {{deleteModelTag}}": "حذف {{deleteModelTag}}",
-	"Deleted {{tagName}}": "حذف {{tagName}}",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} حذف",
+	"Deleted {{tagName}}": "{{tagName}} حذف",
 	"Description": "وصف",
 	"Didn't fully follow instructions": "لم أتبع التعليمات بشكل كامل",
 	"Disabled": "تعطيل",
@@ -153,7 +154,7 @@
 	"Don't have an account?": "ليس لديك حساب؟",
 	"Don't like the style": "لا أحب النمط",
 	"Download": "تحميل",
-	"Download canceled": "",
+	"Download canceled": "تم اللغاء التحميل",
 	"Download Database": "تحميل قاعدة البيانات",
 	"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'.",
@@ -171,16 +172,16 @@
 	"Enter {{role}} message here": "أدخل رسالة {{role}} هنا",
 	"Enter Chunk Overlap": "أدخل Chunk المتداخل",
 	"Enter Chunk Size": "أدخل Chunk الحجم",
-	"Enter Image Size (e.g. 512x512)": "أدخل حجم الصورة (e.g. 512x512)",
-	"Enter language codes": "",
+	"Enter Image Size (e.g. 512x512)": "(e.g. 512x512) أدخل حجم الصورة ",
+	"Enter language codes": "أدخل كود اللغة",
 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "أدخل عنوان URL الأساسي لواجهة برمجة تطبيقات LiteLLM (litellm_params.api_base)",
 	"Enter LiteLLM API Key (litellm_params.api_key)": "أدخل مفتاح LiteLLM API (litellm_params.api_key)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "أدخل LiteLLM API RPM (litllm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "أدخل LiteLLM الموديل (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "أدخل أكثر Tokens (litellm_params.max_tokens)",
 	"Enter Model Display Name": "",
-	"Enter model tag (e.g. {{modelTag}})": "أدخل الموديل تاق (e.g. {{modelTag}})",
-	"Enter Number of Steps (e.g. 50)": "أدخل عدد الخطوات (e.g. 50)",
+	"Enter model tag (e.g. {{modelTag}})": "(e.g. {{modelTag}}) أدخل الموديل تاق",
+	"Enter Number of Steps (e.g. 50)": "(e.g. 50) أدخل عدد الخطوات",
 	"Enter Score": "أدخل النتيجة",
 	"Enter stop sequence": "أدخل تسلسل التوقف",
 	"Enter Top K": "Enter Top K",
@@ -198,7 +199,7 @@
 	"Export Prompts": "مطالبات التصدير",
 	"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
 	"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
-	"February": "",
+	"February": "فبراير",
 	"Feel free to add specific details": "لا تتردد في إضافة تفاصيل محددة",
 	"File Mode": "وضع الملف",
 	"File not found.": "لم يتم العثور على الملف.",
@@ -213,9 +214,10 @@
 	"General Settings": "الاعدادات العامة",
 	"Generation Info": "معلومات الجيل",
 	"Good Response": "استجابة جيدة",
+	"h:mm a": "",
 	"has no conversations.": "ليس لديه محادثات.",
-	"Hello, {{name}}": "مرحبا, {{name}}",
-	"Help": "",
+	"Hello, {{name}}": " {{name}} مرحبا",
+	"Help": "مساعدة",
 	"Hide": "أخفاء",
 	"Hide Additional Params": "إخفاء المعلمات الإضافية",
 	"How can I help you today?": "كيف استطيع مساعدتك اليوم؟",
@@ -231,13 +233,13 @@
 	"Include `--api` flag when running stable-diffusion-webui": "قم بتضمين علامة `-api` عند تشغيل Stable-diffusion-webui",
 	"Input commands": "",
 	"Interface": "واجهه المستخدم",
-	"Invalid Tag": "",
+	"Invalid Tag": "تاق غير صالحة",
 	"Is Model Vision Capable": "",
-	"January": "",
+	"January": "يناير",
 	"join our Discord for help.": "انضم إلى Discord للحصول على المساعدة.",
 	"JSON": "JSON",
-	"July": "",
-	"June": "",
+	"July": "يوليو",
+	"June": "يونيو",
 	"JWT Expiration": "JWT تجريبي",
 	"JWT Token": "JWT Token",
 	"Keep Alive": "Keep Alive",
@@ -245,19 +247,20 @@
 	"Language": "اللغة",
 	"Last Active": "آخر نشاط",
 	"Light": "فاتح",
-	"Listening...": "جاري الاستماع...",
-	"LLMs can make mistakes. Verify important information.": "يمكن أن يرتكب LLM الأخطاء. التحقق من المعلومات الهامة.",
-	"Made by OpenWebUI Community": "تم إنشاؤه بواسطة مجتمع OpenWebUI",
+	"Listening...": "جاري الاستماع",
+	"LLMs can make mistakes. Verify important information.": "يمكن أن تصدر بعض الأخطاء. لذلك يجب التحقق من المعلومات المهمة",
+	"Made by OpenWebUI Community": "OpenWebUI تم إنشاؤه بواسطة مجتمع ",
 	"Make sure to enclose them with": "تأكد من إرفاقها",
-	"Manage LiteLLM Models": "إدارة نماذج LiteLLM",
+	"Manage LiteLLM Models": "LiteLLM إدارة نماذج ",
 	"Manage Model Information": "",
 	"Manage Models": "إدارة النماذج",
-	"Manage Ollama Models": "إدارة موديلات Ollama",
+	"Manage Ollama Models": "Ollama إدارة موديلات ",
 	"March": "",
 	"Max Tokens": "Max Tokens",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "يمكن تنزيل 3 نماذج كحد أقصى في وقت واحد. الرجاء معاودة المحاولة في وقت لاحق.",
 	"May": "",
-	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
+	"Memory": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "لن تتم مشاركة الرسائل التي ترسلها بعد إنشاء الرابط الخاص بك. سيتمكن المستخدمون الذين لديهم عنوان URL من عرض الدردشة المشتركة.",
 	"Minimum Score": "الحد الأدنى من النقاط",
 	"Mirostat": "Mirostat",
 	"Mirostat Eta": "Mirostat Eta",
@@ -267,7 +270,7 @@
 	"Model '{{modelName}}' has been successfully downloaded.": "موديل '{{modelName}}'تم تحميله بنجاح",
 	"Model '{{modelTag}}' is already in queue for downloading.": "موديل '{{modelTag}}' جاري تحميلة الرجاء الانتظار",
 	"Model {{modelId}} not found": "موديل {{modelId}} لم يوجد",
-	"Model {{modelName}} already exists.": "موديل {{modelName}} موجود",
+	"Model {{modelName}} already exists.": "موجود {{modelName}} موديل ",
 	"Model {{modelName}} is not vision capable": "",
 	"Model Description": "",
 	"Model Display Name": "",
@@ -285,24 +288,21 @@
 	"Modelfiles": "ملفات الموديل",
 	"Models": "الموديلات",
 	"More": "المزيد",
-	"My Documents": "مستنداتي",
-	"My Modelfiles": "ملفاتي النموذجية",
-	"My Prompts": "مطالباتي",
 	"Name": "الأسم",
 	"Name Tag": "أسم التاق",
 	"Name your modelfile": "قم بتسمية ملف النموذج الخاص بك",
 	"New Chat": "دردشة جديدة",
 	"New Password": "كلمة المرور الجديدة",
 	"No": "",
-	"No results found": "",
-	"No source available": "",
+	"No results found": "لا توجد نتايج",
+	"No source available": "لا يوجد مصدر متاح",
 	"Not factually correct": "ليس صحيحا من حيث الواقع",
 	"Not sure what to add?": "لست متأكدا ما يجب إضافته؟",
 	"Not sure what to write? Switch to": "لست متأكدا ماذا أكتب؟ التبديل إلى",
 	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "ملاحظة: إذا قمت بتعيين الحد الأدنى من النقاط، فلن يؤدي البحث إلا إلى إرجاع المستندات التي لها نقاط أكبر من أو تساوي الحد الأدنى من النقاط.",
 	"Notifications": "إشعارات",
-	"November": "",
-	"October": "",
+	"November": "نوفمبر",
+	"October": "اكتوبر",
 	"Off": "أغلاق",
 	"Okay, Let's Go!": "حسنا دعنا نذهب!",
 	"OLED Dark": "OLED داكن",
@@ -316,50 +316,51 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "خطاء! يبدو أن عنوان URL غير صالح. يرجى التحقق مرة أخرى والمحاولة مرة أخرى.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "خطاء! أنت تستخدم طريقة غير مدعومة (الواجهة الأمامية فقط). يرجى تقديم واجهة WebUI من الواجهة الخلفية.",
 	"Open": "فتح",
-	"Open AI": "فتح AI",
-	"Open AI (Dall-E)": "فتح AI (Dall-E)",
+	"Open AI": "AI فتح",
+	"Open AI (Dall-E)": "AI (Dall-E) فتح",
 	"Open new chat": "فتح محادثة جديده",
 	"OpenAI": "OpenAI",
 	"OpenAI API": "OpenAI API",
 	"OpenAI API Config": "OpenAI API إعدادات",
-	"OpenAI API Key is required.": "مطلوب مفتاح OpenAI API.",
-	"OpenAI URL/Key required.": "مطلوب عنوان URL/مفتاح OpenAI.",
+	"OpenAI API Key is required.": "OpenAI API.مطلوب مفتاح ",
+	"OpenAI URL/Key required.": "URL/مفتاح OpenAI.مطلوب عنوان ",
 	"or": "أو",
 	"Other": "آخر",
-	"Overview": "",
+	"Overview": "عرض",
 	"Parameters": "Parameters",
 	"Password": "الباسورد",
 	"PDF document (.pdf)": "PDF ملف (.pdf)",
 	"PDF Extract Images (OCR)": "PDF أستخرج الصور (OCR)",
 	"pending": "قيد الانتظار",
-	"Permission denied when accessing microphone: {{error}}": "تم رفض الإذن عند الوصول إلى الميكروفون: {{error}}",
+	"Permission denied when accessing microphone: {{error}}": "{{error}} تم رفض الإذن عند الوصول إلى الميكروفون ",
+	"Personalization": "",
 	"Plain text (.txt)": "نص عادي (.txt)",
 	"Playground": "مكان التجربة",
 	"Positive attitude": "موقف ايجابي",
-	"Previous 30 days": "",
-	"Previous 7 days": "",
+	"Previous 30 days": "أخر 30 يوم",
+	"Previous 7 days": "أخر 7 أيام",
 	"Profile Image": "صورة الملف الشخصي",
-	"Prompt": "",
+	"Prompt": "مطالبة",
 	"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": "سحب الموديل من Ollama.com",
+	"Prompts": "مطالبات",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com \"{{searchValue}}\" أسحب من ",
+	"Pull a model from Ollama.com": "Ollama.com سحب الموديل من ",
 	"Pull Progress": "سحب التقدم",
 	"Query Params": "Query Params",
 	"RAG Template": "RAG تنمبلت",
 	"Raw Format": "Raw فورمات",
 	"Read Aloud": "أقراء لي",
 	"Record voice": "سجل صوت",
-	"Redirecting you to OpenWebUI Community": "إعادة توجيهك إلى مجتمع OpenWebUI",
+	"Redirecting you to OpenWebUI Community": "OpenWebUI إعادة توجيهك إلى مجتمع ",
 	"Refused when it shouldn't have": "رفض عندما لا ينبغي أن يكون",
 	"Regenerate": "تجديد",
 	"Release Notes": "ملاحظات الإصدار",
 	"Remove": "إزالة",
-	"Remove Model": "",
-	"Rename": "",
-	"Repeat Last N": "كرر آخر N",
+	"Remove Model": "حذف الموديل",
+	"Rename": "إعادة تسمية",
+	"Repeat Last N": "N كرر آخر",
 	"Repeat Penalty": "كرر المخالفة",
 	"Request Mode": "وضع الطلب",
 	"Reranking Model": "",
@@ -372,27 +373,27 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "حفظ",
 	"Save & Create": "حفظ وإنشاء",
-	"Save & Submit": "حفظ وإرسال",
 	"Save & Update": "حفظ وتحديث",
 	"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": "لم يعد حفظ سجلات الدردشة مباشرة في مساحة تخزين متصفحك مدعومًا. يرجى تخصيص بعض الوقت لتنزيل وحذف سجلات الدردشة الخاصة بك عن طريق النقر على الزر أدناه. لا تقلق، يمكنك بسهولة إعادة استيراد سجلات الدردشة الخاصة بك إلى الواجهة الخلفية من خلاله",
 	"Scan": "مسح",
 	"Scan complete!": "تم المسح",
-	"Scan for documents from {{path}}": " مسح على الملفات من {{path}}",
+	"Scan for documents from {{path}}": "{{path}} مسح على الملفات من",
 	"Search": "البحث",
 	"Search a model": "البحث عن موديل",
 	"Search Documents": "البحث المستندات",
 	"Search Prompts": "أبحث حث",
-	"See readme.md for instructions": "راجع readme.md للحصول على التعليمات",
+	"See readme.md for instructions": "readme.md للحصول على التعليمات",
 	"See what's new": "ما الجديد",
 	"Seed": "Seed",
 	"Select a mode": "أختار موديل",
 	"Select a model": "أختار الموديل",
-	"Select an Ollama instance": "أختار سيرفر Ollama",
-	"Select model": "",
+	"Select an Ollama instance": "أختار سيرفر ",
+	"Select model": " أختار موديل",
 	"Selected model does not support image inputs.": "",
-	"Send a Message": "أرسل رسالة.",
-	"Send message": "أرسل رسالة",
-	"September": "",
+	"Send": "",
+	"Send a Message": "يُرجى إدخال طلبك هنا",
+	"Send message": "يُرجى إدخال طلبك هنا.",
+	"September": "سبتمبر",
 	"Server connection verified": "تم التحقق من اتصال الخادم",
 	"Set as default": "الافتراضي",
 	"Set Default Model": "تفعيد الموديل الافتراضي",
@@ -407,7 +408,7 @@
 	"Settings saved successfully!": "تم حفظ الاعدادات بنجاح",
 	"Share": "كشاركة",
 	"Share Chat": "مشاركة الدردشة",
-	"Share to OpenWebUI Community": "شارك في مجتمع OpenWebUI",
+	"Share to OpenWebUI Community": "OpenWebUI شارك في مجتمع",
 	"short-summary": "ملخص قصير",
 	"Show": "عرض",
 	"Show Additional Params": "إظهار المعلمات الإضافية",
@@ -418,17 +419,17 @@
 	"Sign Out": "تسجيل الخروج",
 	"Sign up": "تسجيل",
 	"Signing in": "جاري الدخول",
-	"Source": "",
-	"Speech recognition error: {{error}}": "خطأ في التعرف على الكلام: {{error}}",
+	"Source": "المصدر",
+	"Speech recognition error: {{error}}": "{{error}} خطأ في التعرف على الكلام",
 	"Speech-to-Text Engine": "محرك تحويل الكلام إلى نص",
 	"SpeechRecognition API is not supported in this browser.": "API SpeechRecognition غير مدعومة في هذا المتصفح.",
 	"Stop Sequence": "وقف التسلسل",
 	"STT Settings": "STT اعدادات",
 	"Submit": "إرسال",
-	"Subtitle (e.g. about the Roman Empire)": "الترجمة (e.g. about the Roman Empire)",
+	"Subtitle (e.g. about the Roman Empire)": "(e.g. about the Roman Empire) الترجمة",
 	"Success": "نجاح",
-	"Successfully updated.": "تم التحديث بنجاح.",
-	"Suggested": "",
+	"Successfully updated.": "تم التحديث بنجاح",
+	"Suggested": "مقترحات",
 	"Sync All": "مزامنة الكل",
 	"System": "النظام",
 	"System Prompt": "محادثة النظام",
@@ -447,33 +448,33 @@
 	"Thorough explanation": "شرح شامل",
 	"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 (e.g. Tell me a fun fact)": "(e.g. Tell me a fun fact) العناون",
 	"Title Auto-Generation": "توليد تلقائي للعنوان",
-	"Title cannot be an empty string.": "",
+	"Title cannot be an empty string.": "العنوان مطلوب",
 	"Title Generation Prompt": "موجه إنشاء العنوان",
 	"to": "الى",
 	"To access the available model names for downloading,": "للوصول إلى أسماء الموديلات المتاحة للتنزيل،",
 	"To access the GGUF models available for downloading,": "للوصول إلى الموديلات GGUF المتاحة للتنزيل،",
 	"to chat input.": "الى كتابة المحادثه",
-	"Today": "",
+	"Today": "اليوم",
 	"Toggle settings": "فتح وأغلاق الاعدادات",
 	"Toggle sidebar": "فتح وأغلاق الشريط الجانبي",
 	"Top K": "Top K",
 	"Top P": "Top P",
-	"Trouble accessing Ollama?": "هل تواجه مشكلة في الوصول إلى Olma؟",
+	"Trouble accessing Ollama?": "هل تواجه مشكلة في الوصول",
 	"TTS Settings": "TTS اعدادات",
 	"Type Hugging Face Resolve (Download) URL": "اكتب عنوان URL لحل مشكلة الوجه (تنزيل).",
-	"Uh-oh! There was an issue connecting to {{provider}}.": "خطاء أوه! حدثت مشكلة في الاتصال بـ {{provider}}.",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "{{provider}}خطاء أوه! حدثت مشكلة في الاتصال بـ ",
 	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "نوع ملف غير معروف '{{file_type}}', ولكن القبول والتعامل كنص عادي ",
 	"Update and Copy Link": "تحديث ونسخ الرابط",
 	"Update password": "تحديث كلمة المرور",
-	"Upload a GGUF model": "رفع موديل نوع GGUF",
+	"Upload a GGUF model": "GGUF رفع موديل نوع",
 	"Upload files": "رفع الملفات",
 	"Upload Progress": "جاري التحميل",
 	"URL Mode": "رابط الموديل",
 	"Use '#' in the prompt input to load and select your documents.": "أستخدم '#' في المحادثة لربطهامن المستندات",
-	"Use Gravatar": "أستخدم Gravatar",
-	"Use Initials": "أستخدم Initials",
+	"Use Gravatar": "Gravatar أستخدم",
+	"Use Initials": "Initials أستخدم",
 	"user": "مستخدم",
 	"User Permissions": "صلاحيات المستخدم",
 	"Users": "المستخدمين",
@@ -493,13 +494,14 @@
 	"What’s New in": "ما هو الجديد",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "عند إيقاف تشغيل السجل، لن تظهر الدردشات الجديدة على هذا المتصفح في سجلك على أي من أجهزتك.",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "اكتب اقتراحًا سريعًا (على سبيل المثال، من أنت؟)",
-	"Write a summary in 50 words that summarizes [topic or keyword].": "اكتب ملخصًا في 50 كلمة يلخص [الموضوع أو الكلمة الرئيسية].",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "اكتب ملخصًا في 50 كلمة يلخص [الموضوع أو الكلمة الرئيسية]",
 	"Yes": "",
-	"Yesterday": "",
-	"You": "أنت",
-	"You have no archived conversations.": "",
-	"You have shared this chat": "",
+	"Yesterday": "أمس",
+	"You": "",
+	"You have no archived conversations.": "لا تملك محادثات محفوظه",
+	"You have shared this chat": "تم مشاركة هذه المحادثة",
 	"You're a helpful assistant.": "مساعدك المفيد هنا",
 	"You're now logged in.": "لقد قمت الآن بتسجيل الدخول.",
 	"Youtube": "Youtube",

+ 11 - 9
src/lib/i18n/locales/bg-BG/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Режим на Създаване",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Отказ",
 	"Categories": "Категории",
 	"Change Password": "Промяна на Парола",
 	"Chat": "Чат",
+	"Chat Bubble UI": "",
 	"Chat History": "Чат История",
 	"Chat History is off for this browser.": "Чат История е изключен за този браузър.",
 	"Chats": "Чатове",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "Chunk Overlap",
 	"Chunk Params": "Chunk Params",
 	"Chunk Size": "Chunk Size",
-	"Citation": "",
+	"Citation": "Цитат",
 	"Click here for help.": "Натиснете тук за помощ.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Натиснете тук за проверка на други моделфайлове.",
@@ -118,7 +120,6 @@
 	"Dark": "Тъмен",
 	"Dashboard": "",
 	"Database": "База данни",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "По подразбиране",
 	"Default (Automatic1111)": "По подразбиране (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Основни Настройки",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Здравей, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Max Tokens",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 модели могат да бъдат сваляни едновременно. Моля, опитайте отново по-късно.",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Модфайлове",
 	"Models": "Модели",
 	"More": "",
-	"My Documents": "Мои документи",
-	"My Modelfiles": "Мои модфайлове",
-	"My Prompts": "Мои промптове",
 	"Name": "Име",
 	"Name Tag": "Име Таг",
 	"Name your modelfile": "Име на модфайла",
@@ -295,7 +295,7 @@
 	"New Password": "Нова парола",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "Няма наличен източник",
 	"Not factually correct": "",
 	"Not sure what to add?": "Не сте сигурни, какво да добавите?",
 	"Not sure what to write? Switch to": "Не сте сигурни, какво да напишете? Превключете към",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "PDF Extract Images (OCR)",
 	"pending": "в очакване",
 	"Permission denied when accessing microphone: {{error}}": "Permission denied when accessing microphone: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Плейграунд",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Запис",
 	"Save & Create": "Запис & Създаване",
-	"Save & Submit": "Запис & Изпращане",
 	"Save & Update": "Запис & Актуализиране",
 	"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": "Запазването на чат логове директно в хранилището на вашия браузър вече не се поддържа. Моля, отделете малко време, за да изтеглите и изтриете чат логовете си, като щракнете върху бутона по-долу. Не се притеснявайте, можете лесно да импортирате отново чат логовете си в бекенда чрез",
 	"Scan": "Сканиране",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Изберете Ollama инстанция",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Изпращане на Съобщение",
 	"Send message": "Изпращане на съобщение",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Изход",
 	"Sign up": "Регистрация",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Източник",
 	"Speech recognition error: {{error}}": "Speech recognition error: {{error}}",
 	"Speech-to-Text Engine": "Speech-to-Text Engine",
 	"SpeechRecognition API is not supported in this browser.": "SpeechRecognition API is not supported in this browser.",
@@ -493,11 +494,12 @@
 	"What’s New in": "Какво е новото в",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Когато историята е изключена, нови чатове в този браузър ще не се показват в историята на никои от вашия профил.",
 	"Whisper (Local)": "Whisper (Локален)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напиши предложение за промпт (напр. Кой сте вие?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напиши описание в 50 знака, което описва [тема или ключова дума].",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "Вие",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Вие сте полезен асистент.",

+ 11 - 9
src/lib/i18n/locales/bn-BD/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "বিল্ডার মোড",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "বাতিল",
 	"Categories": "ক্যাটাগরিসমূহ",
 	"Change Password": "পাসওয়ার্ড পরিবর্তন করুন",
 	"Chat": "চ্যাট",
+	"Chat Bubble UI": "",
 	"Chat History": "চ্যাট হিস্টোরি",
 	"Chat History is off for this browser.": "এই ব্রাউজারের জন্য চ্যাট হিস্টোরি বন্ধ আছে",
 	"Chats": "চ্যাটসমূহ",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "চাঙ্ক ওভারল্যাপ",
 	"Chunk Params": "চাঙ্ক প্যারামিটার্স",
 	"Chunk Size": "চাঙ্ক সাইজ",
-	"Citation": "",
+	"Citation": "উদ্ধৃতি",
 	"Click here for help.": "সাহায্যের জন্য এখানে ক্লিক করুন",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "অন্যান্য মডেলফাইল চেক করার জন্য এখানে ক্লিক করুন",
@@ -118,7 +120,6 @@
 	"Dark": "ডার্ক",
 	"Dashboard": "",
 	"Database": "ডেটাবেজ",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "ডিফল্ট",
 	"Default (Automatic1111)": "ডিফল্ট (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "সাধারণ সেটিংসমূহ",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "হ্যালো, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "সর্বোচ্চ টোকন",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "একসঙ্গে সর্বোচ্চ তিনটি মডেল ডাউনলোড করা যায়। দয়া করে পরে আবার চেষ্টা করুন।",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "মডেলফাইলসমূহ",
 	"Models": "মডেলসমূহ",
 	"More": "",
-	"My Documents": "আমার ডকুমেন্টসমূহ",
-	"My Modelfiles": "আমার মডেলফাইলসমূহ",
-	"My Prompts": "আমার প্রম্পটসমূহ",
 	"Name": "নাম",
 	"Name Tag": "নামের ট্যাগ",
 	"Name your modelfile": "আপনার মডেলফাইলের নাম দিন",
@@ -295,7 +295,7 @@
 	"New Password": "নতুন পাসওয়ার্ড",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "কোন উৎস পাওয়া যায়নি",
 	"Not factually correct": "",
 	"Not sure what to add?": "কী যুক্ত করতে হবে নিশ্চিত না?",
 	"Not sure what to write? Switch to": "কী লিখতে হবে নিশ্চিত না? পরিবর্তন করুন:",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "পিডিএফ এর ছবি থেকে লেখা বের করুন (OCR)",
 	"pending": "অপেক্ষমান",
 	"Permission denied when accessing microphone: {{error}}": "মাইক্রোফোন ব্যবহারের অনুমতি পাওয়া যায়নি: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "খেলাঘর",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "ভোরের রোজ পাইন",
 	"Save": "সংরক্ষণ",
 	"Save & Create": "সংরক্ষণ এবং তৈরি করুন",
-	"Save & Submit": "সংরক্ষণ এবং সাবমিট করুন",
 	"Save & Update": "সংরক্ষণ এবং আপডেট করুন",
 	"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": "মাধ্যমে",
 	"Scan": "স্ক্যান",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "একটি Ollama ইন্সট্যান্স নির্বাচন করুন",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "একটি মেসেজ পাঠান",
 	"Send message": "মেসেজ পাঠান",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "সাইন আউট",
 	"Sign up": "সাইন আপ",
 	"Signing in": "",
-	"Source": "",
+	"Source": "উৎস",
 	"Speech recognition error: {{error}}": "স্পিচ রিকগনিশনে সমস্যা: {{error}}",
 	"Speech-to-Text Engine": "স্পিচ-টু-টেক্সট ইঞ্জিন",
 	"SpeechRecognition API is not supported in this browser.": "এই ব্রাউজার স্পিচরিকগনিশন এপিআই সাপোর্ট করে না।",
@@ -493,11 +494,12 @@
 	"What’s New in": "এতে নতুন কী",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "যদি হিস্টোরি বন্ধ থাকে তাহলে এই ব্রাউজারের নতুন চ্যাটগুলো আপনার কোন ডিভাইসের হিস্টোরিতেই দেখা যাবে না।",
 	"Whisper (Local)": "Whisper (লোকাল)",
+	"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] এর একটি সারসংক্ষেপ লিখুন।",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "আপনি",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "আপনি একজন উপকারী এসিস্ট্যান্ট",

+ 11 - 9
src/lib/i18n/locales/ca-ES/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Mode Constructor",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Cancel·la",
 	"Categories": "Categories",
 	"Change Password": "Canvia la Contrasenya",
 	"Chat": "Xat",
+	"Chat Bubble UI": "",
 	"Chat History": "Històric del Xat",
 	"Chat History is off for this browser.": "L'historial de xat està desactivat per a aquest navegador.",
 	"Chats": "Xats",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "Solapament de Blocs",
 	"Chunk Params": "Paràmetres de Blocs",
 	"Chunk Size": "Mida del Bloc",
-	"Citation": "",
+	"Citation": "Citació",
 	"Click here for help.": "Fes clic aquí per ajuda.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Fes clic aquí per comprovar altres fitxers de model.",
@@ -118,7 +120,6 @@
 	"Dark": "Fosc",
 	"Dashboard": "",
 	"Database": "Base de Dades",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "Per defecte",
 	"Default (Automatic1111)": "Per defecte (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Configuració General",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Hola, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Màxim de Tokens",
 	"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": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Fitxers de Model",
 	"Models": "Models",
 	"More": "",
-	"My Documents": "Els Meus Documents",
-	"My Modelfiles": "Els Meus Fitxers de Model",
-	"My Prompts": "Els Meus Prompts",
 	"Name": "Nom",
 	"Name Tag": "Etiqueta de Nom",
 	"Name your modelfile": "Nomena el teu fitxer de model",
@@ -295,7 +295,7 @@
 	"New Password": "Nova Contrasenya",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "Sense font disponible",
 	"Not factually correct": "",
 	"Not sure what to add?": "No estàs segur del que afegir?",
 	"Not sure what to write? Switch to": "No estàs segur del que escriure? Canvia a",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "Extreu Imatges de PDF (OCR)",
 	"pending": "pendent",
 	"Permission denied when accessing microphone: {{error}}": "Permís denegat en accedir al micròfon: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Zona de Jocs",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Albada Rosé Pine",
 	"Save": "Guarda",
 	"Save & Create": "Guarda i Crea",
-	"Save & Submit": "Guarda i Envia",
 	"Save & Update": "Guarda i Actualitza",
 	"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 registres de xat directament a l'emmagatzematge del teu navegador ja no és suportat. Si us plau, pren un moment per descarregar i eliminar els teus registres de xat fent clic al botó de sota. No et preocupis, pots reimportar fàcilment els teus registres de xat al backend a través de",
 	"Scan": "Escaneja",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Selecciona una instància d'Ollama",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Envia un Missatge",
 	"Send message": "Envia missatge",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Tanca sessió",
 	"Sign up": "Registra't",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Font",
 	"Speech recognition error: {{error}}": "Error de reconeixement de veu: {{error}}",
 	"Speech-to-Text Engine": "Motor de Veu a Text",
 	"SpeechRecognition API is not supported in this browser.": "L'API de Reconèixer Veu no és compatible amb aquest navegador.",
@@ -493,11 +494,12 @@
 	"What’s New in": "Què hi ha de Nou en",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quan l'historial està desactivat, els nous xats en aquest navegador no apareixeran en el teu historial en cap dels teus dispositius.",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escriu una suggerència de prompt (p. ex. Qui ets tu?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escriu un resum en 50 paraules que resumeixi [tema o paraula clau].",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "Tu",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Ets un assistent útil.",

+ 8 - 6
src/lib/i18n/locales/de-DE/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "Schlechte Antwort",
 	"before": "bereits geteilt",
 	"Being lazy": "Faul sein",
+	"Beta": "",
 	"Builder Mode": "Builder Modus",
 	"Bypass SSL verification for Websites": "Bypass SSL-Verifizierung für Websites",
 	"Cancel": "Abbrechen",
 	"Categories": "Kategorien",
 	"Change Password": "Passwort ändern",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Chat Verlauf",
 	"Chat History is off for this browser.": "Chat Verlauf ist für diesen Browser ausgeschaltet.",
 	"Chats": "Chats",
@@ -118,7 +120,6 @@
 	"Dark": "Dunkel",
 	"Dashboard": "Dashboard",
 	"Database": "Datenbank",
-	"DD/MM/YYYY HH:mm": "DD.MM.YYYY HH:mm",
 	"December": "Dezember",
 	"Default": "Standard",
 	"Default (Automatic1111)": "Standard (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Allgemeine Einstellungen",
 	"Generation Info": "Generierungsinformationen",
 	"Good Response": "Gute Antwort",
+	"h:mm a": "",
 	"has no conversations.": "hat keine Unterhaltungen.",
 	"Hello, {{name}}": "Hallo, {{name}}",
 	"Help": "Hilfe",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Maximale Tokens",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es können maximal 3 Modelle gleichzeitig heruntergeladen werden. Bitte versuche es später erneut.",
 	"May": "Mai",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "Fortlaudende Nachrichten in diesem Chat werden nicht automatisch geteilt. Benutzer mit dem Link können den Chat einsehen.",
 	"Minimum Score": "Mindestscore",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Modelfiles",
 	"Models": "Modelle",
 	"More": "Mehr",
-	"My Documents": "Meine Dokumente",
-	"My Modelfiles": "Meine Modelfiles",
-	"My Prompts": "Meine Prompts",
 	"Name": "Name",
 	"Name Tag": "Namens-Tag",
 	"Name your modelfile": "Benenne dein modelfile",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "Text von Bildern aus PDFs extrahieren (OCR)",
 	"pending": "ausstehend",
 	"Permission denied when accessing microphone: {{error}}": "Zugriff auf das Mikrofon verweigert: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "Nur Text (.txt)",
 	"Playground": "Testumgebung",
 	"Positive attitude": "Positive Einstellung",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Speichern",
 	"Save & Create": "Speichern und erstellen",
-	"Save & Submit": "Speichern und senden",
 	"Save & Update": "Speichern und aktualisieren",
 	"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 Chat-Protokollen im Browser-Speicher wird nicht mehr unterstützt. Bitte nimm dir einen Moment Zeit, um deine Chat-Protokolle herunterzuladen und zu löschen, indem du auf die Schaltfläche unten klickst. Keine Sorge, du kannst deine Chat-Protokolle problemlos über das Backend wieder importieren.",
 	"Scan": "Scannen",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Eine Ollama Instanz auswählen",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Eine Nachricht senden",
 	"Send message": "Nachricht senden",
 	"September": "September",
@@ -493,11 +494,12 @@
 	"What’s New in": "Was gibt's Neues in",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wenn die Historie ausgeschaltet ist, werden neue Chats nicht in deiner Historie auf deine Geräte angezeigt.",
 	"Whisper (Local)": "Whisper (Lokal)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z.B. Wer bist du?)",
 	"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.",
 	"Yes": "",
 	"Yesterday": "Gestern",
-	"You": "Du",
+	"You": "",
 	"You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.",
 	"You have shared this chat": "Du hast diesen Chat",
 	"You're a helpful assistant.": "Du bist ein hilfreicher Assistent.",

+ 10 - 8
src/lib/i18n/locales/dg-DG/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Builder Mode",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Cancel",
 	"Categories": "Categories",
 	"Change Password": "Change Password",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Chat History",
 	"Chat History is off for this browser.": "Chat History off for this browser. Such sadness.",
 	"Chats": "Chats",
@@ -118,7 +120,6 @@
 	"Dark": "Dark",
 	"Dashboard": "",
 	"Database": "Database",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "Default",
 	"Default (Automatic1111)": "Default (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "General Doge Settings",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Much helo, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Max Tokens",
 	"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": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Modelfiles",
 	"Models": "Wowdels",
 	"More": "",
-	"My Documents": "My Doguments",
-	"My Modelfiles": "My Modelfiles",
-	"My Prompts": "My Promptos",
 	"Name": "Name",
 	"Name Tag": "Name Tag",
 	"Name your modelfile": "Name your modelfile",
@@ -295,7 +295,7 @@
 	"New Password": "New Barkword",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "No source available",
 	"Not factually correct": "",
 	"Not sure what to add?": "Not sure what to add?",
 	"Not sure what to write? Switch to": "Not sure what to write? Switch to",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "PDF Extract Wowmages (OCR)",
 	"pending": "pending",
 	"Permission denied when accessing microphone: {{error}}": "Permission denied when accessing microphone: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Playground",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Save much wow",
 	"Save & Create": "Save & Create much create",
-	"Save & Submit": "Save & Submit very submit",
 	"Save & Update": "Save & Update much update",
 	"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",
 	"Scan": "Scan much scan",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Select an Ollama instance very choose",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Send a Message much message",
 	"Send message": "Send message very send",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Sign Out much logout",
 	"Sign up": "Sign up much join",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Source",
 	"Speech recognition error: {{error}}": "Speech recognition error: {{error}} so error",
 	"Speech-to-Text Engine": "Speech-to-Text Engine much speak",
 	"SpeechRecognition API is not supported in this browser.": "SpeechRecognition API is not supported in this browser. Much sad.",
@@ -493,11 +494,12 @@
 	"What’s New in": "What’s New in much new",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "When history is turned off, new chats on this browser won't appear in your history on any of your devices. Much history.",
 	"Whisper (Local)": "Whisper (Local) much whisper",
+	"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.",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "You very you",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "You're a helpful assistant. Much helpful.",

+ 7 - 5
src/lib/i18n/locales/en-GB/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "",
 	"Categories": "",
 	"Change Password": "",
 	"Chat": "",
+	"Chat Bubble UI": "",
 	"Chat History": "",
 	"Chat History is off for this browser.": "",
 	"Chats": "",
@@ -118,7 +120,6 @@
 	"Dark": "",
 	"Dashboard": "",
 	"Database": "",
-	"DD/MM/YYYY HH:mm": "",
 	"December": "",
 	"Default": "",
 	"Default (Automatic1111)": "",
@@ -213,6 +214,7 @@
 	"General Settings": "",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "",
@@ -285,9 +288,6 @@
 	"Modelfiles": "",
 	"Models": "",
 	"More": "",
-	"My Documents": "",
-	"My Modelfiles": "",
-	"My Prompts": "",
 	"Name": "",
 	"Name Tag": "",
 	"Name your modelfile": "",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "",
 	"pending": "",
 	"Permission denied when accessing microphone: {{error}}": "",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "",
 	"Save": "",
 	"Save & Create": "",
-	"Save & Submit": "",
 	"Save & Update": "",
 	"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": "",
 	"Scan": "",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "",
 	"Send message": "",
 	"September": "",
@@ -493,6 +494,7 @@
 	"What’s New in": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "",
 	"Whisper (Local)": "",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
 	"Yes": "",

+ 7 - 5
src/lib/i18n/locales/en-US/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "",
 	"Categories": "",
 	"Change Password": "",
 	"Chat": "",
+	"Chat Bubble UI": "",
 	"Chat History": "",
 	"Chat History is off for this browser.": "",
 	"Chats": "",
@@ -118,7 +120,6 @@
 	"Dark": "",
 	"Dashboard": "",
 	"Database": "",
-	"DD/MM/YYYY HH:mm": "",
 	"December": "",
 	"Default": "",
 	"Default (Automatic1111)": "",
@@ -213,6 +214,7 @@
 	"General Settings": "",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "",
@@ -285,9 +288,6 @@
 	"Modelfiles": "",
 	"Models": "",
 	"More": "",
-	"My Documents": "",
-	"My Modelfiles": "",
-	"My Prompts": "",
 	"Name": "",
 	"Name Tag": "",
 	"Name your modelfile": "",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "",
 	"pending": "",
 	"Permission denied when accessing microphone: {{error}}": "",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "",
 	"Save": "",
 	"Save & Create": "",
-	"Save & Submit": "",
 	"Save & Update": "",
 	"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": "",
 	"Scan": "",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "",
 	"Send message": "",
 	"September": "",
@@ -493,6 +494,7 @@
 	"What’s New in": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "",
 	"Whisper (Local)": "",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
 	"Yes": "",

+ 11 - 9
src/lib/i18n/locales/es-ES/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Modo de Constructor",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Cancelar",
 	"Categories": "Categorías",
 	"Change Password": "Cambia la Contraseña",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Historial del Chat",
 	"Chat History is off for this browser.": "El Historial del Chat está apagado para este navegador.",
 	"Chats": "Chats",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "Superposición de fragmentos",
 	"Chunk Params": "Parámetros de fragmentos",
 	"Chunk Size": "Tamaño de fragmentos",
-	"Citation": "",
+	"Citation": "Cita",
 	"Click here for help.": "Presiona aquí para obtener ayuda.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Presiona aquí para consultar otros modelfiles.",
@@ -118,7 +120,6 @@
 	"Dark": "Oscuro",
 	"Dashboard": "",
 	"Database": "Base de datos",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "Por defecto",
 	"Default (Automatic1111)": "Por defecto (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Opciones Generales",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Hola, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Máximo de Tokens",
 	"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": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Modelfiles",
 	"Models": "Modelos",
 	"More": "",
-	"My Documents": "Mis Documentos",
-	"My Modelfiles": "Mis Modelfiles",
-	"My Prompts": "Mis Prompts",
 	"Name": "Nombre",
 	"Name Tag": "Nombre de etiqueta",
 	"Name your modelfile": "Nombra tu modelfile",
@@ -295,7 +295,7 @@
 	"New Password": "Nueva Contraseña",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "No hay fuente disponible",
 	"Not factually correct": "",
 	"Not sure what to add?": "¿No sabes qué añadir?",
 	"Not sure what to write? Switch to": "¿No sabes qué escribir? Cambia a",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "Extraer imágenes de PDF (OCR)",
 	"pending": "pendiente",
 	"Permission denied when accessing microphone: {{error}}": "Permiso denegado al acceder al micrófono: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Patio de juegos",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Guardar",
 	"Save & Create": "Guardar y Crear",
-	"Save & Submit": "Guardar y Enviar",
 	"Save & Update": "Guardar y Actualizar",
 	"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",
 	"Scan": "Escanear",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Seleccione una instancia de Ollama",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Enviar un Mensaje",
 	"Send message": "Enviar Mensaje",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Cerrar sesión",
 	"Sign up": "Crear una cuenta",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Fuente",
 	"Speech recognition error: {{error}}": "Error de reconocimiento de voz: {{error}}",
 	"Speech-to-Text Engine": "Motor de voz a texto",
 	"SpeechRecognition API is not supported in this browser.": "La API SpeechRecognition no es compatible con este navegador.",
@@ -493,11 +494,12 @@
 	"What’s New in": "Novedades en",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Cuando el historial está desactivado, los nuevos chats en este navegador no aparecerán en el historial de ninguno de sus dispositivos..",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"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].",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "Usted",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Eres un asistente útil.",

+ 11 - 9
src/lib/i18n/locales/fa-IR/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "حالت سازنده",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "لغو",
 	"Categories": "دسته\u200cبندی\u200cها",
 	"Change Password": "تغییر رمز عبور",
 	"Chat": "گپ",
+	"Chat Bubble UI": "",
 	"Chat History": "تاریخچه\u200cی گفتگو",
 	"Chat History is off for this browser.": "سابقه گپ برای این مرورگر خاموش است.",
 	"Chats": "گپ\u200cها",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "همپوشانی تکه",
 	"Chunk Params": "پارامترهای تکه",
 	"Chunk Size": "اندازه تکه",
-	"Citation": "",
+	"Citation": "استناد",
 	"Click here for help.": "برای کمک اینجا را کلیک کنید.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "برای بررسی سایر فایل\u200cهای مدل اینجا را کلیک کنید.",
@@ -118,7 +120,6 @@
 	"Dark": "تیره",
 	"Dashboard": "",
 	"Database": "پایگاه داده",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "پیشفرض",
 	"Default (Automatic1111)": "پیشفرض (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "تنظیمات عمومی",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "سلام، {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "حداکثر توکن",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "حداکثر 3 مدل را می توان به طور همزمان دانلود کرد. لطفاً بعداً دوباره امتحان کنید.",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "فایل\u200cهای مدل",
 	"Models": "مدل\u200cها",
 	"More": "",
-	"My Documents": "اسناد من",
-	"My Modelfiles": "فایل\u200cهای مدل من",
-	"My Prompts": "پرامپت\u200cهای من",
 	"Name": "نام",
 	"Name Tag": "نام تگ",
 	"Name your modelfile": "فایل مدل را نام\u200cگذاری کنید",
@@ -295,7 +295,7 @@
 	"New Password": "رمز عبور جدید",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "منبعی در دسترس نیست",
 	"Not factually correct": "",
 	"Not sure what to add?": "مطمئن نیستید چه چیزی را اضافه کنید؟",
 	"Not sure what to write? Switch to": "مطمئن نیستید چه بنویسید؟ تغییر به",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "استخراج تصاویر از PDF (OCR)",
 	"pending": "در انتظار",
 	"Permission denied when accessing microphone: {{error}}": "هنگام دسترسی به میکروفون، اجازه داده نشد: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "زمین بازی",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "ذخیره",
 	"Save & Create": "ذخیره و ایجاد",
-	"Save & Submit": "ذخیره و ارسال",
 	"Save & Update": "ذخیره و به\u200cروزرسانی",
 	"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شود. لطفاً با کلیک بر روی دکمه زیر، چند لحظه برای دانلود و حذف گزارش های چت خود وقت بگذارید. نگران نباشید، شما به راحتی می توانید گزارش های چت خود را از طریق بکند دوباره وارد کنید",
 	"Scan": "اسکن",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "انتخاب یک نمونه از اولاما",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "ارسال یک پیام",
 	"Send message": "ارسال پیام",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "خروج",
 	"Sign up": "ثبت نام",
 	"Signing in": "",
-	"Source": "",
+	"Source": "منبع",
 	"Speech recognition error: {{error}}": "خطای تشخیص گفتار: {{error}}",
 	"Speech-to-Text Engine": "موتور گفتار به متن",
 	"SpeechRecognition API is not supported in this browser.": "API تشخیص گفتار در این مرورگر پشتیبانی نمی شود.",
@@ -493,11 +494,12 @@
 	"What’s New in": "موارد جدید در",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "وقتی سابقه خاموش است، چت\u200cهای جدید در این مرورگر در سابقه شما در هیچ یک از دستگاه\u200cهایتان ظاهر نمی\u200cشوند.",
 	"Whisper (Local)": "ویسپر (محلی)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "یک پیشنهاد پرامپت بنویسید (مثلاً شما کی هستید؟)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "خلاصه ای در 50 کلمه بنویسید که [موضوع یا کلمه کلیدی] را خلاصه کند.",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "شما",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "تو یک دستیار سودمند هستی.",

+ 8 - 6
src/lib/i18n/locales/fi-FI/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "Epäkelpo vastaus",
 	"before": "ennen",
 	"Being lazy": "Oli laiska",
+	"Beta": "",
 	"Builder Mode": "Rakentajan tila",
 	"Bypass SSL verification for Websites": "Ohita SSL-varmennus verkkosivustoille",
 	"Cancel": "Peruuta",
 	"Categories": "Kategoriat",
 	"Change Password": "Vaihda salasana",
 	"Chat": "Keskustelu",
+	"Chat Bubble UI": "",
 	"Chat History": "Keskusteluhistoria",
 	"Chat History is off for this browser.": "Keskusteluhistoria on pois päältä tällä selaimella.",
 	"Chats": "Keskustelut",
@@ -118,7 +120,6 @@
 	"Dark": "Tumma",
 	"Dashboard": "Kojelauta",
 	"Database": "Tietokanta",
-	"DD/MM/YYYY HH:mm": "DD.MM.YYYY HH:mm",
 	"December": "joulukuu",
 	"Default": "Oletus",
 	"Default (Automatic1111)": "Oletus (AUTOMATIC1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Yleisasetukset",
 	"Generation Info": "Generointitiedot",
 	"Good Response": "Hyvä vastaus",
+	"h:mm a": "",
 	"has no conversations.": "ei ole keskusteluja.",
 	"Hello, {{name}}": "Terve, {{name}}",
 	"Help": "Apua",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Maksimitokenit",
 	"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",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "Viestejä, jotka lähetät luotuasi linkin, ei jaeta. Käyttäjät, joilla on tämä osoite voivat tarkastella jaettua keskustelua.",
 	"Minimum Score": "Vähimmäispisteet",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Mallitiedostot",
 	"Models": "Mallit",
 	"More": "Lisää",
-	"My Documents": "Omat asiakirjat",
-	"My Modelfiles": "Omat mallitiedostot",
-	"My Prompts": "Omat kehotteet",
 	"Name": "Nimi",
 	"Name Tag": "Nimitagi",
 	"Name your modelfile": "Nimeä mallitiedostosi",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "PDF-tiedoston kuvien erottelu (OCR)",
 	"pending": "odottaa",
 	"Permission denied when accessing microphone: {{error}}": "Mikrofonin käyttöoikeus evätty: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "Pelkkä teksti (.txt)",
 	"Playground": "Leikkipaikka",
 	"Positive attitude": "Positiivinen asenne",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Aamuinen Rosee-mänty",
 	"Save": "Tallenna",
 	"Save & Create": "Tallenna ja luo",
-	"Save & Submit": "Tallenna ja lähetä",
 	"Save & Update": "Tallenna ja päivitä",
 	"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",
 	"Scan": "Skannaa",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Valitse Ollama-instanssi",
 	"Select model": "Valitse malli",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Lähetä viesti",
 	"Send message": "Lähetä viesti",
 	"September": "syyskuu",
@@ -493,11 +494,12 @@
 	"What’s New in": "Mitä uutta",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kun historia on pois päältä, uudet keskustelut tässä selaimessa eivät näy historiassasi millään laitteellasi.",
 	"Whisper (Local)": "Whisper (paikallinen)",
+	"Workspace": "",
 	"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].",
 	"Yes": "",
 	"Yesterday": "Eilen",
-	"You": "Sinä",
+	"You": "",
 	"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.",

+ 11 - 9
src/lib/i18n/locales/fr-CA/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Mode Constructeur",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Annuler",
 	"Categories": "Catégories",
 	"Change Password": "Changer le mot de passe",
 	"Chat": "Discussion",
+	"Chat Bubble UI": "",
 	"Chat History": "Historique des discussions",
 	"Chat History is off for this browser.": "L'historique des discussions est désactivé pour ce navigateur.",
 	"Chats": "Discussions",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "Chevauchement de bloc",
 	"Chunk Params": "Paramètres de bloc",
 	"Chunk Size": "Taille de bloc",
-	"Citation": "",
+	"Citation": "Citations",
 	"Click here for help.": "Cliquez ici pour de l'aide.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Cliquez ici pour vérifier d'autres fichiers de modèle.",
@@ -118,7 +120,6 @@
 	"Dark": "Sombre",
 	"Dashboard": "",
 	"Database": "Base de données",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "Par défaut",
 	"Default (Automatic1111)": "Par défaut (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Paramètres généraux",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Bonjour, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Tokens maximaux",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Un maximum de 3 modèles peut être téléchargé simultanément. Veuillez réessayer plus tard.",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Fichiers de modèle",
 	"Models": "Modèles",
 	"More": "",
-	"My Documents": "Mes documents",
-	"My Modelfiles": "Mes fichiers de modèle",
-	"My Prompts": "Mes prompts",
 	"Name": "Nom",
 	"Name Tag": "Tag de nom",
 	"Name your modelfile": "Nommez votre fichier de modèle",
@@ -295,7 +295,7 @@
 	"New Password": "Nouveau mot de passe",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "Aucune source disponible",
 	"Not factually correct": "",
 	"Not sure what to add?": "Pas sûr de quoi ajouter ?",
 	"Not sure what to write? Switch to": "Pas sûr de quoi écrire ? Changez pour",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "Extraction d'images PDF (OCR)",
 	"pending": "en attente",
 	"Permission denied when accessing microphone: {{error}}": "Permission refusée lors de l'accès au microphone : {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Aire de jeu",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Aube Pin Rosé",
 	"Save": "Enregistrer",
 	"Save & Create": "Enregistrer & Créer",
-	"Save & Submit": "Enregistrer & Soumettre",
 	"Save & Update": "Enregistrer & Mettre à jour",
 	"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 moment pour télécharger et supprimer vos journaux de discussion en cliquant sur le bouton ci-dessous. Ne vous inquiétez pas, vous pouvez facilement réimporter vos journaux de discussion dans le backend via",
 	"Scan": "Scanner",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Sélectionner une instance Ollama",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Envoyer un message",
 	"Send message": "Envoyer un message",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Se déconnecter",
 	"Sign up": "S'inscrire",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Source",
 	"Speech recognition error: {{error}}": "Erreur de reconnaissance vocale : {{error}}",
 	"Speech-to-Text Engine": "Moteur reconnaissance vocale",
 	"SpeechRecognition API is not supported in this browser.": "L'API SpeechRecognition n'est pas prise en charge dans ce navigateur.",
@@ -493,11 +494,12 @@
 	"What’s New in": "Quoi de neuf dans",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Lorsque l'historique est désactivé, les nouvelles discussions sur ce navigateur n'apparaîtront pas dans votre historique sur aucun de vos appareils.",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Rédigez une suggestion de prompt (p. ex. Qui êtes-vous ?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Rédigez un résumé en 50 mots qui résume [sujet ou mot-clé].",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "You",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Vous êtes un assistant utile",

+ 11 - 9
src/lib/i18n/locales/fr-FR/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Mode Constructeur",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Annuler",
 	"Categories": "Catégories",
 	"Change Password": "Changer le mot de passe",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Historique du chat",
 	"Chat History is off for this browser.": "L'historique du chat est désactivé pour ce navigateur.",
 	"Chats": "Chats",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "Chevauchement de bloc",
 	"Chunk Params": "Paramètres de bloc",
 	"Chunk Size": "Taille de bloc",
-	"Citation": "",
+	"Citation": "Citations",
 	"Click here for help.": "Cliquez ici pour de l'aide.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Cliquez ici pour vérifier d'autres fichiers de modèle.",
@@ -118,7 +120,6 @@
 	"Dark": "Sombre",
 	"Dashboard": "",
 	"Database": "Base de données",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "Par défaut",
 	"Default (Automatic1111)": "Par défaut (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Paramètres généraux",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Bonjour, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Tokens maximaux",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Un maximum de 3 modèles peut être téléchargé simultanément. Veuillez réessayer plus tard.",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Fichiers de modèle",
 	"Models": "Modèles",
 	"More": "",
-	"My Documents": "Mes documents",
-	"My Modelfiles": "Mes fichiers de modèle",
-	"My Prompts": "Mes prompts",
 	"Name": "Nom",
 	"Name Tag": "Tag de nom",
 	"Name your modelfile": "Nommez votre fichier de modèle",
@@ -295,7 +295,7 @@
 	"New Password": "Nouveau mot de passe",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "Aucune source disponible",
 	"Not factually correct": "",
 	"Not sure what to add?": "Vous ne savez pas quoi ajouter ?",
 	"Not sure what to write? Switch to": "Vous ne savez pas quoi écrire ? Basculer vers",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "Extraction d'images PDF (OCR)",
 	"pending": "en attente",
 	"Permission denied when accessing microphone: {{error}}": "Permission refusée lors de l'accès au microphone : {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Aire de jeu",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Aube Pin Rosé",
 	"Save": "Enregistrer",
 	"Save & Create": "Enregistrer & Créer",
-	"Save & Submit": "Enregistrer & Soumettre",
 	"Save & Update": "Enregistrer & Mettre à jour",
 	"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 chat directement dans le stockage de votre navigateur n'est plus prise en charge. Veuillez prendre un moment pour télécharger et supprimer vos journaux de chat en cliquant sur le bouton ci-dessous. Ne vous inquiétez pas, vous pouvez facilement importer vos sauvegardes de chat via",
 	"Scan": "Scanner",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Sélectionner une instance Ollama",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Envoyer un message",
 	"Send message": "Envoyer un message",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Se déconnecter",
 	"Sign up": "S'inscrire",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Source",
 	"Speech recognition error: {{error}}": "Erreur de reconnaissance vocale : {{error}}",
 	"Speech-to-Text Engine": "Moteur de reconnaissance vocale",
 	"SpeechRecognition API is not supported in this browser.": "L'API SpeechRecognition n'est pas prise en charge dans ce navigateur.",
@@ -493,11 +494,12 @@
 	"What’s New in": "Quoi de neuf dans",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Lorsque l'historique est désactivé, les nouveaux chats sur ce navigateur n'apparaîtront pas dans votre historique sur aucun de vos appareils.",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Écrivez un prompt (e.x. Qui est-tu ?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Ecrivez un résumé en 50 mots [sujet ou mot-clé]",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "You",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Vous êtes un assistant utile",

+ 509 - 0
src/lib/i18n/locales/he-IL/translation.json

@@ -0,0 +1,509 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' או '-1' ללא תפוגה.",
+	"(Beta)": "(בטא)",
+	"(e.g. `sh webui.sh --api`)": "(למשל `sh webui.sh --api`)",
+	"(latest)": "(האחרון)",
+	"{{modelName}} is thinking...": "{{modelName}} חושב...",
+	"{{user}}'s Chats": "צ'אטים של {{user}}",
+	"{{webUIName}} Backend Required": "נדרש Backend של {{webUIName}}",
+	"a user": "משתמש",
+	"About": "אודות",
+	"Account": "חשבון",
+	"Accurate information": "מידע מדויק",
+	"Add a model": "הוסף מודל",
+	"Add a model tag name": "הוסף שם תג למודל",
+	"Add a short description about what this modelfile does": "הוסף תיאור קצר על מה שהקובץ מודל עושה",
+	"Add a short title for this prompt": "הוסף כותרת קצרה לפקודה זו",
+	"Add a tag": "הוסף תג",
+	"Add custom prompt": "הוסף פקודה מותאמת אישית",
+	"Add Docs": "הוסף מסמכים",
+	"Add Files": "הוסף קבצים",
+	"Add message": "הוסף הודעה",
+	"Add Model": "הוסף מודל",
+	"Add Tags": "הוסף תגים",
+	"Add User": "הוסף משתמש",
+	"Adjusting these settings will apply changes universally to all users.": "התאמת הגדרות אלו תחול על כל המשתמשים.",
+	"admin": "מנהל",
+	"Admin Panel": "לוח בקרה למנהל",
+	"Admin Settings": "הגדרות מנהל",
+	"Advanced Parameters": "פרמטרים מתקדמים",
+	"all": "הכל",
+	"All Documents": "כל המסמכים",
+	"All Users": "כל המשתמשים",
+	"Allow": "אפשר",
+	"Allow Chat Deletion": "אפשר מחיקת צ'אט",
+	"alphanumeric characters and hyphens": "תווים אלפאנומריים ומקפים",
+	"Already have an account?": "כבר יש לך חשבון?",
+	"an assistant": "עוזר",
+	"and": "וגם",
+	"and create a new shared link.": "וצור קישור משותף חדש.",
+	"API Base URL": "כתובת URL בסיסית ל-API",
+	"API Key": "מפתח API",
+	"API Key created.": "מפתח API נוצר.",
+	"API keys": "מפתחות API",
+	"API RPM": "RPM של API",
+	"April": "אפריל",
+	"Archive": "ארכיון",
+	"Archived Chats": "צ'אטים מאורכבים",
+	"are allowed - Activate this command by typing": "מותרים - הפעל פקודה זו על ידי הקלדה",
+	"Are you sure?": "האם אתה בטוח?",
+	"Attach file": "צרף קובץ",
+	"Attention to detail": "תשומת לב לפרטים",
+	"Audio": "אודיו",
+	"August": "אוגוסט",
+	"Auto-playback response": "תגובת השמעה אוטומטית",
+	"Auto-send input after 3 sec.": "שליחת קלט אוטומטית אחרי 3 שניות",
+	"AUTOMATIC1111 Base URL": "כתובת URL בסיסית של AUTOMATIC1111",
+	"AUTOMATIC1111 Base URL is required.": "נדרשת כתובת URL בסיסית של AUTOMATIC1111",
+	"available!": "זמין!",
+	"Back": "חזור",
+	"Bad Response": "תגובה שגויה",
+	"before": "לפני",
+	"Being lazy": "להיות עצלן",
+	"Beta": "",
+	"Builder Mode": "מצב בונה",
+	"Bypass SSL verification for Websites": "עקוף אימות SSL עבור אתרים",
+	"Cancel": "בטל",
+	"Categories": "קטגוריות",
+	"Change Password": "שנה סיסמה",
+	"Chat": "צ'אט",
+	"Chat Bubble UI": "",
+	"Chat History": "היסטוריית צ'אט",
+	"Chat History is off for this browser.": "היסטוריית הצ'אט כבויה לדפדפן זה.",
+	"Chats": "צ'אטים",
+	"Check Again": "בדוק שוב",
+	"Check for updates": "בדוק עדכונים",
+	"Checking for updates...": "בודק עדכונים...",
+	"Choose a model before saving...": "בחר מודל לפני השמירה...",
+	"Chunk Overlap": "חפיפת נתונים",
+	"Chunk Params": "פרמטרי נתונים",
+	"Chunk Size": "גודל נתונים",
+	"Citation": "ציטוט",
+	"Click here for help.": "לחץ כאן לעזרה.",
+	"Click here to": "לחץ כאן כדי",
+	"Click here to check other modelfiles.": "לחץ כאן לבדיקת קבצי מודלים אחרים.",
+	"Click here to select": "לחץ כאן לבחירה",
+	"Click here to select a csv file.": "לחץ כאן לבחירת קובץ csv.",
+	"Click here to select documents.": "לחץ כאן לבחירת מסמכים.",
+	"click here.": "לחץ כאן.",
+	"Click on the user role button to change a user's role.": "לחץ על כפתור תפקיד המשתמש כדי לשנות את תפקיד המשתמש.",
+	"Close": "סגור",
+	"Collection": "אוסף",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "כתובת URL בסיסית של ComfyUI",
+	"ComfyUI Base URL is required.": "נדרשת כתובת URL בסיסית של ComfyUI",
+	"Command": "פקודה",
+	"Confirm Password": "אשר סיסמה",
+	"Connections": "חיבורים",
+	"Content": "תוכן",
+	"Context Length": "אורך הקשר",
+	"Continue Response": "המשך תגובה",
+	"Conversation Mode": "מצב שיחה",
+	"Copied shared chat URL to clipboard!": "העתקת כתובת URL של צ'אט משותף ללוח!",
+	"Copy": "העתק",
+	"Copy last code block": "העתק את בלוק הקוד האחרון",
+	"Copy last response": "העתק את התגובה האחרונה",
+	"Copy Link": "העתק קישור",
+	"Copying to clipboard was successful!": "ההעתקה ללוח הייתה מוצלחת!",
+	"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':": "צור ביטוי תמציתי של 3-5 מילים ככותרת לשאילתה הבאה, תוך שמירה מדויקת על מגבלת 3-5 המילים והימנעות משימוש במילה 'כותרת':",
+	"Create a modelfile": "צור קובץ מודל",
+	"Create Account": "צור חשבון",
+	"Create new key": "צור מפתח חדש",
+	"Create new secret key": "צור מפתח סודי חדש",
+	"Created at": "נוצר ב",
+	"Created At": "נוצר ב",
+	"Current Model": "המודל הנוכחי",
+	"Current Models": "",
+	"Current Password": "הסיסמה הנוכחית",
+	"Custom": "מותאם אישית",
+	"Customize Ollama models for a specific purpose": "התאמה אישית של מודלים של Ollama למטרה מסוימת",
+	"Dark": "כהה",
+	"Dashboard": "לוח בקרה",
+	"Database": "מסד נתונים",
+	"December": "דצמבר",
+	"Default": "ברירת מחדל",
+	"Default (Automatic1111)": "ברירת מחדל (Automatic1111)",
+	"Default (SentenceTransformers)": "ברירת מחדל (SentenceTransformers)",
+	"Default (Web API)": "ברירת מחדל (Web API)",
+	"Default model updated": "המודל המוגדר כברירת מחדל עודכן",
+	"Default Prompt Suggestions": "הצעות ברירת מחדל לפקודות",
+	"Default User Role": "תפקיד משתמש ברירת מחדל",
+	"delete": "מחק",
+	"Delete": "מחק",
+	"Delete a model": "מחק מודל",
+	"Delete chat": "מחק צ'אט",
+	"Delete Chat": "מחק צ'אט",
+	"Delete Chats": "מחק צ'אטים",
+	"delete this link": "מחק את הקישור הזה",
+	"Delete User": "מחק משתמש",
+	"Deleted {{deleteModelTag}}": "נמחק {{deleteModelTag}}",
+	"Deleted {{tagName}}": "נמחק {{tagName}}",
+	"Description": "תיאור",
+	"Didn't fully follow instructions": "לא עקב אחרי ההוראות באופן מלא",
+	"Disabled": "מושבת",
+	"Discover a modelfile": "גלה קובץ מודל",
+	"Discover a prompt": "גלה פקודה",
+	"Discover, download, and explore custom prompts": "גלה, הורד, וחקור פקודות מותאמות אישית",
+	"Discover, download, and explore model presets": "גלה, הורד, וחקור הגדרות מודל מוגדרות מראש",
+	"Display the username instead of You in the Chat": "הצג את שם המשתמש במקום 'אתה' בצ'אט",
+	"Document": "מסמך",
+	"Document Settings": "הגדרות מסמך",
+	"Documents": "מסמכים",
+	"does not make any external connections, and your data stays securely on your locally hosted server.": "לא מבצע חיבורים חיצוניים, והנתונים שלך נשמרים באופן מאובטח בשרת המקומי שלך.",
+	"Don't Allow": "אל תאפשר",
+	"Don't have an account?": "אין לך חשבון?",
+	"Don't like the style": "לא אוהב את הסגנון",
+	"Download": "הורד",
+	"Download canceled": "ההורדה בוטלה",
+	"Download Database": "הורד מסד נתונים",
+	"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 Doc": "ערוך מסמך",
+	"Edit User": "ערוך משתמש",
+	"Email": "דוא\"ל",
+	"Embedding Model": "מודל הטמעה",
+	"Embedding Model Engine": "מנוע מודל הטמעה",
+	"Embedding model set to \"{{embedding_model}}\"": "מודל ההטמעה הוגדר ל-\"{{embedding_model}}\"",
+	"Enable Chat History": "הפעל היסטוריית צ'אט",
+	"Enable New Sign Ups": "אפשר הרשמות חדשות",
+	"Enabled": "מופעל",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "ודא שקובץ ה-CSV שלך כולל 4 עמודות בסדר הבא: שם, דוא\"ל, סיסמה, תפקיד.",
+	"Enter {{role}} message here": "הזן הודעת {{role}} כאן",
+	"Enter Chunk Overlap": "הזן חפיפת נתונים",
+	"Enter Chunk Size": "הזן גודל נתונים",
+	"Enter Image Size (e.g. 512x512)": "הזן גודל תמונה (למשל 512x512)",
+	"Enter language codes": "הזן קודי שפה",
+	"Enter LiteLLM API Base URL (litellm_params.api_base)": "הזן כתובת URL בסיסית ל-API של LiteLLM (litellm_params.api_base)",
+	"Enter LiteLLM API Key (litellm_params.api_key)": "הזן מפתח API של LiteLLM (litellm_params.api_key)",
+	"Enter LiteLLM API RPM (litellm_params.rpm)": "הזן RPM של API של LiteLLM (litellm_params.rpm)",
+	"Enter LiteLLM Model (litellm_params.model)": "הזן מודל LiteLLM (litellm_params.model)",
+	"Enter Max Tokens (litellm_params.max_tokens)": "הזן מספר מקסימלי של טוקנים (litellm_params.max_tokens)",
+	"Enter Model Display Name": "",
+	"Enter model tag (e.g. {{modelTag}})": "הזן תג מודל (למשל {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "הזן מספר שלבים (למשל 50)",
+	"Enter Score": "הזן ציון",
+	"Enter stop sequence": "הזן רצף עצירה",
+	"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 Password": "הזן את הסיסמה שלך",
+	"Enter Your Role": "הזן את התפקיד שלך",
+	"Experimental": "ניסיוני",
+	"Export All Chats (All Users)": "ייצוא כל הצ'אטים (כל המשתמשים)",
+	"Export Chats": "ייצוא צ'אטים",
+	"Export Documents Mapping": "ייצוא מיפוי מסמכים",
+	"Export Modelfiles": "ייצוא קבצי מודלים",
+	"Export Prompts": "ייצוא פקודות",
+	"Failed to create API Key.": "יצירת מפתח API נכשלה.",
+	"Failed to read clipboard contents": "קריאת תוכן הלוח נכשלה",
+	"February": "פברואר",
+	"Feel free to add specific details": "נא להוסיף פרטים ספציפיים לפי רצון",
+	"File Mode": "מצב קובץ",
+	"File not found.": "הקובץ לא נמצא.",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "התגלתה הזיית טביעת אצבע: לא ניתן להשתמש בראשי תיבות כאווטאר. משתמש בתמונת פרופיל ברירת מחדל.",
+	"Fluidly stream large external response chunks": "שידור נתונים חיצוניים בקצב רציף",
+	"Focus chat input": "מיקוד הקלט לצ'אט",
+	"Followed instructions perfectly": "עקב אחר ההוראות במושלמות",
+	"Format your variables using square brackets like this:": "עצב את המשתנים שלך באמצעות סוגריים מרובעים כך:",
+	"From (Base Model)": "מ (מודל בסיס)",
+	"Full Screen Mode": "מצב מסך מלא",
+	"General": "כללי",
+	"General Settings": "הגדרות כלליות",
+	"Generation Info": "מידע על היצירה",
+	"Good Response": "תגובה טובה",
+	"h:mm a": "",
+	"has no conversations.": "אין שיחות.",
+	"Hello, {{name}}": "שלום, {{name}}",
+	"Help": "עזרה",
+	"Hide": "הסתר",
+	"Hide Additional Params": "הסתר פרמטרים נוספים",
+	"How can I help you today?": "כיצד אוכל לעזור לך היום?",
+	"Hybrid Search": "חיפוש היברידי",
+	"Image Generation (Experimental)": "יצירת תמונות (ניסיוני)",
+	"Image Generation Engine": "מנוע יצירת תמונות",
+	"Image Settings": "הגדרות תמונה",
+	"Images": "תמונות",
+	"Import Chats": "יבוא צ'אטים",
+	"Import Documents Mapping": "יבוא מיפוי מסמכים",
+	"Import Modelfiles": "יבוא קבצי מודלים",
+	"Import Prompts": "יבוא פקודות",
+	"Include `--api` flag when running stable-diffusion-webui": "כלול את הדגל `--api` בעת הרצת stable-diffusion-webui",
+	"Input commands": "פקודות קלט",
+	"Interface": "ממשק",
+	"Invalid Tag": "תג לא חוקי",
+	"Is Model Vision Capable": "",
+	"January": "ינואר",
+	"join our Discord for help.": "הצטרף ל-Discord שלנו לעזרה.",
+	"JSON": "JSON",
+	"July": "יולי",
+	"June": "יוני",
+	"JWT Expiration": "תפוגת JWT",
+	"JWT Token": "אסימון JWT",
+	"Keep Alive": "השאר פעיל",
+	"Keyboard shortcuts": "קיצורי מקלדת",
+	"Language": "שפה",
+	"Last Active": "פעיל לאחרונה",
+	"Light": "בהיר",
+	"Listening...": "מאזין...",
+	"LLMs can make mistakes. Verify important information.": "מודלים בשפה טבעית יכולים לטעות. אמת מידע חשוב.",
+	"Made by OpenWebUI Community": "נוצר על ידי קהילת OpenWebUI",
+	"Make sure to enclose them with": "ודא להקיף אותם עם",
+	"Manage LiteLLM Models": "נהל מודלים של LiteLLM",
+	"Manage Model Information": "",
+	"Manage Models": "נהל מודלים",
+	"Manage Ollama Models": "נהל מודלים של Ollama",
+	"March": "מרץ",
+	"Max Tokens": "מקסימום טוקנים",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "ניתן להוריד מקסימום 3 מודלים בו זמנית. אנא נסה שוב מאוחר יותר.",
+	"May": "מאי",
+	"Memory": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "הודעות שתשלח לאחר יצירת הקישור שלך לא ישותפו. משתמשים עם הכתובת URL יוכלו לצפות בצ'אט המשותף.",
+	"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",
+	"Model '{{modelName}}' has been successfully downloaded.": "המודל '{{modelName}}' הורד בהצלחה.",
+	"Model '{{modelTag}}' is already in queue for downloading.": "המודל '{{modelTag}}' כבר בתור להורדה.",
+	"Model {{modelId}} not found": "המודל {{modelId}} לא נמצא",
+	"Model {{modelName}} already exists.": "המודל {{modelName}} כבר קיים.",
+	"Model {{modelName}} is not vision capable": "",
+	"Model Description": "",
+	"Model Display Name": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "נתיב מערכת הקבצים של המודל זוהה. נדרש שם קצר של המודל לעדכון, לא ניתן להמשיך.",
+	"Model info for {{modelName}} added successfully": "",
+	"Model info for {{modelName}} deleted successfully": "",
+	"Model Name": "שם המודל",
+	"Model not selected": "לא נבחר מודל",
+	"Model Tag Name": "שם תג המודל",
+	"Model Whitelisting": "רישום לבן של מודלים",
+	"Model(s) Whitelisted": "מודלים שנכללו ברשימה הלבנה",
+	"Modelfile": "קובץ מודל",
+	"Modelfile Advanced Settings": "הגדרות מתקדמות לקובץ מודל",
+	"Modelfile Content": "תוכן קובץ מודל",
+	"Modelfiles": "קבצי מודל",
+	"Models": "מודלים",
+	"More": "עוד",
+	"Name": "שם",
+	"Name Tag": "תג שם",
+	"Name your modelfile": "תן שם לקובץ המודל שלך",
+	"New Chat": "צ'אט חדש",
+	"New Password": "סיסמה חדשה",
+	"No": "",
+	"No results found": "לא נמצאו תוצאות",
+	"No source available": "אין מקור זמין",
+	"Not factually correct": "לא נכון מבחינה עובדתית",
+	"Not sure what to add?": "לא בטוח מה להוסיף?",
+	"Not sure what to write? Switch to": "לא בטוח מה לכתוב? החלף ל",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "הערה: אם תקבע ציון מינימלי, החיפוש יחזיר רק מסמכים עם ציון שגבוה או שווה לציון המינימלי.",
+	"Notifications": "התראות",
+	"November": "נובמבר",
+	"October": "אוקטובר",
+	"Off": "כבוי",
+	"Okay, Let's Go!": "בסדר, בואו נתחיל!",
+	"OLED Dark": "OLED כהה",
+	"Ollama": "Ollama",
+	"Ollama Base URL": "כתובת URL בסיסית של Ollama",
+	"Ollama Version": "גרסת Ollama",
+	"On": "פועל",
+	"Only": "רק",
+	"Only alphanumeric characters and hyphens are allowed in the command string.": "רק תווים אלפאנומריים ומקפים מותרים במחרוזת הפקודה.",
+	"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "אופס! תחזיק מעמד! הקבצים שלך עדיין בתהליך העיבוד. אנו מבשלים אותם לשלמות. נא להתאזר בסבלנות ונודיע לך ברגע שיהיו מוכנים.",
+	"Oops! Looks like the URL is invalid. Please double-check and try again.": "אופס! נראה שהכתובת URL אינה תקינה. אנא בדוק שוב ונסה שנית.",
+	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "אופס! אתה משתמש בשיטה לא נתמכת (רק חזית). אנא שרת את ממשק המשתמש האינטרנטי מהשרת האחורי.",
+	"Open": "פתח",
+	"Open AI": "Open AI",
+	"Open AI (Dall-E)": "Open AI (Dall-E)",
+	"Open new chat": "פתח צ'אט חדש",
+	"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": "אחר",
+	"Overview": "סקירה כללית",
+	"Parameters": "פרמטרים",
+	"Password": "סיסמה",
+	"PDF document (.pdf)": "מסמך PDF (.pdf)",
+	"PDF Extract Images (OCR)": "חילוץ תמונות מ-PDF (OCR)",
+	"pending": "ממתין",
+	"Permission denied when accessing microphone: {{error}}": "ההרשאה נדחתה בעת גישה למיקרופון: {{error}}",
+	"Personalization": "",
+	"Plain text (.txt)": "טקסט פשוט (.txt)",
+	"Playground": "אזור משחקים",
+	"Positive attitude": "גישה חיובית",
+	"Previous 30 days": "30 הימים הקודמים",
+	"Previous 7 days": "7 הימים הקודמים",
+	"Profile Image": "תמונת פרופיל",
+	"Prompt": "פקודה",
+	"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",
+	"Pull Progress": "משוך התקדמות",
+	"Query Params": "פרמטרי שאילתה",
+	"RAG Template": "תבנית RAG",
+	"Raw Format": "פורמט גולמי",
+	"Read Aloud": "קרא בקול",
+	"Record voice": "הקלט קול",
+	"Redirecting you to OpenWebUI Community": "מפנה אותך לקהילת OpenWebUI",
+	"Refused when it shouldn't have": "נדחה כאשר לא היה צריך",
+	"Regenerate": "הפק מחדש",
+	"Release Notes": "הערות שחרור",
+	"Remove": "הסר",
+	"Remove Model": "הסר מודל",
+	"Rename": "שנה שם",
+	"Repeat Last N": "חזור על ה-N האחרונים",
+	"Repeat Penalty": "עונש חזרה",
+	"Request Mode": "מצב בקשה",
+	"Reranking Model": "מודל דירוג מחדש",
+	"Reranking model disabled": "מודל דירוג מחדש מושבת",
+	"Reranking model set to \"{{reranking_model}}\"": "מודל דירוג מחדש הוגדר ל-\"{{reranking_model}}\"",
+	"Reset Vector Storage": "איפוס אחסון וקטורים",
+	"Response AutoCopy to Clipboard": "העתקה אוטומטית של תגובה ללוח",
+	"Role": "תפקיד",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"Save": "שמור",
+	"Save & Create": "שמור וצור",
+	"Save & Update": "שמור ועדכן",
+	"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": "שמירת יומני צ'אט ישירות באחסון הדפדפן שלך אינה נתמכת יותר. אנא הקדש רגע להוריד ולמחוק את יומני הצ'אט שלך על ידי לחיצה על הכפתור למטה. אל דאגה, באפשרותך לייבא מחדש בקלות את יומני הצ'אט שלך לשרת האחורי דרך",
+	"Scan": "סרוק",
+	"Scan complete!": "הסריקה הושלמה!",
+	"Scan for documents from {{path}}": "סרוק מסמכים מ-{{path}}",
+	"Search": "חפש",
+	"Search a model": "חפש מודל",
+	"Search Documents": "חפש מסמכים",
+	"Search Prompts": "חפש פקודות",
+	"See readme.md for instructions": "ראה את readme.md להוראות",
+	"See what's new": "ראה מה חדש",
+	"Seed": "זרע",
+	"Select a mode": "בחר מצב",
+	"Select a model": "בחר מודל",
+	"Select an Ollama instance": "בחר מופע של Ollama",
+	"Select model": "בחר מודל",
+	"Selected model does not support image inputs.": "",
+	"Send": "",
+	"Send a Message": "שלח הודעה",
+	"Send message": "שלח הודעה",
+	"September": "ספטמבר",
+	"Server connection verified": "החיבור לשרת אומת",
+	"Set as default": "הגדר כברירת מחדל",
+	"Set Default Model": "הגדר מודל ברירת מחדל",
+	"Set embedding model (e.g. {{model}})": "הגדר מודל הטמעה (למשל {{model}})",
+	"Set Image Size": "הגדר גודל תמונה",
+	"Set Model": "הגדר מודל",
+	"Set reranking model (e.g. {{model}})": "הגדר מודל דירוג מחדש (למשל {{model}})",
+	"Set Steps": "הגדר שלבים",
+	"Set Title Auto-Generation Model": "הגדר מודל יצירת כותרת אוטומטית",
+	"Set Voice": "הגדר קול",
+	"Settings": "הגדרות",
+	"Settings saved successfully!": "ההגדרות נשמרו בהצלחה!",
+	"Share": "שתף",
+	"Share Chat": "שתף צ'אט",
+	"Share to OpenWebUI Community": "שתף לקהילת OpenWebUI",
+	"short-summary": "סיכום קצר",
+	"Show": "הצג",
+	"Show Additional Params": "הצג פרמטרים נוספים",
+	"Show shortcuts": "הצג קיצורי דרך",
+	"Showcased creativity": "הצגת יצירתיות",
+	"sidebar": "סרגל צד",
+	"Sign in": "",
+	"Sign Out": "",
+	"Sign up": "",
+	"Signing in": "",
+	"Source": "",
+	"Speech recognition error: {{error}}": "",
+	"Speech-to-Text Engine": "",
+	"SpeechRecognition API is not supported in this browser.": "",
+	"Stop Sequence": "",
+	"STT Settings": "",
+	"Submit": "שלח",
+	"Subtitle (e.g. about the Roman Empire)": "",
+	"Success": "הצלחה",
+	"Successfully updated.": "",
+	"Suggested": "",
+	"Sync All": "",
+	"System": "מערכת",
+	"System Prompt": "",
+	"Tags": "",
+	"Tell us more:": "",
+	"Temperature": "",
+	"Template": "תבנית",
+	"Text Completion": "",
+	"Text-to-Speech Engine": "מנוע טקסט לדיבור",
+	"Tfs Z": "",
+	"Thanks for your feedback!": "תודה על המשוב שלך!",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
+	"Theme": "נושא",
+	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "",
+	"This setting does not sync across browsers or devices.": "",
+	"Thorough explanation": "",
+	"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": "",
+	"To access the available model names for downloading,": "",
+	"To access the GGUF models available for downloading,": "",
+	"to chat input.": "",
+	"Today": "",
+	"Toggle settings": "",
+	"Toggle sidebar": "",
+	"Top K": "",
+	"Top P": "",
+	"Trouble accessing Ollama?": "",
+	"TTS Settings": "",
+	"Type Hugging Face Resolve (Download) URL": "",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "",
+	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "",
+	"Update and Copy Link": "",
+	"Update password": "",
+	"Upload a GGUF model": "",
+	"Upload files": "",
+	"Upload Progress": "",
+	"URL Mode": "",
+	"Use '#' in the prompt input to load and select your documents.": "",
+	"Use Gravatar": "",
+	"Use Initials": "",
+	"user": "",
+	"User Permissions": "",
+	"Users": "משתמשים",
+	"Utilize": "",
+	"Valid time units:": "",
+	"variable": "",
+	"variable to have them replaced with clipboard content.": "",
+	"Version": "גרסה",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
+	"Web": "רשת",
+	"Web Loader Settings": "",
+	"Web Params": "",
+	"Webhook URL": "",
+	"WebUI Add-ons": "",
+	"WebUI Settings": "",
+	"WebUI will make requests to": "",
+	"What’s New in": "",
+	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "",
+	"Whisper (Local)": "",
+	"Workspace": "",
+	"Write a prompt suggestion (e.g. Who are you?)": "",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "",
+	"Yes": "",
+	"Yesterday": "אתמול",
+	"You": "",
+	"You have no archived conversations.": "",
+	"You have shared this chat": "",
+	"You're a helpful assistant.": "",
+	"You're now logged in.": "",
+	"Youtube": "",
+	"Youtube Loader Settings": ""
+}

+ 11 - 9
src/lib/i18n/locales/hi-IN/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "ख़राब प्रतिक्रिया",
 	"before": "",
 	"Being lazy": "आलसी होना",
+	"Beta": "",
 	"Builder Mode": "बिल्डर मोड",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "रद्द करें",
 	"Categories": "श्रेणियाँ",
 	"Change Password": "पासवर्ड बदलें",
 	"Chat": "चैट करें",
+	"Chat Bubble UI": "",
 	"Chat History": "चैट का इतिहास",
 	"Chat History is off for this browser.": "इस ब्राउज़र के लिए चैट इतिहास बंद है।",
 	"Chats": "सभी चैट",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "चंक ओवरलैप",
 	"Chunk Params": "चंक पैरामीटर्स",
 	"Chunk Size": "चंक आकार",
-	"Citation": "",
+	"Citation": "उद्धरण",
 	"Click here for help.": "सहायता के लिए यहां क्लिक करें।",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "अन्य मॉडल फ़ाइलों की जांच के लिए यहां क्लिक करें।",
@@ -118,7 +120,6 @@
 	"Dark": "",
 	"Dashboard": "",
 	"Database": "डेटाबेस",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "डिफ़ॉल्ट",
 	"Default (Automatic1111)": "डिफ़ॉल्ट (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "सामान्य सेटिंग्स",
 	"Generation Info": "जनरेशन की जानकारी",
 	"Good Response": "अच्छी प्रतिक्रिया",
+	"h:mm a": "",
 	"has no conversations.": "कोई बातचीत नहीं है",
 	"Hello, {{name}}": "नमस्ते, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "अधिकतम टोकन",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "अधिकतम 3 मॉडल एक साथ डाउनलोड किये जा सकते हैं। कृपया बाद में पुन: प्रयास करें।",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "न्यूनतम स्कोर",
 	"Mirostat": "",
@@ -285,9 +288,6 @@
 	"Modelfiles": "मॉडल फ़ाइलें",
 	"Models": "सभी मॉडल",
 	"More": "और..",
-	"My Documents": "मेरे दस्तावेज़",
-	"My Modelfiles": "मेरी मॉडल फ़ाइलें",
-	"My Prompts": "मेरे प्रॉम्प्ट",
 	"Name": "नाम",
 	"Name Tag": "नाम टैग",
 	"Name your modelfile": "अपनी मॉडलफ़ाइल को नाम दें",
@@ -295,7 +295,7 @@
 	"New Password": "नया पासवर्ड",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "कोई स्रोत उपलब्ध नहीं है",
 	"Not factually correct": "तथ्यात्मक रूप से सही नहीं है",
 	"Not sure what to add?": "निश्चित नहीं कि क्या जोड़ें?",
 	"Not sure what to write? Switch to": "मैं आश्वस्त नहीं हूं कि क्या लिखना है? स्विच करें",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "PDF छवियाँ निकालें (OCR)",
 	"pending": "लंबित",
 	"Permission denied when accessing microphone: {{error}}": "माइक्रोफ़ोन तक पहुँचने पर अनुमति अस्वीकृत: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "सादा पाठ (.txt)",
 	"Playground": "कार्यक्षेत्र",
 	"Positive attitude": "सकारात्मक रवैया",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "",
 	"Save": "सहेजें",
 	"Save & Create": "सहेजें और बनाएं",
-	"Save & Submit": "सहेजें और सबमिट करें",
 	"Save & Update": "सहेजें और अपडेट करें",
 	"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": "चैट लॉग को सीधे आपके ब्राउज़र के स्टोरेज में सहेजना अब समर्थित नहीं है। कृपया नीचे दिए गए बटन पर क्लिक करके डाउनलोड करने और अपने चैट लॉग को हटाने के लिए कुछ समय दें। चिंता न करें, आप आसानी से अपने चैट लॉग को बैकएंड पर पुनः आयात कर सकते हैं",
 	"Scan": "स्कैन",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "एक Ollama Instance चुनें",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "एक संदेश भेजो",
 	"Send message": "मेसेज भेजें",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "साइन आउट",
 	"Sign up": "साइन अप",
 	"Signing in": "साइन इन हो रहा है",
-	"Source": "",
+	"Source": "स्रोत",
 	"Speech recognition error: {{error}}": "वाक् पहचान त्रुटि: {{error}}",
 	"Speech-to-Text Engine": "वाक्-से-पाठ इंजन",
 	"SpeechRecognition API is not supported in this browser.": "इस ब्राउज़र में SpeechRecognition API समर्थित नहीं है",
@@ -493,11 +494,12 @@
 	"What’s New in": "इसमें नया क्या है",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "जब इतिहास बंद हो जाता है, तो इस ब्राउज़र पर नई चैट आपके किसी भी डिवाइस पर इतिहास में दिखाई नहीं देंगी।",
 	"Whisper (Local)": "Whisper (स्थानीय)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "एक त्वरित सुझाव लिखें (जैसे कि आप कौन हैं?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "50 शब्दों में एक सारांश लिखें जो [विषय या कीवर्ड] का सारांश प्रस्तुत करता हो।",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "आप",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "आप एक सहायक सहायक हैं",

+ 148 - 146
src/lib/i18n/locales/it-IT/translation.json

@@ -1,34 +1,34 @@
 {
 	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' o '-1' per nessuna scadenza.",
 	"(Beta)": "(Beta)",
-	"(e.g. `sh webui.sh --api`)": "",
+	"(e.g. `sh webui.sh --api`)": "(p.e. `sh webui.sh --api`)",
 	"(latest)": "(ultima)",
 	"{{modelName}} is thinking...": "{{modelName}} sta pensando...",
-	"{{user}}'s Chats": "",
+	"{{user}}'s Chats": "{{user}} Chat",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend richiesto",
 	"a user": "un utente",
 	"About": "Informazioni",
 	"Account": "Account",
-	"Accurate information": "",
+	"Accurate information": "Informazioni accurate",
 	"Add a model": "Aggiungi un modello",
 	"Add a model tag name": "Aggiungi un nome tag del modello",
 	"Add a short description about what this modelfile does": "Aggiungi una breve descrizione di ciò che fa questo file modello",
 	"Add a short title for this prompt": "Aggiungi un titolo breve per questo prompt",
 	"Add a tag": "Aggiungi un tag",
-	"Add custom prompt": "",
+	"Add custom prompt": "Aggiungi un prompt custom",
 	"Add Docs": "Aggiungi documenti",
 	"Add Files": "Aggiungi file",
 	"Add message": "Aggiungi messaggio",
-	"Add Model": "",
-	"Add Tags": "aggiungi tag",
-	"Add User": "",
+	"Add Model": "Aggiungi modello",
+	"Add Tags": "Aggiungi tag",
+	"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 Panel": "Pannello di amministrazione",
 	"Admin Settings": "Impostazioni amministratore",
 	"Advanced Parameters": "Parametri avanzati",
 	"all": "tutti",
-	"All Documents": "",
+	"All Documents": "Tutti i documenti",
 	"All Users": "Tutti gli utenti",
 	"Allow": "Consenti",
 	"Allow Chat Deletion": "Consenti l'eliminazione della chat",
@@ -36,36 +36,38 @@
 	"Already have an account?": "Hai già un account?",
 	"an assistant": "un assistente",
 	"and": "e",
-	"and create a new shared link.": "",
+	"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.": "",
-	"API keys": "",
+	"API Key created.": "Chiave API creata.",
+	"API keys": "Chiavi API",
 	"API RPM": "API RPM",
-	"April": "",
-	"Archive": "",
+	"April": "Aprile",
+	"Archive": "Archivio",
 	"Archived Chats": "Chat archiviate",
 	"are allowed - Activate this command by typing": "sono consentiti - Attiva questo comando digitando",
 	"Are you sure?": "Sei sicuro?",
-	"Attach file": "",
-	"Attention to detail": "",
+	"Attach file": "Allega file",
+	"Attention to detail": "Attenzione ai dettagli",
 	"Audio": "Audio",
-	"August": "",
+	"August": "Agosto",
 	"Auto-playback response": "Riproduzione automatica della risposta",
 	"Auto-send input after 3 sec.": "Invio automatico dell'input dopo 3 secondi.",
 	"AUTOMATIC1111 Base URL": "URL base AUTOMATIC1111",
 	"AUTOMATIC1111 Base URL is required.": "L'URL base AUTOMATIC1111 è obbligatorio.",
 	"available!": "disponibile!",
 	"Back": "Indietro",
-	"Bad Response": "",
-	"before": "",
-	"Being lazy": "",
+	"Bad Response": "Risposta non valida",
+	"before": "prima",
+	"Being lazy": "Essere pigri",
+	"Beta": "",
 	"Builder Mode": "Modalità costruttore",
-	"Bypass SSL verification for Websites": "",
+	"Bypass SSL verification for Websites": "Aggira la verifica SSL per i siti web",
 	"Cancel": "Annulla",
 	"Categories": "Categorie",
 	"Change Password": "Cambia password",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Cronologia chat",
 	"Chat History is off for this browser.": "La cronologia chat è disattivata per questo browser.",
 	"Chats": "Chat",
@@ -76,69 +78,68 @@
 	"Chunk Overlap": "Sovrapposizione chunk",
 	"Chunk Params": "Parametri chunk",
 	"Chunk Size": "Dimensione chunk",
-	"Citation": "",
+	"Citation": "Citazione",
 	"Click here for help.": "Clicca qui per aiuto.",
-	"Click here to": "",
+	"Click here to": "Clicca qui per",
 	"Click here to check other modelfiles.": "Clicca qui per controllare altri file modello.",
 	"Click here to select": "Clicca qui per selezionare",
-	"Click here to select a csv file.": "",
+	"Click here to select a csv file.": "Clicca qui per selezionare un file csv.",
 	"Click here to select documents.": "Clicca qui per selezionare i documenti.",
 	"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.",
 	"Close": "Chiudi",
 	"Collection": "Collezione",
-	"ComfyUI": "",
-	"ComfyUI Base URL": "",
-	"ComfyUI Base URL is required.": "",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL base ComfyUI",
+	"ComfyUI Base URL is required.": "L'URL base ComfyUI è obbligatorio.",
 	"Command": "Comando",
 	"Confirm Password": "Conferma password",
 	"Connections": "Connessioni",
 	"Content": "Contenuto",
 	"Context Length": "Lunghezza contesto",
-	"Continue Response": "",
+	"Continue Response": "Continua risposta",
 	"Conversation Mode": "Modalità conversazione",
-	"Copied shared chat URL to clipboard!": "",
-	"Copy": "",
+	"Copied shared chat URL to clipboard!": "URL della chat condivisa copiato negli appunti!",
+	"Copy": "Copia",
 	"Copy last code block": "Copia ultimo blocco di codice",
 	"Copy last response": "Copia ultima risposta",
-	"Copy Link": "",
+	"Copy Link": "Copia link",
 	"Copying to clipboard was successful!": "Copia negli appunti riuscita!",
 	"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':": "Crea una frase concisa di 3-5 parole come intestazione per la seguente query, aderendo rigorosamente al limite di 3-5 parole ed evitando l'uso della parola 'titolo':",
 	"Create a modelfile": "Crea un file modello",
 	"Create Account": "Crea account",
-	"Create new key": "",
-	"Create new secret key": "",
+	"Create new key": "Crea nuova chiave",
+	"Create new secret key": "Crea nuova chiave segreta",
 	"Created at": "Creato il",
-	"Created At": "",
+	"Created At": "Creato il",
 	"Current Model": "Modello corrente",
 	"Current Models": "",
 	"Current Password": "Password corrente",
 	"Custom": "Personalizzato",
 	"Customize Ollama models for a specific purpose": "Personalizza i modelli Ollama per uno scopo specifico",
 	"Dark": "Scuro",
-	"Dashboard": "",
+	"Dashboard": "Pannello di controllo",
 	"Database": "Database",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
-	"December": "",
+	"December": "Dicembre",
 	"Default": "Predefinito",
 	"Default (Automatic1111)": "Predefinito (Automatic1111)",
-	"Default (SentenceTransformers)": "",
+	"Default (SentenceTransformers)": "Predefinito (SentenceTransformers)",
 	"Default (Web API)": "Predefinito (API Web)",
 	"Default model updated": "Modello predefinito aggiornato",
 	"Default Prompt Suggestions": "Suggerimenti prompt predefiniti",
 	"Default User Role": "Ruolo utente predefinito",
 	"delete": "elimina",
-	"Delete": "",
+	"Delete": "Elimina",
 	"Delete a model": "Elimina un modello",
 	"Delete chat": "Elimina chat",
-	"Delete Chat": "",
-	"Delete Chats": "Elimina chat",
-	"delete this link": "",
-	"Delete User": "",
+	"Delete Chat": "Elimina chat",
+	"Delete Chats": "Elimina le chat",
+	"delete this link": "elimina questo link",
+	"Delete User": "Elimina utente",
 	"Deleted {{deleteModelTag}}": "Eliminato {{deleteModelTag}}",
-	"Deleted {{tagName}}": "",
+	"Deleted {{tagName}}": "Eliminato {{tagName}}",
 	"Description": "Descrizione",
-	"Didn't fully follow instructions": "",
+	"Didn't fully follow instructions": "Non ha seguito completamente le istruzioni",
 	"Disabled": "Disabilitato",
 	"Discover a modelfile": "Scopri un file modello",
 	"Discover a prompt": "Scopri un prompt",
@@ -151,28 +152,28 @@
 	"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 Allow": "Non consentire",
 	"Don't have an account?": "Non hai un account?",
-	"Don't like the style": "",
-	"Download": "",
-	"Download canceled": "",
+	"Don't like the style": "Non ti piace lo stile",
+	"Download": "Scarica",
+	"Download canceled": "Scaricamento annullato",
 	"Download Database": "Scarica database",
 	"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": "",
+	"Edit": "Modifica",
 	"Edit Doc": "Modifica documento",
 	"Edit User": "Modifica utente",
 	"Email": "Email",
-	"Embedding Model": "",
-	"Embedding Model Engine": "",
-	"Embedding model set to \"{{embedding_model}}\"": "",
+	"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 Chat History": "Abilita cronologia chat",
 	"Enable New Sign Ups": "Abilita nuove iscrizioni",
 	"Enabled": "Abilitato",
-	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
+	"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 Chunk Overlap": "Inserisci la sovrapposizione chunk",
 	"Enter Chunk Size": "Inserisci la dimensione chunk",
 	"Enter Image Size (e.g. 512x512)": "Inserisci la dimensione dell'immagine (ad esempio 512x512)",
-	"Enter language codes": "",
+	"Enter language codes": "Inserisci i codici lingua",
 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "Inserisci l'URL base dell'API LiteLLM (litellm_params.api_base)",
 	"Enter LiteLLM API Key (litellm_params.api_key)": "Inserisci la chiave API LiteLLM (litellm_params.api_key)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Inserisci LiteLLM API RPM (litellm_params.rpm)",
@@ -181,45 +182,46 @@
 	"Enter Model Display Name": "",
 	"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 Score": "",
+	"Enter Score": "Inserisci il punteggio",
 	"Enter stop sequence": "Inserisci la sequenza di arresto",
 	"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)": "",
+	"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 Password": "Inserisci la tua password",
-	"Enter Your Role": "",
+	"Enter Your Role": "Inserisci il tuo ruolo",
 	"Experimental": "Sperimentale",
 	"Export All Chats (All Users)": "Esporta tutte le chat (tutti gli utenti)",
 	"Export Chats": "Esporta chat",
 	"Export Documents Mapping": "Esporta mappatura documenti",
 	"Export Modelfiles": "Esporta file modello",
 	"Export Prompts": "Esporta prompt",
-	"Failed to create API Key.": "",
+	"Failed to create API Key.": "Impossibile creare la chiave API.",
 	"Failed to read clipboard contents": "Impossibile leggere il contenuto degli appunti",
-	"February": "",
-	"Feel free to add specific details": "",
+	"February": "Febbraio",
+	"Feel free to add specific details": "Sentiti libero/a di aggiungere dettagli specifici",
 	"File Mode": "Modalità file",
 	"File not found.": "File non trovato.",
-	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
+	"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",
-	"Followed instructions perfectly": "",
+	"Followed instructions perfectly": "Ha seguito le istruzioni alla perfezione",
 	"Format your variables using square brackets like this:": "Formatta le tue variabili usando parentesi quadre come questa:",
 	"From (Base Model)": "Da (modello base)",
 	"Full Screen Mode": "Modalità a schermo intero",
 	"General": "Generale",
 	"General Settings": "Impostazioni generali",
-	"Generation Info": "",
-	"Good Response": "",
-	"has no conversations.": "",
+	"Generation Info": "Informazioni generazione",
+	"Good Response": "Buona risposta",
+	"h:mm a": "",
+	"has no conversations.": "non ha conversazioni.",
 	"Hello, {{name}}": "Ciao, {{name}}",
-	"Help": "",
+	"Help": "Aiuto",
 	"Hide": "Nascondi",
 	"Hide Additional Params": "Nascondi parametri aggiuntivi",
 	"How can I help you today?": "Come posso aiutarti oggi?",
-	"Hybrid Search": "",
+	"Hybrid Search": "Ricerca ibrida",
 	"Image Generation (Experimental)": "Generazione di immagini (sperimentale)",
 	"Image Generation Engine": "Motore di generazione immagini",
 	"Image Settings": "Impostazioni immagine",
@@ -228,22 +230,22 @@
 	"Import Documents Mapping": "Importa mappatura documenti",
 	"Import Modelfiles": "Importa file modello",
 	"Import Prompts": "Importa prompt",
-	"Include `--api` flag when running stable-diffusion-webui": "",
-	"Input commands": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Includi il flag `--api` quando esegui stable-diffusion-webui",
+	"Input commands": "Comandi di input",
 	"Interface": "Interfaccia",
-	"Invalid Tag": "",
+	"Invalid Tag": "Tag non valido",
 	"Is Model Vision Capable": "",
-	"January": "",
+	"January": "Gennaio",
 	"join our Discord for help.": "unisciti al nostro Discord per ricevere aiuto.",
 	"JSON": "JSON",
-	"July": "",
-	"June": "",
+	"July": "Luglio",
+	"June": "Giugno",
 	"JWT Expiration": "Scadenza JWT",
 	"JWT Token": "Token JWT",
 	"Keep Alive": "Mantieni attivo",
 	"Keyboard shortcuts": "Scorciatoie da tastiera",
 	"Language": "Lingua",
-	"Last Active": "",
+	"Last Active": "Ultima attività",
 	"Light": "Chiaro",
 	"Listening...": "Ascolto...",
 	"LLMs can make mistakes. Verify important information.": "Gli LLM possono commettere errori. Verifica le informazioni importanti.",
@@ -253,17 +255,18 @@
 	"Manage Model Information": "",
 	"Manage Models": "Gestisci modelli",
 	"Manage Ollama Models": "Gestisci modelli Ollama",
-	"March": "",
+	"March": "Marzo",
 	"Max Tokens": "Max token",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "È possibile scaricare un massimo di 3 modelli contemporaneamente. Riprova più tardi.",
-	"May": "",
-	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
-	"Minimum Score": "",
+	"May": "Maggio",
+	"Memory": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "I messaggi che invii dopo aver creato il tuo link non verranno condivisi. Gli utenti con l'URL potranno visualizzare la chat condivisa.",
+	"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",
 	"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",
@@ -271,7 +274,7 @@
 	"Model {{modelName}} is not vision capable": "",
 	"Model Description": "",
 	"Model Display Name": "",
-	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
+	"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 info for {{modelName}} added successfully": "",
 	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "Nome modello",
@@ -284,29 +287,26 @@
 	"Modelfile Content": "Contenuto del file modello",
 	"Modelfiles": "File modello",
 	"Models": "Modelli",
-	"More": "",
-	"My Documents": "I miei documenti",
-	"My Modelfiles": "I miei file modello",
-	"My Prompts": "I miei prompt",
+	"More": "Altro",
 	"Name": "Nome",
 	"Name Tag": "Nome tag",
 	"Name your modelfile": "Assegna un nome al tuo file modello",
 	"New Chat": "Nuova chat",
 	"New Password": "Nuova password",
 	"No": "",
-	"No results found": "",
-	"No source available": "",
-	"Not factually correct": "",
+	"No results found": "Nessun risultato trovato",
+	"No source available": "Nessuna fonte disponibile",
+	"Not factually correct": "Non corretto dal punto di vista fattuale",
 	"Not sure what to add?": "Non sei sicuro di cosa aggiungere?",
 	"Not sure what to write? Switch to": "Non sei sicuro di cosa scrivere? Passa a",
-	"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: 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.",
 	"Notifications": "Notifiche desktop",
-	"November": "",
-	"October": "",
+	"November": "Novembre",
+	"October": "Ottobre",
 	"Off": "Disattivato",
 	"Okay, Let's Go!": "Ok, andiamo!",
-	"OLED Dark": "",
-	"Ollama": "",
+	"OLED Dark": "OLED scuro",
+	"Ollama": "Ollama",
 	"Ollama Base URL": "URL base Ollama",
 	"Ollama Version": "Versione Ollama",
 	"On": "Attivato",
@@ -319,52 +319,53 @@
 	"Open AI": "Open AI",
 	"Open AI (Dall-E)": "Open AI (Dall-E)",
 	"Open new chat": "Apri nuova chat",
-	"OpenAI": "",
+	"OpenAI": "OpenAI",
 	"OpenAI API": "API OpenAI",
-	"OpenAI API Config": "",
+	"OpenAI API Config": "Configurazione API OpenAI",
 	"OpenAI API Key is required.": "La chiave API OpenAI è obbligatoria.",
-	"OpenAI URL/Key required.": "",
+	"OpenAI URL/Key required.": "URL/Chiave OpenAI obbligatori.",
 	"or": "o",
-	"Other": "",
-	"Overview": "",
+	"Other": "Altro",
+	"Overview": "Panoramica",
 	"Parameters": "Parametri",
 	"Password": "Password",
-	"PDF document (.pdf)": "",
+	"PDF document (.pdf)": "Documento PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Estrazione immagini PDF (OCR)",
 	"pending": "in sospeso",
 	"Permission denied when accessing microphone: {{error}}": "Autorizzazione negata durante l'accesso al microfono: {{error}}",
-	"Plain text (.txt)": "",
+	"Personalization": "",
+	"Plain text (.txt)": "Testo normale (.txt)",
 	"Playground": "Terreno di gioco",
-	"Positive attitude": "",
-	"Previous 30 days": "",
-	"Previous 7 days": "",
-	"Profile Image": "",
-	"Prompt": "",
-	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
+	"Positive attitude": "Attitudine positiva",
+	"Previous 30 days": "Ultimi 30 giorni",
+	"Previous 7 days": "Ultimi 7 giorni",
+	"Profile Image": "Immagine del profilo",
+	"Prompt": "Prompt",
+	"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": "",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Estrai \"{{searchValue}}\" da Ollama.com",
 	"Pull a model from Ollama.com": "Estrai un modello da Ollama.com",
 	"Pull Progress": "Avanzamento estrazione",
 	"Query Params": "Parametri query",
 	"RAG Template": "Modello RAG",
 	"Raw Format": "Formato raw",
-	"Read Aloud": "",
+	"Read Aloud": "Leggi ad alta voce",
 	"Record voice": "Registra voce",
 	"Redirecting you to OpenWebUI Community": "Reindirizzamento alla comunità OpenWebUI",
-	"Refused when it shouldn't have": "",
-	"Regenerate": "",
+	"Refused when it shouldn't have": "Rifiutato quando non avrebbe dovuto",
+	"Regenerate": "Rigenera",
 	"Release Notes": "Note di rilascio",
-	"Remove": "",
-	"Remove Model": "",
-	"Rename": "",
+	"Remove": "Rimuovi",
+	"Remove Model": "Rimuovi modello",
+	"Rename": "Rinomina",
 	"Repeat Last N": "Ripeti ultimi N",
 	"Repeat Penalty": "Penalità di ripetizione",
 	"Request Mode": "Modalità richiesta",
-	"Reranking Model": "",
-	"Reranking model disabled": "",
-	"Reranking model set to \"{{reranking_model}}\"": "",
+	"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 Vector Storage": "Reimposta archivio vettoriale",
 	"Response AutoCopy to Clipboard": "Copia automatica della risposta negli appunti",
 	"Role": "Ruolo",
@@ -372,14 +373,13 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Salva",
 	"Save & Create": "Salva e crea",
-	"Save & Submit": "Salva e invia",
 	"Save & Update": "Salva e aggiorna",
 	"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",
 	"Scan": "Scansione",
 	"Scan complete!": "Scansione completata!",
 	"Scan for documents from {{path}}": "Cerca documenti da {{path}}",
 	"Search": "Cerca",
-	"Search a model": "",
+	"Search a model": "Cerca un modello",
 	"Search Documents": "Cerca documenti",
 	"Search Prompts": "Cerca prompt",
 	"See readme.md for instructions": "Vedi readme.md per le istruzioni",
@@ -388,74 +388,75 @@
 	"Select a mode": "Seleziona una modalità",
 	"Select a model": "Seleziona un modello",
 	"Select an Ollama instance": "Seleziona un'istanza Ollama",
-	"Select model": "",
+	"Select model": "Seleziona modello",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Invia un messaggio",
 	"Send message": "Invia messaggio",
-	"September": "",
+	"September": "Settembre",
 	"Server connection verified": "Connessione al server verificata",
 	"Set as default": "Imposta come predefinito",
 	"Set Default Model": "Imposta modello predefinito",
-	"Set embedding model (e.g. {{model}})": "",
+	"Set embedding model (e.g. {{model}})": "Imposta modello di embedding (ad esempio {{model}})",
 	"Set Image Size": "Imposta dimensione immagine",
 	"Set Model": "Imposta modello",
-	"Set reranking model (e.g. {{model}})": "",
+	"Set reranking model (e.g. {{model}})": "Imposta modello di riclassificazione (ad esempio {{model}})",
 	"Set Steps": "Imposta passaggi",
 	"Set Title Auto-Generation Model": "Imposta modello di generazione automatica del titolo",
 	"Set Voice": "Imposta voce",
 	"Settings": "Impostazioni",
 	"Settings saved successfully!": "Impostazioni salvate con successo!",
-	"Share": "",
-	"Share Chat": "",
+	"Share": "Condividi",
+	"Share Chat": "Condividi chat",
 	"Share to OpenWebUI Community": "Condividi con la comunità OpenWebUI",
 	"short-summary": "riassunto-breve",
 	"Show": "Mostra",
 	"Show Additional Params": "Mostra parametri aggiuntivi",
 	"Show shortcuts": "Mostra",
-	"Showcased creativity": "",
+	"Showcased creativity": "Creatività messa in mostra",
 	"sidebar": "barra laterale",
 	"Sign in": "Accedi",
 	"Sign Out": "Esci",
 	"Sign up": "Registrati",
-	"Signing in": "",
-	"Source": "",
+	"Signing in": "Accesso in corso",
+	"Source": "Fonte",
 	"Speech recognition error: {{error}}": "Errore di riconoscimento vocale: {{error}}",
 	"Speech-to-Text Engine": "Motore da voce a testo",
 	"SpeechRecognition API is not supported in this browser.": "L'API SpeechRecognition non è supportata in questo browser.",
 	"Stop Sequence": "Sequenza di arresto",
 	"STT Settings": "Impostazioni STT",
 	"Submit": "Invia",
-	"Subtitle (e.g. about the Roman Empire)": "",
+	"Subtitle (e.g. about the Roman Empire)": "Sottotitolo (ad esempio sull'Impero Romano)",
 	"Success": "Successo",
 	"Successfully updated.": "Aggiornato con successo.",
-	"Suggested": "",
+	"Suggested": "Suggerito",
 	"Sync All": "Sincronizza tutto",
 	"System": "Sistema",
 	"System Prompt": "Prompt di sistema",
 	"Tags": "Tag",
-	"Tell us more:": "",
+	"Tell us more:": "Raccontaci di più:",
 	"Temperature": "Temperatura",
 	"Template": "Modello",
 	"Text Completion": "Completamento del testo",
 	"Text-to-Speech Engine": "Motore da testo a voce",
 	"Tfs Z": "Tfs Z",
-	"Thanks for your feedback!": "",
-	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
+	"Thanks for your feedback!": "Grazie per il tuo feedback!",
+	"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",
 	"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 setting does not sync across browsers or devices.": "Questa impostazione non si sincronizza tra browser o dispositivi.",
-	"Thorough explanation": "",
+	"Thorough explanation": "Spiegazione dettagliata",
 	"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)": "",
+	"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.": "",
+	"Title cannot be an empty string.": "Il titolo non può essere una stringa vuota.",
 	"Title Generation Prompt": "Prompt di generazione del titolo",
 	"to": "a",
 	"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 chat input.": "all'input della chat.",
-	"Today": "",
+	"Today": "Oggi",
 	"Toggle settings": "Attiva/disattiva impostazioni",
 	"Toggle sidebar": "Attiva/disattiva barra laterale",
 	"Top K": "Top K",
@@ -465,7 +466,7 @@
 	"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}}.",
 	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Tipo di file sconosciuto '{{file_type}}', ma accettato e trattato come testo normale",
-	"Update and Copy Link": "",
+	"Update and Copy Link": "Aggiorna e copia link",
 	"Update password": "Aggiorna password",
 	"Upload a GGUF model": "Carica un modello GGUF",
 	"Upload files": "Carica file",
@@ -473,7 +474,7 @@
 	"URL Mode": "Modalità URL",
 	"Use '#' in the prompt input to load and select your documents.": "Usa '#' nell'input del prompt per caricare e selezionare i tuoi documenti.",
 	"Use Gravatar": "Usa Gravatar",
-	"Use Initials": "",
+	"Use Initials": "Usa iniziali",
 	"user": "utente",
 	"User Permissions": "Autorizzazioni utente",
 	"Users": "Utenti",
@@ -482,26 +483,27 @@
 	"variable": "variabile",
 	"variable to have them replaced with clipboard content.": "variabile per farli sostituire con il contenuto degli appunti.",
 	"Version": "Versione",
-	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
+	"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 Loader Settings": "",
-	"Web Params": "",
-	"Webhook URL": "",
+	"Web Loader Settings": "Impostazioni del caricatore Web",
+	"Web Params": "Parametri Web",
+	"Webhook URL": "URL webhook",
 	"WebUI Add-ons": "Componenti aggiuntivi WebUI",
 	"WebUI Settings": "Impostazioni WebUI",
 	"WebUI will make requests to": "WebUI effettuerà richieste a",
 	"What’s New in": "Novità in",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando la cronologia è disattivata, le nuove chat su questo browser non verranno visualizzate nella cronologia su nessuno dei tuoi dispositivi.",
 	"Whisper (Local)": "Whisper (locale)",
+	"Workspace": "",
 	"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].",
 	"Yes": "",
-	"Yesterday": "",
-	"You": "Tu",
-	"You have no archived conversations.": "",
-	"You have shared this chat": "",
+	"Yesterday": "Ieri",
+	"You": "",
+	"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.",
-	"Youtube": "",
-	"Youtube Loader Settings": ""
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Impostazioni del caricatore Youtube"
 }

+ 11 - 9
src/lib/i18n/locales/ja-JP/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "ビルダーモード",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "キャンセル",
 	"Categories": "カテゴリ",
 	"Change Password": "パスワードを変更",
 	"Chat": "チャット",
+	"Chat Bubble UI": "",
 	"Chat History": "チャット履歴",
 	"Chat History is off for this browser.": "このブラウザではチャット履歴が無効になっています。",
 	"Chats": "チャット",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "チャンクオーバーラップ",
 	"Chunk Params": "チャンクパラメーター",
 	"Chunk Size": "チャンクサイズ",
-	"Citation": "",
+	"Citation": "引用文",
 	"Click here for help.": "ヘルプについてはここをクリックしてください。",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "他のモデルファイルを確認するにはここをクリックしてください。",
@@ -118,7 +120,6 @@
 	"Dark": "ダーク",
 	"Dashboard": "",
 	"Database": "データベース",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "デフォルト",
 	"Default (Automatic1111)": "デフォルト (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "一般設定",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "こんにちは、{{name}} さん",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "最大トークン数",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "同時にダウンロードできるモデルは最大 3 つです。後でもう一度お試しください。",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "ミロスタット",
@@ -285,9 +288,6 @@
 	"Modelfiles": "モデルファイル",
 	"Models": "モデル",
 	"More": "",
-	"My Documents": "マイ ドキュメント",
-	"My Modelfiles": "マイ モデルファイル",
-	"My Prompts": "マイ プロンプト",
 	"Name": "名前",
 	"Name Tag": "名前タグ",
 	"Name your modelfile": "モデルファイルに名前を付ける",
@@ -295,7 +295,7 @@
 	"New Password": "新しいパスワード",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "使用可能なソースがありません",
 	"Not factually correct": "",
 	"Not sure what to add?": "何を追加すればよいかわからない?",
 	"Not sure what to write? Switch to": "何を書けばよいかわからない? 次に切り替える",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "PDF 画像抽出 (OCR)",
 	"pending": "保留中",
 	"Permission denied when accessing microphone: {{error}}": "マイクへのアクセス時に権限が拒否されました: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "プレイグラウンド",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "保存",
 	"Save & Create": "保存して作成",
-	"Save & Submit": "保存して送信",
 	"Save & Update": "保存して更新",
 	"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": "チャットログをブラウザのストレージに直接保存する機能はサポートされなくなりました。下のボタンをクリックして、チャットログをダウンロードして削除してください。ご心配なく。チャットログは、次の方法でバックエンドに簡単に再インポートできます。",
 	"Scan": "スキャン",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Ollama インスタンスを選択",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "メッセージを送信",
 	"Send message": "メッセージを送信",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "サインアウト",
 	"Sign up": "サインアップ",
 	"Signing in": "",
-	"Source": "",
+	"Source": "ソース",
 	"Speech recognition error: {{error}}": "音声認識エラー: {{error}}",
 	"Speech-to-Text Engine": "音声テキスト変換エンジン",
 	"SpeechRecognition API is not supported in this browser.": "このブラウザでは SpeechRecognition API がサポートされていません。",
@@ -493,11 +494,12 @@
 	"What’s New in": "新機能",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "履歴が無効になっている場合、このブラウザでの新しいチャットは、どのデバイスの履歴にも表示されません。",
 	"Whisper (Local)": "Whisper (ローカル)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "プロンプトの提案を書いてください (例: あなたは誰ですか?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[トピックまたはキーワード] を要約する 50 語の概要を書いてください。",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "あなた",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "あなたは役に立つアシスタントです。",

+ 11 - 9
src/lib/i18n/locales/ka-GE/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "მოდელის შექმნა",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "გაუქმება",
 	"Categories": "კატეგორიები",
 	"Change Password": "პაროლის შეცვლა",
 	"Chat": "მიმოწერა",
+	"Chat Bubble UI": "",
 	"Chat History": "მიმოწერის ისტორია",
 	"Chat History is off for this browser.": "მიმოწერის ისტორია ამ ბრაუზერისთვის გათიშულია",
 	"Chats": "მიმოწერები",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "გადახურვა ფრაგმენტულია",
 	"Chunk Params": "გადახურვის პარამეტრები",
 	"Chunk Size": "გადახურვის ზომა",
-	"Citation": "",
+	"Citation": "ციტატა",
 	"Click here for help.": "დახმარებისთვის, დააკლიკე აქ",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "სხვა მოდელური ფაილების სანახავად, დააკლიკე აქ",
@@ -118,7 +120,6 @@
 	"Dark": "მუქი",
 	"Dashboard": "",
 	"Database": "მონაცემთა ბაზა",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "დეფოლტი",
 	"Default (Automatic1111)": "დეფოლტ (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "ზოგადი პარამეტრები",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "გამარჯობა, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "მაქსიმალური ტოკენები",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "მაქსიმუმ 3 მოდელის ჩამოტვირთვა შესაძლებელია ერთდროულად. Გთხოვთ სცადოთ მოგვიანებით.",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "მიროსტატი",
@@ -285,9 +288,6 @@
 	"Modelfiles": "მოდელური ფაილები",
 	"Models": "მოდელები",
 	"More": "",
-	"My Documents": "ჩემი დოკუმენტები",
-	"My Modelfiles": "ჩემი მოდელური ფაილები",
-	"My Prompts": "ჩემი მოთხოვნები",
 	"Name": "სახელი",
 	"Name Tag": "სახელის ტეგი",
 	"Name your modelfile": "თქვენი მოდელური ფაილის სახელი",
@@ -295,7 +295,7 @@
 	"New Password": "ახალი პაროლი",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "წყარო არ არის ხელმისაწვდომი",
 	"Not factually correct": "",
 	"Not sure what to add?": "არ იცი რა დაამატო?",
 	"Not sure what to write? Switch to": "არ იცი რა დაწერო? გადართვა:",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "PDF იდან ამოღებული სურათები (OCR)",
 	"pending": "ლოდინის რეჟიმშია",
 	"Permission denied when accessing microphone: {{error}}": "ნებართვა უარყოფილია მიკროფონზე წვდომისას: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "სათამაშო მოედანი",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "ვარდისფერი ფიჭვის გარიჟრაჟი",
 	"Save": "შენახვა",
 	"Save & Create": "დამახსოვრება და შექმნა",
-	"Save & Submit": "დამახსოვრება და გაგზავნა",
 	"Save & Update": "დამახსოვრება და განახლება",
 	"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": "ჩეთის ისტორიის შენახვა პირდაპირ თქვენი ბრაუზერის საცავში აღარ არის მხარდაჭერილი. გთხოვთ, დაუთმოთ და წაშალოთ თქვენი ჩატის ჟურნალები ქვემოთ მოცემულ ღილაკზე დაწკაპუნებით. არ ინერვიულოთ, თქვენ შეგიძლიათ მარტივად ხელახლა შემოიტანოთ თქვენი ჩეთის ისტორია ბექენდში",
 	"Scan": "სკანირება",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "შეტყობინების გაგზავნა",
 	"Send message": "შეტყობინების გაგზავნა",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "გასვლა",
 	"Sign up": "რეგისტრაცია",
 	"Signing in": "",
-	"Source": "",
+	"Source": "წყარო",
 	"Speech recognition error: {{error}}": "მეტყველების ამოცნობის შეცდომა: {{error}}",
 	"Speech-to-Text Engine": "ხმოვან-ტექსტური ძრავი",
 	"SpeechRecognition API is not supported in this browser.": "მეტყველების ამოცნობის API არ არის მხარდაჭერილი ამ ბრაუზერში.",
@@ -493,11 +494,12 @@
 	"What’s New in": "რა არის ახალი",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "როდესაც ისტორია გამორთულია, ახალი ჩეთები ამ ბრაუზერში არ გამოჩნდება თქვენს ისტორიაში არცერთ მოწყობილობაზე.",
 	"Whisper (Local)": "ჩურჩული (ადგილობრივი)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "დაწერეთ მოკლე წინადადება (მაგ. ვინ ხარ?",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "დაწერეთ რეზიუმე 50 სიტყვით, რომელიც აჯამებს [თემას ან საკვანძო სიტყვას].",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "თქვენ",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "თქვენ სასარგებლო ასისტენტი ხართ.",

+ 11 - 9
src/lib/i18n/locales/ko-KR/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "빌더 모드",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "취소",
 	"Categories": "분류",
 	"Change Password": "비밀번호 변경",
 	"Chat": "채팅",
+	"Chat Bubble UI": "",
 	"Chat History": "채팅 기록",
 	"Chat History is off for this browser.": "이 브라우저에서 채팅 기록이 꺼져 있습니다.",
 	"Chats": "채팅",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "Chunk Overlap",
 	"Chunk Params": "Chunk Params",
 	"Chunk Size": "Chunk Size",
-	"Citation": "",
+	"Citation": "인용",
 	"Click here for help.": "도움말을 보려면 여기를 클릭하세요.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "다른 모델파일을 확인하려면 여기를 클릭하세요.",
@@ -118,7 +120,6 @@
 	"Dark": "어두운",
 	"Dashboard": "",
 	"Database": "데이터베이스",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "기본값",
 	"Default (Automatic1111)": "기본값 (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "일반 설정",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "안녕하세요, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "최대 토큰 수",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "최대 3개의 모델을 동시에 다운로드할 수 있습니다. 나중에 다시 시도하세요.",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "모델파일",
 	"Models": "모델",
 	"More": "",
-	"My Documents": "내 문서",
-	"My Modelfiles": "내 모델파일",
-	"My Prompts": "내 프롬프트",
 	"Name": "이름",
 	"Name Tag": "이름 태그",
 	"Name your modelfile": "모델파일 이름 지정",
@@ -295,7 +295,7 @@
 	"New Password": "새 비밀번호",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "사용 가능한 소스 없음",
 	"Not factually correct": "",
 	"Not sure what to add?": "추가할 것이 궁금하세요?",
 	"Not sure what to write? Switch to": "무엇을 쓸지 모르겠나요? 전환하세요.",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "PDF에서 이미지 추출 (OCR)",
 	"pending": "보류 중",
 	"Permission denied when accessing microphone: {{error}}": "마이크 액세스가 거부되었습니다: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "놀이터",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "로제 파인 던",
 	"Save": "저장",
 	"Save & Create": "저장 및 생성",
-	"Save & Submit": "저장 및 제출",
 	"Save & Update": "저장 및 업데이트",
 	"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": "브라우저의 저장소에 채팅 로그를 직접 저장하는 것은 더 이상 지원되지 않습니다. 아래 버튼을 클릭하여 채팅 로그를 다운로드하고 삭제하세요. 걱정 마세요. 백엔드를 통해 채팅 로그를 쉽게 다시 가져올 수 있습니다.",
 	"Scan": "스캔",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Ollama 인스턴스 선택",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "메시지 보내기",
 	"Send message": "메시지 보내기",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "로그아웃",
 	"Sign up": "가입",
 	"Signing in": "",
-	"Source": "",
+	"Source": "출처",
 	"Speech recognition error: {{error}}": "음성 인식 오류: {{error}}",
 	"Speech-to-Text Engine": "음성-텍스트 엔진",
 	"SpeechRecognition API is not supported in this browser.": "이 브라우저에서는 SpeechRecognition API를 지원하지 않습니다.",
@@ -493,11 +494,12 @@
 	"What’s New in": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "기록 기능이 꺼져 있으면 이 브라우저의 새 채팅이 다른 장치의 채팅 기록에 나타나지 않습니다.",
 	"Whisper (Local)": "위스퍼 (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "프롬프트 제안 작성 (예: 당신은 누구인가요?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[주제 또는 키워드]에 대한 50단어 요약문 작성.",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "당신",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "당신은 유용한 어시스턴트입니다.",

+ 4 - 0
src/lib/i18n/locales/languages.json

@@ -47,6 +47,10 @@
 		"code": "fr-FR",
 		"title": "French (France)"
 	},
+	{
+		"code": "he-IL",
+		"title": "עברית (Hebrew)"
+	},
 	{
 		"code": "hi-IN",
 		"title": "Hindi (हिंदी)"

+ 495 - 0
src/lib/i18n/locales/lt-LT/translation.json

@@ -0,0 +1,495 @@
+{
+	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' arba '-1' kad neišteitų iš galiojimo.",
+	"(Beta)": "(Beta)",
+	"(e.g. `sh webui.sh --api`)": "(pvz. `sh webui.sh --api`)",
+	"(latest)": "(naujausias)",
+	"{{modelName}} is thinking...": "{{modelName}} mąsto...",
+	"{{user}}'s Chats": "{{user}} susirašinėjimai",
+	"{{webUIName}} Backend Required": "{{webUIName}} būtinas serveris",
+	"a user": "naudotojas",
+	"About": "Apie",
+	"Account": "Paskyra",
+	"Accurate information": "Tiksli informacija",
+	"Add a model": "Pridėti modelį",
+	"Add a model tag name": "Pridėti žymą modeliui",
+	"Add a short description about what this modelfile does": "Pridėti trumpą šio dokumento aprašymą",
+	"Add a short title for this prompt": "Pridėti trumpą šios užklausos pavadinimą",
+	"Add a tag": "Pridėti žymą",
+	"Add custom prompt": "Pridėti užklausos šabloną",
+	"Add Docs": "Pridėti dokumentų",
+	"Add Files": "Pridėti failus",
+	"Add message": "Pridėti žinutę",
+	"Add Model": "Pridėti modelį",
+	"Add Tags": "Pridėti žymas",
+	"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 Panel": "Administratorių panelė",
+	"Admin Settings": "Administratorių nustatymai",
+	"Advanced Parameters": "Gilieji nustatymai",
+	"all": "visi",
+	"All Documents": "Visi dokumentai",
+	"All Users": "Visi naudotojai",
+	"Allow": "Leisti",
+	"Allow Chat Deletion": "Leisti pokalbių ištrynimą",
+	"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 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",
+	"API RPM": "RPM API",
+	"April": "Balandis",
+	"Archive": "Archyvai",
+	"Archived Chats": "Archyvuoti pokalbiai",
+	"are allowed - Activate this command by typing": "leistina - aktyvuokite komandą rašydami",
+	"Are you sure?": "Are esate tikri?",
+	"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",
+	"Auto-send input after 3 sec.": "Automatiškai išsiųsti įvestį po 3 sek.",
+	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 bazės nuoroda",
+	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 bazės nuoroda reikalinga.",
+	"available!": "prieinama!",
+	"Back": "Atgal",
+	"Bad Response": "Neteisingas atsakymas",
+	"before": "prieš",
+	"Being lazy": "Būvimas tingiu",
+	"Builder Mode": "Statytojo rėžimas",
+	"Bypass SSL verification for Websites": "Išvengti SSL patikros puslapiams",
+	"Cancel": "Atšaukti",
+	"Categories": "Kategorijos",
+	"Change Password": "Keisti slaptažodį",
+	"Chat": "Pokalbis",
+	"Chat History": "Pokalbių istorija",
+	"Chat History is off for this browser.": "Šioje naršyklėje pokalbių istorija išjungta.",
+	"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",
+	"Click here for help.": "Paspauskite čia dėl pagalbos.",
+	"Click here to": "Paspauskite čia, kad:",
+	"Click here to check other modelfiles.": "Paspauskite čia norėdami ieškoti modelių failų.",
+	"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 documents.": "Spauskite čia norėdami pasirinkti dokumentus.",
+	"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ę.",
+	"Close": "Uždaryti",
+	"Collection": "Kolekcija",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI bazės nuoroda",
+	"ComfyUI Base URL is required.": "ComfyUI bazės nuoroda privaloma",
+	"Command": "Command",
+	"Confirm Password": "Patvirtinkite slaptažodį",
+	"Connections": "Ryšiai",
+	"Content": "Turinys",
+	"Context Length": "Konteksto ilgis",
+	"Continue Response": "Tęsti atsakymą",
+	"Conversation Mode": "Pokalbio metodas",
+	"Copied shared chat URL to clipboard!": "Nukopijavote pokalbio nuorodą",
+	"Copy": "Kopijuoti",
+	"Copy last code block": "Kopijuoti paskutinį kodo bloką",
+	"Copy last response": "Kopijuoti paskutinį atsakymą",
+	"Copy Link": "Kopijuoti nuorodą",
+	"Copying to clipboard was successful!": "La copie dans le presse-papiers a réussi !",
+	"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':": "Créez une phrase concise de 3-5 mots comme en-tête pour la requête suivante, en respectant strictement la limite de 3-5 mots et en évitant l'utilisation du mot 'titre' :",
+	"Create a modelfile": "Créer un fichier de modèle",
+	"Create Account": "Créer un compte",
+	"Create new key": "Sukurti naują raktą",
+	"Create new secret key": "Sukurti naują slaptą raktą",
+	"Created at": "Sukurta",
+	"Created At": "Sukurta",
+	"Current Model": "Dabartinis modelis",
+	"Current Password": "Esamas slaptažodis",
+	"Custom": "Personalizuota",
+	"Customize Ollama models for a specific purpose": "Personalizuoti Ollama modelius",
+	"Dark": "Tamsus",
+	"Dashboard": "Skydelis",
+	"Database": "Duomenų bazė",
+	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
+	"December": "Gruodis",
+	"Default": "Numatytasis",
+	"Default (Automatic1111)": "Numatytasis (Automatic1111)",
+	"Default (SentenceTransformers)": "Numatytasis (SentenceTransformers)",
+	"Default (Web API)": "Numatytasis (API Web)",
+	"Default model updated": "Numatytasis modelis atnaujintas",
+	"Default Prompt Suggestions": "Numatytieji užklausų pasiūlymai",
+	"Default User Role": "Numatytoji naudotojo rolė",
+	"delete": "ištrinti",
+	"Delete": "ištrinti",
+	"Delete a model": "Ištrinti modėlį",
+	"Delete chat": "Išrinti pokalbį",
+	"Delete Chat": "Ištrinti pokalbį",
+	"Delete Chats": "Ištrinti pokalbį",
+	"delete this link": "Ištrinti nuorodą",
+	"Delete User": "Ištrinti naudotoją",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} ištrinta",
+	"Deleted {{tagName}}": "{{tagName}} ištrinta",
+	"Description": "Aprašymas",
+	"Didn't fully follow instructions": "Pilnai nesekė instrukcijų",
+	"Disabled": "Neaktyvuota",
+	"Discover a modelfile": "Atrasti modelio failą",
+	"Discover a prompt": "Atrasti užklausas",
+	"Discover, download, and explore custom prompts": "Atrasti ir parsisiųsti užklausas",
+	"Discover, download, and explore model presets": "Atrasti ir parsisiųsti modelių konfigūracija",
+	"Display the username instead of You in the Chat": "Rodyti naudotojo vardą vietoje žodžio Jūs pokalbyje",
+	"Document": "Dokumentas",
+	"Document Settings": "Dokumento nuostatos",
+	"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 Allow": "Neleisti",
+	"Don't have an account?": "Neturite paskyros?",
+	"Don't like the style": "Nepatinka stilius",
+	"Download": "Parsisiųsti",
+	"Download canceled": "Parsisiuntimas atšauktas",
+	"Download Database": "Parsisiųsti duomenų bazę",
+	"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 Doc": "Redaguoti dokumentą",
+	"Edit User": "Redaguoti naudotoją",
+	"Email": "El. paštas",
+	"Embedding Model": "Embedding modelis",
+	"Embedding Model Engine": "Embedding modelio variklis",
+	"Embedding model set to \"{{embedding_model}}\"": "Embedding modelis nustatytas kaip\"{{embedding_model}}\"",
+	"Enable Chat History": "Aktyvuoti pokalbių istoriją",
+	"Enable New Sign Ups": "Aktyvuoti naujas registracijas",
+	"Enabled": "Aktyvuota",
+	"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 Chunk Overlap": "Įveskite blokų persidengimą",
+	"Enter Chunk Size": "Įveskite blokų dydį",
+	"Enter Image Size (e.g. 512x512)": "Įveskite paveiksliuko dydį (pvz. 512x512)",
+	"Enter language codes": "Įveskite kalbos kodus",
+	"Enter LiteLLM API Base URL (litellm_params.api_base)": "Lite LLM API nuoroda (litellm_params.api_base)",
+	"Enter LiteLLM API Key (litellm_params.api_key)": "Lite LLM API raktas (litellm_params.api_key)",
+	"Enter LiteLLM API RPM (litellm_params.rpm)": "Lite LLM API RPM (litellm_params.rpm)",
+	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM modelis (litellm_params.model)",
+	"Enter Max Tokens (litellm_params.max_tokens)": "Įveskite maksimalų žetonų skaičių (litellm_params.max_tokens)",
+	"Enter model tag (e.g. {{modelTag}})": "Įveskite modelio žymą (pvz. {{modelTag}})",
+	"Enter Number of Steps (e.g. 50)": "Įveskite žingsnių kiekį (pvz. 50)",
+	"Enter Score": "Įveskite rezultatą",
+	"Enter stop sequence": "Įveskite pabaigos sekvenciją",
+	"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 Password": "Įveskite slaptažodį",
+	"Enter Your Role": "Įveskite savo rolę",
+	"Experimental": "Eksperimentinis",
+	"Export All Chats (All Users)": "Eksportuoti visų naudotojų visus pokalbius",
+	"Export Chats": "Eksportuoti pokalbius",
+	"Export Documents Mapping": "Eksportuoti dokumentų žemėlapį",
+	"Export Modelfiles": "Eksportuoti modelių failus",
+	"Export Prompts": "Eksportuoti užklausas",
+	"Failed to create API Key.": "Nepavyko sukurti API rakto",
+	"Failed to read clipboard contents": "Nepavyko perskaityti kopijuoklės",
+	"February": "Vasaris",
+	"Feel free to add specific details": "Galite pridėti specifinių detalių",
+	"File Mode": "Dokumentų rėžimas",
+	"File not found.": "Failas nerastas.",
+	"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į",
+	"Followed instructions perfectly": "Tobulai sekė instrukcijas",
+	"Format your variables using square brackets like this:": "Formatuokite kintamuosius su kvadratiniais skliausteliais:",
+	"From (Base Model)": "Iš (bazinis modelis)",
+	"Full Screen Mode": "Pilno ekrano rėžimas",
+	"General": "Bendri",
+	"General Settings": "Bendri nustatymai",
+	"Generation Info": "Generavimo informacija",
+	"Good Response": "Geras atsakymas",
+	"has no conversations.": "neturi pokalbių",
+	"Hello, {{name}}": "Sveiki, {{name}}",
+	"Help": "Pagalba",
+	"Hide": "Paslėpti",
+	"Hide Additional Params": "Pridėti papildomus parametrus",
+	"How can I help you today?": "Kuo galėčiau Jums padėti ?",
+	"Hybrid Search": "Hibridinė paieška",
+	"Image Generation (Experimental)": "Vaizdų generavimas (eksperimentinis)",
+	"Image Generation Engine": "Vaizdų generavimo variklis",
+	"Image Settings": "Vaizdų nustatymai",
+	"Images": "Vaizdai",
+	"Import Chats": "Importuoti pokalbius",
+	"Import Documents Mapping": "Importuoti dokumentų žemėlapį",
+	"Import Modelfiles": "Importuoti modelio failus",
+	"Import Prompts": "Importuoti užklausas",
+	"Include `--api` flag when running stable-diffusion-webui": "Pridėti `--api` kai vykdomas stable-diffusion-webui",
+	"Input commands": "Įvesties komandos",
+	"Interface": "Sąsaja",
+	"Invalid Tag": "Neteisinga žyma",
+	"January": "Sausis",
+	"join our Discord for help.": "prisijunkite prie mūsų Discord.",
+	"JSON": "JSON",
+	"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",
+	"Language": "Kalba",
+	"Last Active": "Paskutinį kartą aktyvus",
+	"Light": "Šviesus",
+	"Listening...": "Klauso...",
+	"LLMs can make mistakes. Verify important information.": "Dideli kalbos modeliai gali klysti. Patikrinkite atsakymų teisingumą.",
+	"Made by OpenWebUI Community": "Sukurta OpenWebUI bendruomenės",
+	"Make sure to enclose them with": "Užtikrinktie, kad įtraukiate viduje:",
+	"Manage LiteLLM Models": "Tvarkyti LiteLLM modelus",
+	"Manage Models": "Tvarkyti modelius",
+	"Manage Ollama Models": "Tvarkyti Ollama modelius",
+	"March": "Kovas",
+	"Max Tokens": "Maksimalūs žetonai",
+	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Daugiausiai trys modeliai gali būti parsisiunčiami vienu metu.",
+	"May": "gegužė",
+	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "Žinutės, kurias siunčia po pasidalinimo nebus matomos nuorodos turėtojams.",
+	"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",
+	"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}} already exists.": "Modelis {{modelName}} jau egzistuoja.",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
+	"Model Name": "Modelio pavadinimas",
+	"Model not selected": "Modelis nepasirinktas",
+	"Model Tag Name": "Modelio žymos pavadinimas",
+	"Model Whitelisting": "Modeliu baltasis sąrašas",
+	"Model(s) Whitelisted": "Modelis baltąjame sąraše",
+	"Modelfile": "Modelio failas",
+	"Modelfile Advanced Settings": "Pažengę nustatymai",
+	"Modelfile Content": "Modelio failo turinys",
+	"Modelfiles": "Modelio failai",
+	"Models": "Modeliai",
+	"More": "Daugiau",
+	"My Documents": "Mano dokumentai",
+	"My Modelfiles": "Mano modelių failai",
+	"My Prompts": "Mano užklausos",
+	"Name": "Pavadinimas",
+	"Name Tag": "Žymos pavadinimas",
+	"Name your modelfile": "Modelio failo pavadinimas",
+	"New Chat": "Naujas pokalbis",
+	"New Password": "Naujas slaptažodis",
+	"No results found": "Rezultatų nerasta",
+	"No source available": "Šaltinių nerasta",
+	"Not factually correct": "Faktiškai netikslu",
+	"Not sure what to add?": "Nežinote ką pridėti ?",
+	"Not sure what to write? Switch to": "Nežinoti ką rašyti ? Pakeiskite į",
+	"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į",
+	"Notifications": "Pranešimai",
+	"November": "lapkritis",
+	"October": "spalis",
+	"Off": "Išjungta",
+	"Okay, Let's Go!": "Gerai, važiuojam!",
+	"OLED Dark": "OLED tamsus",
+	"Ollama": "Ollama",
+	"Ollama Base URL": "Ollama nuoroda",
+	"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.",
+	"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Jūsų failai vis dar tvarkomi.",
+	"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! 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": "Atverti",
+	"Open AI": "Open AI",
+	"Open AI (Dall-E)": "Open AI (Dall-E)",
+	"Open new chat": "Atverti naują pokalbį",
+	"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",
+	"Overview": "Apžvalga",
+	"Parameters": "Nustatymai",
+	"Password": "Slaptažodis",
+	"PDF document (.pdf)": "PDF dokumentas (.pdf)",
+	"PDF Extract Images (OCR)": "PDF paveikslėlių skaitymas (OCR)",
+	"pending": "laukiama",
+	"Permission denied when accessing microphone: {{error}}": "Leidimas naudoti mikrofoną atmestas: {{error}}",
+	"Plain text (.txt)": "Grynas tekstas (.txt)",
+	"Playground": "Eksperimentavimo erdvė",
+	"Positive attitude": "Pozityvus elgesys",
+	"Previous 30 days": "Paskutinės 30 dienų",
+	"Previous 7 days": "Paskutinės 7 dienos",
+	"Profile Image": "Profilio nuotrauka",
+	"Prompt": "Užklausa",
+	"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",
+	"Pull Progress": "Parsisintimo progresas",
+	"Query Params": "Užklausos parametrai",
+	"RAG Template": "RAG šablonas",
+	"Raw Format": "Grynasis formatas",
+	"Read Aloud": "Skaityti garsiai",
+	"Record voice": "Įrašyti balsą",
+	"Redirecting you to OpenWebUI Community": "Perkeliam Jus į OpenWebUI bendruomenę",
+	"Refused when it shouldn't have": "Atmesta kai neturėtų būti atmesta",
+	"Regenerate": "Generuoti iš naujo",
+	"Release Notes": "Naujovės",
+	"Remove": "Pašalinti",
+	"Remove Model": "Pašalinti modelį",
+	"Rename": "Pervadinti",
+	"Repeat Last N": "Pakartoti paskutinius N",
+	"Repeat Penalty": "Kartojimosi bauda",
+	"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 Vector Storage": "Reinicializuoti vektorių atmintį",
+	"Response AutoCopy to Clipboard": "Automatiškai nukopijuoti atsakymą",
+	"Role": "Rolė",
+	"Rosé Pine": "Rosé Pine",
+	"Rosé Pine Dawn": "Rosé Pine Dawn",
+	"Save": "Išsaugoti",
+	"Save & Create": "Išsaugoti ir sukurti",
+	"Save & Submit": "Išsaugoti ir pateikti",
+	"Save & Update": "Išsaugoti ir atnaujinti",
+	"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.",
+	"Scan": "Skenuoti",
+	"Scan complete!": "Skenavimas baigtas!",
+	"Scan for documents from {{path}}": "Skenuoti dokumentus iš {{path}}",
+	"Search": "Ieškoti",
+	"Search a model": "Ieškoti modelio",
+	"Search Documents": "Ieškoti dokumentų",
+	"Search Prompts": "Ieškoti užklausų",
+	"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 mode": "Pasirinkti režimą",
+	"Select a model": "Pasirinkti modelį",
+	"Select an Ollama instance": "Pasirinkti Ollama instanciją",
+	"Select model": "Pasirinkti modelį",
+	"Send a Message": "Siųsti žinutę",
+	"Send message": "Siųsti žinutę",
+	"September": "rugsėjis",
+	"Server connection verified": "Serverio sujungimas patvirtintas",
+	"Set as default": "Nustatyti numatytąjį",
+	"Set Default Model": "Nustatyti numatytąjį modelį",
+	"Set embedding model (e.g. {{model}})": "Nustatyti embedding modelį",
+	"Set Image Size": "Nustatyti paveikslėlių dydį",
+	"Set Model": "Nustatyti modelį",
+	"Set reranking model (e.g. {{model}})": "Nustatyti reranking modelį",
+	"Set Steps": "Numatyti etapus",
+	"Set Title Auto-Generation Model": "Numatyti pavadinimų generavimo modelį",
+	"Set Voice": "Numatyti balsą",
+	"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 Additional Params": "Rodyti papildomus parametrus",
+	"Show shortcuts": "Rodyti trumpinius",
+	"Showcased creativity": "Kūrybingų užklausų paroda",
+	"sidebar": "šoninis meniu",
+	"Sign in": "Prisijungti",
+	"Sign Out": "Atsijungti",
+	"Sign up": "Sukurti paskyrą",
+	"Signing in": "Prisijungiama",
+	"Source": "Šaltinis",
+	"Speech recognition error: {{error}}": "Balso atpažinimo problema: {{error}}",
+	"Speech-to-Text Engine": "Balso atpažinimo modelis",
+	"SpeechRecognition API is not supported in this browser.": "Šioje naršyklėje negalimas balso atpažinimas.",
+	"Stop Sequence": "Baigt sekvenciją",
+	"STT Settings": "STT nustatymai",
+	"Submit": "Pateikti",
+	"Subtitle (e.g. about the Roman Empire)": "Subtitras",
+	"Success": "Sėkmingai",
+	"Successfully updated.": "Sėkmingai atnaujinta.",
+	"Suggested": "Siūloma",
+	"Sync All": "Viską sinhronizuoti",
+	"System": "Sistema",
+	"System Prompt": "Sistemos užklausa",
+	"Tags": "Žymos",
+	"Tell us more:": "Papasakokite daugiau",
+	"Temperature": "Temperatūra",
+	"Template": "Modelis",
+	"Text Completion": "Teksto pildymas",
+	"Text-to-Speech Engine": "Balso sintezės modelis",
+	"Tfs Z": "Tfs Z",
+	"Thanks for your feedback!": "Ačiū už atsiliepimus",
+	"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",
+	"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 setting does not sync across browsers or devices.": "Šis parametras nesisinchronizuoja su skirtingomis naršyklėmis ir įrankiais.",
+	"Thorough explanation": "Platus paaiškinimas",
+	"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": "kam",
+	"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 chat input.": "į pokalbio įvestį",
+	"Today": "Šiandien",
+	"Toggle settings": "Atverti/užverti parametrus",
+	"Toggle sidebar": "Atverti/užverti šoninį meniu",
+	"Top K": "Top K",
+	"Top P": "Top P",
+	"Trouble accessing Ollama?": "Problemos prieinant prie Ollama?",
+	"TTS Settings": "TTS parametrai",
+	"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.",
+	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Nepažįstamas '{{file_type}}' failo formatas, tačiau jis priimtas ir bus apdorotas kaip grynas tekstas",
+	"Update and Copy Link": "Atnaujinti ir kopijuoti nuorodą",
+	"Update password": "Atnaujinti slaptažodį",
+	"Upload a GGUF model": "Parsisiųsti GGUF modelį",
+	"Upload files": "Įkelti failus",
+	"Upload Progress": "Įkėlimo progresas",
+	"URL Mode": "URL režimas",
+	"Use '#' in the prompt input to load and select your documents.": "Naudokite '#' norėdami naudoti dokumentą.",
+	"Use Gravatar": "Naudoti Gravatar",
+	"Use Initials": "Naudotojo inicialai",
+	"user": "naudotojas",
+	"User Permissions": "Naudotojo leidimai",
+	"Users": "Naudotojai",
+	"Utilize": "Naudoti",
+	"Valid time units:": "Teisingūs laiko vienetai :",
+	"variable": "kintamasis",
+	"variable to have them replaced with clipboard content.": "kintamoji pakeičiama kopijuoklės turiniu.",
+	"Version": "Versija",
+	"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 Loader Settings": "Web krovimo nustatymai",
+	"Web Params": "Web nustatymai",
+	"Webhook URL": "Webhook nuoroda",
+	"WebUI Add-ons": "WebUI priedai",
+	"WebUI Settings": "WebUI parametrai",
+	"WebUI will make requests to": "WebUI vykdys užklausas",
+	"What’s New in": "Kas naujo",
+	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kai istorija išjungta, pokalbiai neatsiras jūsų istorijoje.",
+	"Whisper (Local)": "Whisper (lokalus)",
+	"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]",
+	"Yesterday": "Vakar",
+	"You": "Jūs",
+	"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ę.",
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube krovimo nustatymai"
+}

+ 12 - 10
src/lib/i18n/locales/nl-NL/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Bouwer Modus",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Annuleren",
 	"Categories": "Categorieën",
 	"Change Password": "Wijzig Wachtwoord",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Chat Geschiedenis",
 	"Chat History is off for this browser.": "Chat Geschiedenis is uitgeschakeld voor deze browser.",
 	"Chats": "Chats",
@@ -76,8 +78,8 @@
 	"Chunk Overlap": "Chunk Overlap",
 	"Chunk Params": "Chunk Params",
 	"Chunk Size": "Chunk Grootte",
-	"Citation": "",
-	"Click here for help.": "Klik hier voor help.",
+	"Citation": "Citaat",
+	"Click here for help.": "Klik hier voor hulp.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Klik hier om andere modelfiles te controleren.",
 	"Click here to select": "Klik hier om te selecteren",
@@ -118,7 +120,6 @@
 	"Dark": "Donker",
 	"Dashboard": "",
 	"Database": "Database",
-	"DD/MM/YYYY HH:mm": "YYYY/MM/DD HH:mm",
 	"December": "",
 	"Default": "Standaard",
 	"Default (Automatic1111)": "Standaard (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Algemene Instellingen",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Hallo, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Max Tokens",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maximaal 3 modellen kunnen tegelijkertijd worden gedownload. Probeer het later opnieuw.",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Modelfiles",
 	"Models": "Modellen",
 	"More": "",
-	"My Documents": "Mijn Documenten",
-	"My Modelfiles": "Mijn Modelfiles",
-	"My Prompts": "Mijn Prompts",
 	"Name": "Naam",
 	"Name Tag": "Naam Tag",
 	"Name your modelfile": "Benoem je modelfile",
@@ -295,7 +295,7 @@
 	"New Password": "Nieuw Wachtwoord",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "Geen bron beschikbaar",
 	"Not factually correct": "",
 	"Not sure what to add?": "Niet zeker wat toe te voegen?",
 	"Not sure what to write? Switch to": "Niet zeker wat te schrijven? Schakel over naar",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "PDF Extract Afbeeldingen (OCR)",
 	"pending": "wachtend",
 	"Permission denied when accessing microphone: {{error}}": "Toestemming geweigerd bij toegang tot microfoon: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Speeltuin",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Opslaan",
 	"Save & Create": "Opslaan & Creëren",
-	"Save & Submit": "Opslaan & Verzenden",
 	"Save & Update": "Opslaan & Bijwerken",
 	"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",
 	"Scan": "Scan",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Selecteer een Ollama instantie",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Stuur een Bericht",
 	"Send message": "Stuur bericht",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Uitloggen",
 	"Sign up": "Registreren",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Bron",
 	"Speech recognition error: {{error}}": "Spraakherkenning fout: {{error}}",
 	"Speech-to-Text Engine": "Spraak-naar-tekst Engine",
 	"SpeechRecognition API is not supported in this browser.": "SpeechRecognition API wordt niet ondersteund in deze browser.",
@@ -493,11 +494,12 @@
 	"What’s New in": "Wat is nieuw in",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wanneer geschiedenis is uitgeschakeld, zullen nieuwe chats op deze browser niet verschijnen in je geschiedenis op een van je apparaten.",
 	"Whisper (Local)": "Fluister (Lokaal)",
+	"Workspace": "",
 	"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.",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "Jij",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Jij bent een behulpzame assistent.",

+ 148 - 146
src/lib/i18n/locales/pl-PL/translation.json

@@ -4,31 +4,31 @@
 	"(e.g. `sh webui.sh --api`)": "(np. `sh webui.sh --api`)",
 	"(latest)": "(najnowszy)",
 	"{{modelName}} is thinking...": "{{modelName}} myśli...",
-	"{{user}}'s Chats": "",
+	"{{user}}'s Chats": "{{user}} - czaty",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} wymagane",
 	"a user": "użytkownik",
 	"About": "O nas",
 	"Account": "Konto",
-	"Accurate information": "",
+	"Accurate information": "Dokładna informacja",
 	"Add a model": "Dodaj model",
 	"Add a model tag name": "Dodaj nazwę tagu modelu",
 	"Add a short description about what this modelfile does": "Dodaj krótki opis tego, co robi ten plik modelu",
 	"Add a short title for this prompt": "Dodaj krótki tytuł tego polecenia",
 	"Add a tag": "Dodaj tag",
-	"Add custom prompt": "",
+	"Add custom prompt": "Dodaj własne polecenie",
 	"Add Docs": "Dodaj dokumenty",
 	"Add Files": "Dodaj pliki",
 	"Add message": "Dodaj wiadomość",
-	"Add Model": "",
-	"Add Tags": "dodaj tagi",
-	"Add User": "",
+	"Add Model": "Dodaj model",
+	"Add Tags": "Dodaj tagi",
+	"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 Panel": "Panel administracyjny",
 	"Admin Settings": "Ustawienia administratora",
 	"Advanced Parameters": "Zaawansowane parametry",
 	"all": "wszyscy",
-	"All Documents": "",
+	"All Documents": "Wszystkie dokumenty",
 	"All Users": "Wszyscy użytkownicy",
 	"Allow": "Pozwól",
 	"Allow Chat Deletion": "Pozwól na usuwanie czatu",
@@ -36,36 +36,38 @@
 	"Already have an account?": "Masz już konto?",
 	"an assistant": "asystent",
 	"and": "i",
-	"and create a new shared link.": "",
+	"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.": "",
-	"API keys": "",
+	"API Key created.": "Klucz API utworzony.",
+	"API keys": "Klucze API",
 	"API RPM": "Pakiet API RPM",
-	"April": "",
-	"Archive": "",
-	"Archived Chats": "",
+	"April": "Kwiecień",
+	"Archive": "Archiwum",
+	"Archived Chats": "Zarchiwizowane czaty",
 	"are allowed - Activate this command by typing": "są dozwolone - Aktywuj to polecenie, wpisując",
 	"Are you sure?": "Jesteś pewien?",
-	"Attach file": "",
-	"Attention to detail": "",
+	"Attach file": "Dołącz plik",
+	"Attention to detail": "Dbałość o szczegóły",
 	"Audio": "Dźwięk",
-	"August": "",
+	"August": "Sierpień",
 	"Auto-playback response": "Odtwarzanie automatyczne odpowiedzi",
 	"Auto-send input after 3 sec.": "Wysyłanie automatyczne po 3 sek.",
 	"AUTOMATIC1111 Base URL": "Podstawowy adres URL AUTOMATIC1111",
 	"AUTOMATIC1111 Base URL is required.": "Podstawowy adres URL AUTOMATIC1111 jest wymagany.",
 	"available!": "dostępny!",
 	"Back": "Wstecz",
-	"Bad Response": "",
-	"before": "",
-	"Being lazy": "",
+	"Bad Response": "Zła odpowiedź",
+	"before": "przed",
+	"Being lazy": "Jest leniwy",
+	"Beta": "",
 	"Builder Mode": "Tryb budowniczego",
-	"Bypass SSL verification for Websites": "",
+	"Bypass SSL verification for Websites": "Pomiń weryfikację SSL dla stron webowych",
 	"Cancel": "Anuluj",
 	"Categories": "Kategorie",
 	"Change Password": "Zmień hasło",
 	"Chat": "Czat",
+	"Chat Bubble UI": "",
 	"Chat History": "Historia czatu",
 	"Chat History is off for this browser.": "Historia czatu jest wyłączona dla tej przeglądarki.",
 	"Chats": "Czaty",
@@ -76,69 +78,68 @@
 	"Chunk Overlap": "Zachodzenie bloku",
 	"Chunk Params": "Parametry bloku",
 	"Chunk Size": "Rozmiar bloku",
-	"Citation": "",
+	"Citation": "Cytat",
 	"Click here for help.": "Kliknij tutaj, aby uzyskać pomoc.",
-	"Click here to": "",
+	"Click here to": "Kliknij tutaj, żeby",
 	"Click here to check other modelfiles.": "Kliknij tutaj, aby sprawdzić inne pliki modelowe.",
 	"Click here to select": "Kliknij tutaj, aby wybrać",
-	"Click here to select a csv file.": "",
+	"Click here to select a csv file.": "Kliknij tutaj, żeby wybrać plik CSV",
 	"Click here to select documents.": "Kliknij tutaj, aby wybrać dokumenty.",
 	"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.",
 	"Close": "Zamknij",
 	"Collection": "Kolekcja",
-	"ComfyUI": "",
-	"ComfyUI Base URL": "",
-	"ComfyUI Base URL is required.": "",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "Bazowy URL ComfyUI",
+	"ComfyUI Base URL is required.": "Bazowy URL ComfyUI jest wymagany.",
 	"Command": "Polecenie",
 	"Confirm Password": "Potwierdź hasło",
 	"Connections": "Połączenia",
 	"Content": "Zawartość",
 	"Context Length": "Długość kontekstu",
-	"Continue Response": "",
+	"Continue Response": "Kontynuuj odpowiedź",
 	"Conversation Mode": "Tryb rozmowy",
-	"Copied shared chat URL to clipboard!": "",
-	"Copy": "",
+	"Copied shared chat URL to clipboard!": "Skopiowano URL czatu do schowka!",
+	"Copy": "Kopiuj",
 	"Copy last code block": "Skopiuj ostatni blok kodu",
 	"Copy last response": "Skopiuj ostatnią odpowiedź",
-	"Copy Link": "",
+	"Copy Link": "Kopiuj link",
 	"Copying to clipboard was successful!": "Kopiowanie do schowka zakończone powodzeniem!",
 	"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':": "Utwórz zwięzłą frazę składającą się z 3-5 słów jako nagłówek dla następującego zapytania, ściśle przestrzegając limitu od 3 do 5 słów i unikając użycia słowa 'tytuł':",
 	"Create a modelfile": "Utwórz plik modelu",
 	"Create Account": "Utwórz konto",
-	"Create new key": "",
-	"Create new secret key": "",
+	"Create new key": "Utwórz nowy klucz",
+	"Create new secret key": "Utwórz nowy klucz bezpieczeństwa",
 	"Created at": "Utworzono o",
-	"Created At": "",
+	"Created At": "Utworzono o",
 	"Current Model": "Bieżący model",
 	"Current Models": "",
 	"Current Password": "Bieżące hasło",
 	"Custom": "Niestandardowy",
 	"Customize Ollama models for a specific purpose": "Dostosuj modele Ollama do określonego celu",
 	"Dark": "Ciemny",
-	"Dashboard": "",
+	"Dashboard": "Dashboard",
 	"Database": "Baza danych",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
-	"December": "",
+	"December": "Grudzień",
 	"Default": "Domyślny",
 	"Default (Automatic1111)": "Domyślny (Automatic1111)",
-	"Default (SentenceTransformers)": "",
-	"Default (Web API)": "Domyślny (Interfejs API)",
+	"Default (SentenceTransformers)": "Domyślny (SentenceTransformers)",
+	"Default (Web API)": "Domyślny (Web API)",
 	"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": "",
+	"delete": "usuń",
+	"Delete": "Usuń",
 	"Delete a model": "Usuń model",
 	"Delete chat": "Usuń czat",
-	"Delete Chat": "",
+	"Delete Chat": "Usuń czat",
 	"Delete Chats": "Usuń czaty",
-	"delete this link": "",
-	"Delete User": "",
+	"delete this link": "usuń ten link",
+	"Delete User": "Usuń użytkownika",
 	"Deleted {{deleteModelTag}}": "Usunięto {{deleteModelTag}}",
-	"Deleted {{tagName}}": "",
+	"Deleted {{tagName}}": "Usunięto {{tagName}}",
 	"Description": "Opis",
-	"Didn't fully follow instructions": "",
+	"Didn't fully follow instructions": "Nie postępował zgodnie z instrukcjami",
 	"Disabled": "Wyłączone",
 	"Discover a modelfile": "Odkryj plik modelu",
 	"Discover a prompt": "Odkryj prompt",
@@ -151,28 +152,28 @@
 	"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 Allow": "Nie zezwalaj",
 	"Don't have an account?": "Nie masz konta?",
-	"Don't like the style": "",
-	"Download": "",
-	"Download canceled": "",
+	"Don't like the style": "Nie podobał mi się styl",
+	"Download": "Pobieranie",
+	"Download canceled": "Pobieranie przerwane",
 	"Download Database": "Pobierz bazę danych",
 	"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": "",
+	"Edit": "Edytuj",
 	"Edit Doc": "Edytuj dokument",
 	"Edit User": "Edytuj użytkownika",
 	"Email": "Email",
-	"Embedding Model": "",
-	"Embedding Model Engine": "",
-	"Embedding model set to \"{{embedding_model}}\"": "",
+	"Embedding Model": "Model osadzania",
+	"Embedding Model Engine": "Silnik modelu osadzania",
+	"Embedding model set to \"{{embedding_model}}\"": "Model osadzania ustawiono na \"{{embedding_model}}\"",
 	"Enable Chat History": "Włącz historię czatu",
 	"Enable New Sign Ups": "Włącz nowe rejestracje",
 	"Enabled": "Włączone",
-	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
+	"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 Chunk Overlap": "Wprowadź zakchodzenie bloku",
 	"Enter Chunk Size": "Wprowadź rozmiar bloku",
 	"Enter Image Size (e.g. 512x512)": "Wprowadź rozmiar obrazu (np. 512x512)",
-	"Enter language codes": "",
+	"Enter language codes": "Wprowadź kody języków",
 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "Wprowadź bazowy adres URL LiteLLM API (litellm_params.api_base)",
 	"Enter LiteLLM API Key (litellm_params.api_key)": "Wprowadź klucz API LiteLLM (litellm_params.api_key)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Wprowadź API LiteLLM RPM(litellm_params.rpm)",
@@ -181,69 +182,70 @@
 	"Enter Model Display Name": "",
 	"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 Score": "",
+	"Enter Score": "Wprowadź wynik",
 	"Enter stop sequence": "Wprowadź sekwencję zatrzymania",
 	"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)": "",
+	"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 Password": "Wprowadź swoje hasło",
-	"Enter Your Role": "",
+	"Enter Your Role": "Wprowadź swoją rolę",
 	"Experimental": "Eksperymentalne",
 	"Export All Chats (All Users)": "Eksportuj wszystkie czaty (wszyscy użytkownicy)",
 	"Export Chats": "Eksportuj czaty",
 	"Export Documents Mapping": "Eksportuj mapowanie dokumentów",
 	"Export Modelfiles": "Eksportuj pliki modeli",
 	"Export Prompts": "Eksportuj prompty",
-	"Failed to create API Key.": "",
+	"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",
-	"February": "",
-	"Feel free to add specific details": "",
+	"February": "Luty",
+	"Feel free to add specific details": "Podaj inne szczegóły",
 	"File Mode": "Tryb pliku",
 	"File not found.": "Plik nie został znaleziony.",
 	"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",
-	"Followed instructions perfectly": "",
+	"Followed instructions perfectly": "Postępował z idealnie według instrukcji",
 	"Format your variables using square brackets like this:": "Formatuj swoje zmienne, używając nawiasów kwadratowych, np.",
 	"From (Base Model)": "Z (Model Podstawowy)",
 	"Full Screen Mode": "Tryb pełnoekranowy",
 	"General": "Ogólne",
 	"General Settings": "Ogólne ustawienia",
-	"Generation Info": "",
-	"Good Response": "",
-	"has no conversations.": "",
+	"Generation Info": "Informacja o generacji",
+	"Good Response": "Dobra odpowiedź",
+	"h:mm a": "",
+	"has no conversations.": "nie ma rozmów.",
 	"Hello, {{name}}": "Witaj, {{name}}",
-	"Help": "",
+	"Help": "Pomoc",
 	"Hide": "Ukryj",
 	"Hide Additional Params": "Ukryj dodatkowe parametry",
 	"How can I help you today?": "Jak mogę Ci dzisiaj pomóc?",
-	"Hybrid Search": "",
+	"Hybrid Search": "Szukanie hybrydowe",
 	"Image Generation (Experimental)": "Generowanie obrazu (eksperymentalne)",
 	"Image Generation Engine": "Silnik generowania obrazu",
 	"Image Settings": "Ustawienia obrazu",
 	"Images": "Obrazy",
-	"Import Chats": "Importuj rozmowy",
+	"Import Chats": "Importuj czaty",
 	"Import Documents Mapping": "Importuj mapowanie dokumentów",
 	"Import Modelfiles": "Importuj pliki modeli",
 	"Import Prompts": "Importuj prompty",
 	"Include `--api` flag when running stable-diffusion-webui": "Dołącz flagę `--api` podczas uruchamiania stable-diffusion-webui",
-	"Input commands": "",
+	"Input commands": "Wprowadź komendy",
 	"Interface": "Interfejs",
-	"Invalid Tag": "",
+	"Invalid Tag": "Nieprawidłowy tag",
 	"Is Model Vision Capable": "",
-	"January": "",
+	"January": "Styczeń",
 	"join our Discord for help.": "Dołącz do naszego Discorda po pomoc.",
 	"JSON": "JSON",
-	"July": "",
-	"June": "",
+	"July": "Lipiec",
+	"June": "Czerwiec",
 	"JWT Expiration": "Wygaśnięcie JWT",
 	"JWT Token": "Token JWT",
 	"Keep Alive": "Zachowaj łączność",
 	"Keyboard shortcuts": "Skróty klawiszowe",
 	"Language": "Język",
-	"Last Active": "",
+	"Last Active": "Ostatnio aktywny",
 	"Light": "Jasny",
 	"Listening...": "Nasłuchiwanie...",
 	"LLMs can make mistakes. Verify important information.": "LLMy mogą popełniać błędy. Zweryfikuj ważne informacje.",
@@ -253,17 +255,18 @@
 	"Manage Model Information": "",
 	"Manage Models": "Zarządzaj modelami",
 	"Manage Ollama Models": "Zarządzaj modelami Ollama",
-	"March": "",
+	"March": "Marzec",
 	"Max Tokens": "Maksymalna liczba tokenów",
 	"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": "",
-	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
-	"Minimum Score": "",
+	"May": "Maj",
+	"Memory": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "WIadomości, które wyślesz po utworzeniu linku nie będą udostępnione. Użytkownicy z URL-em będą mogli zobaczyć udostępniony czat.",
+	"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",
 	"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",
@@ -284,29 +287,26 @@
 	"Modelfile Content": "Zawartość pliku modelu",
 	"Modelfiles": "Pliki modeli",
 	"Models": "Modele",
-	"More": "",
-	"My Documents": "Moje dokumenty",
-	"My Modelfiles": "Moje pliki modeli",
-	"My Prompts": "Moje prompty",
+	"More": "Więcej",
 	"Name": "Nazwa",
 	"Name Tag": "Etykieta nazwy",
 	"Name your modelfile": "Nadaj nazwę swojemu plikowi modelu",
 	"New Chat": "Nowy czat",
 	"New Password": "Nowe hasło",
 	"No": "",
-	"No results found": "",
-	"No source available": "",
-	"Not factually correct": "",
+	"No results found": "Nie znaleziono rezultatów",
+	"No source available": "Źródło nie dostępne",
+	"Not factually correct": "Nie zgodne z faktami",
 	"Not sure what to add?": "Nie wiesz, co dodać?",
 	"Not sure what to write? Switch to": "Nie wiesz, co napisać? Przełącz się na",
-	"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: 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.",
 	"Notifications": "Powiadomienia",
-	"November": "",
-	"October": "",
+	"November": "Listopad",
+	"October": "Październik",
 	"Off": "Wyłączony",
 	"Okay, Let's Go!": "Okej, zaczynamy!",
-	"OLED Dark": "",
-	"Ollama": "",
+	"OLED Dark": "Ciemny OLED",
+	"Ollama": "Ollama",
 	"Ollama Base URL": "Adres bazowy URL Ollama",
 	"Ollama Version": "Wersja Ollama",
 	"On": "Włączony",
@@ -314,57 +314,58 @@
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "W poleceniu dozwolone są tylko znaki alfanumeryczne i myślniki.",
 	"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Ups! Trzymaj się! Twoje pliki są wciąż w procesie obróbki. Gotujemy je do perfekcji. Prosimy o cierpliwość, poinformujemy Cię, gdy będą gotowe.",
 	"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! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Używasz nieobsługiwaniej metody (tylko interfejs front-end). Proszę obsłużyć interfejs WebUI z poziomu backendu.",
+	"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": "Otwórz",
 	"Open AI": "Open AI",
 	"Open AI (Dall-E)": "Open AI (Dall-E)",
 	"Open new chat": "Otwórz nowy czat",
-	"OpenAI": "",
+	"OpenAI": "OpenAI",
 	"OpenAI API": "OpenAI API",
-	"OpenAI API Config": "",
+	"OpenAI API Config": "Konfiguracja OpenAI API",
 	"OpenAI API Key is required.": "Klucz API OpenAI jest wymagany.",
-	"OpenAI URL/Key required.": "",
+	"OpenAI URL/Key required.": "URL/Klucz OpenAI jest wymagany.",
 	"or": "lub",
-	"Other": "",
-	"Overview": "",
+	"Other": "Inne",
+	"Overview": "Przegląd",
 	"Parameters": "Parametry",
 	"Password": "Hasło",
-	"PDF document (.pdf)": "",
+	"PDF document (.pdf)": "Dokument PDF (.pdf)",
 	"PDF Extract Images (OCR)": "PDF Wyodrębnij obrazy (OCR)",
 	"pending": "oczekujące",
 	"Permission denied when accessing microphone: {{error}}": "Odmowa dostępu do mikrofonu: {{error}}",
-	"Plain text (.txt)": "",
+	"Personalization": "",
+	"Plain text (.txt)": "Zwykły tekst (.txt)",
 	"Playground": "Plac zabaw",
-	"Positive attitude": "",
-	"Previous 30 days": "",
-	"Previous 7 days": "",
-	"Profile Image": "",
-	"Prompt": "",
-	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
+	"Positive attitude": "Pozytywne podejście",
+	"Previous 30 days": "Poprzednie 30 dni",
+	"Previous 7 days": "Poprzednie 7 dni",
+	"Profile Image": "Obrazek profilowy",
+	"Prompt": "Promopt",
+	"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": "",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Pobierz \"{{searchValue}}\" z Ollama.com",
 	"Pull a model from Ollama.com": "Pobierz model z Ollama.com",
 	"Pull Progress": "Postęp pobierania",
 	"Query Params": "Parametry zapytania",
 	"RAG Template": "Szablon RAG",
 	"Raw Format": "Format bez obróbki",
-	"Read Aloud": "",
+	"Read Aloud": "Czytaj na głos",
 	"Record voice": "Nagraj głos",
 	"Redirecting you to OpenWebUI Community": "Przekierowujemy Cię do społeczności OpenWebUI",
-	"Refused when it shouldn't have": "",
-	"Regenerate": "",
+	"Refused when it shouldn't have": "Odmówił, kiedy nie powinien",
+	"Regenerate": "Generuj ponownie",
 	"Release Notes": "Notatki wydania",
-	"Remove": "",
-	"Remove Model": "",
-	"Rename": "",
+	"Remove": "Usuń",
+	"Remove Model": "Usuń model",
+	"Rename": "ZMień nazwę",
 	"Repeat Last N": "Powtórz ostatnie N",
 	"Repeat Penalty": "Kara za powtórzenie",
 	"Request Mode": "Tryb żądania",
-	"Reranking Model": "",
-	"Reranking model disabled": "",
-	"Reranking model set to \"{{reranking_model}}\"": "",
+	"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 Vector Storage": "Resetuj przechowywanie wektorów",
 	"Response AutoCopy to Clipboard": "Automatyczne kopiowanie odpowiedzi do schowka",
 	"Role": "Rola",
@@ -372,14 +373,13 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Zapisz",
 	"Save & Create": "Zapisz i utwórz",
-	"Save & Submit": "Zapisz i wyślij",
 	"Save & Update": "Zapisz i zaktualizuj",
 	"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ą",
 	"Scan": "Skanuj",
 	"Scan complete!": "Skanowanie zakończone!",
 	"Scan for documents from {{path}}": "Skanuj dokumenty z {{path}}",
 	"Search": "Szukaj",
-	"Search a model": "",
+	"Search a model": "Szukaj modelu",
 	"Search Documents": "Szukaj dokumentów",
 	"Search Prompts": "Szukaj promptów",
 	"See readme.md for instructions": "Zajrzyj do readme.md po instrukcje",
@@ -388,74 +388,75 @@
 	"Select a mode": "Wybierz tryb",
 	"Select a model": "Wybierz model",
 	"Select an Ollama instance": "Wybierz instancję Ollama",
-	"Select model": "",
+	"Select model": "Wybierz model",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Wyślij Wiadomość",
 	"Send message": "Wyślij wiadomość",
-	"September": "",
+	"September": "Wrzesień",
 	"Server connection verified": "Połączenie z serwerem zweryfikowane",
 	"Set as default": "Ustaw jako domyślne",
 	"Set Default Model": "Ustaw domyślny model",
-	"Set embedding model (e.g. {{model}})": "",
+	"Set embedding model (e.g. {{model}})": "Ustaw model osadzania (e.g. {{model}})",
 	"Set Image Size": "Ustaw rozmiar obrazu",
 	"Set Model": "Ustaw model",
-	"Set reranking model (e.g. {{model}})": "",
+	"Set reranking model (e.g. {{model}})": "Ustaw zmianę rankingu modelu (e.g. {{model}})",
 	"Set Steps": "Ustaw kroki",
 	"Set Title Auto-Generation Model": "Ustaw model automatycznego generowania tytułów",
 	"Set Voice": "Ustaw głos",
 	"Settings": "Ustawienia",
 	"Settings saved successfully!": "Ustawienia zapisane pomyślnie!",
-	"Share": "",
-	"Share Chat": "",
+	"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 Additional Params": "Pokaż dodatkowe parametry",
 	"Show shortcuts": "Pokaż skróty",
-	"Showcased creativity": "",
+	"Showcased creativity": "Pokaz kreatywności",
 	"sidebar": "Panel boczny",
 	"Sign in": "Zaloguj się",
 	"Sign Out": "Wyloguj się",
 	"Sign up": "Zarejestruj się",
-	"Signing in": "",
-	"Source": "",
+	"Signing in": "Zalogowanie",
+	"Source": "Źródło",
 	"Speech recognition error: {{error}}": "Błąd rozpoznawania mowy: {{error}}",
 	"Speech-to-Text Engine": "Silnik mowy na tekst",
 	"SpeechRecognition API is not supported in this browser.": "API Rozpoznawania Mowy nie jest obsługiwane w tej przeglądarce.",
 	"Stop Sequence": "Zatrzymaj sekwencję",
 	"STT Settings": "Ustawienia STT",
 	"Submit": "Zatwierdź",
-	"Subtitle (e.g. about the Roman Empire)": "",
+	"Subtitle (e.g. about the Roman Empire)": "Podtytuł (np. o Imperium Rzymskim)",
 	"Success": "Sukces",
 	"Successfully updated.": "Pomyślnie zaktualizowano.",
-	"Suggested": "",
+	"Suggested": "Sugerowane",
 	"Sync All": "Synchronizuj wszystko",
 	"System": "System",
 	"System Prompt": "Prompt systemowy",
 	"Tags": "Tagi",
-	"Tell us more:": "",
+	"Tell us more:": "Powiedz nam więcej",
 	"Temperature": "Temperatura",
 	"Template": "Szablon",
 	"Text Completion": "Uzupełnienie tekstu",
 	"Text-to-Speech Engine": "Silnik tekstu na mowę",
 	"Tfs Z": "Tfs Z",
-	"Thanks for your feedback!": "",
-	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
+	"Thanks for your feedback!": "Dzięki za informację zwrotną!",
+	"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",
 	"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 setting does not sync across browsers or devices.": "To ustawienie nie synchronizuje się między przeglądarkami ani urządzeniami.",
-	"Thorough explanation": "",
+	"Thorough explanation": "Dokładne wyjaśnienie",
 	"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)": "",
+	"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.": "",
+	"Title cannot be an empty string.": "Tytuł nie może być pusty",
 	"Title Generation Prompt": "Prompt generowania tytułu",
 	"to": "do",
 	"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 chat input.": "do pola wprowadzania czatu.",
-	"Today": "",
+	"Today": "Dzisiaj",
 	"Toggle settings": "Przełącz ustawienia",
 	"Toggle sidebar": "Przełącz panel boczny",
 	"Top K": "Najlepsze K",
@@ -465,7 +466,7 @@
 	"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}}.",
 	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Nieznany typ pliku '{{file_type}}', ale akceptowany i traktowany jako zwykły tekst",
-	"Update and Copy Link": "",
+	"Update and Copy Link": "Uaktualnij i skopiuj link",
 	"Update password": "Aktualizacja hasła",
 	"Upload a GGUF model": "Prześlij model GGUF",
 	"Upload files": "Prześlij pliki",
@@ -482,26 +483,27 @@
 	"variable": "zmienna",
 	"variable to have them replaced with clipboard content.": "zmienna która zostanie zastąpiona zawartością schowka.",
 	"Version": "Wersja",
-	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
+	"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 Loader Settings": "",
-	"Web Params": "",
-	"Webhook URL": "",
+	"Web Loader Settings": "Ustawienia pobierania z sieci",
+	"Web Params": "Parametry sieci",
+	"Webhook URL": "URL webhook",
 	"WebUI Add-ons": "Dodatki do interfejsu WebUI",
 	"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",
-	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kiedy historia jest wyłączona, nowe rozmowy na tej przeglądarce nie będą widoczne w historii na żadnym z twoich urządzeń.",
+	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kiedy historia jest wyłączona, nowe czaty na tej przeglądarce nie będą widoczne w historii na żadnym z twoich urządzeń.",
 	"Whisper (Local)": "Whisper (Lokalnie)",
+	"Workspace": "",
 	"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].",
 	"Yes": "",
-	"Yesterday": "",
-	"You": "Ty",
-	"You have no archived conversations.": "",
-	"You have shared this chat": "",
+	"Yesterday": "Wczoraj",
+	"You": "",
+	"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.",
-	"Youtube": "",
-	"Youtube Loader Settings": ""
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Ustawienia pobierania z Youtube"
 }

+ 11 - 9
src/lib/i18n/locales/pt-BR/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Modo de Construtor",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Cancelar",
 	"Categories": "Categorias",
 	"Change Password": "Alterar Senha",
 	"Chat": "Bate-papo",
+	"Chat Bubble UI": "",
 	"Chat History": "Histórico de Bate-papo",
 	"Chat History is off for this browser.": "O histórico de bate-papo está desativado para este navegador.",
 	"Chats": "Bate-papos",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "Sobreposição de Fragmento",
 	"Chunk Params": "Parâmetros de Fragmento",
 	"Chunk Size": "Tamanho do Fragmento",
-	"Citation": "",
+	"Citation": "Citação",
 	"Click here for help.": "Clique aqui para obter ajuda.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Clique aqui para verificar outros arquivos de modelo.",
@@ -118,7 +120,6 @@
 	"Dark": "Escuro",
 	"Dashboard": "",
 	"Database": "Banco de dados",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "Padrão",
 	"Default (Automatic1111)": "Padrão (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Configurações Gerais",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Olá, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Máximo de Tokens",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Máximo de 3 modelos podem ser baixados simultaneamente. Tente novamente mais tarde.",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Arquivos de Modelo",
 	"Models": "Modelos",
 	"More": "",
-	"My Documents": "Meus Documentos",
-	"My Modelfiles": "Meus Arquivos de Modelo",
-	"My Prompts": "Meus Prompts",
 	"Name": "Nome",
 	"Name Tag": "Nome da Tag",
 	"Name your modelfile": "Nomeie seu arquivo de modelo",
@@ -295,7 +295,7 @@
 	"New Password": "Nova Senha",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "Nenhuma fonte disponível",
 	"Not factually correct": "",
 	"Not sure what to add?": "Não tem certeza do que adicionar?",
 	"Not sure what to write? Switch to": "Não tem certeza do que escrever? Mude para",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "Extrair Imagens de PDF (OCR)",
 	"pending": "pendente",
 	"Permission denied when accessing microphone: {{error}}": "Permissão negada ao acessar o microfone: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Parque infantil",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Salvar",
 	"Save & Create": "Salvar e Criar",
-	"Save & Submit": "Salvar e Enviar",
 	"Save & Update": "Salvar e Atualizar",
 	"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 logs de bate-papo diretamente no armazenamento do seu navegador não é mais suportado. Reserve um momento para baixar e excluir seus logs de bate-papo clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar seus logs de bate-papo para o backend através de",
 	"Scan": "Digitalizar",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Selecione uma instância Ollama",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Enviar uma Mensagem",
 	"Send message": "Enviar mensagem",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Sair",
 	"Sign up": "Inscrever-se",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Fonte",
 	"Speech recognition error: {{error}}": "Erro de reconhecimento de fala: {{error}}",
 	"Speech-to-Text Engine": "Mecanismo de Fala para Texto",
 	"SpeechRecognition API is not supported in this browser.": "A API SpeechRecognition não é suportada neste navegador.",
@@ -493,11 +494,12 @@
 	"What’s New in": "O que há de novo em",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando o histórico está desativado, novos bate-papos neste navegador não aparecerão em seu histórico em nenhum dos seus dispositivos.",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"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].",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "Você",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Você é um assistente útil.",

+ 11 - 9
src/lib/i18n/locales/pt-PT/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Modo de Construtor",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Cancelar",
 	"Categories": "Categorias",
 	"Change Password": "Alterar Senha",
 	"Chat": "Bate-papo",
+	"Chat Bubble UI": "",
 	"Chat History": "Histórico de Bate-papo",
 	"Chat History is off for this browser.": "O histórico de bate-papo está desativado para este navegador.",
 	"Chats": "Bate-papos",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "Sobreposição de Fragmento",
 	"Chunk Params": "Parâmetros de Fragmento",
 	"Chunk Size": "Tamanho do Fragmento",
-	"Citation": "",
+	"Citation": "Citação",
 	"Click here for help.": "Clique aqui para obter ajuda.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Clique aqui para verificar outros arquivos de modelo.",
@@ -118,7 +120,6 @@
 	"Dark": "Escuro",
 	"Dashboard": "",
 	"Database": "Banco de dados",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "Padrão",
 	"Default (Automatic1111)": "Padrão (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Configurações Gerais",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Olá, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Máximo de Tokens",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Máximo de 3 modelos podem ser baixados simultaneamente. Tente novamente mais tarde.",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Arquivos de Modelo",
 	"Models": "Modelos",
 	"More": "",
-	"My Documents": "Meus Documentos",
-	"My Modelfiles": "Meus Arquivos de Modelo",
-	"My Prompts": "Meus Prompts",
 	"Name": "Nome",
 	"Name Tag": "Tag de Nome",
 	"Name your modelfile": "Nomeie seu arquivo de modelo",
@@ -295,7 +295,7 @@
 	"New Password": "Nova Senha",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "Nenhuma fonte disponível",
 	"Not factually correct": "",
 	"Not sure what to add?": "Não tem certeza do que adicionar?",
 	"Not sure what to write? Switch to": "Não tem certeza do que escrever? Mude para",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "Extrair Imagens de PDF (OCR)",
 	"pending": "pendente",
 	"Permission denied when accessing microphone: {{error}}": "Permissão negada ao acessar o microfone: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Parque infantil",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Salvar",
 	"Save & Create": "Salvar e Criar",
-	"Save & Submit": "Salvar e Enviar",
 	"Save & Update": "Salvar e Atualizar",
 	"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 logs de bate-papo diretamente no armazenamento do seu navegador não é mais suportado. Reserve um momento para baixar e excluir seus logs de bate-papo clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar seus logs de bate-papo para o backend através de",
 	"Scan": "Digitalizar",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Selecione uma instância Ollama",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Enviar uma Mensagem",
 	"Send message": "Enviar mensagem",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Sair",
 	"Sign up": "Inscrever-se",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Fonte",
 	"Speech recognition error: {{error}}": "Erro de reconhecimento de fala: {{error}}",
 	"Speech-to-Text Engine": "Mecanismo de Fala para Texto",
 	"SpeechRecognition API is not supported in this browser.": "A API SpeechRecognition não é suportada neste navegador.",
@@ -493,11 +494,12 @@
 	"What’s New in": "O que há de novo em",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando o histórico está desativado, novos bate-papos neste navegador não aparecerão em seu histórico em nenhum dos seus dispositivos.",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"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].",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "Você",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Você é um assistente útil.",

+ 12 - 10
src/lib/i18n/locales/ru-RU/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Режим конструктор",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Аннулировать",
 	"Categories": "Категории",
 	"Change Password": "Изменить пароль",
 	"Chat": "Чат",
+	"Chat Bubble UI": "",
 	"Chat History": "История чат",
 	"Chat History is off for this browser.": "История чат отключен для этого браузера.",
 	"Chats": "Чаты",
@@ -76,8 +78,8 @@
 	"Chunk Overlap": "Перекрытие фрагментов",
 	"Chunk Params": "Параметры фрагментов",
 	"Chunk Size": "Размер фрагмента",
-	"Citation": "",
-	"Click here for help.": "Нажмите здесь для помощь.",
+	"Citation": "Цитата",
+	"Click here for help.": "Нажмите здесь для помощи.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Нажмите тут чтобы проверить другие файлы моделей.",
 	"Click here to select": "Нажмите тут чтобы выберите",
@@ -118,7 +120,6 @@
 	"Dark": "Тёмный",
 	"Dashboard": "",
 	"Database": "База данных",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "По умолчанию",
 	"Default (Automatic1111)": "По умолчанию (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Общие настройки",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Привет, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Максимальное количество токенов",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимальное количество моделей для загрузки одновременно - 3. Пожалуйста, попробуйте позже.",
 	"May": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Файлы моделей",
 	"Models": "Модели",
 	"More": "",
-	"My Documents": "Мои документы",
-	"My Modelfiles": "Мои файлы моделей",
-	"My Prompts": "Мои подсказки",
 	"Name": "Имя",
 	"Name Tag": "Имя тега",
 	"Name your modelfile": "Назовите свой файл модели",
@@ -295,7 +295,7 @@
 	"New Password": "Новый пароль",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "Нет доступных источников",
 	"Not factually correct": "",
 	"Not sure what to add?": "Не уверены, что добавить?",
 	"Not sure what to write? Switch to": "Не уверены, что написать? Переключитесь на",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "Извлечение изображений из PDF (OCR)",
 	"pending": "ожидание",
 	"Permission denied when accessing microphone: {{error}}": "Отказано в доступе к микрофону: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Площадка",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Розовое сосновое дерево рассвет",
 	"Save": "Сохранить",
 	"Save & Create": "Сохранить и создать",
-	"Save & Submit": "Сохранить и отправить",
 	"Save & Update": "Сохранить и обновить",
 	"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": "Прямое сохранение журналов чата в хранилище вашего браузера больше не поддерживается. Пожалуйста, потратьте минуту, чтобы скачать и удалить ваши журналы чата, нажав на кнопку ниже. Не волнуйтесь, вы легко сможете повторно импортировать свои журналы чата в бэкенд через",
 	"Scan": "Сканировать",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Выберите экземпляр Ollama",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Отправить сообщение",
 	"Send message": "Отправить сообщение",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Выход",
 	"Sign up": "зарегистрировать",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Источник",
 	"Speech recognition error: {{error}}": "Ошибка распознавания речи: {{error}}",
 	"Speech-to-Text Engine": "Система распознавания речи",
 	"SpeechRecognition API is not supported in this browser.": "API распознавания речи не поддерживается в этом браузере.",
@@ -493,11 +494,12 @@
 	"What’s New in": "Что нового в",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Когда история отключена, новые чаты в этом браузере не будут отображаться в вашей истории на любом из ваших устройств.",
 	"Whisper (Local)": "Шепот (локальный)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишите предложение промпта (например, Кто вы?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишите резюме в 50 словах, которое кратко описывает [тему или ключевое слово].",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "Вы",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Вы полезный ассистент.",

+ 11 - 9
src/lib/i18n/locales/sv-SE/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "",
+	"Beta": "",
 	"Builder Mode": "Byggarläge",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Avbryt",
 	"Categories": "Kategorier",
 	"Change Password": "Ändra lösenord",
 	"Chat": "Chatt",
+	"Chat Bubble UI": "",
 	"Chat History": "Chatthistorik",
 	"Chat History is off for this browser.": "Chatthistoriken är avstängd för denna webbläsare.",
 	"Chats": "Chattar",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "Överlappning",
 	"Chunk Params": "Chunk-parametrar",
 	"Chunk Size": "Chunk-storlek",
-	"Citation": "",
+	"Citation": "Citat",
 	"Click here for help.": "Klicka här för hjälp.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Klicka här för att kontrollera andra modelfiler.",
@@ -118,7 +120,6 @@
 	"Dark": "Mörk",
 	"Dashboard": "",
 	"Database": "Databas",
-	"DD/MM/YYYY HH:mm": "DD/MM/ÅÅÅÅ TT:mm",
 	"December": "",
 	"Default": "Standard",
 	"Default (Automatic1111)": "Standard (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Allmänna inställningar",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Hej, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Max antal tokens",
 	"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": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Modelfiler",
 	"Models": "Modeller",
 	"More": "",
-	"My Documents": "Mina dokument",
-	"My Modelfiles": "Mina modelfiler",
-	"My Prompts": "Mina promptar",
 	"Name": "Namn",
 	"Name Tag": "Namntag",
 	"Name your modelfile": "Namnge din modelfil",
@@ -295,7 +295,7 @@
 	"New Password": "Nytt lösenord",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "Ingen tilgjengelig kilde",
 	"Not factually correct": "",
 	"Not sure what to add?": "Inte säker på vad du ska lägga till?",
 	"Not sure what to write? Switch to": "Inte säker på vad du ska skriva? Växla till",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "PDF Extrahera bilder (OCR)",
 	"pending": "väntande",
 	"Permission denied when accessing microphone: {{error}}": "Tillstånd nekades vid åtkomst till mikrofon: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Lekplats",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Spara",
 	"Save & Create": "Spara och skapa",
-	"Save & Submit": "Spara och skicka",
 	"Save & Update": "Spara och uppdatera",
 	"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",
 	"Scan": "Skanna",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Välj en Ollama-instans",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Skicka ett meddelande",
 	"Send message": "Skicka meddelande",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Logga ut",
 	"Sign up": "Registrera dig",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Källa",
 	"Speech recognition error: {{error}}": "Fel vid taligenkänning: {{error}}",
 	"Speech-to-Text Engine": "Tal-till-text-motor",
 	"SpeechRecognition API is not supported in this browser.": "SpeechRecognition API stöds inte i denna webbläsare.",
@@ -493,11 +494,12 @@
 	"What’s New in": "Vad är nytt i",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "När historiken är avstängd visas inte nya chattar i denna webbläsare i din historik på någon av dina enheter.",
 	"Whisper (Local)": "Whisper (lokal)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Skriv ett fö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].",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "Du",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Du är en hjälpsam assistent.",

+ 65 - 63
src/lib/i18n/locales/tr-TR/translation.json

@@ -15,7 +15,7 @@
 	"Add a short description about what this modelfile does": "Bu model dosyasının 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 custom prompt": "",
+	"Add custom prompt": "Özel prompt ekle",
 	"Add Docs": "Dökümanlar Ekle",
 	"Add Files": "Dosyalar Ekle",
 	"Add message": "Mesaj ekle",
@@ -28,7 +28,7 @@
 	"Admin Settings": "Yönetici Ayarları",
 	"Advanced Parameters": "Gelişmiş Parametreler",
 	"all": "tümü",
-	"All Documents": "",
+	"All Documents": "Tüm Belgeler",
 	"All Users": "Tüm Kullanıcılar",
 	"Allow": "İzin ver",
 	"Allow Chat Deletion": "Sohbet Silmeye İzin Ver",
@@ -36,21 +36,21 @@
 	"Already have an account?": "Zaten bir hesabınız mı var?",
 	"an assistant": "bir asistan",
 	"and": "ve",
-	"and create a new shared link.": "",
+	"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ı",
 	"API RPM": "API RPM",
-	"April": "",
+	"April": "Nisan",
 	"Archive": "Arşiv",
 	"Archived Chats": "Arşivlenmiş Sohbetler",
 	"are allowed - Activate this command by typing": "izin verilir - Bu komutu yazarak etkinleştirin",
 	"Are you sure?": "Emin misiniz?",
-	"Attach file": "",
+	"Attach file": "Dosya ekle",
 	"Attention to detail": "Ayrıntılara dikkat",
 	"Audio": "Ses",
-	"August": "",
+	"August": "Ağustos",
 	"Auto-playback response": "Yanıtı otomatik oynatma",
 	"Auto-send input after 3 sec.": "3 saniye sonra otomatik olarak gönder",
 	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Temel URL",
@@ -58,14 +58,16 @@
 	"available!": "mevcut!",
 	"Back": "Geri",
 	"Bad Response": "Kötü Yanıt",
-	"before": "",
+	"before": "önce",
 	"Being lazy": "Tembelleşiyor",
+	"Beta": "",
 	"Builder Mode": "Oluşturucu Modu",
-	"Bypass SSL verification for Websites": "",
+	"Bypass SSL verification for Websites": "Web Siteleri için SSL doğrulamasını atlayın",
 	"Cancel": "İptal",
 	"Categories": "Kategoriler",
 	"Change Password": "Parola Değiştir",
 	"Chat": "Sohbet",
+	"Chat Bubble UI": "",
 	"Chat History": "Sohbet Geçmişi",
 	"Chat History is off for this browser.": "Bu tarayıcı için sohbet geçmişi kapalı.",
 	"Chats": "Sohbetler",
@@ -76,9 +78,9 @@
 	"Chunk Overlap": "Chunk Çakışması",
 	"Chunk Params": "Chunk Parametreleri",
 	"Chunk Size": "Chunk Boyutu",
-	"Citation": "",
+	"Citation": "Alıntı",
 	"Click here for help.": "Yardım için buraya tıklayın.",
-	"Click here to": "",
+	"Click here to": "Şunu yapmak için buraya tıklayın:",
 	"Click here to check other modelfiles.": "Diğer model dosyalarını kontrol etmek için buraya tıklayın.",
 	"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.",
@@ -95,7 +97,7 @@
 	"Connections": "Bağlantılar",
 	"Content": "İçerik",
 	"Context Length": "Bağlam Uzunluğu",
-	"Continue Response": "",
+	"Continue Response": "Yanıta Devam Et",
 	"Conversation Mode": "Sohbet Modu",
 	"Copied shared chat URL to clipboard!": "Paylaşılan sohbet URL'si panoya kopyalandı!",
 	"Copy": "Kopyala",
@@ -106,8 +108,8 @@
 	"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şağıdaki sorgu için başlık olarak 3-5 kelimelik kısa ve öz bir ifade oluşturun, 3-5 kelime sınırına kesinlikle uyun ve 'başlık' kelimesini kullanmaktan kaçının:",
 	"Create a modelfile": "Bir model dosyası oluştur",
 	"Create Account": "Hesap Oluştur",
-	"Create new key": "",
-	"Create new secret key": "",
+	"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:",
 	"Current Model": "Mevcut Model",
@@ -116,10 +118,9 @@
 	"Custom": "Özel",
 	"Customize Ollama models for a specific purpose": "Ollama modellerini belirli bir amaç için özelleştirin",
 	"Dark": "Koyu",
-	"Dashboard": "",
+	"Dashboard": "Panel",
 	"Database": "Veritabanı",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
-	"December": "",
+	"December": "Aralık",
 	"Default": "Varsayılan",
 	"Default (Automatic1111)": "Varsayılan (Automatic1111)",
 	"Default (SentenceTransformers)": "Varsayılan (SentenceTransformers)",
@@ -133,7 +134,7 @@
 	"Delete chat": "Sohbeti sil",
 	"Delete Chat": "Sohbeti Sil",
 	"Delete Chats": "Sohbetleri Sil",
-	"delete this link": "",
+	"delete this link": "bu bağlantıyı sil",
 	"Delete User": "Kullanıcıyı Sil",
 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} silindi",
 	"Deleted {{tagName}}": "{{tagName}} silindi",
@@ -153,7 +154,7 @@
 	"Don't have an account?": "Hesabınız yok mu?",
 	"Don't like the style": "Tarzını beğenmedim",
 	"Download": "İndir",
-	"Download canceled": "",
+	"Download canceled": "İndirme iptal edildi",
 	"Download Database": "Veritabanını İndir",
 	"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'.",
@@ -161,7 +162,7 @@
 	"Edit Doc": "Belgeyi Düzenle",
 	"Edit User": "Kullanıcıyı Düzenle",
 	"Email": "E-posta",
-	"Embedding Model": "",
+	"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 Chat History": "Sohbet Geçmişini Etkinleştir",
@@ -172,7 +173,7 @@
 	"Enter Chunk Overlap": "Chunk Örtüşmesini Girin",
 	"Enter Chunk Size": "Chunk Boyutunu Girin",
 	"Enter Image Size (e.g. 512x512)": "Görüntü Boyutunu Girin (örn. 512x512)",
-	"Enter language codes": "",
+	"Enter language codes": "Dil kodlarını girin",
 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "LiteLLM API Ana URL'sini Girin (litellm_params.api_base)",
 	"Enter LiteLLM API Key (litellm_params.api_key)": "LiteLLM API Anahtarını Girin (litellm_params.api_key)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM'ini Girin (litellm_params.rpm)",
@@ -185,7 +186,7 @@
 	"Enter stop sequence": "Durdurma dizisini 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)": "",
+	"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 Password": "Parolanızı Girin",
@@ -198,7 +199,7 @@
 	"Export Prompts": "Promptları Dışa Aktar",
 	"Failed to create API Key.": "API Anahtarı oluşturulamadı.",
 	"Failed to read clipboard contents": "Pano içeriği okunamadı",
-	"February": "",
+	"February": "Şubat",
 	"Feel free to add specific details": "Spesifik ayrıntılar eklemekten çekinmeyin",
 	"File Mode": "Dosya Modu",
 	"File not found.": "Dosya bulunamadı.",
@@ -213,9 +214,10 @@
 	"General Settings": "Genel Ayarlar",
 	"Generation Info": "Üretim Bilgisi",
 	"Good Response": "İyi Yanıt",
+	"h:mm a": "",
 	"has no conversations.": "hiç konuşması yok.",
 	"Hello, {{name}}": "Merhaba, {{name}}",
-	"Help": "",
+	"Help": "Yardım",
 	"Hide": "Gizle",
 	"Hide Additional Params": "Ek Parametreleri Gizle",
 	"How can I help you today?": "Bugün size nasıl yardımcı olabilirim?",
@@ -229,15 +231,15 @@
 	"Import Modelfiles": "Model Dosyalarını İçe Aktar",
 	"Import Prompts": "Promptları İçe Aktar",
 	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui çalıştırılırken `--api` bayrağını dahil edin",
-	"Input commands": "",
+	"Input commands": "Giriş komutları",
 	"Interface": "Arayüz",
-	"Invalid Tag": "",
+	"Invalid Tag": "Geçersiz etiket",
 	"Is Model Vision Capable": "",
-	"January": "",
+	"January": "Ocak",
 	"join our Discord for help.": "yardım için Discord'umuza katılın.",
 	"JSON": "JSON",
-	"July": "",
-	"June": "",
+	"July": "Temmuz",
+	"June": "Haziran",
 	"JWT Expiration": "JWT Bitişi",
 	"JWT Token": "JWT Token",
 	"Keep Alive": "Canlı Tut",
@@ -253,11 +255,12 @@
 	"Manage Model Information": "",
 	"Manage Models": "Modelleri Yönet",
 	"Manage Ollama Models": "Ollama Modellerini Yönet",
-	"March": "",
+	"March": "Mart",
 	"Max Tokens": "Maksimum Token",
 	"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": "",
-	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
+	"May": "Mayıs",
+	"Memory": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will beable 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.",
 	"Minimum Score": "Minimum Skor",
 	"Mirostat": "Mirostat",
 	"Mirostat Eta": "Mirostat Eta",
@@ -285,24 +288,21 @@
 	"Modelfiles": "Model Dosyaları",
 	"Models": "Modeller",
 	"More": "Daha Fazla",
-	"My Documents": "Belgelerim",
-	"My Modelfiles": "Model Dosyalarım",
-	"My Prompts": "Promptlarım",
 	"Name": "Ad",
 	"Name Tag": "Ad Etiketi",
 	"Name your modelfile": "Model dosyanıza ad verin",
 	"New Chat": "Yeni Sohbet",
 	"New Password": "Yeni Parola",
 	"No": "",
-	"No results found": "",
-	"No source available": "",
+	"No results found": "Sonuç bulunamadı",
+	"No source available": "Kaynak mevcut değil",
 	"Not factually correct": "Gerçeklere göre doğru değil",
 	"Not sure what to add?": "Ne ekleyeceğinizden emin değil misiniz?",
 	"Not sure what to write? Switch to": "Ne yazacağınızdan emin değil misiniz? Şuraya geçin",
 	"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.",
 	"Notifications": "Bildirimler",
-	"November": "",
-	"October": "",
+	"November": "Kasım",
+	"October": "Ekim",
 	"Off": "Kapalı",
 	"Okay, Let's Go!": "Tamam, Hadi Başlayalım!",
 	"OLED Dark": "OLED Koyu",
@@ -326,25 +326,26 @@
 	"OpenAI URL/Key required.": "OpenAI URL/Anahtar gereklidir.",
 	"or": "veya",
 	"Other": "Diğer",
-	"Overview": "",
+	"Overview": "Genel Bakış",
 	"Parameters": "Parametreler",
 	"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 microphone: {{error}}": "Mikrofona erişim izni reddedildi: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "Düz metin (.txt)",
 	"Playground": "Oyun Alanı",
 	"Positive attitude": "Olumlu yaklaşım",
-	"Previous 30 days": "",
-	"Previous 7 days": "",
+	"Previous 30 days": "Önceki 30 gün",
+	"Previous 7 days": "Önceki 7 gün",
 	"Profile Image": "Profil Fotoğrafı",
-	"Prompt": "",
+	"Prompt": "Prompt",
 	"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": "",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com'dan \"{{searchValue}}\" çekin",
 	"Pull a model from Ollama.com": "Ollama.com'dan bir model çekin",
 	"Pull Progress": "Çekme İlerlemesi",
 	"Query Params": "Sorgu Parametreleri",
@@ -357,12 +358,12 @@
 	"Regenerate": "Tekrar Oluştur",
 	"Release Notes": "Sürüm Notları",
 	"Remove": "Kaldır",
-	"Remove Model": "",
-	"Rename": "",
+	"Remove Model": "Modeli Kaldır",
+	"Rename": "Yeniden Adlandır",
 	"Repeat Last N": "Son N'yi Tekrar Et",
 	"Repeat Penalty": "Tekrar Cezası",
 	"Request Mode": "İstek Modu",
-	"Reranking Model": "",
+	"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 Vector Storage": "Vektör Depolamayı Sıfırla",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Kaydet",
 	"Save & Create": "Kaydet ve Oluştur",
-	"Save & Submit": "Kaydet ve Gönder",
 	"Save & Update": "Kaydet ve Güncelle",
 	"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:",
 	"Scan": "Tarama",
@@ -388,18 +388,19 @@
 	"Select a mode": "Bir mod seç",
 	"Select a model": "Bir model seç",
 	"Select an Ollama instance": "Bir Ollama örneği seçin",
-	"Select model": "",
+	"Select model": "Model seç",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Bir Mesaj Gönder",
 	"Send message": "Mesaj gönder",
-	"September": "",
+	"September": "Eylül",
 	"Server connection verified": "Sunucu bağlantısı doğrulandı",
 	"Set as default": "Varsayılan olarak ayarla",
 	"Set Default Model": "Varsayılan Modeli Ayarla",
-	"Set embedding model (e.g. {{model}})": "",
+	"Set embedding model (e.g. {{model}})": "Gömme modelini ayarlayın (örn. {{model}})",
 	"Set Image Size": "Görüntü Boyutunu Ayarla",
 	"Set Model": "Model Ayarla",
-	"Set reranking model (e.g. {{model}})": "",
+	"Set reranking model (e.g. {{model}})": "Yeniden sıralama modelini ayarlayın (örn. {{model}})",
 	"Set Steps": "Adımları Ayarla",
 	"Set Title Auto-Generation Model": "Otomatik Başlık Oluşturma Modelini Ayarla",
 	"Set Voice": "Ses Ayarla",
@@ -412,13 +413,13 @@
 	"Show": "Göster",
 	"Show Additional Params": "Ek Parametreleri Göster",
 	"Show shortcuts": "Kısayolları göster",
-	"Showcased creativity": "",
+	"Showcased creativity": "Sergilenen yaratıcılık",
 	"sidebar": "kenar çubuğu",
 	"Sign in": "Oturum aç",
 	"Sign Out": "Çıkış Yap",
 	"Sign up": "Kaydol",
 	"Signing in": "Oturum açma",
-	"Source": "",
+	"Source": "Kaynak",
 	"Speech recognition error: {{error}}": "Konuşma tanıma hatası: {{error}}",
 	"Speech-to-Text Engine": "Konuşmadan Metne Motoru",
 	"SpeechRecognition API is not supported in this browser.": "SpeechRecognition API bu tarayıcıda desteklenmiyor.",
@@ -428,7 +429,7 @@
 	"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": "",
+	"Suggested": "Önerilen",
 	"Sync All": "Tümünü Senkronize Et",
 	"System": "Sistem",
 	"System Prompt": "Sistem Promptu",
@@ -449,13 +450,13 @@
 	"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.": "",
+	"Title cannot be an empty string.": "Başlık boş bir dize olamaz.",
 	"Title Generation Prompt": "Başlık Oluşturma Promptu",
 	"to": "için",
 	"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 chat input.": "sohbet girişine.",
-	"Today": "",
+	"Today": "Bugün",
 	"Toggle settings": "Ayarları Aç/Kapat",
 	"Toggle sidebar": "Kenar Çubuğunu Aç/Kapat",
 	"Top K": "Top K",
@@ -484,8 +485,8 @@
 	"Version": "Sürüm",
 	"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 Loader Settings": "",
-	"Web Params": "",
+	"Web Loader Settings": "Web Yükleyici Ayarları",
+	"Web Params": "Web Parametreleri",
 	"Webhook URL": "Webhook URL",
 	"WebUI Add-ons": "WebUI Eklentileri",
 	"WebUI Settings": "WebUI Ayarları",
@@ -493,15 +494,16 @@
 	"What’s New in": "Yenilikler:",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Geçmiş kapatıldığında, bu tarayıcıdaki yeni sohbetler hiçbir cihazınızdaki geçmişinizde görünmez.",
 	"Whisper (Local)": "Whisper (Yerel)",
+	"Workspace": "",
 	"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.",
 	"Yes": "",
-	"Yesterday": "",
-	"You": "Siz",
-	"You have no archived conversations.": "",
-	"You have shared this chat": "",
+	"Yesterday": "Dün",
+	"You": "",
+	"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ımcı bir asistansın.",
 	"You're now logged in.": "Şimdi oturum açtınız.",
 	"Youtube": "Youtube",
-	"Youtube Loader Settings": ""
+	"Youtube Loader Settings": "Youtube Yükleyici Ayarları"
 }

+ 8 - 6
src/lib/i18n/locales/uk-UA/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "Неправильна відповідь",
 	"before": "до того, як",
 	"Being lazy": "Не поспішати",
+	"Beta": "",
 	"Builder Mode": "Режим конструктора",
 	"Bypass SSL verification for Websites": "Обхід SSL-перевірки для веб-сайтів",
 	"Cancel": "Скасувати",
 	"Categories": "Категорії",
 	"Change Password": "Змінити пароль",
 	"Chat": "Чат",
+	"Chat Bubble UI": "",
 	"Chat History": "Історія чату",
 	"Chat History is off for this browser.": "Історія чату вимкнена для цього браузера.",
 	"Chats": "Чати",
@@ -118,7 +120,6 @@
 	"Dark": "Темна",
 	"Dashboard": "Панель управління",
 	"Database": "База даних",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "Грудень",
 	"Default": "За замовчуванням",
 	"Default (Automatic1111)": "За замовчуванням (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Загальні налаштування",
 	"Generation Info": "Інформація про генерацію",
 	"Good Response": "Гарна відповідь",
+	"h:mm a": "",
 	"has no conversations.": "не має розмов.",
 	"Hello, {{name}}": "Привіт, {{name}}",
 	"Help": "Допоможіть",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Максимальна кількість токенів",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 моделі можна завантажити одночасно. Будь ласка, спробуйте пізніше.",
 	"May": "Травень",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "Повідомлення, які ви надсилаєте після створення посилання, не будуть опубліковані. Користувачі з URL-адресою зможуть переглядати спільний чат.",
 	"Minimum Score": "Мінімальний бал",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Файли моделей",
 	"Models": "Моделі",
 	"More": "Більше",
-	"My Documents": "Мої документи",
-	"My Modelfiles": "Мої файли моделей",
-	"My Prompts": "Мої промти",
 	"Name": "Ім'я",
 	"Name Tag": "Назва тегу",
 	"Name your modelfile": "Назвіть свій файл моделі",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "Розпізнавання зображень з PDF (OCR)",
 	"pending": "на розгляді",
 	"Permission denied when accessing microphone: {{error}}": "Доступ до мікрофона заборонено: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "Простий текст (.txt)",
 	"Playground": "Майданчик",
 	"Positive attitude": "Позитивне ставлення",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Зберегти",
 	"Save & Create": "Зберегти та створити",
-	"Save & Submit": "Зберегти та надіслати",
 	"Save & Update": "Зберегти та оновити",
 	"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": "Збереження журналів чату безпосередньо в сховище вашого браузера більше не підтримується. Будь ласка, завантажте та видаліть журнали чату, натиснувши кнопку нижче. Не хвилюйтеся, ви можете легко повторно імпортувати журнали чату до бекенду через",
 	"Scan": "Сканування",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Виберіть екземпляр Ollama",
 	"Select model": "Вибрати модель",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Надіслати повідомлення",
 	"Send message": "Надіслати повідомлення",
 	"September": "Вересень",
@@ -493,11 +494,12 @@
 	"What’s New in": "Що нового в",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Коли історія вимкнена, нові чати в цьому браузері не будуть відображатися в історії на жодному з ваших пристроїв.",
 	"Whisper (Local)": "Whisper (локально)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишіть промт (напр., Хто ти?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишіть стислий зміст у 50 слів, який узагальнює [тема або ключове слово].",
 	"Yes": "",
 	"Yesterday": "Вчора",
-	"You": "Ви",
+	"You": "",
 	"You have no archived conversations.": "У вас немає архівованих розмов.",
 	"You have shared this chat": "Ви поділилися цим чатом",
 	"You're a helpful assistant.": "Ви корисний асистент.",

+ 11 - 9
src/lib/i18n/locales/vi-VN/translation.json

@@ -60,12 +60,14 @@
 	"Bad Response": "",
 	"before": "",
 	"Being lazy": "Lười biếng",
+	"Beta": "",
 	"Builder Mode": "Chế độ Builder",
 	"Bypass SSL verification for Websites": "",
 	"Cancel": "Hủy bỏ",
 	"Categories": "Danh mục",
 	"Change Password": "Đổi Mật khẩu",
 	"Chat": "Trò chuyện",
+	"Chat Bubble UI": "",
 	"Chat History": "Lịch sử chat",
 	"Chat History is off for this browser.": "Lịch sử chat đã tắt cho trình duyệt này.",
 	"Chats": "Chat",
@@ -76,7 +78,7 @@
 	"Chunk Overlap": "Chồng lấn (overlap)",
 	"Chunk Params": "Cài đặt số lượng ký tự cho khối ký tự (chunk)",
 	"Chunk Size": "Kích thước khối (size)",
-	"Citation": "",
+	"Citation": "Trích dẫn",
 	"Click here for help.": "Bấm vào đây để được trợ giúp.",
 	"Click here to": "",
 	"Click here to check other modelfiles.": "Bấm vào đây để kiểm tra các tệp mô tả mô hình (modelfiles) khác.",
@@ -118,7 +120,6 @@
 	"Dark": "Tối",
 	"Dashboard": "",
 	"Database": "Cơ sở dữ liệu",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"Default": "Mặc định",
 	"Default (Automatic1111)": "Mặc định (Automatic1111)",
@@ -213,6 +214,7 @@
 	"General Settings": "Cấu hình chung",
 	"Generation Info": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Xin chào, {{name}}",
 	"Help": "",
@@ -257,6 +259,7 @@
 	"Max Tokens": "Max Tokens",
 	"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": "",
+	"Memory": "",
 	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
 	"Minimum Score": "",
 	"Mirostat": "Mirostat",
@@ -285,9 +288,6 @@
 	"Modelfiles": "Tệp Mô hình",
 	"Models": "Mô hình",
 	"More": "",
-	"My Documents": "Tài liệu của tôi",
-	"My Modelfiles": "Tệp Mô hình của tôi",
-	"My Prompts": "Các prompt của tôi",
 	"Name": "Tên",
 	"Name Tag": "Tên Thẻ",
 	"Name your modelfile": "Đặt tên cho tệp mô hình của bạn",
@@ -295,7 +295,7 @@
 	"New Password": "Mật khẩu mới",
 	"No": "",
 	"No results found": "",
-	"No source available": "",
+	"No source available": "Không có nguồn",
 	"Not factually correct": "Không chính xác so với thực tế",
 	"Not sure what to add?": "Không chắc phải thêm gì?",
 	"Not sure what to write? Switch to": "Không chắc phải viết gì? Chuyển sang",
@@ -333,6 +333,7 @@
 	"PDF Extract Images (OCR)": "Trích xuất ảnh từ PDF (OCR)",
 	"pending": "đang chờ phê duyệt",
 	"Permission denied when accessing microphone: {{error}}": "Quyền truy cập micrô bị từ chối: {{error}}",
+	"Personalization": "",
 	"Plain text (.txt)": "",
 	"Playground": "Thử nghiệm (Playground)",
 	"Positive attitude": "",
@@ -372,7 +373,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Lưu",
 	"Save & Create": "Lưu & Tạo",
-	"Save & Submit": "Lưu & Gửi",
 	"Save & Update": "Lưu & Cập nhậ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": "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",
 	"Scan": "Quét tài liệu",
@@ -390,6 +390,7 @@
 	"Select an Ollama instance": "Chọn một thực thể Ollama",
 	"Select model": "",
 	"Selected model does not support image inputs.": "",
+	"Send": "",
 	"Send a Message": "Gửi yêu cầu",
 	"Send message": "Gửi yêu cầu",
 	"September": "",
@@ -418,7 +419,7 @@
 	"Sign Out": "Đăng xuất",
 	"Sign up": "Đăng ký",
 	"Signing in": "",
-	"Source": "",
+	"Source": "Nguồn",
 	"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",
 	"SpeechRecognition API is not supported in this browser.": "Trình duyệt này không hỗ trợ API Nhận dạng Giọng nói.",
@@ -493,11 +494,12 @@
 	"What’s New in": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Khi chế độ lịch sử chat đã tắt, các nội dung chat mới trên trình duyệt này sẽ không xuất hiện trên bất kỳ thiết bị nào của bạn.",
 	"Whisper (Local)": "Whisper (Local)",
+	"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].",
 	"Yes": "",
 	"Yesterday": "",
-	"You": "Bạn",
+	"You": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Bạn là một trợ lý hữu ích.",

+ 145 - 143
src/lib/i18n/locales/zh-CN/translation.json

@@ -2,33 +2,33 @@
 	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' 或 '-1' 表示无过期时间。",
 	"(Beta)": "(测试版)",
 	"(e.g. `sh webui.sh --api`)": "(例如 `sh webui.sh --api`)",
-	"(latest)": "",
+	"(latest)": "(最新版)",
 	"{{modelName}} is thinking...": "{{modelName}} 正在思考...",
-	"{{user}}'s Chats": "",
+	"{{user}}'s Chats": "{{user}} 的聊天记录",
 	"{{webUIName}} Backend Required": "需要 {{webUIName}} 后端",
 	"a user": "用户",
 	"About": "关于",
 	"Account": "账户",
-	"Accurate information": "",
+	"Accurate information": "准确信息",
 	"Add a model": "添加模型",
 	"Add a model tag name": "添加模型标签名称",
 	"Add a short description about what this modelfile does": "为这个模型文件添加一段简短的描述",
 	"Add a short title for this prompt": "为这个提示词添加一个简短的标题",
 	"Add a tag": "添加标签",
-	"Add custom prompt": "",
+	"Add custom prompt": "添加自定义提示词",
 	"Add Docs": "添加文档",
 	"Add Files": "添加文件",
 	"Add message": "添加消息",
-	"Add Model": "",
+	"Add Model": "添加模型",
 	"Add Tags": "添加标签",
-	"Add User": "",
+	"Add User": "添加用户",
 	"Adjusting these settings will apply changes universally to all users.": "调整这些设置将会对所有用户应用更改。",
 	"admin": "管理员",
 	"Admin Panel": "管理员面板",
 	"Admin Settings": "管理员设置",
 	"Advanced Parameters": "高级参数",
 	"all": "所有",
-	"All Documents": "",
+	"All Documents": "所有文档",
 	"All Users": "所有用户",
 	"Allow": "允许",
 	"Allow Chat Deletion": "允许删除聊天记录",
@@ -36,36 +36,38 @@
 	"Already have an account?": "已经有账户了吗?",
 	"an assistant": "助手",
 	"and": "和",
-	"and create a new shared link.": "",
+	"and create a new shared link.": "创建一个新的共享链接。",
 	"API Base URL": "API 基础 URL",
 	"API Key": "API 密钥",
-	"API Key created.": "",
-	"API keys": "",
+	"API Key created.": "API 密钥已创建。",
+	"API keys": "API 密钥",
 	"API RPM": "API RPM",
-	"April": "",
-	"Archive": "",
+	"April": "四月",
+	"Archive": "存档",
 	"Archived Chats": "聊天记录存档",
 	"are allowed - Activate this command by typing": "允许 - 通过输入来激活这个命令",
 	"Are you sure?": "你确定吗?",
-	"Attach file": "",
-	"Attention to detail": "",
+	"Attach file": "附件",
+	"Attention to detail": "注重细节",
 	"Audio": "音频",
-	"August": "",
+	"August": "八月",
 	"Auto-playback response": "自动播放回应",
 	"Auto-send input after 3 sec.": "3 秒后自动发送输入",
 	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 基础 URL",
 	"AUTOMATIC1111 Base URL is required.": "需要 AUTOMATIC1111 基础 URL。",
 	"available!": "可用!",
 	"Back": "返回",
-	"Bad Response": "",
-	"before": "",
-	"Being lazy": "",
+	"Bad Response": "不良响应",
+	"before": "之前",
+	"Being lazy": "懒惰",
+	"Beta": "",
 	"Builder Mode": "构建模式",
-	"Bypass SSL verification for Websites": "",
+	"Bypass SSL verification for Websites": "绕过网站的 SSL 验证",
 	"Cancel": "取消",
 	"Categories": "分类",
 	"Change Password": "更改密码",
 	"Chat": "聊天",
+	"Chat Bubble UI": "",
 	"Chat History": "聊天历史",
 	"Chat History is off for this browser.": "此浏览器已关闭聊天历史功能。",
 	"Chats": "聊天",
@@ -76,69 +78,68 @@
 	"Chunk Overlap": "块重叠 (Chunk Overlap)",
 	"Chunk Params": "块参数 (Chunk Params)",
 	"Chunk Size": "块大小 (Chunk Size)",
-	"Citation": "",
+	"Citation": "引文",
 	"Click here for help.": "点击这里获取帮助。",
-	"Click here to": "",
+	"Click here to": "单击此处",
 	"Click here to check other modelfiles.": "点击这里检查其他模型文件。",
 	"Click here to select": "点击这里选择",
-	"Click here to select a csv file.": "",
+	"Click here to select a csv file.": "单击此处选择 csv 文件。",
 	"Click here to select documents.": "点击这里选择文档。",
 	"click here.": "点击这里。",
 	"Click on the user role button to change a user's role.": "点击用户角色按钮以更改用户的角色。",
 	"Close": "关闭",
 	"Collection": "收藏",
-	"ComfyUI": "",
-	"ComfyUI Base URL": "",
-	"ComfyUI Base URL is required.": "",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "ComfyUI Base URL",
+	"ComfyUI Base URL is required.": "ComfyUI Base URL 是必需的。",
 	"Command": "命令",
 	"Confirm Password": "确认密码",
 	"Connections": "连接",
 	"Content": "内容",
 	"Context Length": "上下文长度",
-	"Continue Response": "",
+	"Continue Response": "继续回复",
 	"Conversation Mode": "对话模式",
-	"Copied shared chat URL to clipboard!": "",
-	"Copy": "",
+	"Copied shared chat URL to clipboard!": "已复制共享聊天 URL 到剪贴板!",
+	"Copy": "复制",
 	"Copy last code block": "复制最后一个代码块",
 	"Copy last response": "复制最后一次回复",
-	"Copy Link": "",
+	"Copy Link": "复制链接",
 	"Copying to clipboard was successful!": "复制到剪贴板成功!",
 	"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':": "为以下查询创建一个简洁的、3-5 个词的短语作为标题,严格遵守 3-5 个词的限制并避免使用“标题”一词:",
 	"Create a modelfile": "创建模型文件",
 	"Create Account": "创建账户",
-	"Create new key": "",
-	"Create new secret key": "",
+	"Create new key": "创建新密钥",
+	"Create new secret key": "创建新安全密钥",
 	"Created at": "创建于",
-	"Created At": "",
+	"Created At": "创建于",
 	"Current Model": "当前模型",
 	"Current Models": "",
 	"Current Password": "当前密码",
 	"Custom": "自定义",
 	"Customize Ollama models for a specific purpose": "定制特定用途的 Ollama 模型",
 	"Dark": "暗色",
-	"Dashboard": "",
+	"Dashboard": "仪表盘",
 	"Database": "数据库",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
-	"December": "",
+	"December": "十二月",
 	"Default": "默认",
 	"Default (Automatic1111)": "默认(Automatic1111)",
-	"Default (SentenceTransformers)": "",
+	"Default (SentenceTransformers)": "默认(SentenceTransformers)",
 	"Default (Web API)": "默认(Web API)",
 	"Default model updated": "默认模型已更新",
 	"Default Prompt Suggestions": "默认提示词建议",
 	"Default User Role": "默认用户角色",
 	"delete": "删除",
-	"Delete": "",
+	"Delete": "删除",
 	"Delete a model": "删除一个模型",
 	"Delete chat": "删除聊天",
-	"Delete Chat": "",
+	"Delete Chat": "删除聊天",
 	"Delete Chats": "删除聊天记录",
-	"delete this link": "",
-	"Delete User": "",
+	"delete this link": "删除这个链接",
+	"Delete User": "删除用户",
 	"Deleted {{deleteModelTag}}": "已删除{{deleteModelTag}}",
-	"Deleted {{tagName}}": "",
+	"Deleted {{tagName}}": "已删除 {{tagName}}",
 	"Description": "描述",
-	"Didn't fully follow instructions": "",
+	"Didn't fully follow instructions": "没有完全遵循指示",
 	"Disabled": "禁用",
 	"Discover a modelfile": "探索模型文件",
 	"Discover a prompt": "探索提示词",
@@ -151,28 +152,28 @@
 	"does not make any external connections, and your data stays securely on your locally hosted server.": "不进行任何外部连接,您的数据安全地存储在您的本地服务器上。",
 	"Don't Allow": "不允许",
 	"Don't have an account?": "没有账户?",
-	"Don't like the style": "",
-	"Download": "",
-	"Download canceled": "",
+	"Don't like the style": "不喜欢这个风格",
+	"Download": "下载",
+	"Download canceled": "下载已取消",
 	"Download Database": "下载数据库",
 	"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": "编辑",
 	"Edit Doc": "编辑文档",
 	"Edit User": "编辑用户",
 	"Email": "电子邮件",
-	"Embedding Model": "",
-	"Embedding Model Engine": "",
-	"Embedding model set to \"{{embedding_model}}\"": "",
+	"Embedding Model": "嵌入模型",
+	"Embedding Model Engine": "嵌入模型引擎",
+	"Embedding model set to \"{{embedding_model}}\"": "嵌入模型设置为 \"{{embedding_model}}\"",
 	"Enable Chat History": "启用聊天历史",
 	"Enable New Sign Ups": "启用新注册",
 	"Enabled": "启用",
-	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "确保您的 CSV 文件按以下顺序包含 4 列: 姓名、电子邮件、密码、角色。",
 	"Enter {{role}} message here": "在此处输入 {{role}} 信息",
 	"Enter Chunk Overlap": "输入块重叠 (Chunk Overlap)",
 	"Enter Chunk Size": "输入块大小 (Chunk Size)",
 	"Enter Image Size (e.g. 512x512)": "输入图片大小 (例如 512x512)",
-	"Enter language codes": "",
+	"Enter language codes": "输入语言代码",
 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "输入 LiteLLM API 基本 URL (litellm_params.api_base)",
 	"Enter LiteLLM API Key (litellm_params.api_key)": "输入 LiteLLM API 密匙 (litellm_params.api_key)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "输入 LiteLLM API 速率限制 (litellm_params.rpm)",
@@ -181,45 +182,46 @@
 	"Enter Model Display Name": "",
 	"Enter model tag (e.g. {{modelTag}})": "输入模型标签 (例如{{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "输入步数 (例如 50)",
-	"Enter Score": "",
+	"Enter Score": "输入分",
 	"Enter stop sequence": "输入停止序列",
 	"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)": "",
+	"Enter URL (e.g. http://localhost:11434)": "输入 URL (例如 http://localhost:11434)",
 	"Enter Your Email": "输入您的电子邮件",
 	"Enter Your Full Name": "输入您的全名",
 	"Enter Your Password": "输入您的密码",
-	"Enter Your Role": "",
+	"Enter Your Role": "输入您的角色",
 	"Experimental": "实验性",
 	"Export All Chats (All Users)": "导出所有聊天(所有用户)",
 	"Export Chats": "导出聊天",
 	"Export Documents Mapping": "导出文档映射",
 	"Export Modelfiles": "导出模型文件",
 	"Export Prompts": "导出提示词",
-	"Failed to create API Key.": "",
+	"Failed to create API Key.": "无法创建 API 密钥。",
 	"Failed to read clipboard contents": "无法读取剪贴板内容",
-	"February": "",
-	"Feel free to add specific details": "",
+	"February": "二月",
+	"Feel free to add specific details": "请随意添加具体细节",
 	"File Mode": "文件模式",
 	"File not found.": "文件未找到。",
-	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "检测到指纹欺骗: 无法使用姓名缩写作为头像。默认使用默认个人形象。",
 	"Fluidly stream large external response chunks": "流畅地传输大型外部响应块",
 	"Focus chat input": "聚焦聊天输入",
-	"Followed instructions perfectly": "",
+	"Followed instructions perfectly": "完全遵循说明",
 	"Format your variables using square brackets like this:": "使用这样的方括号格式化你的变量:",
 	"From (Base Model)": "来自(基础模型)",
 	"Full Screen Mode": "全屏模式",
 	"General": "通用",
 	"General Settings": "通用设置",
-	"Generation Info": "",
-	"Good Response": "",
-	"has no conversations.": "",
+	"Generation Info": "生成信息",
+	"Good Response": "反应良好",
+	"h:mm a": "h:mm a",
+	"has no conversations.": "没有对话。",
 	"Hello, {{name}}": "你好,{{name}}",
-	"Help": "",
+	"Help": "帮助",
 	"Hide": "隐藏",
 	"Hide Additional Params": "隐藏额外参数",
 	"How can I help you today?": "我今天能帮你做什么?",
-	"Hybrid Search": "",
+	"Hybrid Search": "混合搜索",
 	"Image Generation (Experimental)": "图像生成(实验性)",
 	"Image Generation Engine": "图像生成引擎",
 	"Image Settings": "图像设置",
@@ -229,21 +231,21 @@
 	"Import Modelfiles": "导入模型文件",
 	"Import Prompts": "导入提示",
 	"Include `--api` flag when running stable-diffusion-webui": "运行 stable-diffusion-webui 时包含 `--api` 标志",
-	"Input commands": "",
+	"Input commands": "输入命令",
 	"Interface": "界面",
-	"Invalid Tag": "",
+	"Invalid Tag": "无效标签",
 	"Is Model Vision Capable": "",
-	"January": "",
+	"January": "一月",
 	"join our Discord for help.": "加入我们的 Discord 寻求帮助。",
 	"JSON": "JSON",
-	"July": "",
-	"June": "",
+	"July": "七月",
+	"June": "六月",
 	"JWT Expiration": "JWT 过期",
 	"JWT Token": "JWT 令牌",
 	"Keep Alive": "保持活动",
 	"Keyboard shortcuts": "键盘快捷键",
 	"Language": "语言",
-	"Last Active": "",
+	"Last Active": "最后活跃",
 	"Light": "浅色",
 	"Listening...": "监听中...",
 	"LLMs can make mistakes. Verify important information.": "LLM 可能会生成错误信息,请验证重要信息。",
@@ -253,17 +255,18 @@
 	"Manage Model Information": "",
 	"Manage Models": "管理模型",
 	"Manage Ollama Models": "管理 Ollama 模型",
-	"March": "",
+	"March": "三月",
 	"Max Tokens": "最大令牌数",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "最多可以同时下载 3 个模型,请稍后重试。",
-	"May": "",
-	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
-	"Minimum Score": "",
+	"May": "五月",
+	"Memory": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "创建链接后发送的信息不会被共享。拥有 URL 的用户可以查看共享的聊天内容。",
+	"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",
 	"Model '{{modelName}}' has been successfully downloaded.": "模型'{{modelName}}'已成功下载。",
 	"Model '{{modelTag}}' is already in queue for downloading.": "模型'{{modelTag}}'已在下载队列中。",
 	"Model {{modelId}} not found": "未找到模型{{modelId}}",
@@ -271,7 +274,7 @@
 	"Model {{modelName}} is not vision capable": "",
 	"Model Description": "",
 	"Model Display Name": "",
-	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "检测到模型文件系统路径。模型简名是更新所必需的,无法继续。",
 	"Model info for {{modelName}} added successfully": "",
 	"Model info for {{modelName}} deleted successfully": "",
 	"Model Name": "模型名称",
@@ -284,29 +287,26 @@
 	"Modelfile Content": "模型文件内容",
 	"Modelfiles": "模型文件",
 	"Models": "模型",
-	"More": "",
-	"My Documents": "我的文档",
-	"My Modelfiles": "我的模型文件",
-	"My Prompts": "我的提示词",
+	"More": "更多",
 	"Name": "名称",
 	"Name Tag": "名称标签",
 	"Name your modelfile": "命名你的模型文件",
 	"New Chat": "新聊天",
 	"New Password": "新密码",
 	"No": "",
-	"No results found": "",
-	"No source available": "",
-	"Not factually correct": "",
+	"No results found": "未找到结果",
+	"No source available": "没有可用来源",
+	"Not factually correct": "与事实不符",
 	"Not sure what to add?": "不确定要添加什么?",
 	"Not sure what to write? Switch to": "不确定写什么?切换到",
-	"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: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "注意:如果设置了最低分数,搜索只会返回分数大于或等于最低分数的文档。",
 	"Notifications": "桌面通知",
-	"November": "",
-	"October": "",
+	"November": "十一月",
+	"October": "十月",
 	"Off": "关闭",
 	"Okay, Let's Go!": "好的,我们开始吧!",
-	"OLED Dark": "",
-	"Ollama": "",
+	"OLED Dark": "暗黑色",
+	"Ollama": "Ollama",
 	"Ollama Base URL": "Ollama 基础 URL",
 	"Ollama Version": "Ollama 版本",
 	"On": "开",
@@ -319,52 +319,53 @@
 	"Open AI": "Open AI",
 	"Open AI (Dall-E)": "Open AI (Dall-E)",
 	"Open new chat": "打开新聊天",
-	"OpenAI": "",
+	"OpenAI": "OpenAI",
 	"OpenAI API": "OpenAI API",
-	"OpenAI API Config": "",
+	"OpenAI API Config": "OpenAI API 配置",
 	"OpenAI API Key is required.": "需要 OpenAI API 密钥。",
-	"OpenAI URL/Key required.": "",
+	"OpenAI URL/Key required.": "需要 OpenAI URL/Key",
 	"or": "或",
-	"Other": "",
-	"Overview": "",
+	"Other": "其他",
+	"Overview": "概述",
 	"Parameters": "参数",
 	"Password": "密码",
-	"PDF document (.pdf)": "",
+	"PDF document (.pdf)": "PDF 文档 (.pdf)",
 	"PDF Extract Images (OCR)": "PDF 图像处理 (使用 OCR)",
 	"pending": "待定",
 	"Permission denied when accessing microphone: {{error}}": "访问麦克风时权限被拒绝:{{error}}",
-	"Plain text (.txt)": "",
+	"Personalization": "",
+	"Plain text (.txt)": "PDF 文档 (.pdf)",
 	"Playground": "AI 对话游乐场",
-	"Positive attitude": "",
-	"Previous 30 days": "",
-	"Previous 7 days": "",
-	"Profile Image": "",
-	"Prompt": "",
-	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
+	"Positive attitude": "积极态度",
+	"Previous 30 days": "过去 30 天",
+	"Previous 7 days": "过去 7 天",
+	"Profile Image": "用户头像",
+	"Prompt": "提示词",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "提示(例如:告诉我一个关于罗马帝国的有趣事实)",
 	"Prompt Content": "提示词内容",
 	"Prompt suggestions": "提示词建议",
 	"Prompts": "提示词",
-	"Pull \"{{searchValue}}\" from Ollama.com": "",
+	"Pull \"{{searchValue}}\" from Ollama.com": "从 Ollama.com 拉取 \"{{searchValue}}\"",
 	"Pull a model from Ollama.com": "从 Ollama.com 拉取一个模型",
 	"Pull Progress": "拉取进度",
 	"Query Params": "查询参数",
 	"RAG Template": "RAG 模板",
 	"Raw Format": "原始格式",
-	"Read Aloud": "",
+	"Read Aloud": "朗读",
 	"Record voice": "录音",
 	"Redirecting you to OpenWebUI Community": "正在将您重定向到 OpenWebUI 社区",
-	"Refused when it shouldn't have": "",
-	"Regenerate": "",
+	"Refused when it shouldn't have": "在不该拒绝时拒绝",
+	"Regenerate": "重新生成",
 	"Release Notes": "发布说明",
-	"Remove": "",
-	"Remove Model": "",
-	"Rename": "",
+	"Remove": "移除",
+	"Remove Model": "移除模型",
+	"Rename": "重命名",
 	"Repeat Last N": "重复最后 N 次",
 	"Repeat Penalty": "重复惩罚",
 	"Request Mode": "请求模式",
-	"Reranking Model": "",
-	"Reranking model disabled": "",
-	"Reranking model set to \"{{reranking_model}}\"": "",
+	"Reranking Model": "重排模型",
+	"Reranking model disabled": "重排模型已禁用",
+	"Reranking model set to \"{{reranking_model}}\"": "重排模型设置为 \"{{reranking_model}}\"",
 	"Reset Vector Storage": "重置向量存储",
 	"Response AutoCopy to Clipboard": "自动复制回答到剪贴板",
 	"Role": "角色",
@@ -372,14 +373,13 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "保存",
 	"Save & Create": "保存并创建",
-	"Save & Submit": "保存并提交",
 	"Save & Update": "保存并更新",
 	"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": "不再支持直接将聊天记录保存到浏览器存储中。请点击下面的按钮下载并删除您的聊天记录。别担心,您可以通过轻松地将聊天记录重新导入到后端",
 	"Scan": "扫描",
 	"Scan complete!": "扫描完成!",
 	"Scan for documents from {{path}}": "从 {{path}} 扫描文档",
 	"Search": "搜索",
-	"Search a model": "",
+	"Search a model": "搜索模型",
 	"Search Documents": "搜索文档",
 	"Search Prompts": "搜索提示词",
 	"See readme.md for instructions": "查看 readme.md 以获取说明",
@@ -388,74 +388,75 @@
 	"Select a mode": "选择一个模式",
 	"Select a model": "选择一个模型",
 	"Select an Ollama instance": "选择一个 Ollama 实例",
-	"Select model": "",
+	"Select model": "选择模型",
 	"Selected model does not support image inputs.": "",
+	"Send": "发送",
 	"Send a Message": "发送消息",
 	"Send message": "发送消息",
-	"September": "",
+	"September": "九月",
 	"Server connection verified": "已验证服务器连接",
 	"Set as default": "设为默认",
 	"Set Default Model": "设置默认模型",
-	"Set embedding model (e.g. {{model}})": "",
+	"Set embedding model (e.g. {{model}})": "设置嵌入模型(例如 {{model}})",
 	"Set Image Size": "设置图片大小",
 	"Set Model": "设置模型",
-	"Set reranking model (e.g. {{model}})": "",
+	"Set reranking model (e.g. {{model}})": "设置重排模型(例如 {{model}})",
 	"Set Steps": "设置步骤",
 	"Set Title Auto-Generation Model": "设置标题自动生成模型",
 	"Set Voice": "设置声音",
 	"Settings": "设置",
 	"Settings saved successfully!": "设置已保存",
-	"Share": "",
-	"Share Chat": "",
+	"Share": "分享",
+	"Share Chat": "分享聊天",
 	"Share to OpenWebUI Community": "分享到 OpenWebUI 社区",
 	"short-summary": "简短总结",
 	"Show": "显示",
 	"Show Additional Params": "显示额外参数",
 	"Show shortcuts": "显示快捷方式",
-	"Showcased creativity": "",
+	"Showcased creativity": "展示创意",
 	"sidebar": "侧边栏",
 	"Sign in": "登录",
 	"Sign Out": "登出",
 	"Sign up": "注册",
-	"Signing in": "",
-	"Source": "",
+	"Signing in": "正在登录",
+	"Source": "来源",
 	"Speech recognition error: {{error}}": "语音识别错误:{{error}}",
 	"Speech-to-Text Engine": "语音转文字引擎",
 	"SpeechRecognition API is not supported in this browser.": "此浏览器不支持 SpeechRecognition API。",
 	"Stop Sequence": "停止序列",
 	"STT Settings": "语音转文字设置",
 	"Submit": "提交",
-	"Subtitle (e.g. about the Roman Empire)": "",
+	"Subtitle (e.g. about the Roman Empire)": "副标题(如关于罗马帝国的副标题)",
 	"Success": "成功",
 	"Successfully updated.": "成功更新。",
-	"Suggested": "",
+	"Suggested": "建议",
 	"Sync All": "同步所有",
 	"System": "系统",
 	"System Prompt": "系统提示",
 	"Tags": "标签",
-	"Tell us more:": "",
+	"Tell us more:": "告诉我们更多信息",
 	"Temperature": "温度",
 	"Template": "模板",
 	"Text Completion": "文本完成",
 	"Text-to-Speech Engine": "文本转语音引擎",
 	"Tfs Z": "Tfs Z",
-	"Thanks for your feedback!": "",
-	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
+	"Thanks for your feedback!": "感谢你的反馈!",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "分值应介于 0.0(0%)和 1.0(100%)之间。",
 	"Theme": "主题",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "这确保了您宝贵的对话被安全保存到后端数据库中。谢谢!",
 	"This setting does not sync across browsers or devices.": "此设置不会在浏览器或设备之间同步。",
-	"Thorough explanation": "",
+	"Thorough explanation": "详尽的解释",
 	"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 (e.g. Tell me a fun fact)": "标题(例如 告诉我一个有趣的事实)",
 	"Title Auto-Generation": "标题自动生成",
-	"Title cannot be an empty string.": "",
+	"Title cannot be an empty string.": "标题不能为空字符串。",
 	"Title Generation Prompt": "自动生成标题的提示词",
 	"to": "到",
 	"To access the available model names for downloading,": "要访问可下载的模型名称,",
 	"To access the GGUF models available for downloading,": "要访问可下载的 GGUF 模型,",
 	"to chat input.": "到聊天输入。",
-	"Today": "",
+	"Today": "今天",
 	"Toggle settings": "切换设置",
 	"Toggle sidebar": "切换侧边栏",
 	"Top K": "Top K",
@@ -465,7 +466,7 @@
 	"Type Hugging Face Resolve (Download) URL": "输入 Hugging Face 解析(下载)URL",
 	"Uh-oh! There was an issue connecting to {{provider}}.": "哎呀!连接到{{provider}}时出现问题。",
 	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "未知文件类型'{{file_type}}',将视为纯文本进行处理",
-	"Update and Copy Link": "",
+	"Update and Copy Link": "更新和复制链接",
 	"Update password": "更新密码",
 	"Upload a GGUF model": "上传一个 GGUF 模型",
 	"Upload files": "上传文件",
@@ -473,7 +474,7 @@
 	"URL Mode": "URL 模式",
 	"Use '#' in the prompt input to load and select your documents.": "在提示输入中使用'#'来加载和选择你的文档。",
 	"Use Gravatar": "使用 Gravatar",
-	"Use Initials": "",
+	"Use Initials": "使用首字母缩写",
 	"user": "用户",
 	"User Permissions": "用户权限",
 	"Users": "用户",
@@ -482,26 +483,27 @@
 	"variable": "变量",
 	"variable to have them replaced with clipboard content.": "变量将被剪贴板内容替换。",
 	"Version": "版本",
-	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "警告: 如果更新或更改 embedding 模型,则需要重新导入所有文档。",
 	"Web": "网页",
-	"Web Loader Settings": "",
-	"Web Params": "",
-	"Webhook URL": "",
+	"Web Loader Settings": "Web 加载器设置",
+	"Web Params": "Web 参数",
+	"Webhook URL": "Webhook URL",
 	"WebUI Add-ons": "WebUI 插件",
 	"WebUI Settings": "WebUI 设置",
 	"WebUI will make requests to": "WebUI 将请求",
 	"What’s New in": "最新变化",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "当历史记录被关闭时,这个浏览器上的新聊天不会出现在你任何设备的历史记录中。",
 	"Whisper (Local)": "Whisper(本地)",
+	"Workspace": "工作空间",
 	"Write a prompt suggestion (e.g. Who are you?)": "写一个提示建议(例如:你是谁?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "用 50 个字写一个总结 [主题或关键词]。",
 	"Yes": "",
-	"Yesterday": "",
-	"You": "",
-	"You have no archived conversations.": "",
-	"You have shared this chat": "",
+	"Yesterday": "昨天",
+	"You": "",
+	"You have no archived conversations.": "你没有存档的对话。",
+	"You have shared this chat": "你分享了这次聊天",
 	"You're a helpful assistant.": "你是一个有帮助的助手。",
 	"You're now logged in.": "已登录。",
-	"Youtube": "",
-	"Youtube Loader Settings": ""
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Youtube 加载器设置"
 }

部分文件因为文件数量过多而无法显示