Compare commits

..
179 Commits
Author SHA1 Message Date
Timothy Jaeryang Baek 63533c9e3a Merge pull request #12524 from open-webui/dev
0.6.2
2025-04-06 20:41:10 -07:00
Timothy Jaeryang Baek 8e8bc3125e doc: changelog 2025-04-06 20:41:03 -07:00
Timothy Jaeryang Baek 2c083f58d9 chore: format 2025-04-06 20:36:24 -07:00
Timothy Jaeryang Baek 93962f01cc doc: changelog 2025-04-06 20:34:47 -07:00
Timothy Jaeryang Baek 40d019ff50 refac 2025-04-06 20:23:23 -07:00
Timothy Jaeryang Baek 3885ea4a15 Merge pull request #12526 from gaby/dependabot-update
chore: Dependabot update
2025-04-06 19:46:03 -07:00
Juan Calderon-Perez bd28173de6 Track backend/requirements.txt updates 2025-04-06 22:33:34 -04:00
Timothy Jaeryang Baek 06820e701c refac: citations modal 2025-04-06 19:14:24 -07:00
Timothy Jaeryang Baek 2be08f27ea revert 2025-04-06 19:12:08 -07:00
Timothy Jaeryang Baek fabe5876c0 Merge pull request #12525 from TiancongLx/dev
i18n: update zh-TW
2025-04-06 18:57:19 -07:00
Tiancong Li f1a4afeb8f i18n: update zh-TW 2025-04-07 09:28:20 +08:00
Timothy Jaeryang Baek 914eb49767 chore: include accelerate dependency 2025-04-06 17:44:05 -07:00
Timothy Jaeryang Baek a8bc0d65a0 chore: format 2025-04-06 17:36:16 -07:00
Timothy Jaeryang Baek cbe2056587 fix: audio file upload response issue 2025-04-06 17:31:50 -07:00
Timothy Jaeryang Baek 65ed76abe1 refac: embedding prefix 2025-04-06 17:17:24 -07:00
Timothy Jaeryang Baek bc17511a05 refac 2025-04-06 17:10:09 -07:00
Timothy Jaeryang Baek 3413dd8ae1 refac 2025-04-06 17:02:39 -07:00
Timothy Jaeryang Baek 03759e79a3 refac 2025-04-06 17:00:46 -07:00
Timothy Jaeryang Baek 20211a489f refac 2025-04-06 16:57:36 -07:00
Timothy Jaeryang Baek 5d2e725f81 refac 2025-04-06 16:53:30 -07:00
Timothy Jaeryang Baek 2722613cde refac 2025-04-06 16:43:58 -07:00
Timothy Jaeryang Baek f243e523a6 refac 2025-04-06 15:52:38 -07:00
Timothy Jaeryang Baek 155dbd5a66 refac 2025-04-06 15:45:48 -07:00
Timothy Jaeryang Baek 9825d03602 Merge pull request #12507 from Ithanil/fix_web_result_collection_source_ids
fix: fix web results all getting the same source id when using embedding and retrieval
2025-04-06 15:43:21 -07:00
Timothy Jaeryang Baek 66cd40f959 chore: format 2025-04-06 15:37:46 -07:00
Timothy Jaeryang Baek dc2f8ec8c6 refac 2025-04-06 15:37:27 -07:00
Timothy Jaeryang Baek bf118718b3 Merge pull request #12521 from gaby/optimize-workflow
chore: Only run GitHub workflows when needed
2025-04-06 15:24:20 -07:00
Timothy Jaeryang Baek 64a0b28dab refac 2025-04-06 15:23:40 -07:00
Timothy Jaeryang Baek 6751d68034 Merge pull request #12506 from Ithanil/fix_web_result_source_ids
fix: fix web results all getting the same source id when bypassing embedding and retrieval
2025-04-06 15:21:14 -07:00
Timothy Jaeryang Baek 6274f4d0d3 Merge pull request #12520 from gaby/bump-docker
chore: Bump Python Docker image to python3.12
2025-04-06 15:17:27 -07:00
Juan Calderon-Perez 624c5250b1 Optimize GitHub actions 2025-04-06 18:15:56 -04:00
Timothy Jaeryang Baek c1ff69721a refac 2025-04-06 15:14:57 -07:00
Timothy Jaeryang Baek 1e98ae7608 Merge pull request #12431 from gaby/fix-12237
feat: Allow making content optional when listing all files
2025-04-06 15:13:13 -07:00
Timothy Jaeryang Baek 1bde36e072 Merge pull request #12510 from newnol/patch-1
i18n language pack vietnamese
2025-04-06 15:12:09 -07:00
Juan Calderon-Perez 89e7913ff2 Bump Python base Docker image to 3.12 2025-04-06 18:10:38 -04:00
Timothy Jaeryang Baek 51bf9eaa88 Merge pull request #12493 from TiancongLx/dev
i18n: update zh-TW
2025-04-06 15:09:21 -07:00
Timothy Jaeryang Baek b8cf9b738d Merge pull request #12511 from panda44312/patch-11
i18n: zh-cn
2025-04-06 15:09:13 -07:00
Timothy Jaeryang Baek 635c08a5ab Merge pull request #12517 from Ithanil/only_keep_retrieved_urls
fix: only keep URLs as sources for which the content was actually retrieved
2025-04-06 15:08:47 -07:00
Timothy Jaeryang Baek 6323b9f4f0 Merge pull request #12515 from gaby/fix-dependabot
chore: Fix dependabot configuration
2025-04-06 13:41:18 -07:00
Juan Calderon-Perez 94453839ba Update index.ts 2025-04-06 16:02:16 -04:00
Jan Kessler a506a1a61e only keep URLs as sources for which the content could actually be retrieved 2025-04-06 20:31:12 +02:00
Juan Calderon-Perez d266490862 Fix dependabot configuration 2025-04-06 13:58:17 -04:00
Juan Calderon-Perez ff1d454bdd Fix formatting 2025-04-06 13:32:51 -04:00
Juan Calderon-Perez eda3eba084 Merge branch 'open-webui:main' into fix-12237 2025-04-06 13:30:37 -04:00
Panda 60c6e00d9f i18n: fix 2025-04-06 16:47:06 +02:00
Panda 246c839ee6 i18n: zh-cn 2025-04-06 16:42:09 +02:00
Ngô Tấn Tài 2994c58fd9 Update translation.json 2025-04-06 21:26:58 +07:00
Jan Kessler 4476060044 fix web results all getting the same source id when using embedding and retrieval 2025-04-06 15:51:05 +02:00
Jan Kessler 2729d8aab4 fix web results all getting the same source id when bypassing embedding and retrieval 2025-04-06 13:37:42 +02:00
Tiancong Li 26e1bfc772 Merge remote-tracking branch 'upstream/dev' into dev
* upstream/dev:
  refac
  Fallback from desc to summary to placeholder
2025-04-06 10:33:01 +08:00
Timothy Jaeryang Baek 9ea6cea0ba refac 2025-04-05 14:31:22 -07:00
Timothy Jaeryang Baek 71625663c0 Merge pull request #12496 from dan-sullivan/fix-12479/add-desc-to-openapi-tools
fix: openapi tools should pass description to model not summary
2025-04-05 15:30:19 -06:00
Tiancong Li 33451cf66d i18n: update zh-TW 2025-04-06 04:46:31 +08:00
Timothy Jaeryang Baek 41aca84913 Merge pull request #12491 from open-webui/dev
refac
2025-04-05 10:24:32 -07:00
Timothy Jaeryang Baek 5296ee0f71 refac 2025-04-05 11:24:05 -06:00
Timothy Jaeryang Baek da94835165 Merge pull request #12373 from open-webui/dev
0.6.1
2025-04-05 10:15:32 -07:00
Timothy Jaeryang Baek 13dfca4e76 chore: bump 2025-04-05 11:13:30 -06:00
Timothy Jaeryang Baek d5063f47f2 doc: changelog 2025-04-05 11:11:37 -06:00
Timothy Jaeryang Baek ef787e4a79 Merge pull request #12486 from FabioPolito24/text-file-handling-docling
fix: text file handling with docling
2025-04-05 09:55:51 -07:00
Timothy Jaeryang Baek 48d690c55d Merge pull request #12481 from gaby/generic-errors
fix: Improve auth error messages
2025-04-05 09:47:52 -07:00
Timothy Jaeryang Baek 69c68df34b Merge pull request #12480 from Kylapaallikko/dev
i18n: Update Finnish Translation
2025-04-05 09:47:39 -07:00
Timothy Jaeryang Baek b3110ca291 Merge branch 'dev' into dev 2025-04-05 09:47:30 -07:00
Fabio Polito cd0a1b4852 fix: fix for text file handling with docling 2025-04-05 16:44:08 +00:00
Timothy Jaeryang Baek 32f309b41d chore: format 2025-04-05 10:42:21 -06:00
Timothy Jaeryang Baek d23f757c1f refac 2025-04-05 10:41:46 -06:00
Juan Calderon-Perez 324550423c Fix formatting issues 2025-04-05 10:03:24 -04:00
Juan Calderon-Perez b4277c7d2e Make auth error messages generic 2025-04-05 09:56:11 -04:00
Kylapaallikko 84788b73ce Update translation.json 2025-04-05 16:37:54 +03:00
dan-sullivan 09344bbb80 Fallback from desc to summary to placeholder 2025-04-05 14:18:14 +01:00
Timothy Jaeryang Baek fe4f760b4a refac 2025-04-05 05:57:25 -06:00
Timothy Jaeryang Baek 807b208418 refac 2025-04-05 05:55:56 -06:00
Timothy Jaeryang Baek ae484e8edf refac 2025-04-05 05:45:32 -06:00
Timothy Jaeryang Baek 56dc7c5640 refac 2025-04-05 05:38:46 -06:00
Timothy Jaeryang Baek e570a98bf7 refac: substandard codebase overhauled 2025-04-05 05:31:22 -06:00
Timothy Jaeryang Baek 93bb77ede3 refac 2025-04-05 05:03:15 -06:00
Timothy Jaeryang Baek 66db2e1515 refac: tools removed UNNECESSARY CODE 2025-04-05 04:59:11 -06:00
Timothy Jaeryang Baek c9e9ce931b refac 2025-04-05 04:49:07 -06:00
Timothy Jaeryang Baek 9747a0e1f1 refac: tool servers 2025-04-05 04:40:01 -06:00
Timothy Jaeryang Baek 61778c8f71 Merge pull request #12478 from theredcat/main
Improve FR translation
2025-04-05 03:22:54 -07:00
Timothy Jaeryang Baek 579aca65bb Merge pull request #12477 from Ithanil/improved_rag_template
fix: more coherent default RAG template
2025-04-05 03:22:41 -07:00
Timothy Jaeryang Baek ee44383c83 refac 2025-04-05 04:12:57 -06:00
Timothy Jaeryang Baek 0c0505e1cd refac 2025-04-05 04:05:52 -06:00
Nathan DELHAYE 1b8557ce86 i18n: improve fr translation 2025-04-05 12:04:12 +02:00
Jan Kessler e0da6003f1 tweak default rag template to be more coherent and improve consistency with citations 2025-04-05 11:40:48 +02:00
Timothy Jaeryang Baek 0f310b3509 Merge pull request #12476 from Phlogi/dev-hybrid-search
fix: Avoid secondary error when collection is empty in parallel hybrid search
2025-04-05 02:10:29 -07:00
Phlogi 8cf8121812 Update utils.py
Avoid running any tasks for collections that failed to fetch data (have assigned None)
2025-04-05 10:41:21 +02:00
Timothy Jaeryang Baek a2f2203325 Merge pull request #12426 from silentoplayz/fix-feedback
fix: Conditionally hide export buttons
2025-04-05 01:30:51 -07:00
Timothy Jaeryang Baek 193a927aba fix: temp chat 2025-04-05 02:16:35 -06:00
Timothy Jaeryang Baek 4ad10f0c6e chore: format 2025-04-05 01:31:45 -06:00
Timothy Jaeryang Baek 5e06b6d3f4 refac: styling 2025-04-05 01:24:32 -06:00
Timothy Jaeryang Baek cb94a87cb0 refac: tools styling 2025-04-05 01:05:47 -06:00
Timothy Jaeryang Baek 793aa307ef Merge pull request #12433 from gaby/fix-allowed-endpoints
fix: API Key Endpoint Restrictions for Dynamic Paths
2025-04-04 08:24:10 -07:00
Timothy Jaeryang Baek 7e46cc4562 Merge pull request #12439 from narutopden/i18n-add-Tibetan-translation
i18n: add Tibetan language
2025-04-04 08:20:25 -07:00
Timothy Jaeryang Baek b612af29e9 refac 2025-04-04 09:17:19 -06:00
Timothy Jaeryang Baek feaf434d4e refac: input prompt 2025-04-04 09:11:54 -06:00
Timothy Jaeryang Baek 77fa11098a enh: openapi tool server custom path 2025-04-04 08:31:13 -06:00
Timothy Jaeryang Baek a32bb850fa Merge pull request #12450 from mahenning/fix-missing-parameter-rag
Fix: Add missing parameter for query_doc_with_hybrid_search
2025-04-04 07:00:16 -07:00
Timothy Jaeryang Baek 64b68b3641 Merge pull request #12447 from floriankick/fix-openai-api-empty-message-after-tool-call
fix: make content parameter optional in OpenAI chat completion API endpoint
2025-04-04 06:59:12 -07:00
Marko Henning 3b2b6e183d Added missing parameter for query_doc_with_hybrid_search. 2025-04-04 15:30:57 +02:00
Juan Calderon-Perez 138e985d66 Rename field to include_content 2025-04-04 08:38:24 -04:00
Florian Kick ec3435de00 make content parameter optional in OpenAI chat completion API endpoint 2025-04-04 13:30:51 +02:00
narutopden a25d87607e i18n: add Tibetan language 2025-04-04 14:55:49 +08:00
Juan Calderon-Perez 1c57e3e02c Fix API_KEY_ALLOWED_ENDPOINTS 2025-04-03 23:52:10 -04:00
Silentoplayz 41b9ff9e00 fix: funcs, models, prompts, & tools conditional export button 2025-04-03 23:33:30 -04:00
Juan Calderon-Perez 6d5cb6b04e Add query param to remove content from GET /api/v1/files 2025-04-03 23:02:24 -04:00
silentoplayz 1366822f99 Update Feedbacks.svelte
fix
2025-04-04 02:38:03 +00:00
Silentoplayz 2b0d9c26eb fix: don't show export button if nothing to export 2025-04-03 22:20:40 -04:00
Timothy Jaeryang Baek be20e6dec0 refac: message edit 2025-04-03 13:43:45 -06:00
Timothy Jaeryang Baek a1f3300767 fix: tls cert requirement 2025-04-03 10:01:03 -07:00
Timothy Jaeryang Baek dfdde8bb1f refac 2025-04-03 09:40:26 -07:00
Timothy Jaeryang Baek ba77a72925 refac: styling 2025-04-03 01:00:40 -07:00
Timothy Jaeryang Baek bcf0a8791e refac: styling 2025-04-03 01:00:04 -07:00
Timothy Jaeryang Baek 100f5a57e7 refac 2025-04-02 23:56:15 -07:00
Timothy Jaeryang Baek 911321878f refac 2025-04-02 23:49:42 -07:00
Timothy Jaeryang Baek faa68fcdaa enh: image tool response 2025-04-02 23:46:39 -07:00
Timothy Jaeryang Baek 561b2c0b1a refac: styling 2025-04-02 21:23:45 -07:00
Timothy Jaeryang Baek 436e3ff7de refac 2025-04-02 20:42:23 -07:00
Timothy Jaeryang Baek c0711ba0c9 refac 2025-04-02 20:07:09 -07:00
Timothy Jaeryang Baek 5c5160c294 refac: remove None params 2025-04-02 19:56:39 -07:00
Timothy Jaeryang Baek 2277566ce1 fix: tool server api key not being sent 2025-04-02 19:52:12 -07:00
Timothy Jaeryang Baek 959995c715 refac: use selected model for merge response 2025-04-02 19:33:20 -07:00
Timothy Jaeryang Baek 7a1e10f3a7 refac: rm OAUTH_USE_PICTURE_CLAIM 2025-04-02 19:23:24 -07:00
Timothy Jaeryang Baek b15bf0dc70 refac 2025-04-02 19:17:26 -07:00
Timothy Jaeryang Baek 506950b03a Merge pull request #12376 from MushroomLamp-COB/main
feat: Add option to disable OAuth profile pictures
2025-04-02 19:16:36 -07:00
Timothy Jaeryang Baek 94353458a5 refac: settings ui styling 2025-04-02 19:01:39 -07:00
Timothy Jaeryang Baek 7eea95a48c feat: direct tools user permissions 2025-04-02 18:36:03 -07:00
Timothy Jaeryang Baek 94bf49440d enh: unload hybrid model if set to False 2025-04-02 18:15:14 -07:00
Timothy Jaeryang Baek 0644abe402 fix: admin folder deletion issue 2025-04-02 17:55:24 -07:00
Timothy Jaeryang Baek 9036945361 Merge pull request #12355 from silentoplayz/logging-fix
fix: update datetime and logging imports
2025-04-02 17:44:30 -07:00
silentoplayz dd5bafe8af Update env.py
Revert
2025-04-03 00:36:16 +00:00
CityOfBunbury 548c7f17d7 Added OAUTH_USE_PICTURE_CLAIM env var
Added OAUTH_USE_PICTURE_CLAIM to config.py

Added check to oauth.py on OAUTH_USE_PICTURE_CLAIM, to decide whether to user the profile picture in the claim or the default user.png
2025-04-03 08:24:14 +08:00
Timothy Jaeryang Baek bb7f4d408a Merge pull request #12364 from silentoplayz/archive-chats-option
feat: Added Archived Chats option in Chats settings section
2025-04-02 17:12:34 -07:00
Timothy Jaeryang Baek 6ee30042e7 Merge pull request #12353 from SadmL/dev
[i18n] Russian localization update
2025-04-02 15:47:44 -07:00
Timothy Jaeryang Baek 0554bbb1cb Merge pull request #12307 from paddy313/feature/mistral_ocr
feat: Added support for Mistral OCR for Content Extraction
2025-04-02 15:47:33 -07:00
Timothy Jaeryang Baek 517a57b7ca Merge pull request #12331 from panda44312/patch-10
i18n: zh-cn fix
2025-04-02 15:46:42 -07:00
Silentoplayz ee68c9ea55 Update Chats.svelte
Added Archived Chats option in Chats settings section
2025-04-02 14:25:03 -04:00
Silentoplayz d65471c420 fix
my dev environment works again!
2025-04-02 11:28:45 -04:00
SadmL de8f94b008 [i18n] Russian localization update
Fixed typos and translated some lines
2025-04-02 18:13:52 +03:00
Patrick Wachter 0ac00b9256 refactor: update import path for MistralLoader 2025-04-02 13:56:10 +02:00
Timothy Jaeryang Baek d0db4756a6 Merge pull request #12310 from landerrosette/fix_filter_priority
fix: Filter function’s priority valve has no effect
2025-04-02 04:41:00 -07:00
Panda adaa614986 fix 2025-04-02 10:35:05 +02:00
Timothy Jaeryang Baek 5ab783001d Merge pull request #12304 from OriginalSimon/main
18n: Update Ukrainian translation
2025-04-01 16:12:59 -07:00
Timothy Jaeryang Baek 2a163996f9 Merge pull request #12294 from Xelaph/dev
**i18n**: Update Dutch translations
2025-04-01 16:12:48 -07:00
Timothy Jaeryang Baek 1b37bdcec8 Merge pull request #12278 from Saidoua/main
**i18n** Standard Arabic Translation
2025-04-01 16:11:54 -07:00
Timothy Jaeryang Baek 39b0c06630 Merge pull request #12299 from lowlyocean/rpi_onnxruntime
fix: Allow v0.6.0 to run on Raspberry Pi
2025-04-01 16:02:05 -07:00
landerrosette 2b7dd6e399 refactor: standardize filter valve retrieval logic 2025-04-02 02:58:02 +08:00
Patrick Wachter c5a8d2f857 refactor: update MistralLoader documentation and adjust parameters for signed URL retrieval 2025-04-01 20:14:34 +02:00
Patrick Wachter 93d7702e8c refactor: move MistralLoader to a separate module and just use the requests package instead of mistralai 2025-04-01 20:14:34 +02:00
Simon 0447d90951 Update translation.json 2025-04-01 20:01:08 +02:00
Daniel Nowak 8f8c34471e Pin onnxruntime to 1.20.1 to address SIGILL on certain arm64 hosts 2025-04-01 12:03:09 -04:00
Alex 883ad55f5c Update translation.json 2025-04-01 16:12:31 +02:00
Alex 82e5a6410c Update translation.json 2025-04-01 16:09:17 +02:00
Alex 88b93241db Update translation.json 2025-04-01 15:20:04 +02:00
Alex b652b8e5f3 Update translation.json 2025-04-01 14:25:04 +02:00
Patrick Wachter 1ac6879268 Add Mistral OCR integration and configuration support 2025-04-01 14:24:33 +02:00
Said Ouhdachi 825becceb0 Arabic Translation 2025-04-01 13:10:59 +02:00
Timothy Jaeryang Baek fffa793d6d Merge pull request #12270 from TiancongLx/dev
i18n: update zh-TW
2025-04-01 02:29:00 -07:00
Timothy Jaeryang Baek 289d49dbe7 Merge pull request #12269 from panda44312/patch-9
i18n: zh-cn
2025-04-01 02:28:51 -07:00
Tiancong Li fa72c277db i18n: update zh-TW 2025-04-01 17:10:32 +08:00
Panda 8799ff9575 fix 2025-04-01 10:52:05 +02:00
Panda 85c8a9bd68 i18n: zh-cn 2025-04-01 10:47:52 +02:00
Timothy Jaeryang Baek 15f6e3af12 Merge pull request #12259 from weisser-dev/dev
[FEAT] adjust Translations for temporary chat
2025-04-01 00:59:27 -07:00
Timothy Jaeryang Baek c79fa4214f Merge pull request #12262 from lukasz-pekala/fb/fixes-to-polish-translation
i18n: fixes to polish translation
2025-04-01 00:59:08 -07:00
Łukasz Pękala 29d5745335 fix: update Polish translation for "Capture" to improve accuracy 2025-04-01 09:14:29 +02:00
Erik 70bb0562f3 Merge branch 'open-webui:dev' into dev 2025-04-01 09:09:33 +02:00
Erik c8210d4043 upated like in PR discussed 2025-04-01 09:06:15 +02:00
Łukasz Pękala 9f470a4ff5 fix: update Polish translations to sound more natural 2025-04-01 08:53:12 +02:00
Timothy Jaeryang Baek cea5c1a63b Merge pull request #12254 from open-webui/dependabot/pip/backend/dev/einops-0.8.1
build(deps): bump einops from 0.8.0 to 0.8.1 in /backend
2025-03-31 23:45:23 -07:00
Timothy Jaeryang Baek 4ce05ad279 Merge pull request #12255 from open-webui/dependabot/pip/backend/dev/pypandoc-1.15
build(deps): bump pypandoc from 1.13 to 1.15 in /backend
2025-03-31 23:45:15 -07:00
Timothy Jaeryang Baek f3c2614b6c Merge pull request #12253 from open-webui/dependabot/pip/backend/dev/python-multipart-0.0.20
build(deps): bump python-multipart from 0.0.18 to 0.0.20 in /backend
2025-03-31 23:44:40 -07:00
Timothy Jaeryang Baek 79cca06e0c Merge pull request #12260 from weisser-dev/feat-default-value-for-enforce-temp-chat
[improvement] default permission for new groups is false for enforce …
2025-03-31 23:43:08 -07:00
Łukasz Pękala b60beb6ff6 fix: improve Polish translations for clarity and accuracy 2025-04-01 08:39:36 +02:00
Erik 295c7eb4c7 [improvement] default permission for new groups is false for enforce temp chat 2025-04-01 08:27:29 +02:00
Łukasz Pękala 361584b4fd fix: update Polish translations for clarity and accuracy 2025-04-01 08:26:21 +02:00
Erik abba7c128c [FEAT]-Adjust Translations for temporary chat 2025-04-01 08:21:03 +02:00
Timothy Jaeryang Baek e0ec2cdeb0 refac: $user 2025-03-31 20:32:12 -07:00
dependabot[bot] bb0e11bb97 build(deps): bump pypandoc from 1.13 to 1.15 in /backend
Bumps [pypandoc](https://github.com/JessicaTegner/pypandoc) from 1.13 to 1.15.
- [Release notes](https://github.com/JessicaTegner/pypandoc/releases)
- [Changelog](https://github.com/JessicaTegner/pypandoc/blob/master/release.md)
- [Commits](https://github.com/JessicaTegner/pypandoc/compare/v1.13...v1.15)

---
updated-dependencies:
- dependency-name: pypandoc
  dependency-version: '1.15'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-01 02:22:16 +00:00
dependabot[bot] fc5c8dbe3f build(deps): bump einops from 0.8.0 to 0.8.1 in /backend
Bumps [einops](https://github.com/arogozhnikov/einops) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/arogozhnikov/einops/releases)
- [Commits](https://github.com/arogozhnikov/einops/compare/v0.8.0...v0.8.1)

---
updated-dependencies:
- dependency-name: einops
  dependency-version: 0.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-01 02:22:13 +00:00
dependabot[bot] 1d30d8a13a build(deps): bump python-multipart from 0.0.18 to 0.0.20 in /backend
Bumps [python-multipart](https://github.com/Kludex/python-multipart) from 0.0.18 to 0.0.20.
- [Release notes](https://github.com/Kludex/python-multipart/releases)
- [Changelog](https://github.com/Kludex/python-multipart/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Kludex/python-multipart/compare/0.0.18...0.0.20)

---
updated-dependencies:
- dependency-name: python-multipart
  dependency-version: 0.0.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-01 02:22:11 +00:00
144 changed files with 7200 additions and 2218 deletions
+14
View File
@@ -1,12 +1,26 @@
version: 2
updates:
- package-ecosystem: uv
directory: '/'
schedule:
interval: monthly
target-branch: 'dev'
- package-ecosystem: pip
directory: '/backend'
schedule:
interval: monthly
target-branch: 'dev'
- package-ecosystem: npm
directory: '/'
schedule:
interval: monthly
target-branch: 'dev'
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
# Check for updates to GitHub Actions every week
interval: monthly
target-branch: 'dev'
+12 -2
View File
@@ -5,10 +5,18 @@ on:
branches:
- main
- dev
paths:
- 'backend/**'
- 'pyproject.toml'
- 'uv.lock'
pull_request:
branches:
- main
- dev
paths:
- 'backend/**'
- 'pyproject.toml'
- 'uv.lock'
jobs:
build:
@@ -17,7 +25,9 @@ jobs:
strategy:
matrix:
python-version: [3.11]
python-version:
- 3.11.x
- 3.12.x
steps:
- uses: actions/checkout@v4
@@ -25,7 +35,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
python-version: '${{ matrix.python-version }}'
- name: Install dependencies
run: |
+9 -1
View File
@@ -5,10 +5,18 @@ on:
branches:
- main
- dev
paths-ignore:
- 'backend/**'
- 'pyproject.toml'
- 'uv.lock'
pull_request:
branches:
- main
- dev
paths-ignore:
- 'backend/**'
- 'pyproject.toml'
- 'uv.lock'
jobs:
build:
@@ -21,7 +29,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22' # Or specify any other version you want to use
node-version: '22'
- name: Install Dependencies
run: npm install
+40
View File
@@ -5,6 +5,46 @@ 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.6.2] - 2025-04-06
### Added
- 🌍 **Improved Global Language Support**: Expanded and refined translations across multiple languages to enhance clarity and consistency for international users.
### Fixed
- 🛠️ **Accurate Tool Descriptions from OpenAPI Servers**: External tools now use full endpoint descriptions instead of summaries when generating tool specifications—helping AI models understand tool purpose more precisely and choose the right tool more accurately in tool workflows.
- 🔧 **Precise Web Results Source Attribution**: Fixed a key issue where all web search results showed the same source ID—now each result gets its correct and distinct source, ensuring accurate citations and traceability.
- 🔍 **Clean Web Search Retrieval**: Web search now retains only results from URLs where real content was successfully fetched—improving accuracy and removing empty or broken links from citations.
- 🎵 **Audio File Upload Response Restored**: Resolved an issue where uploading audio files did not return valid responses, restoring smooth file handling for transcription and audio-based workflows.
### Changed
- 🧰 **General Backend Refactoring**: Multiple behind-the-scenes improvements streamline backend performance, reduce complexity, and ensure a more stable, maintainable system overall—making everything smoother without changing your workflow.
## [0.6.1] - 2025-04-05
### Added
- 🛠️ **Global Tool Servers Configuration**: Admins can now centrally configure global external tool servers from Admin Settings > Tools, allowing seamless sharing of tool integrations across all users without manual setup per user.
- 🔐 **Direct Tool Usage Permission for Users**: Introduced a new user-level permission toggle that grants non-admin users access to direct external tools, empowering broader team collaboration while maintaining control.
- 🧠 **Mistral OCR Content Extraction Support**: Added native support for Mistral OCR as a high-accuracy document loader, drastically improving text extraction from scanned documents in RAG workflows.
- 🖼️ **Tools Indicator UI Redesign**: Enhanced message input now smartly displays both built-in and external tools via a unified dropdown, making it simpler and more intuitive to activate tools during conversations.
- 📄 **RAG Prompt Improved and More Coherent**: Default RAG system prompt has been revised to be more clear and citation-focused—admins can leave the template field empty to use this new gold-standard prompt.
- 🧰 **Performance & Developer Improvements**: Major internal restructuring of several tool-related components, simplifying styling and merging external/internal handling logic, resulting in better maintainability and performance.
- 🌍 **Improved Translations**: Updated translations for Tibetan, Polish, Chinese (Simplified & Traditional), Arabic, Russian, Ukrainian, Dutch, Finnish, and French to improve clarity and consistency across the interface.
### Fixed
- 🔑 **External Tool Server API Key Bug Resolved**: Fixed a critical issue where authentication headers were not being sent when calling tools from external OpenAPI tool servers, ensuring full security and smooth tool operations.
- 🚫 **Conditional Export Button Visibility**: UI now gracefully hides export buttons when there's nothing to export in models, prompts, tools, or functions, improving visual clarity and reducing confusion.
- 🧪 **Hybrid Search Failure Recovery**: Resolved edge case in parallel hybrid search where empty or unindexed collections caused backend crashes—these are now cleanly skipped to ensure system stability.
- 📂 **Admin Folder Deletion Fix**: Addressed an issue where folders created in the admin workspace couldn't be deleted, restoring full organizational flexibility for admins.
- 🔐 **Improved Generic Error Feedback on Login**: Authentication errors now show simplified, non-revealing messages for privacy and improved UX, especially with federated logins.
- 📝 **Tool Message with Images Improved**: Enhanced how tool-generated messages with image outputs are shown in chat, making them more readable and consistent with the overall UI design.
- ⚙️ **Auto-Exclusion for Broken RAG Collections**: Auto-skips document collections that fail to fetch data or return "None", preventing silent errors and streamlining retrieval workflows.
- 📝 **Docling Text File Handling Fix**: Fixed file parsing inconsistency that broke docling-based RAG functionality for certain plain text files, ensuring wider file compatibility.
## [0.6.0] - 2025-03-31
### Added
+31 -7
View File
@@ -331,12 +331,14 @@ JWT_EXPIRES_IN = PersistentConfig(
# OAuth config
####################################
ENABLE_OAUTH_SIGNUP = PersistentConfig(
"ENABLE_OAUTH_SIGNUP",
"oauth.enable_signup",
os.environ.get("ENABLE_OAUTH_SIGNUP", "False").lower() == "true",
)
OAUTH_MERGE_ACCOUNTS_BY_EMAIL = PersistentConfig(
"OAUTH_MERGE_ACCOUNTS_BY_EMAIL",
"oauth.merge_accounts_by_email",
@@ -466,6 +468,7 @@ OAUTH_USERNAME_CLAIM = PersistentConfig(
os.environ.get("OAUTH_USERNAME_CLAIM", "name"),
)
OAUTH_PICTURE_CLAIM = PersistentConfig(
"OAUTH_PICTURE_CLAIM",
"oauth.oidc.avatar_claim",
@@ -878,6 +881,17 @@ except Exception:
pass
OPENAI_API_BASE_URL = "https://api.openai.com/v1"
####################################
# TOOL_SERVERS
####################################
TOOL_SERVER_CONNECTIONS = PersistentConfig(
"TOOL_SERVER_CONNECTIONS",
"tool_server.connections",
[],
)
####################################
# WEBUI
####################################
@@ -1034,6 +1048,11 @@ USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED = (
== "true"
)
USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS = (
os.environ.get("USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS", "False").lower()
== "true"
)
USER_PERMISSIONS_FEATURES_WEB_SEARCH = (
os.environ.get("USER_PERMISSIONS_FEATURES_WEB_SEARCH", "True").lower() == "true"
)
@@ -1071,6 +1090,7 @@ DEFAULT_USER_PERMISSIONS = {
"temporary_enforced": USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED,
},
"features": {
"direct_tool_servers": USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS,
"web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
"image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
"code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
@@ -1727,6 +1747,11 @@ DOCUMENT_INTELLIGENCE_KEY = PersistentConfig(
os.getenv("DOCUMENT_INTELLIGENCE_KEY", ""),
)
MISTRAL_OCR_API_KEY = PersistentConfig(
"MISTRAL_OCR_API_KEY",
"rag.mistral_ocr_api_key",
os.getenv("MISTRAL_OCR_API_KEY", ""),
)
BYPASS_EMBEDDING_AND_RETRIEVAL = PersistentConfig(
"BYPASS_EMBEDDING_AND_RETRIEVAL",
@@ -1875,7 +1900,7 @@ CHUNK_OVERLAP = PersistentConfig(
)
DEFAULT_RAG_TEMPLATE = """### Task:
Respond to the user query using the provided context, incorporating inline citations in the format [source_id] **only when the <source_id> tag is explicitly provided** in the context.
Respond to the user query using the provided context, incorporating inline citations in the format [id] **only when the <source> tag includes an explicit id attribute** (e.g., <source id="1">).
### Guidelines:
- If you don't know the answer, clearly state that.
@@ -1883,18 +1908,17 @@ Respond to the user query using the provided context, incorporating inline citat
- Respond in the same language as the user's query.
- If the context is unreadable or of poor quality, inform the user and provide the best possible answer.
- If the answer isn't present in the context but you possess the knowledge, explain this to the user and provide the answer using your own understanding.
- **Only include inline citations using [source_id] (e.g., [1], [2]) when a `<source_id>` tag is explicitly provided in the context.**
- Do not cite if the <source_id> tag is not provided in the context.
- **Only include inline citations using [id] (e.g., [1], [2]) when the <source> tag includes an id attribute.**
- Do not cite if the <source> tag does not contain an id attribute.
- Do not use XML tags in your response.
- Ensure citations are concise and directly related to the information provided.
### Example of Citation:
If the user asks about a specific topic and the information is found in "whitepaper.pdf" with a provided <source_id>, the response should include the citation like so:
* "According to the study, the proposed method increases efficiency by 20% [whitepaper.pdf]."
If no <source_id> is present, the response should omit the citation.
If the user asks about a specific topic and the information is found in a source with a provided id attribute, the response should include the citation like in the following example:
* "According to the study, the proposed method increases efficiency by 20% [1]."
### Output:
Provide a clear and direct response to the user's query, including inline citations in the format [source_id] only when the <source_id> tag is present in the context.
Provide a clear and direct response to the user's query, including inline citations in the format [id] only when the <source> tag with id attribute is present in the context.
<context>
{{CONTEXT}}
+18
View File
@@ -105,6 +105,8 @@ from open_webui.config import (
OPENAI_API_CONFIGS,
# Direct Connections
ENABLE_DIRECT_CONNECTIONS,
# Tool Server Configs
TOOL_SERVER_CONNECTIONS,
# Code Execution
ENABLE_CODE_EXECUTION,
CODE_EXECUTION_ENGINE,
@@ -191,6 +193,7 @@ from open_webui.config import (
DOCLING_SERVER_URL,
DOCUMENT_INTELLIGENCE_ENDPOINT,
DOCUMENT_INTELLIGENCE_KEY,
MISTRAL_OCR_API_KEY,
RAG_TOP_K,
RAG_TOP_K_RERANKER,
RAG_TEXT_SPLITTER,
@@ -355,6 +358,7 @@ from open_webui.utils.access_control import has_access
from open_webui.utils.auth import (
get_license_data,
get_http_authorization_cred,
decode_token,
get_admin_user,
get_verified_user,
@@ -477,6 +481,15 @@ app.state.config.OPENAI_API_CONFIGS = OPENAI_API_CONFIGS
app.state.OPENAI_MODELS = {}
########################################
#
# TOOL SERVERS
#
########################################
app.state.config.TOOL_SERVER_CONNECTIONS = TOOL_SERVER_CONNECTIONS
app.state.TOOL_SERVERS = []
########################################
#
# DIRECT CONNECTIONS
@@ -582,6 +595,7 @@ app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL
app.state.config.DOCLING_SERVER_URL = DOCLING_SERVER_URL
app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT = DOCUMENT_INTELLIGENCE_ENDPOINT
app.state.config.DOCUMENT_INTELLIGENCE_KEY = DOCUMENT_INTELLIGENCE_KEY
app.state.config.MISTRAL_OCR_API_KEY = MISTRAL_OCR_API_KEY
app.state.config.TEXT_SPLITTER = RAG_TEXT_SPLITTER
app.state.config.TIKTOKEN_ENCODING_NAME = TIKTOKEN_ENCODING_NAME
@@ -862,6 +876,10 @@ async def commit_session_after_request(request: Request, call_next):
@app.middleware("http")
async def check_url(request: Request, call_next):
start_time = int(time.time())
request.state.token = get_http_authorization_cred(
request.headers.get("Authorization")
)
request.state.enable_api_key = app.state.config.ENABLE_API_KEY
response = await call_next(request)
process_time = int(time.time()) - start_time
+27 -11
View File
@@ -20,6 +20,9 @@ from langchain_community.document_loaders import (
YoutubeLoader,
)
from langchain_core.documents import Document
from open_webui.retrieval.loaders.mistral import MistralLoader
from open_webui.env import SRC_LOG_LEVELS, GLOBAL_LOG_LEVEL
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
@@ -181,13 +184,16 @@ class Loader:
for doc in docs
]
def _is_text_file(self, file_ext: str, file_content_type: str) -> bool:
return file_ext in known_source_ext or (
file_content_type and file_content_type.find("text/") >= 0
)
def _get_loader(self, filename: str, file_content_type: str, file_path: str):
file_ext = filename.split(".")[-1].lower()
if self.engine == "tika" and self.kwargs.get("TIKA_SERVER_URL"):
if file_ext in known_source_ext or (
file_content_type and file_content_type.find("text/") >= 0
):
if self._is_text_file(file_ext, file_content_type):
loader = TextLoader(file_path, autodetect_encoding=True)
else:
loader = TikaLoader(
@@ -196,11 +202,14 @@ class Loader:
mime_type=file_content_type,
)
elif self.engine == "docling" and self.kwargs.get("DOCLING_SERVER_URL"):
loader = DoclingLoader(
url=self.kwargs.get("DOCLING_SERVER_URL"),
file_path=file_path,
mime_type=file_content_type,
)
if self._is_text_file(file_ext, file_content_type):
loader = TextLoader(file_path, autodetect_encoding=True)
else:
loader = DoclingLoader(
url=self.kwargs.get("DOCLING_SERVER_URL"),
file_path=file_path,
mime_type=file_content_type,
)
elif (
self.engine == "document_intelligence"
and self.kwargs.get("DOCUMENT_INTELLIGENCE_ENDPOINT") != ""
@@ -222,6 +231,15 @@ class Loader:
api_endpoint=self.kwargs.get("DOCUMENT_INTELLIGENCE_ENDPOINT"),
api_key=self.kwargs.get("DOCUMENT_INTELLIGENCE_KEY"),
)
elif (
self.engine == "mistral_ocr"
and self.kwargs.get("MISTRAL_OCR_API_KEY") != ""
and file_ext
in ["pdf"] # Mistral OCR currently only supports PDF and images
):
loader = MistralLoader(
api_key=self.kwargs.get("MISTRAL_OCR_API_KEY"), file_path=file_path
)
else:
if file_ext == "pdf":
loader = PyPDFLoader(
@@ -257,9 +275,7 @@ class Loader:
loader = UnstructuredPowerPointLoader(file_path)
elif file_ext == "msg":
loader = OutlookMessageLoader(file_path)
elif file_ext in known_source_ext or (
file_content_type and file_content_type.find("text/") >= 0
):
elif self._is_text_file(file_ext, file_content_type):
loader = TextLoader(file_path, autodetect_encoding=True)
else:
loader = TextLoader(file_path, autodetect_encoding=True)
@@ -0,0 +1,225 @@
import requests
import logging
import os
import sys
from typing import List, Dict, Any
from langchain_core.documents import Document
from open_webui.env import SRC_LOG_LEVELS, GLOBAL_LOG_LEVEL
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
class MistralLoader:
"""
Loads documents by processing them through the Mistral OCR API.
"""
BASE_API_URL = "https://api.mistral.ai/v1"
def __init__(self, api_key: str, file_path: str):
"""
Initializes the loader.
Args:
api_key: Your Mistral API key.
file_path: The local path to the PDF file to process.
"""
if not api_key:
raise ValueError("API key cannot be empty.")
if not os.path.exists(file_path):
raise FileNotFoundError(f"File not found at {file_path}")
self.api_key = api_key
self.file_path = file_path
self.headers = {"Authorization": f"Bearer {self.api_key}"}
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
"""Checks response status and returns JSON content."""
try:
response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
# Handle potential empty responses for certain successful requests (e.g., DELETE)
if response.status_code == 204 or not response.content:
return {} # Return empty dict if no content
return response.json()
except requests.exceptions.HTTPError as http_err:
log.error(f"HTTP error occurred: {http_err} - Response: {response.text}")
raise
except requests.exceptions.RequestException as req_err:
log.error(f"Request exception occurred: {req_err}")
raise
except ValueError as json_err: # Includes JSONDecodeError
log.error(f"JSON decode error: {json_err} - Response: {response.text}")
raise # Re-raise after logging
def _upload_file(self) -> str:
"""Uploads the file to Mistral for OCR processing."""
log.info("Uploading file to Mistral API")
url = f"{self.BASE_API_URL}/files"
file_name = os.path.basename(self.file_path)
try:
with open(self.file_path, "rb") as f:
files = {"file": (file_name, f, "application/pdf")}
data = {"purpose": "ocr"}
upload_headers = self.headers.copy() # Avoid modifying self.headers
response = requests.post(
url, headers=upload_headers, files=files, data=data
)
response_data = self._handle_response(response)
file_id = response_data.get("id")
if not file_id:
raise ValueError("File ID not found in upload response.")
log.info(f"File uploaded successfully. File ID: {file_id}")
return file_id
except Exception as e:
log.error(f"Failed to upload file: {e}")
raise
def _get_signed_url(self, file_id: str) -> str:
"""Retrieves a temporary signed URL for the uploaded file."""
log.info(f"Getting signed URL for file ID: {file_id}")
url = f"{self.BASE_API_URL}/files/{file_id}/url"
params = {"expiry": 1}
signed_url_headers = {**self.headers, "Accept": "application/json"}
try:
response = requests.get(url, headers=signed_url_headers, params=params)
response_data = self._handle_response(response)
signed_url = response_data.get("url")
if not signed_url:
raise ValueError("Signed URL not found in response.")
log.info("Signed URL received.")
return signed_url
except Exception as e:
log.error(f"Failed to get signed URL: {e}")
raise
def _process_ocr(self, signed_url: str) -> Dict[str, Any]:
"""Sends the signed URL to the OCR endpoint for processing."""
log.info("Processing OCR via Mistral API")
url = f"{self.BASE_API_URL}/ocr"
ocr_headers = {
**self.headers,
"Content-Type": "application/json",
"Accept": "application/json",
}
payload = {
"model": "mistral-ocr-latest",
"document": {
"type": "document_url",
"document_url": signed_url,
},
"include_image_base64": False,
}
try:
response = requests.post(url, headers=ocr_headers, json=payload)
ocr_response = self._handle_response(response)
log.info("OCR processing done.")
log.debug("OCR response: %s", ocr_response)
return ocr_response
except Exception as e:
log.error(f"Failed during OCR processing: {e}")
raise
def _delete_file(self, file_id: str) -> None:
"""Deletes the file from Mistral storage."""
log.info(f"Deleting uploaded file ID: {file_id}")
url = f"{self.BASE_API_URL}/files/{file_id}"
# No specific Accept header needed, default or Authorization is usually sufficient
try:
response = requests.delete(url, headers=self.headers)
delete_response = self._handle_response(
response
) # Check status, ignore response body unless needed
log.info(
f"File deleted successfully: {delete_response}"
) # Log the response if available
except Exception as e:
# Log error but don't necessarily halt execution if deletion fails
log.error(f"Failed to delete file ID {file_id}: {e}")
# Depending on requirements, you might choose to raise the error here
def load(self) -> List[Document]:
"""
Executes the full OCR workflow: upload, get URL, process OCR, delete file.
Returns:
A list of Document objects, one for each page processed.
"""
file_id = None
try:
# 1. Upload file
file_id = self._upload_file()
# 2. Get Signed URL
signed_url = self._get_signed_url(file_id)
# 3. Process OCR
ocr_response = self._process_ocr(signed_url)
# 4. Process results
pages_data = ocr_response.get("pages")
if not pages_data:
log.warning("No pages found in OCR response.")
return [Document(page_content="No text content found", metadata={})]
documents = []
total_pages = len(pages_data)
for page_data in pages_data:
page_content = page_data.get("markdown")
page_index = page_data.get("index") # API uses 0-based index
if page_content is not None and page_index is not None:
documents.append(
Document(
page_content=page_content,
metadata={
"page": page_index, # 0-based index from API
"page_label": page_index
+ 1, # 1-based label for convenience
"total_pages": total_pages,
# Add other relevant metadata from page_data if available/needed
# e.g., page_data.get('width'), page_data.get('height')
},
)
)
else:
log.warning(
f"Skipping page due to missing 'markdown' or 'index'. Data: {page_data}"
)
if not documents:
# Case where pages existed but none had valid markdown/index
log.warning(
"OCR response contained pages, but none had valid content/index."
)
return [
Document(
page_content="No text content found in valid pages", metadata={}
)
]
return documents
except Exception as e:
log.error(f"An error occurred during the loading process: {e}")
# Return an empty list or a specific error document on failure
return [Document(page_content=f"Error during processing: {e}", metadata={})]
finally:
# 5. Delete file (attempt even if prior steps failed after upload)
if file_id:
try:
self._delete_file(file_id)
except Exception as del_e:
# Log deletion error, but don't overwrite original error if one occurred
log.error(
f"Cleanup error: Could not delete file ID {file_id}. Reason: {del_e}"
)
+7 -4
View File
@@ -320,10 +320,13 @@ def query_collection_with_hybrid_search(
log.exception(f"Error when querying the collection with hybrid_search: {e}")
return None, e
# Prepare tasks for all collections and queries
# Avoid running any tasks for collections that failed to fetch data (have assigned None)
tasks = [
(collection_name, query)
for collection_name in collection_names
for query in queries
(cn, q)
for cn in collection_names
if collection_results[cn] is not None
for q in queries
]
with ThreadPoolExecutor() as executor:
@@ -354,7 +357,7 @@ def get_embedding_function(
):
if embedding_engine == "":
return lambda query, prefix=None, user=None: embedding_function.encode(
query, prompt=prefix if prefix else None
query, **({"prompt": prefix} if prefix else {})
).tolist()
elif embedding_engine in ["ollama", "openai"]:
func = lambda query, prefix=None, user=None: generate_embeddings(
+22 -18
View File
@@ -194,8 +194,8 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
ciphers=LDAP_CIPHERS,
)
except Exception as e:
log.error(f"An error occurred on TLS: {str(e)}")
raise HTTPException(400, detail=str(e))
log.error(f"TLS configuration error: {str(e)}")
raise HTTPException(400, detail="Failed to configure TLS for LDAP connection.")
try:
server = Server(
@@ -232,7 +232,7 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
username = str(entry[f"{LDAP_ATTRIBUTE_FOR_USERNAME}"]).lower()
email = str(entry[f"{LDAP_ATTRIBUTE_FOR_MAIL}"])
if not email or email == "" or email == "[]":
raise HTTPException(400, f"User {form_data.user} does not have email.")
raise HTTPException(400, "User does not have a valid email address.")
else:
email = email.lower()
@@ -248,7 +248,7 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
authentication="SIMPLE",
)
if not connection_user.bind():
raise HTTPException(400, f"Authentication failed for {form_data.user}")
raise HTTPException(400, "Authentication failed.")
user = Users.get_user_by_email(email)
if not user:
@@ -276,7 +276,10 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
except HTTPException:
raise
except Exception as err:
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
log.error(f"LDAP user creation error: {str(err)}")
raise HTTPException(
500, detail="Internal error occurred during LDAP user creation."
)
user = Auths.authenticate_user_by_trusted_header(email)
@@ -312,12 +315,10 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
else:
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
else:
raise HTTPException(
400,
f"User {form_data.user} does not match the record. Search result: {str(entry[f'{LDAP_ATTRIBUTE_FOR_USERNAME}'])}",
)
raise HTTPException(400, "User record mismatch.")
except Exception as e:
raise HTTPException(400, detail=str(e))
log.error(f"LDAP authentication error: {str(e)}")
raise HTTPException(400, detail="LDAP authentication failed.")
############################
@@ -519,7 +520,8 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
else:
raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
except Exception as err:
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
log.error(f"Signup error: {str(err)}")
raise HTTPException(500, detail="An internal error occurred during signup.")
@router.get("/signout")
@@ -547,7 +549,11 @@ async def signout(request: Request, response: Response):
detail="Failed to fetch OpenID configuration",
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
log.error(f"OpenID signout error: {str(e)}")
raise HTTPException(
status_code=500,
detail="Failed to sign out from the OpenID provider.",
)
return {"status": True}
@@ -591,7 +597,10 @@ async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)):
else:
raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
except Exception as err:
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
log.error(f"Add user error: {str(err)}")
raise HTTPException(
500, detail="An internal error occurred while adding the user."
)
############################
@@ -764,11 +773,6 @@ async def update_ldap_server(
if not value:
raise HTTPException(400, detail=f"Required field {key} is empty")
if form_data.use_tls and not form_data.certificate_path:
raise HTTPException(
400, detail="TLS is enabled but certificate file path is missing"
)
request.app.state.config.LDAP_SERVER_LABEL = form_data.label
request.app.state.config.LDAP_SERVER_HOST = form_data.host
request.app.state.config.LDAP_SERVER_PORT = form_data.port
+73 -2
View File
@@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, Request
from pydantic import BaseModel
from fastapi import APIRouter, Depends, Request, HTTPException
from pydantic import BaseModel, ConfigDict
from typing import Optional
@@ -7,6 +7,8 @@ from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.config import get_config, save_config
from open_webui.config import BannerModel
from open_webui.utils.tools import get_tool_server_data, get_tool_servers_data
router = APIRouter()
@@ -66,6 +68,75 @@ async def set_direct_connections_config(
}
############################
# ToolServers Config
############################
class ToolServerConnection(BaseModel):
url: str
path: str
auth_type: Optional[str]
key: Optional[str]
config: Optional[dict]
model_config = ConfigDict(extra="allow")
class ToolServersConfigForm(BaseModel):
TOOL_SERVER_CONNECTIONS: list[ToolServerConnection]
@router.get("/tool_servers", response_model=ToolServersConfigForm)
async def get_tool_servers_config(request: Request, user=Depends(get_admin_user)):
return {
"TOOL_SERVER_CONNECTIONS": request.app.state.config.TOOL_SERVER_CONNECTIONS,
}
@router.post("/tool_servers", response_model=ToolServersConfigForm)
async def set_tool_servers_config(
request: Request,
form_data: ToolServersConfigForm,
user=Depends(get_admin_user),
):
request.app.state.config.TOOL_SERVER_CONNECTIONS = [
connection.model_dump() for connection in form_data.TOOL_SERVER_CONNECTIONS
]
request.app.state.TOOL_SERVERS = await get_tool_servers_data(
request.app.state.config.TOOL_SERVER_CONNECTIONS
)
return {
"TOOL_SERVER_CONNECTIONS": request.app.state.config.TOOL_SERVER_CONNECTIONS,
}
@router.post("/tool_servers/verify")
async def verify_tool_servers_config(
request: Request, form_data: ToolServerConnection, user=Depends(get_admin_user)
):
"""
Verify the connection to the tool server.
"""
try:
token = None
if form_data.auth_type == "bearer":
token = form_data.key
elif form_data.auth_type == "session":
token = request.state.token.credentials
url = f"{form_data.url}/{form_data.path}"
return await get_tool_server_data(token, url)
except Exception as e:
raise HTTPException(
status_code=400,
detail=f"Failed to connect to the tool server: {str(e)}",
)
############################
# CodeInterpreterConfig
############################
+9 -2
View File
@@ -122,6 +122,7 @@ def upload_file(
]:
file_path = Storage.get_file(file_path)
result = transcribe(request, file_path)
process_file(
request,
ProcessFileForm(file_id=id, content=result.get("text", "")),
@@ -129,7 +130,8 @@ def upload_file(
)
elif file.content_type not in ["image/png", "image/jpeg", "image/gif"]:
process_file(request, ProcessFileForm(file_id=id), user=user)
file_item = Files.get_file_by_id(id=id)
file_item = Files.get_file_by_id(id=id)
except Exception as e:
log.exception(e)
log.error(f"Error processing file: {file_item.id}")
@@ -162,11 +164,16 @@ def upload_file(
@router.get("/", response_model=list[FileModelResponse])
async def list_files(user=Depends(get_verified_user)):
async def list_files(user=Depends(get_verified_user), content: bool = Query(True)):
if user.role == "admin":
files = Files.get_files()
else:
files = Files.get_files_by_user_id(user.id)
if not content:
for file in files:
del file.data["content"]
return files
+2 -1
View File
@@ -236,7 +236,8 @@ async def delete_folder_by_id(
chat_delete_permission = has_permission(
user.id, "chat.delete", request.app.state.config.USER_PERMISSIONS
)
if not chat_delete_permission:
if user.role != "admin" and not chat_delete_permission:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+1 -1
View File
@@ -1197,7 +1197,7 @@ class OpenAIChatMessageContent(BaseModel):
class OpenAIChatMessage(BaseModel):
role: str
content: Union[str, list[OpenAIChatMessageContent]]
content: Union[Optional[str], list[OpenAIChatMessageContent]]
model_config = ConfigDict(extra="allow")
+49 -20
View File
@@ -124,7 +124,7 @@ def get_ef(
def get_rf(
reranking_model: str,
reranking_model: Optional[str] = None,
auto_update: bool = False,
):
rf = None
@@ -150,8 +150,8 @@ def get_rf(
device=DEVICE_TYPE,
trust_remote_code=RAG_RERANKING_MODEL_TRUST_REMOTE_CODE,
)
except:
log.error("CrossEncoder error")
except Exception as e:
log.error(f"CrossEncoder: {e}")
raise Exception(ERROR_MESSAGES.DEFAULT("CrossEncoder error"))
return rf
@@ -174,7 +174,7 @@ class ProcessUrlForm(CollectionNameForm):
url: str
class SearchForm(CollectionNameForm):
class SearchForm(BaseModel):
query: str
@@ -364,6 +364,9 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
"endpoint": request.app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT,
"key": request.app.state.config.DOCUMENT_INTELLIGENCE_KEY,
},
"mistral_ocr_config": {
"api_key": request.app.state.config.MISTRAL_OCR_API_KEY,
},
},
"chunk": {
"text_splitter": request.app.state.config.TEXT_SPLITTER,
@@ -427,11 +430,16 @@ class DocumentIntelligenceConfigForm(BaseModel):
key: str
class MistralOCRConfigForm(BaseModel):
api_key: str
class ContentExtractionConfig(BaseModel):
engine: str = ""
tika_server_url: Optional[str] = None
docling_server_url: Optional[str] = None
document_intelligence_config: Optional[DocumentIntelligenceConfigForm] = None
mistral_ocr_config: Optional[MistralOCRConfigForm] = None
class ChunkParamUpdateForm(BaseModel):
@@ -553,6 +561,10 @@ async def update_rag_config(
request.app.state.config.DOCUMENT_INTELLIGENCE_KEY = (
form_data.content_extraction.document_intelligence_config.key
)
if form_data.content_extraction.mistral_ocr_config is not None:
request.app.state.config.MISTRAL_OCR_API_KEY = (
form_data.content_extraction.mistral_ocr_config.api_key
)
if form_data.chunk is not None:
request.app.state.config.TEXT_SPLITTER = form_data.chunk.text_splitter
@@ -659,6 +671,9 @@ async def update_rag_config(
"endpoint": request.app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT,
"key": request.app.state.config.DOCUMENT_INTELLIGENCE_KEY,
},
"mistral_ocr_config": {
"api_key": request.app.state.config.MISTRAL_OCR_API_KEY,
},
},
"chunk": {
"text_splitter": request.app.state.config.TEXT_SPLITTER,
@@ -747,6 +762,9 @@ async def update_query_settings(
form_data.hybrid if form_data.hybrid else False
)
if not request.app.state.config.ENABLE_RAG_HYBRID_SEARCH:
request.app.state.rf = None
return {
"status": True,
"template": request.app.state.config.RAG_TEMPLATE,
@@ -940,7 +958,7 @@ def process_file(
if form_data.content:
# Update the content in the file
# Usage: /files/{file_id}/data/content/update
# Usage: /files/{file_id}/data/content/update, /files/ (audio file upload pipeline)
try:
# /files/{file_id}/data/content/update
@@ -1007,6 +1025,7 @@ def process_file(
PDF_EXTRACT_IMAGES=request.app.state.config.PDF_EXTRACT_IMAGES,
DOCUMENT_INTELLIGENCE_ENDPOINT=request.app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT,
DOCUMENT_INTELLIGENCE_KEY=request.app.state.config.DOCUMENT_INTELLIGENCE_KEY,
MISTRAL_OCR_API_KEY=request.app.state.config.MISTRAL_OCR_API_KEY,
)
docs = loader.load(
file.filename, file.meta.get("content_type"), file_path
@@ -1445,12 +1464,6 @@ async def process_web_search(
log.debug(f"web_results: {web_results}")
try:
collection_name = form_data.collection_name
if collection_name == "" or collection_name is None:
collection_name = f"web-search-{calculate_sha256_string(form_data.query)}"[
:63
]
urls = [result.link for result in web_results]
loader = get_web_loader(
urls,
@@ -1459,6 +1472,9 @@ async def process_web_search(
trust_env=request.app.state.config.RAG_WEB_SEARCH_TRUST_ENV,
)
docs = await loader.aload()
urls = [
doc.metadata["source"] for doc in docs
] # only keep URLs which could be retrieved
if request.app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL:
return {
@@ -1475,18 +1491,26 @@ async def process_web_search(
"loaded_count": len(docs),
}
else:
await run_in_threadpool(
save_docs_to_vector_db,
request,
docs,
collection_name,
overwrite=True,
user=user,
)
collection_names = []
for doc_idx, doc in enumerate(docs):
collection_name = f"web-search-{calculate_sha256_string(form_data.query + '-' + urls[doc_idx])}"[
:63
]
collection_names.append(collection_name)
await run_in_threadpool(
save_docs_to_vector_db,
request,
[doc],
collection_name,
overwrite=True,
user=user,
)
return {
"status": True,
"collection_name": collection_name,
"collection_names": collection_names,
"filenames": urls,
"loaded_count": len(docs),
}
@@ -1515,8 +1539,13 @@ def query_doc_handler(
):
try:
if request.app.state.config.ENABLE_RAG_HYBRID_SEARCH:
collection_results = {}
collection_results[form_data.collection_name] = VECTOR_DB_CLIENT.get(
collection_name=form_data.collection_name
)
return query_doc_with_hybrid_search(
collection_name=form_data.collection_name,
collection_result=collection_results[form_data.collection_name],
query=form_data.query,
embedding_function=lambda query, prefix: request.app.state.EMBEDDING_FUNCTION(
query, prefix=prefix, user=user
+1 -12
View File
@@ -653,17 +653,6 @@ async def generate_moa_response(
detail="Model not found",
)
# Check if the user has a custom task model
# If the user has a custom task model, use that model
task_model_id = get_task_model_id(
model_id,
request.app.state.config.TASK_MODEL,
request.app.state.config.TASK_MODEL_EXTERNAL,
models,
)
log.debug(f"generating MOA model {task_model_id} for user {user.email} ")
template = DEFAULT_MOA_GENERATION_PROMPT_TEMPLATE
content = moa_response_generation_template(
@@ -673,7 +662,7 @@ async def generate_moa_response(
)
payload = {
"model": task_model_id,
"model": model_id,
"messages": [{"role": "user", "content": content}],
"stream": form_data.get("stream", False),
"metadata": {
+48 -5
View File
@@ -1,6 +1,7 @@
import logging
from pathlib import Path
from typing import Optional
import time
from open_webui.models.tools import (
ToolForm,
@@ -18,6 +19,8 @@ from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_access, has_permission
from open_webui.env import SRC_LOG_LEVELS
from open_webui.utils.tools import get_tool_servers_data
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MAIN"])
@@ -30,11 +33,51 @@ router = APIRouter()
@router.get("/", response_model=list[ToolUserResponse])
async def get_tools(user=Depends(get_verified_user)):
if user.role == "admin":
tools = Tools.get_tools()
else:
tools = Tools.get_tools_by_user_id(user.id, "read")
async def get_tools(request: Request, user=Depends(get_verified_user)):
if not request.app.state.TOOL_SERVERS:
# If the tool servers are not set, we need to set them
# This is done only once when the server starts
# This is done to avoid loading the tool servers every time
request.app.state.TOOL_SERVERS = await get_tool_servers_data(
request.app.state.config.TOOL_SERVER_CONNECTIONS
)
tools = Tools.get_tools()
for idx, server in enumerate(request.app.state.TOOL_SERVERS):
tools.append(
ToolUserResponse(
**{
"id": f"server:{server['idx']}",
"user_id": f"server:{server['idx']}",
"name": server["openapi"]
.get("info", {})
.get("title", "Tool Server"),
"meta": {
"description": server["openapi"]
.get("info", {})
.get("description", ""),
},
"access_control": request.app.state.config.TOOL_SERVER_CONNECTIONS[
idx
]
.get("config", {})
.get("access_control", None),
"updated_at": int(time.time()),
"created_at": int(time.time()),
}
)
)
if user.role != "admin":
tools = [
tool
for tool in tools
if tool.user_id == user.id
or has_access(user.id, "read", tool.access_control)
]
return tools
+1
View File
@@ -93,6 +93,7 @@ class ChatPermissions(BaseModel):
class FeaturesPermissions(BaseModel):
direct_tool_servers: bool = False
web_search: bool = True
image_generation: bool = True
code_interpreter: bool = True
+13 -4
View File
@@ -8,7 +8,9 @@ import requests
import os
from datetime import UTC, datetime, timedelta
from datetime import datetime, timedelta
import pytz
from pytz import UTC
from typing import Optional, Union, List, Dict
from open_webui.models.users import Users
@@ -141,12 +143,14 @@ def create_api_key():
return f"sk-{key}"
def get_http_authorization_cred(auth_header: str):
def get_http_authorization_cred(auth_header: Optional[str]):
if not auth_header:
return None
try:
scheme, credentials = auth_header.split(" ")
return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
except Exception:
raise ValueError(ERROR_MESSAGES.INVALID_TOKEN)
return None
def get_current_user(
@@ -180,7 +184,12 @@ def get_current_user(
).split(",")
]
if request.url.path not in allowed_paths:
# Check if the request path matches any allowed endpoint.
if not any(
request.url.path == allowed
or request.url.path.startswith(allowed + "/")
for allowed in allowed_paths
):
raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.API_KEY_NOT_ALLOWED
)
+3 -3
View File
@@ -12,9 +12,9 @@ log.setLevel(SRC_LOG_LEVELS["MAIN"])
def get_sorted_filter_ids(model: dict):
def get_priority(function_id):
function = Functions.get_function_by_id(function_id)
if function is not None and hasattr(function, "valves"):
# TODO: Fix FunctionModel to include vavles
return (function.valves if function.valves else {}).get("priority", 0)
if function is not None:
valves = Functions.get_function_valves_by_id(function_id)
return valves.get("priority", 0) if valves else 0
return 0
filter_ids = [function.id for function in Functions.get_global_filter_functions()]
+85 -33
View File
@@ -221,13 +221,23 @@ async def chat_completion_tools_handler(
except Exception as e:
tool_result = str(e)
tool_result_files = []
if isinstance(tool_result, list):
for item in tool_result:
# check if string
if isinstance(item, str) and item.startswith("data:"):
tool_result_files.append(item)
tool_result.remove(item)
if isinstance(tool_result, dict) or isinstance(tool_result, list):
tool_result = json.dumps(tool_result, indent=2)
if isinstance(tool_result, str):
tool = tools[tool_function_name]
tool_id = tool.get("toolkit_id", "")
if tool.get("citation", False) or tool.get("direct", False):
tool_id = tool.get("tool_id", "")
if tool.get("metadata", {}).get("citation", False) or tool.get(
"direct", False
):
sources.append(
{
@@ -238,7 +248,7 @@ async def chat_completion_tools_handler(
else f"{tool_function_name}"
),
},
"document": [tool_result],
"document": [tool_result, *tool_result_files],
"metadata": [
{
"source": (
@@ -254,7 +264,7 @@ async def chat_completion_tools_handler(
sources.append(
{
"source": {},
"document": [tool_result],
"document": [tool_result, *tool_result_files],
"metadata": [
{
"source": (
@@ -267,7 +277,11 @@ async def chat_completion_tools_handler(
}
)
if tools[tool_function_name].get("file_handler", False):
if (
tools[tool_function_name]
.get("metadata", {})
.get("file_handler", False)
):
skip_files = True
# check if "tool_calls" in result
@@ -385,24 +399,44 @@ async def chat_web_search_handler(
all_results.append(results)
files = form_data.get("files", [])
if results.get("collection_name"):
files.append(
{
"collection_name": results["collection_name"],
"name": searchQuery,
"type": "web_search",
"urls": results["filenames"],
}
)
if results.get("collection_names"):
for col_idx, collection_name in enumerate(
results.get("collection_names")
):
files.append(
{
"collection_name": collection_name,
"name": searchQuery,
"type": "web_search",
"urls": [results["filenames"][col_idx]],
}
)
elif results.get("docs"):
files.append(
{
"docs": results.get("docs", []),
"name": searchQuery,
"type": "web_search",
"urls": results["filenames"],
}
)
# Invoked when bypass embedding and retrieval is set to True
docs = results["docs"]
if len(docs) == len(results["filenames"]):
# the number of docs and filenames (urls) should be the same
for doc_idx, doc in enumerate(docs):
files.append(
{
"docs": [doc],
"name": searchQuery,
"type": "web_search",
"urls": [results["filenames"][doc_idx]],
}
)
else:
# edge case when the number of docs and filenames (urls) are not the same
# this should not happen, but if it does, we will just append the docs
files.append(
{
"docs": results.get("docs", []),
"name": searchQuery,
"type": "web_search",
"urls": results["filenames"],
}
)
form_data["files"] = files
except Exception as e:
@@ -625,27 +659,28 @@ def apply_params_to_form_data(form_data, model):
if "keep_alive" in params:
form_data["keep_alive"] = params["keep_alive"]
else:
if "seed" in params:
if "seed" in params and params["seed"] is not None:
form_data["seed"] = params["seed"]
if "stop" in params:
if "stop" in params and params["stop"] is not None:
form_data["stop"] = params["stop"]
if "temperature" in params:
if "temperature" in params and params["temperature"] is not None:
form_data["temperature"] = params["temperature"]
if "max_tokens" in params:
if "max_tokens" in params and params["max_tokens"] is not None:
form_data["max_tokens"] = params["max_tokens"]
if "top_p" in params:
if "top_p" in params and params["top_p"] is not None:
form_data["top_p"] = params["top_p"]
if "frequency_penalty" in params:
if "frequency_penalty" in params and params["frequency_penalty"] is not None:
form_data["frequency_penalty"] = params["frequency_penalty"]
if "reasoning_effort" in params:
if "reasoning_effort" in params and params["reasoning_effort"] is not None:
form_data["reasoning_effort"] = params["reasoning_effort"]
if "logit_bias" in params:
if "logit_bias" in params and params["logit_bias"] is not None:
try:
form_data["logit_bias"] = json.loads(
convert_logit_bias_input_to_json(params["logit_bias"])
@@ -865,7 +900,9 @@ async def process_chat_payload(request, form_data, user, metadata, model):
for source_idx, source in enumerate(sources):
if "document" in source:
for doc_idx, doc_context in enumerate(source["document"]):
context_string += f"<source><source_id>{source_idx + 1}</source_id><source_context>{doc_context}</source_context></source>\n"
context_string += (
f'<source id="{source_idx + 1}">{doc_context}</source>\n'
)
context_string = context_string.strip()
prompt = get_last_user_message(form_data["messages"])
@@ -1198,13 +1235,15 @@ async def process_chat_response(
)
tool_result = None
tool_result_files = None
for result in results:
if tool_call_id == result.get("tool_call_id", ""):
tool_result = result.get("content", None)
tool_result_files = result.get("files", None)
break
if tool_result:
tool_calls_display_content = f'{tool_calls_display_content}\n<details type="tool_calls" done="true" id="{tool_call_id}" name="{tool_name}" arguments="{html.escape(json.dumps(tool_arguments))}" result="{html.escape(json.dumps(tool_result))}">\n<summary>Tool Executed</summary>\n</details>'
tool_calls_display_content = f'{tool_calls_display_content}\n<details type="tool_calls" done="true" id="{tool_call_id}" name="{tool_name}" arguments="{html.escape(json.dumps(tool_arguments))}" result="{html.escape(json.dumps(tool_result))}" files="{html.escape(json.dumps(tool_result_files)) if tool_result_files else ""}">\n<summary>Tool Executed</summary>\n</details>\n'
else:
tool_calls_display_content = f'{tool_calls_display_content}\n<details type="tool_calls" done="false" id="{tool_call_id}" name="{tool_name}" arguments="{html.escape(json.dumps(tool_arguments))}">\n<summary>Executing...</summary>\n</details>'
@@ -1805,7 +1844,7 @@ async def process_chat_response(
await stream_body_handler(response)
MAX_TOOL_CALL_RETRIES = 5
MAX_TOOL_CALL_RETRIES = 10
tool_call_retries = 0
while len(tool_calls) > 0 and tool_call_retries < MAX_TOOL_CALL_RETRIES:
@@ -1898,6 +1937,14 @@ async def process_chat_response(
except Exception as e:
tool_result = str(e)
tool_result_files = []
if isinstance(tool_result, list):
for item in tool_result:
# check if string
if isinstance(item, str) and item.startswith("data:"):
tool_result_files.append(item)
tool_result.remove(item)
if isinstance(tool_result, dict) or isinstance(
tool_result, list
):
@@ -1907,6 +1954,11 @@ async def process_chat_response(
{
"tool_call_id": tool_call_id,
"content": tool_result,
**(
{"files": tool_result_files}
if tool_result_files
else {}
),
}
)
+38 -33
View File
@@ -326,40 +326,45 @@ class OAuthManager:
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
picture_claim = auth_manager_config.OAUTH_PICTURE_CLAIM
picture_url = user_data.get(
picture_claim, OAUTH_PROVIDERS[provider].get("picture_url", "")
)
if picture_url:
# Download the profile image into a base64 string
try:
access_token = token.get("access_token")
get_kwargs = {}
if access_token:
get_kwargs["headers"] = {
"Authorization": f"Bearer {access_token}",
}
async with aiohttp.ClientSession() as session:
async with session.get(picture_url, **get_kwargs) as resp:
if resp.ok:
picture = await resp.read()
base64_encoded_picture = base64.b64encode(
picture
).decode("utf-8")
guessed_mime_type = mimetypes.guess_type(
picture_url
)[0]
if guessed_mime_type is None:
# assume JPG, browsers are tolerant enough of image formats
guessed_mime_type = "image/jpeg"
picture_url = f"data:{guessed_mime_type};base64,{base64_encoded_picture}"
else:
picture_url = "/user.png"
except Exception as e:
log.error(
f"Error downloading profile image '{picture_url}': {e}"
)
if picture_claim:
picture_url = user_data.get(
picture_claim, OAUTH_PROVIDERS[provider].get("picture_url", "")
)
if picture_url:
# Download the profile image into a base64 string
try:
access_token = token.get("access_token")
get_kwargs = {}
if access_token:
get_kwargs["headers"] = {
"Authorization": f"Bearer {access_token}",
}
async with aiohttp.ClientSession() as session:
async with session.get(
picture_url, **get_kwargs
) as resp:
if resp.ok:
picture = await resp.read()
base64_encoded_picture = base64.b64encode(
picture
).decode("utf-8")
guessed_mime_type = mimetypes.guess_type(
picture_url
)[0]
if guessed_mime_type is None:
# assume JPG, browsers are tolerant enough of image formats
guessed_mime_type = "image/jpeg"
picture_url = f"data:{guessed_mime_type};base64,{base64_encoded_picture}"
else:
picture_url = "/user.png"
except Exception as e:
log.error(
f"Error downloading profile image '{picture_url}': {e}"
)
picture_url = "/user.png"
if not picture_url:
picture_url = "/user.png"
if not picture_url:
else:
picture_url = "/user.png"
username_claim = auth_manager_config.OAUTH_USERNAME_CLAIM
+6 -6
View File
@@ -68,23 +68,23 @@ def replace_imports(content):
return content
def load_tools_module_by_id(toolkit_id, content=None):
def load_tools_module_by_id(tool_id, content=None):
if content is None:
tool = Tools.get_tool_by_id(toolkit_id)
tool = Tools.get_tool_by_id(tool_id)
if not tool:
raise Exception(f"Toolkit not found: {toolkit_id}")
raise Exception(f"Toolkit not found: {tool_id}")
content = tool.content
content = replace_imports(content)
Tools.update_tool_by_id(toolkit_id, {"content": content})
Tools.update_tool_by_id(tool_id, {"content": content})
else:
frontmatter = extract_frontmatter(content)
# Install required packages found within the frontmatter
install_frontmatter_requirements(frontmatter.get("requirements", ""))
module_name = f"tool_{toolkit_id}"
module_name = f"tool_{tool_id}"
module = types.ModuleType(module_name)
sys.modules[module_name] = module
@@ -108,7 +108,7 @@ def load_tools_module_by_id(toolkit_id, content=None):
else:
raise Exception("No Tools class found in the module")
except Exception as e:
log.error(f"Error loading module: {toolkit_id}: {e}")
log.error(f"Error loading module: {tool_id}: {e}")
del sys.modules[module_name] # Clean up
raise e
finally:
+398 -64
View File
@@ -2,9 +2,10 @@ import inspect
import logging
import re
import inspect
import uuid
import aiohttp
import asyncio
from typing import Any, Awaitable, Callable, get_type_hints
from typing import Any, Awaitable, Callable, get_type_hints, Dict, List, Union, Optional
from functools import update_wrapper, partial
@@ -17,96 +18,162 @@ from open_webui.models.tools import Tools
from open_webui.models.users import UserModel
from open_webui.utils.plugin import load_tools_module_by_id
import copy
log = logging.getLogger(__name__)
def apply_extra_params_to_tool_function(
def get_async_tool_function_and_apply_extra_params(
function: Callable, extra_params: dict
) -> Callable[..., Awaitable]:
sig = inspect.signature(function)
extra_params = {k: v for k, v in extra_params.items() if k in sig.parameters}
partial_func = partial(function, **extra_params)
if inspect.iscoroutinefunction(function):
update_wrapper(partial_func, function)
return partial_func
else:
# Make it a coroutine function
async def new_function(*args, **kwargs):
return partial_func(*args, **kwargs)
async def new_function(*args, **kwargs):
return partial_func(*args, **kwargs)
update_wrapper(new_function, function)
return new_function
update_wrapper(new_function, function)
return new_function
# Mutation on extra_params
def get_tools(
request: Request, tool_ids: list[str], user: UserModel, extra_params: dict
) -> dict[str, dict]:
tools_dict = {}
for tool_id in tool_ids:
tools = Tools.get_tool_by_id(tool_id)
if tools is None:
continue
tool = Tools.get_tool_by_id(tool_id)
if tool is None:
if tool_id.startswith("server:"):
server_idx = int(tool_id.split(":")[1])
tool_server_connection = (
request.app.state.config.TOOL_SERVER_CONNECTIONS[server_idx]
)
tool_server_data = request.app.state.TOOL_SERVERS[server_idx]
specs = tool_server_data.get("specs", [])
module = request.app.state.TOOLS.get(tool_id, None)
if module is None:
module, _ = load_tools_module_by_id(tool_id)
request.app.state.TOOLS[tool_id] = module
for spec in specs:
function_name = spec["name"]
extra_params["__id__"] = tool_id
if hasattr(module, "valves") and hasattr(module, "Valves"):
valves = Tools.get_tool_valves_by_id(tool_id) or {}
module.valves = module.Valves(**valves)
auth_type = tool_server_connection.get("auth_type", "bearer")
token = None
if hasattr(module, "UserValves"):
extra_params["__user__"]["valves"] = module.UserValves( # type: ignore
**Tools.get_user_valves_by_id_and_user_id(tool_id, user.id)
)
if auth_type == "bearer":
token = tool_server_connection.get("key", "")
elif auth_type == "session":
token = request.state.token.credentials
for spec in tools.specs:
# TODO: Fix hack for OpenAI API
# Some times breaks OpenAI but others don't. Leaving the comment
for val in spec.get("parameters", {}).get("properties", {}).values():
if val["type"] == "str":
val["type"] = "string"
def make_tool_function(function_name, token, tool_server_data):
async def tool_function(**kwargs):
print(
f"Executing tool function {function_name} with params: {kwargs}"
)
return await execute_tool_server(
token=token,
url=tool_server_data["url"],
name=function_name,
params=kwargs,
server_data=tool_server_data,
)
# Remove internal parameters
spec["parameters"]["properties"] = {
key: val
for key, val in spec["parameters"]["properties"].items()
if not key.startswith("__")
}
return tool_function
function_name = spec["name"]
tool_function = make_tool_function(
function_name, token, tool_server_data
)
# convert to function that takes only model params and inserts custom params
original_func = getattr(module, function_name)
callable = apply_extra_params_to_tool_function(original_func, extra_params)
callable = get_async_tool_function_and_apply_extra_params(
tool_function,
{},
)
if callable.__doc__ and callable.__doc__.strip() != "":
s = re.split(":(param|return)", callable.__doc__, 1)
spec["description"] = s[0]
tool_dict = {
"tool_id": tool_id,
"callable": callable,
"spec": spec,
}
# TODO: if collision, prepend toolkit name
if function_name in tools_dict:
log.warning(
f"Tool {function_name} already exists in another tools!"
)
log.warning(f"Discarding {tool_id}.{function_name}")
else:
tools_dict[function_name] = tool_dict
else:
spec["description"] = function_name
continue
else:
module = request.app.state.TOOLS.get(tool_id, None)
if module is None:
module, _ = load_tools_module_by_id(tool_id)
request.app.state.TOOLS[tool_id] = module
# TODO: This needs to be a pydantic model
tool_dict = {
"spec": spec,
"callable": callable,
"toolkit_id": tool_id,
"pydantic_model": function_to_pydantic_model(callable),
# Misc info
"file_handler": hasattr(module, "file_handler") and module.file_handler,
"citation": hasattr(module, "citation") and module.citation,
}
extra_params["__id__"] = tool_id
# TODO: if collision, prepend toolkit name
if function_name in tools_dict:
log.warning(f"Tool {function_name} already exists in another tools!")
log.warning(f"Collision between {tools} and {tool_id}.")
log.warning(f"Discarding {tools}.{function_name}")
else:
tools_dict[function_name] = tool_dict
# Set valves for the tool
if hasattr(module, "valves") and hasattr(module, "Valves"):
valves = Tools.get_tool_valves_by_id(tool_id) or {}
module.valves = module.Valves(**valves)
if hasattr(module, "UserValves"):
extra_params["__user__"]["valves"] = module.UserValves( # type: ignore
**Tools.get_user_valves_by_id_and_user_id(tool_id, user.id)
)
for spec in tool.specs:
# TODO: Fix hack for OpenAI API
# Some times breaks OpenAI but others don't. Leaving the comment
for val in spec.get("parameters", {}).get("properties", {}).values():
if val["type"] == "str":
val["type"] = "string"
# Remove internal reserved parameters (e.g. __id__, __user__)
spec["parameters"]["properties"] = {
key: val
for key, val in spec["parameters"]["properties"].items()
if not key.startswith("__")
}
# convert to function that takes only model params and inserts custom params
function_name = spec["name"]
tool_function = getattr(module, function_name)
callable = get_async_tool_function_and_apply_extra_params(
tool_function, extra_params
)
# TODO: Support Pydantic models as parameters
if callable.__doc__ and callable.__doc__.strip() != "":
s = re.split(":(param|return)", callable.__doc__, 1)
spec["description"] = s[0]
else:
spec["description"] = function_name
tool_dict = {
"tool_id": tool_id,
"callable": callable,
"spec": spec,
# Misc info
"metadata": {
"file_handler": hasattr(module, "file_handler")
and module.file_handler,
"citation": hasattr(module, "citation") and module.citation,
},
}
# TODO: if collision, prepend toolkit name
if function_name in tools_dict:
log.warning(
f"Tool {function_name} already exists in another tools!"
)
log.warning(f"Discarding {tool_id}.{function_name}")
else:
tools_dict[function_name] = tool_dict
return tools_dict
@@ -214,6 +281,273 @@ def get_callable_attributes(tool: object) -> list[Callable]:
def get_tools_specs(tool_class: object) -> list[dict]:
function_list = get_callable_attributes(tool_class)
models = map(function_to_pydantic_model, function_list)
return [convert_to_openai_function(tool) for tool in models]
function_model_list = map(
function_to_pydantic_model, get_callable_attributes(tool_class)
)
return [
convert_to_openai_function(function_model)
for function_model in function_model_list
]
def resolve_schema(schema, components):
"""
Recursively resolves a JSON schema using OpenAPI components.
"""
if not schema:
return {}
if "$ref" in schema:
ref_path = schema["$ref"]
ref_parts = ref_path.strip("#/").split("/")
resolved = components
for part in ref_parts[1:]: # Skip the initial 'components'
resolved = resolved.get(part, {})
return resolve_schema(resolved, components)
resolved_schema = copy.deepcopy(schema)
# Recursively resolve inner schemas
if "properties" in resolved_schema:
for prop, prop_schema in resolved_schema["properties"].items():
resolved_schema["properties"][prop] = resolve_schema(
prop_schema, components
)
if "items" in resolved_schema:
resolved_schema["items"] = resolve_schema(resolved_schema["items"], components)
return resolved_schema
def convert_openapi_to_tool_payload(openapi_spec):
"""
Converts an OpenAPI specification into a custom tool payload structure.
Args:
openapi_spec (dict): The OpenAPI specification as a Python dict.
Returns:
list: A list of tool payloads.
"""
tool_payload = []
for path, methods in openapi_spec.get("paths", {}).items():
for method, operation in methods.items():
tool = {
"type": "function",
"name": operation.get("operationId"),
"description": operation.get(
"description", operation.get("summary", "No description available.")
),
"parameters": {"type": "object", "properties": {}, "required": []},
}
# Extract path and query parameters
for param in operation.get("parameters", []):
param_name = param["name"]
param_schema = param.get("schema", {})
tool["parameters"]["properties"][param_name] = {
"type": param_schema.get("type"),
"description": param_schema.get("description", ""),
}
if param.get("required"):
tool["parameters"]["required"].append(param_name)
# Extract and resolve requestBody if available
request_body = operation.get("requestBody")
if request_body:
content = request_body.get("content", {})
json_schema = content.get("application/json", {}).get("schema")
if json_schema:
resolved_schema = resolve_schema(
json_schema, openapi_spec.get("components", {})
)
if resolved_schema.get("properties"):
tool["parameters"]["properties"].update(
resolved_schema["properties"]
)
if "required" in resolved_schema:
tool["parameters"]["required"] = list(
set(
tool["parameters"]["required"]
+ resolved_schema["required"]
)
)
elif resolved_schema.get("type") == "array":
tool["parameters"] = resolved_schema # special case for array
tool_payload.append(tool)
return tool_payload
async def get_tool_server_data(token: str, url: str) -> Dict[str, Any]:
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
}
if token:
headers["Authorization"] = f"Bearer {token}"
error = None
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as response:
if response.status != 200:
error_body = await response.json()
raise Exception(error_body)
res = await response.json()
except Exception as err:
print("Error:", err)
if isinstance(err, dict) and "detail" in err:
error = err["detail"]
else:
error = str(err)
raise Exception(error)
data = {
"openapi": res,
"info": res.get("info", {}),
"specs": convert_openapi_to_tool_payload(res),
}
print("Fetched data:", data)
return data
async def get_tool_servers_data(
servers: List[Dict[str, Any]], session_token: Optional[str] = None
) -> List[Dict[str, Any]]:
# Prepare list of enabled servers along with their original index
server_entries = []
for idx, server in enumerate(servers):
if server.get("config", {}).get("enable"):
url_path = server.get("path", "openapi.json")
full_url = f"{server.get('url')}/{url_path}"
auth_type = server.get("auth_type", "bearer")
token = None
if auth_type == "bearer":
token = server.get("key", "")
elif auth_type == "session":
token = session_token
server_entries.append((idx, server, full_url, token))
# Create async tasks to fetch data
tasks = [get_tool_server_data(token, url) for (_, _, url, token) in server_entries]
# Execute tasks concurrently
responses = await asyncio.gather(*tasks, return_exceptions=True)
# Build final results with index and server metadata
results = []
for (idx, server, url, _), response in zip(server_entries, responses):
if isinstance(response, Exception):
print(f"Failed to connect to {url} OpenAPI tool server")
continue
results.append(
{
"idx": idx,
"url": server.get("url"),
"openapi": response.get("openapi"),
"info": response.get("info"),
"specs": response.get("specs"),
}
)
return results
async def execute_tool_server(
token: str, url: str, name: str, params: Dict[str, Any], server_data: Dict[str, Any]
) -> Any:
error = None
try:
openapi = server_data.get("openapi", {})
paths = openapi.get("paths", {})
matching_route = None
for route_path, methods in paths.items():
for http_method, operation in methods.items():
if isinstance(operation, dict) and operation.get("operationId") == name:
matching_route = (route_path, methods)
break
if matching_route:
break
if not matching_route:
raise Exception(f"No matching route found for operationId: {name}")
route_path, methods = matching_route
method_entry = None
for http_method, operation in methods.items():
if operation.get("operationId") == name:
method_entry = (http_method.lower(), operation)
break
if not method_entry:
raise Exception(f"No matching method found for operationId: {name}")
http_method, operation = method_entry
path_params = {}
query_params = {}
body_params = {}
for param in operation.get("parameters", []):
param_name = param["name"]
param_in = param["in"]
if param_name in params:
if param_in == "path":
path_params[param_name] = params[param_name]
elif param_in == "query":
query_params[param_name] = params[param_name]
final_url = f"{url}{route_path}"
for key, value in path_params.items():
final_url = final_url.replace(f"{{{key}}}", str(value))
if query_params:
query_string = "&".join(f"{k}={v}" for k, v in query_params.items())
final_url = f"{final_url}?{query_string}"
if operation.get("requestBody", {}).get("content"):
if params:
body_params = params
else:
raise Exception(
f"Request body expected for operation '{name}' but none found."
)
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
async with aiohttp.ClientSession() as session:
request_method = getattr(session, http_method.lower())
if http_method in ["post", "put", "patch"]:
async with request_method(
final_url, json=body_params, headers=headers
) as response:
if response.status >= 400:
text = await response.text()
raise Exception(f"HTTP error {response.status}: {text}")
return await response.json()
else:
async with request_method(final_url, headers=headers) as response:
if response.status >= 400:
text = await response.text()
raise Exception(f"HTTP error {response.status}: {text}")
return await response.json()
except Exception as err:
error = str(err)
print("API Request Error:", error)
return {"error": error}
+6 -3
View File
@@ -1,7 +1,7 @@
fastapi==0.115.7
uvicorn[standard]==0.34.0
pydantic==2.10.6
python-multipart==0.0.18
python-multipart==0.0.20
python-socketio==5.11.3
python-jose==3.4.0
@@ -54,8 +54,9 @@ elasticsearch==8.17.1
transformers
sentence-transformers==3.3.1
accelerate
colbert-ai==0.2.21
einops==0.8.0
einops==0.8.1
ftfy==6.2.3
@@ -67,7 +68,7 @@ python-pptx==1.0.0
unstructured==0.16.17
nltk==3.9.1
Markdown==3.7
pypandoc==1.13
pypandoc==1.15
pandas==2.2.3
openpyxl==3.1.5
pyxlsb==1.0.10
@@ -83,6 +84,8 @@ opencv-python-headless==4.11.0.86
rapidocr-onnxruntime==1.3.24
rank-bm25==0.2.2
onnxruntime==1.20.1
faster-whisper==1.1.1
PyJWT[crypto]==2.10.1
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "open-webui",
"version": "0.6.0",
"version": "0.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-webui",
"version": "0.6.0",
"version": "0.6.2",
"dependencies": {
"@azure/msal-browser": "^4.5.0",
"@codemirror/lang-javascript": "^6.2.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "open-webui",
"version": "0.6.0",
"version": "0.6.2",
"private": true,
"scripts": {
"dev": "npm run pyodide:fetch && vite dev --host",
+6 -3
View File
@@ -45,7 +45,7 @@ dependencies = [
"openai",
"anthropic",
"google-generativeai==0.7.2",
"google-generativeai==0.8.4",
"tiktoken",
"langchain==0.3.19",
@@ -61,8 +61,9 @@ dependencies = [
"transformers",
"sentence-transformers==3.3.1",
"accelerate",
"colbert-ai==0.2.21",
"einops==0.8.0",
"einops==0.8.1",
"ftfy==6.2.3",
"pypdf==4.3.1",
@@ -73,7 +74,7 @@ dependencies = [
"unstructured==0.16.17",
"nltk==3.9.1",
"Markdown==3.7",
"pypandoc==1.13",
"pypandoc==1.15",
"pandas==2.2.3",
"openpyxl==3.1.5",
"pyxlsb==1.0.10",
@@ -89,6 +90,8 @@ dependencies = [
"rapidocr-onnxruntime==1.3.24",
"rank-bm25==0.2.2",
"onnxruntime==1.20.1",
"faster-whisper==1.1.1",
"PyJWT[crypto]==2.10.1",
+8
View File
@@ -46,6 +46,14 @@ math {
@apply rounded-lg;
}
input::placeholder {
direction: auto;
}
textarea::placeholder {
direction: auto;
}
.input-prose {
@apply prose dark:prose-invert prose-headings:font-semibold prose-hr:my-4 prose-hr:border-gray-100 prose-hr:dark:border-gray-800 prose-p:my-0 prose-img:my-1 prose-headings:my-1 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-ul:-my-0 prose-ol:-my-0 prose-li:-my-0 whitespace-pre-line;
}
+87
View File
@@ -115,6 +115,93 @@ export const setDirectConnectionsConfig = async (token: string, config: object)
return res;
};
export const getToolServerConnections = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/configs/tool_servers`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res;
};
export const setToolServerConnections = async (token: string, connections: object) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/configs/tool_servers`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
...connections
})
})
.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 verifyToolServerConnection = async (token: string, connection: object) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/configs/tool_servers/verify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
...connection
})
})
.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 getCodeExecutionConfig = async (token: string) => {
let error = null;
+6 -3
View File
@@ -262,7 +262,7 @@ export const stopTask = async (token: string, id: string) => {
export const getToolServerData = async (token: string, url: string) => {
let error = null;
const res = await fetch(`${url}/openapi.json`, {
const res = await fetch(`${url}`, {
method: 'GET',
headers: {
Accept: 'application/json',
@@ -304,10 +304,13 @@ export const getToolServersData = async (i18n, servers: object[]) => {
servers
.filter((server) => server?.config?.enable)
.map(async (server) => {
const data = await getToolServerData(server?.key, server?.url).catch((err) => {
const data = await getToolServerData(
server?.key,
server?.url + '/' + (server?.path ?? 'openapi.json')
).catch((err) => {
toast.error(
i18n.t(`Failed to connect to {{URL}} OpenAPI tool server`, {
URL: server?.url
URL: server?.url + '/' + (server?.path ?? 'openapi.json')
})
);
return null;
+153 -21
View File
@@ -15,6 +15,9 @@
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Switch from '$lib/components/common/Switch.svelte';
import Tags from './common/Tags.svelte';
import { getToolServerData } from '$lib/apis';
import { verifyToolServerConnection } from '$lib/apis/configs';
import AccessControl from './workspace/common/AccessControl.svelte';
export let onSubmit: Function = () => {};
export let onDelete: Function = () => {};
@@ -22,14 +25,66 @@
export let show = false;
export let edit = false;
export let direct = false;
export let connection = null;
let url = '';
let path = 'openapi.json';
let auth_type = 'bearer';
let key = '';
let accessControl = null;
let enable = true;
let loading = false;
const verifyHandler = async () => {
if (url === '') {
toast.error($i18n.t('Please enter a valid URL'));
return;
}
if (path === '') {
toast.error($i18n.t('Please enter a valid path'));
return;
}
if (direct) {
const res = await getToolServerData(
auth_type === 'bearer' ? key : localStorage.token,
`${url}/${path}`
).catch((err) => {
toast.error($i18n.t('Connection failed'));
});
if (res) {
toast.success($i18n.t('Connection successful'));
console.debug('Connection successful', res);
}
} else {
const res = await verifyToolServerConnection(localStorage.token, {
url,
path,
auth_type,
key,
config: {
enable: enable,
access_control: accessControl
}
}).catch((err) => {
toast.error($i18n.t('Connection failed'));
});
if (res) {
toast.success($i18n.t('Connection successful'));
console.debug('Connection successful', res);
}
}
};
const submitHandler = async () => {
loading = true;
@@ -38,9 +93,12 @@
const connection = {
url,
path,
auth_type,
key,
config: {
enable: enable
enable: enable,
access_control: accessControl
}
};
@@ -50,16 +108,24 @@
show = false;
url = '';
path = 'openapi.json';
key = '';
auth_type = 'bearer';
enable = true;
accessControl = null;
};
const init = () => {
if (connection) {
url = connection.url;
key = connection.key;
path = connection?.path ?? 'openapi.json';
auth_type = connection?.auth_type ?? 'bearer';
key = connection?.key ?? '';
enable = connection.config?.enable ?? true;
accessControl = connection.config?.access_control ?? null;
}
};
@@ -113,47 +179,113 @@
<div class="px-1">
<div class="flex gap-2">
<div class="flex flex-col w-full">
<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('URL')}</div>
<div class="flex justify-between mb-0.5">
<div class=" text-xs text-gray-500">{$i18n.t('URL')}</div>
</div>
<div class="flex-1">
<div class="flex flex-1 items-center">
<input
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
class="w-full flex-1 text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
type="text"
bind:value={url}
placeholder={$i18n.t('API Base URL')}
autocomplete="off"
required
/>
</div>
</div>
<div class="flex flex-col shrink-0 self-end">
<Tooltip content={enable ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
<Switch bind:state={enable} />
</Tooltip>
<Tooltip
content={$i18n.t('Verify Connection')}
className="shrink-0 flex items-center mr-1"
>
<button
class="self-center p-1 bg-transparent hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
on:click={() => {
verifyHandler();
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
clip-rule="evenodd"
/>
</svg>
</button>
</Tooltip>
<Tooltip content={enable ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
<Switch bind:state={enable} />
</Tooltip>
</div>
<div class="flex-1 flex items-center">
<div class="text-sm">/</div>
<input
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
type="text"
bind:value={path}
placeholder={$i18n.t('openapi.json Path')}
autocomplete="off"
required
/>
</div>
</div>
</div>
<div class="text-xs text-gray-500 mt-1">
{$i18n.t(`WebUI will make requests to "{{url}}/openapi.json"`, {
url: url
{$i18n.t(`WebUI will make requests to "{{url}}"`, {
url: `${url}/${path}`
})}
</div>
<div class="flex gap-2 mt-2">
<div class="flex flex-col w-full">
<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('Key')}</div>
<div class=" text-xs text-gray-500">{$i18n.t('Auth')}</div>
<div class="flex-1">
<SensitiveInput
className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
bind:value={key}
placeholder={$i18n.t('API Key')}
required={false}
/>
<div class="flex gap-2">
<div class="flex-shrink-0 self-start">
<select
class="w-full text-sm bg-transparent dark:bg-gray-900 placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden pr-5"
bind:value={auth_type}
>
<option value="bearer">Bearer</option>
<option value="session">Session</option>
</select>
</div>
<div class="flex flex-1 items-center">
{#if auth_type === 'bearer'}
<SensitiveInput
className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
bind:value={key}
placeholder={$i18n.t('API Key')}
required={false}
/>
{:else if auth_type === 'session'}
<div class="text-xs text-gray-500 self-center translate-y-[1px]">
{$i18n.t('Forwards system user session credentials to authenticate')}
</div>
{/if}
</div>
</div>
</div>
</div>
{#if !direct}
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
<div class="my-2 -mx-2">
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
<AccessControl bind:accessControl />
</div>
</div>
{/if}
</div>
<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
@@ -115,7 +115,7 @@
<span class="text-lg font-medium text-gray-500 dark:text-gray-300">{feedbacks.length}</span>
</div>
<div>
{#if feedbacks.length > 0}
<div>
<Tooltip content={$i18n.t('Export')}>
<button
@@ -128,7 +128,7 @@
</button>
</Tooltip>
</div>
</div>
{/if}
</div>
<div
+33 -31
View File
@@ -430,39 +430,41 @@
</div>
</button>
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
const _functions = await exportFunctions(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (_functions) {
let blob = new Blob([JSON.stringify(_functions)], {
type: 'application/json'
{#if $functions.length}
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
const _functions = await exportFunctions(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
saveAs(blob, `functions-export-${Date.now()}.json`);
}
}}
>
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Functions')}</div>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
if (_functions) {
let blob = new Blob([JSON.stringify(_functions)], {
type: 'application/json'
});
saveAs(blob, `functions-export-${Date.now()}.json`);
}
}}
>
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Functions')}</div>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
{/if}
</div>
</div>
+29
View File
@@ -20,6 +20,7 @@
import DocumentChartBar from '../icons/DocumentChartBar.svelte';
import Evaluations from './Settings/Evaluations.svelte';
import CodeExecution from './Settings/CodeExecution.svelte';
import Tools from './Settings/Tools.svelte';
const i18n = getContext('i18n');
@@ -135,6 +136,32 @@
<div class=" self-center">{$i18n.t('Evaluations')}</div>
</button>
<button
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
'tools'
? ''
: ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
on:click={() => {
selectedTab = 'tools';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
fill-rule="evenodd"
d="M12 6.75a5.25 5.25 0 0 1 6.775-5.025.75.75 0 0 1 .313 1.248l-3.32 3.319c.063.475.276.934.641 1.299.365.365.824.578 1.3.64l3.318-3.319a.75.75 0 0 1 1.248.313 5.25 5.25 0 0 1-5.472 6.756c-1.018-.086-1.87.1-2.309.634L7.344 21.3A3.298 3.298 0 1 1 2.7 16.657l8.684-7.151c.533-.44.72-1.291.634-2.309A5.342 5.342 0 0 1 12 6.75ZM4.117 19.125a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75h-.008a.75.75 0 0 1-.75-.75v-.008Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Tools')}</div>
</button>
<button
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
'documents'
@@ -373,6 +400,8 @@
<Models />
{:else if selectedTab === 'evaluations'}
<Evaluations />
{:else if selectedTab === 'tools'}
<Tools />
{:else if selectedTab === 'documents'}
<Documents
on:save={async () => {
@@ -136,7 +136,7 @@
};
onMount(async () => {
if ($user.role === 'admin') {
if ($user?.role === 'admin') {
let ollamaConfig = {};
let openaiConfig = {};
@@ -54,6 +54,8 @@
let documentIntelligenceEndpoint = '';
let documentIntelligenceKey = '';
let showDocumentIntelligenceConfig = false;
let mistralApiKey = '';
let showMistralOcrConfig = false;
let textSplitter = '';
let chunkSize = 0;
@@ -189,6 +191,10 @@
toast.error($i18n.t('Document Intelligence endpoint and key required.'));
return;
}
if (contentExtractionEngine === 'mistral_ocr' && mistralApiKey === '') {
toast.error($i18n.t('Mistral OCR API Key required.'));
return;
}
if (!BYPASS_EMBEDDING_AND_RETRIEVAL) {
await embeddingModelUpdateHandler();
@@ -220,6 +226,9 @@
document_intelligence_config: {
key: documentIntelligenceKey,
endpoint: documentIntelligenceEndpoint
},
mistral_ocr_config: {
api_key: mistralApiKey
}
}
});
@@ -284,6 +293,8 @@
documentIntelligenceEndpoint = res.content_extraction.document_intelligence_config.endpoint;
documentIntelligenceKey = res.content_extraction.document_intelligence_config.key;
showDocumentIntelligenceConfig = contentExtractionEngine === 'document_intelligence';
mistralApiKey = res.content_extraction.mistral_ocr_config.api_key;
showMistralOcrConfig = contentExtractionEngine === 'mistral_ocr';
fileMaxSize = res?.file.max_size ?? '';
fileMaxCount = res?.file.max_count ?? '';
@@ -335,21 +346,21 @@
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class=" mb-2.5 flex flex-col w-full justify-between">
<div class="mb-2.5 flex flex-col w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
<div class="self-center text-xs font-medium">
{$i18n.t('Content Extraction Engine')}
</div>
<div class="">
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
bind:value={contentExtractionEngine}
>
<option value="">{$i18n.t('Default')} </option>
<option value="">{$i18n.t('Default')}</option>
<option value="tika">{$i18n.t('Tika')}</option>
<option value="docling">{$i18n.t('Docling')}</option>
<option value="document_intelligence">{$i18n.t('Document Intelligence')}</option>
<option value="mistral_ocr">{$i18n.t('Mistral OCR')}</option>
</select>
</div>
</div>
@@ -378,12 +389,18 @@
placeholder={$i18n.t('Enter Document Intelligence Endpoint')}
bind:value={documentIntelligenceEndpoint}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Document Intelligence Key')}
bind:value={documentIntelligenceKey}
/>
</div>
{:else if contentExtractionEngine === 'mistral_ocr'}
<div class="my-0.5 flex gap-2 pr-2">
<SensitiveInput
placeholder={$i18n.t('Enter Mistral API Key')}
bind:value={mistralApiKey}
/>
</div>
{/if}
</div>
@@ -77,7 +77,7 @@
};
onMount(async () => {
if ($user.role === 'admin') {
if ($user?.role === 'admin') {
evaluationConfig = await getConfig(localStorage.token).catch((err) => {
toast.error(err);
return null;
@@ -176,7 +176,7 @@
};
onMount(async () => {
if ($user.role === 'admin') {
if ($user?.role === 'admin') {
const res = await getConfig(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
@@ -380,7 +380,7 @@
</div>
</div>
{#if $user.role === 'admin'}
{#if $user?.role === 'admin'}
<div class=" space-y-3">
<div class="flex w-full justify-between mb-2">
<div class=" self-center text-sm font-semibold">
@@ -19,7 +19,7 @@
let ollamaConfig = null;
onMount(async () => {
if ($user.role === 'admin') {
if ($user?.role === 'admin') {
await Promise.all([
(async () => {
ollamaConfig = await getOllamaConfig(localStorage.token);
@@ -0,0 +1,134 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
import { getModels as _getModels } from '$lib/apis';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
import { models, settings, user } from '$lib/stores';
import Switch from '$lib/components/common/Switch.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Plus from '$lib/components/icons/Plus.svelte';
import Connection from '$lib/components/chat/Settings/Tools/Connection.svelte';
import AddServerModal from '$lib/components/AddServerModal.svelte';
import { getToolServerConnections, setToolServerConnections } from '$lib/apis/configs';
export let saveSettings: Function;
let servers = null;
let showConnectionModal = false;
const addConnectionHandler = async (server) => {
servers = [...servers, server];
await updateHandler();
};
const updateHandler = async () => {
const res = await setToolServerConnections(localStorage.token, {
TOOL_SERVER_CONNECTIONS: servers
}).catch((err) => {
toast.error($i18n.t('Failed to save connections'));
return null;
});
if (res) {
toast.success($i18n.t('Connections saved successfully'));
}
};
onMount(async () => {
const res = await getToolServerConnections(localStorage.token);
servers = res.TOOL_SERVER_CONNECTIONS;
});
</script>
<AddServerModal bind:show={showConnectionModal} onSubmit={addConnectionHandler} />
<form
class="flex flex-col h-full justify-between text-sm"
on:submit|preventDefault={() => {
updateHandler();
}}
>
<div class=" overflow-y-scroll scrollbar-hidden h-full">
{#if servers !== null}
<div class="">
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class="mb-2.5 flex flex-col w-full justify-between">
<!-- {$i18n.t(`Failed to connect to {{URL}} OpenAPI tool server`, {
URL: 'server?.url'
})} -->
<div class="flex justify-between items-center mb-0.5">
<div class="font-medium">{$i18n.t('Manage Tool Servers')}</div>
<Tooltip content={$i18n.t(`Add Connection`)}>
<button
class="px-1"
on:click={() => {
showConnectionModal = true;
}}
type="button"
>
<Plus />
</button>
</Tooltip>
</div>
<div class="flex flex-col gap-1.5">
{#each servers as server, idx}
<Connection
bind:connection={server}
onSubmit={() => {
updateHandler();
}}
onDelete={() => {
servers = servers.filter((_, i) => i !== idx);
updateHandler();
}}
/>
{/each}
</div>
<div class="my-1.5">
<div class="text-xs text-gray-500">
{$i18n.t('Connect to your own OpenAPI compatible external tool servers.')}
</div>
</div>
</div>
<!-- <div class="mb-2.5 flex w-full justify-between">
<div class=" text-xs font-medium">{$i18n.t('Arena Models')}</div>
<Tooltip content={$i18n.t(`Message rating should be enabled to use this feature`)}>
<Switch bind:state={evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS} />
</Tooltip>
</div> -->
</div>
</div>
{:else}
<div class="flex h-full justify-center">
<div class="my-auto">
<Spinner className="size-6" />
</div>
</div>
{/if}
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
<button
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
type="submit"
>
{$i18n.t('Save')}
</button>
</div>
</form>
+2 -1
View File
@@ -64,9 +64,10 @@
delete: true,
edit: true,
temporary: true,
temporary_enforced: true
temporary_enforced: false
},
features: {
direct_tool_servers: false,
web_search: true,
image_generation: true,
code_interpreter: true
@@ -38,6 +38,12 @@
prompts: false,
tools: false
},
sharing: {
public_models: false,
public_knowledge: false,
public_prompts: false,
public_tools: false
},
chat: {
controls: true,
file_upload: true,
@@ -46,6 +52,7 @@
temporary: true
},
features: {
direct_tool_servers: false,
web_search: true,
image_generation: true,
code_interpreter: true
@@ -25,9 +25,10 @@
edit: true,
file_upload: true,
temporary: true,
temporary_enforced: true
temporary_enforced: false
},
features: {
direct_tool_servers: false,
web_search: true,
image_generation: true,
code_interpreter: true
@@ -295,6 +296,14 @@
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Features Permissions')}</div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">
{$i18n.t('Direct Tool Servers')}
</div>
<Switch bind:state={permissions.features.direct_tool_servers} />
</div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">
{$i18n.t('Web Search')}
+1 -1
View File
@@ -106,7 +106,7 @@
messages[idx] = data;
}
} else if (type === 'typing' && event.message_id === null) {
if (event.user.id === $user.id) {
if (event.user.id === $user?.id) {
return;
}
@@ -381,7 +381,7 @@
>
<div
class="flex-1 flex flex-col relative w-full rounded-3xl px-1 bg-gray-600/5 dark:bg-gray-400/5 dark:text-gray-100"
dir={$settings?.chatDirection ?? 'LTR'}
dir={$settings?.chatDirection ?? 'auto'}
>
{#if files.length > 0}
<div class="mx-2 mt-2.5 -mb-1 flex flex-wrap gap-2">
+4 -4
View File
@@ -132,7 +132,7 @@
if (
(message?.reactions ?? [])
.find((reaction) => reaction.name === name)
?.user_ids?.includes($user.id) ??
?.user_ids?.includes($user?.id) ??
false
) {
messages = messages.map((m) => {
@@ -140,7 +140,7 @@
const reaction = m.reactions.find((reaction) => reaction.name === name);
if (reaction) {
reaction.user_ids = reaction.user_ids.filter((id) => id !== $user.id);
reaction.user_ids = reaction.user_ids.filter((id) => id !== $user?.id);
reaction.count = reaction.user_ids.length;
if (reaction.count === 0) {
@@ -167,12 +167,12 @@
const reaction = m.reactions.find((reaction) => reaction.name === name);
if (reaction) {
reaction.user_ids.push($user.id);
reaction.user_ids.push($user?.id);
reaction.count = reaction.user_ids.length;
} else {
m.reactions.push({
name: name,
user_ids: [$user.id],
user_ids: [$user?.id],
count: 1
});
}
@@ -106,7 +106,7 @@
</Tooltip>
{/if}
{#if message.user_id === $user.id || $user.role === 'admin'}
{#if message.user_id === $user?.id || $user?.role === 'admin'}
<Tooltip content={$i18n.t('Edit')}>
<button
class="hover:bg-gray-100 dark:hover:bg-gray-800 transition rounded-lg p-1"
@@ -265,7 +265,7 @@
<Tooltip content={`:${reaction.name}:`}>
<button
class="flex items-center gap-1.5 transition rounded-xl px-2 py-1 cursor-pointer {reaction.user_ids.includes(
$user.id
$user?.id
)
? ' bg-blue-300/10 outline outline-blue-500/50 outline-1'
: 'bg-gray-300/10 dark:bg-gray-500/10 hover:outline hover:outline-gray-700/30 dark:hover:outline-gray-300/30 hover:outline-1'}"
+2 -2
View File
@@ -58,7 +58,7 @@
{#if $user !== undefined}
<UserMenu
className="max-w-[200px]"
role={$user.role}
role={$user?.role}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChats.set(true);
@@ -71,7 +71,7 @@
>
<div class=" self-center">
<img
src={$user.profile_image_url}
src={$user?.profile_image_url}
class="size-6 object-cover rounded-full"
alt="User profile"
draggable="false"
+1 -1
View File
@@ -89,7 +89,7 @@
}
}
} else if (type === 'typing' && event.message_id === threadId) {
if (event.user.id === $user.id) {
if (event.user.id === $user?.id) {
return;
}
+21 -7
View File
@@ -48,7 +48,8 @@
splitStream,
sleep,
removeDetails,
getPromptVariables
getPromptVariables,
processDetails
} from '$lib/utils';
import { generateChatCompletion } from '$lib/apis/ollama';
@@ -1336,6 +1337,10 @@
parentId: string,
{ modelId = null, modelIdx = null, newChat = false } = {}
) => {
if (autoScroll) {
scrollToBottom();
}
let _chatId = JSON.parse(JSON.stringify($chatId));
_history = JSON.parse(JSON.stringify(_history));
@@ -1498,7 +1503,7 @@
role: 'system',
content: `${promptTemplate(
params?.system ?? $settings?.system ?? '',
$user.name,
$user?.name,
$settings?.userLocation
? await getAndUpdateUserLocation(localStorage.token).catch((err) => {
console.error(err);
@@ -1514,7 +1519,7 @@
: undefined,
...createMessagesList(_history, responseMessageId).map((message) => ({
...message,
content: removeDetails(message.content, ['reasoning', 'code_interpreter'])
content: processDetails(message.content)
}))
].filter((message) => message);
@@ -1572,23 +1577,23 @@
features: {
image_generation:
$config?.features?.enable_image_generation &&
($user.role === 'admin' || $user?.permissions?.features?.image_generation)
($user?.role === 'admin' || $user?.permissions?.features?.image_generation)
? imageGenerationEnabled
: false,
code_interpreter:
$config?.features?.enable_code_interpreter &&
($user.role === 'admin' || $user?.permissions?.features?.code_interpreter)
($user?.role === 'admin' || $user?.permissions?.features?.code_interpreter)
? codeInterpreterEnabled
: false,
web_search:
$config?.features?.enable_web_search &&
($user.role === 'admin' || $user?.permissions?.features?.web_search)
($user?.role === 'admin' || $user?.permissions?.features?.web_search)
? webSearchEnabled || ($settings?.webSearch ?? false) === 'always'
: false
},
variables: {
...getPromptVariables(
$user.name,
$user?.name,
$settings?.userLocation
? await getAndUpdateUserLocation(localStorage.token).catch((err) => {
console.error(err);
@@ -1739,6 +1744,11 @@
history.currentId = userMessageId;
await tick();
if (autoScroll) {
scrollToBottom();
}
await sendPrompt(history, userPrompt, userMessageId);
};
@@ -1749,6 +1759,10 @@
let userMessage = history.messages[message.parentId];
let userPrompt = userMessage.content;
if (autoScroll) {
scrollToBottom();
}
if ((userMessage?.models ?? [...selectedModels]).length == 1) {
// If user message has only one model selected, sendPrompt automatically selects it for regeneration
await sendPrompt(history, userPrompt, userMessage.id);
@@ -68,12 +68,12 @@
{#if $temporaryChatEnabled}
<Tooltip
content="This chat won't appear in history and your messages will not be saved."
className="w-fit"
placement="top-start"
content={$i18n.t('This chat won’t appear in history and your messages will not be saved.')}
className="w-full flex justify-center mb-0.5"
placement="top"
>
<div class="flex items-center gap-2 text-gray-500 font-medium text-lg my-2 w-fit">
<EyeSlash strokeWidth="2.5" className="size-5" /> Temporary Chat
<EyeSlash strokeWidth="2.5" className="size-5" />{$i18n.t('Temporary Chat')}
</div>
</Tooltip>
{/if}
@@ -86,7 +86,7 @@
{#if models[selectedModelIdx]?.name}
{models[selectedModelIdx]?.name}
{:else}
{$i18n.t('Hello, {{name}}', { name: $user.name })}
{$i18n.t('Hello, {{name}}', { name: $user?.name })}
{/if}
</div>
@@ -67,7 +67,7 @@
</div>
</Collapsible>
{#if $user.role === 'admin' || $user?.permissions.chat?.controls}
{#if $user?.role === 'admin' || $user?.permissions.chat?.controls}
<hr class="my-2 border-gray-50 dark:border-gray-700/10" />
<Collapsible title={$i18n.t('System Prompt')} open={true} buttonClassName="w-full">
+42 -84
View File
@@ -21,7 +21,12 @@
TTSWorker
} from '$lib/stores';
import { blobToFile, compressImage, createMessagesList, findWordIndices } from '$lib/utils';
import {
blobToFile,
compressImage,
createMessagesList,
extractCurlyBraceWords
} from '$lib/utils';
import { transcribeAudio } from '$lib/apis/audio';
import { uploadFile } from '$lib/apis/files';
import { generateAutoCompletion } from '$lib/apis';
@@ -47,6 +52,7 @@
import CommandLine from '../icons/CommandLine.svelte';
import { KokoroWorker } from '$lib/workers/KokoroWorker';
import ToolServersModal from './ToolServersModal.svelte';
import Wrench from '../icons/Wrench.svelte';
const i18n = getContext('i18n');
@@ -85,7 +91,7 @@
webSearchEnabled
});
let showToolServers = false;
let showTools = false;
let loaded = false;
let recording = false;
@@ -348,7 +354,7 @@
<FilesOverlay show={dragged} />
<ToolServersModal bind:show={showToolServers} />
<ToolServersModal bind:show={showTools} {selectedToolIds} />
{#if loaded}
<div class="w-full font-primary">
@@ -392,38 +398,6 @@
<div
class="px-3 pb-0.5 pt-1.5 text-left w-full flex flex-col absolute bottom-0 left-0 right-0 bg-linear-to-t from-white dark:from-gray-900 z-10"
>
{#if selectedToolIds.length > 0}
<div class="flex items-center justify-between w-full">
<div class="flex items-center gap-2.5 text-sm dark:text-gray-500">
<div class="pl-1">
<span class="relative flex size-2">
<span
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-yellow-400 opacity-75"
/>
<span class="relative inline-flex rounded-full size-2 bg-yellow-500" />
</span>
</div>
<div class=" text-ellipsis line-clamp-1 flex">
{#each selectedToolIds.map((id) => {
return $tools ? $tools.find((t) => t.id === id) : { id: id, name: id };
}) as tool, toolIdx (toolIdx)}
<Tooltip
content={tool?.meta?.description ?? ''}
className=" {toolIdx !== 0 ? 'pl-0.5' : ''} shrink-0"
placement="top"
>
{tool.name}
</Tooltip>
{#if toolIdx !== selectedToolIds.length - 1}
<span>, </span>
{/if}
{/each}
</div>
</div>
</div>
{/if}
{#if atSelectedModel !== undefined}
<div class="flex items-center justify-between w-full">
<div class="pl-[1px] flex items-center gap-2 text-sm dark:text-gray-500">
@@ -536,7 +510,7 @@
>
<div
class="flex-1 flex flex-col relative w-full shadow-lg rounded-3xl border border-gray-100 dark:border-gray-850 hover:border-gray-200 focus-within:border-gray-200 hover:dark:border-gray-800 focus-within:dark:border-gray-800 transition px-1 bg-white/90 dark:bg-gray-400/5 dark:text-gray-100"
dir={$settings?.chatDirection ?? 'LTR'}
dir={$settings?.chatDirection ?? 'auto'}
>
{#if files.length > 0}
<div class="mx-2 mt-2.5 -mb-1 flex items-center flex-wrap gap-2">
@@ -631,6 +605,7 @@
{#if $settings?.richTextInput ?? true}
<div
class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-3 px-1 resize-none h-fit max-h-80 overflow-auto"
id="chat-input-container"
>
<RichTextInput
bind:this={chatInputElement}
@@ -846,6 +821,7 @@
{:else}
<textarea
id="chat-input"
dir="auto"
bind:this={chatInputElement}
class="scrollbar-hidden bg-transparent dark:text-gray-100 outline-hidden w-full pt-3 px-1 resize-none"
placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
@@ -977,7 +953,7 @@
}
if (e.key === 'Tab') {
const words = findWordIndices(prompt);
const words = extractCurlyBraceWords(prompt);
if (words.length > 0) {
const word = words.at(0);
@@ -1057,8 +1033,8 @@
{/if}
</div>
<div class=" flex justify-between mt-1.5 mb-2.5 mx-0.5 max-w-full">
<div class="ml-1 self-end gap-0.5 flex items-center flex-1 max-w-[80%]">
<div class=" flex justify-between mt-1.5 mb-2.5 mx-0.5 max-w-full" dir="ltr">
<div class="ml-1 self-end flex items-center flex-1 max-w-[80%] gap-0.5">
<InputMenu
bind:selectedToolIds
{screenCaptureHandler}
@@ -1126,7 +1102,30 @@
</button>
</InputMenu>
<div class="flex gap-0.5 items-center overflow-x-auto scrollbar-none flex-1">
<div class="flex gap-[2px] items-center overflow-x-auto scrollbar-none flex-1">
{#if toolServers.length + selectedToolIds.length > 0}
<Tooltip
content={$i18n.t('{{COUNT}} Available Tools', {
COUNT: toolServers.length + selectedToolIds.length
})}
>
<button
class="translate-y-[0.5px] flex gap-1 items-center text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 rounded-lg p-1 self-center transition"
aria-label="Available Tools"
type="button"
on:click={() => {
showTools = !showTools;
}}
>
<Wrench className="size-4" strokeWidth="1.75" />
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">
{toolServers.length + selectedToolIds.length}
</span>
</button>
</Tooltip>
{/if}
{#if $_user}
{#if $config?.features?.enable_web_search && ($_user.role === 'admin' || $_user?.permissions?.features?.web_search)}
<Tooltip content={$i18n.t('Search the internet')} placement="top">
@@ -1140,7 +1139,7 @@
>
<GlobeAlt className="size-5" strokeWidth="1.75" />
<span
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px]"
>{$i18n.t('Web Search')}</span
>
</button>
@@ -1159,7 +1158,7 @@
>
<Photo className="size-5" strokeWidth="1.75" />
<span
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px]"
>{$i18n.t('Image')}</span
>
</button>
@@ -1178,7 +1177,7 @@
>
<CommandLine className="size-5" strokeWidth="1.75" />
<span
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px]"
>{$i18n.t('Code Interpreter')}</span
>
</button>
@@ -1189,47 +1188,6 @@
</div>
<div class="self-end flex space-x-1 mr-1 shrink-0">
{#if toolServers.length > 0}
<Tooltip
content={$i18n.t('{{COUNT}} Available Tool Servers', {
COUNT: toolServers.length
})}
>
<button
class="translate-y-[1.5px] flex gap-1 items-center text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 rounded-lg px-1.5 py-0.5 mr-0.5 self-center border border-gray-100 dark:border-gray-800 transition"
aria-label="Available Tool Servers"
type="button"
on:click={() => {
showToolServers = !showToolServers;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-3"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21.75 6.75a4.5 4.5 0 0 1-4.884 4.484c-1.076-.091-2.264.071-2.95.904l-7.152 8.684a2.548 2.548 0 1 1-3.586-3.586l8.684-7.152c.833-.686.995-1.874.904-2.95a4.5 4.5 0 0 1 6.336-4.486l-3.276 3.276a3.004 3.004 0 0 0 2.25 2.25l3.276-3.276c.256.565.398 1.192.398 1.852Z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.867 19.125h.008v.008h-.008v-.008Z"
/>
</svg>
<span class="text-xs">
{toolServers.length}
</span>
</button>
</Tooltip>
{/if}
{#if !history?.currentId || history.messages[history.currentId]?.done == true}
<Tooltip content={$i18n.t('Record voice')}>
<button
@@ -1,7 +1,7 @@
<script lang="ts">
import { prompts, settings, user } from '$lib/stores';
import {
findWordIndices,
extractCurlyBraceWords,
getUserPosition,
getFormattedDate,
getFormattedTime,
@@ -86,7 +86,7 @@
if (command.content.includes('{{USER_NAME}}')) {
console.log($user);
const name = $user.name || 'User';
const name = $user?.name || 'User';
text = text.replaceAll('{{USER_NAME}}', name);
}
@@ -127,29 +127,49 @@
const lastWord = lastLineWords.pop();
if ($settings?.richTextInput ?? true) {
lastLineWords.push(`${text.replace(/</g, '&lt;').replace(/>/g, '&gt;')}`);
lastLineWords.push(
`${text.replace(/</g, '&lt;').replace(/>/g, '&gt;').replaceAll('\n', '<br/>')}`
);
lines.push(lastLineWords.join(' '));
prompt = lines.join('<br/>');
} else {
lastLineWords.push(text);
lines.push(lastLineWords.join(' '));
prompt = lines.join('\n');
}
prompt = lines.join('\n');
const chatInputContainerElement = document.getElementById('chat-input-container');
const chatInputElement = document.getElementById('chat-input');
await tick();
if (chatInputContainerElement) {
chatInputContainerElement.style.height = '';
chatInputContainerElement.style.height =
Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
chatInputContainerElement.scrollTop = chatInputContainerElement.scrollHeight;
}
await tick();
if (chatInputElement) {
chatInputElement.focus();
chatInputElement.dispatchEvent(new Event('input'));
const words = extractCurlyBraceWords(prompt);
if (words.length > 0) {
const word = words.at(0);
const fullPrompt = prompt;
prompt = prompt.substring(0, word?.endIndex + 1);
await tick();
chatInputElement.scrollTop = chatInputElement.scrollHeight;
prompt = fullPrompt;
await tick();
chatInputElement.setSelectionRange(word?.startIndex, word.endIndex + 1);
} else {
chatInputElement.scrollTop = chatInputElement.scrollHeight;
}
}
};
</script>
@@ -39,7 +39,7 @@
}
let fileUploadEnabled = true;
$: fileUploadEnabled = $user.role === 'admin' || $user?.permissions?.chat?.file_upload;
$: fileUploadEnabled = $user?.role === 'admin' || $user?.permissions?.chat?.file_upload;
const init = async () => {
if ($_tools === null) {
@@ -2,7 +2,7 @@
import { toast } from 'svelte-sonner';
import { createEventDispatcher, tick, getContext, onMount, onDestroy } from 'svelte';
import { config, settings } from '$lib/stores';
import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
import { blobToFile, calculateSHA256, extractCurlyBraceWords } from '$lib/utils';
import { transcribeAudio } from '$lib/apis/audio';
+1 -14
View File
@@ -14,7 +14,7 @@
import { toast } from 'svelte-sonner';
import { getChatList, updateChatById } from '$lib/apis/chats';
import { copyToClipboard, findWordIndices } from '$lib/utils';
import { copyToClipboard, extractCurlyBraceWords } from '$lib/utils';
import Message from './Messages/Message.svelte';
import Loader from '../common/Loader.svelte';
@@ -406,19 +406,6 @@
}
prompt = text;
await tick();
const chatInputContainerElement = document.getElementById('chat-input-container');
if (chatInputContainerElement) {
prompt = p;
chatInputContainerElement.style.height = '';
chatInputContainerElement.style.height =
Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
chatInputContainerElement.focus();
}
await tick();
}}
/>
@@ -14,6 +14,7 @@
let mergedDocuments = [];
function calculatePercentage(distance: number) {
if (typeof distance !== 'number') return null;
if (distance < 0) return 0;
if (distance > 1) return 100;
return Math.round(distance * 10000) / 100;
@@ -122,11 +123,14 @@
<div class="text-sm my-1 dark:text-gray-400 flex items-center gap-2 w-fit">
{#if showPercentage}
{@const percentage = calculatePercentage(document.distance)}
<span
class={`px-1 rounded-sm font-medium ${getRelevanceColor(percentage)}`}
>
{percentage.toFixed(2)}%
</span>
{#if typeof percentage === 'number'}
<span
class={`px-1 rounded-sm font-medium ${getRelevanceColor(percentage)}`}
>
{percentage.toFixed(2)}%
</span>
{/if}
<span class="text-gray-500 dark:text-gray-500">
({(document?.distance ?? 0).toFixed(4)})
</span>
@@ -184,9 +184,9 @@
{/if}
{:else if token.type === 'list'}
{#if token.ordered}
<ol start={token.start || 1}>
<ol start={token.start || 1} dir="auto">
{#each token.items as item, itemIdx}
<li dir="auto" class="text-start">
<li class="text-start">
{#if item?.task}
<input
class=" translate-y-[1px] -translate-x-1"
@@ -216,9 +216,9 @@
{/each}
</ol>
{:else}
<ul>
<ul dir="auto">
{#each token.items as item, itemIdx}
<li dir="auto" class="text-start">
<li class="text-start">
{#if item?.task}
<input
class=" translate-y-[1px] -translate-x-1"
@@ -295,7 +295,7 @@
</p>
{:else if token.type === 'text'}
{#if top}
<p dir="auto">
<p>
{#if token.tokens}
<MarkdownInlineTokens id={`${id}-${tokenIdx}-t`} tokens={token.tokens} {onSourceClick} />
{:else}
@@ -332,9 +332,37 @@
}
};
let preprocessedDetailsCache = [];
function preprocessForEditing(content: string): string {
// Replace <details>...</details> with unique ID placeholder
const detailsBlocks = [];
let i = 0;
content = content.replace(/<details[\s\S]*?<\/details>/gi, (match) => {
detailsBlocks.push(match);
return `<details id="__DETAIL_${i++}__"/>`;
});
// Store original blocks in the editedContent or globally (see merging later)
preprocessedDetailsCache = detailsBlocks;
return content;
}
function postprocessAfterEditing(content: string): string {
const restoredContent = content.replace(
/<details id="__DETAIL_(\d+)__"\/>/g,
(_, index) => preprocessedDetailsCache[parseInt(index)] || ''
);
return restoredContent;
}
const editMessageHandler = async () => {
edit = true;
editedContent = message.content;
editedContent = preprocessForEditing(message.content);
await tick();
@@ -343,7 +371,8 @@
};
const editMessageConfirmHandler = async () => {
editMessage(message.id, editedContent ? editedContent : '', false);
const messageContent = postprocessAfterEditing(editedContent ? editedContent : '');
editMessage(message.id, messageContent, false);
edit = false;
editedContent = '';
@@ -352,7 +381,9 @@
};
const saveAsCopyHandler = async () => {
editMessage(message.id, editedContent ? editedContent : '');
const messageContent = postprocessAfterEditing(editedContent ? editedContent : '');
editMessage(message.id, messageContent);
edit = false;
editedContent = '';
@@ -551,7 +582,7 @@
id="message-{message.id}"
dir={$settings.chatDirection}
>
<div class={`shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
<div class={`shrink-0 ltr:mr-3 rtl:ml-3`}>
<ProfileImage
src={model?.info?.meta?.profile_image_url ??
($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
@@ -698,7 +729,7 @@
<div>
<button
id="save-new-message-button"
class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border border-gray-100 dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
on:click={() => {
saveAsCopyHandler();
}}
@@ -920,7 +951,7 @@
{#if message.done}
{#if !readOnly}
{#if $user.role === 'user' ? ($user?.permissions?.chat?.edit ?? true) : true}
{#if $user?.role === 'user' ? ($user?.permissions?.chat?.edit ?? true) : true}
<Tooltip content={$i18n.t('Edit')} placement="bottom">
<button
class="{isLastMessage
@@ -1053,7 +1084,7 @@
</button>
</Tooltip>
{#if $config?.features.enable_image_generation && ($user.role === 'admin' || $user?.permissions?.features?.image_generation) && !readOnly}
{#if $config?.features.enable_image_generation && ($user?.role === 'admin' || $user?.permissions?.features?.image_generation) && !readOnly}
<Tooltip content={$i18n.t('Generate Image')} placement="bottom">
<button
class="{isLastMessage
@@ -102,7 +102,7 @@
<div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">
{#if !($settings?.chatBubble ?? true)}
<div class={`shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
<div class={`shrink-0 ltr:mr-3 rtl:ml-3`}>
<ProfileImage
src={message.user
? ($models.find((m) => m.id === message.user)?.info?.meta?.profile_image_url ??
+1 -1
View File
@@ -45,7 +45,7 @@
label: model.name,
model: model
}))}
showTemporaryChatControl={$user.role === 'user'
showTemporaryChatControl={$user?.role === 'user'
? ($user?.permissions?.chat?.temporary ?? true) &&
!($user?.permissions?.chat?.temporary_enforced ?? false)
: true}
@@ -634,7 +634,7 @@
</div>
{/each}
{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user.role === 'admin'}
{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user?.role === 'admin'}
<Tooltip
content={$i18n.t(`Pull "{{searchValue}}" from Ollama.com`, {
searchValue: searchValue
+3 -3
View File
@@ -147,10 +147,10 @@
</button>
</Tooltip>
{#if $user !== undefined}
{#if $user !== undefined && $user !== null}
<UserMenu
className="max-w-[200px]"
role={$user.role}
role={$user?.role}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChats.set(true);
@@ -163,7 +163,7 @@
>
<div class=" self-center">
<img
src={$user.profile_image_url}
src={$user?.profile_image_url}
class="size-6 object-cover rounded-full"
alt="User profile"
draggable="false"
+5 -7
View File
@@ -8,7 +8,7 @@
const dispatch = createEventDispatcher();
import { config, user, models as _models, temporaryChatEnabled } from '$lib/stores';
import { sanitizeResponseContent, findWordIndices } from '$lib/utils';
import { sanitizeResponseContent, extractCurlyBraceWords } from '$lib/utils';
import { WEBUI_BASE_URL } from '$lib/constants';
import Suggestions from './Suggestions.svelte';
@@ -65,9 +65,7 @@
const chatInputElement = document.getElementById('chat-input');
if (chatInputContainerElement) {
chatInputContainerElement.style.height = '';
chatInputContainerElement.style.height =
Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
chatInputContainerElement.scrollTop = chatInputContainerElement.scrollHeight;
}
await tick();
@@ -93,12 +91,12 @@
<div class="m-auto w-full max-w-6xl px-2 @2xl:px-20 translate-y-6 py-24 text-center">
{#if $temporaryChatEnabled}
<Tooltip
content="This chat won't appear in history and your messages will not be saved."
content={$i18n.t('This chat won’t appear in history and your messages will not be saved.')}
className="w-full flex justify-center mb-0.5"
placement="top"
>
<div class="flex items-center gap-2 text-gray-500 font-medium text-lg my-2 w-fit">
<EyeSlash strokeWidth="2.5" className="size-5" /> Temporary Chat
<EyeSlash strokeWidth="2.5" className="size-5" />{$i18n.t('Temporary Chat')}
</div>
</Tooltip>
{/if}
@@ -142,7 +140,7 @@
{#if models[selectedModelIdx]?.name}
{models[selectedModelIdx]?.name}
{:else}
{$i18n.t('Hello, {{name}}', { name: $user.name })}
{$i18n.t('Hello, {{name}}', { name: $user?.name })}
{/if}
</div>
</div>
@@ -31,8 +31,8 @@
let profileImageInputElement: HTMLInputElement;
const submitHandler = async () => {
if (name !== $user.name) {
if (profileImageUrl === generateInitialsImage($user.name) || profileImageUrl === '') {
if (name !== $user?.name) {
if (profileImageUrl === generateInitialsImage($user?.name) || profileImageUrl === '') {
profileImageUrl = generateInitialsImage(name);
}
}
@@ -75,8 +75,8 @@
};
onMount(async () => {
name = $user.name;
profileImageUrl = $user.profile_image_url;
name = $user?.name;
profileImageUrl = $user?.profile_image_url;
webhookUrl = $settings?.notifications?.webhook_url ?? '';
APIKey = await getAPIKey(localStorage.token).catch((error) => {
@@ -214,7 +214,7 @@
<button
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850"
on:click={async () => {
const url = await getGravatarUrl(localStorage.token, $user.email);
const url = await getGravatarUrl(localStorage.token, $user?.email);
profileImageUrl = url;
}}>{$i18n.t('Use Gravatar')}</button
@@ -236,10 +236,11 @@
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
class="w-full text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
bind:value={name}
required
placeholder={$i18n.t('Enter your name')}
/>
</div>
</div>
@@ -268,7 +269,7 @@
<UpdatePassword />
</div>
<hr class="border-gray-100 dark:border-gray-850 my-4" />
<hr class="border-gray-50 dark:border-gray-850 my-2" />
<div class="flex justify-between items-center text-sm">
<div class=" font-medium">{$i18n.t('API keys')}</div>
@@ -786,62 +786,6 @@
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'Control the repetition of token sequences in the generated text. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 1.1) will be more lenient. At 1, it is disabled.'
)}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Repeat Penalty (Ollama)')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
type="button"
on:click={() => {
params.repeat_penalty = (params?.repeat_penalty ?? null) === null ? 1.1 : null;
}}
>
{#if (params?.repeat_penalty ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
</Tooltip>
{#if (params?.repeat_penalty ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="-2"
max="2"
step="0.05"
bind:value={params.repeat_penalty}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div>
<input
bind:value={params.repeat_penalty}
type="number"
class=" bg-transparent text-center w-14"
min="-2"
max="2"
step="any"
/>
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t('Sets how far back for the model to look back to prevent repetition.')}
@@ -952,6 +896,172 @@
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.'
)}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Tokens To Keep On Context Refresh (num_keep)')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
type="button"
on:click={() => {
params.num_keep = (params?.num_keep ?? null) === null ? 24 : null;
}}
>
{#if (params?.num_keep ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
</Tooltip>
{#if (params?.num_keep ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="-1"
max="10240000"
step="1"
bind:value={params.num_keep}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div class="">
<input
bind:value={params.num_keep}
type="number"
class=" bg-transparent text-center w-14"
min="-1"
step="1"
/>
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.'
)}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Max Tokens (num_predict)')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
type="button"
on:click={() => {
params.max_tokens = (params?.max_tokens ?? null) === null ? 128 : null;
}}
>
{#if (params?.max_tokens ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
</Tooltip>
{#if (params?.max_tokens ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="-2"
max="131072"
step="1"
bind:value={params.max_tokens}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div>
<input
bind:value={params.max_tokens}
type="number"
class=" bg-transparent text-center w-14"
min="-2"
step="1"
/>
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'Control the repetition of token sequences in the generated text. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 1.1) will be more lenient. At 1, it is disabled.'
)}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Repeat Penalty (Ollama)')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
type="button"
on:click={() => {
params.repeat_penalty = (params?.repeat_penalty ?? null) === null ? 1.1 : null;
}}
>
{#if (params?.repeat_penalty ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
</Tooltip>
{#if (params?.repeat_penalty ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="-2"
max="2"
step="0.05"
bind:value={params.repeat_penalty}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div>
<input
bind:value={params.repeat_penalty}
type="number"
class=" bg-transparent text-center w-14"
min="-2"
max="2"
step="any"
/>
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t('Sets the size of the context window used to generate the next token.')}
@@ -1061,116 +1171,6 @@
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.'
)}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Tokens To Keep On Context Refresh (num_keep)')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
type="button"
on:click={() => {
params.num_keep = (params?.num_keep ?? null) === null ? 24 : null;
}}
>
{#if (params?.num_keep ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
</Tooltip>
{#if (params?.num_keep ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="-1"
max="10240000"
step="1"
bind:value={params.num_keep}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div class="">
<input
bind:value={params.num_keep}
type="number"
class=" bg-transparent text-center w-14"
min="-1"
step="1"
/>
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.'
)}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Max Tokens (num_predict)')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
type="button"
on:click={() => {
params.max_tokens = (params?.max_tokens ?? null) === null ? 128 : null;
}}
>
{#if (params?.max_tokens ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
</Tooltip>
{#if (params?.max_tokens ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="-2"
max="131072"
step="1"
bind:value={params.max_tokens}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div>
<input
bind:value={params.max_tokens}
type="number"
class=" bg-transparent text-center w-14"
min="-2"
step="1"
/>
</div>
</div>
{/if}
</div>
{#if admin}
<div class=" py-0.5 w-full justify-between">
<Tooltip
@@ -293,7 +293,7 @@
<div class="flex-1">
<input
list="voice-list"
class="w-full rounded-lg py-2 px-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
class="w-full text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
bind:value={voice}
placeholder="Select a voice"
/>
@@ -330,7 +330,7 @@
<div class="flex w-full">
<div class="flex-1">
<select
class="w-full rounded-lg py-2 px-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
class="w-full text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
bind:value={voice}
>
<option value="" selected={voice !== ''}>{$i18n.t('Default')}</option>
@@ -361,7 +361,7 @@
<div class="flex-1">
<input
list="voice-list"
class="w-full rounded-lg py-2 px-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
class="w-full text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
bind:value={voice}
placeholder="Select a voice"
/>
@@ -16,6 +16,7 @@
import { onMount, getContext } from 'svelte';
import { goto } from '$app/navigation';
import { toast } from 'svelte-sonner';
import ArchivedChatsModal from '$lib/components/layout/Sidebar/ArchivedChatsModal.svelte';
const i18n = getContext('i18n');
@@ -26,6 +27,7 @@
let showArchiveConfirm = false;
let showDeleteConfirm = false;
let showArchivedChatsModal = false;
let chatImportInputElement: HTMLInputElement;
@@ -95,8 +97,16 @@
await chats.set(await getChatList(localStorage.token, $currentChatPage));
scrollPaginationEnabled.set(true);
};
const handleArchivedChatsChange = async () => {
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
scrollPaginationEnabled.set(true);
};
</script>
<ArchivedChatsModal bind:show={showArchivedChatsModal} on:change={handleArchivedChatsChange} />
<div class="flex flex-col h-full justify-between space-y-3 text-sm">
<div class=" space-y-2 overflow-y-scroll max-h-[28rem] lg:max-h-full">
<div class="flex flex-col">
@@ -157,6 +167,32 @@
<hr class=" border-gray-100 dark:border-gray-850" />
<div class="flex flex-col">
<button
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
showArchivedChatsModal = true;
}}
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
d="M3.375 3C2.339 3 1.5 3.84 1.5 4.875v.75c0 1.036.84 1.875 1.875 1.875h17.25c1.035 0 1.875-.84 1.875-1.875v-.75C22.5 3.839 21.66 3 20.625 3H3.375Z"
/>
<path
fill-rule="evenodd"
d="m3.087 9 .54 9.176A3 3 0 0 0 6.62 21h10.757a3 3 0 0 0 2.995-2.824L20.913 9H3.087ZM12 10.5a.75.75 0 0 1 .75.75v4.94l1.72-1.72a.75.75 0 1 1 1.06 1.06l-3 3a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 1 1 1.06-1.06l1.72 1.72v-4.94a.75.75 0 0 1 .75-.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">{$i18n.t('Archived Chats')}</div>
</button>
{#if showArchiveConfirm}
<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
<div class="flex items-center space-x-3">
@@ -308,15 +308,16 @@
</div>
</div>
{#if $user.role === 'admin' || $user?.permissions.chat?.controls}
<hr class="border-gray-100 dark:border-gray-850 my-3" />
{#if $user?.role === 'admin' || $user?.permissions.chat?.controls}
<hr class="border-gray-50 dark:border-gray-850 my-3" />
<div>
<div class=" my-2.5 text-sm font-medium">{$i18n.t('System Prompt')}</div>
<textarea
<Textarea
bind:value={system}
class="w-full rounded-lg p-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden resize-none"
className="w-full text-sm bg-white dark:text-gray-300 dark:bg-gray-900 outline-hidden resize-none"
rows="4"
placeholder={$i18n.t('Enter system prompt here')}
/>
</div>
@@ -358,7 +359,7 @@
{#if keepAlive !== null}
<div class="flex mt-1 space-x-2">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
class="w-full text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
bind:value={keepAlive}
@@ -398,7 +399,7 @@
{#if requestFormat !== null}
<div class="flex mt-1 space-x-2">
<Textarea
className="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
className="w-full text-sm dark:text-gray-300 dark:bg-gray-900 outline-hidden"
placeholder={$i18n.t('e.g. "json" or a JSON schema')}
bind:value={requestFormat}
/>
@@ -39,7 +39,7 @@
let landingPageMode = '';
let chatBubble = true;
let chatDirection: 'LTR' | 'RTL' = 'LTR';
let chatDirection: 'LTR' | 'RTL' | 'auto' = 'auto';
let ctrlEnterToSend = false;
let collapseCodeBlocks = false;
@@ -211,7 +211,13 @@
};
const toggleChangeChatDirection = async () => {
chatDirection = chatDirection === 'LTR' ? 'RTL' : 'LTR';
if (chatDirection === 'auto') {
chatDirection = 'LTR';
} else if (chatDirection === 'LTR') {
chatDirection = 'RTL';
} else if (chatDirection === 'RTL') {
chatDirection = 'auto';
}
saveSettings({ chatDirection });
};
@@ -257,7 +263,7 @@
widescreenMode = $settings.widescreenMode ?? false;
splitLargeChunks = $settings.splitLargeChunks ?? false;
scrollOnBranchChange = $settings.scrollOnBranchChange ?? true;
chatDirection = $settings.chatDirection ?? 'LTR';
chatDirection = $settings.chatDirection ?? 'auto';
userLocation = $settings.userLocation ?? false;
notificationSound = $settings.notificationSound ?? true;
@@ -412,8 +418,10 @@
>
{#if chatDirection === 'LTR'}
<span class="ml-2 self-center">{$i18n.t('LTR')}</span>
{:else}
{:else if chatDirection === 'RTL'}
<span class="ml-2 self-center">{$i18n.t('RTL')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Auto')}</span>
{/if}
</button>
</div>
@@ -441,7 +449,7 @@
</div>
</div>
{#if $user.role === 'admin'}
{#if $user?.role === 'admin'}
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs">
@@ -82,7 +82,7 @@
<div class="relative overflow-x-auto">
<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
<thead
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-850"
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 border-gray-50 dark:border-gray-850"
>
<tr>
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
@@ -94,7 +94,7 @@
</thead>
<tbody>
{#each memories as memory}
<tr class="border-b dark:border-gray-850 items-center">
<tr class="border-b border-gray-50 dark:border-gray-850 items-center">
<td class="px-3 py-1">
<div class="line-clamp-1">
{memory.content}
+11 -4
View File
@@ -39,7 +39,7 @@
});
</script>
<AddServerModal bind:show={showConnectionModal} onSubmit={addConnectionHandler} />
<AddServerModal bind:show={showConnectionModal} onSubmit={addConnectionHandler} direct />
<form
class="flex flex-col h-full justify-between text-sm"
@@ -74,9 +74,8 @@
<div class="flex flex-col gap-1.5">
{#each servers as server, idx}
<Connection
bind:url={server.url}
bind:key={server.key}
bind:config={server.config}
bind:connection={server}
direct
onSubmit={() => {
updateHandler();
}}
@@ -98,6 +97,14 @@
)}
</div>
</div>
<div class=" text-xs text-gray-600 dark:text-gray-300 mb-2">
<a
class="underline"
href="https://github.com/open-webui/openapi-servers"
target="_blank">{$i18n.t('Learn more about OpenAPI tool servers.')}</a
>
</div>
</div>
</div>
{:else}
@@ -11,11 +11,8 @@
export let onDelete = () => {};
export let onSubmit = () => {};
export let pipeline = false;
export let url = '';
export let key = '';
export let config = {};
export let connection = null;
export let direct = false;
let showConfigModal = false;
let showDeleteConfirmDialog = false;
@@ -23,21 +20,15 @@
<AddServerModal
edit
direct
{direct}
bind:show={showConfigModal}
connection={{
url,
key,
config
}}
{connection}
onDelete={() => {
showDeleteConfirmDialog = true;
}}
onSubmit={(connection) => {
url = connection.url;
key = connection.key;
config = connection.config;
onSubmit(connection);
onSubmit={(c) => {
connection = c;
onSubmit(c);
}}
/>
@@ -52,12 +43,12 @@
<div class="flex w-full gap-2 items-center">
<Tooltip
className="w-full relative"
content={$i18n.t(`WebUI will make requests to "{{url}}/openapi.json"`, {
url: url
content={$i18n.t(`WebUI will make requests to "{{url}}"`, {
url: `${connection?.url}/${connection?.path ?? 'openapi.json'}`
})}
placement="top-start"
>
{#if !(config?.enable ?? true)}
{#if !(connection?.config?.enable ?? true)}
<div
class="absolute top-0 bottom-0 left-0 right-0 opacity-60 bg-white dark:bg-gray-900 z-10"
></div>
@@ -65,19 +56,21 @@
<div class="flex w-full">
<div class="flex-1 relative">
<input
class=" outline-hidden w-full bg-transparent {pipeline ? 'pr-8' : ''}"
class=" outline-hidden w-full bg-transparent"
placeholder={$i18n.t('API Base URL')}
bind:value={url}
bind:value={connection.url}
autocomplete="off"
/>
</div>
<SensitiveInput
inputClassName=" outline-hidden bg-transparent w-full"
placeholder={$i18n.t('API Key')}
bind:value={key}
required={false}
/>
{#if (connection?.auth_type ?? 'bearer') === 'bearer'}
<SensitiveInput
inputClassName=" outline-hidden bg-transparent w-full"
placeholder={$i18n.t('API Key')}
bind:value={connection.key}
required={false}
/>
{/if}
</div>
</Tooltip>
+3 -3
View File
@@ -462,7 +462,7 @@
<div class=" self-center">{$i18n.t('Interface')}</div>
</button>
{:else if tabId === 'connections'}
{#if $user.role === 'admin' || ($user.role === 'user' && $config?.features?.enable_direct_connections)}
{#if $user?.role === 'admin' || ($user?.role === 'user' && $config?.features?.enable_direct_connections)}
<button
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
'connections'
@@ -488,7 +488,7 @@
</button>
{/if}
{:else if tabId === 'tools'}
{#if $user.role === 'admin' || ($user.role === 'user' && $config?.features?.enable_direct_tools)}
{#if $user?.role === 'admin' || ($user?.role === 'user' && $user?.permissions?.features?.direct_tool_servers)}
<button
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
'tools'
@@ -636,7 +636,7 @@
<div class=" self-center">{$i18n.t('About')}</div>
</button>
{:else if tabId === 'admin'}
{#if $user.role === 'admin'}
{#if $user?.role === 'admin'}
<button
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
'admin'
+84 -41
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import { models, config, toolServers } from '$lib/stores';
import { models, config, toolServers, tools } from '$lib/stores';
import { toast } from 'svelte-sonner';
import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
@@ -11,6 +11,11 @@
import Collapsible from '../common/Collapsible.svelte';
export let show = false;
export let selectedToolIds = [];
let selectedTools = [];
$: selectedTools = $tools.filter((tool) => selectedToolIds.includes(tool.id));
const i18n = getContext('i18n');
</script>
@@ -18,7 +23,7 @@
<Modal bind:show size="md">
<div>
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-0.5">
<div class=" text-lg font-medium self-center">{$i18n.t('Available Tool Servers')}</div>
<div class=" text-lg font-medium self-center">{$i18n.t('Available Tools')}</div>
<button
class="self-center"
on:click={() => {
@@ -38,47 +43,85 @@
</button>
</div>
<div class="px-5 pb-5 w-full flex flex-col justify-center">
<div class=" text-sm dark:text-gray-300 mb-2">
Open WebUI can use tools provided by any OpenAPI server. <br /><a
class="underline"
href="https://github.com/open-webui/openapi-servers"
target="_blank">Learn more about OpenAPI tool servers.</a
>
</div>
<div class=" text-sm dark:text-gray-300 mb-1">
{#each $toolServers as toolServer}
<Collapsible buttonClassName="w-full" chevron>
<div>
<div class="text-base font-medium dark:text-gray-100 text-gray-800">
{toolServer?.openapi?.info?.title} - v{toolServer?.openapi?.info?.version}
</div>
{#if selectedTools.length > 0}
{#if $toolServers.length > 0}
<div class=" flex justify-between dark:text-gray-300 px-5 pb-1">
<div class=" text-base font-medium self-center">{$i18n.t('Tools')}</div>
</div>
{/if}
<div class="text-sm text-gray-500">
{toolServer?.openapi?.info?.description}
</div>
<div class="text-sm text-gray-500">
{toolServer?.url}
</div>
</div>
<div slot="content">
{#each toolServer?.specs ?? [] as tool_spec}
<div class="my-1">
<div class="font-medium text-gray-800 dark:text-gray-100">
{tool_spec?.name}
</div>
<div>
{tool_spec?.description}
</div>
<div class="px-5 pb-3 w-full flex flex-col justify-center">
<div class=" text-sm dark:text-gray-300 mb-1">
{#each selectedTools as tool}
<Collapsible buttonClassName="w-full mb-0.5">
<div>
<div class="text-sm font-medium dark:text-gray-100 text-gray-800">
{tool?.name}
</div>
{/each}
</div>
</Collapsible>
{/each}
{#if tool?.meta?.description}
<div class="text-xs text-gray-500">
{tool?.meta?.description}
</div>
{/if}
</div>
<!-- <div slot="content">
{JSON.stringify(tool, null, 2)}
</div> -->
</Collapsible>
{/each}
</div>
</div>
</div>
{/if}
{#if $toolServers.length > 0}
<div class=" flex justify-between dark:text-gray-300 px-5 pb-0.5">
<div class=" text-base font-medium self-center">{$i18n.t('Tool Servers')}</div>
</div>
<div class="px-5 pb-5 w-full flex flex-col justify-center">
<div class=" text-xs text-gray-600 dark:text-gray-300 mb-2">
{$i18n.t('Open WebUI can use tools provided by any OpenAPI server.')} <br /><a
class="underline"
href="https://github.com/open-webui/openapi-servers"
target="_blank">{$i18n.t('Learn more about OpenAPI tool servers.')}</a
>
</div>
<div class=" text-sm dark:text-gray-300 mb-1">
{#each $toolServers as toolServer}
<Collapsible buttonClassName="w-full" chevron>
<div>
<div class="text-sm font-medium dark:text-gray-100 text-gray-800">
{toolServer?.openapi?.info?.title} - v{toolServer?.openapi?.info?.version}
</div>
<div class="text-xs text-gray-500">
{toolServer?.openapi?.info?.description}
</div>
<div class="text-xs text-gray-500">
{toolServer?.url}
</div>
</div>
<div slot="content">
{#each toolServer?.specs ?? [] as tool_spec}
<div class="my-1">
<div class="font-medium text-gray-800 dark:text-gray-100">
{tool_spec?.name}
</div>
<div>
{tool_spec?.description}
</div>
</div>
{/each}
</div>
</Collapsible>
{/each}
</div>
</div>
{/if}
</div>
</Modal>
+60 -25
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { decode } from 'html-entities';
import { v4 as uuidv4 } from 'uuid';
import { getContext, createEventDispatcher } from 'svelte';
const i18n = getContext('i18n');
@@ -36,6 +37,7 @@
import Spinner from './Spinner.svelte';
import CodeBlock from '../chat/Messages/CodeBlock.svelte';
import Markdown from '../chat/Messages/Markdown.svelte';
import Image from './Image.svelte';
export let open = false;
@@ -53,9 +55,19 @@
export let disabled = false;
export let hide = false;
function formatJSONString(obj) {
const collapsibleId = uuidv4();
function parseJSONString(str) {
try {
const parsed = JSON.parse(JSON.parse(obj));
return parseJSONString(JSON.parse(str));
} catch (e) {
return str;
}
}
function formatJSONString(str) {
try {
const parsed = parseJSONString(str);
// If parsed is an object/array, then it's valid JSON
if (typeof parsed === 'object') {
return JSON.stringify(parsed, null, 2);
@@ -65,7 +77,7 @@
}
} catch (e) {
// Not valid JSON, return as-is
return obj;
return str;
}
}
</script>
@@ -119,15 +131,15 @@
{:else if attributes?.type === 'tool_calls'}
{#if attributes?.done === 'true'}
<Markdown
id={`tool-calls-${attributes?.id}`}
content={$i18n.t('View Result from `{{NAME}}`', {
id={`${collapsibleId}-tool-calls-${attributes?.id}`}
content={$i18n.t('View Result from **{{NAME}}**', {
NAME: attributes.name
})}
/>
{:else}
<Markdown
id={`tool-calls-${attributes?.id}`}
content={$i18n.t('Executing `{{NAME}}`...', {
id={`${collapsibleId}-tool-calls-${attributes?.id}-executing`}
content={$i18n.t('Executing **{{NAME}}**...', {
NAME: attributes.name
})}
/>
@@ -188,32 +200,55 @@
</div>
{/if}
{#if !grow}
{#if open && !hide}
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
{#if attributes?.type === 'tool_calls'}
{@const args = decode(attributes?.arguments)}
{@const result = decode(attributes?.result ?? '')}
{#if attributes?.type === 'tool_calls'}
{@const args = decode(attributes?.arguments)}
{@const result = decode(attributes?.result ?? '')}
{@const files = parseJSONString(decode(attributes?.files ?? ''))}
{#if attributes?.done === 'true'}
<Markdown
id={`tool-calls-${attributes?.id}-result`}
content={`> \`\`\`json
{#if !grow}
{#if open && !hide}
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
{#if attributes?.type === 'tool_calls'}
{#if attributes?.done === 'true'}
<Markdown
id={`${collapsibleId}-tool-calls-${attributes?.id}-result`}
content={`> \`\`\`json
> ${formatJSONString(args)}
> ${formatJSONString(result)}
> \`\`\``}
/>
{:else}
<Markdown
id={`tool-calls-${attributes?.id}-result`}
content={`> \`\`\`json
/>
{:else}
<Markdown
id={`${collapsibleId}-tool-calls-${attributes?.id}-result`}
content={`> \`\`\`json
> ${formatJSONString(args)}
> \`\`\``}
/>
/>
{/if}
{:else}
<slot name="content" />
{/if}
{:else}
<slot name="content" />
</div>
{/if}
{#if attributes?.done === 'true'}
{#if typeof files === 'object'}
{#each files ?? [] as file, idx}
{#if file.startsWith('data:image/')}
<Image
id={`${collapsibleId}-tool-calls-${attributes?.id}-result-${idx}`}
src={file}
alt="Image"
/>
{/if}
{/each}
{/if}
{/if}
{/if}
{:else if !grow}
{#if open && !hide}
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
<slot name="content" />
</div>
{/if}
{/if}
+1 -1
View File
@@ -7,7 +7,7 @@
export let show = true;
export let size = 'md';
export let containerClassName = 'p-3';
export let className = 'bg-gray-50 dark:bg-gray-900 rounded-2xl';
export let className = 'bg-white dark:bg-gray-900 rounded-2xl';
let modalElement = null;
let mounted = false;
+2 -2
View File
@@ -167,7 +167,7 @@
{#if $user !== undefined}
<UserMenu
className="max-w-[200px]"
role={$user.role}
role={$user?.role}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChats.set(true);
@@ -180,7 +180,7 @@
>
<div class=" self-center">
<img
src={$user.profile_image_url}
src={$user?.profile_image_url}
class="size-6 object-cover rounded-full"
alt="User profile"
draggable="false"
+7 -7
View File
@@ -446,7 +446,7 @@
});
if (res) {
$socket.emit('join-channels', { auth: { token: $user.token } });
$socket.emit('join-channels', { auth: { token: $user?.token } });
await initChannels();
showCreateChannel = false;
}
@@ -627,13 +627,13 @@
? 'opacity-20'
: ''}"
>
{#if $config?.features?.enable_channels && ($user.role === 'admin' || $channels.length > 0) && !search}
{#if $config?.features?.enable_channels && ($user?.role === 'admin' || $channels.length > 0) && !search}
<Folder
className="px-2 mt-0.5"
name={$i18n.t('Channels')}
dragAndDrop={false}
onAdd={async () => {
if ($user.role === 'admin') {
if ($user?.role === 'admin') {
await tick();
setTimeout(() => {
@@ -891,9 +891,9 @@
<div class="px-2">
<div class="flex flex-col font-primary">
{#if $user !== undefined}
{#if $user !== undefined && $user !== null}
<UserMenu
role={$user.role}
role={$user?.role}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChats.set(true);
@@ -908,12 +908,12 @@
>
<div class=" self-center mr-3">
<img
src={$user.profile_image_url}
src={$user?.profile_image_url}
class=" max-w-[30px] object-cover rounded-full"
alt="User profile"
/>
</div>
<div class=" self-center font-medium">{$user.name}</div>
<div class=" self-center font-medium">{$user?.name}</div>
</button>
</UserMenu>
{/if}
@@ -134,7 +134,7 @@
<div class="relative overflow-x-auto">
<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
<thead
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-850"
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 border-gray-50 dark:border-gray-850"
>
<tr>
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
@@ -150,7 +150,7 @@
.includes(searchValue.toLowerCase())) as chat, idx}
<tr
class="bg-transparent {idx !== chats.length - 1 &&
'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs"
'border-b'} dark:bg-gray-900 border-gray-50 dark:border-gray-850 text-xs"
>
<td class="px-3 py-1 w-2/3">
<a href="/c/{chat.id}" target="_blank">
@@ -163,7 +163,7 @@
{#if focused && (filteredOptions.length > 0 || filteredTags.length > 0)}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="absolute top-0 mt-8 left-0 right-1 border dark:border-gray-900 bg-gray-50 dark:bg-gray-950 rounded-lg z-10 shadow-lg"
class="absolute top-0 mt-8 left-0 right-1 border border-gray-100 dark:border-gray-900 bg-gray-50 dark:bg-gray-950 rounded-lg z-10 shadow-lg"
in:fade={{ duration: 50 }}
on:mouseenter={() => {
selectedIdx = null;
+26 -25
View File
@@ -93,13 +93,10 @@
const tab = await window.open(`${url}/models/create`, '_blank');
// Define the event handler function
const messageHandler = (event) => {
if (event.origin !== url) return;
if (event.data === 'loaded') {
tab.postMessage(JSON.stringify(model), '*');
// Remove the event listener after handling the message
window.removeEventListener('message', messageHandler);
}
};
@@ -477,29 +474,33 @@
</div>
</button>
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
downloadModels(models);
}}
>
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Models')}</div>
{#if models.length}
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
downloadModels(models);
}}
>
<div class=" self-center mr-2 font-medium line-clamp-1">
{$i18n.t('Export Models')}
</div>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-3.5 h-3.5"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-3.5 h-3.5"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
{/if}
</div>
</div>
{/if}
+29 -26
View File
@@ -285,33 +285,36 @@
</div>
</button>
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
// promptsImportInputElement.click();
let blob = new Blob([JSON.stringify(prompts)], {
type: 'application/json'
});
saveAs(blob, `prompts-export-${Date.now()}.json`);
}}
>
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Prompts')}</div>
{#if prompts.length}
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
let blob = new Blob([JSON.stringify(prompts)], {
type: 'application/json'
});
saveAs(blob, `prompts-export-${Date.now()}.json`);
}}
>
<div class=" self-center mr-2 font-medium line-clamp-1">
{$i18n.t('Export Prompts')}
</div>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
{/if}
</div>
</div>
{/if}
+34 -36
View File
@@ -71,13 +71,10 @@
const tab = await window.open(`${url}/tools/create`, '_blank');
// Define the event handler function
const messageHandler = (event) => {
if (event.origin !== url) return;
if (event.data === 'loaded') {
tab.postMessage(JSON.stringify(item), '*');
// Remove the event listener after handling the message
window.removeEventListener('message', messageHandler);
}
};
@@ -124,8 +121,7 @@
if (res) {
toast.success($i18n.t('Tool deleted successfully'));
init();
await init();
}
};
@@ -398,39 +394,41 @@
</div>
</button>
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
const _tools = await exportTools(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (_tools) {
let blob = new Blob([JSON.stringify(_tools)], {
type: 'application/json'
{#if tools.length}
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
const _tools = await exportTools(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
saveAs(blob, `tools-export-${Date.now()}.json`);
}
}}
>
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Tools')}</div>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
if (_tools) {
let blob = new Blob([JSON.stringify(_tools)], {
type: 'application/json'
});
saveAs(blob, `tools-export-${Date.now()}.json`);
}
}}
>
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Tools')}</div>
<div class=" self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
{/if}
</div>
</div>
{/if}
+25 -5
View File
@@ -6,7 +6,7 @@
"(latest)": "(الأخير)",
"(Ollama)": "",
"{{ models }}": "{{ نماذج }}",
"{{COUNT}} Available Tool Servers": "",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "",
"{{COUNT}} Replies": "",
"{{user}}'s Chats": "دردشات {{user}}",
@@ -108,8 +108,10 @@
"Attribute for Username": "",
"Audio": "صوتي",
"August": "أغسطس",
"Auth": "",
"Authenticate": "",
"Authentication": "",
"Auto": "",
"Auto-Copy Response to Clipboard": "النسخ التلقائي للاستجابة إلى الحافظة",
"Auto-playback response": "استجابة التشغيل التلقائي",
"Autocomplete Generation": "",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 الرابط الرئيسي",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 الرابط مطلوب",
"Available list": "",
"Available Tool Servers": "",
"Available Tools": "",
"available!": "متاح",
"Awful": "",
"Azure AI Speech": "",
@@ -218,7 +220,10 @@
"Confirm your new password": "",
"Connect to your own OpenAI compatible API endpoints.": "",
"Connect to your own OpenAPI compatible external tool servers.": "",
"Connection failed": "",
"Connection successful": "",
"Connections": "اتصالات",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
"Contact Admin for WebUI Access": "",
"Content": "الاتصال",
@@ -303,6 +308,7 @@
"Direct Connections": "",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
"Direct Connections settings updated": "",
"Direct Tool Servers": "",
"Disabled": "",
"Discover a function": "",
"Discover a model": "اكتشف نموذجا",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "",
"Enter Key Behavior": "",
"Enter language codes": "أدخل كود اللغة",
"Enter Mistral API Key": "",
"Enter Model ID": "",
"Enter model tag (e.g. {{modelTag}})": "(e.g. {{modelTag}}) أدخل الموديل تاق",
"Enter Mojeek Search API Key": "",
@@ -436,6 +443,7 @@
"Enter server port": "",
"Enter stop sequence": "أدخل تسلسل التوقف",
"Enter system prompt": "",
"Enter system prompt here": "",
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
@@ -449,6 +457,7 @@
"Enter Your Email": "أدخل البريد الاكتروني",
"Enter Your Full Name": "أدخل الاسم كامل",
"Enter your message": "",
"Enter your name": "",
"Enter your new password": "",
"Enter Your Password": "ادخل كلمة المرور",
"Enter Your Role": "أدخل الصلاحيات",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
"Exclude": "",
"Execute code for analysis": "",
"Executing `{{NAME}}`...": "",
"Executing **{{NAME}}**...": "",
"Expand": "",
"Experimental": "تجريبي",
"Explain": "",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
"Failed to fetch models": "",
"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
"Failed to save connections": "",
"Failed to save models configuration": "",
"Failed to update settings": "",
"Failed to upload file.": "",
@@ -525,6 +535,7 @@
"Forge new paths": "",
"Form": "",
"Format your variables using brackets like this:": "",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "عقوبة التردد",
"Full Context Mode": "",
"Function": "",
@@ -644,6 +655,7 @@
"LDAP": "",
"LDAP server updated": "",
"Leaderboard": "",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
@@ -694,6 +706,8 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "تم تحميل النموذج '{{modelName}}' بنجاح",
"Model '{{modelTag}}' is already in queue for downloading.": "النموذج '{{modelTag}}' موجود بالفعل في قائمة الانتظار للتحميل",
@@ -780,6 +794,7 @@
"Open file": "",
"Open in full screen": "",
"Open new chat": "فتح محادثة جديده",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "OpenAI API.مطلوب مفتاح ",
"OpenAI API settings updated": "",
"OpenAI URL/Key required.": "URL/مفتاح OpenAI.مطلوب عنوان ",
"openapi.json Path": "",
"or": "أو",
"Organize your users": "",
"Other": "آخر",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "",
"Please do not close the settings page while loading the model.": "",
"Please enter a prompt": "",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "",
"Please select a model first.": "",
"Please select a model.": "",
@@ -1042,6 +1060,7 @@
"Thinking...": "",
"This action cannot be undone. Do you wish to continue?": "",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
"This chat won’t appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "وهذا يضمن حفظ محادثاتك القيمة بشكل آمن في قاعدة بياناتك الخلفية. شكرًا لك!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
@@ -1089,6 +1108,7 @@
"Tool ID": "",
"Tool imported successfully": "",
"Tool Name": "",
"Tool Servers": "",
"Tool updated successfully": "",
"Tools": "",
"Tools Access": "",
@@ -1158,7 +1178,7 @@
"Version": "إصدار",
"Version {{selectedVersion}} of {{totalVersions}}": "",
"View Replies": "",
"View Result from `{{NAME}}`": "",
"View Result from **{{NAME}}**": "",
"Visibility": "",
"Voice": "",
"Voice Input": "",
@@ -1176,9 +1196,9 @@
"Webhook URL": "Webhook الرابط",
"WebUI Settings": "WebUI اعدادات",
"WebUI URL": "",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "",
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
"What are you trying to achieve?": "",
"What are you working on?": "",
"What’s New in": "ما هو الجديد",
File diff suppressed because it is too large Load Diff
+25 -5
View File
@@ -6,7 +6,7 @@
"(latest)": "(последна)",
"(Ollama)": "",
"{{ models }}": "{{ models }}",
"{{COUNT}} Available Tool Servers": "",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "",
"{{COUNT}} Replies": "{{COUNT}} Отговори",
"{{user}}'s Chats": "{{user}}'s чатове",
@@ -108,8 +108,10 @@
"Attribute for Username": "Атрибут за потребителско име",
"Audio": "Аудио",
"August": "Август",
"Auth": "",
"Authenticate": "Удостоверяване",
"Authentication": "Автентикация",
"Auto": "",
"Auto-Copy Response to Clipboard": "Автоматично копиране на отговор в клипборда",
"Auto-playback response": "Автоматично възпроизвеждане на отговора",
"Autocomplete Generation": "Генериране на автоматично довършване",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Базов URL",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Базов URL е задължителен.",
"Available list": "Наличен списък",
"Available Tool Servers": "",
"Available Tools": "",
"available!": "наличен!",
"Awful": "Ужасно",
"Azure AI Speech": "Azure AI Реч",
@@ -218,7 +220,10 @@
"Confirm your new password": "Потвърдете новата си парола",
"Connect to your own OpenAI compatible API endpoints.": "Свържете се със собствени крайни точки на API, съвместими с OpenAI.",
"Connect to your own OpenAPI compatible external tool servers.": "",
"Connection failed": "",
"Connection successful": "",
"Connections": "Връзки",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
"Contact Admin for WebUI Access": "Свържете се с администратор за достъп до WebUI",
"Content": "Съдържание",
@@ -303,6 +308,7 @@
"Direct Connections": "Директни връзки",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Директните връзки позволяват на потребителите да се свързват със собствени OpenAI съвместими API крайни точки.",
"Direct Connections settings updated": "Настройките за директни връзки са актуализирани",
"Direct Tool Servers": "",
"Disabled": "Деактивирано",
"Discover a function": "Открийте функция",
"Discover a model": "Открийте модел",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "Въведете API ключ за Kagi Search",
"Enter Key Behavior": "",
"Enter language codes": "Въведете кодове на езика",
"Enter Mistral API Key": "",
"Enter Model ID": "Въведете ID на модела",
"Enter model tag (e.g. {{modelTag}})": "Въведете таг на модел (напр. {{modelTag}})",
"Enter Mojeek Search API Key": "Въведете API ключ за Mojeek Search",
@@ -436,6 +443,7 @@
"Enter server port": "Въведете порт на сървъра",
"Enter stop sequence": "Въведете стоп последователност",
"Enter system prompt": "Въведете системен промпт",
"Enter system prompt here": "",
"Enter Tavily API Key": "Въведете API ключ за Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Въведете публичния URL адрес на вашия WebUI. Този URL адрес ще бъде използван за генериране на връзки в известията.",
"Enter Tika Server URL": "Въведете URL адрес на Tika сървър",
@@ -449,6 +457,7 @@
"Enter Your Email": "Въведете имейл",
"Enter Your Full Name": "Въведете вашето пълно име",
"Enter your message": "Въведете съобщението си",
"Enter your name": "",
"Enter your new password": "Въведете новата си парола",
"Enter Your Password": "Въведете вашата парола",
"Enter Your Role": "Въведете вашата роля",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
"Exclude": "Изключи",
"Execute code for analysis": "Изпълнете код за анализ",
"Executing `{{NAME}}`...": "",
"Executing **{{NAME}}**...": "",
"Expand": "",
"Experimental": "Експериментално",
"Explain": "",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "Неуспешно създаване на API ключ.",
"Failed to fetch models": "Неуспешно извличане на модели",
"Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда",
"Failed to save connections": "",
"Failed to save models configuration": "Неуспешно запазване на конфигурацията на моделите",
"Failed to update settings": "Неуспешно актуализиране на настройките",
"Failed to upload file.": "Неуспешно качване на файл.",
@@ -525,6 +535,7 @@
"Forge new paths": "Изковете нови пътища",
"Form": "Форма",
"Format your variables using brackets like this:": "Форматирайте вашите променливи, използвайки скоби като това:",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "Наказание за честота",
"Full Context Mode": "Режим на пълен контекст",
"Function": "Функция",
@@ -644,6 +655,7 @@
"LDAP": "LDAP",
"LDAP server updated": "LDAP сървърът е актуализиран",
"Leaderboard": "Класация",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "Оставете празно за неограничено",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
@@ -694,6 +706,8 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "Модел",
"Model '{{modelName}}' has been successfully downloaded.": "Моделът '{{modelName}}' беше успешно свален.",
"Model '{{modelTag}}' is already in queue for downloading.": "Моделът '{{modelTag}}' е вече в очакване за сваляне.",
@@ -780,6 +794,7 @@
"Open file": "Отвори файл",
"Open in full screen": "Отвори на цял екран",
"Open new chat": "Отвори нов чат",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "Open WebUI използва вътрешно faster-whisper.",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "Open WebUI използва SpeechT5 и CMU Arctic говорителни вграждания.",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Версията на Open WebUI (v{{OPEN_WEBUI_VERSION}}) е по-ниска от необходимата версия (v{{REQUIRED_VERSION}})",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "OpenAI API ключ е задължителен.",
"OpenAI API settings updated": "Настройките на OpenAI API са актуализирани",
"OpenAI URL/Key required.": "OpenAI URL/Key е задължителен.",
"openapi.json Path": "",
"or": "или",
"Organize your users": "Организирайте вашите потребители",
"Other": "Друго",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "Моля, внимателно прегледайте следните предупреждения:",
"Please do not close the settings page while loading the model.": "Моля, не затваряйте страницата с настройки, докато моделът се зарежда.",
"Please enter a prompt": "Моля, въведете промпт",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "Моля, попълнете всички полета.",
"Please select a model first.": "Моля, първо изберете модел.",
"Please select a model.": "Моля, изберете модел.",
@@ -1042,6 +1060,7 @@
"Thinking...": "Мисля...",
"This action cannot be undone. Do you wish to continue?": "Това действие не може да бъде отменено. Желаете ли да продължите?",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
"This chat won’t appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Това гарантира, че ценните ви разговори се запазват сигурно във вашата бекенд база данни. Благодарим ви!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Това е експериментална функция, може да не работи според очакванията и подлежи на промяна по всяко време.",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
@@ -1089,6 +1108,7 @@
"Tool ID": "ID на инструмента",
"Tool imported successfully": "Инструментът е импортиран успешно",
"Tool Name": "Име на инструмента",
"Tool Servers": "",
"Tool updated successfully": "Инструментът е актуализиран успешно",
"Tools": "Инструменти",
"Tools Access": "Достъп до инструменти",
@@ -1158,7 +1178,7 @@
"Version": "Версия",
"Version {{selectedVersion}} of {{totalVersions}}": "Версия {{selectedVersion}} от {{totalVersions}}",
"View Replies": "Преглед на отговорите",
"View Result from `{{NAME}}`": "",
"View Result from **{{NAME}}**": "",
"Visibility": "Видимост",
"Voice": "Глас",
"Voice Input": "Гласов вход",
@@ -1176,9 +1196,9 @@
"Webhook URL": "Уебхук URL",
"WebUI Settings": "WebUI Настройки",
"WebUI URL": "WebUI URL",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI ще прави заявки към \"{{url}}/api/chat\"",
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI ще прави заявки към \"{{url}}/chat/completions\"",
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
"What are you trying to achieve?": "Какво се опитвате да постигнете?",
"What are you working on?": "Върху какво работите?",
"What’s New in": "",
+25 -5
View File
@@ -6,7 +6,7 @@
"(latest)": "(সর্বশেষ)",
"(Ollama)": "",
"{{ models }}": "{{ মডেল}}",
"{{COUNT}} Available Tool Servers": "",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "",
"{{COUNT}} Replies": "",
"{{user}}'s Chats": "{{user}}র চ্যাটস",
@@ -108,8 +108,10 @@
"Attribute for Username": "",
"Audio": "অডিও",
"August": "আগস্ট",
"Auth": "",
"Authenticate": "",
"Authentication": "",
"Auto": "",
"Auto-Copy Response to Clipboard": "রেসপন্সগুলো স্বয়ংক্রিভাবে ক্লিপবোর্ডে কপি হবে",
"Auto-playback response": "রেসপন্স অটো-প্লেব্যাক",
"Autocomplete Generation": "",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 বেজ ইউআরএল",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 বেজ ইউআরএল আবশ্যক",
"Available list": "",
"Available Tool Servers": "",
"Available Tools": "",
"available!": "উপলব্ধ!",
"Awful": "",
"Azure AI Speech": "",
@@ -218,7 +220,10 @@
"Confirm your new password": "",
"Connect to your own OpenAI compatible API endpoints.": "",
"Connect to your own OpenAPI compatible external tool servers.": "",
"Connection failed": "",
"Connection successful": "",
"Connections": "কানেকশনগুলো",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
"Contact Admin for WebUI Access": "",
"Content": "বিষয়বস্তু",
@@ -303,6 +308,7 @@
"Direct Connections": "",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
"Direct Connections settings updated": "",
"Direct Tool Servers": "",
"Disabled": "",
"Discover a function": "",
"Discover a model": "একটি মডেল আবিষ্কার করুন",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "",
"Enter Key Behavior": "",
"Enter language codes": "ল্যাঙ্গুয়েজ কোড লিখুন",
"Enter Mistral API Key": "",
"Enter Model ID": "",
"Enter model tag (e.g. {{modelTag}})": "মডেল ট্যাগ লিখুন (e.g. {{modelTag}})",
"Enter Mojeek Search API Key": "",
@@ -436,6 +443,7 @@
"Enter server port": "",
"Enter stop sequence": "স্টপ সিকোয়েন্স লিখুন",
"Enter system prompt": "",
"Enter system prompt here": "",
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
@@ -449,6 +457,7 @@
"Enter Your Email": "আপনার ইমেইল লিখুন",
"Enter Your Full Name": "আপনার পূর্ণ নাম লিখুন",
"Enter your message": "",
"Enter your name": "",
"Enter your new password": "",
"Enter Your Password": "আপনার পাসওয়ার্ড লিখুন",
"Enter Your Role": "আপনার রোল লিখুন",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
"Exclude": "",
"Execute code for analysis": "",
"Executing `{{NAME}}`...": "",
"Executing **{{NAME}}**...": "",
"Expand": "",
"Experimental": "পরিক্ষামূলক",
"Explain": "",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "API Key তৈরি করা যায়নি।",
"Failed to fetch models": "",
"Failed to read clipboard contents": "ক্লিপবোর্ডের বিষয়বস্তু পড়া সম্ভব হয়নি",
"Failed to save connections": "",
"Failed to save models configuration": "",
"Failed to update settings": "",
"Failed to upload file.": "",
@@ -525,6 +535,7 @@
"Forge new paths": "",
"Form": "",
"Format your variables using brackets like this:": "",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "ফ্রিকোয়েন্সি পেনাল্টি",
"Full Context Mode": "",
"Function": "",
@@ -644,6 +655,7 @@
"LDAP": "",
"LDAP server updated": "",
"Leaderboard": "",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
@@ -694,6 +706,8 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' মডেল সফলভাবে ডাউনলোড হয়েছে।",
"Model '{{modelTag}}' is already in queue for downloading.": "{{modelTag}} ডাউনলোডের জন্য আগে থেকেই অপেক্ষমান আছে।",
@@ -780,6 +794,7 @@
"Open file": "",
"Open in full screen": "",
"Open new chat": "নতুন চ্যাট খুলুন",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "OpenAI API কোড আবশ্যক",
"OpenAI API settings updated": "",
"OpenAI URL/Key required.": "OpenAI URL/Key আবশ্যক",
"openapi.json Path": "",
"or": "অথবা",
"Organize your users": "",
"Other": "অন্যান্য",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "",
"Please do not close the settings page while loading the model.": "",
"Please enter a prompt": "",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "",
"Please select a model first.": "",
"Please select a model.": "",
@@ -1042,6 +1060,7 @@
"Thinking...": "",
"This action cannot be undone. Do you wish to continue?": "",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
"This chat won’t appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "এটা নিশ্চিত করে যে, আপনার গুরুত্বপূর্ণ আলোচনা নিরাপদে আপনার ব্যাকএন্ড ডেটাবেজে সংরক্ষিত আছে। ধন্যবাদ!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
@@ -1089,6 +1108,7 @@
"Tool ID": "",
"Tool imported successfully": "",
"Tool Name": "",
"Tool Servers": "",
"Tool updated successfully": "",
"Tools": "",
"Tools Access": "",
@@ -1158,7 +1178,7 @@
"Version": "ভার্সন",
"Version {{selectedVersion}} of {{totalVersions}}": "",
"View Replies": "",
"View Result from `{{NAME}}`": "",
"View Result from **{{NAME}}**": "",
"Visibility": "",
"Voice": "",
"Voice Input": "",
@@ -1176,9 +1196,9 @@
"Webhook URL": "ওয়েবহুক URL",
"WebUI Settings": "WebUI সেটিংসমূহ",
"WebUI URL": "",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "",
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
"What are you trying to achieve?": "",
"What are you working on?": "",
"What’s New in": "এতে নতুন কী",
File diff suppressed because it is too large Load Diff
+25 -5
View File
@@ -6,7 +6,7 @@
"(latest)": "(últim)",
"(Ollama)": "(Ollama)",
"{{ models }}": "{{ models }}",
"{{COUNT}} Available Tool Servers": "{{COUNT}} Servidors d'eines disponibles",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "{{COUNT}} línies ocultes",
"{{COUNT}} Replies": "{{COUNT}} respostes",
"{{user}}'s Chats": "Els xats de {{user}}",
@@ -108,8 +108,10 @@
"Attribute for Username": "Atribut per al Nom d'usuari",
"Audio": "Àudio",
"August": "Agost",
"Auth": "",
"Authenticate": "Autenticar",
"Authentication": "Autenticació",
"Auto": "",
"Auto-Copy Response to Clipboard": "Copiar la resposta automàticament al porta-retalls",
"Auto-playback response": "Reproduir la resposta automàticament",
"Autocomplete Generation": "Generació automàtica",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "URL Base d'AUTOMATIC1111",
"AUTOMATIC1111 Base URL is required.": "Es requereix l'URL Base d'AUTOMATIC1111.",
"Available list": "Llista de disponibles",
"Available Tool Servers": "Servidors d'eines disponibles",
"Available Tools": "",
"available!": "disponible!",
"Awful": "Terrible",
"Azure AI Speech": "Azure AI Speech",
@@ -218,7 +220,10 @@
"Confirm your new password": "Confirma la teva nova contrasenya",
"Connect to your own OpenAI compatible API endpoints.": "Connecta als teus propis punts de connexió de l'API compatible amb OpenAI",
"Connect to your own OpenAPI compatible external tool servers.": "Connecta als teus propis servidors d'eines externs compatibles amb OpenAPI",
"Connection failed": "",
"Connection successful": "",
"Connections": "Connexions",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "Restringeix l'esforç de raonament dels models de raonament. Només aplicable a models de raonament de proveïdors específics que donen suport a l'esforç de raonament.",
"Contact Admin for WebUI Access": "Posat en contacte amb l'administrador per accedir a WebUI",
"Content": "Contingut",
@@ -303,6 +308,7 @@
"Direct Connections": "Connexions directes",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Les connexions directes permeten als usuaris connectar-se als seus propis endpoints d'API compatibles amb OpenAI.",
"Direct Connections settings updated": "Configuració de les connexions directes actualitzada",
"Direct Tool Servers": "",
"Disabled": "Deshabilitat",
"Discover a function": "Descobrir una funció",
"Discover a model": "Descobrir un model",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "Introdueix la clau API de Kagi Search",
"Enter Key Behavior": "Introdueix el comportament de clau",
"Enter language codes": "Introdueix els codis de llenguatge",
"Enter Mistral API Key": "",
"Enter Model ID": "Introdueix l'identificador del model",
"Enter model tag (e.g. {{modelTag}})": "Introdueix l'etiqueta del model (p. ex. {{modelTag}})",
"Enter Mojeek Search API Key": "Introdueix la clau API de Mojeek Search",
@@ -436,6 +443,7 @@
"Enter server port": "Introdueix el port del servidor",
"Enter stop sequence": "Introdueix la seqüència de parada",
"Enter system prompt": "Introdueix la indicació de sistema",
"Enter system prompt here": "",
"Enter Tavily API Key": "Introdueix la clau API de Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Entra la URL pública de WebUI. Aquesta URL s'utilitzarà per generar els enllaços en les notificacions.",
"Enter Tika Server URL": "Introdueix l'URL del servidor Tika",
@@ -449,6 +457,7 @@
"Enter Your Email": "Introdueix el teu correu electrònic",
"Enter Your Full Name": "Introdueix el teu nom complet",
"Enter your message": "Introdueix el teu missatge",
"Enter your name": "",
"Enter your new password": "Introdueix la teva nova contrasenya",
"Enter Your Password": "Introdueix la teva contrasenya",
"Enter Your Role": "Introdueix el teu rol",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "S'ha superat el nombre de places a la vostra llicència. Poseu-vos en contacte amb el servei d'assistència per augmentar el nombre de places.",
"Exclude": "Excloure",
"Execute code for analysis": "Executar el codi per analitzar-lo",
"Executing `{{NAME}}`...": "Executant `{{NAME}}`...",
"Executing **{{NAME}}**...": "",
"Expand": "Expandir",
"Experimental": "Experimental",
"Explain": "Explicar",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "No s'ha pogut crear la clau API.",
"Failed to fetch models": "No s'han pogut obtenir els models",
"Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls",
"Failed to save connections": "",
"Failed to save models configuration": "No s'ha pogut desar la configuració dels models",
"Failed to update settings": "No s'han pogut actualitzar les preferències",
"Failed to upload file.": "No s'ha pogut pujar l'arxiu.",
@@ -525,6 +535,7 @@
"Forge new paths": "Crea nous camins",
"Form": "Formulari",
"Format your variables using brackets like this:": "Formata les teves variables utilitzant claudàtors així:",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "Penalització per freqüència",
"Full Context Mode": "Mode de context complert",
"Function": "Funció",
@@ -644,6 +655,7 @@
"LDAP": "LDAP",
"LDAP server updated": "Servidor LDAP actualitzat",
"Leaderboard": "Tauler de classificació",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "Deixar-ho buit per il·limitat",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "Deixar-ho buit per incloure tots els models del punt de connexió \"{{url}}/api/tags\"",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "Deixar-ho buit per incloure tots els models del punt de connexió \"{{url}}/models\"",
@@ -694,6 +706,8 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Eta de Mirostat",
"Mirostat Tau": "Tau de Mirostat",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "Model",
"Model '{{modelName}}' has been successfully downloaded.": "El model '{{modelName}}' s'ha descarregat correctament.",
"Model '{{modelTag}}' is already in queue for downloading.": "El model '{{modelTag}}' ja està en cua per ser descarregat.",
@@ -780,6 +794,7 @@
"Open file": "Obrir arxiu",
"Open in full screen": "Obrir en pantalla complerta",
"Open new chat": "Obre un xat nou",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "Open WebUI utilitza faster-whisper internament.",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "Open WebUI utilitza incrustacions de SpeechT5 i CMU Arctic.",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "La versió d'Open WebUI (v{{OPEN_WEBUI_VERSION}}) és inferior a la versió requerida (v{{REQUIRED_VERSION}})",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "Es requereix la clau API d'OpenAI.",
"OpenAI API settings updated": "Configuració de l'API d'OpenAI actualitzada",
"OpenAI URL/Key required.": "URL/Clau d'OpenAI requerides.",
"openapi.json Path": "",
"or": "o",
"Organize your users": "Organitza els teus usuaris",
"Other": "Altres",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "Si us plau, revisa els següents avisos amb cura:",
"Please do not close the settings page while loading the model.": "No tanquis la pàgina de configuració mentre carregues el model.",
"Please enter a prompt": "Si us plau, entra una indicació",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "Emplena tots els camps, si us plau.",
"Please select a model first.": "Si us plau, selecciona un model primer",
"Please select a model.": "Si us plau, selecciona un model.",
@@ -1042,6 +1060,7 @@
"Thinking...": "Pensant...",
"This action cannot be undone. Do you wish to continue?": "Aquesta acció no es pot desfer. Vols continuar?",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Aquest canal es va crear el dia {{createdAt}}. Aquest és el començament del canal {{channelName}}.",
"This chat won’t appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Això assegura que les teves converses valuoses queden desades de manera segura a la teva base de dades. Gràcies!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Aquesta és una funció experimental, és possible que no funcioni com s'espera i està subjecta a canvis en qualsevol moment.",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "Aquesta opció controla quants tokens es conserven en actualitzar el context. Per exemple, si s'estableix en 2, es conservaran els darrers 2 tokens del context de conversa. Preservar el context pot ajudar a mantenir la continuïtat d'una conversa, però pot reduir la capacitat de respondre a nous temes.",
@@ -1089,6 +1108,7 @@
"Tool ID": "ID de l'eina",
"Tool imported successfully": "Eina importada correctament",
"Tool Name": "Nom de l'eina",
"Tool Servers": "",
"Tool updated successfully": "Eina actualitzada correctament",
"Tools": "Eines",
"Tools Access": "Accés a les eines",
@@ -1158,7 +1178,7 @@
"Version": "Versió",
"Version {{selectedVersion}} of {{totalVersions}}": "Versió {{selectedVersion}} de {{totalVersions}}",
"View Replies": "Veure les respostes",
"View Result from `{{NAME}}`": "Veure el resultat de `{{NAME}}`",
"View Result from **{{NAME}}**": "",
"Visibility": "Visibilitat",
"Voice": "Veu",
"Voice Input": "Entrada de veu",
@@ -1176,9 +1196,9 @@
"Webhook URL": "URL del webhook",
"WebUI Settings": "Preferències de WebUI",
"WebUI URL": "URL de WebUI",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI farà peticions a \"{{url}}/api/chat\"",
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI farà peticions a \"{{url}}/chat/completions\"",
"WebUI will make requests to \"{{url}}/openapi.json\"": "WebUI farà peticions a \"{{url}}/openapi.json\"",
"What are you trying to achieve?": "Què intentes aconseguir?",
"What are you working on?": "En què estàs treballant?",
"What’s New in": "Què hi ha de nou a",
+25 -5
View File
@@ -6,7 +6,7 @@
"(latest)": "",
"(Ollama)": "",
"{{ models }}": "",
"{{COUNT}} Available Tool Servers": "",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "",
"{{COUNT}} Replies": "",
"{{user}}'s Chats": "",
@@ -108,8 +108,10 @@
"Attribute for Username": "",
"Audio": "Audio",
"August": "",
"Auth": "",
"Authenticate": "",
"Authentication": "",
"Auto": "",
"Auto-Copy Response to Clipboard": "Awtomatikong kopya sa tubag sa clipboard",
"Auto-playback response": "Autoplay nga tubag",
"Autocomplete Generation": "",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "Base URL AUTOMATIC1111",
"AUTOMATIC1111 Base URL is required.": "Ang AUTOMATIC1111 base URL gikinahanglan.",
"Available list": "",
"Available Tool Servers": "",
"Available Tools": "",
"available!": "magamit!",
"Awful": "",
"Azure AI Speech": "",
@@ -218,7 +220,10 @@
"Confirm your new password": "",
"Connect to your own OpenAI compatible API endpoints.": "",
"Connect to your own OpenAPI compatible external tool servers.": "",
"Connection failed": "",
"Connection successful": "",
"Connections": "Mga koneksyon",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
"Contact Admin for WebUI Access": "",
"Content": "Kontento",
@@ -303,6 +308,7 @@
"Direct Connections": "",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
"Direct Connections settings updated": "",
"Direct Tool Servers": "",
"Disabled": "",
"Discover a function": "",
"Discover a model": "",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "",
"Enter Key Behavior": "",
"Enter language codes": "",
"Enter Mistral API Key": "",
"Enter Model ID": "",
"Enter model tag (e.g. {{modelTag}})": "Pagsulod sa template tag (e.g. {{modelTag}})",
"Enter Mojeek Search API Key": "",
@@ -436,6 +443,7 @@
"Enter server port": "",
"Enter stop sequence": "Pagsulod sa katapusan nga han-ay",
"Enter system prompt": "",
"Enter system prompt here": "",
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
@@ -449,6 +457,7 @@
"Enter Your Email": "Pagsulod sa imong e-mail address",
"Enter Your Full Name": "Ibutang ang imong tibuok nga ngalan",
"Enter your message": "",
"Enter your name": "",
"Enter your new password": "",
"Enter Your Password": "Ibutang ang imong password",
"Enter Your Role": "",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
"Exclude": "",
"Execute code for analysis": "",
"Executing `{{NAME}}`...": "",
"Executing **{{NAME}}**...": "",
"Expand": "",
"Experimental": "Eksperimento",
"Explain": "",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "",
"Failed to fetch models": "",
"Failed to read clipboard contents": "Napakyas sa pagbasa sa sulod sa clipboard",
"Failed to save connections": "",
"Failed to save models configuration": "",
"Failed to update settings": "",
"Failed to upload file.": "",
@@ -525,6 +535,7 @@
"Forge new paths": "",
"Form": "",
"Format your variables using brackets like this:": "",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "",
"Full Context Mode": "",
"Function": "",
@@ -644,6 +655,7 @@
"LDAP": "",
"LDAP server updated": "",
"Leaderboard": "",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
@@ -694,6 +706,8 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "Ang modelo'{{modelName}}' malampuson nga na-download.",
"Model '{{modelTag}}' is already in queue for downloading.": "Ang modelo'{{modelTag}}' naa na sa pila para ma-download.",
@@ -780,6 +794,7 @@
"Open file": "",
"Open in full screen": "",
"Open new chat": "Ablihi ang bag-ong diskusyon",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "Ang yawe sa OpenAI API gikinahanglan.",
"OpenAI API settings updated": "",
"OpenAI URL/Key required.": "",
"openapi.json Path": "",
"or": "O",
"Organize your users": "",
"Other": "",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "",
"Please do not close the settings page while loading the model.": "",
"Please enter a prompt": "",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "",
"Please select a model first.": "",
"Please select a model.": "",
@@ -1042,6 +1060,7 @@
"Thinking...": "",
"This action cannot be undone. Do you wish to continue?": "",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
"This chat won’t appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Kini nagsiguro nga ang imong bililhon nga mga panag-istoryahanay luwas nga natipig sa imong backend database. ",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
@@ -1089,6 +1108,7 @@
"Tool ID": "",
"Tool imported successfully": "",
"Tool Name": "",
"Tool Servers": "",
"Tool updated successfully": "",
"Tools": "",
"Tools Access": "",
@@ -1158,7 +1178,7 @@
"Version": "Bersyon",
"Version {{selectedVersion}} of {{totalVersions}}": "",
"View Replies": "",
"View Result from `{{NAME}}`": "",
"View Result from **{{NAME}}**": "",
"Visibility": "",
"Voice": "",
"Voice Input": "",
@@ -1176,9 +1196,9 @@
"Webhook URL": "",
"WebUI Settings": "Mga Setting sa WebUI",
"WebUI URL": "",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "",
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
"What are you trying to achieve?": "",
"What are you working on?": "",
"What’s New in": "Unsay bag-o sa",
+25 -5
View File
@@ -6,7 +6,7 @@
"(latest)": "Nejnovější",
"(Ollama)": "",
"{{ models }}": "{{ models }}",
"{{COUNT}} Available Tool Servers": "",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "",
"{{COUNT}} Replies": "",
"{{user}}'s Chats": "{{user}}'s konverzace",
@@ -108,8 +108,10 @@
"Attribute for Username": "",
"Audio": "Zvuk",
"August": "Srpen",
"Auth": "",
"Authenticate": "Autentikace",
"Authentication": "",
"Auto": "",
"Auto-Copy Response to Clipboard": "Automatické kopírování odpovědi do schránky",
"Auto-playback response": "Automatická odpověď při přehrávání",
"Autocomplete Generation": "",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "Výchozí URL pro AUTOMATIC1111",
"AUTOMATIC1111 Base URL is required.": "Vyžaduje se základní URL pro AUTOMATIC1111.",
"Available list": "Dostupný seznam",
"Available Tool Servers": "",
"Available Tools": "",
"available!": "k dispozici!",
"Awful": "",
"Azure AI Speech": "Azure AI syntéza řeči",
@@ -218,7 +220,10 @@
"Confirm your new password": "",
"Connect to your own OpenAI compatible API endpoints.": "",
"Connect to your own OpenAPI compatible external tool servers.": "",
"Connection failed": "",
"Connection successful": "",
"Connections": "Připojení",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
"Contact Admin for WebUI Access": "Kontaktujte administrátora pro přístup k webovému rozhraní.",
"Content": "Obsah",
@@ -303,6 +308,7 @@
"Direct Connections": "",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
"Direct Connections settings updated": "",
"Direct Tool Servers": "",
"Disabled": "Zakázáno",
"Discover a function": "Objevit funkci",
"Discover a model": "Objevte model",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "",
"Enter Key Behavior": "",
"Enter language codes": "Zadejte kódy jazyků",
"Enter Mistral API Key": "",
"Enter Model ID": "Zadejte ID modelu",
"Enter model tag (e.g. {{modelTag}})": "Zadejte označení modelu (např. {{modelTag}})",
"Enter Mojeek Search API Key": "",
@@ -436,6 +443,7 @@
"Enter server port": "",
"Enter stop sequence": "Zadejte ukončovací sekvenci",
"Enter system prompt": "Vložte systémový prompt",
"Enter system prompt here": "",
"Enter Tavily API Key": "Zadejte API klíč Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Zadejte URL serveru Tika",
@@ -449,6 +457,7 @@
"Enter Your Email": "Zadejte svůj email",
"Enter Your Full Name": "Zadejte své plné jméno",
"Enter your message": "Zadejte svou zprávu",
"Enter your name": "",
"Enter your new password": "",
"Enter Your Password": "Zadejte své heslo",
"Enter Your Role": "Zadejte svou roli",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
"Exclude": "Vyloučit",
"Execute code for analysis": "",
"Executing `{{NAME}}`...": "",
"Executing **{{NAME}}**...": "",
"Expand": "",
"Experimental": "Experimentální",
"Explain": "",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "Nepodařilo se vytvořit API klíč.",
"Failed to fetch models": "",
"Failed to read clipboard contents": "Nepodařilo se přečíst obsah schránky",
"Failed to save connections": "",
"Failed to save models configuration": "",
"Failed to update settings": "Nepodařilo se aktualizovat nastavení",
"Failed to upload file.": "Nepodařilo se nahrát soubor.",
@@ -525,6 +535,7 @@
"Forge new paths": "",
"Form": "Formulář",
"Format your variables using brackets like this:": "Formátujte své proměnné pomocí závorek takto:",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "Penalizace frekvence",
"Full Context Mode": "",
"Function": "Funkce",
@@ -644,6 +655,7 @@
"LDAP": "",
"LDAP server updated": "",
"Leaderboard": "Žebříček",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "Nechte prázdné pro neomezeně",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
@@ -694,6 +706,8 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "Model",
"Model '{{modelName}}' has been successfully downloaded.": "Model „{{modelName}}“ byl úspěšně stažen.",
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' je již zařazen do fronty pro stahování.",
@@ -780,6 +794,7 @@
"Open file": "Otevřít soubor",
"Open in full screen": "Otevřít na celou obrazovku",
"Open new chat": "Otevřít nový chat",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "Open WebUI interně používá faster-whisper.",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Verze Open WebUI (v{{OPEN_WEBUI_VERSION}}) je nižší než požadovaná verze (v{{REQUIRED_VERSION}})",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "Je vyžadován klíč OpenAI API.",
"OpenAI API settings updated": "",
"OpenAI URL/Key required.": "Je vyžadován odkaz/adresa URL nebo klíč OpenAI.",
"openapi.json Path": "",
"or": "nebo",
"Organize your users": "",
"Other": "Jiné",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "Prosím, pečlivě si přečtěte následující upozornění:",
"Please do not close the settings page while loading the model.": "",
"Please enter a prompt": "Prosím, zadejte zadání.",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "Prosím, vyplňte všechna pole.",
"Please select a model first.": "",
"Please select a model.": "",
@@ -1042,6 +1060,7 @@
"Thinking...": "Přemýšlím...",
"This action cannot be undone. Do you wish to continue?": "Tuto akci nelze vrátit zpět. Přejete si pokračovat?",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
"This chat won’t appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "To zajišťuje, že vaše cenné konverzace jsou bezpečně uloženy ve vaší backendové databázi. Děkujeme!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Jedná se o experimentální funkci, nemusí fungovat podle očekávání a může být kdykoliv změněna.",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
@@ -1089,6 +1108,7 @@
"Tool ID": "ID nástroje",
"Tool imported successfully": "Nástroj byl úspěšně importován",
"Tool Name": "",
"Tool Servers": "",
"Tool updated successfully": "Nástroj byl úspěšně aktualizován.",
"Tools": "Nástroje",
"Tools Access": "",
@@ -1158,7 +1178,7 @@
"Version": "Verze",
"Version {{selectedVersion}} of {{totalVersions}}": "Verze {{selectedVersion}} z {{totalVersions}}",
"View Replies": "",
"View Result from `{{NAME}}`": "",
"View Result from **{{NAME}}**": "",
"Visibility": "Viditelnost",
"Voice": "Hlas",
"Voice Input": "Hlasový vstup",
@@ -1176,9 +1196,9 @@
"Webhook URL": "Webhook URL",
"WebUI Settings": "Nastavení WebUI",
"WebUI URL": "",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "",
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
"What are you trying to achieve?": "",
"What are you working on?": "",
"What’s New in": "Co je nového v",
+25 -5
View File
@@ -6,7 +6,7 @@
"(latest)": "(seneste)",
"(Ollama)": "",
"{{ models }}": "{{ modeller }}",
"{{COUNT}} Available Tool Servers": "",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "",
"{{COUNT}} Replies": "",
"{{user}}'s Chats": "{{user}}s chats",
@@ -108,8 +108,10 @@
"Attribute for Username": "",
"Audio": "Lyd",
"August": "august",
"Auth": "",
"Authenticate": "",
"Authentication": "",
"Auto": "",
"Auto-Copy Response to Clipboard": "Automatisk kopiering af svar til udklipsholder",
"Auto-playback response": "Automatisk afspil svar",
"Autocomplete Generation": "",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Base URL er påkrævet.",
"Available list": "Tilgængelige lister",
"Available Tool Servers": "",
"Available Tools": "",
"available!": "tilgængelig!",
"Awful": "",
"Azure AI Speech": "Azure AI Speech",
@@ -218,7 +220,10 @@
"Confirm your new password": "",
"Connect to your own OpenAI compatible API endpoints.": "",
"Connect to your own OpenAPI compatible external tool servers.": "",
"Connection failed": "",
"Connection successful": "",
"Connections": "Forbindelser",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
"Contact Admin for WebUI Access": "Kontakt din administrator for adgang til WebUI",
"Content": "Indhold",
@@ -303,6 +308,7 @@
"Direct Connections": "",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
"Direct Connections settings updated": "",
"Direct Tool Servers": "",
"Disabled": "Inaktiv",
"Discover a function": "Find en funktion",
"Discover a model": "Find en model",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "",
"Enter Key Behavior": "",
"Enter language codes": "Indtast sprogkoder",
"Enter Mistral API Key": "",
"Enter Model ID": "Indtast model-ID",
"Enter model tag (e.g. {{modelTag}})": "Indtast modelmærke (f.eks. {{modelTag}})",
"Enter Mojeek Search API Key": "",
@@ -436,6 +443,7 @@
"Enter server port": "",
"Enter stop sequence": "Indtast stopsekvens",
"Enter system prompt": "Indtast systemprompt",
"Enter system prompt here": "",
"Enter Tavily API Key": "Indtast Tavily API-nøgle",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Indtast Tika Server URL",
@@ -449,6 +457,7 @@
"Enter Your Email": "Indtast din e-mail",
"Enter Your Full Name": "Indtast dit fulde navn",
"Enter your message": "Indtast din besked",
"Enter your name": "",
"Enter your new password": "",
"Enter Your Password": "Indtast din adgangskode",
"Enter Your Role": "Indtast din rolle",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
"Exclude": "",
"Execute code for analysis": "",
"Executing `{{NAME}}`...": "",
"Executing **{{NAME}}**...": "",
"Expand": "",
"Experimental": "Eksperimentel",
"Explain": "",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "Kunne ikke oprette API-nøgle.",
"Failed to fetch models": "",
"Failed to read clipboard contents": "Kunne ikke læse indholdet af udklipsholderen",
"Failed to save connections": "",
"Failed to save models configuration": "",
"Failed to update settings": "Kunne ikke opdatere indstillinger",
"Failed to upload file.": "Kunne ikke uploade fil.",
@@ -525,6 +535,7 @@
"Forge new paths": "",
"Form": "Formular",
"Format your variables using brackets like this:": "",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "Hyppighedsstraf",
"Full Context Mode": "",
"Function": "",
@@ -644,6 +655,7 @@
"LDAP": "",
"LDAP server updated": "",
"Leaderboard": "",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "Lad stå tomt for ubegrænset",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
@@ -694,6 +706,8 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' er blevet downloadet.",
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' er allerede i kø til download.",
@@ -780,6 +794,7 @@
"Open file": "Åbn fil",
"Open in full screen": "Åbn i fuld skærm",
"Open new chat": "Åbn ny chat",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI-version (v{{OPEN_WEBUI_VERSION}}) er lavere end den krævede version (v{{REQUIRED_VERSION}})",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "OpenAI API-nøgle er påkrævet.",
"OpenAI API settings updated": "",
"OpenAI URL/Key required.": "OpenAI URL/nøgle påkrævet.",
"openapi.json Path": "",
"or": "eller",
"Organize your users": "",
"Other": "Andet",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "Gennemgå omhyggeligt følgende advarsler:",
"Please do not close the settings page while loading the model.": "",
"Please enter a prompt": "",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "Udfyld alle felter.",
"Please select a model first.": "",
"Please select a model.": "",
@@ -1042,6 +1060,7 @@
"Thinking...": "Tænker...",
"This action cannot be undone. Do you wish to continue?": "Denne handling kan ikke fortrydes. Vil du fortsætte?",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
"This chat won’t appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dette sikrer, at dine værdifulde samtaler gemmes sikkert i din backend-database. Tak!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dette er en eksperimentel funktion, den fungerer muligvis ikke som forventet og kan ændres når som helst.",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
@@ -1089,6 +1108,7 @@
"Tool ID": "",
"Tool imported successfully": "Værktøj importeret.",
"Tool Name": "",
"Tool Servers": "",
"Tool updated successfully": "Værktøj opdateret.",
"Tools": "Værktøjer",
"Tools Access": "",
@@ -1158,7 +1178,7 @@
"Version": "Version",
"Version {{selectedVersion}} of {{totalVersions}}": "Version {{selectedVersion}} af {{totalVersions}}",
"View Replies": "",
"View Result from `{{NAME}}`": "",
"View Result from **{{NAME}}**": "",
"Visibility": "",
"Voice": "Stemme",
"Voice Input": "",
@@ -1176,9 +1196,9 @@
"Webhook URL": "Webhook-URL",
"WebUI Settings": "WebUI-indstillinger",
"WebUI URL": "",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "",
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
"What are you trying to achieve?": "",
"What are you working on?": "",
"What’s New in": "Nyheder i",
+27 -7
View File
@@ -6,7 +6,7 @@
"(latest)": "(neueste)",
"(Ollama)": "",
"{{ models }}": "{{ Modelle }}",
"{{COUNT}} Available Tool Servers": "",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "{{COUNT}} versteckte Zeilen",
"{{COUNT}} Replies": "{{COUNT}} Antworten",
"{{user}}'s Chats": "{{user}}s Chats",
@@ -108,8 +108,10 @@
"Attribute for Username": "Attribut für Benutzername",
"Audio": "Audio",
"August": "August",
"Auth": "",
"Authenticate": "Authentifizieren",
"Authentication": "Authentifizierung",
"Auto": "",
"Auto-Copy Response to Clipboard": "Antwort automatisch in die Zwischenablage kopieren",
"Auto-playback response": "Antwort automatisch abspielen",
"Autocomplete Generation": "Automatische Vervollständigung",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "AUTOMATIC1111-Basis-URL",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111-Basis-URL ist erforderlich.",
"Available list": "Verfügbare Liste",
"Available Tool Servers": "",
"Available Tools": "",
"available!": "Verfügbar!",
"Awful": "Schrecklich",
"Azure AI Speech": "Azure AI Speech",
@@ -218,7 +220,10 @@
"Confirm your new password": "Neues Passwort bestätigen",
"Connect to your own OpenAI compatible API endpoints.": "Verbinden Sie sich zu Ihren OpenAI-kompatiblen Endpunkten.",
"Connect to your own OpenAPI compatible external tool servers.": "",
"Connection failed": "",
"Connection successful": "",
"Connections": "Verbindungen",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
"Contact Admin for WebUI Access": "Kontaktieren Sie den Administrator für den Zugriff auf die Weboberfläche",
"Content": "Info",
@@ -303,6 +308,7 @@
"Direct Connections": "Direktverbindungen",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Direktverbindungen ermöglichen es Benutzern, sich mit ihren eigenen OpenAI-kompatiblen API-Endpunkten zu verbinden.",
"Direct Connections settings updated": "Direktverbindungs-Einstellungen aktualisiert",
"Direct Tool Servers": "",
"Disabled": "Deaktiviert",
"Discover a function": "Entdecken Sie weitere Funktionen",
"Discover a model": "Entdecken Sie weitere Modelle",
@@ -379,7 +385,7 @@
"Enable Mirostat sampling for controlling perplexity.": "",
"Enable New Sign Ups": "Registrierung erlauben",
"Enabled": "Aktiviert",
"Enforce Temporary Chat": "",
"Enforce Temporary Chat": "Temporären Chat erzwingen",
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Stellen Sie sicher, dass Ihre CSV-Datei 4 Spalten in dieser Reihenfolge enthält: Name, E-Mail, Passwort, Rolle.",
"Enter {{role}} message here": "Geben Sie die {{role}}-Nachricht hier ein",
"Enter a detail about yourself for your LLMs to recall": "Geben Sie ein Detail über sich selbst ein, das Ihre Sprachmodelle (LLMs) sich merken sollen",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "Geben Sie den Kagi Search API-Schlüssel ein",
"Enter Key Behavior": "Verhalten von 'Enter'",
"Enter language codes": "Geben Sie die Sprachcodes ein",
"Enter Mistral API Key": "",
"Enter Model ID": "Geben Sie die Modell-ID ein",
"Enter model tag (e.g. {{modelTag}})": "Geben Sie den Model-Tag ein",
"Enter Mojeek Search API Key": "Geben Sie den Mojeek Search API-Schlüssel ein",
@@ -436,6 +443,7 @@
"Enter server port": "Geben Sie den Server-Port ein",
"Enter stop sequence": "Stop-Sequenz eingeben",
"Enter system prompt": "Systemprompt eingeben",
"Enter system prompt here": "",
"Enter Tavily API Key": "Geben Sie den Tavily-API-Schlüssel ein",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Geben sie die öffentliche URL Ihrer WebUI ein. Diese URL wird verwendet, um Links in den Benachrichtigungen zu generieren.",
"Enter Tika Server URL": "Geben Sie die Tika-Server-URL ein",
@@ -449,6 +457,7 @@
"Enter Your Email": "Geben Sie Ihre E-Mail-Adresse ein",
"Enter Your Full Name": "Geben Sie Ihren vollständigen Namen ein",
"Enter your message": "Geben Sie Ihre Nachricht ein",
"Enter your name": "",
"Enter your new password": "Geben Sie Ihr neues Passwort ein",
"Enter Your Password": "Geben Sie Ihr Passwort ein",
"Enter Your Role": "Geben Sie Ihre Rolle ein",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
"Exclude": "Ausschließen",
"Execute code for analysis": "Code für Analyse ausführen",
"Executing `{{NAME}}`...": "",
"Executing **{{NAME}}**...": "",
"Expand": "Aufklappen",
"Experimental": "Experimentell",
"Explain": "Erklären",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "Fehler beim Erstellen des API-Schlüssels.",
"Failed to fetch models": "Fehler beim Abrufen der Modelle",
"Failed to read clipboard contents": "Fehler beim Abruf der Zwischenablage",
"Failed to save connections": "",
"Failed to save models configuration": "Fehler beim Speichern der Modellkonfiguration",
"Failed to update settings": "Fehler beim Aktualisieren der Einstellungen",
"Failed to upload file.": "Fehler beim Hochladen der Datei.",
@@ -525,6 +535,7 @@
"Forge new paths": "Neue Wege beschreiten",
"Form": "Formular",
"Format your variables using brackets like this:": "Formatieren Sie Ihre Variablen mit Klammern, wie hier:",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "Frequenzstrafe",
"Full Context Mode": "Voll-Kontext Modus",
"Function": "Funktion",
@@ -644,6 +655,7 @@
"LDAP": "LDAP",
"LDAP server updated": "LDAP-Server aktualisiert",
"Leaderboard": "Bestenliste",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "Leer lassen für unbegrenzt",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
@@ -694,6 +706,8 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "Modell",
"Model '{{modelName}}' has been successfully downloaded.": "Modell '{{modelName}}' wurde erfolgreich heruntergeladen.",
"Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange zum Herunterladen.",
@@ -780,6 +794,7 @@
"Open file": "Datei öffnen",
"Open in full screen": "Im Vollbildmodus öffnen",
"Open new chat": "Neuen Chat öffnen",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "Open WebUI verwendet intern faster-whisper.",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "Open WebUI verwendet SpeechT5 und CMU Arctic-Sprecher-Embeddings.",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Die installierte Open-WebUI-Version (v{{OPEN_WEBUI_VERSION}}) ist niedriger als die erforderliche Version (v{{REQUIRED_VERSION}})",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "OpenAI-API-Schlüssel erforderlich.",
"OpenAI API settings updated": "OpenAI-API-Einstellungen aktualisiert",
"OpenAI URL/Key required.": "OpenAI-URL/Schlüssel erforderlich.",
"openapi.json Path": "",
"or": "oder",
"Organize your users": "Organisieren Sie Ihre Benutzer",
"Other": "Andere",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "Bitte überprüfen Sie die folgenden Warnungen sorgfältig:",
"Please do not close the settings page while loading the model.": "Bitte schließen die Einstellungen-Seite nicht, während das Modell lädt.",
"Please enter a prompt": "Bitte geben Sie einen Prompt ein",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "Bitte füllen Sie alle Felder aus.",
"Please select a model first.": "Bitte wählen Sie zuerst ein Modell aus.",
"Please select a model.": "Bitte wählen Sie ein Modell aus.",
@@ -1021,7 +1039,7 @@
"Tell us more:": "Erzähl uns mehr",
"Temperature": "Temperatur",
"Template": "Vorlage",
"Temporary Chat": "Temporärer Chat",
"Temporary Chat": "Temporäre Unterhaltung",
"Text Splitter": "Text-Splitter",
"Text-to-Speech Engine": "Text-zu-Sprache-Engine",
"Tfs Z": "Tfs Z",
@@ -1042,6 +1060,7 @@
"Thinking...": "Denke nach...",
"This action cannot be undone. Do you wish to continue?": "Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie fortfahren?",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Dieser Kanal wurde am {{createdAt}} erstellt. Dies ist der Beginn des {{channelName}} Kanals.",
"This chat won’t appear in history and your messages will not be saved.": "Diese Unterhaltung erscheint nicht in deinem Chat-Verlauf. Alle Nachrichten sind privat und werden nicht gespeichert.",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dies stellt sicher, dass Ihre wertvollen Chats sicher in Ihrer Backend-Datenbank gespeichert werden. Vielen Dank!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dies ist eine experimentelle Funktion, sie funktioniert möglicherweise nicht wie erwartet und kann jederzeit geändert werden.",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
@@ -1089,6 +1108,7 @@
"Tool ID": "Werkzeug-ID",
"Tool imported successfully": "Werkzeug erfolgreich importiert",
"Tool Name": "Werkzeugname",
"Tool Servers": "",
"Tool updated successfully": "Werkzeug erfolgreich aktualisiert",
"Tools": "Werkzeuge",
"Tools Access": "Werkzeugzugriff",
@@ -1158,7 +1178,7 @@
"Version": "Version",
"Version {{selectedVersion}} of {{totalVersions}}": "Version {{selectedVersion}} von {{totalVersions}}",
"View Replies": "Antworten anzeigen",
"View Result from `{{NAME}}`": "",
"View Result from **{{NAME}}**": "",
"Visibility": "Sichtbarkeit",
"Voice": "Stimme",
"Voice Input": "Spracheingabe",
@@ -1176,9 +1196,9 @@
"Webhook URL": "Webhook URL",
"WebUI Settings": "WebUI-Einstellungen",
"WebUI URL": "WebUI-URL",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI wird Anfragen an \"{{url}}/api/chat\" senden",
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI wird Anfragen an \"{{url}}/chat/completions\" senden",
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
"What are you trying to achieve?": "Was versuchen Sie zu erreichen?",
"What are you working on?": "Woran arbeiten Sie?",
"What’s New in": "Neuigkeiten von",
+25 -5
View File
@@ -6,7 +6,7 @@
"(latest)": "(much latest)",
"(Ollama)": "",
"{{ models }}": "",
"{{COUNT}} Available Tool Servers": "",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "",
"{{COUNT}} Replies": "",
"{{user}}'s Chats": "",
@@ -108,8 +108,10 @@
"Attribute for Username": "",
"Audio": "Audio",
"August": "",
"Auth": "",
"Authenticate": "",
"Authentication": "",
"Auto": "",
"Auto-Copy Response to Clipboard": "Copy Bark Auto Bark",
"Auto-playback response": "Auto-playback response",
"Autocomplete Generation": "",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Base URL is required.",
"Available list": "",
"Available Tool Servers": "",
"Available Tools": "",
"available!": "available! So excite!",
"Awful": "",
"Azure AI Speech": "",
@@ -218,7 +220,10 @@
"Confirm your new password": "",
"Connect to your own OpenAI compatible API endpoints.": "",
"Connect to your own OpenAPI compatible external tool servers.": "",
"Connection failed": "",
"Connection successful": "",
"Connections": "Connections",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
"Contact Admin for WebUI Access": "",
"Content": "Content",
@@ -303,6 +308,7 @@
"Direct Connections": "",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
"Direct Connections settings updated": "",
"Direct Tool Servers": "",
"Disabled": "",
"Discover a function": "",
"Discover a model": "",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "",
"Enter Key Behavior": "",
"Enter language codes": "",
"Enter Mistral API Key": "",
"Enter Model ID": "",
"Enter model tag (e.g. {{modelTag}})": "Enter model doge tag (e.g. {{modelTag}})",
"Enter Mojeek Search API Key": "",
@@ -436,6 +443,7 @@
"Enter server port": "",
"Enter stop sequence": "Enter stop bark",
"Enter system prompt": "",
"Enter system prompt here": "",
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
@@ -449,6 +457,7 @@
"Enter Your Email": "Enter Your Dogemail",
"Enter Your Full Name": "Enter Your Full Wow",
"Enter your message": "",
"Enter your name": "",
"Enter your new password": "",
"Enter Your Password": "Enter Your Barkword",
"Enter Your Role": "",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
"Exclude": "",
"Execute code for analysis": "",
"Executing `{{NAME}}`...": "",
"Executing **{{NAME}}**...": "",
"Expand": "",
"Experimental": "Much Experiment",
"Explain": "",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "",
"Failed to fetch models": "",
"Failed to read clipboard contents": "Failed to read clipboard borks",
"Failed to save connections": "",
"Failed to save models configuration": "",
"Failed to update settings": "",
"Failed to upload file.": "",
@@ -525,6 +535,7 @@
"Forge new paths": "",
"Form": "",
"Format your variables using brackets like this:": "",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "",
"Full Context Mode": "",
"Function": "",
@@ -644,6 +655,7 @@
"LDAP": "",
"LDAP server updated": "",
"Leaderboard": "",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
@@ -694,6 +706,8 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' has been successfully downloaded.",
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' is already in queue for downloading.",
@@ -780,6 +794,7 @@
"Open file": "",
"Open in full screen": "",
"Open new chat": "Open new bark",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "OpenAI Bark Key is required.",
"OpenAI API settings updated": "",
"OpenAI URL/Key required.": "",
"openapi.json Path": "",
"or": "or",
"Organize your users": "",
"Other": "",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "",
"Please do not close the settings page while loading the model.": "",
"Please enter a prompt": "",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "",
"Please select a model first.": "",
"Please select a model.": "",
@@ -1042,6 +1060,7 @@
"Thinking...": "",
"This action cannot be undone. Do you wish to continue?": "",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
"This chat won’t appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "This ensures that your valuable conversations are securely saved to your backend database. Thank you! Much secure!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
@@ -1089,6 +1108,7 @@
"Tool ID": "",
"Tool imported successfully": "",
"Tool Name": "",
"Tool Servers": "",
"Tool updated successfully": "",
"Tools": "",
"Tools Access": "",
@@ -1158,7 +1178,7 @@
"Version": "Version much version",
"Version {{selectedVersion}} of {{totalVersions}}": "",
"View Replies": "",
"View Result from `{{NAME}}`": "",
"View Result from **{{NAME}}**": "",
"Visibility": "",
"Voice": "",
"Voice Input": "",
@@ -1176,9 +1196,9 @@
"Webhook URL": "",
"WebUI Settings": "WebUI Settings much settings",
"WebUI URL": "",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "",
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
"What are you trying to achieve?": "",
"What are you working on?": "",
"What’s New in": "What’s New in much new",
+25 -5
View File
@@ -6,7 +6,7 @@
"(latest)": "(τελευταίο)",
"(Ollama)": "",
"{{ models }}": "{{ models }}",
"{{COUNT}} Available Tool Servers": "",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "",
"{{COUNT}} Replies": "",
"{{user}}'s Chats": "Συνομιλίες του {{user}}",
@@ -108,8 +108,10 @@
"Attribute for Username": "Ιδιότητα για Όνομα Χρήστη",
"Audio": "Ήχος",
"August": "Αύγουστος",
"Auth": "",
"Authenticate": "Επαλήθευση",
"Authentication": "",
"Auto": "",
"Auto-Copy Response to Clipboard": "Αυτόματη Αντιγραφή Απάντησης στο Πρόχειρο",
"Auto-playback response": "Αυτόματη αναπαραγωγή της απάντησης",
"Autocomplete Generation": "",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "Βασικό URL AUTOMATIC1111",
"AUTOMATIC1111 Base URL is required.": "Απαιτείται το Βασικό URL AUTOMATIC1111.",
"Available list": "Διαθέσιμη λίστα",
"Available Tool Servers": "",
"Available Tools": "",
"available!": "διαθέσιμο!",
"Awful": "Ασχημο",
"Azure AI Speech": "Ομιλία Azure AI",
@@ -218,7 +220,10 @@
"Confirm your new password": "",
"Connect to your own OpenAI compatible API endpoints.": "",
"Connect to your own OpenAPI compatible external tool servers.": "",
"Connection failed": "",
"Connection successful": "",
"Connections": "Συνδέσεις",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
"Contact Admin for WebUI Access": "Επικοινωνήστε με τον Διαχειριστή για Πρόσβαση στο WebUI",
"Content": "Περιεχόμενο",
@@ -303,6 +308,7 @@
"Direct Connections": "",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
"Direct Connections settings updated": "",
"Direct Tool Servers": "",
"Disabled": "Απενεργοποιημένο",
"Discover a function": "Ανακάλυψη λειτουργίας",
"Discover a model": "Ανακάλυψη μοντέλου",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "",
"Enter Key Behavior": "",
"Enter language codes": "Εισάγετε κωδικούς γλώσσας",
"Enter Mistral API Key": "",
"Enter Model ID": "Εισάγετε το ID Μοντέλου",
"Enter model tag (e.g. {{modelTag}})": "Εισάγετε την ετικέτα μοντέλου (π.χ. {{modelTag}})",
"Enter Mojeek Search API Key": "Εισάγετε το Κλειδί API Mojeek Search",
@@ -436,6 +443,7 @@
"Enter server port": "Εισάγετε την θύρα διακομιστή",
"Enter stop sequence": "Εισάγετε τη σειρά παύσης",
"Enter system prompt": "Εισάγετε την προτροπή συστήματος",
"Enter system prompt here": "",
"Enter Tavily API Key": "Εισάγετε το Κλειδί API Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Εισάγετε το URL διακομιστή Tika",
@@ -449,6 +457,7 @@
"Enter Your Email": "Εισάγετε το Email σας",
"Enter Your Full Name": "Εισάγετε το Πλήρες Όνομά σας",
"Enter your message": "Εισάγετε το μήνυμά σας",
"Enter your name": "",
"Enter your new password": "",
"Enter Your Password": "Εισάγετε τον Κωδικό σας",
"Enter Your Role": "Εισάγετε τον Ρόλο σας",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
"Exclude": "Εξαίρεση",
"Execute code for analysis": "",
"Executing `{{NAME}}`...": "",
"Executing **{{NAME}}**...": "",
"Expand": "",
"Experimental": "Πειραματικό",
"Explain": "",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "Αποτυχία δημιουργίας Κλειδιού API.",
"Failed to fetch models": "",
"Failed to read clipboard contents": "Αποτυχία ανάγνωσης περιεχομένων πρόχειρου",
"Failed to save connections": "",
"Failed to save models configuration": "Αποτυχία αποθήκευσης ρυθμίσεων μοντέλων",
"Failed to update settings": "Αποτυχία ενημέρωσης ρυθμίσεων",
"Failed to upload file.": "Αποτυχία ανεβάσματος αρχείου.",
@@ -525,6 +535,7 @@
"Forge new paths": "Δημιουργήστε νέες διαδρομές",
"Form": "Φόρμα",
"Format your variables using brackets like this:": "Μορφοποιήστε τις μεταβλητές σας χρησιμοποιώντας αγκύλες όπως αυτό:",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "Ποινή Συχνότητας",
"Full Context Mode": "",
"Function": "Λειτουργία",
@@ -644,6 +655,7 @@
"LDAP": "LDAP",
"LDAP server updated": "Ο διακομιστής LDAP ενημερώθηκε",
"Leaderboard": "Κατάταξη",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "Αφήστε κενό για απεριόριστο",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
@@ -694,6 +706,8 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "Μοντέλο",
"Model '{{modelName}}' has been successfully downloaded.": "Το μοντέλο '{{modelName}}' κατεβάστηκε με επιτυχία.",
"Model '{{modelTag}}' is already in queue for downloading.": "Το μοντέλο '{{modelTag}}' βρίσκεται ήδη στην ουρά για λήψη.",
@@ -780,6 +794,7 @@
"Open file": "Άνοιγμα αρχείου",
"Open in full screen": "Άνοιγμα σε πλήρη οθόνη",
"Open new chat": "Άνοιγμα νέας συνομιλίας",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "Το Open WebUI χρησιμοποιεί το faster-whisper εσωτερικά.",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "Το Open WebUI χρησιμοποιεί τα SpeechT5 και CMU Arctic embeddings ομιλητών.",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Η έκδοση Open WebUI (v{{OPEN_WEBUI_VERSION}}) είναι χαμηλότερη από την απαιτούμενη έκδοση (v{{REQUIRED_VERSION}})",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "Απαιτείται το Κλειδί API OpenAI.",
"OpenAI API settings updated": "Οι ρυθμίσεις API OpenAI ενημερώθηκαν",
"OpenAI URL/Key required.": "Απαιτείται URL/Kλειδί OpenAI.",
"openapi.json Path": "",
"or": "ή",
"Organize your users": "Οργανώστε τους χρήστες σας",
"Other": "Άλλο",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "Παρακαλώ αναθεωρήστε προσεκτικά τις ακόλουθες προειδοποιήσεις:",
"Please do not close the settings page while loading the model.": "",
"Please enter a prompt": "Παρακαλώ εισάγετε μια προτροπή",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "Παρακαλώ συμπληρώστε όλα τα πεδία.",
"Please select a model first.": "",
"Please select a model.": "",
@@ -1042,6 +1060,7 @@
"Thinking...": "Σκέφτομαι...",
"This action cannot be undone. Do you wish to continue?": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. Θέλετε να συνεχίσετε;",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
"This chat won’t appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Αυτό διασφαλίζει ότι οι πολύτιμες συνομιλίες σας αποθηκεύονται με ασφάλεια στη βάση δεδομένων backend σας. Ευχαριστούμε!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Αυτή είναι μια πειραματική λειτουργία, μπορεί να μην λειτουργεί όπως αναμένεται και υπόκειται σε αλλαγές οποιαδήποτε στιγμή.",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
@@ -1089,6 +1108,7 @@
"Tool ID": "ID Εργαλείου",
"Tool imported successfully": "Το εργαλείο εισήχθη με επιτυχία",
"Tool Name": "Όνομα Εργαλείου",
"Tool Servers": "",
"Tool updated successfully": "Το εργαλείο ενημερώθηκε με επιτυχία",
"Tools": "Εργαλεία",
"Tools Access": "Πρόσβαση Εργαλείων",
@@ -1158,7 +1178,7 @@
"Version": "Έκδοση",
"Version {{selectedVersion}} of {{totalVersions}}": "Έκδοση {{selectedVersion}} από {{totalVersions}}",
"View Replies": "",
"View Result from `{{NAME}}`": "",
"View Result from **{{NAME}}**": "",
"Visibility": "Ορατότητα",
"Voice": "Φωνή",
"Voice Input": "Εισαγωγή Φωνής",
@@ -1176,9 +1196,9 @@
"Webhook URL": "URL Webhook",
"WebUI Settings": "Ρυθμίσεις WebUI",
"WebUI URL": "",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "Το WebUI θα κάνει αιτήματα στο \"{{url}}/api/chat\"",
"WebUI will make requests to \"{{url}}/chat/completions\"": "Το WebUI θα κάνει αιτήματα στο \"{{url}}/chat/completions\"",
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
"What are you trying to achieve?": "Τι προσπαθείτε να πετύχετε?",
"What are you working on?": "Τι εργάζεστε;",
"What’s New in": "Τι νέο υπάρχει στο",
+25 -5
View File
@@ -6,7 +6,7 @@
"(latest)": "",
"(Ollama)": "",
"{{ models }}": "",
"{{COUNT}} Available Tool Servers": "",
"{{COUNT}} Available Tools": "",
"{{COUNT}} hidden lines": "",
"{{COUNT}} Replies": "",
"{{user}}'s Chats": "",
@@ -108,8 +108,10 @@
"Attribute for Username": "",
"Audio": "",
"August": "",
"Auth": "",
"Authenticate": "",
"Authentication": "",
"Auto": "",
"Auto-Copy Response to Clipboard": "",
"Auto-playback response": "",
"Autocomplete Generation": "",
@@ -119,7 +121,7 @@
"AUTOMATIC1111 Base URL": "",
"AUTOMATIC1111 Base URL is required.": "",
"Available list": "",
"Available Tool Servers": "",
"Available Tools": "",
"available!": "",
"Awful": "",
"Azure AI Speech": "",
@@ -218,7 +220,10 @@
"Confirm your new password": "",
"Connect to your own OpenAI compatible API endpoints.": "",
"Connect to your own OpenAPI compatible external tool servers.": "",
"Connection failed": "",
"Connection successful": "",
"Connections": "",
"Connections saved successfully": "",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
"Contact Admin for WebUI Access": "",
"Content": "",
@@ -303,6 +308,7 @@
"Direct Connections": "",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
"Direct Connections settings updated": "",
"Direct Tool Servers": "",
"Disabled": "",
"Discover a function": "",
"Discover a model": "",
@@ -412,6 +418,7 @@
"Enter Kagi Search API Key": "",
"Enter Key Behavior": "",
"Enter language codes": "",
"Enter Mistral API Key": "",
"Enter Model ID": "",
"Enter model tag (e.g. {{modelTag}})": "",
"Enter Mojeek Search API Key": "",
@@ -436,6 +443,7 @@
"Enter server port": "",
"Enter stop sequence": "",
"Enter system prompt": "",
"Enter system prompt here": "",
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
@@ -449,6 +457,7 @@
"Enter Your Email": "",
"Enter Your Full Name": "",
"Enter your message": "",
"Enter your name": "",
"Enter your new password": "",
"Enter Your Password": "",
"Enter Your Role": "",
@@ -468,7 +477,7 @@
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
"Exclude": "",
"Execute code for analysis": "",
"Executing `{{NAME}}`...": "",
"Executing **{{NAME}}**...": "",
"Expand": "",
"Experimental": "",
"Explain": "",
@@ -493,6 +502,7 @@
"Failed to create API Key.": "",
"Failed to fetch models": "",
"Failed to read clipboard contents": "",
"Failed to save connections": "",
"Failed to save models configuration": "",
"Failed to update settings": "",
"Failed to upload file.": "",
@@ -525,6 +535,7 @@
"Forge new paths": "",
"Form": "",
"Format your variables using brackets like this:": "",
"Forwards system user session credentials to authenticate": "",
"Frequency Penalty": "",
"Full Context Mode": "",
"Function": "",
@@ -644,6 +655,7 @@
"LDAP": "",
"LDAP server updated": "",
"Leaderboard": "",
"Learn more about OpenAPI tool servers.": "",
"Leave empty for unlimited": "",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
@@ -694,6 +706,8 @@
"Mirostat": "",
"Mirostat Eta": "",
"Mirostat Tau": "",
"Mistral OCR": "",
"Mistral OCR API Key required.": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "",
"Model '{{modelTag}}' is already in queue for downloading.": "",
@@ -780,6 +794,7 @@
"Open file": "",
"Open in full screen": "",
"Open new chat": "",
"Open WebUI can use tools provided by any OpenAPI server.": "",
"Open WebUI uses faster-whisper internally.": "",
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
@@ -789,6 +804,7 @@
"OpenAI API Key is required.": "",
"OpenAI API settings updated": "",
"OpenAI URL/Key required.": "",
"openapi.json Path": "",
"or": "",
"Organize your users": "",
"Other": "",
@@ -820,6 +836,8 @@
"Please carefully review the following warnings:": "",
"Please do not close the settings page while loading the model.": "",
"Please enter a prompt": "",
"Please enter a valid path": "",
"Please enter a valid URL": "",
"Please fill in all fields.": "",
"Please select a model first.": "",
"Please select a model.": "",
@@ -1042,6 +1060,7 @@
"Thinking...": "",
"This action cannot be undone. Do you wish to continue?": "",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
"This chat won’t appear in history and your messages will not be saved.": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
@@ -1089,6 +1108,7 @@
"Tool ID": "",
"Tool imported successfully": "",
"Tool Name": "",
"Tool Servers": "",
"Tool updated successfully": "",
"Tools": "",
"Tools Access": "",
@@ -1158,7 +1178,7 @@
"Version": "",
"Version {{selectedVersion}} of {{totalVersions}}": "",
"View Replies": "",
"View Result from `{{NAME}}`": "",
"View Result from **{{NAME}}**": "",
"Visibility": "",
"Voice": "",
"Voice Input": "",
@@ -1176,9 +1196,9 @@
"Webhook URL": "",
"WebUI Settings": "",
"WebUI URL": "",
"WebUI will make requests to \"{{url}}\"": "",
"WebUI will make requests to \"{{url}}/api/chat\"": "",
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
"What are you trying to achieve?": "",
"What are you working on?": "",
"What’s New in": "",

Some files were not shown because too many files have changed in this diff Show More