Compare commits

...

133 Commits

Author SHA1 Message Date
Timothy Jaeryang Baek 22c50f62cb Merge pull request #1631 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Failing after 49s
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Failing after 51s
Create and publish Docker images with specific build args / merge-main-images (push) Has been skipped
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Failing after 54s
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Failing after 53s
Create and publish Docker images with specific build args / merge-cuda-images (push) Has been skipped
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Failing after 55s
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Failing after 51s
Create and publish Docker images with specific build args / merge-ollama-images (push) Has been skipped
0.1.120
2024-04-20 17:41:00 -07:00
Timothy J. Baek eefe01454f refac: include archived flag in exports 2024-04-20 19:32:32 -05:00
Timothy J. Baek 74dd9b561a revert: export chats to include archived chats 2024-04-20 19:24:58 -05:00
Timothy J. Baek 2fd2f792d6 refac 2024-04-20 19:20:46 -05:00
Timothy J. Baek 93bd20b854 doc: changelog 2024-04-20 19:15:11 -05:00
Timothy J. Baek 291c7595c4 refac 2024-04-20 19:15:04 -05:00
Timothy J. Baek 68de49e533 fix: feedback area scroll into view 2024-04-20 19:10:05 -05:00
Timothy J. Baek d6a0805966 refac: audio settings 2024-04-20 19:01:46 -05:00
Timothy J. Baek a5f8e87f3f feat: archived chat link 2024-04-20 18:50:41 -05:00
Timothy J. Baek 45ecaaf392 feat: unarchive 2024-04-20 18:47:20 -05:00
Timothy J. Baek 4c221eabef refac 2024-04-20 18:29:14 -05:00
Timothy J. Baek b12edb4a7a refac: replace timestamp field 2024-04-20 18:24:18 -05:00
Timothy J. Baek 50e8979c00 Update README.md 2024-04-20 17:40:55 -05:00
Timothy J. Baek 459a41774e Update bug_report.md 2024-04-20 17:11:03 -05:00
Timothy J. Baek fbd520bf07 feat: archive chat 2024-04-20 17:03:39 -05:00
Timothy J. Baek 00b01c973e feat: archive button 2024-04-20 16:13:16 -05:00
Timothy Jaeryang Baek f04164378a Merge pull request #1632 from cheahjs/fix/error-object-object
fix: use model name when outputting error message
2024-04-20 14:05:23 -07:00
Timothy J. Baek c468df2f71 feat: env var for audio 2024-04-20 16:04:16 -05:00
Timothy J. Baek fa9593b4e8 refac: openai error message 2024-04-20 16:01:25 -05:00
Timothy J. Baek cbd18ec63c feat: external openai tts support 2024-04-20 16:00:24 -05:00
Jun Siang Cheah 7e5bda6016 fix: use model name when outputting error message 2024-04-20 21:24:02 +01:00
Timothy J. Baek 713934edb6 refac 2024-04-20 15:21:52 -05:00
Timothy J. Baek 710850e442 refac: audio 2024-04-20 15:15:59 -05:00
Timothy J. Baek 2a10438b4d feat: share from chat menu 2024-04-20 14:40:06 -05:00
Timothy J. Baek 7eb14437ff feat: shortcut 2024-04-20 14:26:27 -05:00
Timothy J. Baek 9451726ee6 feat: save edited message shortcut 2024-04-20 14:24:19 -05:00
Timothy J. Baek 97d68a6a05 feat: multiple files input
Co-Authored-By: Entaigner <61445450+entaigner@users.noreply.github.com>
2024-04-20 13:41:16 -05:00
Timothy Jaeryang Baek cd79afb425 Merge pull request #1624 from que-nguyen/dev
Add i18n translation for feedback reasons
2024-04-20 11:35:19 -07:00
Timothy J. Baek b79596332a chore: format 2024-04-20 13:35:01 -05:00
Timothy Jaeryang Baek 21cc6ebcb7 Merge pull request #1623 from que-nguyen/main
Fix hover and selected state issues for reason buttons in light theme.
2024-04-20 11:33:43 -07:00
Timothy Jaeryang Baek 80ecea980e Merge pull request #1622 from giga-t/main
Added Georgian Language (ka-GE)
2024-04-20 11:33:07 -07:00
Timothy Jaeryang Baek 4c8932d71c Merge pull request #1621 from Entaigner/patch-4
Tagged chat suggestion placeholders for translation (settings>interface)
2024-04-20 11:32:41 -07:00
Que Nguyen 7a5a3c45e0 Merge branch 'open-webui:dev' into dev 2024-04-20 13:26:49 +07:00
Que Nguyen 3df03fa3fe Add i18n translation for feedback reasons 2024-04-20 11:51:11 +07:00
Que Nguyen 77ec9dd1f2 Fix hover and selected state issues for reason buttons in light theme. 2024-04-20 11:03:47 +07:00
Giga 22b694e0fd Update translation.json
fixed one word.
2024-04-19 23:26:23 +02:00
Giga 5e9ace1c6e Add files via upload
Added Georgian translation (ka-GE).
2024-04-19 23:24:19 +02:00
Entaigner 76bd77bc56 Enable translation for chat suggestion placeholders (settings>interface) 2024-04-19 23:13:17 +02:00
Timothy J. Baek a4083f43cb fix: safari copy link issue 2024-04-19 06:34:55 -05:00
Timothy Jaeryang Baek b67f80f7a4 Merge pull request #1600 from Fusseldieb/patch-1 2024-04-18 06:30:33 -07:00
Valentino Stillhardt a53c1cfc77 Fixed translated author name 2024-04-18 09:40:38 -03:00
Valentino Stillhardt b788514a10 Fixed translated author name 2024-04-18 09:38:26 -03:00
Valentino Stillhardt 5f16ec077a Fixed malformed time format 2024-04-18 09:34:03 -03:00
Valentino Stillhardt 941dc41c3d Fixed translated variable names and malformed date string 2024-04-18 09:28:10 -03:00
Valentino Stillhardt 44f9e930d2 Fixed malformed date string 2024-04-18 09:26:54 -03:00
Valentino Stillhardt c235a3d539 Fixed translated variable names 2024-04-18 09:25:39 -03:00
Valentino Stillhardt e720afacfa Fixed translated variable names 2024-04-18 09:24:29 -03:00
Valentino Stillhardt b352732b43 Fixed translated variable names 2024-04-18 09:19:55 -03:00
Valentino Stillhardt 0caf04617e Fixed translated variable name
"w" as in "week" was translated into "s" (as in "semana"), which is obviously wrong.
2024-04-18 09:17:35 -03:00
Timothy Jaeryang Baek e0ebd7aeaf Merge pull request #1590 from open-webui/dev
dev
2024-04-17 13:23:58 -07:00
Timothy Jaeryang Baek 2ffe55f128 Merge pull request #1584 from pkrolkgp/patch-2
Update translation.json
2024-04-17 13:22:59 -07:00
Timothy Jaeryang Baek 9d73f22aff Merge pull request #1586 from Fusseldieb/patch-1
Fixed malformed date string (again)
2024-04-17 13:22:38 -07:00
Timothy J. Baek 9dad7e7c9a fix 2024-04-17 15:22:21 -05:00
Valentino Stillhardt b5372bf715 Fixed malformed date string 2024-04-17 11:08:02 -03:00
pkrolkgp 20a3db975d Update translation.json
fix translated variable names to original
2024-04-17 12:28:32 +00:00
Timothy Jaeryang Baek 851754700a Merge pull request #1555 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Failing after 59s
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Failing after 1m7s
Create and publish Docker images with specific build args / merge-main-images (push) Has been skipped
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Failing after 53s
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Failing after 1m0s
Create and publish Docker images with specific build args / merge-cuda-images (push) Has been skipped
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Failing after 52s
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Failing after 1m1s
Create and publish Docker images with specific build args / merge-ollama-images (push) Has been skipped
0.1.119
2024-04-16 15:12:52 -07:00
Timothy J. Baek 375056f8dc refac: wording 2024-04-16 17:11:05 -05:00
Timothy J. Baek 236e2c040c refac 2024-04-16 17:08:50 -05:00
Timothy J. Baek cf811edefb doc: changelog 2024-04-16 17:06:16 -05:00
Timothy J. Baek f6e839611b refac 2024-04-16 17:03:12 -05:00
Timothy J. Baek 2a79c30657 refac 2024-04-16 16:51:01 -05:00
Timothy J. Baek d882cb41ac chore: unused var 2024-04-16 16:43:19 -05:00
Timothy J. Baek 323b9adc63 feat: chat menu tag 2024-04-16 16:31:06 -05:00
Timothy J. Baek 3488b7f006 refac: changelog modal close behaviour 2024-04-16 16:01:12 -05:00
Timothy J. Baek daed66f7c6 feat: sidebar swipe support 2024-04-16 15:57:14 -05:00
Timothy Jaeryang Baek b219906fa3 Merge pull request #1580 from djismgaming/spanish-translation-update
Small fixes to the Spanish translation
2024-04-16 13:42:41 -07:00
Ismael a556e39651 little word fix 2024-04-16 16:30:13 -04:00
Ismael b35316177b translation, into Spanish, of imported strings from English 2024-04-16 16:24:27 -04:00
Ismael 3ed509c5d3 added new strings from English to be translated to Spanish 2024-04-16 16:15:23 -04:00
Ismael 6699f0c339 small fixes to the spanish translation 2024-04-16 15:59:33 -04:00
Timothy J. Baek 9dbd223b4e fix: formatting 2024-04-16 14:32:27 -05:00
Timothy J. Baek 11e0aaa32f revert 2024-04-16 14:32:02 -05:00
Timothy J. Baek 9aea0fb03d Update translation.json 2024-04-16 14:25:45 -05:00
Timothy J. Baek b51922809b Update languages.json 2024-04-16 14:24:15 -05:00
Timothy J. Baek 314b5b5f6c fix 2024-04-16 14:23:57 -05:00
Timothy Jaeryang Baek 7c0c11b776 Merge pull request #1573 from pkrolkgp/patch-1
Create translation.json for polish language
2024-04-16 12:18:14 -07:00
Timothy Jaeryang Baek 44ad7c4132 Merge pull request #1577 from justinh-rahb/pip2uv2
Switch `pip3` to `uv` in Dockerfile
2024-04-16 12:17:47 -07:00
Justin Hayes 748a930a5f Replace pip3 with uv 2024-04-16 09:57:32 -04:00
pkrolkgp dba45acbda Create translation.json for polish language
Created polish language translation
2024-04-16 12:09:57 +00:00
Timothy J. Baek ac7bb03cb3 chore: formatting 2024-04-14 20:19:16 -04:00
Timothy J. Baek 1be60c9729 chore: version bump 2024-04-14 20:13:00 -04:00
Timothy Jaeryang Baek 54a4b7db14 Merge pull request #1554 from open-webui/external-embeddings
feat: external embeddings
2024-04-14 16:57:57 -07:00
Timothy J. Baek 741ed5dc4c fix 2024-04-14 19:56:33 -04:00
Timothy J. Baek b1b72441bb feat: openai embeddings integration 2024-04-14 19:48:15 -04:00
Timothy J. Baek b48e73fa43 feat: openai embeddings support 2024-04-14 19:15:39 -04:00
Timothy Jaeryang Baek 2e0def73eb Merge pull request #1553 from open-webui/external-embeddings
feat: external embeddings support
2024-04-14 15:48:15 -07:00
Timothy J. Baek 36ce157907 fix: integration 2024-04-14 18:47:45 -04:00
Timothy J. Baek 9cdb5bf9fe feat: frontend integration 2024-04-14 18:31:40 -04:00
Timothy J. Baek 2952e61167 feat: external embeddings support 2024-04-14 17:55:00 -04:00
Timothy Jaeryang Baek 8b10b058e5 Merge pull request #1539 from cheahjs/feat/customizable-title-prompt-length
feat: add {{prompt:start:length}} and {{prompt:end:length}} to title gen
2024-04-14 14:04:39 -07:00
Timothy J. Baek 0c441b588c refac: naming convention 2024-04-14 17:04:24 -04:00
Timothy Jaeryang Baek a938ffb586 Merge branch 'dev' into feat/customizable-title-prompt-length 2024-04-14 14:02:53 -07:00
Timothy Jaeryang Baek b5d882606a Merge pull request #1499 from lainedfles/whisper_auto_update
feat: introduce Whisper model auto-update control.
2024-04-14 13:58:17 -07:00
Timothy Jaeryang Baek d9ce1d3ea3 Merge pull request #1544 from lainedfles/generation_info_approximate_total
feat: human readable Generation Info total
2024-04-14 13:57:23 -07:00
Timothy J. Baek 9091513c39 fix 2024-04-14 16:52:59 -04:00
Timothy J. Baek 0f5ecafc57 fix: api usage issue 2024-04-14 16:51:13 -04:00
Timothy J. Baek 98c16776f8 refac: styling 2024-04-14 16:41:58 -04:00
Timothy J. Baek 91dda2401d Update Selector.svelte 2024-04-14 16:29:27 -04:00
Timothy Jaeryang Baek 6238495d61 Merge pull request #1395 from 7a6ac0/admin_pagination
feat: admin panel user list pagination
2024-04-14 13:23:36 -07:00
Timothy J. Baek 0708a3d75e fix: styling 2024-04-14 16:23:13 -04:00
Timothy J. Baek b25e3ed364 refac 2024-04-14 16:19:46 -04:00
Timothy J. Baek 1fa3bf6793 fix: semi-lazy load 2024-04-14 15:44:19 -04:00
Self Denial b93337e62f Format fix 2024-04-13 22:41:22 -06:00
Self Denial d2d255228c Format fix 2024-04-13 22:39:10 -06:00
Self Denial 23b674ddda feat: human readable Generation Info total
Add new function to convert nanoseconds into `approximate_total:` for *Generation Info* tooltip.
2024-04-13 22:27:00 -06:00
Timothy Jaeryang Baek 0d3ce18a61 Merge pull request #1543 from lainedfles/fix-pr-1500 2024-04-13 19:04:31 -07:00
Self Denial 067c94810a Fix open-webui/open-webui#1500
Looks like the convention update per commit 0981fae161 missed `config.py`.
2024-04-13 19:55:43 -06:00
Timothy Jaeryang Baek 916ce9bc4f Merge pull request #1500 from lainedfles/config-image-generation-enabled
feat: add IMAGE_GENERATION_ENABLED env var
2024-04-13 14:51:01 -07:00
Timothy J. Baek 0981fae161 refac: convention 2024-04-13 14:50:45 -07:00
Timothy J. Baek 2649d29a3e fix: manifest.json issue 2024-04-13 14:42:38 -07:00
Timothy J. Baek 838778aa3e fix: typo 2024-04-13 14:40:54 -07:00
Timothy Jaeryang Baek 8c8388ea9f Merge pull request #1516 from que-nguyen/main
Update locales/vi-VN/translation.json
2024-04-13 14:39:27 -07:00
Timothy Jaeryang Baek b40054c54d Merge pull request #1534 from lainedfles/ollama-pull-qol
feat: small change to support ollama pull QOL
2024-04-13 14:39:13 -07:00
Jun Siang Cheah fffd42e4d7 fix: replace all instances of prompt:start and prompt:end 2024-04-13 18:44:49 +01:00
Jun Siang Cheah db817fcf29 feat: add {{prompt:start:length}} and {{prompt:end:length}} to title gen 2024-04-13 18:26:50 +01:00
Self Denial faa5884150 Use log.warning instead of log.debug 2024-04-13 03:18:13 -06:00
Self Denial 922628c1ee feat: small change to support ollama pull QOL
Use regex replace during trim "sanitize" to support `ollama run ` or `ollama pull ` syntax.
2024-04-13 03:04:11 -06:00
Timothy Jaeryang Baek 8e30948de9 Merge pull request #1532 from dariothornhill/1478-remove-trailing-slash
fix(helm): remove trailing slash
2024-04-13 00:06:29 -07:00
Dario Thornhill e08c144f0b fix(helm): remove trailing slash
The trailing `/` causes requests to be written with `//` and results in 404 responses from the ollama service.

This results in ollama models being unusable. Removing the training slash here resolves the issue.
2024-04-13 07:32:28 +02:00
Timothy Jaeryang Baek 51afb1e378 Merge pull request #1523 from 7a6ac0/call_before_defining
fix: Invoke the function before it is defined
2024-04-12 13:31:40 -07:00
tabacoWang c49cc3fa86 fix: Invoke the function before it is defined 2024-04-12 17:27:40 +08:00
Que Nguyen e17d66eb61 Update locales/vi-VN/translation.json
Fixed translation errors in Vietnamese locale file `locales/vi-VN/translation.json`
2024-04-12 09:44:40 +07:00
Timothy Jaeryang Baek d6260fd20c Merge pull request #1514 from JanSolo1/EMBBEDING-MODELS-COMMENT-FIX
Comment fix config.py ln 412
2024-04-11 18:53:12 -07:00
JanSolo1 79a4abc3ec Comment spelling mistake fix 2024-04-12 00:44:44 +02:00
JanSolo1 6623ecb302 Comment fix config.py ln 412 2024-04-12 00:36:48 +02:00
Timothy Jaeryang Baek 18a3f06a62 Merge pull request #1505 from Fusseldieb/patch-1
Fixed malformed date format
2024-04-11 09:55:28 -07:00
Valentino Stillhardt 1d8496eabb Fixed malformed date format 2024-04-11 10:58:46 -03:00
tabacoWang 8a9cf44dbc feat: combine with search result 2024-04-11 14:00:28 +08:00
tabacoWang 872ea83c50 feat: admin panel user list pagination 2024-04-11 14:00:28 +08:00
Self Denial ff01398812 Format fix 2024-04-10 23:39:11 -06:00
Self Denial 3b8ea55bc8 Add IMAGE_GENERATION_ENABLED env var
With default of `False`.
2024-04-10 23:21:12 -06:00
Self Denial 81c8717d75 Format fix 2024-04-10 20:44:44 -06:00
Self Denial 429242b4d3 Introduce Whisper model auto-update control.
* Introduce WHISPER_MODEL_AUTO_UPDATE env var
* Pass local_files_only to WhisperModel()
* Handle cases where auto-update is disabled but model is non-existent
2024-04-10 20:30:00 -06:00
71 changed files with 2894 additions and 505 deletions
+3
View File
@@ -24,6 +24,9 @@ assignees: ''
## Environment
- **Open WebUI Version:** [e.g., 0.1.120]
- **Ollama (if applicable):** [e.g., 0.1.30, 0.1.32-rc1]
- **Operating System:** [e.g., Windows 10, macOS Big Sur, Ubuntu 20.04]
- **Browser (if applicable):** [e.g., Chrome 100.0, Firefox 98.0]
+36
View File
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.120] - 2024-04-20
### Added
- **📦 Archive Chat Feature**: Easily archive chats with a new sidebar button, and access archived chats via the profile button > archived chats.
- **🔊 Configurable Text-to-Speech Endpoint**: Customize your Text-to-Speech experience with configurable OpenAI endpoints.
- **🛠️ Improved Error Handling**: Enhanced error message handling for connection failures.
- **⌨️ Enhanced Shortcut**: When editing messages, use ctrl/cmd+enter to save and submit, and esc to close.
- **🌐 Language Support**: Added support for Georgian and enhanced translations for Portuguese and Vietnamese.
### Fixed
- **🔧 Model Selector**: Resolved issue where default model selection was not saving.
- **🔗 Share Link Copy Button**: Fixed bug where the copy button wasn't copying links in Safari.
- **🎨 Light Theme Styling**: Addressed styling issue with the light theme.
## [0.1.119] - 2024-04-16
### Added
- **🌟 Enhanced RAG Embedding Support**: Ollama, and OpenAI models can now be used for RAG embedding model.
- **🔄 Seamless Integration**: Copy 'ollama run <model name>' directly from Ollama page to easily select and pull models.
- **🏷️ Tagging Feature**: Add tags to chats directly via the sidebar chat menu.
- **📱 Mobile Accessibility**: Swipe left and right on mobile to effortlessly open and close the sidebar.
- **🔍 Improved Navigation**: Admin panel now supports pagination for user list.
- **🌍 Additional Language Support**: Added Polish language support.
### Fixed
- **🌍 Language Enhancements**: Vietnamese and Spanish translations have been improved.
- **🔧 Helm Fixes**: Resolved issues with Helm trailing slash and manifest.json.
### Changed
- **🐳 Docker Optimization**: Updated docker image build process to utilize 'uv' for significantly faster builds compared to 'pip3'.
## [0.1.118] - 2024-04-10
### Added
+4 -3
View File
@@ -93,15 +93,16 @@ RUN if [ "$USE_OLLAMA" = "true" ]; then \
# install python dependencies
COPY ./backend/requirements.txt ./requirements.txt
RUN if [ "$USE_CUDA" = "true" ]; then \
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 && \
pip3 install -r requirements.txt --no-cache-dir && \
uv pip install --system -r requirements.txt --no-cache-dir && \
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])" && \
python -c "import os; from chromadb.utils import embedding_functions; sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=os.environ['RAG_EMBEDDING_MODEL'], device='cpu')"; \
else \
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
pip3 install -r requirements.txt --no-cache-dir && \
uv pip install --system -r requirements.txt --no-cache-dir && \
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])" && \
python -c "import os; from chromadb.utils import embedding_functions; sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=os.environ['RAG_EMBEDDING_MODEL'], device='cpu')"; \
fi
+1 -1
View File
@@ -185,4 +185,4 @@ If you have any questions, suggestions, or need assistance, please open an issue
---
Created by [Timothy J. Baek](https://github.com/tjbck) - Let's make Open Web UI even more amazing together! 💪
Created by [Timothy J. Baek](https://github.com/tjbck) - Let's make Open WebUI even more amazing together! 💪
+125 -7
View File
@@ -10,8 +10,19 @@ from fastapi import (
File,
Form,
)
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
from fastapi.middleware.cors import CORSMiddleware
from faster_whisper import WhisperModel
from pydantic import BaseModel
import requests
import hashlib
from pathlib import Path
import json
from constants import ERROR_MESSAGES
from utils.utils import (
@@ -28,7 +39,10 @@ from config import (
UPLOAD_DIR,
WHISPER_MODEL,
WHISPER_MODEL_DIR,
WHISPER_MODEL_AUTO_UPDATE,
DEVICE_TYPE,
AUDIO_OPENAI_API_BASE_URL,
AUDIO_OPENAI_API_KEY,
)
log = logging.getLogger(__name__)
@@ -43,12 +57,104 @@ 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
# setting device type for whisper model
whisper_device_type = DEVICE_TYPE if DEVICE_TYPE and DEVICE_TYPE == "cuda" else "cpu"
log.info(f"whisper_device_type: {whisper_device_type}")
SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
SPEECH_CACHE_DIR.mkdir(parents=True, exist_ok=True)
@app.post("/transcribe")
class OpenAIConfigUpdateForm(BaseModel):
url: str
key: str
@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,
}
@app.post("/config/update")
async def update_openai_config(
form_data: OpenAIConfigUpdateForm, user=Depends(get_admin_user)
):
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
return {
"status": True,
"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL,
"OPENAI_API_KEY": app.state.OPENAI_API_KEY,
}
@app.post("/speech")
async def speech(request: Request, user=Depends(get_verified_user)):
body = await request.body()
name = hashlib.sha256(body).hexdigest()
file_path = SPEECH_CACHE_DIR.joinpath(f"{name}.mp3")
file_body_path = SPEECH_CACHE_DIR.joinpath(f"{name}.json")
# Check if the file already exists in the cache
if file_path.is_file():
return FileResponse(file_path)
headers = {}
headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEY}"
headers["Content-Type"] = "application/json"
r = None
try:
r = requests.post(
url=f"{app.state.OPENAI_API_BASE_URL}/audio/speech",
data=body,
headers=headers,
stream=True,
)
r.raise_for_status()
# Save the streaming content to a file
with open(file_path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
with open(file_body_path, "w") as f:
json.dump(json.loads(body.decode("utf-8")), f)
# Return the saved file
return FileResponse(file_path)
except Exception as e:
log.exception(e)
error_detail = "Open WebUI: Server Connection Error"
if r is not None:
try:
res = r.json()
if "error" in res:
error_detail = f"External: {res['error']['message']}"
except:
error_detail = f"External: {e}"
raise HTTPException(
status_code=r.status_code if r != None else 500,
detail=error_detail,
)
@app.post("/transcriptions")
def transcribe(
file: UploadFile = File(...),
user=Depends(get_current_user),
@@ -69,12 +175,24 @@ def transcribe(
f.write(contents)
f.close()
model = WhisperModel(
WHISPER_MODEL,
device=whisper_device_type,
compute_type="int8",
download_root=WHISPER_MODEL_DIR,
)
whisper_kwargs = {
"model_size_or_path": WHISPER_MODEL,
"device": whisper_device_type,
"compute_type": "int8",
"download_root": WHISPER_MODEL_DIR,
"local_files_only": not WHISPER_MODEL_AUTO_UPDATE,
}
log.debug(f"whisper_kwargs: {whisper_kwargs}")
try:
model = WhisperModel(**whisper_kwargs)
except:
log.warning(
"WhisperModel initialization failed, attempting download with local_files_only=False"
)
whisper_kwargs["local_files_only"] = False
model = WhisperModel(**whisper_kwargs)
segments, info = model.transcribe(file_path, beam_size=5)
log.info(
+14 -4
View File
@@ -29,7 +29,15 @@ import base64
import json
import logging
from config import SRC_LOG_LEVELS, CACHE_DIR, AUTOMATIC1111_BASE_URL, COMFYUI_BASE_URL
from config import (
SRC_LOG_LEVELS,
CACHE_DIR,
ENABLE_IMAGE_GENERATION,
AUTOMATIC1111_BASE_URL,
COMFYUI_BASE_URL,
OPENAI_API_BASE_URL,
OPENAI_API_KEY,
)
log = logging.getLogger(__name__)
@@ -48,9 +56,11 @@ app.add_middleware(
)
app.state.ENGINE = ""
app.state.ENABLED = False
app.state.ENABLED = ENABLE_IMAGE_GENERATION
app.state.OPENAI_API_BASE_URL = OPENAI_API_BASE_URL
app.state.OPENAI_API_KEY = OPENAI_API_KEY
app.state.OPENAI_API_KEY = ""
app.state.MODEL = ""
@@ -354,7 +364,7 @@ def generate_image(
}
r = requests.post(
url=f"https://api.openai.com/v1/images/generations",
url=f"{app.state.OPENAI_API_BASE_URL}/images/generations",
json=data,
headers=headers,
)
+82 -8
View File
@@ -612,8 +612,13 @@ async def generate_embeddings(
user=Depends(get_current_user),
):
if url_idx == None:
if form_data.model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
model = form_data.model
if ":" not in model:
model = f"{model}:latest"
if model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[model]["urls"])
else:
raise HTTPException(
status_code=400,
@@ -649,6 +654,60 @@ async def generate_embeddings(
)
def generate_ollama_embeddings(
form_data: GenerateEmbeddingsForm,
url_idx: Optional[int] = None,
):
log.info(f"generate_ollama_embeddings {form_data}")
if url_idx == None:
model = form_data.model
if ":" not in model:
model = f"{model}:latest"
if model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[model]["urls"])
else:
raise HTTPException(
status_code=400,
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
)
url = app.state.OLLAMA_BASE_URLS[url_idx]
log.info(f"url: {url}")
try:
r = requests.request(
method="POST",
url=f"{url}/api/embeddings",
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
data = r.json()
log.info(f"generate_ollama_embeddings {data}")
if "embedding" in data:
return data["embedding"]
else:
raise "Something went wrong :/"
except Exception as e:
log.exception(e)
error_detail = "Open WebUI: Server Connection Error"
if r is not None:
try:
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except:
error_detail = f"Ollama: {e}"
raise error_detail
class GenerateCompletionForm(BaseModel):
model: str
prompt: str
@@ -672,8 +731,13 @@ async def generate_completion(
):
if url_idx == None:
if form_data.model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
model = form_data.model
if ":" not in model:
model = f"{model}:latest"
if model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[model]["urls"])
else:
raise HTTPException(
status_code=400,
@@ -770,8 +834,13 @@ async def generate_chat_completion(
):
if url_idx == None:
if form_data.model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
model = form_data.model
if ":" not in model:
model = f"{model}:latest"
if model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[model]["urls"])
else:
raise HTTPException(
status_code=400,
@@ -874,8 +943,13 @@ async def generate_openai_chat_completion(
):
if url_idx == None:
if form_data.model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
model = form_data.model
if ":" not in model:
model = f"{model}:latest"
if model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[model]["urls"])
else:
raise HTTPException(
status_code=400,
+1 -1
View File
@@ -341,7 +341,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
try:
res = r.json()
if "error" in res:
error_detail = f"External: {res['error']}"
error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}"
except:
error_detail = f"External: {e}"
+185 -46
View File
@@ -39,13 +39,22 @@ import uuid
import json
from apps.ollama.main import generate_ollama_embeddings, GenerateEmbeddingsForm
from apps.web.models.documents import (
Documents,
DocumentForm,
DocumentResponse,
)
from apps.rag.utils import query_doc, query_collection, get_embedding_model_path
from apps.rag.utils import (
query_doc,
query_embeddings_doc,
query_collection,
query_embeddings_collection,
get_embedding_model_path,
generate_openai_embeddings,
)
from utils.misc import (
calculate_sha256,
@@ -58,8 +67,11 @@ from config import (
SRC_LOG_LEVELS,
UPLOAD_DIR,
DOCS_DIR,
RAG_EMBEDDING_ENGINE,
RAG_EMBEDDING_MODEL,
RAG_EMBEDDING_MODEL_AUTO_UPDATE,
RAG_OPENAI_API_BASE_URL,
RAG_OPENAI_API_KEY,
DEVICE_TYPE,
CHROMA_CLIENT,
CHUNK_SIZE,
@@ -74,16 +86,21 @@ log.setLevel(SRC_LOG_LEVELS["RAG"])
app = FastAPI()
app.state.PDF_EXTRACT_IMAGES = False
app.state.CHUNK_SIZE = CHUNK_SIZE
app.state.CHUNK_OVERLAP = CHUNK_OVERLAP
app.state.RAG_TEMPLATE = RAG_TEMPLATE
app.state.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL
app.state.TOP_K = 4
app.state.CHUNK_SIZE = CHUNK_SIZE
app.state.CHUNK_OVERLAP = CHUNK_OVERLAP
app.state.RAG_EMBEDDING_ENGINE = RAG_EMBEDDING_ENGINE
app.state.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL
app.state.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.PDF_EXTRACT_IMAGES = False
app.state.sentence_transformer_ef = (
embedding_functions.SentenceTransformerEmbeddingFunction(
@@ -121,45 +138,72 @@ async def get_status():
"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,
}
@app.get("/embedding/model")
async def get_embedding_model(user=Depends(get_admin_user)):
@app.get("/embedding")
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,
"openai_config": {
"url": app.state.OPENAI_API_BASE_URL,
"key": app.state.OPENAI_API_KEY,
},
}
class OpenAIConfigForm(BaseModel):
url: str
key: str
class EmbeddingModelUpdateForm(BaseModel):
openai_config: Optional[OpenAIConfigForm] = None
embedding_engine: str
embedding_model: str
@app.post("/embedding/model/update")
async def update_embedding_model(
@app.post("/embedding/update")
async def update_embedding_config(
form_data: EmbeddingModelUpdateForm, user=Depends(get_admin_user)
):
log.info(
f"Updating embedding model: {app.state.RAG_EMBEDDING_MODEL} to {form_data.embedding_model}"
)
try:
sentence_transformer_ef = (
embedding_functions.SentenceTransformerEmbeddingFunction(
model_name=get_embedding_model_path(form_data.embedding_model, True),
device=DEVICE_TYPE,
)
)
app.state.RAG_EMBEDDING_ENGINE = form_data.embedding_engine
app.state.RAG_EMBEDDING_MODEL = form_data.embedding_model
app.state.sentence_transformer_ef = sentence_transformer_ef
if app.state.RAG_EMBEDDING_ENGINE in ["ollama", "openai"]:
app.state.RAG_EMBEDDING_MODEL = form_data.embedding_model
app.state.sentence_transformer_ef = None
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
else:
sentence_transformer_ef = (
embedding_functions.SentenceTransformerEmbeddingFunction(
model_name=get_embedding_model_path(
form_data.embedding_model, True
),
device=DEVICE_TYPE,
)
)
app.state.RAG_EMBEDDING_MODEL = form_data.embedding_model
app.state.sentence_transformer_ef = sentence_transformer_ef
return {
"status": True,
"embedding_engine": app.state.RAG_EMBEDDING_ENGINE,
"embedding_model": app.state.RAG_EMBEDDING_MODEL,
"openai_config": {
"url": app.state.OPENAI_API_BASE_URL,
"key": app.state.OPENAI_API_KEY,
},
}
except Exception as e:
@@ -252,12 +296,37 @@ def query_doc_handler(
):
try:
return query_doc(
collection_name=form_data.collection_name,
query=form_data.query,
k=form_data.k if form_data.k else app.state.TOP_K,
embedding_function=app.state.sentence_transformer_ef,
)
if app.state.RAG_EMBEDDING_ENGINE == "":
return query_doc(
collection_name=form_data.collection_name,
query=form_data.query,
k=form_data.k if form_data.k else app.state.TOP_K,
embedding_function=app.state.sentence_transformer_ef,
)
else:
if app.state.RAG_EMBEDDING_ENGINE == "ollama":
query_embeddings = generate_ollama_embeddings(
GenerateEmbeddingsForm(
**{
"model": app.state.RAG_EMBEDDING_MODEL,
"prompt": form_data.query,
}
)
)
elif app.state.RAG_EMBEDDING_ENGINE == "openai":
query_embeddings = generate_openai_embeddings(
model=app.state.RAG_EMBEDDING_MODEL,
text=form_data.query,
key=app.state.OPENAI_API_KEY,
url=app.state.OPENAI_API_BASE_URL,
)
return query_embeddings_doc(
collection_name=form_data.collection_name,
query_embeddings=query_embeddings,
k=form_data.k if form_data.k else app.state.TOP_K,
)
except Exception as e:
log.exception(e)
raise HTTPException(
@@ -277,12 +346,45 @@ def query_collection_handler(
form_data: QueryCollectionsForm,
user=Depends(get_current_user),
):
return query_collection(
collection_names=form_data.collection_names,
query=form_data.query,
k=form_data.k if form_data.k else app.state.TOP_K,
embedding_function=app.state.sentence_transformer_ef,
)
try:
if app.state.RAG_EMBEDDING_ENGINE == "":
return query_collection(
collection_names=form_data.collection_names,
query=form_data.query,
k=form_data.k if form_data.k else app.state.TOP_K,
embedding_function=app.state.sentence_transformer_ef,
)
else:
if app.state.RAG_EMBEDDING_ENGINE == "ollama":
query_embeddings = generate_ollama_embeddings(
GenerateEmbeddingsForm(
**{
"model": app.state.RAG_EMBEDDING_MODEL,
"prompt": form_data.query,
}
)
)
elif app.state.RAG_EMBEDDING_ENGINE == "openai":
query_embeddings = generate_openai_embeddings(
model=app.state.RAG_EMBEDDING_MODEL,
text=form_data.query,
key=app.state.OPENAI_API_KEY,
url=app.state.OPENAI_API_BASE_URL,
)
return query_embeddings_collection(
collection_names=form_data.collection_names,
query_embeddings=query_embeddings,
k=form_data.k if form_data.k else app.state.TOP_K,
)
except Exception as e:
log.exception(e)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT(e),
)
@app.post("/web")
@@ -317,9 +419,11 @@ def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> b
chunk_overlap=app.state.CHUNK_OVERLAP,
add_start_index=True,
)
docs = text_splitter.split_documents(data)
if len(docs) > 0:
log.info(f"store_data_in_vector_db {docs}")
return store_docs_in_vector_db(docs, collection_name, overwrite), None
else:
raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)
@@ -338,6 +442,7 @@ def store_text_in_vector_db(
def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> bool:
log.info(f"store_docs_in_vector_db {docs} {collection_name}")
texts = [doc.page_content for doc in docs]
metadatas = [doc.metadata for doc in docs]
@@ -349,18 +454,52 @@ def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> b
log.info(f"deleting existing collection {collection_name}")
CHROMA_CLIENT.delete_collection(name=collection_name)
collection = CHROMA_CLIENT.create_collection(
name=collection_name,
embedding_function=app.state.sentence_transformer_ef,
)
if app.state.RAG_EMBEDDING_ENGINE == "":
for batch in create_batches(
api=CHROMA_CLIENT,
ids=[str(uuid.uuid1()) for _ in texts],
metadatas=metadatas,
documents=texts,
):
collection.add(*batch)
collection = CHROMA_CLIENT.create_collection(
name=collection_name,
embedding_function=app.state.sentence_transformer_ef,
)
for batch in create_batches(
api=CHROMA_CLIENT,
ids=[str(uuid.uuid1()) for _ in texts],
metadatas=metadatas,
documents=texts,
):
collection.add(*batch)
else:
collection = CHROMA_CLIENT.create_collection(name=collection_name)
if app.state.RAG_EMBEDDING_ENGINE == "ollama":
embeddings = [
generate_ollama_embeddings(
GenerateEmbeddingsForm(
**{"model": app.state.RAG_EMBEDDING_MODEL, "prompt": text}
)
)
for text in texts
]
elif app.state.RAG_EMBEDDING_ENGINE == "openai":
embeddings = [
generate_openai_embeddings(
model=app.state.RAG_EMBEDDING_MODEL,
text=text,
key=app.state.OPENAI_API_KEY,
url=app.state.OPENAI_API_BASE_URL,
)
for text in texts
]
for batch in create_batches(
api=CHROMA_CLIENT,
ids=[str(uuid.uuid1()) for _ in texts],
metadatas=metadatas,
embeddings=embeddings,
documents=texts,
):
collection.add(*batch)
return True
except Exception as e:
+130 -16
View File
@@ -2,10 +2,16 @@ import os
import re
import logging
from typing import List
import requests
from huggingface_hub import snapshot_download
from apps.ollama.main import generate_ollama_embeddings, GenerateEmbeddingsForm
from config import SRC_LOG_LEVELS, CHROMA_CLIENT
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -26,6 +32,24 @@ def query_doc(collection_name: str, query: str, k: int, embedding_function):
raise e
def query_embeddings_doc(collection_name: str, query_embeddings, k: int):
try:
# if you use docker use the model from the environment variable
log.info(f"query_embeddings_doc {query_embeddings}")
collection = CHROMA_CLIENT.get_collection(
name=collection_name,
)
result = collection.query(
query_embeddings=[query_embeddings],
n_results=k,
)
log.info(f"query_embeddings_doc:result {result}")
return result
except Exception as e:
raise e
def merge_and_sort_query_results(query_results, k):
# Initialize lists to store combined data
combined_ids = []
@@ -96,14 +120,46 @@ def query_collection(
return merge_and_sort_query_results(results, k)
def query_embeddings_collection(collection_names: List[str], query_embeddings, k: int):
results = []
log.info(f"query_embeddings_collection {query_embeddings}")
for collection_name in collection_names:
try:
collection = CHROMA_CLIENT.get_collection(name=collection_name)
result = collection.query(
query_embeddings=[query_embeddings],
n_results=k,
)
results.append(result)
except:
pass
return merge_and_sort_query_results(results, k)
def rag_template(template: str, context: str, query: str):
template = template.replace("[context]", context)
template = template.replace("[query]", query)
return template
def rag_messages(docs, messages, template, k, embedding_function):
log.debug(f"docs: {docs}")
def rag_messages(
docs,
messages,
template,
k,
embedding_engine,
embedding_model,
embedding_function,
openai_key,
openai_url,
):
log.debug(
f"docs: {docs} {messages} {embedding_engine} {embedding_model} {embedding_function} {openai_key} {openai_url}"
)
last_user_message_idx = None
for i in range(len(messages) - 1, -1, -1):
@@ -136,22 +192,57 @@ def rag_messages(docs, messages, template, k, embedding_function):
context = None
try:
if doc["type"] == "collection":
context = query_collection(
collection_names=doc["collection_names"],
query=query,
k=k,
embedding_function=embedding_function,
)
elif doc["type"] == "text":
if doc["type"] == "text":
context = doc["content"]
else:
context = query_doc(
collection_name=doc["collection_name"],
query=query,
k=k,
embedding_function=embedding_function,
)
if embedding_engine == "":
if doc["type"] == "collection":
context = query_collection(
collection_names=doc["collection_names"],
query=query,
k=k,
embedding_function=embedding_function,
)
else:
context = query_doc(
collection_name=doc["collection_name"],
query=query,
k=k,
embedding_function=embedding_function,
)
else:
if embedding_engine == "ollama":
query_embeddings = generate_ollama_embeddings(
GenerateEmbeddingsForm(
**{
"model": embedding_model,
"prompt": query,
}
)
)
elif embedding_engine == "openai":
query_embeddings = generate_openai_embeddings(
model=embedding_model,
text=query,
key=openai_key,
url=openai_url,
)
if doc["type"] == "collection":
context = query_embeddings_collection(
collection_names=doc["collection_names"],
query_embeddings=query_embeddings,
k=k,
)
else:
context = query_embeddings_doc(
collection_name=doc["collection_name"],
query_embeddings=query_embeddings,
k=k,
)
except Exception as e:
log.exception(e)
context = None
@@ -230,3 +321,26 @@ def get_embedding_model_path(
except Exception as e:
log.exception(f"Cannot determine embedding model snapshot path: {e}")
return embedding_model
def generate_openai_embeddings(
model: str, text: str, key: str, url: str = "https://api.openai.com/v1"
):
try:
r = requests.post(
f"{url}/embeddings",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {key}",
},
json={"input": text, "model": model},
)
r.raise_for_status()
data = r.json()
if "data" in data:
return data["data"][0]["embedding"]
else:
raise "Something went wrong :/"
except Exception as e:
print(e)
return None
@@ -0,0 +1,46 @@
"""Peewee migrations -- 002_add_local_sharing.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from contextlib import suppress
import peewee as pw
from peewee_migrate import Migrator
with suppress(ImportError):
import playhouse.postgres_ext as pw_pext
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your migrations here."""
migrator.add_fields("chat", archived=pw.BooleanField(default=False))
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your rollback migrations here."""
migrator.remove_fields("chat", "archived")
@@ -0,0 +1,77 @@
"""Peewee migrations -- 002_add_local_sharing.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from contextlib import suppress
import peewee as pw
from peewee_migrate import Migrator
with suppress(ImportError):
import playhouse.postgres_ext as pw_pext
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your migrations here."""
# Adding fields created_at and updated_at to the 'chat' table
migrator.add_fields(
"chat",
created_at=pw.DateTimeField(null=True), # Allow null for transition
updated_at=pw.DateTimeField(null=True), # Allow null for transition
)
# Populate the new fields from an existing 'timestamp' field
migrator.sql(
"UPDATE chat SET created_at = timestamp, updated_at = timestamp WHERE timestamp IS NOT NULL"
)
# Now that the data has been copied, remove the original 'timestamp' field
migrator.remove_fields("chat", "timestamp")
# Update the fields to be not null now that they are populated
migrator.change_fields(
"chat",
created_at=pw.DateTimeField(null=False),
updated_at=pw.DateTimeField(null=False),
)
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your rollback migrations here."""
# Recreate the timestamp field initially allowing null values for safe transition
migrator.add_fields("chat", timestamp=pw.DateTimeField(null=True))
# Copy the earliest created_at date back into the new timestamp field
# This assumes created_at was originally a copy of timestamp
migrator.sql("UPDATE chat SET timestamp = created_at")
# Remove the created_at and updated_at fields
migrator.remove_fields("chat", "created_at", "updated_at")
# Finally, alter the timestamp field to not allow nulls if that was the original setting
migrator.change_fields("chat", timestamp=pw.DateTimeField(null=False))
+53 -10
View File
@@ -19,8 +19,12 @@ class Chat(Model):
user_id = CharField()
title = CharField()
chat = TextField() # Save Chat JSON as Text
timestamp = DateField()
created_at = DateTimeField()
updated_at = DateTimeField()
share_id = CharField(null=True, unique=True)
archived = BooleanField(default=False)
class Meta:
database = DB
@@ -31,8 +35,12 @@ class ChatModel(BaseModel):
user_id: str
title: str
chat: str
timestamp: int # timestamp in epoch
created_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
share_id: Optional[str] = None
archived: bool = False
####################
@@ -53,13 +61,17 @@ class ChatResponse(BaseModel):
user_id: str
title: str
chat: dict
timestamp: int # timestamp in epoch
updated_at: int # timestamp in epoch
created_at: int # timestamp in epoch
share_id: Optional[str] = None # id of the chat to be shared
archived: bool
class ChatTitleIdResponse(BaseModel):
id: str
title: str
updated_at: int
created_at: int
class ChatTable:
@@ -77,7 +89,8 @@ class ChatTable:
form_data.chat["title"] if "title" in form_data.chat else "New Chat"
),
"chat": json.dumps(form_data.chat),
"timestamp": int(time.time()),
"created_at": int(time.time()),
"updated_at": int(time.time()),
}
)
@@ -89,7 +102,7 @@ class ChatTable:
query = Chat.update(
chat=json.dumps(chat),
title=chat["title"] if "title" in chat else "New Chat",
timestamp=int(time.time()),
updated_at=int(time.time()),
).where(Chat.id == id)
query.execute()
@@ -111,7 +124,8 @@ class ChatTable:
"user_id": f"shared-{chat_id}",
"title": chat.title,
"chat": chat.chat,
"timestamp": int(time.time()),
"created_at": chat.created_at,
"updated_at": int(time.time()),
}
)
shared_result = Chat.create(**shared_chat.model_dump())
@@ -163,14 +177,42 @@ class ChatTable:
except:
return None
def toggle_chat_archive_by_id(self, id: str) -> Optional[ChatModel]:
try:
chat = self.get_chat_by_id(id)
query = Chat.update(
archived=(not chat.archived),
).where(Chat.id == id)
query.execute()
chat = Chat.get(Chat.id == id)
return ChatModel(**model_to_dict(chat))
except:
return None
def get_archived_chat_lists_by_user_id(
self, user_id: str, skip: int = 0, limit: int = 50
) -> List[ChatModel]:
return [
ChatModel(**model_to_dict(chat))
for chat in Chat.select()
.where(Chat.archived == True)
.where(Chat.user_id == user_id)
.order_by(Chat.updated_at.desc())
# .limit(limit)
# .offset(skip)
]
def get_chat_lists_by_user_id(
self, user_id: str, skip: int = 0, limit: int = 50
) -> List[ChatModel]:
return [
ChatModel(**model_to_dict(chat))
for chat in Chat.select()
.where(Chat.archived == False)
.where(Chat.user_id == user_id)
.order_by(Chat.timestamp.desc())
.order_by(Chat.updated_at.desc())
# .limit(limit)
# .offset(skip)
]
@@ -181,14 +223,15 @@ class ChatTable:
return [
ChatModel(**model_to_dict(chat))
for chat in Chat.select()
.where(Chat.archived == False)
.where(Chat.id.in_(chat_ids))
.order_by(Chat.timestamp.desc())
.order_by(Chat.updated_at.desc())
]
def get_all_chats(self) -> List[ChatModel]:
return [
ChatModel(**model_to_dict(chat))
for chat in Chat.select().order_by(Chat.timestamp.desc())
for chat in Chat.select().order_by(Chat.updated_at.desc())
]
def get_all_chats_by_user_id(self, user_id: str) -> List[ChatModel]:
@@ -196,7 +239,7 @@ class ChatTable:
ChatModel(**model_to_dict(chat))
for chat in Chat.select()
.where(Chat.user_id == user_id)
.order_by(Chat.timestamp.desc())
.order_by(Chat.updated_at.desc())
]
def get_chat_by_id(self, id: str) -> Optional[ChatModel]:
+29
View File
@@ -47,6 +47,18 @@ async def get_user_chats(
return Chats.get_chat_lists_by_user_id(user.id, skip, limit)
############################
# GetArchivedChats
############################
@router.get("/archived", response_model=List[ChatTitleIdResponse])
async def get_archived_user_chats(
user=Depends(get_current_user), skip: int = 0, limit: int = 50
):
return Chats.get_archived_chat_lists_by_user_id(user.id, skip, limit)
############################
# GetAllChats
############################
@@ -189,6 +201,23 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_
return result
############################
# ArchiveChat
############################
@router.get("/{id}/archive", response_model=Optional[ChatResponse])
async def archive_chat_by_id(id: str, user=Depends(get_current_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
chat = Chats.toggle_chat_archive_by_id(id)
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
)
############################
# ShareChatById
############################
+73 -42
View File
@@ -18,6 +18,51 @@ from secrets import token_bytes
from constants import ERROR_MESSAGES
####################################
# LOGGING
####################################
log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
if GLOBAL_LOG_LEVEL in log_levels:
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
else:
GLOBAL_LOG_LEVEL = "INFO"
log = logging.getLogger(__name__)
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
log_sources = [
"AUDIO",
"COMFYUI",
"CONFIG",
"DB",
"IMAGES",
"LITELLM",
"MAIN",
"MODELS",
"OLLAMA",
"OPENAI",
"RAG",
"WEBHOOK",
]
SRC_LOG_LEVELS = {}
for source in log_sources:
log_env_var = source + "_LOG_LEVEL"
SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
if SRC_LOG_LEVELS[source] not in log_levels:
SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
log.setLevel(SRC_LOG_LEVELS["CONFIG"])
####################################
# Load .env file
####################################
try:
from dotenv import load_dotenv, find_dotenv
@@ -122,47 +167,6 @@ STATIC_DIR = str(Path(os.getenv("STATIC_DIR", "./static")).resolve())
shutil.copyfile(f"{FRONTEND_BUILD_DIR}/favicon.png", f"{STATIC_DIR}/favicon.png")
####################################
# LOGGING
####################################
log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
if GLOBAL_LOG_LEVEL in log_levels:
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
else:
GLOBAL_LOG_LEVEL = "INFO"
log = logging.getLogger(__name__)
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
log_sources = [
"AUDIO",
"COMFYUI",
"CONFIG",
"DB",
"IMAGES",
"LITELLM",
"MAIN",
"MODELS",
"OLLAMA",
"OPENAI",
"RAG",
"WEBHOOK",
]
SRC_LOG_LEVELS = {}
for source in log_sources:
log_env_var = source + "_LOG_LEVEL"
SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
if SRC_LOG_LEVELS[source] not in log_levels:
SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
log.setLevel(SRC_LOG_LEVELS["CONFIG"])
####################################
# CUSTOM_NAME
####################################
@@ -317,6 +321,13 @@ OPENAI_API_BASE_URLS = [
for url in OPENAI_API_BASE_URLS.split(";")
]
OPENAI_API_KEY = ""
OPENAI_API_KEY = OPENAI_API_KEYS[
OPENAI_API_BASE_URLS.index("https://api.openai.com/v1")
]
OPENAI_API_BASE_URL = "https://api.openai.com/v1"
####################################
# WEBUI
####################################
@@ -401,6 +412,9 @@ if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db"
# 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 (all-MiniLM-L6-v2)
RAG_EMBEDDING_ENGINE = os.environ.get("RAG_EMBEDDING_ENGINE", "")
RAG_EMBEDDING_MODEL = os.environ.get("RAG_EMBEDDING_MODEL", "all-MiniLM-L6-v2")
log.info(f"Embedding model set: {RAG_EMBEDDING_MODEL}"),
@@ -409,7 +423,7 @@ RAG_EMBEDDING_MODEL_AUTO_UPDATE = (
)
# device type ebbeding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
# device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")
if USE_CUDA.lower() == "true":
@@ -440,17 +454,34 @@ And answer according to the language of the user's question.
Given the context information, answer the query.
Query: [query]"""
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)
####################################
# Transcribe
####################################
WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base")
WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models")
WHISPER_MODEL_AUTO_UPDATE = (
os.environ.get("WHISPER_MODEL_AUTO_UPDATE", "").lower() == "true"
)
####################################
# Images
####################################
ENABLE_IMAGE_GENERATION = (
os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true"
)
AUTOMATIC1111_BASE_URL = os.getenv("AUTOMATIC1111_BASE_URL", "")
COMFYUI_BASE_URL = os.getenv("COMFYUI_BASE_URL", "")
####################################
# 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)
+4
View File
@@ -114,7 +114,11 @@ class RAGMiddleware(BaseHTTPMiddleware):
data["messages"],
rag_app.state.RAG_TEMPLATE,
rag_app.state.TOP_K,
rag_app.state.RAG_EMBEDDING_ENGINE,
rag_app.state.RAG_EMBEDDING_MODEL,
rag_app.state.sentence_transformer_ef,
rag_app.state.RAG_OPENAI_API_KEY,
rag_app.state.RAG_OPENAI_API_BASE_URL,
)
del data["docs"]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 11 KiB

+1 -1
View File
@@ -10,7 +10,7 @@ ollama
{{- if .Values.ollama.externalHost }}
{{- printf .Values.ollama.externalHost }}
{{- else }}
{{- printf "http://%s.%s.svc.cluster.local:%d/" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }}
{{- printf "http://%s.%s.svc.cluster.local:%d" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }}
{{- end }}
{{- end }}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "open-webui",
"version": "0.1.118",
"version": "0.1.120",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-webui",
"version": "0.1.118",
"version": "0.1.120",
"dependencies": {
"@sveltejs/adapter-node": "^1.3.1",
"async": "^3.2.5",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "open-webui",
"version": "0.1.118",
"version": "0.1.120",
"private": true,
"scripts": {
"dev": "vite dev --host",
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="manifest" href="%sveltekit.assets%/manifest.json" />
<link rel="manifest" href="%sveltekit.assets%/manifest.json" crossorigin="use-credentials" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="robots" content="noindex,nofollow" />
<script>
+100 -1
View File
@@ -1,11 +1,73 @@
import { AUDIO_API_BASE_URL } from '$lib/constants';
export const getAudioConfig = async (token: string) => {
let error = null;
const res = await fetch(`${AUDIO_API_BASE_URL}/config`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res;
};
type OpenAIConfigForm = {
url: string;
key: string;
};
export const updateAudioConfig = async (token: string, payload: OpenAIConfigForm) => {
let error = null;
const res = await fetch(`${AUDIO_API_BASE_URL}/config/update`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
...payload
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res;
};
export const transcribeAudio = async (token: string, file: File) => {
const data = new FormData();
data.append('file', file);
let error = null;
const res = await fetch(`${AUDIO_API_BASE_URL}/transcribe`, {
const res = await fetch(`${AUDIO_API_BASE_URL}/transcriptions`, {
method: 'POST',
headers: {
Accept: 'application/json',
@@ -29,3 +91,40 @@ export const transcribeAudio = async (token: string, file: File) => {
return res;
};
export const synthesizeOpenAISpeech = async (
token: string = '',
speaker: string = 'alloy',
text: string = ''
) => {
let error = null;
const res = await fetch(`${AUDIO_API_BASE_URL}/speech`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'tts-1',
input: text,
voice: speaker
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
+63
View File
@@ -62,6 +62,37 @@ export const getChatList = async (token: string = '') => {
return res;
};
export const getArchivedChatList = async (token: string = '') => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/archived`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const getAllChats = async (token: string) => {
let error = null;
@@ -282,6 +313,38 @@ export const shareChatById = async (token: string, id: string) => {
return res;
};
export const archiveChatById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/archive`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const deleteSharedChatById = async (token: string, id: string) => {
let error = null;
+28 -1
View File
@@ -1,4 +1,5 @@
import { OLLAMA_API_BASE_URL } from '$lib/constants';
import { promptTemplate } from '$lib/utils';
export const getOllamaUrls = async (token: string = '') => {
let error = null;
@@ -144,7 +145,7 @@ export const generateTitle = async (
) => {
let error = null;
template = template.replace(/{{prompt}}/g, prompt);
template = promptTemplate(template, prompt);
console.log(template);
@@ -219,6 +220,32 @@ export const generatePrompt = async (token: string = '', model: string, conversa
return res;
};
export const generateEmbeddings = async (token: string = '', model: string, text: string) => {
let error = null;
const res = await fetch(`${OLLAMA_API_BASE_URL}/api/embeddings`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
model: model,
prompt: text
})
}).catch((err) => {
error = err;
return null;
});
if (error) {
throw error;
}
return res;
};
export const generateTextCompletion = async (token: string = '', model: string, text: string) => {
let error = null;
+2 -1
View File
@@ -1,4 +1,5 @@
import { OPENAI_API_BASE_URL } from '$lib/constants';
import { promptTemplate } from '$lib/utils';
export const getOpenAIUrls = async (token: string = '') => {
let error = null;
@@ -273,7 +274,7 @@ export const generateTitle = async (
) => {
let error = null;
template = template.replace(/{{prompt}}/g, prompt);
template = promptTemplate(template, prompt);
console.log(template);
+11 -4
View File
@@ -346,10 +346,10 @@ export const resetVectorDB = async (token: string) => {
return res;
};
export const getEmbeddingModel = async (token: string) => {
export const getEmbeddingConfig = async (token: string) => {
let error = null;
const res = await fetch(`${RAG_API_BASE_URL}/embedding/model`, {
const res = await fetch(`${RAG_API_BASE_URL}/embedding`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -373,14 +373,21 @@ export const getEmbeddingModel = async (token: string) => {
return res;
};
type OpenAIConfigForm = {
key: string;
url: string;
};
type EmbeddingModelUpdateForm = {
openai_config?: OpenAIConfigForm;
embedding_engine: string;
embedding_model: string;
};
export const updateEmbeddingModel = async (token: string, payload: EmbeddingModelUpdateForm) => {
export const updateEmbeddingConfig = async (token: string, payload: EmbeddingModelUpdateForm) => {
let error = null;
const res = await fetch(`${RAG_API_BASE_URL}/embedding/model/update`, {
const res = await fetch(`${RAG_API_BASE_URL}/embedding/update`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
+1
View File
@@ -32,6 +32,7 @@
<button
class="self-center"
on:click={() => {
localStorage.version = $config.version;
show = false;
}}
>
+42 -38
View File
@@ -328,27 +328,28 @@
];
};
const inputFiles = e.dataTransfer?.files;
const inputFiles = Array.from(e.dataTransfer?.files);
if (inputFiles && inputFiles.length > 0) {
const file = inputFiles[0];
console.log(file, file.name.split('.').at(-1));
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
reader.readAsDataURL(file);
} else if (
SUPPORTED_FILE_TYPE.includes(file['type']) ||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
) {
uploadDoc(file);
} else {
toast.error(
$i18n.t(
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
{ file_type: file['type'] }
)
);
uploadDoc(file);
}
inputFiles.forEach((file) => {
console.log(file, file.name.split('.').at(-1));
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
reader.readAsDataURL(file);
} else if (
SUPPORTED_FILE_TYPE.includes(file['type']) ||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
) {
uploadDoc(file);
} else {
toast.error(
$i18n.t(
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
{ file_type: file['type'] }
)
);
uploadDoc(file);
}
});
} else {
toast.error($i18n.t(`File not found.`));
}
@@ -467,6 +468,7 @@
bind:files={inputFiles}
type="file"
hidden
multiple
on:change={async () => {
let reader = new FileReader();
reader.onload = (event) => {
@@ -482,25 +484,27 @@
};
if (inputFiles && inputFiles.length > 0) {
const file = inputFiles[0];
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
reader.readAsDataURL(file);
} else if (
SUPPORTED_FILE_TYPE.includes(file['type']) ||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
) {
uploadDoc(file);
filesInputElement.value = '';
} else {
toast.error(
$i18n.t(
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
{ file_type: file['type'] }
)
);
uploadDoc(file);
filesInputElement.value = '';
}
const _inputFiles = Array.from(inputFiles);
_inputFiles.forEach((file) => {
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
reader.readAsDataURL(file);
} else if (
SUPPORTED_FILE_TYPE.includes(file['type']) ||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
) {
uploadDoc(file);
filesInputElement.value = '';
} else {
toast.error(
$i18n.t(
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
{ file_type: file['type'] }
)
);
uploadDoc(file);
filesInputElement.value = '';
}
});
} else {
toast.error($i18n.t(`File not found.`));
}
+7 -35
View File
@@ -12,6 +12,7 @@
import Placeholder from './Messages/Placeholder.svelte';
import Spinner from '../common/Spinner.svelte';
import { imageGenerations } from '$lib/apis/images';
import { copyToClipboard } from '$lib/utils';
const i18n = getContext('i18n');
@@ -42,40 +43,11 @@
element.scrollTop = element.scrollHeight;
};
const copyToClipboard = (text) => {
if (!navigator.clipboard) {
var textArea = document.createElement('textarea');
textArea.value = text;
// Avoid scrolling to bottom
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.position = 'fixed';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
console.log('Fallback: Copying text command was ' + msg);
} catch (err) {
console.error('Fallback: Oops, unable to copy', err);
}
document.body.removeChild(textArea);
return;
const copyToClipboardWithToast = async (text) => {
const res = await copyToClipboard(text);
if (res) {
toast.success($i18n.t('Copying to clipboard was successful!'));
}
navigator.clipboard.writeText(text).then(
function () {
console.log('Async: Copying to clipboard was successful!');
toast.success($i18n.t('Copying to clipboard was successful!'));
},
function (err) {
console.error('Async: Could not copy text: ', err);
}
);
};
const confirmEditMessage = async (messageId, content) => {
@@ -330,7 +302,7 @@
{confirmEditMessage}
{showPreviousMessage}
{showNextMessage}
{copyToClipboard}
copyToClipboard={copyToClipboardWithToast}
/>
{:else}
<ResponseMessage
@@ -344,7 +316,7 @@
{showPreviousMessage}
{showNextMessage}
{rateMessage}
{copyToClipboard}
copyToClipboard={copyToClipboardWithToast}
{continueGeneration}
{regenerateResponse}
on:save={async (e) => {
@@ -1,31 +1,39 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import { createEventDispatcher, onMount } from 'svelte';
import { createEventDispatcher, onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let messageId = null;
export let show = false;
export let message;
const LIKE_REASONS = [
`Accurate information`,
`Followed instructions perfectly`,
`Showcased creativity`,
`Positive attitude`,
`Attention to detail`,
`Thorough explanation`,
`Other`
];
let LIKE_REASONS = [];
let DISLIKE_REASONS = [];
const DISLIKE_REASONS = [
`Don't like the style`,
`Not factually correct`,
`Didn't fully follow instructions`,
`Refused when it shouldn't have`,
`Being Lazy`,
`Other`
];
function loadReasons() {
LIKE_REASONS = [
$i18n.t('Accurate information'),
$i18n.t('Followed instructions perfectly'),
$i18n.t('Showcased creativity'),
$i18n.t('Positive attitude'),
$i18n.t('Attention to detail'),
$i18n.t('Thorough explanation'),
$i18n.t('Other')
];
DISLIKE_REASONS = [
$i18n.t("Don't like the style"),
$i18n.t('Not factually correct'),
$i18n.t("Didn't fully follow instructions"),
$i18n.t("Refused when it shouldn't have"),
$i18n.t('Being lazy'),
$i18n.t('Other')
];
}
let reasons = [];
let selectedReason = null;
@@ -40,6 +48,7 @@
onMount(() => {
selectedReason = message.annotation.reason;
comment = message.annotation.comment;
loadReasons();
});
const submitHandler = () => {
@@ -50,14 +59,17 @@
dispatch('submit');
toast.success('Thanks for your feedback!');
toast.success($i18n.t('Thanks for your feedback!'));
show = false;
};
</script>
<div class=" my-2.5 rounded-xl px-4 py-3 border dark:border-gray-850">
<div
class=" my-2.5 rounded-xl px-4 py-3 border dark:border-gray-850"
id="message-feedback-{messageId}"
>
<div class="flex justify-between items-center">
<div class=" text-sm">Tell us more:</div>
<div class=" text-sm">{$i18n.t('Tell us more:')}</div>
<button
on:click={() => {
@@ -81,9 +93,9 @@
<div class="flex flex-wrap gap-2 text-sm mt-2.5">
{#each reasons as reason}
<button
class="px-3.5 py-1 border dark:border-gray-850 dark:hover:bg-gray-850 {selectedReason ===
class="px-3.5 py-1 border dark:border-gray-850 hover:bg-gray-100 dark:hover:bg-gray-850 {selectedReason ===
reason
? 'dark:bg-gray-800'
? 'bg-gray-200 dark:bg-gray-800'
: ''} transition rounded-lg"
on:click={() => {
selectedReason = reason;
@@ -99,7 +111,7 @@
<textarea
bind:value={comment}
class="w-full text-sm px-1 py-2 bg-transparent outline-none resize-none rounded-xl"
placeholder="Feel free to add specific details"
placeholder={$i18n.t('Feel free to add specific details')}
rows="2"
/>
</div>
@@ -15,9 +15,10 @@
const dispatch = createEventDispatcher();
import { config, settings } from '$lib/stores';
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
import { synthesizeOpenAISpeech } from '$lib/apis/audio';
import { imageGenerations } from '$lib/apis/images';
import {
approximateToHumanReadable,
extractSentences,
revertSanitizedResponseContent,
sanitizeResponseContent
@@ -122,7 +123,10 @@
eval_count: ${message.info.eval_count ?? 'N/A'}<br/>
eval_duration: ${
Math.round(((message.info.eval_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
}ms</span>`,
}ms<br/>
approximate_total: ${approximateToHumanReadable(
message.info.total_duration
)}</span>`,
allowHTML: true
});
}
@@ -172,10 +176,12 @@
const toggleSpeakMessage = async () => {
if (speaking) {
speechSynthesis.cancel();
try {
speechSynthesis.cancel();
sentencesAudio[speakingIdx].pause();
sentencesAudio[speakingIdx].currentTime = 0;
sentencesAudio[speakingIdx].pause();
sentencesAudio[speakingIdx].currentTime = 0;
} catch {}
speaking = null;
speakingIdx = null;
@@ -217,6 +223,10 @@
sentence
).catch((error) => {
toast.error(error);
speaking = null;
loadingSpeech = false;
return null;
});
@@ -226,7 +236,6 @@
const audio = new Audio(blobUrl);
sentencesAudio[idx] = audio;
loadingSpeech = false;
lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx));
}
}
@@ -547,6 +556,12 @@
on:click={() => {
rateMessage(message.id, 1);
showRateComment = true;
window.setTimeout(() => {
document
.getElementById(`message-feedback-${message.id}`)
?.scrollIntoView();
}, 0);
}}
>
<svg
@@ -576,6 +591,11 @@
on:click={() => {
rateMessage(message.id, -1);
showRateComment = true;
window.setTimeout(() => {
document
.getElementById(`message-feedback-${message.id}`)
?.scrollIntoView();
}, 0);
}}
>
<svg
@@ -835,6 +855,7 @@
{#if showRateComment}
<RateComment
messageId={message.id}
bind:show={showRateComment}
bind:message
on:submit={() => {
@@ -176,10 +176,23 @@
e.target.style.height = '';
e.target.style.height = `${e.target.scrollHeight}px`;
}}
on:keydown={(e) => {
if (e.key === 'Escape') {
document.getElementById('close-edit-message-button')?.click();
}
const isCmdOrCtrlPressed = e.metaKey || e.ctrlKey;
const isEnterPressed = e.key === 'Enter';
if (isCmdOrCtrlPressed && isEnterPressed) {
document.getElementById('save-edit-message-button')?.click();
}
}}
/>
<div class=" mt-2 mb-1 flex justify-center space-x-2 text-sm font-medium">
<button
id="save-edit-message-button"
class="px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded-lg"
on:click={() => {
editMessageConfirmHandler();
@@ -189,6 +202,7 @@
</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"
on:click={() => {
cancelEditMessage();
@@ -33,7 +33,7 @@
: items;
const pullModelHandler = async () => {
const sanitizedModelTag = searchValue.trim();
const sanitizedModelTag = searchValue.trim().replace(/^ollama\s+(run|pull)\s+/, '');
console.log($MODEL_DOWNLOAD_POOL);
if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) {
+60 -9
View File
@@ -1,4 +1,6 @@
<script lang="ts">
import { getAudioConfig, updateAudioConfig } from '$lib/apis/audio';
import { user } from '$lib/stores';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher();
@@ -9,6 +11,9 @@
// Audio
let OpenAIUrl = '';
let OpenAIKey = '';
let STTEngines = ['', 'openai'];
let STTEngine = '';
@@ -69,6 +74,18 @@
saveSettings({ speechAutoSend: speechAutoSend });
};
const updateConfigHandler = async () => {
const res = await updateAudioConfig(localStorage.token, {
url: OpenAIUrl,
key: OpenAIKey
});
if (res) {
OpenAIUrl = res.OPENAI_API_BASE_URL;
OpenAIKey = res.OPENAI_API_KEY;
}
};
onMount(async () => {
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
@@ -85,12 +102,24 @@
} else {
getWebAPIVoices();
}
if ($user.role === 'admin') {
const res = await getAudioConfig(localStorage.token);
if (res) {
OpenAIUrl = res.OPENAI_API_BASE_URL;
OpenAIKey = res.OPENAI_API_KEY;
}
}
});
</script>
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={() => {
on:submit|preventDefault={async () => {
if ($user.role === 'admin') {
await updateConfigHandler();
}
saveSettings({
audio: {
STTEngine: STTEngine !== '' ? STTEngine : undefined,
@@ -101,7 +130,7 @@
dispatch('save');
}}
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
<div>
<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
@@ -196,6 +225,26 @@
</div>
</div>
{#if $user.role === 'admin'}
{#if TTSEngine === 'openai'}
<div class="mt-1 flex gap-2 mb-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Base URL')}
bind:value={OpenAIUrl}
required
/>
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Key')}
bind:value={OpenAIKey}
required
/>
</div>
{/if}
{/if}
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Auto-playback response')}</div>
@@ -223,7 +272,7 @@
<div class="flex w-full">
<div class="flex-1">
<select
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={speaker}
placeholder="Select a voice"
>
@@ -241,16 +290,18 @@
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
<div class="flex w-full">
<div class="flex-1">
<select
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
<input
list="voice-list"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={speaker}
placeholder="Select a voice"
>
/>
<datalist id="voice-list">
{#each voices as voice}
<option value={voice.name} class="bg-gray-100 dark:bg-gray-700">{voice.name}</option
>
<option value={voice.name} />
{/each}
</select>
</datalist>
</div>
</div>
</div>
@@ -288,20 +288,20 @@
<div class="flex border-b dark:border-gray-600 w-full">
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder="Title (e.g. Tell me a fun fact)"
placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
bind:value={prompt.title[0]}
/>
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder="Subtitle (e.g. about the Roman Empire)"
placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
bind:value={prompt.title[1]}
/>
</div>
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder="Prompt (e.g. Tell me a fun fact about the Roman Empire)"
placeholder={$i18n.t('Prompt (e.g. Tell me a fun fact about the Roman Empire)')}
bind:value={prompt.content}
/>
</div>
@@ -139,7 +139,7 @@
};
const pullModelHandler = async () => {
const sanitizedModelTag = modelTag.trim();
const sanitizedModelTag = modelTag.trim().replace(/^ollama\s+(run|pull)\s+/, '');
if (modelDownloadStatus[sanitizedModelTag]) {
toast.error(
$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, {
+2 -30
View File
@@ -3,9 +3,7 @@
import { toast } from 'svelte-sonner';
import { models, settings, user } from '$lib/stores';
import { getOllamaModels } from '$lib/apis/ollama';
import { getOpenAIModels } from '$lib/apis/openai';
import { getLiteLLMModels } from '$lib/apis/litellm';
import { getModels } from '$lib/utils';
import Modal from '../common/Modal.svelte';
import Account from './Settings/Account.svelte';
@@ -25,37 +23,11 @@
const saveSettings = async (updated) => {
console.log(updated);
await settings.set({ ...$settings, ...updated });
await models.set(await getModels());
await models.set(await getModels(localStorage.token));
localStorage.setItem('settings', JSON.stringify($settings));
};
let selectedTab = 'general';
const getModels = async () => {
let models = await Promise.all([
await getOllamaModels(localStorage.token).catch((error) => {
console.log(error);
return null;
}),
await getOpenAIModels(localStorage.token).catch((error) => {
console.log(error);
return null;
}),
await getLiteLLMModels(localStorage.token).catch((error) => {
console.log(error);
return null;
})
]);
models = models
.filter((models) => models)
.reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [{ name: 'hr' }] : [])), []);
// models.push(...(ollamaModels ? [{ name: 'hr' }, ...ollamaModels] : []));
// models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
// models.push(...(liteLLMModels ? [{ name: 'hr' }, ...liteLLMModels] : []));
return models;
};
</script>
<Modal bind:show>
+18 -11
View File
@@ -3,24 +3,27 @@
import { toast } from 'svelte-sonner';
import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
import { chatId, modelfiles } from '$lib/stores';
import { modelfiles } from '$lib/stores';
import { copyToClipboard } from '$lib/utils';
import Modal from '../common/Modal.svelte';
import Link from '../icons/Link.svelte';
export let chatId;
let chat = null;
let shareUrl = null;
const i18n = getContext('i18n');
const shareLocalChat = async () => {
const _chat = chat;
const sharedChat = await shareChatById(localStorage.token, $chatId);
const chatShareUrl = `${window.location.origin}/s/${sharedChat.id}`;
const sharedChat = await shareChatById(localStorage.token, chatId);
shareUrl = `${window.location.origin}/s/${sharedChat.id}`;
console.log(shareUrl);
chat = await getChatById(localStorage.token, chatId);
toast.success($i18n.t('Copied shared chat URL to clipboard!'));
copyToClipboard(chatShareUrl);
chat = await getChatById(localStorage.token, $chatId);
return shareUrl;
};
const shareChat = async () => {
@@ -56,8 +59,8 @@
$: if (show) {
(async () => {
if ($chatId) {
chat = await getChatById(localStorage.token, $chatId);
if (chatId) {
chat = await getChatById(localStorage.token, chatId);
} else {
chat = null;
console.log(chat);
@@ -101,10 +104,10 @@
<button
class="underline"
on:click={async () => {
const res = await deleteSharedChatById(localStorage.token, $chatId);
const res = await deleteSharedChatById(localStorage.token, chatId);
if (res) {
chat = await getChatById(localStorage.token, $chatId);
chat = await getChatById(localStorage.token, chatId);
}
}}>delete this link</button
> and create a new shared link.
@@ -131,8 +134,12 @@
<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"
on:click={() => {
on:pointerdown={() => {
shareLocalChat();
}}
on:click={async () => {
copyToClipboard(shareUrl);
toast.success($i18n.t('Copied shared chat URL to clipboard!'));
show = false;
}}
>
+52
View File
@@ -0,0 +1,52 @@
<script>
import {
addTagById,
deleteTagById,
getAllChatTags,
getTagsById,
updateChatById
} from '$lib/apis/chats';
import { tags as _tags } from '$lib/stores';
import { onMount } from 'svelte';
import Tags from '../common/Tags.svelte';
export let chatId = '';
let tags = [];
const getTags = async () => {
return await getTagsById(localStorage.token, chatId).catch(async (error) => {
return [];
});
};
const addTag = async (tagName) => {
const res = await addTagById(localStorage.token, chatId, tagName);
tags = await getTags();
await updateChatById(localStorage.token, chatId, {
tags: tags
});
_tags.set(await getAllChatTags(localStorage.token));
};
const deleteTag = async (tagName) => {
const res = await deleteTagById(localStorage.token, chatId, tagName);
tags = await getTags();
await updateChatById(localStorage.token, chatId, {
tags: tags
});
_tags.set(await getAllChatTags(localStorage.token));
};
onMount(async () => {
if (chatId) {
tags = await getTags();
}
});
</script>
<Tags {tags} {deleteTag} {addTag} />
@@ -4,10 +4,12 @@
import { flyAndScale } from '$lib/utils/transitions';
export let show = false;
const dispatch = createEventDispatcher();
</script>
<DropdownMenu.Root
bind:open={show}
onOpenChange={(state) => {
dispatch('change', state);
}}
+4 -2
View File
@@ -15,8 +15,10 @@
return 'w-[16rem]';
} else if (size === 'sm') {
return 'w-[30rem]';
} else {
} else if (size === 'md') {
return 'w-[44rem]';
} else {
return 'w-[48rem]';
}
};
@@ -47,7 +49,7 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
bind:this={modalElement}
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain"
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain"
in:fade={{ duration: 10 }}
on:click={() => {
show = false;
@@ -0,0 +1,42 @@
<script lang="ts">
import { Pagination } from 'bits-ui';
import { createEventDispatcher } from 'svelte';
import ChevronLeft from '../icons/ChevronLeft.svelte';
import ChevronRight from '../icons/ChevronRight.svelte';
export let page = 0;
export let count = 0;
export let perPage = 20;
</script>
<div class="flex justify-center">
<Pagination.Root bind:page {count} {perPage} let:pages>
<div class="my-2 flex items-center">
<Pagination.PrevButton
class="mr-[25px] inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 active:scale-98 disabled:cursor-not-allowed disabled:text-gray-400 dark:disabled:text-gray-700 hover:disabled:bg-transparent dark:hover:disabled:bg-transparent"
>
<ChevronLeft className="size-4" strokeWidth="2" />
</Pagination.PrevButton>
<div class="flex items-center gap-2.5">
{#each pages as page (page.key)}
{#if page.type === 'ellipsis'}
<div class="text-sm font-medium text-foreground-alt">...</div>
{:else}
<Pagination.Page
{page}
class="inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 text-sm font-medium hover:bg-dark-10 active:scale-98 disabled:cursor-not-allowed disabled:opacity-50 hover:disabled:bg-transparent data-[selected]:bg-black data-[selected]:text-gray-100 data-[selected]:hover:bg-black dark:data-[selected]:bg-white dark:data-[selected]:text-gray-900 dark:data-[selected]:hover:bg-white"
>
{page.value}
</Pagination.Page>
{/if}
{/each}
</div>
<Pagination.NextButton
class="ml-[25px] inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 active:scale-98 disabled:cursor-not-allowed disabled:text-gray-400 dark:disabled:text-gray-700 hover:disabled:bg-transparent dark:hover:disabled:bg-transparent"
>
<ChevronRight className="size-4" strokeWidth="2" />
</Pagination.NextButton>
</div>
</Pagination.Root>
</div>
@@ -16,7 +16,6 @@
const i18n = getContext('i18n');
export let show = false;
export let selectedDoc;
let uploadDocInputElement: HTMLInputElement;
let inputFiles;
let tags = [];
@@ -7,11 +7,11 @@
scanDocs,
updateQuerySettings,
resetVectorDB,
getEmbeddingModel,
updateEmbeddingModel
getEmbeddingConfig,
updateEmbeddingConfig
} from '$lib/apis/rag';
import { documents } from '$lib/stores';
import { documents, models } from '$lib/stores';
import { onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
@@ -26,6 +26,12 @@
let showResetConfirm = false;
let embeddingEngine = '';
let embeddingModel = '';
let OpenAIKey = '';
let OpenAIUrl = '';
let chunkSize = 0;
let chunkOverlap = 0;
let pdfExtractImages = true;
@@ -35,8 +41,6 @@
k: 4
};
let embeddingModel = '';
const scanHandler = async () => {
scanDirLoading = true;
const res = await scanDocs(localStorage.token);
@@ -49,7 +53,15 @@
};
const embeddingModelUpdateHandler = async () => {
if (embeddingModel.split('/').length - 1 > 1) {
if (embeddingEngine === '' && embeddingModel.split('/').length - 1 > 1) {
toast.error(
$i18n.t(
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
)
);
return;
}
if (embeddingEngine === 'ollama' && embeddingModel === '') {
toast.error(
$i18n.t(
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
@@ -58,14 +70,37 @@
return;
}
if (embeddingEngine === 'openai' && embeddingModel === '') {
toast.error(
$i18n.t(
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
)
);
return;
}
if ((embeddingEngine === 'openai' && OpenAIKey === '') || OpenAIUrl === '') {
toast.error($i18n.t('OpenAI URL/Key required.'));
return;
}
console.log('Update embedding model attempt:', embeddingModel);
updateEmbeddingModelLoading = true;
const res = await updateEmbeddingModel(localStorage.token, {
embedding_model: embeddingModel
const res = await updateEmbeddingConfig(localStorage.token, {
embedding_engine: embeddingEngine,
embedding_model: embeddingModel,
...(embeddingEngine === 'openai'
? {
openai_config: {
key: OpenAIKey,
url: OpenAIUrl
}
}
: {})
}).catch(async (error) => {
toast.error(error);
embeddingModel = (await getEmbeddingModel(localStorage.token)).embedding_model;
await setEmbeddingConfig();
return null;
});
updateEmbeddingModelLoading = false;
@@ -73,7 +108,7 @@
if (res) {
console.log('embeddingModelUpdateHandler:', res);
if (res.status === true) {
toast.success($i18n.t('Model {{embedding_model}} update complete!', res), {
toast.success($i18n.t('Embedding model set to "{{embedding_model}}"', res), {
duration: 1000 * 10
});
}
@@ -91,6 +126,18 @@
querySettings = await updateQuerySettings(localStorage.token, querySettings);
};
const setEmbeddingConfig = async () => {
const embeddingConfig = await getEmbeddingConfig(localStorage.token);
if (embeddingConfig) {
embeddingEngine = embeddingConfig.embedding_engine;
embeddingModel = embeddingConfig.embedding_model;
OpenAIKey = embeddingConfig.openai_config.key;
OpenAIUrl = embeddingConfig.openai_config.url;
}
};
onMount(async () => {
const res = await getRAGConfig(localStorage.token);
@@ -101,7 +148,7 @@
chunkOverlap = res.chunk.chunk_overlap;
}
embeddingModel = (await getEmbeddingModel(localStorage.token)).embedding_model;
await setEmbeddingConfig();
querySettings = await getQuerySettings(localStorage.token);
});
@@ -118,81 +165,212 @@
<div>
<div class=" mb-2 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' })}
<div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Model Engine')}</div>
<div class="flex items-center relative">
<select
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={embeddingEngine}
placeholder="Select an embedding model engine"
on:change={(e) => {
if (e.target.value === 'ollama') {
embeddingModel = '';
} else if (e.target.value === 'openai') {
embeddingModel = 'text-embedding-3-small';
}
}}
>
<option value="">{$i18n.t('Default (SentenceTransformer)')}</option>
<option value="ollama">{$i18n.t('Ollama')}</option>
<option value="openai">{$i18n.t('OpenAI')}</option>
</select>
</div>
<button
class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg flex flex-row space-x-1 items-center {scanDirLoading
? ' cursor-not-allowed'
: ''}"
on:click={() => {
scanHandler();
console.log('check');
}}
type="button"
disabled={scanDirLoading}
>
<div class="self-center font-medium">{$i18n.t('Scan')}</div>
{#if scanDirLoading}
<div class="ml-3 self-center">
<svg
class=" w-3 h-3"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
{/if}
</button>
</div>
</div>
<hr class=" dark:border-gray-700" />
{#if embeddingEngine === 'openai'}
<div class="mt-1 flex gap-2">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Base URL')}
bind:value={OpenAIUrl}
required
/>
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Key')}
bind:value={OpenAIKey}
required
/>
</div>
{/if}
</div>
<div class="space-y-2">
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Update Embedding Model')}</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Update embedding model (e.g. {{model}})', {
model: embeddingModel.slice(-40)
})}
bind:value={embeddingModel}
/>
</div>
<button
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
embeddingModelUpdateHandler();
}}
disabled={updateEmbeddingModelLoading}
>
{#if updateEmbeddingModelLoading}
<div class="self-center">
{#if embeddingEngine === 'ollama'}
<div class="flex w-full">
<div class="flex-1 mr-2">
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={embeddingModel}
placeholder={$i18n.t('Select a model')}
required
>
{#if !embeddingModel}
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
{/if}
{#each $models.filter((m) => m.id && !m.external) as model}
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
>
{/each}
</select>
</div>
<button
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
embeddingModelUpdateHandler();
}}
disabled={updateEmbeddingModelLoading}
>
{#if updateEmbeddingModelLoading}
<div class="self-center">
<svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
{:else}
<svg
class=" w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z"
clip-rule="evenodd"
/>
</svg>
{/if}
</button>
</div>
{:else}
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Update embedding model (e.g. {{model}})', {
model: embeddingModel.slice(-40)
})}
bind:value={embeddingModel}
/>
</div>
<button
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
embeddingModelUpdateHandler();
}}
disabled={updateEmbeddingModelLoading}
>
{#if updateEmbeddingModelLoading}
<div class="self-center">
<svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
/>
<path
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
/>
</svg>
{/if}
</button>
</div>
{/if}
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t(
'Warning: If you update or change your embedding model, you will need to re-import all documents.'
)}
</div>
<hr class=" dark:border-gray-700 my-3" />
<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' })}
</div>
<button
class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg flex flex-row space-x-1 items-center {scanDirLoading
? ' cursor-not-allowed'
: ''}"
on:click={() => {
scanHandler();
console.log('check');
}}
type="button"
disabled={scanDirLoading}
>
<div class="self-center font-medium">{$i18n.t('Scan')}</div>
{#if scanDirLoading}
<div class="ml-3 self-center">
<svg
class=" w-3 h-3"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
@@ -215,30 +393,10 @@
/></svg
>
</div>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
/>
<path
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
/>
</svg>
{/if}
</button>
</div>
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t(
'Warning: If you update or change your embedding model, you will need to re-import all documents.'
)}
</div>
<hr class=" dark:border-gray-700 my-3" />
<div class=" ">
@@ -0,0 +1,19 @@
<script lang="ts">
export let className = 'size-3.5';
export let strokeWidth = '2.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z"
/>
</svg>
@@ -0,0 +1,15 @@
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
</svg>
@@ -0,0 +1,15 @@
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
+11
View File
@@ -0,0 +1,11 @@
<script lang="ts">
export let className = 'w-4 h-4';
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
<path
fill-rule="evenodd"
d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z"
clip-rule="evenodd"
/>
</svg>
+1 -8
View File
@@ -19,17 +19,13 @@
export let chat;
export let selectedModels;
export let tags = [];
export let addTag: Function;
export let deleteTag: Function;
export let showModelSelector = true;
let showShareChatModal = false;
let showDownloadChatModal = false;
</script>
<ShareChatModal bind:show={showShareChatModal} />
<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 {$settings?.fullScreenMode ?? null ? 'max-w-full' : 'max-w-3xl'}
@@ -85,9 +81,6 @@
downloadHandler={() => {
showDownloadChatModal = !showDownloadChatModal;
}}
{tags}
{deleteTag}
{addTag}
>
<button
class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
+3 -7
View File
@@ -10,8 +10,8 @@
import { flyAndScale } from '$lib/utils/transitions';
import Dropdown from '$lib/components/common/Dropdown.svelte';
import Tags from '$lib/components/common/Tags.svelte';
import { WEBUI_BASE_URL } from '$lib/constants';
import Tags from '$lib/components/chat/Tags.svelte';
import { downloadChatAsPDF } from '$lib/apis/utils';
export let shareEnabled: boolean = false;
@@ -21,10 +21,6 @@
// export let tagHandler: Function;
export let chat;
export let tags;
export let deleteTag: Function;
export let addTag: Function;
export let onClose: Function = () => {};
const downloadTxt = async () => {
@@ -190,7 +186,7 @@
<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
<div class="flex p-1">
<Tags {tags} {deleteTag} {addTag} />
<Tags chatId={chat.id} />
</div>
<!-- <DropdownMenu.Item
+95 -10
View File
@@ -17,13 +17,17 @@
getChatById,
getChatListByTagName,
updateChatById,
getAllChatTags
getAllChatTags,
archiveChatById
} from '$lib/apis/chats';
import { toast } from 'svelte-sonner';
import { fade, slide } from 'svelte/transition';
import { WEBUI_BASE_URL } from '$lib/constants';
import Tooltip from '../common/Tooltip.svelte';
import ChatMenu from './Sidebar/ChatMenu.svelte';
import ShareChatModal from '../chat/ShareChatModal.svelte';
import ArchiveBox from '../icons/ArchiveBox.svelte';
import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
let show = false;
let navElement;
@@ -31,12 +35,16 @@
let title: string = 'UI';
let search = '';
let shareChatId = null;
let selectedChatId = null;
let chatDeleteId = null;
let chatTitleEditId = null;
let chatTitle = '';
let showArchivedChatsModal = false;
let showShareChatModal = false;
let showDropdown = false;
let isEditing = false;
@@ -45,6 +53,39 @@
show = true;
}
await chats.set(await getChatList(localStorage.token));
let touchstartX = 0;
let touchendX = 0;
function checkDirection() {
const screenWidth = window.innerWidth;
const swipeDistance = Math.abs(touchendX - touchstartX);
if (swipeDistance >= screenWidth / 4) {
if (touchendX < touchstartX) {
show = false;
}
if (touchendX > touchstartX) {
show = true;
}
}
}
const onTouchStart = (e) => {
touchstartX = e.changedTouches[0].screenX;
};
const onTouchEnd = (e) => {
touchendX = e.changedTouches[0].screenX;
checkDirection();
};
document.addEventListener('touchstart', onTouchStart);
document.addEventListener('touchend', onTouchEnd);
return () => {
document.removeEventListener('touchstart', onTouchStart);
document.removeEventListener('touchend', onTouchEnd);
};
});
// Helper function to fetch and add chat content to each chat
@@ -101,8 +142,21 @@
localStorage.setItem('settings', JSON.stringify($settings));
location.href = '/';
};
const archiveChatHandler = async (id) => {
await archiveChatById(localStorage.token, id);
await chats.set(await getChatList(localStorage.token));
};
</script>
<ShareChatModal bind:show={showShareChatModal} chatId={shareChatId} />
<ArchivedChatsModal
bind:show={showArchivedChatsModal}
on:change={async () => {
await chats.set(await getChatList(localStorage.token));
}}
/>
<div
bind:this={navElement}
class="h-screen max-h-[100dvh] min-h-screen {show
@@ -511,8 +565,13 @@
</button>
</div>
{:else}
<div class="flex self-center space-x-1.5 z-10">
<div class="flex self-center space-x-1 z-10">
<ChatMenu
chatId={chat.id}
shareHandler={() => {
shareChatId = selectedChatId;
showShareChatModal = true;
}}
renameHandler={() => {
chatTitle = chat.title;
chatTitleEditId = chat.id;
@@ -543,6 +602,18 @@
</svg>
</button>
</ChatMenu>
<Tooltip content="Archive">
<button
aria-label="Archive"
class=" self-center dark:hover:text-white transition"
on:click={() => {
archiveChatHandler(chat.id);
}}
>
<ArchiveBox />
</button>
</Tooltip>
</div>
{/if}
</div>
@@ -575,13 +646,13 @@
{#if showDropdown}
<div
id="dropdownDots"
class="absolute z-40 bottom-[70px] 4.5rem rounded-xl shadow w-[240px] bg-white dark:bg-gray-900"
class="absolute z-40 bottom-[70px] rounded-lg shadow w-[240px] bg-white dark:bg-gray-900"
transition:fade|slide={{ duration: 100 }}
>
<div class="py-2 w-full">
<div class="p-1 py-2 w-full">
{#if $user.role === 'admin'}
<button
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
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;
@@ -607,7 +678,7 @@
</button>
<button
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
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;
@@ -634,7 +705,20 @@
{/if}
<button
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
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;
@@ -665,11 +749,11 @@
</button>
</div>
<hr class=" dark:border-gray-700 m-0 p-0" />
<hr class=" dark:border-gray-800 m-0 p-0" />
<div class="py-2 w-full">
<div class="p-1 py-2 w-full">
<button
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
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';
@@ -706,6 +790,7 @@
</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"
>
<Tooltip
@@ -0,0 +1,168 @@
<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 { archiveChatById, deleteChatById, getArchivedChatList } from '$lib/apis/chats';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
export let show = false;
let chats = [];
const unarchiveChatHandler = async (chatId) => {
const res = await archiveChatById(localStorage.token, chatId).catch((error) => {
toast.error(error);
});
chats = await getArchivedChatList(localStorage.token);
dispatch('change');
};
const deleteChatHandler = async (chatId) => {
const res = await deleteChatById(localStorage.token, chatId).catch((error) => {
toast.error(error);
});
chats = await getArchivedChatList(localStorage.token);
};
$: 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 py-4">
<div class=" text-lg font-medium self-center">{$i18n.t('Archived Chats')}</div>
<button
class="self-center"
on:click={() => {
show = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
<hr class=" dark:border-gray-850" />
<div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200">
<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
{#if chats.length > 0}
<div class="text-left text-sm w-full mb-4">
<div class="relative overflow-x-auto">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto">
<thead
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 border-gray-800"
>
<tr>
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
<th scope="col" class="px-3 py-2"> {$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-white {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">
{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
</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">You have no archived conversations.</div>
{/if}
</div>
</div>
</div>
</Modal>
@@ -6,14 +6,21 @@
import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
import Pencil from '$lib/components/icons/Pencil.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Tags from '$lib/components/chat/Tags.svelte';
import Share from '$lib/components/icons/Share.svelte';
export let shareHandler: Function;
export let renameHandler: Function;
export let deleteHandler: Function;
export let onClose: Function;
export let chatId = '';
let show = false;
</script>
<Dropdown
bind:show
on:change={(e) => {
if (e.detail === false) {
onClose();
@@ -26,14 +33,24 @@
<div slot="content">
<DropdownMenu.Content
class="w-full max-w-[130px] 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"
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"
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"
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
on:click={() => {
shareHandler();
}}
>
<Share />
<div class="flex items-center">Share</div>
</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"
on:click={() => {
renameHandler();
}}
@@ -43,7 +60,7 @@
</DropdownMenu.Item>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer"
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
on:click={() => {
deleteHandler();
}}
@@ -51,6 +68,12 @@
<GarbageBin strokeWidth="2" />
<div class="flex items-center">Delete</div>
</DropdownMenu.Item>
<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
<div class="flex p-1">
<Tags {chatId} />
</div>
</DropdownMenu.Content>
</div>
</Dropdown>
+1 -1
View File
@@ -1,5 +1,5 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'с', 'м', 'ч', 'д', 'с' или '-1' за неограничен срок.",
"'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)": "(последна)",
+18 -9
View File
@@ -63,7 +63,7 @@
"Click here to select": "Presiona aquí para seleccionar",
"Click here to select documents.": "Presiona aquí para seleccionar documentos",
"click here.": "Presiona aquí.",
"Click on the user role button to change a user's role.": "Presiona en el botón de roles del usuario para cambiar el rol de un usuario.",
"Click on the user role button to change a user's role.": "Presiona en el botón de roles del usuario para cambiar su rol.",
"Close": "Cerrar",
"Collection": "Colección",
"Command": "Comando",
@@ -120,9 +120,10 @@
"Edit Doc": "Editar Documento",
"Edit User": "Editar Usuario",
"Email": "Email",
"Embedding model: {{embedding_model}}": "Modelo de Embedding: {{embedding_model}}",
"Enable Chat History": "Activa el Historial de Chat",
"Enable New Sign Ups": "Habilitar Nuevos Registros",
"Enabled": "Habilitado",
"Enabled": "Activado",
"Enter {{role}} message here": "Introduzca el mensaje {{role}} aquí",
"Enter API Key": "Ingrese la clave API",
"Enter Chunk Overlap": "Ingresar superposición de fragmentos",
@@ -145,11 +146,12 @@
"Export All Chats (All Users)": "Exportar todos los chats (Todos los usuarios)",
"Export Chats": "Exportar Chats",
"Export Documents Mapping": "Exportar el mapeo de documentos",
"Export Modelfiles": "Exportal Modelfiles",
"Export Modelfiles": "Exportar Modelfiles",
"Export Prompts": "Exportar Prompts",
"Failed to read clipboard contents": "No se pudo leer el contenido del portapapeles",
"File Mode": "Modo de archivo",
"File not found.": "Archivo no encontrado.",
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Se detectó suplantación de huellas: No se pueden usar las iniciales como avatar. Por defecto se utiliza la imagen de perfil predeterminada.",
"Focus chat input": "Enfoca la entrada del chat",
"Format your variables using square brackets like this:": "Formatee sus variables usando corchetes así:",
"From (Base Model)": "Desde (Modelo Base)",
@@ -193,8 +195,11 @@
"MMMM DD, YYYY": "MMMM DD, YYYY",
"Model '{{modelName}}' has been successfully downloaded.": "El modelo '{{modelName}}' se ha descargado correctamente.",
"Model '{{modelTag}}' is already in queue for downloading.": "El modelo '{{modelTag}}' ya está en cola para descargar.",
"Model {{embedding_model}} update complete!": "¡La actualizacón del modelo {{embedding_model}} fué completada!",
"Model {{embedding_model}} update failed or not required!": "¡La actualización del modelo {{embedding_model}} falló o no es requerida!",
"Model {{modelId}} not found": "El modelo {{modelId}} no fue encontrado",
"Model {{modelName}} already exists.": "El modelo {{modelName}} ya existe.",
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Se detectó la ruta del sistema de archivos del modelo. Se requiere el nombre corto del modelo para la actualización, no se puede continuar.",
"Model Name": "Nombre del modelo",
"Model not selected": "Modelo no seleccionado",
"Model Tag Name": "Nombre de la etiqueta del modelo",
@@ -215,11 +220,11 @@
"New Password": "Nueva Contraseña",
"Not sure what to add?": "¿No estás seguro de qué añadir?",
"Not sure what to write? Switch to": "¿No estás seguro de qué escribir? Cambia a",
"Off": "Apagado",
"Off": "Desactivado",
"Okay, Let's Go!": "Okay, Let's Go!",
"Ollama Base URL": "URL base de Ollama",
"Ollama Version": "Version de Ollama",
"On": "Encendido",
"On": "Activado",
"Only": "Solamente",
"Only alphanumeric characters and hyphens are allowed in the command string.": "Sólo se permiten caracteres alfanuméricos y guiones en la cadena de comando.",
"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! ¡Agárrate fuerte! Tus archivos todavía están en el horno de procesamiento. Los estamos cocinando a la perfección. Tenga paciencia y le avisaremos una vez que estén listos.",
@@ -227,7 +232,7 @@
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "¡Ups! Estás utilizando un método no compatible (solo frontend). Sirve la WebUI desde el backend.",
"Open": "Abrir",
"Open AI": "Open AI",
"Open AI (Dall-E)": "",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "Abrir nuevo chat",
"OpenAI API": "OpenAI API",
"OpenAI API Key": "Clave de OpenAI API",
@@ -243,7 +248,7 @@
"Prompt Content": "Contenido del Prompt",
"Prompt suggestions": "Sugerencias de Prompts",
"Prompts": "Prompts",
"Pull a model from Ollama.com": "Extraer un modelo de Ollama.com",
"Pull a model from Ollama.com": "Halar un modelo de Ollama.com",
"Pull Progress": "Progreso de extracción",
"Query Params": "Parámetros de consulta",
"RAG Template": "Plantilla de RAG",
@@ -256,7 +261,7 @@
"Request Mode": "Modo de petición",
"Reset Vector Storage": "Restablecer almacenamiento vectorial",
"Response AutoCopy to Clipboard": "Copiar respuesta automáticamente al portapapeles",
"Role": "personalizados",
"Role": "Rol",
"Rosé Pine": "Rosé Pine",
"Rosé Pine Dawn": "Rosé Pine Dawn",
"Save": "Guardar",
@@ -332,7 +337,10 @@
"TTS Settings": "Configuración de TTS",
"Type Hugging Face Resolve (Download) URL": "Type Hugging Face Resolve (Download) URL",
"Uh-oh! There was an issue connecting to {{provider}}.": "¡UH oh! Hubo un problema al conectarse a {{provider}}.",
"Understand that updating or changing your embedding model requires reset of the vector database and re-import of all documents. You have been warned!": "Comprenda que actualizar o cambiar su modelo de embedding requiere restablecer la base de datos de vectores y volver a importar todos los documentos. ¡Usted ha sido advertido!",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Tipo de archivo desconocido '{{file_type}}', pero se acepta y se trata como texto sin formato",
"Update": "Actualizar",
"Update embedding model {{embedding_model}}": "Actualizar modelo de embedding {{embedding_model}}",
"Update password": "Actualiza contraseña",
"Upload a GGUF model": "Sube un modelo GGUF",
"Upload files": "Subir archivos",
@@ -340,6 +348,7 @@
"URL Mode": "Modo de URL",
"Use '#' in the prompt input to load and select your documents.": "Utilice '#' en el prompt para cargar y seleccionar sus documentos.",
"Use Gravatar": "Usar Gravatar",
"Use Initials": "Usar Iniciales",
"user": "usuario",
"User Permissions": "Permisos de usuario",
"Users": "Usuarios",
@@ -347,7 +356,7 @@
"Valid time units:": "Unidades válidas de tiempo:",
"variable": "variable",
"variable to have them replaced with clipboard content.": "variable para reemplazarlos con el contenido del portapapeles.",
"Version": "Version",
"Version": "Versión",
"Web": "Web",
"WebUI Add-ons": "WebUI Add-ons",
"WebUI Settings": "Configuración del WebUI",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'', '', '時間', '', '' または '-1' で無期限。",
"'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)": "(最新)",
+372
View File
@@ -0,0 +1,372 @@
{
"'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}} ფიქრობს...",
"{{webUIName}} Backend Required": "{{webUIName}} საჭიროა ბექენდი",
"a user": "მომხმარებელი",
"About": "შესახებ",
"Account": "ანგარიში",
"Action": "ქმედება",
"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 Docs": "დოკუმენტის დამატება",
"Add Files": "ფაილების დამატება",
"Add message": "შეტყობინების დამატება",
"add tags": "ტეგების დამატება",
"Adjusting these settings will apply changes universally to all users.": "ამ პარამეტრების რეგულირება ცვლილებებს უნივერსალურად გამოიყენებს ყველა მომხმარებლისთვის",
"admin": "ადმინისტრატორი",
"Admin Panel": "ადმინ პანელი",
"Admin Settings": "ადმინისტრატორის ხელსაწყოები",
"Advanced Parameters": "დამატებითი პარამეტრები",
"all": "ყველა",
"All Users": "ყველა მომხმარებელი",
"Allow": "ნების დართვა",
"Allow Chat Deletion": "მიმოწერის წაშლის დაშვება",
"alphanumeric characters and hyphens": "ალფანუმერული სიმბოლოები და დეფისები",
"Already have an account?": "უკვე გაქვს ანგარიში?",
"an assistant": "ასისტენტი",
"and": "და",
"API Base URL": "API საბაზისო URL",
"API Key": "API გასაღები",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "დაშვებულია - ბრძანების გასააქტიურებლად აკრიფეთ:",
"Are you sure?": "დარწმუნებული ხარ?",
"Audio": "ხმოვანი",
"Auto-playback response": "ავტომატური დაკვრის პასუხი",
"Auto-send input after 3 sec.": "შეყვანის ავტომატური გაგზავნა 3 წამის შემდეგ ",
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 საბაზისო მისამართი",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 საბაზისო მისამართი აუცილებელია",
"available!": "ხელმისაწვდომია!",
"Back": "უკან",
"Builder Mode": "მოდელის შექმნა",
"Cancel": "გაუქმება",
"Categories": "კატეგორიები",
"Change Password": "პაროლის შეცვლა",
"Chat": "მიმოწერა",
"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": "გადახურვის ზომა",
"Click here for help.": "დახმარებისთვის, დააკლიკე აქ",
"Click here to check other modelfiles.": "სხვა მოდელური ფაილების სანახავად, დააკლიკე აქ",
"Click here to select": "ასარჩევად, დააკლიკე აქ",
"Click here to select documents.": "დოკუმენტების ასარჩევად, დააკლიკე აქ",
"click here.": "დააკლიკე აქ",
"Click on the user role button to change a user's role.": "დააკლიკეთ მომხმარებლის როლის ღილაკს რომ შეცვალოთ მომხმარების როლი",
"Close": "დახურვა",
"Collection": "ნაკრები",
"Command": "ბრძანება",
"Confirm Password": "პაროლის დამოწმება",
"Connections": "კავშირები",
"Content": "კონტენტი",
"Context Length": "კონტექსტის სიგრძე",
"Conversation Mode": "საუბრი რეჟიმი",
"Copy last code block": "ბოლო ბლოკის კოპირება",
"Copy last response": "ბოლო პასუხის კოპირება",
"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": "ანგარიშის შექმნა",
"Created at": "შექმნილია",
"Created by": "ავტორი",
"Current Model": "მიმდინარე მოდელი",
"Current Password": "მიმდინარე პაროლი",
"Custom": "საკუთარი",
"Customize Ollama models for a specific purpose": "Ollama მოდელების დამუშავება სპეციფიური დანიშნულებისთვის",
"Dark": "მუქი",
"Database": "მონაცემთა ბაზა",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "დეფოლტი",
"Default (Automatic1111)": "დეფოლტ (Automatic1111)",
"Default (Web API)": "დეფოლტ (Web API)",
"Default model updated": "დეფოლტ მოდელი განახლებულია",
"Default Prompt Suggestions": "",
"Default User Role": "მომხმარებლის დეფოლტ როლი",
"delete": "წაშლა",
"Delete a model": "მოდელის წაშლა",
"Delete chat": "შეტყობინების წაშლა",
"Delete Chats": "შეტყობინებების წაშლა",
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} წაშლილია",
"Deleted {tagName}": "{tagName} წაშლილია",
"Description": "აღწერა",
"Notifications": "შეტყობინება",
"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?": "არ გაქვს ანგარიში?",
"Download as a File": "გადმოწერე როგორც ფაილი",
"Download Database": "გადმოწერე მონაცემთა ბაზა",
"Drop any files here to add to the conversation": "გადაიტანეთ ფაილები აქ, რათა დაამატოთ ისინი მიმოწერაში",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "მაგალითად, '30წ', '10მ'. მოქმედი დროის ერთეულები: 'წ', 'წთ', 'სთ'.",
"Edit Doc": "დოკუმენტის ედიტირება",
"Edit User": "მომხმარებლის ედიტირება",
"Email": "ელ-ფოსტა",
"Embedding model: {{embedding_model}}": "ჩაშენების მოდელი: {{embedding_model}}",
"Enable Chat History": "მიმოწერის ისტორიის ჩართვა",
"Enable New Sign Ups": "ახალი რეგისტრაციების ჩართვა",
"Enabled": "ჩართულია",
"Enter {{role}} message here": "შეიყვანე {{role}} შეტყობინება აქ",
"Enter API Key": "შეიყვანე API Key",
"Enter Chunk Overlap": "შეიყვანეთ ნაწილის გადახურვა",
"Enter Chunk Size": "შეიყვანე ბლოკის ზომა",
"Enter Image Size (e.g. 512x512)": "შეიყვანეთ სურათის ზომა (მაგ. 512x512)",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "შეიყვანეთ LiteLLM API ბაზის მისამართი (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 (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 tag (e.g. {{modelTag}})": "შეიყვანეთ მოდელის ტეგი (მაგ. {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "შეიყვანეთ ნაბიჯების რაოდენობა (მაგ. 50)",
"Enter stop sequence": "შეიყვანეთ ტოპ თანმიმდევრობა",
"Enter Top K": "შეიყვანეთ Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "შეიყვანეთ მისამართი (მაგალითად http://127.0.0.1:7860/)",
"Enter Your Email": "შეიყვანეთ თქვენი ელ-ფოსტა",
"Enter Your Full Name": "შეიყვანეთ თქვენი სრული სახელი",
"Enter Your Password": "შეიყვანეთ თქვენი პაროლი",
"Experimental": "ექსპერიმენტალური",
"Export All Chats (All Users)": "",
"Export Chats": "მიმოწერის ექსპორტირება",
"Export Documents Mapping": "დოკუმენტების კავშირის ექსპორტი",
"Export Modelfiles": "მოდელური ფაილების ექსპორტი",
"Export Prompts": "მოთხოვნების ექსპორტი",
"Failed to read clipboard contents": "ბუფერში შიგთავსის წაკითხვა ვერ მოხერხდა",
"File Mode": "ფაილური რეჟიმი",
"File not found.": "ფაილი ვერ მოიძებნა",
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "აღმოჩენილია თითის ანაბეჭდის გაყალბება: ინიციალების გამოყენება ავატარად შეუძლებელია. დეფოლტ პროფილის დეფოლტ სურათი.",
"Focus chat input": "ჩეთის შეყვანის ფოკუსი",
"Format your variables using square brackets like this:": "დააფორმატეთ თქვენი ცვლადები კვადრატული ფრჩხილების გამოყენებით:",
"From (Base Model)": "(საბაზო მოდელი) დან",
"Full Screen Mode": "Სრული ეკრანის რეჟიმი",
"General": "ზოგადი",
"General Settings": "ზოგადი პარამეტრები",
"Hello, {{name}}": "გამარჯობა, {{name}}",
"Hide": "დამალვა",
"Hide Additional Params": "დამატებითი პარამეტრების დამალვა",
"How can I help you today?": "როგორ შემიძლია დაგეხმარო დღეს?",
"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-ის გაშვებისას",
"Interface": "ინტერფეისი",
"join our Discord for help.": "შეუერთდით ჩვენს Discord-ს დახმარებისთვის",
"JSON": "JSON",
"JWT Expiration": "JWT-ის ვადა",
"JWT Token": "JWT ტოკენი",
"Keep Alive": "აქტიურად დატოვება",
"Keyboard shortcuts": "კლავიატურის მალსახმობები",
"Language": "ენა",
"Light": "მსუბუქი",
"Listening...": "გისმენ...",
"LLMs can make mistakes. Verify important information.": "შესაძლოა LLM-ებმა შეცდომები დაუშვან. გადაამოწმეთ მნიშვნელოვანი ინფორმაცია.",
"Made by OpenWebUI Community": "დამზადებულია OpenWebUI საზოგადოების მიერ",
"Make sure to enclose them with": "დარწმუნდით, რომ დაურთეთ ისინი",
"Manage LiteLLM Models": "LiteLLM მოდელების მართვა",
"Manage Models": "მოდელების მართვა",
"Manage Ollama Models": "Ollama მოდელების მართვა",
"Max Tokens": "მაქსიმალური ტოკენები",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "მაქსიმუმ 3 მოდელის ჩამოტვირთვა შესაძლებელია ერთდროულად. Გთხოვთ სცადოთ მოგვიანებით.",
"Mirostat": "მიროსტატი",
"Mirostat Eta": "მიროსტატი ეტა",
"Mirostat Tau": "მიროსტატი ტაუ",
"MMMM DD, YYYY": "თვე დღე, წელი",
"Model '{{modelName}}' has been successfully downloaded.": "მოდელი „{{modelName}}“ წარმატებით ჩამოიტვირთა.",
"Model '{{modelTag}}' is already in queue for downloading.": "მოდელი „{{modelTag}}“ უკვე ჩამოტვირთვის რიგშია.",
"Model {{embedding_model}} update complete!": "მოდელის {{embedding_model}} განახლება დასრულდა!",
"Model {{embedding_model}} update failed or not required!": "მოდელის {{embedding_model}} განახლება ვერ მოხერხდა ან არ არის საჭირო!",
"Model {{modelId}} not found": "მოდელი {{modelId}} ვერ მოიძებნა",
"Model {{modelName}} already exists.": "მოდელი {{modelName}} უკვე არსებობს.",
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "აღმოჩენილია მოდელის ფაილური სისტემის გზა. განახლებისთვის საჭიროა მოდელის მოკლე სახელი, გაგრძელება შეუძლებელია.",
"Model Name": "მოდელის სახელი",
"Model not selected": "მოდელი არ არის არჩეული",
"Model Tag Name": "მოდელის ტეგის სახელი",
"Model Whitelisting": "მოდელის თეთრ სიაში შეყვანა",
"Model(s) Whitelisted": "მოდელ(ებ)ი თეთრ სიაშია",
"Modelfile": "მოდელური ფაილი",
"Modelfile Advanced Settings": "მოდელური ფაილის პარამეტრები",
"Modelfile Content": "მოდელური ფაილის კონტენტი",
"Modelfiles": "მოდელური ფაილები",
"Models": "მოდელები",
"My Documents": "ჩემი დოკუმენტები",
"My Modelfiles": "ჩემი მოდელური ფაილები",
"My Prompts": "ჩემი მოთხოვნები",
"Name": "სახელი",
"Name Tag": "სახელის ტეგი",
"Name your modelfile": "თქვენი მოდელური ფაილის სახელი",
"New Chat": "ახალი მიმოწერა",
"New Password": "ახალი პაროლი",
"Not sure what to add?": "არ იცი რა დაამატო?",
"Not sure what to write? Switch to": "არ იცი რა დაწერო? გადართვა:",
"Off": "გამორთვა",
"Okay, Let's Go!": "კარგი, წავედით!",
"Ollama Base 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.": "უი! როგორც ჩანს, მისამართი არასწორია. გთხოვთ, გადაამოწმოთ და ისევ სცადოთ.",
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "უპს! თქვენ იყენებთ მხარდაუჭერელ მეთოდს (მხოლოდ frontend). გთხოვთ, მოემსახუროთ WebUI-ს ბექენდიდან",
"Open": "ღია",
"Open AI": "ღია AI",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "ახალი მიმოწერის გახსნა",
"OpenAI API": "OpenAI API",
"OpenAI API Key": "OpenAI API გასაღები",
"OpenAI API Key is required.": "OpenAI API გასაღები აუცილებელია",
"or": "ან",
"Parameters": "პარამეტრები",
"Password": "პაროლი",
"PDF Extract Images (OCR)": "PDF იდან ამოღებული სურათები (OCR)",
"pending": "ლოდინის რეჟიმშია",
"Permission denied when accessing microphone: {{error}}": "ნებართვა უარყოფილია მიკროფონზე წვდომისას: {{error}}",
"Playground": "სათამაშო მოედანი",
"Profile": "პროფილი",
"Prompt Content": "მოთხოვნის შინაარსი",
"Prompt suggestions": "მოთხოვნის რჩევები",
"Prompts": "მოთხოვნები",
"Pull a model from Ollama.com": "Ollama.com იდან მოდელის გადაწერა ",
"Pull Progress": "პროგრესის გადაწერა",
"Query Params": "პარამეტრების ძიება",
"RAG Template": "RAG შაბლონი",
"Raw Format": "საწყისი ფორმატი",
"Record voice": "ხმის ჩაწერა",
"Redirecting you to OpenWebUI Community": "გადამისამართდებით OpenWebUI საზოგადოებაში",
"Release Notes": "Გამოშვების შენიშვნები",
"Repeat Last N": "გაიმეორეთ ბოლო N",
"Repeat Penalty": "გაიმეორეთ პენალტი",
"Request Mode": "მოთხოვნის რეჟიმი",
"Reset Vector Storage": "ვექტორული მეხსიერების გადატვირთვა",
"Response AutoCopy to Clipboard": "პასუხის ავტომატური კოპირება ბუფერში",
"Role": "როლი",
"Rosé Pine": "ვარდისფერი ფიჭვის ხე",
"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 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": "",
"Send a Message": "შეტყობინების გაგზავნა",
"Send message": "შეტყობინების გაგზავნა",
"Server connection verified": "სერვერთან კავშირი დადასტურებულია",
"Set as default": "დეფოლტად დაყენება",
"Set Default Model": "დეფოლტ მოდელის დაყენება",
"Set Image Size": "სურათის ზომის დაყენება",
"Set Steps": "ნაბიჯების დაყენება",
"Set Title Auto-Generation Model": "სათაურის ავტომატური გენერაციის მოდელის დაყენება",
"Set Voice": "ხმის დაყენება",
"Settings": "ხელსაწყოები",
"Settings saved successfully!": "პარამეტრები წარმატებით განახლდა!",
"Share to OpenWebUI Community": "გააზიარე OpenWebUI საზოგადოებაში ",
"short-summary": "მოკლე შინაარსი",
"Show": "ჩვენება",
"Show Additional Params": "დამატებითი პარამეტრების ჩვენება",
"Show shortcuts": "მალსახმობების ჩვენება",
"sidebar": "საიდბარი",
"Sign in": "ავტორიზაცია",
"Sign Out": "გასვლა",
"Sign up": "რეგისტრაცია",
"Speech recognition error: {{error}}": "მეტყველების ამოცნობის შეცდომა: {{error}}",
"Speech-to-Text Engine": "ხმოვან-ტექსტური ძრავი",
"SpeechRecognition API is not supported in this browser.": "მეტყველების ამოცნობის API არ არის მხარდაჭერილი ამ ბრაუზერში.",
"Stop Sequence": "შეჩერების თანმიმდევრობა",
"STT Settings": "მეტყველების ამოცნობის პარამეტრები",
"Submit": "გაგზავნა",
"Success": "წარმატება",
"Successfully updated.": "წარმატებით განახლდა",
"Sync All": "სინქრონიზაცია",
"System": "სისტემა",
"System Prompt": "სისტემური მოთხოვნა",
"Tags": "ტეგები",
"Temperature": "ტემპერატურა",
"Template": "შაბლონი",
"Text Completion": "ტექსტის დასრულება",
"Text-to-Speech Engine": "ტექსტურ-ხმოვანი ძრავი",
"Tfs Z": "Tfs Z",
"Theme": "თემა",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "ეს უზრუნველყოფს, რომ თქვენი ძვირფასი საუბრები უსაფრთხოდ შეინახება თქვენს backend მონაცემთა ბაზაში. Გმადლობთ!",
"This setting does not sync across browsers or devices.": "ეს პარამეტრი არ სინქრონიზდება ბრაუზერებსა და მოწყობილობებში",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "რჩევა: განაახლეთ რამდენიმე ცვლადი სლოტი თანმიმდევრულად, ყოველი ჩანაცვლების შემდეგ ჩატის ღილაკზე დაჭერით.",
"Title": "სათაური",
"Title Auto-Generation": "სათაურის ავტო-გენერაცია",
"Title Generation Prompt": "სათაურის გენერაციის მოთხოვნა ",
"to": "ში",
"To access the available model names for downloading,": "ჩამოტვირთვისთვის ხელმისაწვდომი მოდელების სახელებზე წვდომისთვის",
"To access the GGUF models available for downloading,": "ჩასატვირთად ხელმისაწვდომი GGUF მოდელებზე წვდომისთვის",
"to chat input.": "ჩატში",
"Toggle settings": "პარამეტრების გადართვა",
"Toggle sidebar": "გვერდითი ზოლის გადართვა",
"Top K": "ტოპ K",
"Top P": "ტოპ P",
"Trouble accessing Ollama?": "Ollama-ს ვერ უკავშირდები?",
"TTS Settings": "TTS პარამეტრები",
"Type Hugging Face Resolve (Download) URL": "სცადე გადმოწერო Hugging Face Resolve URL",
"Uh-oh! There was an issue connecting to {{provider}}.": "{{provider}}-თან დაკავშირების პრობლემა წარმოიშვა.",
"Understand that updating or changing your embedding model requires reset of the vector database and re-import of all documents. You have been warned!": "გაითვალისწინეთ, რომ თქვენი ჩაშენების მოდელის განახლება ან შეცვლა მოითხოვს ვექტორული მონაცემთა ბაზის გადატვირთვას და ყველა დოკუმენტის ხელახლა იმპორტს. ფრთხილად!",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "უცნობი ფაილის ტიპი „{{file_type}}“, მაგრამ მიიღება და განიხილება როგორც მარტივი ტექსტი",
"Update": "განახლება",
"Update embedding model {{embedding_model}}": "განაახლე ჩაშენების მოდელი {{embedding_model}}",
"Update password": "პაროლის განახლება",
"Upload a GGUF model": "GGUF მოდელის ატვირთვა",
"Upload files": "ფაილების ატვირთვა",
"Upload Progress": "პროგრესის ატვირთვა",
"URL Mode": "URL რეჟიმი",
"Use '#' in the prompt input to load and select your documents.": "",
"Use Gravatar": "გამოიყენე Gravatar",
"Use Initials": "გამოიყენე ინიციალები",
"user": "მომხმარებელი",
"User Permissions": "მომხმარებლის უფლებები",
"Users": "მომხმარებლები",
"Utilize": "გამოყენება",
"Valid time units:": "მოქმედი დროის ერთეულები",
"variable": "ცვლადი",
"variable to have them replaced with clipboard content.": "ცვლადი, რომ შეცვალოს ისინი ბუფერში შიგთავსით.",
"Version": "ვერსია",
"Web": "ვები",
"WebUI Add-ons": "WebUI დანამატები",
"WebUI Settings": "WebUI პარამეტრები",
"WebUI will make requests to": "WebUI გამოგიგზავნით მოთხოვნებს",
"Whats 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)": "ჩურჩული (ადგილობრივი)",
"Write a prompt suggestion (e.g. Who are you?)": "დაწერეთ მოკლე წინადადება (მაგ. ვინ ხარ?",
"Write a summary in 50 words that summarizes [topic or keyword].": "დაწერეთ რეზიუმე 50 სიტყვით, რომელიც აჯამებს [თემას ან საკვანძო სიტყვას].",
"You": "თქვენ",
"You're a helpful assistant.": "თქვენ სასარგებლო ასისტენტი ხართ.",
"You're now logged in.": "თქვენ შესული ხართ."
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'', '', '시간', '', '' 또는 만료 없음 '-1'",
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' 또는 만료 없음 '-1'",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "(예: `sh webui.sh --api`)",
"(latest)": "(latest)",
+10 -2
View File
@@ -43,6 +43,10 @@
"code": "ja-JP",
"title": "Japanese"
},
{
"code": "ka-GE",
"title": "Georgian"
},
{
"code": "ko-KR",
"title": "Korean"
@@ -52,13 +56,17 @@
"title": "Dutch (Netherlands)"
},
{
"code": "pt-PT",
"title": "Portuguese (Portugal)"
"code": "pl-PL",
"title": "Polish"
},
{
"code": "pt-BR",
"title": "Portuguese (Brazil)"
},
{
"code": "pt-PT",
"title": "Portuguese (Portugal)"
},
{
"code": "ru-RU",
"title": "Russian (Russia)"
+372
View File
@@ -0,0 +1,372 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' lub '-1' dla bez wygaśnięcia.",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "(np. `sh webui.sh --api`)",
"(latest)": "(najnowszy)",
"{{modelName}} is thinking...": "{{modelName}} myśli...",
"{{webUIName}} Backend Required": "Backend {{webUIName}} wymagane",
"a user": "użytkownik",
"About": "O nas",
"Account": "Konto",
"Action": "Akcja",
"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 Docs": "Dodaj dokumenty",
"Add Files": "Dodaj pliki",
"Add message": "Dodaj wiadomość",
"add tags": "dodaj tagi",
"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 Users": "Wszyscy użytkownicy",
"Allow": "Pozwól",
"Allow Chat Deletion": "Pozwól na usuwanie czatu",
"alphanumeric characters and hyphens": "znaki alfanumeryczne i myślniki",
"Already have an account?": "Masz już konto?",
"an assistant": "asystent",
"and": "i",
"API Base URL": "Podstawowy adres URL interfejsu API",
"API Key": "Klucz API",
"API RPM": "Pakiet API RPM",
"are allowed - Activate this command by typing": "są dozwolone - Aktywuj to polecenie, wpisując",
"Are you sure?": "Jesteś pewien?",
"Audio": "Dźwięk",
"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",
"Builder Mode": "Tryb budowniczego",
"Cancel": "Anuluj",
"Categories": "Kategorie",
"Change Password": "Zmień hasło",
"Chat": "Czat",
"Chat History": "Historia czatu",
"Chat History is off for this browser.": "Historia czatu jest wyłączona dla tej przeglądarki.",
"Chats": "Czaty",
"Check Again": "Sprawdź ponownie",
"Check for updates": "Sprawdź aktualizacje",
"Checking for updates...": "Sprawdzanie aktualizacji...",
"Choose a model before saving...": "Wybierz model przed zapisaniem...",
"Chunk Overlap": "Zachodzenie bloku",
"Chunk Params": "Parametry bloku",
"Chunk Size": "Rozmiar bloku",
"Click here for help.": "Kliknij tutaj, aby uzyskać pomoc.",
"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 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",
"Command": "Polecenie",
"Confirm Password": "Potwierdź hasło",
"Connections": "Połączenia",
"Content": "Zawartość",
"Context Length": "Długość kontekstu",
"Conversation Mode": "Tryb rozmowy",
"Copy last code block": "Skopiuj ostatni blok kodu",
"Copy last response": "Skopiuj ostatnią odpowiedź",
"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",
"Created at": "Utworzono o",
"Created by": "Utworzono przez",
"Current Model": "Bieżący model",
"Current Password": "Bieżące hasło",
"Custom": "Niestandardowy",
"Customize Ollama models for a specific purpose": "Dostosuj modele Ollama do określonego celu",
"Dark": "Ciemny",
"Database": "Baza danych",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "Domyślny",
"Default (Automatic1111)": "Domyślny (Automatic1111)",
"Default (Web API)": "Domyślny (Interfejs 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 a model": "Usuń model",
"Delete chat": "Usuń czat",
"Delete Chats": "Usuń czaty",
"Deleted {{deleteModelTag}}": "Usunięto {{deleteModelTag}}",
"Deleted {tagName}": "Usunięto {tagName}",
"Description": "Opis",
"Notifications": "Powiadomienia",
"Disabled": "Wyłączone",
"Discover a modelfile": "Odkryj plik modelu",
"Discover a prompt": "Odkryj prompt",
"Discover, download, and explore custom prompts": "Odkryj, pobierz i eksploruj niestandardowe prompty",
"Discover, download, and explore model presets": "Odkryj, pobierz i eksploruj ustawienia modeli",
"Display the username instead of You in the Chat": "Wyświetl nazwę użytkownika zamiast Ty w czacie",
"Document": "Dokument",
"Document Settings": "Ustawienia dokumentu",
"Documents": "Dokumenty",
"does not make any external connections, and your data stays securely on your locally hosted server.": "nie nawiązuje żadnych zewnętrznych połączeń, a Twoje dane pozostają bezpiecznie na Twoim lokalnie hostowanym serwerze.",
"Don't Allow": "Nie zezwalaj",
"Don't have an account?": "Nie masz konta?",
"Download as a File": "Pobierz jako plik",
"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 Doc": "Edytuj dokument",
"Edit User": "Edytuj użytkownika",
"Email": "Email",
"Embedding model: {{embedding_model}}": "Osadzony model: {{embedding_model}}",
"Enable Chat History": "Włącz historię czatu",
"Enable New Sign Ups": "Włącz nowe rejestracje",
"Enabled": "Włączone",
"Enter {{role}} message here": "Wprowadź wiadomość {{role}} tutaj",
"Enter API Key": "Wprowadź klucz API",
"Enter Chunk Overlap": "Wprowadź zakchodzenie bloku",
"Enter Chunk Size": "Wprowadź rozmiar bloku",
"Enter Image Size (e.g. 512x512)": "Wprowadź rozmiar obrazu (np. 512x512)",
"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)",
"Enter LiteLLM Model (litellm_params.model)": "Wprowadź model LiteLLM (litellm_params.model)",
"Enter Max Tokens (litellm_params.max_tokens)": "Wprowadź maksymalną liczbę tokenów (litellm_params.max_tokens)",
"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 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 Your Email": "Wprowadź swój adres email",
"Enter Your Full Name": "Wprowadź swoje imię i nazwisko",
"Enter Your Password": "Wprowadź swoje hasło",
"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 read clipboard contents": "Nie udało się odczytać zawartości schowka",
"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.",
"Focus chat input": "Skoncentruj na czacie",
"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",
"Hello, {{name}}": "Witaj, {{name}}",
"Hide": "Ukryj",
"Hide Additional Params": "Ukryj dodatkowe parametry",
"How can I help you today?": "Jak mogę Ci dzisiaj pomóc?",
"Image Generation (Experimental)": "Generowanie obrazu (eksperymentalne)",
"Image Generation Engine": "Silnik generowania obrazu",
"Image Settings": "Ustawienia obrazu",
"Images": "Obrazy",
"Import Chats": "Importuj rozmowy",
"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",
"Interface": "Interfejs",
"join our Discord for help.": "Dołącz do naszego Discorda po pomoc.",
"JSON": "JSON",
"JWT Expiration": "Wygaśnięcie JWT",
"JWT Token": "Token JWT",
"Keep Alive": "Zachowaj łączność",
"Keyboard shortcuts": "Skróty klawiszowe",
"Language": "Język",
"Light": "Jasny",
"Listening...": "Nasłuchiwanie...",
"LLMs can make mistakes. Verify important information.": "LLMy mogą popełniać błędy. Zweryfikuj ważne informacje.",
"Made by OpenWebUI Community": "Stworzone przez społeczność OpenWebUI",
"Make sure to enclose them with": "Upewnij się, że są one zamknięte w",
"Manage LiteLLM Models": "Zarządzaj modelami LiteLLM",
"Manage Models": "Zarządzaj modelami",
"Manage Ollama Models": "Zarządzaj modelami Ollama",
"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.",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"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 {{embedding_model}} update complete!": "Aktualizacja modelu {{embedding_model}} zakończona pomyślnie!",
"Model {{embedding_model}} update failed or not required!": "Model {{embedding_model}} aktualizacja nie powiodła się lub nie jest wymagana!",
"Model {{modelId}} not found": "Model {{modelId}} nie został znaleziony",
"Model {{modelName}} already exists.": "Model {{modelName}} już istnieje.",
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Wykryto ścieżkę systemu plików modelu. Wymagana jest krótka nazwa modelu do aktualizacji, nie można kontynuować.",
"Model Name": "Nazwa modelu",
"Model not selected": "Model nie został wybrany",
"Model Tag Name": "Nazwa tagu modelu",
"Model Whitelisting": "Whitelisting modelu",
"Model(s) Whitelisted": "Model(e) dodane do listy białej",
"Modelfile": "Plik modelu",
"Modelfile Advanced Settings": "Zaawansowane ustawienia pliku modelu",
"Modelfile Content": "Zawartość pliku modelu",
"Modelfiles": "Pliki modeli",
"Models": "Modele",
"My Documents": "Moje dokumenty",
"My Modelfiles": "Moje pliki modeli",
"My Prompts": "Moje prompty",
"Name": "Nazwa",
"Name Tag": "Etykieta nazwy",
"Name your modelfile": "Nadaj nazwę swojemu plikowi modelu",
"New Chat": "Nowy czat",
"New Password": "Nowe hasło",
"Not sure what to add?": "Nie wiesz, co dodać?",
"Not sure what to write? Switch to": "Nie wiesz, co napisać? Przełącz się na",
"Off": "Wyłączony",
"Okay, Let's Go!": "Okej, zaczynamy!",
"Ollama Base URL": "Adres bazowy URL Ollama",
"Ollama Version": "Wersja Ollama",
"On": "Włączony",
"Only": "Tylko",
"Only alphanumeric characters and hyphens are allowed in the command string.": "W poleceniu dozwolone są tylko znaki alfanumeryczne i myślniki.",
"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.",
"Open": "Otwórz",
"Open AI": "Open AI",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "Otwórz nowy czat",
"OpenAI API": "OpenAI API",
"OpenAI API Key": "Klucz API OpenAI",
"OpenAI API Key is required.": "Klucz API OpenAI jest wymagany.",
"or": "lub",
"Parameters": "Parametry",
"Password": "Hasło",
"PDF Extract Images (OCR)": "PDF Wyodrębnij obrazy (OCR)",
"pending": "oczekujące",
"Permission denied when accessing microphone: {{error}}": "Odmowa dostępu do mikrofonu: {{error}}",
"Playground": "Plac zabaw",
"Profile": "Profil",
"Prompt Content": "Zawartość prompta",
"Prompt suggestions": "Sugestie prompta",
"Prompts": "Prompty",
"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",
"Record voice": "Nagraj głos",
"Redirecting you to OpenWebUI Community": "Przekierowujemy Cię do społeczności OpenWebUI",
"Release Notes": "Notatki wydania",
"Repeat Last N": "Powtórz ostatnie N",
"Repeat Penalty": "Kara za powtórzenie",
"Request Mode": "Tryb żądania",
"Reset Vector Storage": "Resetuj przechowywanie wektorów",
"Response AutoCopy to Clipboard": "Automatyczne kopiowanie odpowiedzi do schowka",
"Role": "Rola",
"Rosé Pine": "Rosé Pine",
"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 Documents": "Szukaj dokumentów",
"Search Prompts": "Szukaj promptów",
"See readme.md for instructions": "Zajrzyj do readme.md po instrukcje",
"See what's new": "Zobacz co nowego",
"Seed": "Seed",
"Select a mode": "Wybierz tryb",
"Select a model": "Wybierz model",
"Select an Ollama instance": "Wybierz instancję Ollama",
"Send a Message": "Wyślij Wiadomość",
"Send message": "Wyślij wiadomość",
"Server connection verified": "Połączenie z serwerem zweryfikowane",
"Set as default": "Ustaw jako domyślne",
"Set Default Model": "Ustaw domyślny model",
"Set Image Size": "Ustaw rozmiar obrazu",
"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 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",
"sidebar": "Panel boczny",
"Sign in": "Zaloguj się",
"Sign Out": "Wyloguj się",
"Sign up": "Zarejestruj się",
"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ź",
"Success": "Sukces",
"Successfully updated.": "Pomyślnie zaktualizowano.",
"Sync All": "Synchronizuj wszystko",
"System": "System",
"System Prompt": "Prompt systemowy",
"Tags": "Tagi",
"Temperature": "Temperatura",
"Template": "Szablon",
"Text Completion": "Uzupełnienie tekstu",
"Text-to-Speech Engine": "Silnik tekstu na mowę",
"Tfs Z": "Tfs Z",
"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.",
"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 Auto-Generation": "Automatyczne generowanie tytułu",
"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.",
"Toggle settings": "Przełącz ustawienia",
"Toggle sidebar": "Przełącz panel boczny",
"Top K": "Najlepsze K",
"Top P": "Najlepsze P",
"Trouble accessing Ollama?": "Problemy z dostępem do Ollama?",
"TTS Settings": "Ustawienia TTS",
"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}}.",
"Understand that updating or changing your embedding model requires reset of the vector database and re-import of all documents. You have been warned!": "Zrozum, że aktualizacja lub zmiana modelu osadzania wymaga zresetowania bazy wektorów i ponownego zaimportowania wszystkich dokumentów. Zostałeś ostrzeżony!",
"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": "Aktualizacja",
"Update embedding model {{embedding_model}}": "Aktualizuj modelu osadzania {{embedding_model}}",
"Update password": "Aktualizacja hasła",
"Upload a GGUF model": "Prześlij model GGUF",
"Upload files": "Prześlij pliki",
"Upload Progress": "Postęp przesyłania",
"URL Mode": "Tryb adresu URL",
"Use '#' in the prompt input to load and select your documents.": "Użyj '#' w polu wprowadzania polecenia, aby załadować i wybrać swoje dokumenty.",
"Use Gravatar": "Użyj Gravatara",
"Use Initials": "Użyj inicjałów",
"user": "użytkownik",
"User Permissions": "Uprawnienia użytkownika",
"Users": "Użytkownicy",
"Utilize": "Wykorzystaj",
"Valid time units:": "Poprawne jednostki czasu:",
"variable": "zmienna",
"variable to have them replaced with clipboard content.": "zmienna która zostanie zastąpiona zawartością schowka.",
"Version": "Wersja",
"Web": "Sieć",
"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",
"Whats 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ń.",
"Whisper (Local)": "Whisper (Lokalnie)",
"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].",
"You": "Ty",
"You're a helpful assistant.": "Jesteś pomocnym asystentem.",
"You're now logged in.": "Jesteś teraz zalogowany."
}
+3 -3
View File
@@ -1,5 +1,5 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 's' ou '-1' para não expirar.",
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' ou '-1' para não expirar.",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "(por exemplo, `sh webui.sh --api`)",
"(latest)": "(mais recente)",
@@ -86,7 +86,7 @@
"Customize Ollama models for a specific purpose": "Personalize os modelos Ollama para um propósito específico",
"Dark": "Escuro",
"Database": "Banco de dados",
"DD/MM/YYYY HH:mm": "DD/MM/AAAA HH:mm",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "Padrão",
"Default (Automatic1111)": "Padrão (Automatic1111)",
"Default (Web API)": "Padrão (API Web)",
@@ -190,7 +190,7 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, AAAA",
"MMMM DD, YYYY": "DD/MM/YYYY",
"Model '{{modelName}}' has been successfully downloaded.": "O modelo '{{modelName}}' foi baixado com sucesso.",
"Model '{{modelTag}}' is already in queue for downloading.": "O modelo '{{modelTag}}' já está na fila para download.",
"Model {{modelId}} not found": "Modelo {{modelId}} não encontrado",
+3 -3
View File
@@ -1,5 +1,5 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 's' ou '-1' para nenhuma expiração.",
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' ou '-1' para nenhuma expiração.",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "(por exemplo, `sh webui.sh --api`)",
"(latest)": "(mais recente)",
@@ -86,7 +86,7 @@
"Customize Ollama models for a specific purpose": "Personalize os modelos Ollama para um propósito específico",
"Dark": "Escuro",
"Database": "Banco de dados",
"DD/MM/YYYY HH:mm": "DD/MM/AAAA HH:mm",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "Padrão",
"Default (Automatic1111)": "Padrão (Automatic1111)",
"Default (Web API)": "Padrão (API Web)",
@@ -190,7 +190,7 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, AAAA",
"MMMM DD, YYYY": "DD/MM/YYYY",
"Model '{{modelName}}' has been successfully downloaded.": "O modelo '{{modelName}}' foi baixado com sucesso.",
"Model '{{modelTag}}' is already in queue for downloading.": "O modelo '{{modelTag}}' já está na fila para download.",
"Model {{modelId}} not found": "Modelo {{modelId}} não encontrado",
+1 -1
View File
@@ -88,7 +88,7 @@
"Database": "База данных",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "По умолчанию",
"Default (Automatic1111)": "По умолчанию (Автоматический1111)",
"Default (Automatic1111)": "По умолчанию (Automatic1111)",
"Default (Web API)": "По умолчанию (Web API)",
"Default model updated": "Модель по умолчанию обновлена",
"Default Prompt Suggestions": "Предложения промтов по умолчанию",
+21 -6
View File
@@ -55,9 +55,9 @@
"Check for updates": "Kiểm tra cập nhật",
"Checking for updates...": "Đang kiểm tra cập nhật...",
"Choose a model before saving...": "Chọn mô hình trước khi lưu...",
"Chunk Overlap": "Kích thước chồng lấn (overlap)",
"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 thc khối (size)",
"Chunk Size": "Kích thước khối (size)",
"Click here for help.": "Bấm vào đây để được trợ giúp.",
"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.",
"Click here to select": "Bấm vào đây để chọn",
@@ -65,7 +65,7 @@
"click here.": "bấm vào đây.",
"Click on the user role button to change a user's role.": "Bấm vào nút trong cột VAI TRÒ để thay đổi quyền của người sử dụng.",
"Close": "Đóng",
"Collection": "Bộ sưu tập",
"Collection": "Tổng hợp mọi tài liệu",
"Command": "Lệnh",
"Confirm Password": "Xác nhận Mật khẩu",
"Connections": "Kết nối",
@@ -76,7 +76,7 @@
"Copy last response": "Sao chép phản hồi cuối cùng",
"Copying to clipboard was successful!": "Sao chép vào clipboard thành công!",
"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':": "Tạo một cụm từ súc tích, 3-5 từ làm tiêu đề cho truy vấn sau, tuân thủ nghiêm ngặt giới hạn 3-5 từ và tránh sử dụng từ 'tiêu đề':",
"Create a modelfile": "Tạo tệp mô tả mô hình",
"Create a modelfile": "Tạo tệp mô tả cho mô hình",
"Create Account": "Tạo Tài khoản",
"Created at": "Được tạo vào lúc",
"Created by": "Được tạo bởi",
@@ -347,7 +347,7 @@
"Valid time units:": "Đơn vị thời gian hợp lệ:",
"variable": "biến",
"variable to have them replaced with clipboard content.": "biến để có chúng được thay thế bằng nội dung clipboard.",
"Version": "Phiên bản",
"Version": "Version",
"Web": "Web",
"WebUI Add-ons": "Tiện ích WebUI",
"WebUI Settings": "Cài đặt WebUI",
@@ -359,5 +359,20 @@
"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].",
"You": "Bạn",
"You're a helpful assistant.": "Bạn là một trợ lý hữu ích.",
"You're now logged in.": "Bạn đã đăng nhập."
"You're now logged in.": "Bạn đã đăng nhập.",
"Accurate information": "Thông tin chính xác",
"Followed instructions perfectly": "Tuân theo chỉ dẫn một cách hoàn hảo",
"Showcased creativity": "Thể hiện sự sáng tạo",
"Positive attitude": "Thể hiện thái độ tích cực",
"Attention to detail": "Có sự chú ý đến chi tiết của vấn đề",
"Thorough explanation": "Giải thích kỹ lưỡng",
"Don't like the style": "Không thích phong cách trả lời",
"Not factually correct": "Không chính xác so với thực tế",
"Didn't fully follow instructions": "Không tuân theo chỉ dẫn một cách đầy đủ",
"Refused when it shouldn't have": "Từ chối trả lời mà nhẽ không nên làm vậy",
"Being lazy": "Lười biếng",
"Other": "Khác",
"Thanks for your feedback!": "Cám ơn bạn đã gửi phản hồi!",
"Tell us more:": "Hãy cho chúng tôi hiểu thêm về chất lượng của câu trả lời:",
"Feel free to add specific details": "Mô tả chi tiết về chất lượng của câu hỏi và phương án trả lời"
}
+66 -12
View File
@@ -20,9 +20,7 @@ export const getModels = async (token: string) => {
})
]);
models = models
.filter((models) => models)
.reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [{ name: 'hr' }] : [])), []);
models = models.filter((models) => models).reduce((a, e, i, arr) => a.concat(e), []);
return models;
};
@@ -187,7 +185,8 @@ export const generateInitialsImage = (name) => {
return canvas.toDataURL();
};
export const copyToClipboard = (text) => {
export const copyToClipboard = async (text) => {
let result = false;
if (!navigator.clipboard) {
const textArea = document.createElement('textarea');
textArea.value = text;
@@ -205,21 +204,27 @@ export const copyToClipboard = (text) => {
const successful = document.execCommand('copy');
const msg = successful ? 'successful' : 'unsuccessful';
console.log('Fallback: Copying text command was ' + msg);
result = true;
} catch (err) {
console.error('Fallback: Oops, unable to copy', err);
}
document.body.removeChild(textArea);
return;
return result;
}
navigator.clipboard.writeText(text).then(
function () {
result = await navigator.clipboard
.writeText(text)
.then(() => {
console.log('Async: Copying to clipboard was successful!');
},
function (err) {
console.error('Async: Could not copy text: ', err);
}
);
return true;
})
.catch((error) => {
console.error('Async: Could not copy text: ', error);
return false;
});
return result;
};
export const compareVersion = (latest, current) => {
@@ -467,3 +472,52 @@ export const blobToFile = (blob, fileName) => {
const file = new File([blob], fileName, { type: blob.type });
return file;
};
// promptTemplate replaces any occurrences of the following in the template with the prompt
// {{prompt}} will be replaced with the prompt
// {{prompt:start:<length>}} will be replaced with the first <length> characters of the prompt
// {{prompt:end:<length>}} will be replaced with the last <length> characters of the prompt
// Character length is used as we don't have the ability to tokenize the prompt
export const promptTemplate = (template: string, prompt: string) => {
template = template.replace(/{{prompt}}/g, prompt);
// Replace all instances of {{prompt:start:<length>}} with the first <length> characters of the prompt
const startRegex = /{{prompt:start:(\d+)}}/g;
let startMatch: RegExpMatchArray | null;
while ((startMatch = startRegex.exec(template)) !== null) {
const length = parseInt(startMatch[1]);
template = template.replace(startMatch[0], prompt.substring(0, length));
}
// Replace all instances of {{prompt:end:<length>}} with the last <length> characters of the prompt
const endRegex = /{{prompt:end:(\d+)}}/g;
let endMatch: RegExpMatchArray | null;
while ((endMatch = endRegex.exec(template)) !== null) {
const length = parseInt(endMatch[1]);
template = template.replace(endMatch[0], prompt.substring(prompt.length - length));
}
return template;
};
export const approximateToHumanReadable = (nanoseconds: number) => {
const seconds = Math.floor((nanoseconds / 1e9) % 60);
const minutes = Math.floor((nanoseconds / 6e10) % 60);
const hours = Math.floor((nanoseconds / 3.6e12) % 24);
const results: string[] = [];
if (seconds >= 0) {
results.push(`${seconds}s`);
}
if (minutes > 0) {
results.push(`${minutes}m`);
}
if (hours > 0) {
results.push(`${hours}h`);
}
return results.reverse().join(' ');
};
-4
View File
@@ -106,11 +106,7 @@
// IndexedDB Not Found
}
console.log();
await models.set(await getModels());
await tick();
await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
await modelfiles.set(await getModelfiles(localStorage.token));
+5 -6
View File
@@ -681,16 +681,18 @@
}
} else {
toast.error(
$i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, { provider: model })
$i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: model.name ?? model.id
})
);
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: model
provider: model.name ?? model.id
});
}
responseMessage.error = true;
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: model
provider: model.name ?? model.id
});
responseMessage.done = true;
messages = messages;
@@ -849,9 +851,6 @@
shareEnabled={messages.length > 0}
{chat}
{initNewChat}
{tags}
{addTag}
{deleteTag}
/>
<div class="flex flex-col flex-auto">
<div
+16 -9
View File
@@ -12,6 +12,7 @@
import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
import SettingsModal from '$lib/components/admin/SettingsModal.svelte';
import Pagination from '$lib/components/common/Pagination.svelte';
const i18n = getContext('i18n');
@@ -21,6 +22,8 @@
let search = '';
let selectedUser = null;
let page = 1;
let showSettingsModal = false;
let showEditUserModal = false;
@@ -159,15 +162,17 @@
</tr>
</thead>
<tbody>
{#each users.filter((user) => {
if (search === '') {
return true;
} else {
let name = user.name.toLowerCase();
const query = search.toLowerCase();
return name.includes(query);
}
}) as user}
{#each users
.filter((user) => {
if (search === '') {
return true;
} else {
let name = user.name.toLowerCase();
const query = search.toLowerCase();
return name.includes(query);
}
})
.slice((page - 1) * 20, page * 20) as user}
<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700 text-xs">
<td class="px-3 py-2 min-w-[7rem] w-28">
<button
@@ -270,6 +275,8 @@
<div class=" text-gray-500 text-xs mt-2 text-right">
{$i18n.t("Click on the user role button to change a user's role.")}
</div>
<Pagination bind:page count={users.length} />
</div>
</div>
</div>
+6 -9
View File
@@ -30,14 +30,12 @@
getTagsById,
updateChatById
} from '$lib/apis/chats';
import { queryCollection, queryDoc } from '$lib/apis/rag';
import { generateOpenAIChatCompletion, generateTitle } from '$lib/apis/openai';
import MessageInput from '$lib/components/chat/MessageInput.svelte';
import Messages from '$lib/components/chat/Messages.svelte';
import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
import Navbar from '$lib/components/layout/Navbar.svelte';
import { RAGTemplate } from '$lib/utils/rag';
import {
LITELLM_API_BASE_URL,
OPENAI_API_BASE_URL,
@@ -695,16 +693,18 @@
}
} else {
toast.error(
$i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, { provider: model })
$i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: model.name ?? model.id
})
);
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: model
provider: model.name ?? model.id
});
}
responseMessage.error = true;
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: model
provider: model.name ?? model.id
});
responseMessage.done = true;
messages = messages;
@@ -877,9 +877,6 @@
goto('/');
}}
{tags}
{addTag}
{deleteTag}
/>
<div class="flex flex-col flex-auto">
<div
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 11 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="347.666" y="139" width="44.3349" height="221.675" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M202.643 360.287C263.75 360.287 313.287 310.75 313.287 249.643C313.287 188.537 263.75 139 202.643 139C141.537 139 92 188.537 92 249.643C92 310.75 141.537 360.287 202.643 360.287ZM202.645 316.029C239.309 316.029 269.031 286.307 269.031 249.643C269.031 212.979 239.309 183.257 202.645 183.257C165.981 183.257 136.259 212.979 136.259 249.643C136.259 286.307 165.981 316.029 202.645 316.029Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 634 B