Compare commits

...

237 Commits

Author SHA1 Message Date
Timothy Jaeryang Baek ab94468ffa Merge pull request #9387 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.5.9
2025-02-05 02:38:21 -08:00
Timothy Jaeryang Baek 13c7d96515 refac 2025-02-05 02:38:05 -08:00
Timothy Jaeryang Baek bffbf7e238 doc: changelog 2025-02-05 02:35:16 -08:00
Timothy Jaeryang Baek 90cd6f272f fix: tag support 2025-02-05 02:33:40 -08:00
Timothy Jaeryang Baek c36c63f1e8 fix: <think> tag 2025-02-05 02:10:28 -08:00
Timothy Jaeryang Baek dc3b2f1f1e Merge pull request #9190 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.5.8
2025-02-05 01:16:35 -08:00
Timothy Jaeryang Baek 1ab66e83c6 refac: styling 2025-02-05 01:14:46 -08:00
Timothy Jaeryang Baek 3b7926f00d doc: changelog 2025-02-05 01:09:23 -08:00
Timothy Jaeryang Baek e27de61318 Merge pull request #9377 from OriginalSimon/dev
18n: Update Ukrainian translation
2025-02-05 01:08:39 -08:00
Timothy Jaeryang Baek cda6b5ad17 refac: styling 2025-02-05 01:07:38 -08:00
Simon 3f188b0b86 Update translation.json 2025-02-05 10:05:38 +01:00
Timothy Jaeryang Baek b33b49789e enh: responsive styling 2025-02-05 01:03:40 -08:00
Timothy Jaeryang Baek cce1762cb8 refac: tool calling 2025-02-05 01:03:16 -08:00
Simon 1fbbd36433 Update translation.json 2025-02-05 10:02:59 +01:00
Timothy Jaeryang Baek f6b955e6a0 chore: format 2025-02-05 00:44:07 -08:00
Timothy Jaeryang Baek 41ca399dfd chore: format 2025-02-05 00:42:51 -08:00
Timothy Jaeryang Baek 5385d39420 Merge pull request #9374 from KarlLee830/translate
i18n: Update Chinese translation
2025-02-05 00:38:18 -08:00
Timothy Jaeryang Baek d4a18a5765 refac: always on web search 2025-02-05 00:37:47 -08:00
KarlLee830 3f547eb395 i18n: Update Chinese translation 2025-02-05 16:34:51 +08:00
Timothy Jaeryang Baek 336984c254 Merge pull request #9370 from hurxxxx/feat/always-web-search
feat: Add "Always-On Web Search" configuration.
2025-02-05 00:27:29 -08:00
Timothy Jaeryang Baek 58bb4758df chore: bump 2025-02-05 00:25:11 -08:00
Timothy Jaeryang Baek 102b56b938 doc: changelog 2025-02-05 00:23:30 -08:00
Timothy Jaeryang Baek e41a2682f5 chore: format 2025-02-05 00:07:45 -08:00
Timothy Jaeryang Baek f6f8c08cb0 Merge pull request #9068 from df-cgdm/main
**feat** Add user related headers when calling an external embedding api
2025-02-05 00:05:44 -08:00
Timothy Jaeryang Baek 8e831a5433 Merge pull request #9358 from KarlLee830/translate
i18n: Update Chinese translation
2025-02-05 00:04:12 -08:00
Timothy Jaeryang Baek c778345e84 refac 2025-02-05 00:01:24 -08:00
hurxxxx 0e3c1ac392 Merge remote-tracking branch 'upstream/dev' into feat/always-web-search
# Conflicts:
#	src/lib/components/chat/Chat.svelte
2025-02-05 16:56:34 +09:00
Timothy Jaeryang Baek 2b0af33274 refac 2025-02-04 23:48:01 -08:00
Timothy Jaeryang Baek 9624493b55 refac 2025-02-04 23:44:51 -08:00
Timothy Jaeryang Baek e349e26cd8 refac 2025-02-04 23:20:09 -08:00
Timothy Jaeryang Baek 314b674f32 feat: native tool calling support 2025-02-04 23:05:14 -08:00
Timothy Jaeryang Baek 7766a08b70 refac: ollama tool calls 2025-02-04 21:42:49 -08:00
Timothy Jaeryang Baek c5a9cfacf1 refac 2025-02-04 21:20:03 -08:00
Timothy Jaeryang Baek 8d520eeecd fix: ollama native tool calling 2025-02-04 21:08:26 -08:00
Timothy Jaeryang Baek f88a80ac47 wip: pyodide kernel 2025-02-04 21:02:11 -08:00
Timothy Jaeryang Baek e7da506add refac: middleware 2025-02-04 21:01:53 -08:00
Timothy Jaeryang Baek 2e61ea7cc9 refac: chat 2025-02-04 20:56:13 -08:00
KarlLee830 0a5b421ac1 i18n: Update Chinese translation 2025-02-05 12:53:06 +08:00
Timothy Jaeryang Baek a4ea2e0996 fix: multi response message 2025-02-04 20:33:02 -08:00
Timothy Jaeryang Baek 23df351239 refac: ollama tool calling support
Co-Authored-By: smonux <85928277+smonux@users.noreply.github.com>
2025-02-04 19:22:08 -08:00
Timothy Jaeryang Baek ec9be0d20d feat: native tool calling frontend 2025-02-04 19:14:59 -08:00
Timothy Jaeryang Baek 533b62f062 refac: deprecate non web worker pyodide 2025-02-04 18:41:53 -08:00
Timothy Jaeryang Baek fba25c6cec refac 2025-02-04 18:36:22 -08:00
Timothy Jaeryang Baek 6ee924924e refac: code interpreter 2025-02-04 18:33:22 -08:00
Timothy Jaeryang Baek 9c0a783991 refac: pyodide 2025-02-04 17:29:10 -08:00
Timothy Jaeryang Baek 266899acc0 refac 2025-02-04 14:45:47 -08:00
Timothy Jaeryang Baek 2b0c5f322f fix: suggestions 2025-02-04 14:41:42 -08:00
Timothy Jaeryang Baek 08ce91ad86 Merge pull request #9325 from Mavyre/patch-1
fix: Corrected chat generation error after failed web search query generation
2025-02-04 14:02:10 -08:00
Timothy Jaeryang Baek d062af4129 Merge pull request #9332 from le-ross/main
fixing french typo
2025-02-04 14:01:38 -08:00
Timothy Jaeryang Baek 5cda8a57e7 Merge pull request #9337 from abdalrohman/exa_integration
feat: implement Exa search engine integration
2025-02-04 14:00:06 -08:00
Timothy Jaeryang Baek b02a38487c Merge pull request #9328 from kahghi/fix-unstructured-pkg-version
fix: increment unstructured package version to fix UnstructuredExcelLoader
2025-02-04 13:59:39 -08:00
M.Abdulrahman Alnaseer 2bb6b49f11 feat: implement Exa search engine integration 2025-02-04 21:13:16 +03:00
rossm d6b88c7ef7 fixing french typo 2025-02-04 10:43:19 -05:00
kahghi 1868cf0a91 increment unstructured package version 2025-02-04 22:40:49 +08:00
Mavyre bbffdeb758 Corrected chat generation error after failed web search query generation 2025-02-04 12:13:54 +01:00
Timothy Jaeryang Baek ce08ea5f6c refac 2025-02-04 01:06:03 -08:00
Timothy Jaeryang Baek 12a7f4d43b refac: styling 2025-02-04 01:03:41 -08:00
Timothy Jaeryang Baek 6ba9ccdd79 refac: styling 2025-02-04 00:55:55 -08:00
Timothy Jaeryang Baek 0b01fd9d6f Merge pull request #9322 from jannikstdl/smart-suggestions
feat: smart suggestions
2025-02-04 00:38:56 -08:00
Jannik Streidl 17c1c2f0f3 fix: only animate on change 2025-02-04 09:36:11 +01:00
Jannik Streidl b8b82b9156 i18n 2025-02-04 09:17:55 +01:00
Jannik Streidl 43a490457d feat: smart suggestions 2025-02-04 09:14:02 +01:00
Timothy Jaeryang Baek f58ae6bd17 refac: styling 2025-02-03 22:11:01 -08:00
Timothy Jaeryang Baek 49e5d01213 refac: pyodide 2025-02-03 22:07:59 -08:00
Timothy Jaeryang Baek b20603e01a refac 2025-02-03 22:05:52 -08:00
Timothy Jaeryang Baek 3adfa29f7d chore: format 2025-02-03 21:56:35 -08:00
Timothy Jaeryang Baek a38ce75eda refac: styling 2025-02-03 21:25:16 -08:00
Timothy Jaeryang Baek 7e971f9dd1 refac 2025-02-03 21:13:27 -08:00
Timothy Jaeryang Baek 074a443790 refac 2025-02-03 21:10:49 -08:00
Timothy Jaeryang Baek 454294a101 refac: design
thanks @jannikstdl for nudging me to redesign 🙌

Co-Authored-By: Jannik S. <jannik@streidl.dev>
2025-02-03 20:53:46 -08:00
Timothy Jaeryang Baek d1c7a3cff3 enh: code interpreter prompt 2025-02-03 20:49:54 -08:00
Timothy Jaeryang Baek 1b8dc673e7 Merge pull request #9314 from roryeckel/validate-rag-urls
Validate URLs returned by search engine
2025-02-03 20:26:34 -08:00
Timothy Jaeryang Baek 71039b00a8 chore: format 2025-02-03 20:21:49 -08:00
Timothy Jaeryang Baek 16d62d7869 refac: tooltip 2025-02-03 20:21:11 -08:00
Timothy Jaeryang Baek 0ae7d12343 refac 2025-02-03 20:15:19 -08:00
Timothy Jaeryang Baek 1c518ab688 refac: styling 2025-02-03 20:13:14 -08:00
Timothy Jaeryang Baek ebde5c47d9 refac: styling 2025-02-03 20:06:58 -08:00
Timothy Jaeryang Baek 2bf68593af refac: styling 2025-02-03 20:06:17 -08:00
Timothy Jaeryang Baek b61138e676 refac: message input 2025-02-03 19:54:20 -08:00
Timothy Jaeryang Baek 2517a2b653 refac: styling 2025-02-03 19:34:36 -08:00
Timothy Jaeryang Baek 6d797820f5 refac: input ui 2025-02-03 19:18:31 -08:00
Timothy Jaeryang Baek f527f31d2b refac 2025-02-03 19:11:23 -08:00
Timothy Jaeryang Baek 94c6428ae2 refac: chat 2025-02-03 19:06:15 -08:00
Rory 1a1cc1514f Merge branch 'dev' into validate-rag-urls 2025-02-03 20:44:59 -06:00
Timothy Jaeryang Baek 8fdd3024f7 refac 2025-02-03 18:36:49 -08:00
Timothy Jaeryang Baek aa7184ae0d refac 2025-02-03 18:17:38 -08:00
Timothy Jaeryang Baek 51de2ffcc0 fix: ff 2025-02-03 18:03:38 -08:00
Timothy Jaeryang Baek e5bb0f014a refac 2025-02-03 17:19:33 -08:00
Rory 310a18eef2 Merge branch 'dev' into validate-rag-urls 2025-02-03 19:09:11 -06:00
Timothy Jaeryang Baek df07d671ce refac 2025-02-03 16:21:44 -08:00
Rory 3db6b4352f fix: Filter out invalid RAG web URLs (continued) 2025-02-03 18:18:49 -06:00
Timothy Jaeryang Baek 31c176a485 refac 2025-02-03 16:18:07 -08:00
Timothy Jaeryang Baek 943a57474f refac 2025-02-03 16:14:00 -08:00
Timothy Jaeryang Baek 1635dcb69b refac 2025-02-03 16:07:43 -08:00
Rory 121a13d4ed fix: Filter to valid RAG web search URLs 2025-02-03 17:37:20 -06:00
Timothy Jaeryang Baek e3214d08b5 refac 2025-02-03 15:20:27 -08:00
Timothy Jaeryang Baek db0ff839dd refac 2025-02-03 14:24:30 -08:00
Timothy Jaeryang Baek d87a7b2f8b refac: styling 2025-02-03 14:16:22 -08:00
Timothy Jaeryang Baek b891a398fd refac 2025-02-03 13:50:57 -08:00
Timothy Jaeryang Baek c85ce07f92 refac 2025-02-03 13:42:03 -08:00
Timothy Jaeryang Baek 5c0854c6b3 Merge pull request #9287 from taylorwilsdon/authenticate_gooogle_settings
fix: Restrict Google Drive credentials to authenticated users
2025-02-03 13:40:39 -08:00
Timothy Jaeryang Baek 3061d4bd10 Merge pull request #9291 from moblangeois/french-translation
i18n: Update French translation
2025-02-03 13:38:26 -08:00
Timothy Jaeryang Baek d37a7a1fe1 Merge branch 'dev' into french-translation 2025-02-03 13:38:08 -08:00
Timothy Jaeryang Baek 70b8199030 chore: format 2025-02-03 13:37:29 -08:00
Timothy Jaeryang Baek 9b78dde8a9 Merge pull request #9295 from OriginalSimon/dev
18n: Update Ukrainian translation
2025-02-03 13:36:42 -08:00
Simon bc56f442c1 Update translation.json 2025-02-03 21:04:17 +01:00
Morgan Blangeois 6c81cf9795 i18n: Add French translation 2025-02-03 20:16:53 +01:00
Taylor Wilsdon 74cf5e2a2e fix: Restrict Google Drive credentials to authenticated users 2025-02-03 11:55:52 -05:00
Timothy Jaeryang Baek 70116e7251 refac: styling 2025-02-03 01:18:16 -08:00
Timothy Jaeryang Baek 95c6d0e78c enh: code interpreter toggle 2025-02-03 01:14:38 -08:00
Timothy Jaeryang Baek ca4b839e6d enh: code interpreter 2025-02-03 00:24:09 -08:00
Timothy Jaeryang Baek 58d2fd9ac1 refac: styling 2025-02-03 00:03:41 -08:00
Timothy Jaeryang Baek 78853a67e4 refac 2025-02-02 23:35:58 -08:00
Timothy Jaeryang Baek 09cd95bffe enh: pyodide 2025-02-02 23:08:37 -08:00
Timothy Jaeryang Baek 8685256002 feat: code interpreter 2025-02-02 22:38:19 -08:00
Timothy Jaeryang Baek 0d33725d21 enh: pyodide 2025-02-02 21:18:52 -08:00
Timothy Jaeryang Baek eb1ede119e refac: reasoning tag 2025-02-02 20:50:54 -08:00
Timothy Jaeryang Baek e4fc2e0e51 refac 2025-02-02 19:24:07 -08:00
Timothy Jaeryang Baek 74172461c7 enh: pyodide python result handling 2025-02-02 16:56:21 -08:00
Timothy Jaeryang Baek b94b691993 enh: utils 2025-02-02 15:11:50 -08:00
Timothy Jaeryang Baek fc1cec10be chore: bump 2025-02-01 21:05:08 -08:00
Timothy Jaeryang Baek 95c94d8a85 Merge pull request #9191 from NYU-ITS/milvus_auth_token
[feat] Milvus: new optional config var, MILVUS_TOKEN
2025-02-01 21:04:30 -08:00
Timothy Jaeryang Baek 5ce4b1c570 Merge pull request #9173 from KarlLee830/fix-duckduckgo
Fix DuckDuckGo's 202 Error RateLimit
2025-02-01 21:02:31 -08:00
Timothy Jaeryang Baek af6f0641cb chore: bump 2025-02-01 21:02:12 -08:00
Timothy Jaeryang Baek 642a093d02 refac: tool calls 2025-02-01 21:01:06 -08:00
Timothy Jaeryang Baek 28cf616147 Merge pull request #9200 from open-webui/dependabot/pip/backend/dev/opencv-python-headless-4.11.0.86
build(deps): bump opencv-python-headless from 4.10.0.84 to 4.11.0.86 in /backend
2025-02-01 10:04:26 -08:00
Timothy Jaeryang Baek ffe1abfda9 Merge pull request #9198 from open-webui/dependabot/pip/backend/dev/aiohttp-3.11.11
build(deps): bump aiohttp from 3.11.8 to 3.11.11 in /backend
2025-02-01 10:04:18 -08:00
Timothy Jaeryang Baek 6b879abdb9 Merge pull request #9199 from open-webui/dependabot/pip/backend/dev/soundfile-0.13.1
build(deps): bump soundfile from 0.12.1 to 0.13.1 in /backend
2025-02-01 10:04:11 -08:00
Timothy Jaeryang Baek a4b8e3d958 Merge pull request #9201 from open-webui/dependabot/pip/backend/dev/authlib-1.4.1
build(deps): bump authlib from 1.3.2 to 1.4.1 in /backend
2025-02-01 10:04:02 -08:00
Timothy Jaeryang Baek 442ad41d2b Merge pull request #9202 from open-webui/dependabot/pip/backend/dev/pymdown-extensions-10.14.2
build(deps): bump pymdown-extensions from 10.11.2 to 10.14.2 in /backend
2025-02-01 10:03:53 -08:00
Timothy Jaeryang Baek 3502e09d9f fix: image prompt gen template 2025-01-31 23:50:58 -08:00
Timothy Jaeryang Baek af10f87075 refac: textarea 2025-01-31 23:49:25 -08:00
Timothy Jaeryang Baek 4e375c892a refac 2025-01-31 23:15:49 -08:00
Timothy Jaeryang Baek bc5452ea41 Merge pull request #9195 from rragundez/default-picture
[EASY] Set default picture to user.png
2025-01-31 19:35:52 -08:00
dependabot[bot] aa2eb37e10 build(deps): bump pymdown-extensions from 10.11.2 to 10.14.2 in /backend
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.11.2 to 10.14.2.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.11.2...10.14.2)

---
updated-dependencies:
- dependency-name: pymdown-extensions
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 02:36:20 +00:00
dependabot[bot] c00d1a2f87 build(deps): bump authlib from 1.3.2 to 1.4.1 in /backend
Bumps [authlib](https://github.com/lepture/authlib) from 1.3.2 to 1.4.1.
- [Release notes](https://github.com/lepture/authlib/releases)
- [Changelog](https://github.com/lepture/authlib/blob/master/docs/changelog.rst)
- [Commits](https://github.com/lepture/authlib/compare/v1.3.2...v1.4.1)

---
updated-dependencies:
- dependency-name: authlib
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 02:36:17 +00:00
dependabot[bot] 824aad4703 build(deps): bump opencv-python-headless in /backend
Bumps [opencv-python-headless](https://github.com/opencv/opencv-python) from 4.10.0.84 to 4.11.0.86.
- [Release notes](https://github.com/opencv/opencv-python/releases)
- [Commits](https://github.com/opencv/opencv-python/commits)

---
updated-dependencies:
- dependency-name: opencv-python-headless
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 02:36:14 +00:00
dependabot[bot] f661bfde7d build(deps): bump soundfile from 0.12.1 to 0.13.1 in /backend
Bumps [soundfile](https://github.com/bastibe/python-soundfile) from 0.12.1 to 0.13.1.
- [Release notes](https://github.com/bastibe/python-soundfile/releases)
- [Commits](https://github.com/bastibe/python-soundfile/compare/0.12.1...0.13.1)

---
updated-dependencies:
- dependency-name: soundfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 02:36:11 +00:00
dependabot[bot] 9d52120c62 build(deps): bump aiohttp from 3.11.8 to 3.11.11 in /backend
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 02:36:08 +00:00
Rodrigo Agundez 30e4b32a5b Set default picture to user.png 2025-02-01 08:32:04 +09:00
Sajid Ali 7b31c75271 Milvus: new optional config var, MILVUS_TOKEN
modified:   backend/open_webui/config.py
	modified:   backend/open_webui/retrieval/vector/dbs/milvus.py
2025-01-31 17:01:00 -05:00
Timothy Jaeryang Baek 9b640b0ac2 Merge pull request #9163 from rragundez/default-role
Bug fix, configurable default if using oauth via the `DEFAULT_USER_ROLE` env variable
2025-01-31 12:38:32 -08:00
KarlLee830 e7de41499a fix: DuckDuckGo's RateLimit exception 2025-02-01 02:01:22 +08:00
Rodrigo Agundez 1f2b5fa68f move comment to top 2025-01-31 23:05:33 +09:00
Rodrigo Agundez 681851ca6b Configurable default role for oauth 2025-01-31 23:00:24 +09:00
Timothy Jaeryang Baek 74aec6bdf9 Merge pull request #9143 from kostich/dev
i18n: Update Serbian translation
2025-01-31 00:47:50 -08:00
Timothy Jaeryang Baek e3e7176b0b Merge pull request #9142 from dannyl1u/feat/delete-unused-response-message
feat: delete unused response message from other "branches"
2025-01-30 21:28:10 -08:00
Марко М. Костић (Marko M. Kostić) 968787ac3e Update Serbian translation 2025-01-31 06:16:08 +01:00
dannyl1u e9f05b6cd7 feat: delete message responses 2025-01-30 20:48:06 -08:00
Timothy Jaeryang Baek 8fdb37a17f Merge pull request #9131 from 0xThresh/dev
fix: Add error catching for function pip install issues
2025-01-30 15:02:47 -08:00
Timothy Jaeryang Baek 4d376c011a Merge pull request #9129 from imJack6/dev
i18n: Corrected localization typos in Simplified Chinese
2025-01-30 15:00:21 -08:00
Timothy Jaeryang Baek 5ebcd44163 Merge pull request #9110 from muhac/dev
i18n: improved Chinese translations of think tag
2025-01-30 15:00:04 -08:00
James W. 124fdcc48c Merge branch 'open-webui:dev' into dev 2025-01-30 13:34:01 -07:00
James Westbrook 48c2fb0d24 fix: Add error catching for function pip install issues 2025-01-30 13:32:11 -07:00
Jack Lei 0d4d81e012 i18n: Corrected localization typos in Simplified Chinese 2025-01-31 03:47:37 +08:00
Muhan Li 14bbcd7046 i18n: improved Chinese translations of think tag 2025-01-30 05:22:59 -05:00
Timothy Jaeryang Baek cc99673906 refac: prompt variables 2025-01-29 21:56:51 -08:00
Timothy Jaeryang Baek d6c82939e6 refac 2025-01-29 21:56:28 -08:00
Timothy Jaeryang Baek 33fffd6b1d refac: rm print statement 2025-01-29 21:36:05 -08:00
Timothy Jaeryang Baek cd088b2f71 fix 2025-01-29 21:32:07 -08:00
Timothy Jaeryang Baek a8f2919e9e fix: #9003 2025-01-29 21:31:18 -08:00
Timothy Jaeryang Baek be665f2a3e Merge pull request #8493 from kyunwang/fix/oidc-500-error-name-field
fix: Check OAuth name type with fallback
2025-01-29 21:08:21 -08:00
Timothy Jaeryang Baek 8727b91664 fix: display usage for non-streaming ollama response 2025-01-29 21:07:22 -08:00
Timothy Jaeryang Baek 248fcf1636 refac: styling 2025-01-29 21:06:01 -08:00
Timothy Jaeryang Baek 7f61d01fde refac: playground 2025-01-29 20:35:37 -08:00
Timothy Jaeryang Baek 52010accc9 refac 2025-01-29 20:11:37 -08:00
Timothy Jaeryang Baek 6658e67113 fix: title gen 2025-01-29 20:03:46 -08:00
Timothy Jaeryang Baek 08ad4ee018 enh: check for reasoning tags 2025-01-29 14:59:23 -08:00
Timothy Jaeryang Baek 5420c165c6 refac: title generation 2025-01-29 14:40:36 -08:00
Timothy Jaeryang Baek 4abede9a2b fix: uploaded file should not be deleted 2025-01-29 14:20:51 -08:00
Timothy Jaeryang Baek 40a4443949 Merge pull request #9082 from Alex1607/main
FIX max_tokens not being set properly
2025-01-29 11:48:05 -08:00
Timothy Jaeryang Baek 84c700b92c Merge pull request #9059 from jrespeto/jrespeto-patch-1
fix:  Update ollama.py - Missing await in coroutine
2025-01-29 11:39:42 -08:00
Timothy Jaeryang Baek 5fef92c95e Merge pull request #9064 from iidx/i18n-typo
i18n: update ko-KR
2025-01-29 11:39:29 -08:00
Alexander c2e742afe1 Fix max_tokens not being set properly 2025-01-29 19:28:09 +01:00
Jonathan Respeto a32782e527 fix: Update openai.py
need to await get_filtered_models
2025-01-29 08:47:54 -05:00
Didier FOURNOUT 6ca295ec59 Add user related headers when calling an external embedding api 2025-01-29 10:55:52 +00:00
iidx 938a151f12 i18n: some korean update 2025-01-29 17:30:53 +09:00
iidx f502e7db51 i18n: some korean update 2025-01-29 17:18:13 +09:00
iidx bd24ed953a i18n: Corrected typos in korean 2025-01-29 15:57:44 +09:00
Timothy Jaeryang Baek 9418364062 chore: fastapi bump 2025-01-28 18:36:23 -08:00
Jonathan Respeto b4be58f6b6 Update ollama.py
Missing await in coroutine

causing 500 errors on /ollama/api/tags since v0.5.0
2025-01-28 18:27:41 -05:00
Timothy Jaeryang Baek c5b5303a55 Merge pull request #9031 from bunnerz-king/main
LDAP auth, return user permissions in response
2025-01-28 14:50:04 -08:00
Timothy Jaeryang Baek c021aba094 enh: chat "clone" i18n 2025-01-28 12:49:06 -08:00
hurxxxx 8e0c0b4ae3 fix : Infinite loading issue occurs. 2025-01-29 04:42:41 +09:00
hurxxxx 857b3f0da8 Merge remote-tracking branch 'upstream/dev' into feat/rag-status 2025-01-29 02:01:50 +09:00
hurxxxx af979ce49a feat: Add user setting for "Always Enable Search" 2025-01-29 02:01:46 +09:00
Andrew King a1b5c18ef7 ldap pass user permissions into response 2025-01-28 09:51:21 -05:00
Timothy Jaeryang Baek 8c7e52e5a1 Merge pull request #9010 from Shadowfita/patch-1
fix: Include empty delta object on openai_chat_chunk_message_template per OpenAI API documentation
2025-01-27 17:51:09 -08:00
Orion 95f4d99e3b Update misc.py
Include empty delta object on openai_chat_chunk_message_template per OpenAI API documentation. 

https://platform.openai.com/docs/api-reference/chat/streaming#chat/streaming
2025-01-28 09:53:22 +10:00
Timothy Jaeryang Baek ce6e8d1264 Merge pull request #9006 from TiancongLx/dev
i18n: update zh-TW
2025-01-27 15:15:32 -08:00
Tiancong Li 45e19fb9bd i18n: update zh-TW 2025-01-28 05:08:19 +08:00
Timothy Jaeryang Baek cd560a359c Merge pull request #8946 from zeroxs/feat/localize-dates
feat: Localized dates and times
2025-01-27 10:33:59 -08:00
Timothy Jaeryang Baek ec62104211 Merge pull request #8978 from antpyykk-kone/feature/separate-auth-cookie-config
fix: Separate cookie configuration between session & auth cookies to prevent oauth flow from breaking
2025-01-27 10:32:13 -08:00
Timothy Jaeryang Baek acdcb32ac5 Merge pull request #9003 from tarmst/fix-no-file-upload-for-write-groups
Fix: Users with write adding docs to knowledge, deleting, and fixing read/write toggle on model & knowledge creation
2025-01-27 10:30:18 -08:00
tarmst 751a61a364 Adding more checks for write access. Adding accessRoles to Model & Knowledge creation 2025-01-27 18:11:52 +00:00
Kangyun Wang 564c0fed95 Fallback using email
- Use Email ass fallback for missing "name" field
- "email" because the email scope is required unlike the profile scope
2025-01-27 13:11:20 +01:00
Kevin Wang 9eaf01c323 Merge branch 'open-webui:main' into fix/oidc-500-error-name-field 2025-01-27 13:10:08 +01:00
Timothy Jaeryang Baek 6eb51ab62e enh: file upload permission indicator 2025-01-26 23:30:46 -08:00
Timothy Jaeryang Baek 6f3c92f6d5 enh: chat loading screen 2025-01-26 23:17:58 -08:00
Timothy Jaeryang Baek 91221cd4b1 Merge pull request #8948 from kokutaro/i18n/japanese-translation
Fix typo on Japanese
2025-01-26 20:11:14 -08:00
kokutaro 2d562ed317 Fixed the type on translation.json
Fixed the typo on Japanese translation file
2025-01-26 17:51:20 +09:00
Sharon Fox dda945f4ad feat: Localized dates and times 2025-01-26 02:47:23 -05:00
Timothy Jaeryang Baek 24ab79f5e9 Merge pull request #8857 from KarlLee830/translate
i18n: Update Chinese Translation
2025-01-24 08:41:02 -08:00
KarlLee830 6e306beaa2 i18n: Update Chinese Translation 2025-01-24 16:02:35 +08:00
Timothy Jaeryang Baek b72150c881 Merge pull request #8835 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.5.7
2025-01-23 13:47:40 -08:00
Timothy Jaeryang Baek ceaefd8053 refac 2025-01-23 13:45:45 -08:00
Timothy Jaeryang Baek 2d84db2621 doc: changelog 2025-01-23 13:36:38 -08:00
Timothy Jaeryang Baek 3527023d88 refac: think tag 2025-01-23 13:04:37 -08:00
Timothy Jaeryang Baek e99453219a chore: format 2025-01-23 12:58:39 -08:00
Timothy Jaeryang Baek 45f88b80ad refac: think status 2025-01-23 12:53:17 -08:00
Timothy Jaeryang Baek ba81b47800 refac 2025-01-23 12:06:25 -08:00
Timothy Jaeryang Baek 646671c576 Merge pull request #8837 from TiancongLx/dev
i18n: update zh-TW
2025-01-23 11:23:40 -08:00
Tiancong Li 3d5e361a21 i18n: update zh-TW 2025-01-24 03:13:10 +08:00
Timothy Jaeryang Baek bb45d35a36 fix: ollama prefix id 2025-01-23 11:04:50 -08:00
Timothy Jaeryang Baek 6a9978dd12 Merge pull request #8801 from aleixdorca/dev
i18n: Update catalan translation.json
2025-01-23 10:53:24 -08:00
Timothy Jaeryang Baek 1dd8e5fafd Merge pull request #8821 from panda44312/patch-3
i18n - Update zh-cn Updated translation
2025-01-23 10:52:26 -08:00
Timothy Jaeryang Baek dd6de749d5 fix: model update access 2025-01-23 10:40:49 -08:00
Timothy Jaeryang Baek 8fc5532e2f fix: tools valves access 2025-01-23 10:37:44 -08:00
Panda 863f73de20 Update zh-cn 2025-01-23 18:44:52 +01:00
Antti Pyykkönen 412923dc91 feat: separate cookie settings between session & auth cookies
Introducing two new env config options to control cookies settings regarding
authentication. These values are taken into use when setting 'token' and 'oauth_id_token'.
To maintain backwards compatibility, the original session cookie values are used as
fallback.

Separation is done to prevent issues with the session cookie. When the config value was
set as 'strict', the oauth flow was broken (since the session cookie was not provided
after the callback).

Providing a separate config for auth & session cookies allows us to keep the 'strict'
settings for auth related cookies, while also allowing the session cookie to behave as
intended (e.g., by configuring it as 'lax').

The original config was added in commit #af4f8aa. However a later commit #a2e889c reused
this config option for other type of cookies, which was not the original intent.
2025-01-23 16:16:50 +02:00
Aleix Dorca 6f17c29d93 Update catalan translation.json 2025-01-23 08:39:02 +01:00
Timothy Jaeryang Baek 6c8d68b6fc Merge pull request #8793 from open-webui/dev
fix: model ids not being applied
2025-01-22 19:01:31 -08:00
Timothy Jaeryang Baek 4a2792b4da fix: model ids not being applied 2025-01-22 19:00:46 -08:00
Timothy Jaeryang Baek 9dd45ddf7c Merge pull request #8779 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
fix: changelog
2025-01-22 14:13:56 -08:00
Timothy Jaeryang Baek dcc57500dd fix: changelog 2025-01-22 14:13:06 -08:00
Timothy Jaeryang Baek e3ae30e42f Merge pull request #8776 from open-webui/dev
refac: image gen
2025-01-22 12:49:53 -08:00
Timothy Jaeryang Baek 14e650077a refac 2025-01-22 12:49:29 -08:00
Timothy Jaeryang Baek 36b9bcee0f Merge pull request #8770 from open-webui/dev
0.5.6
2025-01-22 12:47:05 -08:00
Timothy Jaeryang Baek 6186dec781 doc: changelog 2025-01-22 12:43:46 -08:00
Timothy Jaeryang Baek 667928efb0 chore: format 2025-01-22 12:38:42 -08:00
Timothy Jaeryang Baek f4b5039adf enh: new ollama create model method 2025-01-22 12:36:18 -08:00
Timothy Jaeryang Baek 8380d3f5ae refac 2025-01-22 12:19:11 -08:00
Timothy Jaeryang Baek 419005a578 refac: manage ollama 2025-01-22 12:19:01 -08:00
Timothy Jaeryang Baek 7593d11b97 refac: wording 2025-01-22 12:08:08 -08:00
Timothy Jaeryang Baek aaf97b85dc enh: reasoning_effort param support for openai 2025-01-22 12:07:04 -08:00
Timothy Jaeryang Baek ea7d4ec6ea refac: styling 2025-01-22 11:56:39 -08:00
Kangyun Wang 1ad9be9c07 Check OAuth name type with fallback 2025-01-12 12:21:53 +01:00
150 changed files with 4861 additions and 1821 deletions
+62
View File
@@ -5,6 +5,68 @@ 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.5.9] - 2025-02-05
### Fixed
- **💡 "Think" Tag Display Issue**: Resolved a bug where the "Think" tag was not functioning correctly, ensuring proper visualization of the model's reasoning process before delivering responses.
## [0.5.8] - 2025-02-05
### Added
- **🖥️ Code Interpreter**: Models can now execute code in real time to refine their answers dynamically, running securely within a sandboxed browser environment using Pyodide. Perfect for calculations, data analysis, and AI-assisted coding tasks!
- **💬 Redesigned Chat Input UI**: Enjoy a sleeker and more intuitive message input with improved feature selection, making it easier than ever to toggle tools, enable search, and interact with AI seamlessly.
- **🛠️ Native Tool Calling Support (Experimental)**: Supported models can now call tools natively, reducing query latency and improving contextual responses. More enhancements coming soon!
- **🔗 Exa Search Engine Integration**: A new search provider has been added, allowing users to retrieve up-to-date and relevant information without leaving the chat interface.
- **🌍 Localized Dates & Times**: Date and time formats now match your system locale, ensuring a more natural, region-specific experience.
- **📎 User Headers for External Embedding APIs**: API calls to external embedding services now include user-related headers.
- **🌍 "Always On" Web Search Toggle**: A new option under Settings > Interface allows users to enable Web Search by default—transform Open WebUI into your go-to search engine, ensuring AI-powered results with every query.
- **🚀 General Performance & Stability**: Significant improvements across the platform for a faster, more reliable experience.
- **🖼️ UI/UX Enhancements**: Numerous design refinements improving readability, responsiveness, and accessibility.
- **🌍 Improved Translations**: Chinese, Korean, French, Ukrainian and Serbian translations have been updated with refined terminologies for better clarity.
### Fixed
- **🔄 OAuth Name Field Fallback**: Resolves OAuth login failures by using the email field as a fallback when a name is missing.
- **🔑 Google Drive Credentials Restriction**: Ensures only authenticated users can access Google Drive credentials for enhanced security.
- **🌐 DuckDuckGo Search Rate Limit Handling**: Fixes issues where users would encounter 202 errors due to rate limits when using DuckDuckGo for web search.
- **📁 File Upload Permission Indicator**: Users are now notified when they lack permission to upload files, improving clarity on system restrictions.
- **🔧 Max Tokens Issue**: Fixes cases where 'max_tokens' were not applied correctly, ensuring proper model behavior.
- **🔍 Validation for RAG Web Search URLs**: Filters out invalid or unsupported URLs when using web-based retrieval augmentation.
- **🖋️ Title Generation Bug**: Fixes inconsistencies in title generation, ensuring proper chat organization.
### Removed
- **⚡ Deprecated Non-Web Worker Pyodide Execution**: Moves entirely to browser sandboxing for better performance and security.
## [0.5.7] - 2025-01-23
### Added
- **🌍 Enhanced Internationalization (i18n)**: Refined and expanded translations for greater global accessibility and a smoother experience for international users.
### Fixed
- **🔗 Connection Model ID Resolution**: Resolved an issue preventing model IDs from registering in connections.
- **💡 Prefix ID for Ollama Connections**: Fixed a bug where prefix IDs in Ollama connections were non-functional.
- **🔧 Ollama Model Enable/Disable Functionality**: Addressed the issue of enable/disable toggles not working for Ollama base models.
- **🔒 RBAC Permissions for Tools and Models**: Corrected incorrect Role-Based Access Control (RBAC) permissions for tools and models, ensuring that users now only access features according to their assigned privileges, enhancing security and role clarity.
## [0.5.6] - 2025-01-22
### Added
- **🧠 Effortful Reasoning Control for OpenAI Models**: Introduced the reasoning_effort parameter in chat controls for supported OpenAI models, enabling users to fine-tune how much cognitive effort a model dedicates to its responses, offering greater customization for complex queries and reasoning tasks.
### Fixed
- **🔄 Chat Controls Loading UI Bug**: Resolved an issue where collapsible chat controls appeared as "loading," ensuring a smoother and more intuitive user experience for managing chat settings.
### Changed
- **🔧 Updated Ollama Model Creation**: Revamped the Ollama model creation method to align with their new JSON payload format, ensuring seamless compatibility and more efficient model setup workflows.
## [0.5.5] - 2025-01-22
### Added
+70 -12
View File
@@ -927,6 +927,12 @@ USER_PERMISSIONS_FEATURES_IMAGE_GENERATION = (
== "true"
)
USER_PERMISSIONS_FEATURES_CODE_INTERPRETER = (
os.environ.get("USER_PERMISSIONS_FEATURES_CODE_INTERPRETER", "True").lower()
== "true"
)
DEFAULT_USER_PERMISSIONS = {
"workspace": {
"models": USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS,
@@ -944,6 +950,7 @@ DEFAULT_USER_PERMISSIONS = {
"features": {
"web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
"image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
"code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
},
}
@@ -1094,21 +1101,27 @@ TITLE_GENERATION_PROMPT_TEMPLATE = PersistentConfig(
os.environ.get("TITLE_GENERATION_PROMPT_TEMPLATE", ""),
)
DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE = """Create a concise, 3-5 word title with an emoji as a title for the chat history, in the given language. Suitable Emojis for the summary can be used to enhance understanding but avoid quotation marks or special formatting. RESPOND ONLY WITH THE TITLE TEXT.
Examples of titles:
📉 Stock Market Trends
🍪 Perfect Chocolate Chip Recipe
Evolution of Music Streaming
Remote Work Productivity Tips
Artificial Intelligence in Healthcare
🎮 Video Game Development Insights
DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE = """### Task:
Generate a concise, 3-5 word title with an emoji summarizing the chat history.
### Guidelines:
- The title should clearly represent the main theme or subject of the conversation.
- Use emojis that enhance understanding of the topic, but avoid quotation marks or special formatting.
- Write the title in the chat's primary language; default to English if multilingual.
- Prioritize accuracy over excessive creativity; keep it clear and simple.
### Output:
JSON format: { "title": "your concise title here" }
### Examples:
- { "title": "📉 Stock Market Trends" },
- { "title": "🍪 Perfect Chocolate Chip Recipe" },
- { "title": "Evolution of Music Streaming" },
- { "title": "Remote Work Productivity Tips" },
- { "title": "Artificial Intelligence in Healthcare" },
- { "title": "🎮 Video Game Development Insights" }
### Chat History:
<chat_history>
{{MESSAGES:END:2}}
</chat_history>"""
TAGS_GENERATION_PROMPT_TEMPLATE = PersistentConfig(
"TAGS_GENERATION_PROMPT_TEMPLATE",
"task.tags.prompt_template",
@@ -1277,7 +1290,28 @@ TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = PersistentConfig(
)
DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = """Available Tools: {{TOOLS}}\nReturn an empty string if no tools match the query. If a function tool matches, construct and return a JSON object in the format {\"name\": \"functionName\", \"parameters\": {\"requiredFunctionParamKey\": \"requiredFunctionParamValue\"}} using the appropriate tool and its parameters. Only return the object and limit the response to the JSON object without additional text."""
DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = """Available Tools: {{TOOLS}}
Your task is to choose and return the correct tool(s) from the list of available tools based on the query. Follow these guidelines:
- Return only the JSON object, without any additional text or explanation.
- If no tools match the query, return an empty array:
{
"tool_calls": []
}
- If one or more tools match the query, construct a JSON response containing a "tool_calls" array with objects that include:
- "name": The tool's name.
- "parameters": A dictionary of required parameters and their corresponding values.
The format for the JSON response is strictly:
{
"tool_calls": [
{"name": "toolName1", "parameters": {"key1": "value1"}},
{"name": "toolName2", "parameters": {"key2": "value2"}}
]
}"""
DEFAULT_EMOJI_GENERATION_PROMPT_TEMPLATE = """Your task is to reflect the speaker's likely facial expression through a fitting emoji. Interpret emotions from the message and reflect their facial expression using fitting, diverse emojis (e.g., 😊, 😢, 😡, 😱).
@@ -1290,6 +1324,24 @@ Your task is to synthesize these responses into a single, high-quality response.
Responses from models: {{responses}}"""
DEFAULT_CODE_INTERPRETER_PROMPT = """
#### Tools Available
1. **Code Interpreter**: `<code_interpreter type="code" lang="python"></code_interpreter>`
- You have access to a Python shell that runs directly in the user's browser, enabling fast execution of code for analysis, calculations, or problem-solving. Use it in this response.
- The Python code you write can incorporate a wide array of libraries, handle data manipulation or visualization, perform API calls for web-related tasks, or tackle virtually any computational challenge. Use this flexibility to **think outside the box, craft elegant solutions, and harness Python's full potential**.
- To use it, **you must enclose your code within `<code_interpreter type="code" lang="python">` XML tags** and stop right away. If you don't, the code won't execute. Do NOT use triple backticks.
- When coding, **always aim to print meaningful outputs** (e.g., results, tables, summaries, or visuals) to better interpret and verify the findings. Avoid relying on implicit outputs; prioritize explicit and clear print statements so the results are effectively communicated to the user.
- After obtaining the printed output, **always provide a concise analysis, interpretation, or next steps to help the user understand the findings or refine the outcome further.**
- If the results are unclear, unexpected, or require validation, refine the code and execute it again as needed. Always aim to deliver meaningful insights from the results, iterating if necessary.
- If a link is provided for an image, audio, or any file, include it in the response exactly as given to ensure the user has access to the original resource.
- All responses should be communicated in the chat's primary language, ensuring seamless understanding. If the chat is multilingual, default to English for clarity.
- **If a link to an image, audio, or any file is provided in markdown format, explicitly display it as part of the response to ensure the user can access it easily, do NOT change the link.**
Ensure that the tools are effectively utilized to achieve the highest-quality analysis for the user."""
####################################
# Vector Database
####################################
@@ -1319,6 +1371,7 @@ CHROMA_HTTP_SSL = os.environ.get("CHROMA_HTTP_SSL", "false").lower() == "true"
MILVUS_URI = os.environ.get("MILVUS_URI", f"{DATA_DIR}/vector_db/milvus.db")
MILVUS_DB = os.environ.get("MILVUS_DB", "default")
MILVUS_TOKEN = os.environ.get("MILVUS_TOKEN", None)
# Qdrant
QDRANT_URI = os.environ.get("QDRANT_URI", None)
@@ -1699,6 +1752,11 @@ BING_SEARCH_V7_SUBSCRIPTION_KEY = PersistentConfig(
os.environ.get("BING_SEARCH_V7_SUBSCRIPTION_KEY", ""),
)
EXA_API_KEY = PersistentConfig(
"EXA_API_KEY",
"rag.web.search.exa_api_key",
os.getenv("EXA_API_KEY", ""),
)
RAG_WEB_SEARCH_RESULT_COUNT = PersistentConfig(
"RAG_WEB_SEARCH_RESULT_COUNT",
+1 -1
View File
@@ -57,7 +57,7 @@ class ERROR_MESSAGES(str, Enum):
)
FILE_NOT_SENT = "FILE_NOT_SENT"
FILE_NOT_SUPPORTED = "Oops! It seems like the file format you're trying to upload is not supported. Please upload a file with a supported format (e.g., JPG, PNG, PDF, TXT) and try again."
FILE_NOT_SUPPORTED = "Oops! It seems like the file format you're trying to upload is not supported. Please upload a file with a supported format and try again."
NOT_FOUND = "We could not find what you're looking for :/"
USER_NOT_FOUND = "We could not find what you're looking for :/"
+14 -6
View File
@@ -356,14 +356,22 @@ WEBUI_SECRET_KEY = os.environ.get(
), # DEPRECATED: remove at next major version
)
WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get(
"WEBUI_SESSION_COOKIE_SAME_SITE",
os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax"),
WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax")
WEBUI_SESSION_COOKIE_SECURE = (
os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true"
)
WEBUI_SESSION_COOKIE_SECURE = os.environ.get(
"WEBUI_SESSION_COOKIE_SECURE",
os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true",
WEBUI_AUTH_COOKIE_SAME_SITE = os.environ.get(
"WEBUI_AUTH_COOKIE_SAME_SITE", WEBUI_SESSION_COOKIE_SAME_SITE
)
WEBUI_AUTH_COOKIE_SECURE = (
os.environ.get(
"WEBUI_AUTH_COOKIE_SECURE",
os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false"),
).lower()
== "true"
)
if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
+23 -6
View File
@@ -177,6 +177,7 @@ from open_webui.config import (
BING_SEARCH_V7_ENDPOINT,
BING_SEARCH_V7_SUBSCRIPTION_KEY,
BRAVE_SEARCH_API_KEY,
EXA_API_KEY,
KAGI_SEARCH_API_KEY,
MOJEEK_SEARCH_API_KEY,
GOOGLE_PSE_API_KEY,
@@ -523,6 +524,7 @@ app.state.config.SEARCHAPI_ENGINE = SEARCHAPI_ENGINE
app.state.config.JINA_API_KEY = JINA_API_KEY
app.state.config.BING_SEARCH_V7_ENDPOINT = BING_SEARCH_V7_ENDPOINT
app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY = BING_SEARCH_V7_SUBSCRIPTION_KEY
app.state.config.EXA_API_KEY = EXA_API_KEY
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = RAG_WEB_SEARCH_RESULT_COUNT
app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
@@ -859,6 +861,7 @@ async def chat_completion(
if model_id not in request.app.state.MODELS:
raise Exception("Model not found")
model = request.app.state.MODELS[model_id]
model_info = Models.get_model_by_id(model_id)
# Check if user has access to the model
if not BYPASS_MODEL_ACCESS_CONTROL and user.role == "user":
@@ -875,12 +878,25 @@ async def chat_completion(
"tool_ids": form_data.get("tool_ids", None),
"files": form_data.get("files", None),
"features": form_data.get("features", None),
"variables": form_data.get("variables", None),
"model": model_info,
**(
{"function_calling": "native"}
if form_data.get("params", {}).get("function_calling") == "native"
or (
model_info
and model_info.params.model_dump().get("function_calling")
== "native"
)
else {}
),
}
form_data["metadata"] = metadata
form_data, events = await process_chat_payload(
form_data, metadata, events = await process_chat_payload(
request, form_data, metadata, user, model
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -889,6 +905,7 @@ async def chat_completion(
try:
response = await chat_completion_handler(request, form_data, user)
return await process_chat_response(
request, response, form_data, user, events, metadata, tasks
)
@@ -1007,10 +1024,6 @@ async def get_app_config(request: Request):
else {}
),
},
"google_drive": {
"client_id": GOOGLE_DRIVE_CLIENT_ID.value,
"api_key": GOOGLE_DRIVE_API_KEY.value,
},
**(
{
"default_models": app.state.config.DEFAULT_MODELS,
@@ -1030,6 +1043,10 @@ async def get_app_config(request: Request):
"max_count": app.state.config.FILE_MAX_COUNT,
},
"permissions": {**app.state.config.USER_PERMISSIONS},
"google_drive": {
"client_id": GOOGLE_DRIVE_CLIENT_ID.value,
"api_key": GOOGLE_DRIVE_API_KEY.value,
},
}
if user is not None
else {}
@@ -1063,7 +1080,7 @@ async def get_app_version():
@app.get("/api/version/updates")
async def get_app_latest_release_version():
async def get_app_latest_release_version(user=Depends(get_verified_user)):
if OFFLINE_MODE:
log.debug(
f"Offline mode is enabled, returning current version as latest version"
+55 -16
View File
@@ -15,8 +15,13 @@ from langchain_core.documents import Document
from open_webui.config import VECTOR_DB
from open_webui.retrieval.vector.connector import VECTOR_DB_CLIENT
from open_webui.utils.misc import get_last_user_message
from open_webui.models.users import UserModel
from open_webui.env import SRC_LOG_LEVELS, OFFLINE_MODE
from open_webui.env import (
SRC_LOG_LEVELS,
OFFLINE_MODE,
ENABLE_FORWARD_USER_INFO_HEADERS,
)
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -61,9 +66,7 @@ class VectorSearchRetriever(BaseRetriever):
def query_doc(
collection_name: str,
query_embedding: list[float],
k: int,
collection_name: str, query_embedding: list[float], k: int, user: UserModel = None
):
try:
result = VECTOR_DB_CLIENT.search(
@@ -259,26 +262,31 @@ def get_embedding_function(
embedding_batch_size,
):
if embedding_engine == "":
return lambda query: embedding_function.encode(query).tolist()
return lambda query, user=None: embedding_function.encode(query).tolist()
elif embedding_engine in ["ollama", "openai"]:
func = lambda query: generate_embeddings(
func = lambda query, user=None: generate_embeddings(
engine=embedding_engine,
model=embedding_model,
text=query,
url=url,
key=key,
user=user,
)
def generate_multiple(query, func):
def generate_multiple(query, user, func):
if isinstance(query, list):
embeddings = []
for i in range(0, len(query), embedding_batch_size):
embeddings.extend(func(query[i : i + embedding_batch_size]))
embeddings.extend(
func(query[i : i + embedding_batch_size], user=user)
)
return embeddings
else:
return func(query)
return func(query, user)
return lambda query: generate_multiple(query, func)
return lambda query, user=None: generate_multiple(query, user, func)
else:
raise ValueError(f"Unknown embedding engine: {embedding_engine}")
def get_sources_from_files(
@@ -423,7 +431,11 @@ def get_model_path(model: str, update_model: bool = False):
def generate_openai_batch_embeddings(
model: str, texts: list[str], url: str = "https://api.openai.com/v1", key: str = ""
model: str,
texts: list[str],
url: str = "https://api.openai.com/v1",
key: str = "",
user: UserModel = None,
) -> Optional[list[list[float]]]:
try:
r = requests.post(
@@ -431,6 +443,16 @@ def generate_openai_batch_embeddings(
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {key}",
**(
{
"X-OpenWebUI-User-Name": user.name,
"X-OpenWebUI-User-Id": user.id,
"X-OpenWebUI-User-Email": user.email,
"X-OpenWebUI-User-Role": user.role,
}
if ENABLE_FORWARD_USER_INFO_HEADERS and user
else {}
),
},
json={"input": texts, "model": model},
)
@@ -446,7 +468,7 @@ def generate_openai_batch_embeddings(
def generate_ollama_batch_embeddings(
model: str, texts: list[str], url: str, key: str = ""
model: str, texts: list[str], url: str, key: str = "", user: UserModel = None
) -> Optional[list[list[float]]]:
try:
r = requests.post(
@@ -454,6 +476,16 @@ def generate_ollama_batch_embeddings(
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {key}",
**(
{
"X-OpenWebUI-User-Name": user.name,
"X-OpenWebUI-User-Id": user.id,
"X-OpenWebUI-User-Email": user.email,
"X-OpenWebUI-User-Role": user.role,
}
if ENABLE_FORWARD_USER_INFO_HEADERS
else {}
),
},
json={"input": texts, "model": model},
)
@@ -472,22 +504,29 @@ def generate_ollama_batch_embeddings(
def generate_embeddings(engine: str, model: str, text: Union[str, list[str]], **kwargs):
url = kwargs.get("url", "")
key = kwargs.get("key", "")
user = kwargs.get("user")
if engine == "ollama":
if isinstance(text, list):
embeddings = generate_ollama_batch_embeddings(
**{"model": model, "texts": text, "url": url, "key": key}
**{"model": model, "texts": text, "url": url, "key": key, "user": user}
)
else:
embeddings = generate_ollama_batch_embeddings(
**{"model": model, "texts": [text], "url": url, "key": key}
**{
"model": model,
"texts": [text],
"url": url,
"key": key,
"user": user,
}
)
return embeddings[0] if isinstance(text, str) else embeddings
elif engine == "openai":
if isinstance(text, list):
embeddings = generate_openai_batch_embeddings(model, text, url, key)
embeddings = generate_openai_batch_embeddings(model, text, url, key, user)
else:
embeddings = generate_openai_batch_embeddings(model, [text], url, key)
embeddings = generate_openai_batch_embeddings(model, [text], url, key, user)
return embeddings[0] if isinstance(text, str) else embeddings
@@ -8,13 +8,17 @@ from open_webui.retrieval.vector.main import VectorItem, SearchResult, GetResult
from open_webui.config import (
MILVUS_URI,
MILVUS_DB,
MILVUS_TOKEN,
)
class MilvusClient:
def __init__(self):
self.collection_prefix = "open_webui"
self.client = Client(uri=MILVUS_URI, database=MILVUS_DB)
if MILVUS_TOKEN is None:
self.client = Client(uri=MILVUS_URI, database=MILVUS_DB)
else:
self.client = Client(uri=MILVUS_URI, database=MILVUS_DB, token=MILVUS_TOKEN)
def _result_to_get_result(self, result) -> GetResult:
ids = []
+76
View File
@@ -0,0 +1,76 @@
import logging
from dataclasses import dataclass
from typing import Optional
import requests
from open_webui.env import SRC_LOG_LEVELS
from open_webui.retrieval.web.main import SearchResult
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
EXA_API_BASE = "https://api.exa.ai"
@dataclass
class ExaResult:
url: str
title: str
text: str
def search_exa(
api_key: str,
query: str,
count: int,
filter_list: Optional[list[str]] = None,
) -> list[SearchResult]:
"""Search using Exa Search API and return the results as a list of SearchResult objects.
Args:
api_key (str): A Exa Search API key
query (str): The query to search for
count (int): Number of results to return
filter_list (Optional[list[str]]): List of domains to filter results by
"""
log.info(f"Searching with Exa for query: {query}")
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
payload = {
"query": query,
"numResults": count or 5,
"includeDomains": filter_list,
"contents": {"text": True, "highlights": True},
"type": "auto", # Use the auto search type (keyword or neural)
}
try:
response = requests.post(
f"{EXA_API_BASE}/search", headers=headers, json=payload
)
response.raise_for_status()
data = response.json()
results = []
for result in data["results"]:
results.append(
ExaResult(
url=result["url"],
title=result["title"],
text=result["text"],
)
)
log.info(f"Found {len(results)} results")
return [
SearchResult(
link=result.url,
title=result.title,
snippet=result.text,
)
for result in results
]
except Exception as e:
log.error(f"Error searching Exa: {e}")
return []
+4
View File
@@ -1,3 +1,5 @@
import validators
from typing import Optional
from urllib.parse import urlparse
@@ -10,6 +12,8 @@ def get_filtered_results(results, filter_list):
filtered_results = []
for result in results:
url = result.get("url") or result.get("link", "")
if not validators.url(url):
continue
domain = urlparse(url).netloc
if any(domain.endswith(filtered_domain) for filtered_domain in filter_list):
filtered_results.append(result)
+15 -4
View File
@@ -43,6 +43,17 @@ def validate_url(url: Union[str, Sequence[str]]):
return False
def safe_validate_urls(url: Sequence[str]) -> Sequence[str]:
valid_urls = []
for u in url:
try:
if validate_url(u):
valid_urls.append(u)
except ValueError:
continue
return valid_urls
def resolve_hostname(hostname):
# Get address information
addr_info = socket.getaddrinfo(hostname, None)
@@ -86,11 +97,11 @@ def get_web_loader(
verify_ssl: bool = True,
requests_per_second: int = 2,
):
# Check if the URL is valid
if not validate_url(urls):
raise ValueError(ERROR_MESSAGES.INVALID_URL)
# Check if the URLs are valid
safe_urls = safe_validate_urls([urls] if isinstance(urls, str) else urls)
return SafeWebBaseLoader(
urls,
safe_urls,
verify_ssl=verify_ssl,
requests_per_second=requests_per_second,
continue_on_failure=True,
+14 -9
View File
@@ -25,8 +25,8 @@ from open_webui.env import (
WEBUI_AUTH,
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
WEBUI_AUTH_TRUSTED_NAME_HEADER,
WEBUI_SESSION_COOKIE_SAME_SITE,
WEBUI_SESSION_COOKIE_SECURE,
WEBUI_AUTH_COOKIE_SAME_SITE,
WEBUI_AUTH_COOKIE_SECURE,
SRC_LOG_LEVELS,
)
from fastapi import APIRouter, Depends, HTTPException, Request, status
@@ -95,8 +95,8 @@ async def get_session_user(
value=token,
expires=datetime_expires_at,
httponly=True, # Ensures the cookie is not accessible via JavaScript
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
secure=WEBUI_SESSION_COOKIE_SECURE,
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
secure=WEBUI_AUTH_COOKIE_SECURE,
)
user_permissions = get_permissions(
@@ -164,7 +164,7 @@ async def update_password(
############################
# LDAP Authentication
############################
@router.post("/ldap", response_model=SigninResponse)
@router.post("/ldap", response_model=SessionUserResponse)
async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
ENABLE_LDAP = request.app.state.config.ENABLE_LDAP
LDAP_SERVER_LABEL = request.app.state.config.LDAP_SERVER_LABEL
@@ -288,6 +288,10 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
httponly=True, # Ensures the cookie is not accessible via JavaScript
)
user_permissions = get_permissions(
user.id, request.app.state.config.USER_PERMISSIONS
)
return {
"token": token,
"token_type": "Bearer",
@@ -296,6 +300,7 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
"name": user.name,
"role": user.role,
"profile_image_url": user.profile_image_url,
"permissions": user_permissions,
}
else:
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
@@ -378,8 +383,8 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
value=token,
expires=datetime_expires_at,
httponly=True, # Ensures the cookie is not accessible via JavaScript
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
secure=WEBUI_SESSION_COOKIE_SECURE,
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
secure=WEBUI_AUTH_COOKIE_SECURE,
)
user_permissions = get_permissions(
@@ -473,8 +478,8 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
value=token,
expires=datetime_expires_at,
httponly=True, # Ensures the cookie is not accessible via JavaScript
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
secure=WEBUI_SESSION_COOKIE_SECURE,
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
secure=WEBUI_AUTH_COOKIE_SECURE,
)
if request.app.state.config.WEBHOOK_URL:
+8 -2
View File
@@ -444,15 +444,21 @@ async def pin_chat_by_id(id: str, user=Depends(get_verified_user)):
############################
class CloneForm(BaseModel):
title: Optional[str] = None
@router.post("/{id}/clone", response_model=Optional[ChatResponse])
async def clone_chat_by_id(id: str, user=Depends(get_verified_user)):
async def clone_chat_by_id(
form_data: CloneForm, id: str, user=Depends(get_verified_user)
):
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
updated_chat = {
**chat.chat,
"originalChatId": chat.id,
"branchPointMessageId": chat.chat["history"]["currentId"],
"title": f"Clone of {chat.title}",
"title": form_data.title if form_data.title else f"Clone of {chat.title}",
}
chat = Chats.insert_new_chat(user.id, ChatForm(**{"chat": updated_chat}))
+4 -2
View File
@@ -71,7 +71,7 @@ def upload_file(
)
try:
process_file(request, ProcessFileForm(file_id=id))
process_file(request, ProcessFileForm(file_id=id), user=user)
file_item = Files.get_file_by_id(id=id)
except Exception as e:
log.exception(e)
@@ -193,7 +193,9 @@ async def update_file_data_content_by_id(
if file and (file.user_id == user.id or user.role == "admin"):
try:
process_file(
request, ProcessFileForm(file_id=id, content=form_data.content)
request,
ProcessFileForm(file_id=id, content=form_data.content),
user=user,
)
file = Files.get_file_by_id(id=id)
except Exception as e:
+37 -12
View File
@@ -264,7 +264,11 @@ def add_file_to_knowledge_by_id(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if knowledge.user_id != user.id and user.role != "admin":
if (
knowledge.user_id != user.id
and not has_access(user.id, "write", knowledge.access_control)
and user.role != "admin"
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -285,7 +289,9 @@ def add_file_to_knowledge_by_id(
# Add content to the vector database
try:
process_file(
request, ProcessFileForm(file_id=form_data.file_id, collection_name=id)
request,
ProcessFileForm(file_id=form_data.file_id, collection_name=id),
user=user,
)
except Exception as e:
log.debug(e)
@@ -342,7 +348,12 @@ def update_file_from_knowledge_by_id(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if knowledge.user_id != user.id and user.role != "admin":
if (
knowledge.user_id != user.id
and not has_access(user.id, "write", knowledge.access_control)
and user.role != "admin"
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -363,7 +374,9 @@ def update_file_from_knowledge_by_id(
# Add content to the vector database
try:
process_file(
request, ProcessFileForm(file_id=form_data.file_id, collection_name=id)
request,
ProcessFileForm(file_id=form_data.file_id, collection_name=id),
user=user,
)
except Exception as e:
raise HTTPException(
@@ -406,7 +419,11 @@ def remove_file_from_knowledge_by_id(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if knowledge.user_id != user.id and user.role != "admin":
if (
knowledge.user_id != user.id
and not has_access(user.id, "write", knowledge.access_control)
and user.role != "admin"
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -429,10 +446,6 @@ def remove_file_from_knowledge_by_id(
if VECTOR_DB_CLIENT.has_collection(collection_name=file_collection):
VECTOR_DB_CLIENT.delete_collection(collection_name=file_collection)
# Delete physical file
if file.path:
Storage.delete_file(file.path)
# Delete file from database
Files.delete_file_by_id(form_data.file_id)
@@ -484,7 +497,11 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user)):
detail=ERROR_MESSAGES.NOT_FOUND,
)
if knowledge.user_id != user.id and user.role != "admin":
if (
knowledge.user_id != user.id
and not has_access(user.id, "write", knowledge.access_control)
and user.role != "admin"
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -543,7 +560,11 @@ async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user)):
detail=ERROR_MESSAGES.NOT_FOUND,
)
if knowledge.user_id != user.id and user.role != "admin":
if (
knowledge.user_id != user.id
and not has_access(user.id, "write", knowledge.access_control)
and user.role != "admin"
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -582,7 +603,11 @@ def add_files_to_knowledge_batch(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if knowledge.user_id != user.id and user.role != "admin":
if (
knowledge.user_id != user.id
and not has_access(user.id, "write", knowledge.access_control)
and user.role != "admin"
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+6 -4
View File
@@ -57,7 +57,7 @@ async def add_memory(
{
"id": memory.id,
"text": memory.content,
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content, user),
"metadata": {"created_at": memory.created_at},
}
],
@@ -82,7 +82,7 @@ async def query_memory(
):
results = VECTOR_DB_CLIENT.search(
collection_name=f"user-memory-{user.id}",
vectors=[request.app.state.EMBEDDING_FUNCTION(form_data.content)],
vectors=[request.app.state.EMBEDDING_FUNCTION(form_data.content, user)],
limit=form_data.k,
)
@@ -105,7 +105,7 @@ async def reset_memory_from_vector_db(
{
"id": memory.id,
"text": memory.content,
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content, user),
"metadata": {
"created_at": memory.created_at,
"updated_at": memory.updated_at,
@@ -160,7 +160,9 @@ async def update_memory_by_id(
{
"id": memory.id,
"text": memory.content,
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
"vector": request.app.state.EMBEDDING_FUNCTION(
memory.content, user
),
"metadata": {
"created_at": memory.created_at,
"updated_at": memory.updated_at,
+15 -1
View File
@@ -155,6 +155,16 @@ async def update_model_by_id(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if (
model.user_id != user.id
and not has_access(user.id, "write", model.access_control)
and user.role != "admin"
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
model = Models.update_model_by_id(id, form_data)
return model
@@ -173,7 +183,11 @@ async def delete_model_by_id(id: str, user=Depends(get_verified_user)):
detail=ERROR_MESSAGES.NOT_FOUND,
)
if model.user_id != user.id and user.role != "admin":
if (
user.role != "admin"
and model.user_id != user.id
and not has_access(user.id, "write", model.access_control)
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.UNAUTHORIZED,
+13 -11
View File
@@ -261,7 +261,7 @@ async def get_all_models(request: Request):
if request.app.state.config.ENABLE_OLLAMA_API:
request_tasks = []
for idx, url in enumerate(request.app.state.config.OLLAMA_BASE_URLS):
if (str(idx) not in request.app.state.config.OLLAMA_API_CONFIGS) or (
if (str(idx) not in request.app.state.config.OLLAMA_API_CONFIGS) and (
url not in request.app.state.config.OLLAMA_API_CONFIGS # Legacy support
):
request_tasks.append(send_get_request(f"{url}/api/tags"))
@@ -395,7 +395,7 @@ async def get_ollama_tags(
)
if user.role == "user" and not BYPASS_MODEL_ACCESS_CONTROL:
models["models"] = get_filtered_models(models, user)
models["models"] = await get_filtered_models(models, user)
return models
@@ -551,11 +551,12 @@ async def push_model(
class CreateModelForm(BaseModel):
name: str
modelfile: Optional[str] = None
model: Optional[str] = None
stream: Optional[bool] = None
path: Optional[str] = None
model_config = ConfigDict(extra="allow")
@router.post("/api/create")
@router.post("/api/create/{url_idx}")
@@ -938,6 +939,7 @@ async def generate_completion(
class ChatMessage(BaseModel):
role: str
content: str
tool_calls: Optional[list[dict]] = None
images: Optional[list[str]] = None
@@ -949,6 +951,7 @@ class GenerateChatCompletionForm(BaseModel):
template: Optional[str] = None
stream: Optional[bool] = True
keep_alive: Optional[Union[int, str]] = None
tools: Optional[list[dict]] = None
async def get_ollama_url(request: Request, model: str, url_idx: Optional[int] = None):
@@ -961,7 +964,7 @@ async def get_ollama_url(request: Request, model: str, url_idx: Optional[int] =
)
url_idx = random.choice(models[model].get("urls", []))
url = request.app.state.config.OLLAMA_BASE_URLS[url_idx]
return url
return url, url_idx
@router.post("/api/chat")
@@ -976,6 +979,7 @@ async def generate_chat_completion(
if BYPASS_MODEL_ACCESS_CONTROL:
bypass_filter = True
metadata = form_data.pop("metadata", None)
try:
form_data = GenerateChatCompletionForm(**form_data)
except Exception as e:
@@ -986,8 +990,6 @@ async def generate_chat_completion(
)
payload = {**form_data.model_dump(exclude_none=True)}
if "metadata" in payload:
del payload["metadata"]
model_id = payload["model"]
model_info = Models.get_model_by_id(model_id)
@@ -1005,7 +1007,7 @@ async def generate_chat_completion(
payload["options"] = apply_model_params_to_body_ollama(
params, payload["options"]
)
payload = apply_model_system_prompt_to_body(params, payload, user)
payload = apply_model_system_prompt_to_body(params, payload, metadata)
# Check if user has access to the model
if not bypass_filter and user.role == "user":
@@ -1029,7 +1031,7 @@ async def generate_chat_completion(
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
url = await get_ollama_url(request, payload["model"], url_idx)
url, url_idx = await get_ollama_url(request, payload["model"], url_idx)
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
str(url_idx),
request.app.state.config.OLLAMA_API_CONFIGS.get(url, {}), # Legacy support
@@ -1131,7 +1133,7 @@ async def generate_openai_completion(
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
url = await get_ollama_url(request, payload["model"], url_idx)
url, url_idx = await get_ollama_url(request, payload["model"], url_idx)
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
str(url_idx),
request.app.state.config.OLLAMA_API_CONFIGS.get(url, {}), # Legacy support
@@ -1208,7 +1210,7 @@ async def generate_openai_chat_completion(
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
url = await get_ollama_url(request, payload["model"], url_idx)
url, url_idx = await get_ollama_url(request, payload["model"], url_idx)
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
str(url_idx),
request.app.state.config.OLLAMA_API_CONFIGS.get(url, {}), # Legacy support
+5 -5
View File
@@ -266,7 +266,7 @@ async def get_all_models_responses(request: Request) -> list:
request_tasks = []
for idx, url in enumerate(request.app.state.config.OPENAI_API_BASE_URLS):
if (str(idx) not in request.app.state.config.OPENAI_API_CONFIGS) or (
if (str(idx) not in request.app.state.config.OPENAI_API_CONFIGS) and (
url not in request.app.state.config.OPENAI_API_CONFIGS # Legacy support
):
request_tasks.append(
@@ -489,7 +489,7 @@ async def get_models(
raise HTTPException(status_code=500, detail=error_detail)
if user.role == "user" and not BYPASS_MODEL_ACCESS_CONTROL:
models["data"] = get_filtered_models(models, user)
models["data"] = await get_filtered_models(models, user)
return models
@@ -551,9 +551,9 @@ async def generate_chat_completion(
bypass_filter = True
idx = 0
payload = {**form_data}
if "metadata" in payload:
del payload["metadata"]
metadata = payload.pop("metadata", None)
model_id = form_data.get("model")
model_info = Models.get_model_by_id(model_id)
@@ -566,7 +566,7 @@ async def generate_chat_completion(
params = model_info.params.model_dump()
payload = apply_model_params_to_body_openai(params, payload)
payload = apply_model_system_prompt_to_body(params, payload, user)
payload = apply_model_system_prompt_to_body(params, payload, metadata)
# Check if user has access to the model
if not bypass_filter and user.role == "user":
+5 -1
View File
@@ -147,7 +147,11 @@ async def delete_prompt_by_command(command: str, user=Depends(get_verified_user)
detail=ERROR_MESSAGES.NOT_FOUND,
)
if prompt.user_id != user.id and user.role != "admin":
if (
prompt.user_id != user.id
and not has_access(user.id, "write", prompt.access_control)
and user.role != "admin"
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+42 -9
View File
@@ -55,6 +55,7 @@ from open_webui.retrieval.web.serply import search_serply
from open_webui.retrieval.web.serpstack import search_serpstack
from open_webui.retrieval.web.tavily import search_tavily
from open_webui.retrieval.web.bing import search_bing
from open_webui.retrieval.web.exa import search_exa
from open_webui.retrieval.utils import (
@@ -388,6 +389,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
"jina_api_key": request.app.state.config.JINA_API_KEY,
"bing_search_v7_endpoint": request.app.state.config.BING_SEARCH_V7_ENDPOINT,
"bing_search_v7_subscription_key": request.app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY,
"exa_api_key": request.app.state.config.EXA_API_KEY,
"result_count": request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
"concurrent_requests": request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
},
@@ -436,6 +438,7 @@ class WebSearchConfig(BaseModel):
jina_api_key: Optional[str] = None
bing_search_v7_endpoint: Optional[str] = None
bing_search_v7_subscription_key: Optional[str] = None
exa_api_key: Optional[str] = None
result_count: Optional[int] = None
concurrent_requests: Optional[int] = None
@@ -542,6 +545,8 @@ async def update_rag_config(
form_data.web.search.bing_search_v7_subscription_key
)
request.app.state.config.EXA_API_KEY = form_data.web.search.exa_api_key
request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = (
form_data.web.search.result_count
)
@@ -591,6 +596,7 @@ async def update_rag_config(
"jina_api_key": request.app.state.config.JINA_API_KEY,
"bing_search_v7_endpoint": request.app.state.config.BING_SEARCH_V7_ENDPOINT,
"bing_search_v7_subscription_key": request.app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY,
"exa_api_key": request.app.state.config.EXA_API_KEY,
"result_count": request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
"concurrent_requests": request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
},
@@ -660,6 +666,7 @@ def save_docs_to_vector_db(
overwrite: bool = False,
split: bool = True,
add: bool = False,
user=None,
) -> bool:
def _get_docs_info(docs: list[Document]) -> str:
docs_info = set()
@@ -775,7 +782,7 @@ def save_docs_to_vector_db(
)
embeddings = embedding_function(
list(map(lambda x: x.replace("\n", " "), texts))
list(map(lambda x: x.replace("\n", " "), texts)), user=user
)
items = [
@@ -933,6 +940,7 @@ def process_file(
"hash": hash,
},
add=(True if form_data.collection_name else False),
user=user,
)
if result:
@@ -990,7 +998,7 @@ def process_text(
text_content = form_data.content
log.debug(f"text_content: {text_content}")
result = save_docs_to_vector_db(request, docs, collection_name)
result = save_docs_to_vector_db(request, docs, collection_name, user=user)
if result:
return {
"status": True,
@@ -1023,7 +1031,9 @@ def process_youtube_video(
content = " ".join([doc.page_content for doc in docs])
log.debug(f"text_content: {content}")
save_docs_to_vector_db(request, docs, collection_name, overwrite=True)
save_docs_to_vector_db(
request, docs, collection_name, overwrite=True, user=user
)
return {
"status": True,
@@ -1064,7 +1074,9 @@ def process_web(
content = " ".join([doc.page_content for doc in docs])
log.debug(f"text_content: {content}")
save_docs_to_vector_db(request, docs, collection_name, overwrite=True)
save_docs_to_vector_db(
request, docs, collection_name, overwrite=True, user=user
)
return {
"status": True,
@@ -1099,6 +1111,7 @@ def search_web(request: Request, engine: str, query: str) -> list[SearchResult]:
- SERPER_API_KEY
- SERPLY_API_KEY
- TAVILY_API_KEY
- EXA_API_KEY
- SEARCHAPI_API_KEY + SEARCHAPI_ENGINE (by default `google`)
Args:
query (str): The query to search for
@@ -1233,6 +1246,13 @@ def search_web(request: Request, engine: str, query: str) -> list[SearchResult]:
request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
)
elif engine == "exa":
return search_exa(
request.app.state.config.EXA_API_KEY,
query,
request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
)
else:
raise Exception("No search engine API key found in environment variables")
@@ -1272,7 +1292,9 @@ def process_web_search(
requests_per_second=request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
)
docs = loader.load()
save_docs_to_vector_db(request, docs, collection_name, overwrite=True)
save_docs_to_vector_db(
request, docs, collection_name, overwrite=True, user=user
)
return {
"status": True,
@@ -1306,7 +1328,9 @@ def query_doc_handler(
return query_doc_with_hybrid_search(
collection_name=form_data.collection_name,
query=form_data.query,
embedding_function=request.app.state.EMBEDDING_FUNCTION,
embedding_function=lambda query: request.app.state.EMBEDDING_FUNCTION(
query, user=user
),
k=form_data.k if form_data.k else request.app.state.config.TOP_K,
reranking_function=request.app.state.rf,
r=(
@@ -1314,12 +1338,16 @@ def query_doc_handler(
if form_data.r
else request.app.state.config.RELEVANCE_THRESHOLD
),
user=user,
)
else:
return query_doc(
collection_name=form_data.collection_name,
query_embedding=request.app.state.EMBEDDING_FUNCTION(form_data.query),
query_embedding=request.app.state.EMBEDDING_FUNCTION(
form_data.query, user=user
),
k=form_data.k if form_data.k else request.app.state.config.TOP_K,
user=user,
)
except Exception as e:
log.exception(e)
@@ -1348,7 +1376,9 @@ def query_collection_handler(
return query_collection_with_hybrid_search(
collection_names=form_data.collection_names,
queries=[form_data.query],
embedding_function=request.app.state.EMBEDDING_FUNCTION,
embedding_function=lambda query: request.app.state.EMBEDDING_FUNCTION(
query, user=user
),
k=form_data.k if form_data.k else request.app.state.config.TOP_K,
reranking_function=request.app.state.rf,
r=(
@@ -1361,7 +1391,9 @@ def query_collection_handler(
return query_collection(
collection_names=form_data.collection_names,
queries=[form_data.query],
embedding_function=request.app.state.EMBEDDING_FUNCTION,
embedding_function=lambda query: request.app.state.EMBEDDING_FUNCTION(
query, user=user
),
k=form_data.k if form_data.k else request.app.state.config.TOP_K,
)
@@ -1509,6 +1541,7 @@ def process_files_batch(
docs=all_docs,
collection_name=collection_name,
add=True,
user=user,
)
# Update all files with collection name
+19 -3
View File
@@ -4,6 +4,7 @@ from fastapi.responses import JSONResponse, RedirectResponse
from pydantic import BaseModel
from typing import Optional
import logging
import re
from open_webui.utils.chat import generate_chat_completion
from open_webui.utils.task import (
@@ -89,6 +90,10 @@ async def update_task_config(
form_data.TITLE_GENERATION_PROMPT_TEMPLATE
)
request.app.state.config.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = (
form_data.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE
)
request.app.state.config.ENABLE_AUTOCOMPLETE_GENERATION = (
form_data.ENABLE_AUTOCOMPLETE_GENERATION
)
@@ -161,9 +166,20 @@ async def generate_title(
else:
template = DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE
messages = form_data["messages"]
# Remove reasoning details from the messages
for message in messages:
message["content"] = re.sub(
r"<details\s+type=\"reasoning\"[^>]*>.*?<\/details>",
"",
message["content"],
flags=re.S,
).strip()
content = title_generation_template(
template,
form_data["messages"],
messages,
{
"name": user.name,
"location": user.info.get("location") if user.info else None,
@@ -175,10 +191,10 @@ async def generate_title(
"messages": [{"role": "user", "content": content}],
"stream": False,
**(
{"max_tokens": 50}
{"max_tokens": 1000}
if models[task_model_id]["owned_by"] == "ollama"
else {
"max_completion_tokens": 50,
"max_completion_tokens": 1000,
}
),
"metadata": {
+16 -1
View File
@@ -227,7 +227,11 @@ async def delete_tools_by_id(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if tools.user_id != user.id and user.role != "admin":
if (
tools.user_id != user.id
and not has_access(user.id, "write", tools.access_control)
and user.role != "admin"
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.UNAUTHORIZED,
@@ -309,6 +313,17 @@ async def update_tools_valves_by_id(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.NOT_FOUND,
)
if (
tools.user_id != user.id
and not has_access(user.id, "write", tools.access_control)
and user.role != "admin"
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if id in request.app.state.TOOLS:
tools_module = request.app.state.TOOLS[id]
else:
+1
View File
@@ -79,6 +79,7 @@ class ChatPermissions(BaseModel):
class FeaturesPermissions(BaseModel):
web_search: bool = True
image_generation: bool = True
code_interpreter: bool = True
class UserPermissions(BaseModel):
+5 -2
View File
@@ -325,7 +325,7 @@ def get_event_emitter(request_info):
def get_event_call(request_info):
async def __event_call__(event_data):
async def __event_caller__(event_data):
response = await sio.call(
"chat-events",
{
@@ -337,7 +337,10 @@ def get_event_call(request_info):
)
return response
return __event_call__
return __event_caller__
get_event_caller = get_event_call
def get_user_id_from_session_pool(sid):
File diff suppressed because it is too large Load Diff
+66 -5
View File
@@ -7,6 +7,18 @@ from pathlib import Path
from typing import Callable, Optional
import collections.abc
def deep_update(d, u):
for k, v in u.items():
if isinstance(v, collections.abc.Mapping):
d[k] = deep_update(d.get(k, {}), v)
else:
d[k] = v
return d
def get_message_list(messages, message_id):
"""
Reconstructs a list of messages in order up to the specified message_id.
@@ -20,7 +32,7 @@ def get_message_list(messages, message_id):
current_message = messages.get(message_id)
if not current_message:
return f"Message ID {message_id} not found in the history."
return None
# Reconstruct the chain by following the parentId links
message_list = []
@@ -131,6 +143,44 @@ def add_or_update_system_message(content: str, messages: list[dict]):
return messages
def add_or_update_user_message(content: str, messages: list[dict]):
"""
Adds a new user message at the end of the messages list
or updates the existing user message at the end.
:param msg: The message to be added or appended.
:param messages: The list of message dictionaries.
:return: The updated list of message dictionaries.
"""
if messages and messages[-1].get("role") == "user":
messages[-1]["content"] = f"{messages[-1]['content']}\n{content}"
else:
# Insert at the end
messages.append({"role": "user", "content": content})
return messages
def append_or_update_assistant_message(content: str, messages: list[dict]):
"""
Adds a new assistant message at the end of the messages list
or updates the existing assistant message at the end.
:param msg: The message to be added or appended.
:param messages: The list of message dictionaries.
:return: The updated list of message dictionaries.
"""
if messages and messages[-1].get("role") == "assistant":
messages[-1]["content"] = f"{messages[-1]['content']}\n{content}"
else:
# Insert at the end
messages.append({"role": "assistant", "content": content})
return messages
def openai_chat_message_template(model: str):
return {
"id": f"{model}-{str(uuid.uuid4())}",
@@ -141,13 +191,24 @@ def openai_chat_message_template(model: str):
def openai_chat_chunk_message_template(
model: str, message: Optional[str] = None, usage: Optional[dict] = None
model: str,
content: Optional[str] = None,
tool_calls: Optional[list[dict]] = None,
usage: Optional[dict] = None,
) -> dict:
template = openai_chat_message_template(model)
template["object"] = "chat.completion.chunk"
if message:
template["choices"][0]["delta"] = {"content": message}
else:
template["choices"][0]["index"] = 0
template["choices"][0]["delta"] = {}
if content:
template["choices"][0]["delta"]["content"] = content
if tool_calls:
template["choices"][0]["delta"]["tool_calls"] = tool_calls
if not content and not tool_calls:
template["choices"][0]["finish_reason"] = "stop"
if usage:
+14 -8
View File
@@ -35,7 +35,7 @@ from open_webui.config import (
AppConfig,
)
from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
from open_webui.env import WEBUI_SESSION_COOKIE_SAME_SITE, WEBUI_SESSION_COOKIE_SECURE
from open_webui.env import WEBUI_AUTH_COOKIE_SAME_SITE, WEBUI_AUTH_COOKIE_SECURE
from open_webui.utils.misc import parse_duration
from open_webui.utils.auth import get_password_hash, create_token
from open_webui.utils.webhook import post_webhook
@@ -82,7 +82,8 @@ class OAuthManager:
oauth_allowed_roles = auth_manager_config.OAUTH_ALLOWED_ROLES
oauth_admin_roles = auth_manager_config.OAUTH_ADMIN_ROLES
oauth_roles = None
role = "pending" # Default/fallback role if no matching roles are found
# Default/fallback role if no matching roles are found
role = auth_manager_config.DEFAULT_USER_ROLE
# Next block extracts the roles from the user data, accepting nested claims of any depth
if oauth_claim and oauth_allowed_roles and oauth_admin_roles:
@@ -273,11 +274,16 @@ class OAuthManager:
log.error(
f"Error downloading profile image '{picture_url}': {e}"
)
picture_url = ""
picture_url = "/user.png"
if not picture_url:
picture_url = "/user.png"
username_claim = auth_manager_config.OAUTH_USERNAME_CLAIM
name = user_data.get(username_claim)
if not isinstance(user, str):
name = email
role = self.get_user_role(None, user_data)
user = Auths.insert_new_auth(
@@ -285,7 +291,7 @@ class OAuthManager:
password=get_password_hash(
str(uuid.uuid4())
), # Random password, not used
name=user_data.get(username_claim, "User"),
name=name,
profile_image_url=picture_url,
role=role,
oauth_sub=provider_sub,
@@ -323,8 +329,8 @@ class OAuthManager:
key="token",
value=jwt_token,
httponly=True, # Ensures the cookie is not accessible via JavaScript
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
secure=WEBUI_SESSION_COOKIE_SECURE,
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
secure=WEBUI_AUTH_COOKIE_SECURE,
)
if ENABLE_OAUTH_SIGNUP.value:
@@ -333,8 +339,8 @@ class OAuthManager:
key="oauth_id_token",
value=oauth_id_token,
httponly=True,
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
secure=WEBUI_SESSION_COOKIE_SECURE,
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
secure=WEBUI_AUTH_COOKIE_SECURE,
)
# Redirect back to the frontend with the JWT token
redirect_url = f"{request.base_url}auth#token={jwt_token}"
+16 -10
View File
@@ -1,4 +1,4 @@
from open_webui.utils.task import prompt_template
from open_webui.utils.task import prompt_variables_template
from open_webui.utils.misc import (
add_or_update_system_message,
)
@@ -7,19 +7,18 @@ from typing import Callable, Optional
# inplace function: form_data is modified
def apply_model_system_prompt_to_body(params: dict, form_data: dict, user) -> dict:
def apply_model_system_prompt_to_body(
params: dict, form_data: dict, metadata: Optional[dict] = None
) -> dict:
system = params.get("system", None)
if not system:
return form_data
if user:
template_params = {
"user_name": user.name,
"user_location": user.info.get("location") if user.info else None,
}
else:
template_params = {}
system = prompt_template(system, **template_params)
if metadata:
print("apply_model_system_prompt_to_body: metadata", metadata)
variables = metadata.get("variables", {})
system = prompt_variables_template(system, variables)
form_data["messages"] = add_or_update_system_message(
system, form_data.get("messages", [])
)
@@ -47,6 +46,7 @@ def apply_model_params_to_body_openai(params: dict, form_data: dict) -> dict:
"top_p": float,
"max_tokens": int,
"frequency_penalty": float,
"reasoning_effort": str,
"seed": lambda x: x,
"stop": lambda x: [bytes(s, "utf-8").decode("unicode_escape") for s in x],
}
@@ -154,6 +154,9 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
)
ollama_payload["stream"] = openai_payload.get("stream", False)
if "tools" in openai_payload:
ollama_payload["tools"] = openai_payload["tools"]
if "format" in openai_payload:
ollama_payload["format"] = openai_payload["format"]
@@ -187,4 +190,7 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
if ollama_options:
ollama_payload["options"] = ollama_options
if "metadata" in openai_payload:
ollama_payload["metadata"] = openai_payload["metadata"]
return ollama_payload
+9 -4
View File
@@ -167,9 +167,14 @@ def load_function_module_by_id(function_id, content=None):
def install_frontmatter_requirements(requirements):
if requirements:
req_list = [req.strip() for req in requirements.split(",")]
for req in req_list:
log.info(f"Installing requirement: {req}")
subprocess.check_call([sys.executable, "-m", "pip", "install", req])
try:
req_list = [req.strip() for req in requirements.split(",")]
for req in req_list:
log.info(f"Installing requirement: {req}")
subprocess.check_call([sys.executable, "-m", "pip", "install", req])
except Exception as e:
log.error(f"Error installing package: {req}")
raise e
else:
log.info("No requirements found in frontmatter.")
+61 -2
View File
@@ -1,4 +1,5 @@
import json
from uuid import uuid4
from open_webui.utils.misc import (
openai_chat_chunk_message_template,
openai_chat_completion_message_template,
@@ -9,7 +10,48 @@ def convert_response_ollama_to_openai(ollama_response: dict) -> dict:
model = ollama_response.get("model", "ollama")
message_content = ollama_response.get("message", {}).get("content", "")
response = openai_chat_completion_message_template(model, message_content)
data = ollama_response
usage = {
"response_token/s": (
round(
(
(
data.get("eval_count", 0)
/ ((data.get("eval_duration", 0) / 10_000_000))
)
* 100
),
2,
)
if data.get("eval_duration", 0) > 0
else "N/A"
),
"prompt_token/s": (
round(
(
(
data.get("prompt_eval_count", 0)
/ ((data.get("prompt_eval_duration", 0) / 10_000_000))
)
* 100
),
2,
)
if data.get("prompt_eval_duration", 0) > 0
else "N/A"
),
"total_duration": data.get("total_duration", 0),
"load_duration": data.get("load_duration", 0),
"prompt_eval_count": data.get("prompt_eval_count", 0),
"prompt_eval_duration": data.get("prompt_eval_duration", 0),
"eval_count": data.get("eval_count", 0),
"eval_duration": data.get("eval_duration", 0),
"approximate_total": (lambda s: f"{s // 3600}h{(s % 3600) // 60}m{s % 60}s")(
(data.get("total_duration", 0) or 0) // 1_000_000_000
),
}
response = openai_chat_completion_message_template(model, message_content, usage)
return response
@@ -19,6 +61,23 @@ async def convert_streaming_response_ollama_to_openai(ollama_streaming_response)
model = data.get("model", "ollama")
message_content = data.get("message", {}).get("content", "")
tool_calls = data.get("message", {}).get("tool_calls", None)
openai_tool_calls = None
if tool_calls:
openai_tool_calls = []
for tool_call in tool_calls:
openai_tool_call = {
"index": tool_call.get("index", 0),
"id": tool_call.get("id", f"call_{str(uuid4())}"),
"type": "function",
"function": {
"name": tool_call.get("function", {}).get("name", ""),
"arguments": f"{tool_call.get('function', {}).get('arguments', {})}",
},
}
openai_tool_calls.append(openai_tool_call)
done = data.get("done", False)
usage = None
@@ -64,7 +123,7 @@ async def convert_streaming_response_ollama_to_openai(ollama_streaming_response)
}
data = openai_chat_chunk_message_template(
model, message_content if not done else None, usage
model, message_content if not done else None, openai_tool_calls, usage
)
line = f"data: {json.dumps(data)}\n\n"
+6
View File
@@ -32,6 +32,12 @@ def get_task_model_id(
return task_model_id
def prompt_variables_template(template: str, variables: dict[str, str]) -> str:
for variable, value in variables.items():
template = template.replace(variable, value)
return template
def prompt_template(
template: str, user_name: Optional[str] = None, user_location: Optional[str] = None
) -> str:
+13
View File
@@ -61,6 +61,12 @@ def get_tools(
)
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"
# Remove internal parameters
spec["parameters"]["properties"] = {
key: val
@@ -73,6 +79,13 @@ def get_tools(
# convert to function that takes only model params and inserts custom params
original_func = getattr(module, function_name)
callable = apply_extra_params_to_tool_function(original_func, extra_params)
if callable.__doc__ and callable.__doc__.strip() != "":
s = re.split(":(param|return)", callable.__doc__, 1)
spec["description"] = s[0]
else:
spec["description"] = function_name
# TODO: This needs to be a pydantic model
tool_dict = {
"toolkit_id": tool_id,
+8 -8
View File
@@ -1,4 +1,4 @@
fastapi==0.111.0
fastapi==0.115.7
uvicorn[standard]==0.30.6
pydantic==2.9.2
python-multipart==0.0.18
@@ -11,7 +11,7 @@ python-jose==3.3.0
passlib[bcrypt]==1.7.4
requests==2.32.3
aiohttp==3.11.8
aiohttp==3.11.11
async-timeout
aiocache
aiofiles
@@ -57,10 +57,10 @@ einops==0.8.0
ftfy==6.2.3
pypdf==4.3.1
fpdf2==2.8.2
pymdown-extensions==10.11.2
pymdown-extensions==10.14.2
docx2txt==0.8
python-pptx==1.0.0
unstructured==0.15.9
unstructured==0.16.11
nltk==3.9.1
Markdown==3.7
pypandoc==1.13
@@ -71,16 +71,16 @@ xlrd==2.0.1
validators==0.34.0
psutil
sentencepiece
soundfile==0.12.1
soundfile==0.13.1
opencv-python-headless==4.10.0.84
opencv-python-headless==4.11.0.86
rapidocr-onnxruntime==1.3.24
rank-bm25==0.2.2
faster-whisper==1.0.3
PyJWT[crypto]==2.10.1
authlib==1.3.2
authlib==1.4.1
black==24.8.0
langfuse==2.44.0
@@ -89,7 +89,7 @@ pytube==15.0.0
extract_msg
pydub
duckduckgo-search~=7.2.1
duckduckgo-search~=7.3.0
## Google Drive
google-api-python-client
+18 -6
View File
@@ -1,12 +1,12 @@
{
"name": "open-webui",
"version": "0.5.5",
"version": "0.5.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-webui",
"version": "0.5.5",
"version": "0.5.9",
"dependencies": {
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.6",
@@ -56,7 +56,7 @@
"prosemirror-schema-list": "^1.4.1",
"prosemirror-state": "^1.4.3",
"prosemirror-view": "^1.34.3",
"pyodide": "^0.26.1",
"pyodide": "^0.27.2",
"socket.io-client": "^4.2.0",
"sortablejs": "^1.15.2",
"svelte-sonner": "^0.3.19",
@@ -69,6 +69,7 @@
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.5.20",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.13",
"@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.17.0",
@@ -2343,6 +2344,16 @@
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/container-queries": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz",
"integrity": "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"tailwindcss": ">=3.2.0"
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz",
@@ -9366,9 +9377,10 @@
}
},
"node_modules/pyodide": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.26.1.tgz",
"integrity": "sha512-P+Gm88nwZqY7uBgjbQH8CqqU6Ei/rDn7pS1t02sNZsbyLJMyE2OVXjgNuqVT3KqYWnyGREUN0DbBUCJqk8R0ew==",
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.27.2.tgz",
"integrity": "sha512-sfA2kiUuQVRpWI4BYnU3sX5PaTTt/xrcVEmRzRcId8DzZXGGtPgCBC0gCqjUTUYSa8ofPaSjXmzESc86yvvCHg==",
"license": "Apache-2.0",
"dependencies": {
"ws": "^8.5.0"
},
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "open-webui",
"version": "0.5.5",
"version": "0.5.9",
"private": true,
"scripts": {
"dev": "npm run pyodide:fetch && vite dev --host",
@@ -25,6 +25,7 @@
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.5.20",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.13",
"@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.17.0",
@@ -98,7 +99,7 @@
"prosemirror-schema-list": "^1.4.1",
"prosemirror-state": "^1.4.3",
"prosemirror-view": "^1.34.3",
"pyodide": "^0.26.1",
"pyodide": "^0.27.2",
"socket.io-client": "^4.2.0",
"sortablejs": "^1.15.2",
"svelte-sonner": "^0.3.19",
+8 -8
View File
@@ -6,7 +6,7 @@ authors = [
]
license = { file = "LICENSE" }
dependencies = [
"fastapi==0.111.0",
"fastapi==0.115.7",
"uvicorn[standard]==0.30.6",
"pydantic==2.9.2",
"python-multipart==0.0.18",
@@ -19,7 +19,7 @@ dependencies = [
"passlib[bcrypt]==1.7.4",
"requests==2.32.3",
"aiohttp==3.11.8",
"aiohttp==3.11.11",
"async-timeout",
"aiocache",
"aiofiles",
@@ -62,10 +62,10 @@ dependencies = [
"ftfy==6.2.3",
"pypdf==4.3.1",
"fpdf2==2.8.2",
"pymdown-extensions==10.11.2",
"pymdown-extensions==10.14.2",
"docx2txt==0.8",
"python-pptx==1.0.0",
"unstructured==0.15.9",
"unstructured==0.16.11",
"nltk==3.9.1",
"Markdown==3.7",
"pypandoc==1.13",
@@ -76,16 +76,16 @@ dependencies = [
"validators==0.34.0",
"psutil",
"sentencepiece",
"soundfile==0.12.1",
"soundfile==0.13.1",
"opencv-python-headless==4.10.0.84",
"opencv-python-headless==4.11.0.86",
"rapidocr-onnxruntime==1.3.24",
"rank-bm25==0.2.2",
"faster-whisper==1.0.3",
"PyJWT[crypto]==2.10.1",
"authlib==1.3.2",
"authlib==1.4.1",
"black==24.8.0",
"langfuse==2.44.0",
@@ -94,7 +94,7 @@ dependencies = [
"extract_msg",
"pydub",
"duckduckgo-search~=7.2.1",
"duckduckgo-search~=7.3.0",
"google-api-python-client",
"google-auth-httplib2",
+4 -1
View File
@@ -9,7 +9,10 @@ const packages = [
'scikit-learn',
'scipy',
'regex',
'seaborn'
'sympy',
'tiktoken',
'seaborn',
'pytz'
];
import { loadPyodide } from 'pyodide';
+5 -2
View File
@@ -580,7 +580,7 @@ export const toggleChatPinnedStatusById = async (token: string, id: string) => {
return res;
};
export const cloneChatById = async (token: string, id: string) => {
export const cloneChatById = async (token: string, id: string, title?: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/clone`, {
@@ -589,7 +589,10 @@ export const cloneChatById = async (token: string, id: string) => {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
},
body: JSON.stringify({
...(title && { title: title })
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
+3 -2
View File
@@ -880,13 +880,14 @@ export const getChangelog = async () => {
return res;
};
export const getVersionUpdates = async () => {
export const getVersionUpdates = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_BASE_URL}/api/version/updates`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
+1 -1
View File
@@ -219,7 +219,7 @@ export const deleteModelById = async (token: string, id: string) => {
return json;
})
.catch((err) => {
error = err;
error = err.detail;
console.log(err);
return null;
+2 -10
View File
@@ -360,12 +360,7 @@ export const generateChatCompletion = async (token: string = '', body: object) =
return [res, controller];
};
export const createModel = async (
token: string,
tagName: string,
content: string,
urlIdx: string | null = null
) => {
export const createModel = async (token: string, payload: object, urlIdx: string | null = null) => {
let error = null;
const res = await fetch(
@@ -377,10 +372,7 @@ export const createModel = async (
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
name: tagName,
modelfile: content
})
body: JSON.stringify(payload)
}
).catch((err) => {
error = err;
+1 -1
View File
@@ -322,7 +322,7 @@ export const generateOpenAIChatCompletion = async (
return res.json();
})
.catch((err) => {
error = `${err?.detail ?? 'Network Problem'}`;
error = `${err?.detail ?? err}`;
return null;
});
@@ -51,7 +51,7 @@
models = [];
} else {
const res = await _getModels(localStorage.token).catch((e) => {
toast.error(e);
toast.error(`${e}`);
});
if (res) {
@@ -74,7 +74,7 @@
}, 100);
} else {
const res = await _getVoices(localStorage.token).catch((e) => {
toast.error(e);
toast.error(`${e}`);
});
if (res) {
@@ -31,7 +31,8 @@
ENABLE_TAGS_GENERATION: true,
ENABLE_SEARCH_QUERY_GENERATION: true,
ENABLE_RETRIEVAL_QUERY_GENERATION: true,
QUERY_GENERATION_PROMPT_TEMPLATE: ''
QUERY_GENERATION_PROMPT_TEMPLATE: '',
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: ''
};
let promptSuggestions = [];
@@ -251,6 +252,20 @@
</div>
</div>
<div class="mt-3">
<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Tools Function Calling Prompt')}</div>
<Tooltip
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
placement="top-start"
>
<Textarea
bind:value={taskConfig.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE}
placeholder={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
/>
</Tooltip>
</div>
<hr class=" border-gray-50 dark:border-gray-850 my-3" />
<div class=" space-y-3 {banners.length > 0 ? ' mb-3' : ''}">
@@ -44,8 +44,9 @@
let modelTag = '';
let createModelLoading = false;
let createModelTag = '';
let createModelContent = '';
let createModelName = '';
let createModelObject = '';
let createModelDigest = '';
let createModelPullProgress = null;
@@ -427,10 +428,23 @@
const createModelHandler = async () => {
createModelLoading = true;
let modelObject = {};
// parse createModelObject
try {
modelObject = JSON.parse(createModelObject);
} catch (error) {
toast.error(`${error}`);
createModelLoading = false;
return;
}
const res = await createModel(
localStorage.token,
createModelTag,
createModelContent,
{
model: createModelName,
...modelObject
},
urlIdx
).catch((error) => {
toast.error(`${error}`);
@@ -496,18 +510,22 @@
createModelLoading = false;
createModelTag = '';
createModelContent = '';
createModelName = '';
createModelObject = '';
createModelDigest = '';
createModelPullProgress = null;
};
const init = async () => {
loading = true;
ollamaModels = await getOllamaModels(localStorage.token, urlIdx);
ollamaModels = await getOllamaModels(localStorage.token, urlIdx).catch((error) => {
toast.error(`${error}`);
return null;
});
console.log(ollamaModels);
loading = false;
if (ollamaModels) {
loading = false;
}
};
$: if (urlIdx !== null) {
@@ -747,15 +765,15 @@
placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
modelTag: 'my-modelfile'
})}
bind:value={createModelTag}
bind:value={createModelName}
disabled={createModelLoading}
/>
<textarea
bind:value={createModelContent}
bind:value={createModelObject}
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none scrollbar-hidden"
rows="6"
placeholder={`TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`}
placeholder={`e.g. {"model": "my-modelfile", "from": "ollama:7b"})`}
disabled={createModelLoading}
/>
</div>
@@ -1013,6 +1031,12 @@
</div>
</div>
</div>
{:else if ollamaModels === null}
<div class="flex justify-center items-center w-full h-full text-xs py-3">
{$i18n.t('Failed to fetch models')}
</div>
{:else}
<Spinner />
<div class="flex justify-center items-center w-full h-full py-3">
<Spinner />
</div>
{/if}
@@ -25,7 +25,8 @@
'duckduckgo',
'tavily',
'jina',
'bing'
'bing',
'exa'
];
let youtubeLanguage = 'en';
@@ -261,6 +262,17 @@
bind:value={webConfig.search.jina_api_key}
/>
</div>
{:else if webConfig.search.engine === 'exa'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Exa API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Exa API Key')}
bind:value={webConfig.search.exa_api_key}
/>
</div>
{:else if webConfig.search.engine === 'bing'}
<div>
<div class=" self-center text-xs font-medium mb-1">
+2 -1
View File
@@ -61,7 +61,8 @@
},
features: {
web_search: true,
image_generation: true
image_generation: true,
code_interpreter: true
}
};
@@ -45,7 +45,8 @@
},
features: {
web_search: true,
image_generation: true
image_generation: true,
code_interpreter: true
}
};
export let userIds = [];
@@ -22,7 +22,8 @@
},
features: {
web_search: true,
image_generation: true
image_generation: true,
code_interpreter: true
}
};
@@ -257,5 +258,13 @@
<Switch bind:state={permissions.features.image_generation} />
</div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">
{$i18n.t('Code Interpreter')}
</div>
<Switch bind:state={permissions.features.code_interpreter} />
</div>
</div>
</div>
@@ -6,7 +6,9 @@
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import localizedFormat from 'dayjs/plugin/localizedFormat';
dayjs.extend(relativeTime);
dayjs.extend(localizedFormat);
import { toast } from 'svelte-sonner';
@@ -364,7 +366,7 @@
</td>
<td class=" px-3 py-1">
{dayjs(user.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
{dayjs(user.created_at * 1000).format('LL')}
</td>
<td class=" px-3 py-1"> {user.oauth_sub ?? ''} </td>
@@ -7,9 +7,11 @@
import { updateUserById } from '$lib/apis/users';
import Modal from '$lib/components/common/Modal.svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
dayjs.extend(localizedFormat);
export let show = false;
export let selectedUser;
@@ -87,7 +89,7 @@
<div class="text-xs text-gray-500">
{$i18n.t('Created at')}
{dayjs(selectedUser.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
{dayjs(selectedUser.created_at * 1000).format('LL')}
</div>
</div>
</div>
@@ -2,8 +2,10 @@
import { toast } from 'svelte-sonner';
import dayjs from 'dayjs';
import { getContext, createEventDispatcher } from 'svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
const dispatch = createEventDispatcher();
dayjs.extend(localizedFormat);
import { getChatListByUserId, deleteChatById, getArchivedChatList } from '$lib/apis/chats';
@@ -130,7 +132,7 @@
<td class=" px-3 py-1 hidden md:flex h-[2.5rem] justify-end">
<div class="my-auto shrink-0">
{dayjs(chat.updated_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
{dayjs(chat.updated_at * 1000).format('LLL')}
</div>
</td>
+35 -33
View File
@@ -200,7 +200,7 @@
files = files.filter((item) => item?.itemId !== tempItemId);
}
} catch (e) {
toast.error(e);
toast.error(`${e}`);
files = files.filter((item) => item?.itemId !== tempItemId);
}
};
@@ -398,7 +398,7 @@
dir={$settings?.chatDirection ?? 'LTR'}
>
{#if files.length > 0}
<div class="mx-1 mt-2.5 mb-1 flex flex-wrap gap-2">
<div class="mx-2 mt-2.5 -mb-1 flex flex-wrap gap-2">
{#each files as file, fileIdx}
{#if file.type === 'image'}
<div class=" relative group">
@@ -411,7 +411,7 @@
</div>
<div class=" absolute -top-1 -right-1">
<button
class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition"
type="button"
on:click={() => {
files.splice(fileIdx, 1);
@@ -453,35 +453,9 @@
</div>
{/if}
<div class=" flex">
<div class="ml-1 self-end mb-1.5 flex space-x-1">
<InputMenu
{screenCaptureHandler}
uploadFilesHandler={() => {
filesInputElement.click();
}}
>
<button
class="bg-transparent hover:bg-white/80 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-2 outline-none focus:outline-none"
type="button"
aria-label="More"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5"
>
<path
d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"
/>
</svg>
</button>
</InputMenu>
</div>
<div class="px-2.5">
<div
class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-none w-full py-2.5 px-1 rounded-xl resize-none h-fit max-h-80 overflow-auto"
class="scrollbar-hidden font-primary text-left bg-transparent dark:text-gray-100 outline-none w-full pt-3 px-1 rounded-xl resize-none h-fit max-h-80 overflow-auto"
>
<RichTextInput
bind:value={content}
@@ -528,8 +502,36 @@
}}
/>
</div>
</div>
<div class="self-end mb-1.5 flex space-x-1 mr-1">
<div class=" flex justify-between mb-2.5 mt-1.5 mx-0.5">
<div class="ml-1 self-end flex space-x-1">
<InputMenu
{screenCaptureHandler}
uploadFilesHandler={() => {
filesInputElement.click();
}}
>
<button
class="bg-transparent hover:bg-white/80 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5 outline-none focus:outline-none"
type="button"
aria-label="More"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5"
>
<path
d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"
/>
</svg>
</button>
</InputMenu>
</div>
<div class="self-end flex space-x-1 mr-1">
{#if content === ''}
<Tooltip content={$i18n.t('Record voice')}>
<button
@@ -591,7 +593,7 @@
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="size-6"
class="size-5"
>
<path
fill-rule="evenodd"
@@ -3,10 +3,12 @@
import relativeTime from 'dayjs/plugin/relativeTime';
import isToday from 'dayjs/plugin/isToday';
import isYesterday from 'dayjs/plugin/isYesterday';
import localizedFormat from 'dayjs/plugin/localizedFormat';
dayjs.extend(relativeTime);
dayjs.extend(isToday);
dayjs.extend(isYesterday);
dayjs.extend(localizedFormat);
import { getContext, onMount } from 'svelte';
const i18n = getContext<Writable<i18nType>>('i18n');
@@ -153,9 +155,7 @@
<div
class="mt-1.5 flex flex-shrink-0 items-center text-xs self-center invisible group-hover:visible text-gray-500 font-medium first-letter:capitalize"
>
<Tooltip
content={dayjs(message.created_at / 1000000).format('dddd, DD MMMM YYYY HH:mm')}
>
<Tooltip content={dayjs(message.created_at / 1000000).format('LLLL')}>
{dayjs(message.created_at / 1000000).format('HH:mm')}
</Tooltip>
</div>
@@ -174,9 +174,7 @@
<div
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
>
<Tooltip
content={dayjs(message.created_at / 1000000).format('dddd, DD MMMM YYYY HH:mm')}
>
<Tooltip content={dayjs(message.created_at / 1000000).format('LLLL')}>
<span class="line-clamp-1">{formatDate(message.created_at / 1000000)}</span>
</Tooltip>
</div>
+104 -77
View File
@@ -41,11 +41,13 @@
convertMessagesToHistory,
copyToClipboard,
getMessageContentParts,
createMessagesList,
extractSentencesForAudio,
promptTemplate,
splitStream,
sleep,
removeDetailsWithReasoning
removeDetails,
getPromptVariables
} from '$lib/utils';
import { generateChatCompletion } from '$lib/apis/ollama';
@@ -82,10 +84,12 @@
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
import Placeholder from './Placeholder.svelte';
import NotificationToast from '../NotificationToast.svelte';
import Spinner from '../common/Spinner.svelte';
export let chatIdProp = '';
let loaded = false;
let loading = false;
const eventTarget = new EventTarget();
let controlPane;
let controlPaneComponent;
@@ -114,7 +118,7 @@
let selectedToolIds = [];
let imageGenerationEnabled = false;
let webSearchEnabled = false;
let codeInterpreterEnabled = false;
let chat = null;
let tags = [];
@@ -133,6 +137,7 @@
$: if (chatIdProp) {
(async () => {
loading = true;
console.log(chatIdProp);
prompt = '';
@@ -141,11 +146,9 @@
webSearchEnabled = false;
imageGenerationEnabled = false;
loaded = false;
if (chatIdProp && (await loadChat())) {
await tick();
loaded = true;
loading = false;
if (localStorage.getItem(`chat-input-${chatIdProp}`)) {
try {
@@ -223,7 +226,7 @@
}
await tick();
saveChatHandler(_chatId);
saveChatHandler(_chatId, history);
};
const chatEventHandler = async (event, cb) => {
@@ -627,7 +630,7 @@
} catch (e) {
// Remove the failed doc from the files array
files = files.filter((f) => f.name !== url);
toast.error(e);
toast.error(`${e}`);
}
};
@@ -715,6 +718,7 @@
if ($page.url.searchParams.get('web-search') === 'true') {
webSearchEnabled = true;
}
if ($page.url.searchParams.get('image-generation') === 'true') {
imageGenerationEnabled = true;
}
@@ -823,20 +827,6 @@
messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight;
}
};
const createMessagesList = (responseMessageId) => {
if (responseMessageId === null) {
return [];
}
const message = history.messages[responseMessageId];
if (message?.parentId) {
return [...createMessagesList(message.parentId), message];
} else {
return [message];
}
};
const chatCompletedHandler = async (chatId, modelId, responseMessageId, messages) => {
const res = await chatCompleted(localStorage.token, {
model: modelId,
@@ -893,7 +883,7 @@
};
const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => {
const messages = createMessagesList(responseMessageId);
const messages = createMessagesList(history, responseMessageId);
const res = await chatAction(localStorage.token, actionId, {
model: modelId,
@@ -962,7 +952,7 @@
const modelId = selectedModels[0];
const model = $models.filter((m) => m.id === modelId).at(0);
const messages = createMessagesList(history.currentId);
const messages = createMessagesList(history, history.currentId);
const parentMessage = messages.length !== 0 ? messages.at(-1) : null;
const userMessageId = uuidv4();
@@ -1007,9 +997,9 @@
}
if (messages.length === 0) {
await initChatHandler();
await initChatHandler(history);
} else {
await saveChatHandler($chatId);
await saveChatHandler($chatId, history);
}
}
};
@@ -1071,9 +1061,9 @@
}
if (messages.length === 0) {
await initChatHandler();
await initChatHandler(history);
} else {
await saveChatHandler($chatId);
await saveChatHandler($chatId, history);
}
};
@@ -1207,7 +1197,12 @@
);
history.messages[message.id] = message;
await chatCompletedHandler(chatId, message.model, message.id, createMessagesList(message.id));
await chatCompletedHandler(
chatId,
message.model,
message.id,
createMessagesList(history, message.id)
);
}
console.log(data);
@@ -1223,7 +1218,7 @@
const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
console.log('submitPrompt', userPrompt, $chatId);
const messages = createMessagesList(history.currentId);
const messages = createMessagesList(history, history.currentId);
const _selectedModels = selectedModels.map((modelId) =>
$models.map((m) => m.id).includes(modelId) ? modelId : ''
);
@@ -1271,7 +1266,6 @@
}
prompt = '';
await tick();
// Reset chat input textarea
const chatInputElement = document.getElementById('chat-input');
@@ -1313,34 +1307,25 @@
history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
}
// Wait until history/message have been updated
await tick();
// focus on chat input
const chatInput = document.getElementById('chat-input');
chatInput?.focus();
saveSessionSelectedModels();
await sendPrompt(userPrompt, userMessageId, { newChat: true });
await sendPrompt(history, userPrompt, userMessageId, { newChat: true });
};
const sendPrompt = async (
_history,
prompt: string,
parentId: string,
{ modelId = null, modelIdx = null, newChat = false } = {}
) => {
// Create new chat if newChat is true and first user message
if (
newChat &&
history.messages[history.currentId].parentId === null &&
history.messages[history.currentId].role === 'user'
) {
await initChatHandler();
} else {
await saveChatHandler($chatId);
}
let _chatId = JSON.parse(JSON.stringify($chatId));
_history = JSON.parse(JSON.stringify(_history));
const responseMessageIds: Record<PropertyKey, string> = {};
// If modelId is provided, use it, else use selected model
let selectedModelIds = modelId
? [modelId]
@@ -1349,7 +1334,6 @@
: selectedModels;
// Create response messages for each selected model
const responseMessageIds: Record<PropertyKey, string> = {};
for (const [_modelIdx, modelId] of selectedModelIds.entries()) {
const model = $models.filter((m) => m.id === modelId).at(0);
@@ -1384,19 +1368,26 @@
responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`] = responseMessageId;
}
}
history = history;
// Create new chat if newChat is true and first user message
if (newChat && _history.messages[_history.currentId].parentId === null) {
_chatId = await initChatHandler(_history);
}
await tick();
_history = JSON.parse(JSON.stringify(history));
// Save chat after all messages have been created
await saveChatHandler($chatId);
await saveChatHandler(_chatId, _history);
const _chatId = JSON.parse(JSON.stringify($chatId));
await Promise.all(
selectedModelIds.map(async (modelId, _modelIdx) => {
console.log('modelId', modelId);
const model = $models.filter((m) => m.id === modelId).at(0);
if (model) {
const messages = createMessagesList(parentId);
const messages = createMessagesList(_history, parentId);
// If there are image files, check if model is vision capable
const hasImages = messages.some((message) =>
message.files?.some((file) => file.type === 'image')
@@ -1412,7 +1403,7 @@
let responseMessageId =
responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`];
let responseMessage = history.messages[responseMessageId];
let responseMessage = _history.messages[responseMessageId];
let userContext = null;
if ($settings?.memory ?? false) {
@@ -1441,7 +1432,7 @@
const chatEventEmitter = await getChatEventEmitter(model.id, _chatId);
scrollToBottom();
await sendPromptSocket(model, responseMessageId, _chatId);
await sendPromptSocket(_history, model, responseMessageId, _chatId);
if (chatEventEmitter) clearInterval(chatEventEmitter);
} else {
@@ -1454,9 +1445,9 @@
chats.set(await getChatList(localStorage.token, $currentChatPage));
};
const sendPromptSocket = async (model, responseMessageId, _chatId) => {
const responseMessage = history.messages[responseMessageId];
const userMessage = history.messages[responseMessage.parentId];
const sendPromptSocket = async (_history, model, responseMessageId, _chatId) => {
const responseMessage = _history.messages[responseMessageId];
const userMessage = _history.messages[responseMessage.parentId];
let files = JSON.parse(JSON.stringify(chatFiles));
files.push(
@@ -1504,9 +1495,9 @@
}`
}
: undefined,
...createMessagesList(responseMessageId).map((message) => ({
...createMessagesList(_history, responseMessageId).map((message) => ({
...message,
content: removeDetailsWithReasoning(message.content)
content: removeDetails(message.content, ['reasoning', 'code_interpreter'])
}))
]
.filter((message) => message?.content?.trim())
@@ -1557,9 +1548,28 @@
files: (files?.length ?? 0) > 0 ? files : undefined,
tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
features: {
image_generation: imageGenerationEnabled,
web_search: webSearchEnabled
image_generation:
$config?.features?.enable_image_generation &&
($user.role === 'admin' || $user?.permissions?.features?.image_generation)
? imageGenerationEnabled
: false,
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)
? webSearchEnabled || ($settings?.webSearch ?? false) === 'always'
: false
},
variables: {
...getPromptVariables(
$user.name,
$settings?.userLocation ? await getAndUpdateUserLocation(localStorage.token) : undefined
)
},
session_id: $socket?.id,
@@ -1590,12 +1600,15 @@
},
`${WEBUI_BASE_URL}/api`
).catch((error) => {
console.log(error);
toast.error(`${error}`);
responseMessage.error = {
content: error
};
responseMessage.done = true;
history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId;
return null;
});
@@ -1693,7 +1706,7 @@
history.currentId = userMessageId;
await tick();
await sendPrompt(userPrompt, userMessageId);
await sendPrompt(history, userPrompt, userMessageId);
};
const regenerateResponse = async (message) => {
@@ -1705,11 +1718,11 @@
if ((userMessage?.models ?? [...selectedModels]).length == 1) {
// If user message has only one model selected, sendPrompt automatically selects it for regeneration
await sendPrompt(userPrompt, userMessage.id);
await sendPrompt(history, userPrompt, userMessage.id);
} else {
// If there are multiple models selected, use the model of the response message for regeneration
// e.g. many model chat
await sendPrompt(userPrompt, userMessage.id, {
await sendPrompt(history, userPrompt, userMessage.id, {
modelId: message.model,
modelIdx: message.modelIdx
});
@@ -1731,7 +1744,7 @@
.at(0);
if (model) {
await sendPromptSocket(model, responseMessage.id, _chatId);
await sendPromptSocket(history, model, responseMessage.id, _chatId);
}
}
};
@@ -1774,7 +1787,7 @@
}
}
await saveChatHandler(_chatId);
await saveChatHandler(_chatId, history);
} else {
console.error(res);
}
@@ -1783,42 +1796,48 @@
}
};
const initChatHandler = async () => {
const initChatHandler = async (history) => {
let _chatId = $chatId;
if (!$temporaryChatEnabled) {
chat = await createNewChat(localStorage.token, {
id: $chatId,
id: _chatId,
title: $i18n.t('New Chat'),
models: selectedModels,
system: $settings.system ?? undefined,
params: params,
history: history,
messages: createMessagesList(history.currentId),
messages: createMessagesList(history, history.currentId),
tags: [],
timestamp: Date.now()
});
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
await chatId.set(chat.id);
_chatId = chat.id;
await chatId.set(_chatId);
window.history.replaceState(history.state, '', `/c/${chat.id}`);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
currentChatPage.set(1);
window.history.replaceState(history.state, '', `/c/${_chatId}`);
} else {
_chatId = 'local';
await chatId.set('local');
}
await tick();
return _chatId;
};
const saveChatHandler = async (_chatId) => {
const saveChatHandler = async (_chatId, history) => {
if ($chatId == _chatId) {
if (!$temporaryChatEnabled) {
chat = await updateChatById(localStorage.token, _chatId, {
models: selectedModels,
history: history,
messages: createMessagesList(history.currentId),
messages: createMessagesList(history, history.currentId),
params: params,
files: chatFiles
});
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
}
@@ -1861,7 +1880,7 @@
: ' '} w-full max-w-full flex flex-col"
id="chat-container"
>
{#if !chatIdProp || (loaded && chatIdProp)}
{#if chatIdProp === '' || (!loading && chatIdProp)}
{#if $settings?.backgroundImageUrl ?? null}
<div
class="absolute {$showSidebar
@@ -1921,8 +1940,8 @@
</div>
{/if}
<div class="flex flex-col flex-auto z-10 w-full">
{#if $settings?.landingPageMode === 'chat' || createMessagesList(history.currentId).length > 0}
<div class="flex flex-col flex-auto z-10 w-full @container">
{#if $settings?.landingPageMode === 'chat' || createMessagesList(history, history.currentId).length > 0}
<div
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full z-10 scrollbar-hidden"
id="messages-container"
@@ -1962,6 +1981,7 @@
bind:autoScroll
bind:selectedToolIds
bind:imageGenerationEnabled
bind:codeInterpreterEnabled
bind:webSearchEnabled
bind:atSelectedModel
transparentBackground={$settings?.backgroundImageUrl ?? false}
@@ -2013,6 +2033,7 @@
bind:autoScroll
bind:selectedToolIds
bind:imageGenerationEnabled
bind:codeInterpreterEnabled
bind:webSearchEnabled
bind:atSelectedModel
transparentBackground={$settings?.backgroundImageUrl ?? false}
@@ -2065,5 +2086,11 @@
{eventTarget}
/>
</PaneGroup>
{:else if loading}
<div class=" flex items-center justify-center h-full w-full">
<div class="m-auto">
<Spinner />
</div>
</div>
{/if}
</div>
+168 -80
View File
@@ -22,23 +22,27 @@
import { blobToFile, compressImage, createMessagesList, findWordIndices } from '$lib/utils';
import { transcribeAudio } from '$lib/apis/audio';
import { uploadFile } from '$lib/apis/files';
import { getTools } from '$lib/apis/tools';
import { generateAutoCompletion } from '$lib/apis';
import { deleteFileById } from '$lib/apis/files';
import { WEBUI_BASE_URL, WEBUI_API_BASE_URL, PASTED_TEXT_CHARACTER_LIMIT } from '$lib/constants';
import Tooltip from '../common/Tooltip.svelte';
import InputMenu from './MessageInput/InputMenu.svelte';
import Headphone from '../icons/Headphone.svelte';
import VoiceRecording from './MessageInput/VoiceRecording.svelte';
import FileItem from '../common/FileItem.svelte';
import FilesOverlay from './MessageInput/FilesOverlay.svelte';
import Commands from './MessageInput/Commands.svelte';
import XMark from '../icons/XMark.svelte';
import RichTextInput from '../common/RichTextInput.svelte';
import { generateAutoCompletion } from '$lib/apis';
import { error, text } from '@sveltejs/kit';
import Tooltip from '../common/Tooltip.svelte';
import FileItem from '../common/FileItem.svelte';
import Image from '../common/Image.svelte';
import { deleteFileById } from '$lib/apis/files';
import XMark from '../icons/XMark.svelte';
import Headphone from '../icons/Headphone.svelte';
import GlobeAlt from '../icons/GlobeAlt.svelte';
import PhotoSolid from '../icons/PhotoSolid.svelte';
import Photo from '../icons/Photo.svelte';
import CommandLine from '../icons/CommandLine.svelte';
const i18n = getContext('i18n');
@@ -65,6 +69,7 @@
export let imageGenerationEnabled = false;
export let webSearchEnabled = false;
export let codeInterpreterEnabled = false;
$: onChange({
prompt,
@@ -211,7 +216,7 @@
files = files.filter((item) => item?.itemId !== tempItemId);
}
} catch (e) {
toast.error(e);
toast.error(`${e}`);
files = files.filter((item) => item?.itemId !== tempItemId);
}
};
@@ -385,7 +390,7 @@
</div>
<div class="w-full relative">
{#if atSelectedModel !== undefined || selectedToolIds.length > 0 || webSearchEnabled || imageGenerationEnabled}
{#if atSelectedModel !== undefined || selectedToolIds.length > 0 || webSearchEnabled || ($settings?.webSearch ?? false) === 'always' || imageGenerationEnabled || codeInterpreterEnabled}
<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-gradient-to-t from-white dark:from-gray-900 z-10"
>
@@ -421,23 +426,39 @@
</div>
{/if}
{#if webSearchEnabled || ($settings?.webSearch ?? false) === 'always'}
<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-blue-400 opacity-75"
/>
<span class="relative inline-flex rounded-full size-2 bg-blue-500" />
</span>
</div>
<div class=" translate-y-[0.5px]">{$i18n.t('Search the internet')}</div>
</div>
</div>
{/if}
{#if imageGenerationEnabled}
<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-green-400 opacity-75"
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-teal-400 opacity-75"
/>
<span class="relative inline-flex rounded-full size-2 bg-green-500" />
<span class="relative inline-flex rounded-full size-2 bg-teal-500" />
</span>
</div>
<div class=" ">{$i18n.t('Image generation')}</div>
<div class=" translate-y-[0.5px]">{$i18n.t('Generate an image')}</div>
</div>
</div>
{/if}
{#if webSearchEnabled}
{#if codeInterpreterEnabled}
<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">
@@ -448,7 +469,7 @@
<span class="relative inline-flex rounded-full size-2 bg-green-500" />
</span>
</div>
<div class=" ">{$i18n.t('Search the web')}</div>
<div class=" translate-y-[0.5px]">{$i18n.t('Execute code for analysis')}</div>
</div>
</div>
{/if}
@@ -568,7 +589,7 @@
dir={$settings?.chatDirection ?? 'LTR'}
>
{#if files.length > 0}
<div class="mx-1 mt-2.5 mb-1 flex items-center flex-wrap gap-2">
<div class="mx-2 mt-2.5 -mb-1 flex items-center flex-wrap gap-2">
{#each files as file, fileIdx}
{#if file.type === 'image'}
<div class=" relative group">
@@ -606,7 +627,7 @@
</div>
<div class=" absolute -top-1 -right-1">
<button
class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition"
type="button"
on:click={() => {
files.splice(fileIdx, 1);
@@ -656,65 +677,10 @@
</div>
{/if}
<div class=" flex">
<div class="ml-1 self-end mb-1.5 flex space-x-1">
<InputMenu
bind:imageGenerationEnabled
bind:webSearchEnabled
bind:selectedToolIds
{screenCaptureHandler}
uploadFilesHandler={() => {
filesInputElement.click();
}}
uploadGoogleDriveHandler={async () => {
try {
const fileData = await createPicker();
if (fileData) {
const file = new File([fileData.blob], fileData.name, {
type: fileData.blob.type
});
await uploadFileHandler(file);
} else {
console.log('No file was selected from Google Drive');
}
} catch (error) {
console.error('Google Drive Error:', error);
toast.error(
$i18n.t('Error accessing Google Drive: {{error}}', {
error: error.message
})
);
}
}}
onClose={async () => {
await tick();
const chatInput = document.getElementById('chat-input');
chatInput?.focus();
}}
>
<button
class="bg-transparent hover:bg-white/80 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-2 outline-none focus:outline-none"
type="button"
aria-label="More"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5"
>
<path
d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"
/>
</svg>
</button>
</InputMenu>
</div>
<div class="px-2.5">
{#if $settings?.richTextInput ?? true}
<div
class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-none w-full py-2.5 px-1 rounded-xl resize-none h-fit max-h-80 overflow-auto"
class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-none w-full pt-3 px-1 resize-none h-fit max-h-80 overflow-auto"
>
<RichTextInput
bind:this={chatInputElement}
@@ -918,7 +884,7 @@
<textarea
id="chat-input"
bind:this={chatInputElement}
class="scrollbar-hidden bg-transparent dark:text-gray-100 outline-none w-full py-3 px-1 rounded-xl resize-none h-[48px]"
class="scrollbar-hidden bg-transparent dark:text-gray-100 outline-none w-full pt-3 px-1 resize-none"
placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
bind:value={prompt}
on:keypress={(e) => {
@@ -1107,8 +1073,125 @@
}}
/>
{/if}
</div>
<div class="self-end mb-1.5 flex space-x-1 mr-1">
<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%]">
<InputMenu
bind:selectedToolIds
{screenCaptureHandler}
{inputFilesHandler}
uploadFilesHandler={() => {
filesInputElement.click();
}}
uploadGoogleDriveHandler={async () => {
try {
const fileData = await createPicker();
if (fileData) {
const file = new File([fileData.blob], fileData.name, {
type: fileData.blob.type
});
await uploadFileHandler(file);
} else {
console.log('No file was selected from Google Drive');
}
} catch (error) {
console.error('Google Drive Error:', error);
toast.error(
$i18n.t('Error accessing Google Drive: {{error}}', {
error: error.message
})
);
}
}}
onClose={async () => {
await tick();
const chatInput = document.getElementById('chat-input');
chatInput?.focus();
}}
>
<button
class="bg-transparent hover:bg-gray-100 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5 outline-none focus:outline-none"
type="button"
aria-label="More"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5"
>
<path
d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"
/>
</svg>
</button>
</InputMenu>
<div class="flex gap-0.5 items-center overflow-x-auto scrollbar-none flex-1">
{#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">
<button
on:click|preventDefault={() => (webSearchEnabled = !webSearchEnabled)}
type="button"
class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-none max-w-full overflow-hidden {webSearchEnabled ||
($settings?.webSearch ?? false) === 'always'
? 'bg-blue-100 dark:bg-blue-500/20 text-blue-500 dark:text-blue-400'
: 'bg-transparent text-gray-600 dark:text-gray-400 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'}"
>
<GlobeAlt className="size-5" strokeWidth="1.75" />
<span
class="hidden @sm:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
>{$i18n.t('Web Search')}</span
>
</button>
</Tooltip>
{/if}
{#if $config?.features?.enable_image_generation && ($_user.role === 'admin' || $_user?.permissions?.features?.image_generation)}
<Tooltip content={$i18n.t('Generate an image')} placement="top">
<button
on:click|preventDefault={() =>
(imageGenerationEnabled = !imageGenerationEnabled)}
type="button"
class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-none max-w-full overflow-hidden {imageGenerationEnabled
? 'bg-gray-100 dark:bg-gray-500/20 text-gray-600 dark:text-gray-400'
: 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}"
>
<Photo className="size-5" strokeWidth="1.75" />
<span
class="hidden @sm:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
>{$i18n.t('Image')}</span
>
</button>
</Tooltip>
{/if}
{#if $_user.role === 'admin' || $_user?.permissions?.features?.code_interpreter}
<Tooltip content={$i18n.t('Execute code for analysis')} placement="top">
<button
on:click|preventDefault={() =>
(codeInterpreterEnabled = !codeInterpreterEnabled)}
type="button"
class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-none max-w-full overflow-hidden {codeInterpreterEnabled
? 'bg-gray-100 dark:bg-gray-500/20 text-gray-600 dark:text-gray-400'
: 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}"
>
<CommandLine className="size-5" strokeWidth="1.75" />
<span
class="hidden @sm:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
>{$i18n.t('Code Interpreter')}</span
>
</button>
</Tooltip>
{/if}
{/if}
</div>
</div>
<div class="self-end flex space-x-1 mr-1 flex-shrink-0">
{#if !history?.currentId || history.messages[history.currentId]?.done == true}
<Tooltip content={$i18n.t('Record voice')}>
<button
@@ -1163,7 +1246,10 @@
<div class=" flex items-center">
<Tooltip content={$i18n.t('Call')}>
<button
class=" bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full p-2 self-center"
class=" {webSearchEnabled ||
($settings?.webSearch ?? false) === 'always'
? 'bg-blue-500 text-white hover:bg-blue-400 '
: 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100'} transition rounded-full p-1.5 self-center"
type="button"
on:click={async () => {
if (selectedModels.length > 1) {
@@ -1216,7 +1302,9 @@
<button
id="send-message-button"
class="{prompt !== ''
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
? webSearchEnabled || ($settings?.webSearch ?? false) === 'always'
? 'bg-blue-500 text-white hover:bg-blue-400 '
: 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1.5 self-center"
type="submit"
disabled={prompt === ''}
@@ -1225,7 +1313,7 @@
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="size-6"
class="size-5"
>
<path
fill-rule="evenodd"
@@ -1250,7 +1338,7 @@
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-6"
class="size-5"
>
<path
fill-rule="evenodd"
@@ -15,39 +15,30 @@
import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
import CameraSolid from '$lib/components/icons/CameraSolid.svelte';
import PhotoSolid from '$lib/components/icons/PhotoSolid.svelte';
import CommandLineSolid from '$lib/components/icons/CommandLineSolid.svelte';
const i18n = getContext('i18n');
export let screenCaptureHandler: Function;
export let uploadFilesHandler: Function;
export let inputFilesHandler: Function;
export let uploadGoogleDriveHandler: Function;
export let selectedToolIds: string[] = [];
export let webSearchEnabled: boolean;
export let imageGenerationEnabled: boolean;
export let onClose: Function;
let tools = {};
let show = false;
let showImageGeneration = false;
$: showImageGeneration =
$config?.features?.enable_image_generation &&
($user.role === 'admin' || $user?.permissions?.features?.image_generation);
let showWebSearch = false;
$: showWebSearch =
$config?.features?.enable_web_search &&
($user.role === 'admin' || $user?.permissions?.features?.web_search);
$: if (show) {
init();
}
let fileUploadEnabled = true;
$: fileUploadEnabled = $user.role === 'admin' || $user?.permissions?.chat?.file_upload;
const init = async () => {
if ($_tools === null) {
await _tools.set(await getTools(localStorage.token));
@@ -62,8 +53,31 @@
return a;
}, {});
};
const detectMobile = () => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
return /android|iphone|ipad|ipod|windows phone/i.test(userAgent);
};
function handleFileChange(event) {
const inputFiles = Array.from(event.target?.files);
if (inputFiles && inputFiles.length > 0) {
console.log(inputFiles);
inputFilesHandler(inputFiles);
}
}
</script>
<!-- Hidden file input used to open the camera on mobile -->
<input
id="camera-input"
type="file"
accept="image/*"
capture="environment"
on:change={handleFileChange}
style="display: none;"
/>
<Dropdown
bind:show
on:change={(e) => {
@@ -129,63 +143,51 @@
<hr class="border-black/5 dark:border-white/5 my-1" />
{/if}
{#if showImageGeneration}
<button
class="flex w-full justify-between gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
on:click={() => {
imageGenerationEnabled = !imageGenerationEnabled;
}}
>
<div class="flex-1 flex items-center gap-2">
<PhotoSolid />
<div class=" line-clamp-1">{$i18n.t('Image')}</div>
</div>
<Switch state={imageGenerationEnabled} />
</button>
{/if}
{#if showWebSearch}
<button
class="flex w-full justify-between gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
on:click={() => {
webSearchEnabled = !webSearchEnabled;
}}
>
<div class="flex-1 flex items-center gap-2">
<GlobeAltSolid />
<div class=" line-clamp-1">{$i18n.t('Web Search')}</div>
</div>
<Switch state={webSearchEnabled} />
</button>
{/if}
{#if showImageGeneration || showWebSearch}
<hr class="border-black/5 dark:border-white/5 my-1" />
{/if}
{#if !$mobile}
<Tooltip
content={!fileUploadEnabled ? $i18n.t('You do not have permission to upload files') : ''}
className="w-full"
>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl {!fileUploadEnabled
? 'opacity-50'
: ''}"
on:click={() => {
screenCaptureHandler();
if (fileUploadEnabled) {
if (!detectMobile()) {
screenCaptureHandler();
} else {
const cameraInputElement = document.getElementById('camera-input');
if (cameraInputElement) {
cameraInputElement.click();
}
}
}
}}
>
<CameraSolid />
<div class=" line-clamp-1">{$i18n.t('Capture')}</div>
</DropdownMenu.Item>
{/if}
</Tooltip>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
on:click={() => {
uploadFilesHandler();
}}
<Tooltip
content={!fileUploadEnabled ? $i18n.t('You do not have permission to upload files') : ''}
className="w-full"
>
<DocumentArrowUpSolid />
<div class="line-clamp-1">{$i18n.t('Upload Files')}</div>
</DropdownMenu.Item>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl {!fileUploadEnabled
? 'opacity-50'
: ''}"
on:click={() => {
if (fileUploadEnabled) {
uploadFilesHandler();
}
}}
>
<DocumentArrowUpSolid />
<div class="line-clamp-1">{$i18n.t('Upload Files')}</div>
</DropdownMenu.Item>
</Tooltip>
{#if $config?.features?.enable_google_drive_integration}
<DropdownMenu.Item
+1 -1
View File
@@ -233,7 +233,7 @@
history.currentId = userMessageId;
await tick();
await sendPrompt(userPrompt, userMessageId);
await sendPrompt(history, userPrompt, userMessageId);
} else {
// Edit user message
history.messages[messageId].content = content;
@@ -25,6 +25,7 @@
export let token;
export let lang = '';
export let code = '';
export let attributes = {};
export let className = 'my-2';
export let editorClassName = '';
@@ -49,6 +50,7 @@
let stdout = null;
let stderr = null;
let result = null;
let files = null;
let copied = false;
let saved = false;
@@ -109,86 +111,7 @@
};
const executePython = async (code) => {
if (!code.includes('input') && !code.includes('matplotlib')) {
executePythonAsWorker(code);
} else {
result = null;
stdout = null;
stderr = null;
executing = true;
document.pyodideMplTarget = document.getElementById(`plt-canvas-${id}`);
let pyodide = await loadPyodide({
indexURL: '/pyodide/',
stdout: (text) => {
console.log('Python output:', text);
if (stdout) {
stdout += `${text}\n`;
} else {
stdout = `${text}\n`;
}
},
stderr: (text) => {
console.log('An error occurred:', text);
if (stderr) {
stderr += `${text}\n`;
} else {
stderr = `${text}\n`;
}
},
packages: ['micropip']
});
try {
const micropip = pyodide.pyimport('micropip');
// await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
let packages = [
code.includes('requests') ? 'requests' : null,
code.includes('bs4') ? 'beautifulsoup4' : null,
code.includes('numpy') ? 'numpy' : null,
code.includes('pandas') ? 'pandas' : null,
code.includes('matplotlib') ? 'matplotlib' : null,
code.includes('sklearn') ? 'scikit-learn' : null,
code.includes('scipy') ? 'scipy' : null,
code.includes('re') ? 'regex' : null,
code.includes('seaborn') ? 'seaborn' : null
].filter(Boolean);
console.log(packages);
await micropip.install(packages);
result = await pyodide.runPythonAsync(`from js import prompt
def input(p):
return prompt(p)
__builtins__.input = input`);
result = await pyodide.runPython(code);
if (!result) {
result = '[NO OUTPUT]';
}
console.log(result);
console.log(stdout);
console.log(stderr);
const pltCanvasElement = document.getElementById(`plt-canvas-${id}`);
if (pltCanvasElement?.innerHTML !== '') {
pltCanvasElement.classList.add('pt-4');
}
} catch (error) {
console.error('Error:', error);
stderr = error;
}
executing = false;
}
executePythonAsWorker(code);
};
const executePythonAsWorker = async (code) => {
@@ -206,7 +129,11 @@ __builtins__.input = input`);
code.includes('sklearn') ? 'scikit-learn' : null,
code.includes('scipy') ? 'scipy' : null,
code.includes('re') ? 'regex' : null,
code.includes('seaborn') ? 'seaborn' : null
code.includes('seaborn') ? 'seaborn' : null,
code.includes('sympy') ? 'sympy' : null,
code.includes('tiktoken') ? 'tiktoken' : null,
code.includes('matplotlib') ? 'matplotlib' : null,
code.includes('pytz') ? 'pytz' : null
].filter(Boolean);
console.log(packages);
@@ -233,7 +160,31 @@ __builtins__.input = input`);
console.log(id, data);
data['stdout'] && (stdout = data['stdout']);
if (data['stdout']) {
stdout = data['stdout'];
const stdoutLines = stdout.split('\n');
for (const [idx, line] of stdoutLines.entries()) {
if (line.startsWith('data:image/png;base64')) {
if (files) {
files.push({
type: 'image/png',
data: line
});
} else {
files = [
{
type: 'image/png',
data: line
}
];
}
stdout = stdout.replace(`${line}\n`, ``);
}
}
}
data['stderr'] && (stderr = data['stderr']);
data['result'] && (result = data['result']);
@@ -279,6 +230,36 @@ __builtins__.input = input`);
$: dispatch('code', { lang, code });
$: if (attributes) {
onAttributesUpdate();
}
const onAttributesUpdate = () => {
if (attributes?.output) {
// Create a helper function to unescape HTML entities
const unescapeHtml = (html) => {
const textArea = document.createElement('textarea');
textArea.innerHTML = html;
return textArea.value;
};
try {
// Unescape the HTML-encoded string
const unescapedOutput = unescapeHtml(attributes.output);
// Parse the unescaped string into JSON
const output = JSON.parse(unescapedOutput);
// Assign the parsed values to variables
stdout = output.stdout;
stderr = output.stderr;
result = output.result;
} catch (error) {
console.error('Error:', error);
}
}
};
onMount(async () => {
console.log('codeblock', lang, code);
@@ -376,18 +357,43 @@ __builtins__.input = input`);
<div
id="plt-canvas-{id}"
class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
class="bg-gray-50 dark:bg-[#202123] dark:text-white max-w-full overflow-x-auto scrollbar-hidden"
/>
{#if executing}
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">Running...</div>
</div>
{:else if stdout || stderr || result}
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">{stdout || stderr || result}</div>
{#if executing || stdout || stderr || result}
<div
class="bg-gray-50 dark:bg-[#202123] dark:text-white !rounded-b-lg py-4 px-4 flex flex-col gap-2"
>
{#if executing}
<div class=" ">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">Running...</div>
</div>
{:else}
{#if stdout || stderr}
<div class=" ">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">{stdout || stderr}</div>
</div>
{/if}
{#if result || files}
<div class=" ">
<div class=" text-gray-500 text-xs mb-1">RESULT</div>
{#if result}
<div class="text-sm">{`${JSON.stringify(result)}`}</div>
{/if}
{#if files}
<div class="flex flex-col gap-2">
{#each files as file}
{#if file.type.startsWith('image')}
<img src={file.data} alt="Output" />
{/if}
{/each}
</div>
{/if}
</div>
{/if}
{/if}
</div>
{/if}
{/if}
@@ -23,6 +23,7 @@
export let id: string;
export let tokens: Token[];
export let top = true;
export let attributes = {};
export let save = false;
export let onSourceClick: Function = () => {};
@@ -83,6 +84,7 @@
{token}
lang={token?.lang ?? ''}
code={token?.text ?? ''}
{attributes}
{save}
on:code={(e) => {
dispatch('code', e.detail);
@@ -195,9 +197,13 @@
</ul>
{/if}
{:else if token.type === 'details'}
<Collapsible title={token.summary} attributes={token?.attributes} className="w-fit space-y-1">
<Collapsible title={token.summary} attributes={token?.attributes} className="w-full space-y-1">
<div class=" mb-1.5" slot="content">
<svelte:self id={`${id}-${tokenIdx}-d`} tokens={marked.lexer(token.text)} />
<svelte:self
id={`${id}-${tokenIdx}-d`}
tokens={marked.lexer(token.text)}
attributes={token?.attributes}
/>
</div>
</Collapsible>
{:else if token.type === 'html'}
@@ -78,6 +78,7 @@
{rateMessage}
{actionMessage}
{submitMessage}
{deleteMessage}
{continueResponse}
{regenerateResponse}
{addMessages}
@@ -95,6 +96,7 @@
{rateMessage}
{actionMessage}
{submitMessage}
{deleteMessage}
{continueResponse}
{regenerateResponse}
{mergeResponses}
@@ -16,7 +16,9 @@
import Markdown from './Markdown.svelte';
import Name from './Name.svelte';
import Skeleton from './Skeleton.svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
const i18n = getContext('i18n');
dayjs.extend(localizedFormat);
export let chatId;
export let history;
@@ -32,6 +34,8 @@
export let actionMessage: Function;
export let submitMessage: Function;
export let deleteMessage: Function;
export let continueResponse: Function;
export let regenerateResponse: Function;
export let mergeResponses: Function;
@@ -226,6 +230,7 @@
{editMessage}
{saveMessage}
{rateMessage}
{deleteMessage}
{actionMessage}
{submitMessage}
{continueResponse}
@@ -264,7 +269,7 @@
<span
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
>
{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
{dayjs(message.timestamp * 1000).format('LT')}
</span>
{/if}
</Name>
@@ -114,6 +114,7 @@
export let saveMessage: Function;
export let rateMessage: Function;
export let actionMessage: Function;
export let deleteMessage: Function;
export let submitMessage: Function;
export let continueResponse: Function;
@@ -461,6 +462,10 @@
feedbackLoading = false;
};
const deleteMessageHandler = async () => {
deleteMessage(message.id);
};
$: if (!edit) {
(async () => {
await tick();
@@ -500,7 +505,7 @@
<div
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
>
<Tooltip content={dayjs(message.timestamp * 1000).format('dddd, DD MMMM YYYY HH:mm')}>
<Tooltip content={dayjs(message.timestamp * 1000).format('LLLL')}>
<span class="line-clamp-1">{formatDate(message.timestamp * 1000)}</span>
</Tooltip>
</div>
@@ -1176,6 +1181,36 @@
</button>
</Tooltip>
{#if siblings.length > 1}
<Tooltip content={$i18n.t('Delete')} placement="bottom">
<button
type="button"
id="continue-response-button"
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={() => {
deleteMessageHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
</button>
</Tooltip>
{/if}
{#if isLastMessage}
{#each model?.actions ?? [] as action}
<Tooltip content={action.name} placement="bottom">
@@ -13,8 +13,10 @@
import FileItem from '$lib/components/common/FileItem.svelte';
import Markdown from './Markdown.svelte';
import Image from '$lib/components/common/Image.svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
const i18n = getContext('i18n');
dayjs.extend(localizedFormat);
export let user;
@@ -112,7 +114,7 @@
<div
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
>
<Tooltip content={dayjs(message.timestamp * 1000).format('dddd, DD MMMM YYYY HH:mm')}>
<Tooltip content={dayjs(message.timestamp * 1000).format('LLLL')}>
<span class="line-clamp-1">{formatDate(message.timestamp * 1000)}</span>
</Tooltip>
</div>
+8 -9
View File
@@ -35,6 +35,7 @@
export let selectedToolIds = [];
export let imageGenerationEnabled = false;
export let codeInterpreterEnabled = false;
export let webSearchEnabled = false;
let models = [];
@@ -87,7 +88,7 @@
onMount(() => {});
</script>
<div class="m-auto w-full max-w-6xl px-2 xl:px-20 translate-y-6 py-24 text-center">
<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."
@@ -104,7 +105,7 @@
class="w-full text-3xl text-gray-800 dark:text-gray-100 font-medium text-center flex items-center gap-4 font-primary"
>
<div class="w-full flex flex-col justify-center items-center">
<div class="flex flex-row justify-center gap-3 sm:gap-3.5 w-fit px-5">
<div class="flex flex-row justify-center gap-3 @sm:gap-3.5 w-fit px-5">
<div class="flex flex-shrink-0 justify-center">
<div class="flex -space-x-4 mb-0.5" in:fade={{ duration: 100 }}>
{#each models as model, modelIdx}
@@ -125,7 +126,7 @@
($i18n.language === 'dg-DG'
? `/doge.png`
: `${WEBUI_BASE_URL}/static/favicon.png`)}
class=" size-9 sm:size-10 rounded-full border-[1px] border-gray-200 dark:border-none"
class=" size-9 @sm:size-10 rounded-full border-[1px] border-gray-200 dark:border-none"
alt="logo"
draggable="false"
/>
@@ -135,7 +136,7 @@
</div>
</div>
<div class=" text-3xl sm:text-4xl line-clamp-1" in:fade={{ duration: 100 }}>
<div class=" text-3xl @sm:text-4xl line-clamp-1" in:fade={{ duration: 100 }}>
{#if models[selectedModelIdx]?.name}
{models[selectedModelIdx]?.name}
{:else}
@@ -183,11 +184,7 @@
</div>
</div>
<div
class="text-base font-normal xl:translate-x-6 md:max-w-3xl w-full py-3 {atSelectedModel
? 'mt-2'
: ''}"
>
<div class="text-base font-normal @md:max-w-3xl w-full py-3 {atSelectedModel ? 'mt-2' : ''}">
<MessageInput
{history}
{selectedModels}
@@ -196,6 +193,7 @@
bind:autoScroll
bind:selectedToolIds
bind:imageGenerationEnabled
bind:codeInterpreterEnabled
bind:webSearchEnabled
bind:atSelectedModel
{transparentBackground}
@@ -218,6 +216,7 @@
suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
$config?.default_prompt_suggestions ??
[]}
inputValue={prompt}
on:select={(e) => {
selectSuggestionPrompt(e.detail);
}}
@@ -12,9 +12,11 @@
export let params = {
// Advanced
stream_response: null, // Set stream responses for this model individually
function_calling: null,
seed: null,
stop: null,
temperature: null,
reasoning_effort: null,
frequency_penalty: null,
repeat_last_n: null,
mirostat: null,
@@ -80,6 +82,35 @@
</Tooltip>
</div>
<div>
<Tooltip
content={$i18n.t(
'Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.'
)}
placement="top-start"
className="inline-tooltip"
>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Function Calling')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
on:click={() => {
params.function_calling = (params?.function_calling ?? null) === null ? 'native' : null;
}}
type="button"
>
{#if params.function_calling === 'native'}
<span class="ml-2 self-center">{$i18n.t('Native')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if}
</button>
</div>
</Tooltip>
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
@@ -158,7 +189,7 @@
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-1 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
placeholder={$i18n.t('Enter stop sequence')}
bind:value={params.stop}
@@ -224,6 +255,49 @@
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)'
)}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Reasoning Effort')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
type="button"
on:click={() => {
params.reasoning_effort = (params?.reasoning_effort ?? null) === null ? 'medium' : null;
}}
>
{#if (params?.reasoning_effort ?? 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?.reasoning_effort ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
class="w-full rounded-lg py-2 px-1 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
placeholder={$i18n.t('Enter reasoning effort')}
bind:value={params.reasoning_effort}
autocomplete="off"
/>
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
@@ -39,7 +39,7 @@
}, 100);
} else {
const res = await _getVoices(localStorage.token).catch((e) => {
toast.error(e);
toast.error(`${e}`);
});
if (res) {
@@ -46,6 +46,7 @@
let params = {
// Advanced
stream_response: null,
function_calling: null,
seed: null,
temperature: null,
frequency_penalty: null,
@@ -52,6 +52,8 @@
let voiceInterruption = false;
let hapticFeedback = false;
let webSearch = null;
const toggleSplitLargeChunks = async () => {
splitLargeChunks = !splitLargeChunks;
saveSettings({ splitLargeChunks: splitLargeChunks });
@@ -198,6 +200,11 @@
});
};
const toggleWebSearch = async () => {
webSearch = webSearch === null ? 'always' : null;
saveSettings({ webSearch: webSearch });
};
onMount(async () => {
titleAutoGenerate = $settings?.title?.auto ?? true;
autoTags = $settings.autoTags ?? true;
@@ -235,6 +242,7 @@
}
backgroundImageUrl = $settings.backgroundImageUrl ?? null;
webSearch = $settings.webSearch ?? null;
});
</script>
@@ -666,6 +674,26 @@
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs">{$i18n.t('Web Search in Chat')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
on:click={() => {
toggleWebSearch();
}}
type="button"
>
{#if webSearch === 'always'}
<span class="ml-2 self-center">{$i18n.t('Always')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if}
</button>
</div>
</div>
<div class=" my-1.5 text-sm font-medium">{$i18n.t('Voice')}</div>
<div>
@@ -11,8 +11,10 @@
import Tooltip from '$lib/components/common/Tooltip.svelte';
import { error } from '@sveltejs/kit';
import EditMemoryModal from './EditMemoryModal.svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
const i18n = getContext('i18n');
dayjs.extend(localizedFormat);
export let show = false;
@@ -84,9 +86,7 @@
</td>
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
<div class="my-auto whitespace-nowrap">
{dayjs(memory.updated_at * 1000).format(
$i18n.t('MMMM DD, YYYY hh:mm:ss A')
)}
{dayjs(memory.updated_at * 1000).format('LLL')}
</div>
</td>
<td class="px-3 py-1">
+2 -1
View File
@@ -118,7 +118,8 @@
'displayemoji',
'save',
'interfaceoptions',
'interfacecustomization'
'interfacecustomization',
'alwaysonwebsearch'
]
},
{
+113 -35
View File
@@ -1,53 +1,131 @@
<script lang="ts">
import Fuse from 'fuse.js';
import Bolt from '$lib/components/icons/Bolt.svelte';
import { onMount, getContext, createEventDispatcher } from 'svelte';
import { WEBUI_NAME } from '$lib/stores';
import { WEBUI_VERSION } from '$lib/constants';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let suggestionPrompts = [];
export let className = '';
export let inputValue = '';
let prompts = [];
let sortedPrompts = [];
$: prompts = (suggestionPrompts ?? [])
.reduce((acc, current) => [...acc, ...[current]], [])
.sort(() => Math.random() - 0.5);
const fuseOptions = {
keys: ['content', 'title'],
threshold: 0.5
};
let fuse;
let filteredPrompts = [];
// Initialize Fuse
$: fuse = new Fuse(sortedPrompts, fuseOptions);
// Update the filteredPrompts if inputValue changes
// Only increase version if something wirklich geändert hat
$: getFilteredPrompts(inputValue);
// Helper function to check if arrays are the same
// (based on unique IDs oder content)
function arraysEqual(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if ((a[i].id ?? a[i].content) !== (b[i].id ?? b[i].content)) {
return false;
}
}
return true;
}
const getFilteredPrompts = (inputValue) => {
const newFilteredPrompts = inputValue.trim()
? fuse.search(inputValue.trim()).map((result) => result.item)
: sortedPrompts;
// Compare with the oldFilteredPrompts
// If there's a difference, update array + version
if (!arraysEqual(filteredPrompts, newFilteredPrompts)) {
filteredPrompts = newFilteredPrompts;
}
};
$: if (suggestionPrompts) {
sortedPrompts = [...(suggestionPrompts ?? [])].sort(() => Math.random() - 0.5);
getFilteredPrompts(inputValue);
}
</script>
{#if prompts.length > 0}
<div class="mb-1 flex gap-1 text-sm font-medium items-center text-gray-400 dark:text-gray-600">
<div class="mb-1 flex gap-1 text-xs font-medium items-center text-gray-400 dark:text-gray-600">
{#if filteredPrompts.length > 0}
<Bolt />
{$i18n.t('Suggested')}
</div>
{/if}
{:else}
<!-- Keine Vorschläge -->
<div class=" h-40 max-h-full overflow-auto scrollbar-none {className}">
{#each prompts as prompt, promptIdx}
<button
class="flex flex-col flex-1 shrink-0 w-full justify-between px-3 py-2 rounded-xl bg-transparent hover:bg-black/5 dark:hover:bg-white/5 transition group"
on:click={() => {
dispatch('select', prompt.content);
}}
<div
class="flex w-full text-center items-center justify-center self-start text-gray-400 dark:text-gray-600"
>
<div class="flex flex-col text-left">
{#if prompt.title && prompt.title[0] !== ''}
<div
class=" font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
>
{prompt.title[0]}
</div>
<div class="text-xs text-gray-500 font-normal line-clamp-1">{prompt.title[1]}</div>
{:else}
<div
class=" font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
>
{prompt.content}
</div>
<div class="text-xs text-gray-500 font-normal line-clamp-1">Prompt</div>
{/if}
</div>
</button>
{/each}
{$WEBUI_NAME} ‧ v{WEBUI_VERSION}
</div>
{/if}
</div>
<div class="h-40 overflow-auto scrollbar-none {className} items-start">
{#if filteredPrompts.length > 0}
{#each filteredPrompts as prompt, idx (prompt.id || prompt.content)}
<button
class="waterfall flex flex-col flex-1 shrink-0 w-full justify-between
px-3 py-2 rounded-xl bg-transparent hover:bg-black/5
dark:hover:bg-white/5 transition group"
style="animation-delay: {idx * 60}ms"
on:click={() => dispatch('select', prompt.content)}
>
<div class="flex flex-col text-left">
{#if prompt.title && prompt.title[0] !== ''}
<div
class="font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
>
{prompt.title[0]}
</div>
<div class="text-xs text-gray-500 font-normal line-clamp-1">
{prompt.title[1]}
</div>
{:else}
<div
class="font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
>
{prompt.content}
</div>
<div class="text-xs text-gray-500 font-normal line-clamp-1">{$i18n.t('Prompt')}</div>
{/if}
</div>
</button>
{/each}
{/if}
</div>
<style>
/* Waterfall animation for the suggestions */
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.waterfall {
opacity: 0;
animation-name: fadeInUp;
animation-duration: 200ms;
animation-fill-mode: forwards;
animation-timing-function: ease;
}
</style>
+42 -4
View File
@@ -1,5 +1,27 @@
<script lang="ts">
import { getContext, createEventDispatcher } from 'svelte';
const i18n = getContext('i18n');
import dayjs from '$lib/dayjs';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(duration);
dayjs.extend(relativeTime);
async function loadLocale(locales) {
for (const locale of locales) {
try {
dayjs.locale(locale);
break; // Stop after successfully loading the first available locale
} catch (error) {
console.error(`Could not load locale '${locale}':`, error);
}
}
}
// Assuming $i18n.languages is an array of language codes
$: loadLocale($i18n.languages);
const dispatch = createEventDispatcher();
$: dispatch('change', open);
@@ -37,20 +59,36 @@
}}
>
<div
class=" w-full font-medium flex items-center justify-between gap-2 {attributes?.done !==
'true'
class=" w-full font-medium flex items-center justify-between gap-2 {attributes?.done &&
attributes?.done !== 'true'
? 'shimmer'
: ''}
"
>
{#if attributes?.done !== 'true'}
{#if attributes?.done && attributes?.done !== 'true'}
<div>
<Spinner className="size-4" />
</div>
{/if}
<div class="">
{title}
{#if attributes?.type === 'reasoning'}
{#if attributes?.done === 'true' && attributes?.duration}
{$i18n.t('Thought for {{DURATION}}', {
DURATION: dayjs.duration(attributes.duration, 'seconds').humanize()
})}
{:else}
{$i18n.t('Thinking...')}
{/if}
{:else if attributes?.type === 'code_interpreter'}
{#if attributes?.done === 'true'}
{$i18n.t('Analyzed')}
{:else}
{$i18n.t('Analyzing...')}
{/if}
{:else}
{title}
{/if}
</div>
<div class="flex self-center translate-y-[1px]">
+1 -1
View File
@@ -119,7 +119,7 @@
{#if dismissible}
<div class=" absolute -top-1 -right-1">
<button
class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition"
type="button"
on:click|stopPropagation={() => {
dispatch('dismiss');
+32 -51
View File
@@ -3,68 +3,49 @@
export let value = '';
export let placeholder = '';
export let rows = 1;
export let required = false;
export let className =
'w-full rounded-lg px-3 py-2 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none resize-none h-full';
export let onKeydown: Function = () => {};
'w-full rounded-lg px-3 py-2 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none h-full';
let textareaElement;
$: if (textareaElement) {
if (textareaElement.innerText !== value && value !== '') {
textareaElement.innerText = value ?? '';
}
}
// Adjust height on mount and after setting the element.
onMount(async () => {
await tick();
resize();
requestAnimationFrame(() => {
// setInterveal to cehck until textareaElement is set
const interval = setInterval(() => {
if (textareaElement) {
clearInterval(interval);
resize();
}
}, 100);
});
});
// Handle paste event to ensure only plaintext is pasted
function handlePaste(event: ClipboardEvent) {
event.preventDefault(); // Prevent the default paste action
const clipboardData = event.clipboardData?.getData('text/plain'); // Get plaintext from clipboard
// Insert plaintext into the textarea
document.execCommand('insertText', false, clipboardData);
}
const resize = () => {
if (textareaElement) {
textareaElement.style.height = '';
textareaElement.style.height = `${textareaElement.scrollHeight}px`;
}
};
</script>
<div
contenteditable="true"
<textarea
bind:this={textareaElement}
class="{className} whitespace-pre-wrap relative {value
? !value.trim()
? 'placeholder'
: ''
: 'placeholder'}"
style="field-sizing: content; -moz-user-select: text !important;"
on:input={() => {
const text = textareaElement.innerText;
if (text === '\n') {
value = '';
return;
}
value = text;
bind:value
{placeholder}
class={className}
style="field-sizing: content;"
{rows}
{required}
on:input={(e) => {
resize();
}}
on:focus={() => {
resize();
}}
on:paste={handlePaste}
on:keydown={onKeydown}
data-placeholder={placeholder}
/>
<style>
.placeholder::before {
/* absolute */
position: absolute;
content: attr(data-placeholder);
color: #adb5bd;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
pointer-events: none;
touch-action: none;
}
</style>
@@ -0,0 +1,19 @@
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z"
/>
</svg>
@@ -0,0 +1,11 @@
<script lang="ts">
export let className = 'size-4';
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
<path
fill-rule="evenodd"
d="M2.25 6a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3V6Zm3.97.97a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1 0 1.06l-2.25 2.25a.75.75 0 0 1-1.06-1.06l1.72-1.72-1.72-1.72a.75.75 0 0 1 0-1.06Zm4.28 4.28a.75.75 0 0 0 0 1.5h3a.75.75 0 0 0 0-1.5h-3Z"
clip-rule="evenodd"
/>
</svg>
+21
View File
@@ -0,0 +1,21 @@
<script lang="ts">
export let className = 'size-4';
export let strokeWidth = '1.5';
</script>
<svg
class={className}
stroke-width={strokeWidth}
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m3 16 5-7 6 6.5m6.5 2.5L16 13l-4.286 6M14 10h.01M4 19h16a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1Z"
/>
</svg>
+2 -2
View File
@@ -10,7 +10,7 @@
let showShortcuts = false;
</script>
<div class=" hidden lg:flex fixed bottom-0 right-0 px-2 py-2 z-20">
<div class=" hidden lg:flex fixed bottom-0 right-0 px-1 py-1 z-20">
<button
id="show-shortcuts-button"
class="hidden"
@@ -29,7 +29,7 @@
>
<Tooltip content={$i18n.t('Help')} placement="left">
<button
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 size-5 flex items-center justify-center text-[0.7rem] rounded-full"
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 size-4 flex items-center justify-center text-[0.7rem] rounded-full"
>
?
</button>
@@ -4,6 +4,9 @@
import { toast } from 'svelte-sonner';
import dayjs from 'dayjs';
import { getContext, createEventDispatcher } from 'svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
dayjs.extend(localizedFormat);
const dispatch = createEventDispatcher();
@@ -159,7 +162,7 @@
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
<div class="my-auto">
{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
{dayjs(chat.created_at * 1000).format('LLL')}
</div>
</td>
@@ -87,7 +87,13 @@
};
const cloneChatHandler = async (id) => {
const res = await cloneChatById(localStorage.token, id).catch((error) => {
const res = await cloneChatById(
localStorage.token,
id,
$i18n.t('Clone of {{TITLE}}', {
TITLE: title
})
).catch((error) => {
toast.error(`${error}`);
return null;
});
+26 -1
View File
@@ -33,6 +33,7 @@
let loading = false;
let stopResponseFlag = false;
let systemTextareaElement: HTMLTextAreaElement;
let messagesContainerElement: HTMLDivElement;
let showSystem = false;
@@ -58,8 +59,29 @@
console.log('stopResponse');
};
const resizeSystemTextarea = async () => {
await tick();
if (systemTextareaElement) {
systemTextareaElement.style.height = '';
systemTextareaElement.style.height = Math.min(systemTextareaElement.scrollHeight, 555) + 'px';
}
};
$: if (showSystem) {
resizeSystemTextarea();
}
const chatCompletionHandler = async () => {
if (selectedModelId === '') {
toast.error($i18n.t('Please select a model.'));
return;
}
const model = $models.find((model) => model.id === selectedModelId);
if (!model) {
selectedModelId = '';
return;
}
const [res, controller] = await chatCompletion(
localStorage.token,
@@ -258,10 +280,13 @@
<div slot="content">
<div class="pt-1 px-1.5">
<textarea
id="system-textarea"
bind:this={systemTextareaElement}
class="w-full h-full bg-transparent resize-none outline-none text-sm"
bind:value={system}
placeholder={$i18n.t("You're a helpful assistant.")}
on:input={() => {
resizeSystemTextarea();
}}
rows="4"
/>
</div>
@@ -0,0 +1,76 @@
<script lang="ts">
import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
export let message;
export let idx;
export let onDelete;
let textAreaElement: HTMLTextAreaElement;
onMount(() => {
textAreaElement.style.height = '';
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
});
</script>
<div class="flex gap-2 group">
<div class="flex items-start pt-1">
<div
class="px-2 py-1 text-sm font-semibold uppercase min-w-[6rem] text-left rounded-lg transition"
>
{$i18n.t(message.role)}
</div>
</div>
<div class="flex-1">
<!-- $i18n.t('a user') -->
<!-- $i18n.t('an assistant') -->
<textarea
id="{message.role}-{idx}-textarea"
bind:this={textAreaElement}
class="w-full bg-transparent outline-none rounded-lg p-2 text-sm resize-none overflow-hidden"
placeholder={$i18n.t(`Enter {{role}} message here`, {
role: message.role === 'user' ? $i18n.t('a user') : $i18n.t('an assistant')
})}
rows="1"
on:input={(e) => {
textAreaElement.style.height = '';
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
}}
on:focus={(e) => {
textAreaElement.style.height = '';
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
// e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
}}
bind:value={message.content}
/>
</div>
<div class=" pt-1">
<button
class=" group-hover:text-gray-500 dark:text-gray-900 dark:hover:text-gray-300 transition"
on:click={() => {
onDelete();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
</svg>
</button>
</div>
</div>
@@ -1,77 +1,20 @@
<script lang="ts">
import { onMount, getContext } from 'svelte';
import Message from './Message.svelte';
const i18n = getContext('i18n');
export let messages = [];
let textAreaElement: HTMLTextAreaElement;
onMount(() => {
messages.forEach((message, idx) => {
textAreaElement.style.height = '';
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
});
});
</script>
<div class="py-3 space-y-3">
{#each messages as message, idx}
<div class="flex gap-2 group">
<div class="flex items-start pt-1">
<div
class="px-2 py-1 text-sm font-semibold uppercase min-w-[6rem] text-left rounded-lg transition"
>
{$i18n.t(message.role)}
</div>
</div>
<div class="flex-1">
<!-- $i18n.t('a user') -->
<!-- $i18n.t('an assistant') -->
<textarea
id="{message.role}-{idx}-textarea"
bind:this={textAreaElement}
class="w-full bg-transparent outline-none rounded-lg p-2 text-sm resize-none overflow-hidden"
placeholder={$i18n.t(`Enter {{role}} message here`, {
role: message.role === 'user' ? $i18n.t('a user') : $i18n.t('an assistant')
})}
rows="1"
on:input={(e) => {
textAreaElement.style.height = '';
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
}}
on:focus={(e) => {
textAreaElement.style.height = '';
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
// e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
}}
bind:value={message.content}
/>
</div>
<div class=" pt-1">
<button
class=" group-hover:text-gray-500 dark:text-gray-900 dark:hover:text-gray-300 transition"
on:click={() => {
messages = messages.filter((message, messageIdx) => messageIdx !== idx);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
</svg>
</button>
</div>
</div>
<Message
{message}
{idx}
onDelete={() => {
messages = messages.filter((message, messageIdx) => messageIdx !== idx);
}}
/>
{/each}
</div>
@@ -54,7 +54,7 @@
const deleteHandler = async (item) => {
const res = await deleteKnowledgeById(localStorage.token, item.id).catch((e) => {
toast.error(e);
toast.error(`${e}`);
});
if (res) {
@@ -31,7 +31,7 @@
description,
accessControl
).catch((e) => {
toast.error(e);
toast.error(`${e}`);
});
if (res) {
@@ -112,7 +112,7 @@
<div class="mt-2">
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
<AccessControl bind:accessControl />
<AccessControl bind:accessControl accessRoles={['read', 'write']} />
</div>
</div>
@@ -149,7 +149,7 @@
try {
const uploadedFile = await uploadFile(localStorage.token, file).catch((e) => {
toast.error(e);
toast.error(`${e}`);
return null;
});
@@ -169,7 +169,7 @@
toast.error($i18n.t('Failed to upload file.'));
}
} catch (e) {
toast.error(e);
toast.error(`${e}`);
}
};
@@ -339,7 +339,7 @@
const syncDirectoryHandler = async () => {
if ((knowledge?.files ?? []).length > 0) {
const res = await resetKnowledgeById(localStorage.token, id).catch((e) => {
toast.error(e);
toast.error(`${e}`);
});
if (res) {
@@ -357,7 +357,7 @@
const addFileHandler = async (fileId) => {
const updatedKnowledge = await addFileToKnowledgeById(localStorage.token, id, fileId).catch(
(e) => {
toast.error(e);
toast.error(`${e}`);
return null;
}
);
@@ -386,7 +386,7 @@
}
} catch (e) {
console.error('Error in deleteFileHandler:', e);
toast.error(e);
toast.error(`${e}`);
}
};
@@ -395,7 +395,7 @@
const content = selectedFile.data.content;
const res = updateFileDataContentById(localStorage.token, fileId, content).catch((e) => {
toast.error(e);
toast.error(`${e}`);
});
const updatedKnowledge = await updateFileFromKnowledgeById(
@@ -403,7 +403,7 @@
id,
fileId
).catch((e) => {
toast.error(e);
toast.error(`${e}`);
});
if (res && updatedKnowledge) {
@@ -430,7 +430,7 @@
description: knowledge.description,
access_control: knowledge.access_control
}).catch((e) => {
toast.error(e);
toast.error(`${e}`);
});
if (res) {
@@ -522,7 +522,7 @@
id = $page.params.id;
const res = await getKnowledgeById(localStorage.token, id).catch((e) => {
toast.error(e);
toast.error(`${e}`);
return null;
});
+1 -1
View File
@@ -60,7 +60,7 @@
const deleteModelHandler = async (model) => {
const res = await deleteModelById(localStorage.token, model.id).catch((e) => {
toast.error(e);
toast.error(`${e}`);
return null;
});
@@ -531,7 +531,7 @@
<div class="my-2">
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
<AccessControl bind:accessControl />
<AccessControl bind:accessControl accessRoles={['read', 'write']} />
</div>
</div>
@@ -136,7 +136,7 @@
<div class="flex justify-end pt-3 text-sm font-medium">
<button
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg flex flex-row space-x-1 items-center {saving
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 {saving
? ' cursor-not-allowed'
: ''}"
type="submit"
+104
View File
@@ -0,0 +1,104 @@
import dayjs from 'dayjs';
// Import all locales
import 'dayjs/locale/af';
import 'dayjs/locale/am';
import 'dayjs/locale/ar';
import 'dayjs/locale/az';
import 'dayjs/locale/be';
import 'dayjs/locale/bg';
import 'dayjs/locale/bi';
import 'dayjs/locale/bm';
import 'dayjs/locale/bn';
import 'dayjs/locale/bo';
import 'dayjs/locale/br';
import 'dayjs/locale/bs';
import 'dayjs/locale/ca';
import 'dayjs/locale/cs';
import 'dayjs/locale/cv';
import 'dayjs/locale/cy';
import 'dayjs/locale/da';
import 'dayjs/locale/de';
import 'dayjs/locale/dv';
import 'dayjs/locale/el';
import 'dayjs/locale/en';
import 'dayjs/locale/eo';
import 'dayjs/locale/es';
import 'dayjs/locale/eu';
import 'dayjs/locale/fa';
import 'dayjs/locale/fi';
import 'dayjs/locale/fo';
import 'dayjs/locale/fr';
import 'dayjs/locale/fy';
import 'dayjs/locale/ga';
import 'dayjs/locale/gd';
import 'dayjs/locale/gl';
import 'dayjs/locale/gu';
import 'dayjs/locale/he';
import 'dayjs/locale/hi';
import 'dayjs/locale/hr';
import 'dayjs/locale/ht';
import 'dayjs/locale/hu';
import 'dayjs/locale/id';
import 'dayjs/locale/is';
import 'dayjs/locale/it';
import 'dayjs/locale/ja';
import 'dayjs/locale/jv';
import 'dayjs/locale/ka';
import 'dayjs/locale/kk';
import 'dayjs/locale/km';
import 'dayjs/locale/kn';
import 'dayjs/locale/ko';
import 'dayjs/locale/ku';
import 'dayjs/locale/ky';
import 'dayjs/locale/lb';
import 'dayjs/locale/lo';
import 'dayjs/locale/lt';
import 'dayjs/locale/lv';
import 'dayjs/locale/me';
import 'dayjs/locale/mi';
import 'dayjs/locale/mk';
import 'dayjs/locale/ml';
import 'dayjs/locale/mn';
import 'dayjs/locale/mr';
import 'dayjs/locale/ms';
import 'dayjs/locale/mt';
import 'dayjs/locale/my';
import 'dayjs/locale/nb';
import 'dayjs/locale/ne';
import 'dayjs/locale/nl';
import 'dayjs/locale/nn';
import 'dayjs/locale/pl';
import 'dayjs/locale/pt';
import 'dayjs/locale/ro';
import 'dayjs/locale/ru';
import 'dayjs/locale/rw';
import 'dayjs/locale/sd';
import 'dayjs/locale/se';
import 'dayjs/locale/si';
import 'dayjs/locale/sk';
import 'dayjs/locale/sl';
import 'dayjs/locale/sq';
import 'dayjs/locale/sr';
import 'dayjs/locale/ss';
import 'dayjs/locale/sv';
import 'dayjs/locale/sw';
import 'dayjs/locale/ta';
import 'dayjs/locale/te';
import 'dayjs/locale/tet';
import 'dayjs/locale/tg';
import 'dayjs/locale/th';
import 'dayjs/locale/tk';
import 'dayjs/locale/tlh';
import 'dayjs/locale/tr';
import 'dayjs/locale/tzl';
import 'dayjs/locale/tzm';
import 'dayjs/locale/uk';
import 'dayjs/locale/ur';
import 'dayjs/locale/uz';
import 'dayjs/locale/vi';
import 'dayjs/locale/yo';
import 'dayjs/locale/zh';
import 'dayjs/locale/et';
export default dayjs;
+23 -6
View File
@@ -63,8 +63,11 @@
"Allowed Endpoints": "",
"Already have an account?": "هل تملك حساب ؟",
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
"Always": "",
"Amazing": "",
"an assistant": "مساعد",
"Analyzed": "",
"Analyzing...": "",
"and": "و",
"and {{COUNT}} more": "",
"and create a new shared link.": "و أنشئ رابط مشترك جديد.",
@@ -169,9 +172,11 @@
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
"Clone": "استنساخ",
"Clone Chat": "",
"Clone of {{TITLE}}": "",
"Close": "أغلق",
"Code execution": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Collection": "مجموعة",
"Color": "",
"ComfyUI": "ComfyUI",
@@ -189,6 +194,7 @@
"Confirm your action": "",
"Confirm your new password": "",
"Connections": "اتصالات",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
"Contact Admin for WebUI Access": "",
"Content": "الاتصال",
"Content Extraction": "",
@@ -232,6 +238,7 @@
"Default": "الإفتراضي",
"Default (Open AI)": "",
"Default (SentenceTransformers)": "(SentenceTransformers) الإفتراضي",
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
"Default Model": "النموذج الافتراضي",
"Default model updated": "الإفتراضي تحديث الموديل",
"Default Models": "",
@@ -342,6 +349,7 @@
"Enter Chunk Overlap": "أدخل الChunk Overlap",
"Enter Chunk Size": "أدخل Chunk الحجم",
"Enter description": "",
"Enter Exa API Key": "",
"Enter Github Raw URL": "أدخل عنوان URL ل Github Raw",
"Enter Google PSE API Key": "أدخل مفتاح واجهة برمجة تطبيقات PSE من Google",
"Enter Google PSE Engine Id": "أدخل معرف محرك PSE من Google",
@@ -354,6 +362,7 @@
"Enter Mojeek Search API Key": "",
"Enter Number of Steps (e.g. 50)": "(e.g. 50) أدخل عدد الخطوات",
"Enter proxy URL (e.g. https://user:password@host:port)": "",
"Enter reasoning effort": "",
"Enter Sampler (e.g. Euler a)": "",
"Enter Scheduler (e.g. Karras)": "",
"Enter Score": "أدخل النتيجة",
@@ -389,12 +398,14 @@
"Error accessing Google Drive: {{error}}": "",
"Error uploading file: {{error}}": "",
"Evaluations": "",
"Exa API Key": "",
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
"Example: ALL": "",
"Example: mail": "",
"Example: ou=users,dc=foo,dc=example": "",
"Example: sAMAccountName or uid or userPrincipalName": "",
"Exclude": "",
"Execute code for analysis": "",
"Experimental": "تجريبي",
"Explore the cosmos": "",
"Export": "تصدير",
@@ -412,6 +423,7 @@
"External Models": "",
"Failed to add file.": "",
"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
"Failed to fetch models": "",
"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
"Failed to save models configuration": "",
"Failed to update settings": "",
@@ -446,6 +458,7 @@
"Format your variables using brackets like this:": "",
"Frequency Penalty": "عقوبة التردد",
"Function": "",
"Function Calling": "",
"Function created successfully": "",
"Function deleted successfully": "",
"Function Description": "",
@@ -460,6 +473,7 @@
"Functions imported successfully": "",
"General": "عام",
"General Settings": "الاعدادات العامة",
"Generate an image": "",
"Generate Image": "",
"Generating search query": "إنشاء استعلام بحث",
"Get started": "",
@@ -475,7 +489,6 @@
"Group Name": "",
"Group updated successfully": "",
"Groups": "",
"h:mm a": "الساعة:الدقائق صباحا/مساء",
"Haptic Feedback": "",
"has no conversations.": "ليس لديه محادثات.",
"Hello, {{name}}": " {{name}} مرحبا",
@@ -493,7 +506,6 @@
"Ignite curiosity": "",
"Image": "",
"Image Compression": "",
"Image generation": "",
"Image Generation": "",
"Image Generation (Experimental)": "توليد الصور (تجريبي)",
"Image Generation Engine": "محرك توليد الصور",
@@ -592,9 +604,6 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
"MMMM DD, YYYY hh:mm:ss A": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "تم تحميل النموذج '{{modelName}}' بنجاح",
"Model '{{modelTag}}' is already in queue for downloading.": "النموذج '{{modelTag}}' موجود بالفعل في قائمة الانتظار للتحميل",
@@ -621,6 +630,7 @@
"More": "المزيد",
"Name": "الأسم",
"Name your knowledge base": "",
"Native": "",
"New Chat": "دردشة جديدة",
"New Folder": "",
"New Password": "كلمة المرور الجديدة",
@@ -715,6 +725,7 @@
"Please enter a prompt": "",
"Please fill in all fields.": "",
"Please select a model first.": "",
"Please select a model.": "",
"Please select a reason": "",
"Port": "",
"Positive attitude": "موقف ايجابي",
@@ -723,6 +734,7 @@
"Previous 30 days": "أخر 30 يوم",
"Previous 7 days": "أخر 7 أيام",
"Profile Image": "صورة الملف الشخصي",
"Prompt": "",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "موجه (على سبيل المثال: أخبرني بحقيقة ممتعة عن الإمبراطورية الرومانية)",
"Prompt Content": "محتوى عاجل",
"Prompt created successfully": "",
@@ -740,6 +752,7 @@
"Re-rank models by topic similarity": "",
"Read": "",
"Read Aloud": "أقراء لي",
"Reasoning Effort": "",
"Record voice": "سجل صوت",
"Redirecting you to OpenWebUI Community": "OpenWebUI إعادة توجيهك إلى مجتمع ",
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
@@ -797,7 +810,7 @@
"Search options": "",
"Search Prompts": "أبحث حث",
"Search Result Count": "عدد نتائج البحث",
"Search the web": "",
"Search the internet": "",
"Search Tools": "",
"SearchApi API Key": "",
"SearchApi Engine": "",
@@ -930,6 +943,7 @@
"This will delete all models including custom models and cannot be undone.": "",
"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
"Thorough explanation": "شرح شامل",
"Thought for {{DURATION}}": "",
"Tika": "",
"Tika Server URL required.": "",
"Tiktoken": "",
@@ -966,6 +980,7 @@
"Tools": "",
"Tools Access": "",
"Tools are a function calling system with arbitrary code execution": "",
"Tools Function Calling Prompt": "",
"Tools have a function calling system that allows arbitrary code execution": "",
"Tools have a function calling system that allows arbitrary code execution.": "",
"Top K": "Top K",
@@ -1036,6 +1051,7 @@
"Web Loader Settings": "Web تحميل اعدادات",
"Web Search": "بحث الويب",
"Web Search Engine": "محرك بحث الويب",
"Web Search in Chat": "",
"Web Search Query Generation": "",
"Webhook URL": "Webhook الرابط",
"WebUI Settings": "WebUI اعدادات",
@@ -1065,6 +1081,7 @@
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
"You cannot upload an empty file.": "",
"You do not have permission to access this feature.": "",
"You do not have permission to upload files": "",
"You do not have permission to upload files.": "",
"You have no archived conversations.": "لا تملك محادثات محفوظه",
"You have shared this chat": "تم مشاركة هذه المحادثة",
+23 -6
View File
@@ -63,8 +63,11 @@
"Allowed Endpoints": "",
"Already have an account?": "Вече имате акаунт? ",
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
"Always": "",
"Amazing": "",
"an assistant": "асистент",
"Analyzed": "",
"Analyzing...": "",
"and": "и",
"and {{COUNT}} more": "",
"and create a new shared link.": "и създай нов общ линк.",
@@ -169,9 +172,11 @@
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
"Clone": "Клонинг",
"Clone Chat": "",
"Clone of {{TITLE}}": "",
"Close": "Затвори",
"Code execution": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Collection": "Колекция",
"Color": "",
"ComfyUI": "ComfyUI",
@@ -189,6 +194,7 @@
"Confirm your action": "",
"Confirm your new password": "",
"Connections": "Връзки",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
"Contact Admin for WebUI Access": "",
"Content": "Съдържание",
"Content Extraction": "",
@@ -232,6 +238,7 @@
"Default": "По подразбиране",
"Default (Open AI)": "",
"Default (SentenceTransformers)": "По подразбиране (SentenceTransformers)",
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
"Default Model": "Модел по подразбиране",
"Default model updated": "Моделът по подразбиране е обновен",
"Default Models": "",
@@ -342,6 +349,7 @@
"Enter Chunk Overlap": "Въведете Chunk Overlap",
"Enter Chunk Size": "Въведете Chunk Size",
"Enter description": "",
"Enter Exa API Key": "",
"Enter Github Raw URL": "Въведете URL адреса на Github Raw",
"Enter Google PSE API Key": "Въведете Google PSE API ключ",
"Enter Google PSE Engine Id": "Въведете идентификатор на двигателя на Google PSE",
@@ -354,6 +362,7 @@
"Enter Mojeek Search API Key": "",
"Enter Number of Steps (e.g. 50)": "Въведете брой стъпки (напр. 50)",
"Enter proxy URL (e.g. https://user:password@host:port)": "",
"Enter reasoning effort": "",
"Enter Sampler (e.g. Euler a)": "",
"Enter Scheduler (e.g. Karras)": "",
"Enter Score": "Въведете оценка",
@@ -389,12 +398,14 @@
"Error accessing Google Drive: {{error}}": "",
"Error uploading file: {{error}}": "",
"Evaluations": "",
"Exa API Key": "",
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
"Example: ALL": "",
"Example: mail": "",
"Example: ou=users,dc=foo,dc=example": "",
"Example: sAMAccountName or uid or userPrincipalName": "",
"Exclude": "",
"Execute code for analysis": "",
"Experimental": "Експериментално",
"Explore the cosmos": "",
"Export": "Износ",
@@ -412,6 +423,7 @@
"External Models": "",
"Failed to add file.": "",
"Failed to create API Key.": "Неуспешно създаване на API ключ.",
"Failed to fetch models": "",
"Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда",
"Failed to save models configuration": "",
"Failed to update settings": "",
@@ -446,6 +458,7 @@
"Format your variables using brackets like this:": "",
"Frequency Penalty": "Наказание за честота",
"Function": "",
"Function Calling": "",
"Function created successfully": "",
"Function deleted successfully": "",
"Function Description": "",
@@ -460,6 +473,7 @@
"Functions imported successfully": "",
"General": "Основни",
"General Settings": "Основни Настройки",
"Generate an image": "",
"Generate Image": "",
"Generating search query": "Генериране на заявка за търсене",
"Get started": "",
@@ -475,7 +489,6 @@
"Group Name": "",
"Group updated successfully": "",
"Groups": "",
"h:mm a": "h:mm a",
"Haptic Feedback": "",
"has no conversations.": "няма разговори.",
"Hello, {{name}}": "Здравей, {{name}}",
@@ -493,7 +506,6 @@
"Ignite curiosity": "",
"Image": "",
"Image Compression": "",
"Image generation": "",
"Image Generation": "",
"Image Generation (Experimental)": "Генерация на изображения (Експериментално)",
"Image Generation Engine": "Двигател за генериране на изображения",
@@ -592,9 +604,6 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
"MMMM DD, YYYY hh:mm:ss A": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "Моделът '{{modelName}}' беше успешно свален.",
"Model '{{modelTag}}' is already in queue for downloading.": "Моделът '{{modelTag}}' е вече в очакване за сваляне.",
@@ -621,6 +630,7 @@
"More": "Повече",
"Name": "Име",
"Name your knowledge base": "",
"Native": "",
"New Chat": "Нов чат",
"New Folder": "",
"New Password": "Нова парола",
@@ -715,6 +725,7 @@
"Please enter a prompt": "",
"Please fill in all fields.": "",
"Please select a model first.": "",
"Please select a model.": "",
"Please select a reason": "",
"Port": "",
"Positive attitude": "Позитивна ативност",
@@ -723,6 +734,7 @@
"Previous 30 days": "Предыдущите 30 дни",
"Previous 7 days": "Предыдущите 7 дни",
"Profile Image": "Профилна снимка",
"Prompt": "",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Промпт (напр. Обмисли ме забавна факт за Римската империя)",
"Prompt Content": "Съдържание на промпта",
"Prompt created successfully": "",
@@ -740,6 +752,7 @@
"Re-rank models by topic similarity": "",
"Read": "",
"Read Aloud": "Прочети на Голос",
"Reasoning Effort": "",
"Record voice": "Записване на глас",
"Redirecting you to OpenWebUI Community": "Пренасочване към OpenWebUI общността",
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
@@ -797,7 +810,7 @@
"Search options": "",
"Search Prompts": "Търси Промптове",
"Search Result Count": "Брой резултати от търсенето",
"Search the web": "",
"Search the internet": "",
"Search Tools": "",
"SearchApi API Key": "",
"SearchApi Engine": "",
@@ -930,6 +943,7 @@
"This will delete all models including custom models and cannot be undone.": "",
"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
"Thorough explanation": "Това е подробно описание.",
"Thought for {{DURATION}}": "",
"Tika": "",
"Tika Server URL required.": "",
"Tiktoken": "",
@@ -966,6 +980,7 @@
"Tools": "",
"Tools Access": "",
"Tools are a function calling system with arbitrary code execution": "",
"Tools Function Calling Prompt": "",
"Tools have a function calling system that allows arbitrary code execution": "",
"Tools have a function calling system that allows arbitrary code execution.": "",
"Top K": "Top K",
@@ -1036,6 +1051,7 @@
"Web Loader Settings": "Настройки за зареждане на уеб",
"Web Search": "Търсене в уеб",
"Web Search Engine": "Уеб търсачка",
"Web Search in Chat": "",
"Web Search Query Generation": "",
"Webhook URL": "Уебхук URL",
"WebUI Settings": "WebUI Настройки",
@@ -1065,6 +1081,7 @@
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
"You cannot upload an empty file.": "",
"You do not have permission to access this feature.": "",
"You do not have permission to upload files": "",
"You do not have permission to upload files.": "",
"You have no archived conversations.": "Нямате архивирани разговори.",
"You have shared this chat": "Вие сте споделели този чат",
+23 -6
View File
@@ -63,8 +63,11 @@
"Allowed Endpoints": "",
"Already have an account?": "আগে থেকেই একাউন্ট আছে?",
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
"Always": "",
"Amazing": "",
"an assistant": "একটা এসিস্ট্যান্ট",
"Analyzed": "",
"Analyzing...": "",
"and": "এবং",
"and {{COUNT}} more": "",
"and create a new shared link.": "এবং একটি নতুন শেয়ারে লিংক তৈরি করুন.",
@@ -169,9 +172,11 @@
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
"Clone": "ক্লোন",
"Clone Chat": "",
"Clone of {{TITLE}}": "",
"Close": "বন্ধ",
"Code execution": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Collection": "সংগ্রহ",
"Color": "",
"ComfyUI": "ComfyUI",
@@ -189,6 +194,7 @@
"Confirm your action": "",
"Confirm your new password": "",
"Connections": "কানেকশনগুলো",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
"Contact Admin for WebUI Access": "",
"Content": "বিষয়বস্তু",
"Content Extraction": "",
@@ -232,6 +238,7 @@
"Default": "ডিফল্ট",
"Default (Open AI)": "",
"Default (SentenceTransformers)": "ডিফল্ট (SentenceTransformers)",
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
"Default Model": "ডিফল্ট মডেল",
"Default model updated": "ডিফল্ট মডেল আপডেট হয়েছে",
"Default Models": "",
@@ -342,6 +349,7 @@
"Enter Chunk Overlap": "চাঙ্ক ওভারল্যাপ লিখুন",
"Enter Chunk Size": "চাংক সাইজ লিখুন",
"Enter description": "",
"Enter Exa API Key": "",
"Enter Github Raw URL": "গিটহাব কাঁচা URL লিখুন",
"Enter Google PSE API Key": "গুগল পিএসই এপিআই কী লিখুন",
"Enter Google PSE Engine Id": "গুগল পিএসই ইঞ্জিন আইডি লিখুন",
@@ -354,6 +362,7 @@
"Enter Mojeek Search API Key": "",
"Enter Number of Steps (e.g. 50)": "ধাপের সংখ্যা দিন (যেমন: 50)",
"Enter proxy URL (e.g. https://user:password@host:port)": "",
"Enter reasoning effort": "",
"Enter Sampler (e.g. Euler a)": "",
"Enter Scheduler (e.g. Karras)": "",
"Enter Score": "স্কোর দিন",
@@ -389,12 +398,14 @@
"Error accessing Google Drive: {{error}}": "",
"Error uploading file: {{error}}": "",
"Evaluations": "",
"Exa API Key": "",
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
"Example: ALL": "",
"Example: mail": "",
"Example: ou=users,dc=foo,dc=example": "",
"Example: sAMAccountName or uid or userPrincipalName": "",
"Exclude": "",
"Execute code for analysis": "",
"Experimental": "পরিক্ষামূলক",
"Explore the cosmos": "",
"Export": "রপ্তানি",
@@ -412,6 +423,7 @@
"External Models": "",
"Failed to add file.": "",
"Failed to create API Key.": "API Key তৈরি করা যায়নি।",
"Failed to fetch models": "",
"Failed to read clipboard contents": "ক্লিপবোর্ডের বিষয়বস্তু পড়া সম্ভব হয়নি",
"Failed to save models configuration": "",
"Failed to update settings": "",
@@ -446,6 +458,7 @@
"Format your variables using brackets like this:": "",
"Frequency Penalty": "ফ্রিকোয়েন্সি পেনাল্টি",
"Function": "",
"Function Calling": "",
"Function created successfully": "",
"Function deleted successfully": "",
"Function Description": "",
@@ -460,6 +473,7 @@
"Functions imported successfully": "",
"General": "সাধারণ",
"General Settings": "সাধারণ সেটিংসমূহ",
"Generate an image": "",
"Generate Image": "",
"Generating search query": "অনুসন্ধান ক্যোয়ারী তৈরি করা হচ্ছে",
"Get started": "",
@@ -475,7 +489,6 @@
"Group Name": "",
"Group updated successfully": "",
"Groups": "",
"h:mm a": "h:mm a",
"Haptic Feedback": "",
"has no conversations.": "কোন কনভার্সেশন আছে না।",
"Hello, {{name}}": "হ্যালো, {{name}}",
@@ -493,7 +506,6 @@
"Ignite curiosity": "",
"Image": "",
"Image Compression": "",
"Image generation": "",
"Image Generation": "",
"Image Generation (Experimental)": "ইমেজ জেনারেশন (পরিক্ষামূলক)",
"Image Generation Engine": "ইমেজ জেনারেশন ইঞ্জিন",
@@ -592,9 +604,6 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
"MMMM DD, YYYY hh:mm:ss A": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' মডেল সফলভাবে ডাউনলোড হয়েছে।",
"Model '{{modelTag}}' is already in queue for downloading.": "{{modelTag}} ডাউনলোডের জন্য আগে থেকেই অপেক্ষমান আছে।",
@@ -621,6 +630,7 @@
"More": "আরো",
"Name": "নাম",
"Name your knowledge base": "",
"Native": "",
"New Chat": "নতুন চ্যাট",
"New Folder": "",
"New Password": "নতুন পাসওয়ার্ড",
@@ -715,6 +725,7 @@
"Please enter a prompt": "",
"Please fill in all fields.": "",
"Please select a model first.": "",
"Please select a model.": "",
"Please select a reason": "",
"Port": "",
"Positive attitude": "পজিটিভ আক্রমণ",
@@ -723,6 +734,7 @@
"Previous 30 days": "পূর্ব ৩০ দিন",
"Previous 7 days": "পূর্ব দিন",
"Profile Image": "প্রোফাইল ইমেজ",
"Prompt": "",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "প্রম্প্ট (উদাহরণস্বরূপ, আমি রোমান ইমপার্টের সম্পর্কে একটি উপস্থিতি জানতে বল)",
"Prompt Content": "প্রম্পট কন্টেন্ট",
"Prompt created successfully": "",
@@ -740,6 +752,7 @@
"Re-rank models by topic similarity": "",
"Read": "",
"Read Aloud": "পড়াশোনা করুন",
"Reasoning Effort": "",
"Record voice": "ভয়েস রেকর্ড করুন",
"Redirecting you to OpenWebUI Community": "আপনাকে OpenWebUI কমিউনিটিতে পাঠানো হচ্ছে",
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
@@ -797,7 +810,7 @@
"Search options": "",
"Search Prompts": "প্রম্পটসমূহ অনুসন্ধান করুন",
"Search Result Count": "অনুসন্ধানের ফলাফল গণনা",
"Search the web": "",
"Search the internet": "",
"Search Tools": "",
"SearchApi API Key": "",
"SearchApi Engine": "",
@@ -930,6 +943,7 @@
"This will delete all models including custom models and cannot be undone.": "",
"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
"Thorough explanation": "পুঙ্খানুপুঙ্খ ব্যাখ্যা",
"Thought for {{DURATION}}": "",
"Tika": "",
"Tika Server URL required.": "",
"Tiktoken": "",
@@ -966,6 +980,7 @@
"Tools": "",
"Tools Access": "",
"Tools are a function calling system with arbitrary code execution": "",
"Tools Function Calling Prompt": "",
"Tools have a function calling system that allows arbitrary code execution": "",
"Tools have a function calling system that allows arbitrary code execution.": "",
"Top K": "Top K",
@@ -1036,6 +1051,7 @@
"Web Loader Settings": "ওয়েব লোডার সেটিংস",
"Web Search": "ওয়েব অনুসন্ধান",
"Web Search Engine": "ওয়েব সার্চ ইঞ্জিন",
"Web Search in Chat": "",
"Web Search Query Generation": "",
"Webhook URL": "ওয়েবহুক URL",
"WebUI Settings": "WebUI সেটিংসমূহ",
@@ -1065,6 +1081,7 @@
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
"You cannot upload an empty file.": "",
"You do not have permission to access this feature.": "",
"You do not have permission to upload files": "",
"You do not have permission to upload files.": "",
"You have no archived conversations.": "আপনার কোনও আর্কাইভ করা কথোপকথন নেই।",
"You have shared this chat": "আপনি এই চ্যাটটি শেয়ার করেছেন",
+53 -36
View File
@@ -5,7 +5,7 @@
"(e.g. `sh webui.sh --api`)": "(p. ex. `sh webui.sh --api`)",
"(latest)": "(últim)",
"{{ models }}": "{{ models }}",
"{{COUNT}} Replies": "",
"{{COUNT}} Replies": "{{COUNT}} respostes",
"{{user}}'s Chats": "Els xats de {{user}}",
"{{webUIName}} Backend Required": "El Backend de {{webUIName}} és necessari",
"*Prompt node ID(s) are required for image generation": "*Els identificadors de nodes d'indicacions són necessaris per a la generació d'imatges",
@@ -35,7 +35,7 @@
"Add Group": "Afegir grup",
"Add Memory": "Afegir memòria",
"Add Model": "Afegir un model",
"Add Reaction": "",
"Add Reaction": "Afegir reacció",
"Add Tag": "Afegir etiqueta",
"Add Tags": "Afegir etiquetes",
"Add text content": "Afegir contingut de text",
@@ -51,7 +51,7 @@
"Advanced Params": "Paràmetres avançats",
"All Documents": "Tots els documents",
"All models deleted successfully": "Tots els models s'han eliminat correctament",
"Allow Chat Controls": "",
"Allow Chat Controls": "Permetre els controls de xat",
"Allow Chat Delete": "Permetre eliminar el xat",
"Allow Chat Deletion": "Permetre la supressió del xat",
"Allow Chat Edit": "Permetre editar el xat",
@@ -60,18 +60,21 @@
"Allow Temporary Chat": "Permetre el xat temporal",
"Allow User Location": "Permetre la ubicació de l'usuari",
"Allow Voice Interruption in Call": "Permetre la interrupció de la veu en una trucada",
"Allowed Endpoints": "",
"Allowed Endpoints": "Punts d'accés permesos",
"Already have an account?": "Ja tens un compte?",
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "Alternativa al top_p, i pretén garantir un equilibri de qualitat i varietat. El paràmetre p representa la probabilitat mínima que es consideri un token, en relació amb la probabilitat del token més probable. Per exemple, amb p=0,05 i el token més probable amb una probabilitat de 0,9, es filtren els logits amb un valor inferior a 0,045. (Per defecte: 0.0)",
"Always": "",
"Amazing": "Al·lucinant",
"an assistant": "un assistent",
"Analyzed": "",
"Analyzing...": "",
"and": "i",
"and {{COUNT}} more": "i {{COUNT}} més",
"and create a new shared link.": "i crear un nou enllaç compartit.",
"API Base URL": "URL Base de l'API",
"API Key": "clau API",
"API Key created.": "clau API creada.",
"API Key Endpoint Restrictions": "",
"API Key Endpoint Restrictions": "Restriccions del punt d'accés de la Clau API",
"API keys": "Claus de l'API",
"Application DN": "DN d'aplicació",
"Application DN Password": "Contrasenya del DN d'aplicació",
@@ -91,7 +94,7 @@
"Assistant": "Assistent",
"Attach file": "Adjuntar arxiu",
"Attention to detail": "Atenció al detall",
"Attribute for Mail": "",
"Attribute for Mail": "Atribut per al Correu",
"Attribute for Username": "Atribut per al Nom d'usuari",
"Audio": "Àudio",
"August": "Agost",
@@ -168,10 +171,12 @@
"Click on the user role button to change a user's role.": "Clica sobre el botó de rol d'usuari per canviar el rol d'un usuari.",
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Permís d'escriptura al porta-retalls denegat. Comprova els ajustos de navegador per donar l'accés necessari.",
"Clone": "Clonar",
"Clone Chat": "",
"Clone Chat": "Clonar el xat",
"Clone of {{TITLE}}": "",
"Close": "Tancar",
"Code execution": "Execució de codi",
"Code formatted successfully": "Codi formatat correctament",
"Code Interpreter": "",
"Collection": "Col·lecció",
"Color": "Color",
"ComfyUI": "ComfyUI",
@@ -189,6 +194,7 @@
"Confirm your action": "Confirma la teva acció",
"Confirm your new password": "Confirma la teva nova contrasenya",
"Connections": "Connexions",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "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. (Per defecte: mitjà)",
"Contact Admin for WebUI Access": "Posat en contacte amb l'administrador per accedir a WebUI",
"Content": "Contingut",
"Content Extraction": "Extracció de contingut",
@@ -232,6 +238,7 @@
"Default": "Per defecte",
"Default (Open AI)": "Per defecte (Open AI)",
"Default (SentenceTransformers)": "Per defecte (SentenceTransformers)",
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
"Default Model": "Model per defecte",
"Default model updated": "Model per defecte actualitzat",
"Default Models": "Models per defecte",
@@ -316,7 +323,7 @@
"Embedding Model": "Model d'incrustació",
"Embedding Model Engine": "Motor de model d'incrustació",
"Embedding model set to \"{{embedding_model}}\"": "Model d'incrustació configurat a \"{{embedding_model}}\"",
"Enable API Key": "",
"Enable API Key": "Activar la Clau API",
"Enable autocomplete generation for chat messages": "Activar la generació automàtica per als missatges del xat",
"Enable Community Sharing": "Activar l'ús compartit amb la comunitat",
"Enable Google Drive": "Activar Google Drive",
@@ -342,6 +349,7 @@
"Enter Chunk Overlap": "Introdueix la mida de solapament de blocs",
"Enter Chunk Size": "Introdueix la mida del bloc",
"Enter description": "Introdueix la descripció",
"Enter Exa API Key": "",
"Enter Github Raw URL": "Introdueix l'URL en brut de Github",
"Enter Google PSE API Key": "Introdueix la clau API de Google PSE",
"Enter Google PSE Engine Id": "Introdueix l'identificador del motor PSE de Google",
@@ -354,6 +362,7 @@
"Enter Mojeek Search API Key": "Introdueix la clau API de Mojeek Search",
"Enter Number of Steps (e.g. 50)": "Introdueix el nombre de passos (p. ex. 50)",
"Enter proxy URL (e.g. https://user:password@host:port)": "Entra l'URL (p. ex. https://user:password@host:port)",
"Enter reasoning effort": "Introdueix l'esforç de raonament",
"Enter Sampler (e.g. Euler a)": "Introdueix el mostrejador (p.ex. Euler a)",
"Enter Scheduler (e.g. Karras)": "Entra el programador (p.ex. Karras)",
"Enter Score": "Introdueix la puntuació",
@@ -389,12 +398,14 @@
"Error accessing Google Drive: {{error}}": "Error en accedir a Google Drive: {{error}}",
"Error uploading file: {{error}}": "Error en pujar l'arxiu: {{error}}",
"Evaluations": "Avaluacions",
"Exa API Key": "",
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Exemple: (&(objectClass=inetOrgPerson)(uid=%s))",
"Example: ALL": "Exemple: TOTS",
"Example: mail": "",
"Example: mail": "Exemple: mail",
"Example: ou=users,dc=foo,dc=example": "Exemple: ou=users,dc=foo,dc=example",
"Example: sAMAccountName or uid or userPrincipalName": "Exemple: sAMAccountName o uid o userPrincipalName",
"Exclude": "Excloure",
"Execute code for analysis": "",
"Experimental": "Experimental",
"Explore the cosmos": "Explorar el cosmos",
"Export": "Exportar",
@@ -412,11 +423,12 @@
"External Models": "Models externs",
"Failed to add file.": "No s'ha pogut afegir l'arxiu.",
"Failed to create API Key.": "No s'ha pogut crear la clau API.",
"Failed to 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 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.",
"Features Permissions": "",
"Features Permissions": "Permisos de les característiques",
"February": "Febrer",
"Feedback History": "Històric de comentaris",
"Feedbacks": "Comentaris",
@@ -446,6 +458,7 @@
"Format your variables using brackets like this:": "Formata les teves variables utilitzant claudàtors així:",
"Frequency Penalty": "Penalització per freqüència",
"Function": "Funció",
"Function Calling": "",
"Function created successfully": "La funció s'ha creat correctament",
"Function deleted successfully": "La funció s'ha eliminat correctament",
"Function Description": "Descripció de la funció",
@@ -460,6 +473,7 @@
"Functions imported successfully": "Les funcions s'han importat correctament",
"General": "General",
"General Settings": "Preferències generals",
"Generate an image": "",
"Generate Image": "Generar imatge",
"Generating search query": "Generant consulta",
"Get started": "Començar",
@@ -475,7 +489,6 @@
"Group Name": "Nom del grup",
"Group updated successfully": "Grup actualitzat correctament",
"Groups": "Grups",
"h:mm a": "h:mm a",
"Haptic Feedback": "Retorn hàptic",
"has no conversations.": "no té converses.",
"Hello, {{name}}": "Hola, {{name}}",
@@ -491,15 +504,14 @@
"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Afirmo que he llegit i entenc les implicacions de la meva acció. Soc conscient dels riscos associats a l'execució de codi arbitrari i he verificat la fiabilitat de la font.",
"ID": "ID",
"Ignite curiosity": "Despertar la curiositat",
"Image": "",
"Image": "Imatge",
"Image Compression": "Compressió d'imatges",
"Image generation": "",
"Image Generation": "",
"Image Generation": "Generació d'imatges",
"Image Generation (Experimental)": "Generació d'imatges (Experimental)",
"Image Generation Engine": "Motor de generació d'imatges",
"Image Max Compression Size": "Mida màxima de la compressió d'imatges",
"Image Prompt Generation": "",
"Image Prompt Generation Prompt": "",
"Image Prompt Generation": "Generació d'indicacions d'imatge",
"Image Prompt Generation Prompt": "Indicació per a la generació d'indicacions d'imatge",
"Image Settings": "Preferències d'imatges",
"Images": "Imatges",
"Import Chats": "Importar xats",
@@ -520,7 +532,7 @@
"Interface": "Interfície",
"Invalid file format.": "Format d'arxiu no vàlid.",
"Invalid Tag": "Etiqueta no vàlida",
"is typing...": "",
"is typing...": "està escrivint...",
"January": "Gener",
"Jina API Key": "Clau API de Jina",
"join our Discord for help.": "uneix-te al nostre Discord per obtenir ajuda.",
@@ -545,7 +557,7 @@
"Language": "Idioma",
"Last Active": "Activitat recent",
"Last Modified": "Modificació",
"Last reply": "",
"Last reply": "Darrera resposta",
"LDAP": "LDAP",
"LDAP server updated": "Servidor LDAP actualitzat",
"Leaderboard": "Tauler de classificació",
@@ -556,7 +568,7 @@
"Leave empty to use the default prompt, or enter a custom prompt": "Deixa-ho en blanc per utilitzar la indicació predeterminada o introdueix una indicació personalitzada",
"Light": "Clar",
"Listening...": "Escoltant...",
"Llama.cpp": "",
"Llama.cpp": "Llama.cpp",
"LLMs can make mistakes. Verify important information.": "Els models de llenguatge poden cometre errors. Verifica la informació important.",
"Local": "Local",
"Local Models": "Models locals",
@@ -567,7 +579,7 @@
"Make sure to export a workflow.json file as API format from ComfyUI.": "Assegura't d'exportar un fitxer workflow.json com a format API des de ComfyUI.",
"Manage": "Gestionar",
"Manage Arena Models": "Gestionar els models de l'Arena",
"Manage Models": "",
"Manage Models": "Gestionar els models",
"Manage Ollama": "Gestionar Ollama",
"Manage Ollama API Connections": "Gestionar les connexions a l'API d'Ollama",
"Manage OpenAI API Connections": "Gestionar les connexions a l'API d'OpenAI",
@@ -592,9 +604,6 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Eta de Mirostat",
"Mirostat Tau": "Tau de Mirostat",
"MMMM DD, YYYY": "DD de MMMM, YYYY",
"MMMM DD, YYYY HH:mm": "DD de MMMM, YYYY HH:mm",
"MMMM DD, YYYY hh:mm:ss A": "DD de MMMM, YYYY HH:mm:ss, A",
"Model": "Model",
"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.",
@@ -621,8 +630,9 @@
"More": "Més",
"Name": "Nom",
"Name your knowledge base": "Anomena la teva base de coneixement",
"Native": "",
"New Chat": "Nou xat",
"New Folder": "",
"New Folder": "Nova carpeta",
"New Password": "Nova contrasenya",
"new-channel": "nou-canal",
"No content found": "No s'ha trobat contingut",
@@ -633,7 +643,7 @@
"No files found.": "No s'han trobat arxius.",
"No groups with access, add a group to grant access": "No hi ha cap grup amb accés, afegeix un grup per concedir accés",
"No HTML, CSS, or JavaScript content found.": "No s'ha trobat contingut HTML, CSS o JavaScript.",
"No inference engine with management support found": "",
"No inference engine with management support found": "No s'ha trobat un motor d'inferència amb suport de gestió",
"No knowledge found": "No s'ha trobat Coneixement",
"No model IDs": "No hi ha IDs de model",
"No models found": "No s'han trobat models",
@@ -715,6 +725,7 @@
"Please enter a prompt": "Si us plau, entra una indicació",
"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.": "",
"Please select a reason": "Si us plau, selecciona una raó",
"Port": "Port",
"Positive attitude": "Actitud positiva",
@@ -723,6 +734,7 @@
"Previous 30 days": "30 dies anteriors",
"Previous 7 days": "7 dies anteriors",
"Profile Image": "Imatge de perfil",
"Prompt": "",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Indicació (p.ex. Digues-me quelcom divertit sobre l'Imperi Romà)",
"Prompt Content": "Contingut de la indicació",
"Prompt created successfully": "Indicació creada correctament",
@@ -738,8 +750,9 @@
"RAG Template": "Plantilla RAG",
"Rating": "Valoració",
"Re-rank models by topic similarity": "Reclassificar els models per similitud de temes",
"Read": "",
"Read": "Llegit",
"Read Aloud": "Llegir en veu alta",
"Reasoning Effort": "Esforç de raonament",
"Record voice": "Enregistrar la veu",
"Redirecting you to OpenWebUI Community": "Redirigint-te a la comunitat OpenWebUI",
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "Redueix la probabilitat de generar ximpleries. Un valor més alt (p. ex. 100) donarà respostes més diverses, mentre que un valor més baix (p. ex. 10) serà més conservador. (Per defecte: 40)",
@@ -754,7 +767,7 @@
"Rename": "Canviar el nom",
"Reorder Models": "Reordenar els models",
"Repeat Last N": "Repeteix els darrers N",
"Reply in Thread": "",
"Reply in Thread": "Respondre al fil",
"Request Mode": "Mode de sol·licitud",
"Reranking Model": "Model de reavaluació",
"Reranking model disabled": "Model de reavaluació desactivat",
@@ -763,7 +776,7 @@
"Reset All Models": "Restablir tots els models",
"Reset Upload Directory": "Restableix el directori de pujades",
"Reset Vector Storage/Knowledge": "Restableix el Repositori de vectors/Coneixement",
"Reset view": "",
"Reset view": "Netejar la vista",
"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Les notifications de resposta no es poden activar perquè els permisos del lloc web han estat rebutjats. Comprova les preferències del navegador per donar l'accés necessari.",
"Response splitting": "Divisió de la resposta",
"Result": "Resultat",
@@ -797,7 +810,7 @@
"Search options": "Opcions de cerca",
"Search Prompts": "Cercar indicacions",
"Search Result Count": "Recompte de resultats de cerca",
"Search the web": "Cercar la web",
"Search the internet": "",
"Search Tools": "Cercar eines",
"SearchApi API Key": "Clau API de SearchApi",
"SearchApi Engine": "Motor de SearchApi",
@@ -816,7 +829,7 @@
"Select a pipeline": "Seleccionar una Pipeline",
"Select a pipeline url": "Seleccionar l'URL d'una Pipeline",
"Select a tool": "Seleccionar una eina",
"Select an Ollama instance": "",
"Select an Ollama instance": "Seleccionar una instància d'Ollama",
"Select Engine": "Seleccionar el motor",
"Select Knowledge": "Seleccionar coneixement",
"Select model": "Seleccionar un model",
@@ -843,7 +856,7 @@
"Set Scheduler": "Establir el programador",
"Set Steps": "Establir el nombre de passos",
"Set Task Model": "Establir el model de tasca",
"Set the number of layers, which will be off-loaded to GPU. Increasing this value can significantly improve performance for models that are optimized for GPU acceleration but may also consume more power and GPU resources.": "",
"Set the number of layers, which will be off-loaded to GPU. Increasing this value can significantly improve performance for models that are optimized for GPU acceleration but may also consume more power and GPU resources.": "Estableix el nombre de capes que es descarregaran a la GPU. Augmentar aquest valor pot millorar significativament el rendiment dels models optimitzats per a l'acceleració de la GPU, però també pot consumir més energia i recursos de GPU.",
"Set the number of worker threads used for computation. This option controls how many threads are used to process incoming requests concurrently. Increasing this value can improve performance under high concurrency workloads but may also consume more CPU resources.": "Establir el nombre de fils de treball utilitzats per al càlcul. Aquesta opció controla quants fils s'utilitzen per processar les sol·licituds entrants simultàniament. Augmentar aquest valor pot millorar el rendiment amb càrregues de treball de concurrència elevada, però també pot consumir més recursos de CPU.",
"Set Voice": "Establir la veu",
"Set whisper model": "Establir el model whisper",
@@ -908,7 +921,7 @@
"The batch size determines how many text requests are processed together at once. A higher batch size can increase the performance and speed of the model, but it also requires more memory. (Default: 512)": "La mida del lot determina quantes sol·licituds de text es processen alhora. Una mida de lot més gran pot augmentar el rendiment i la velocitat del model, però també requereix més memòria. (Per defecte: 512)",
"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Els desenvolupadors d'aquest complement són voluntaris apassionats de la comunitat. Si trobeu útil aquest complement, considereu contribuir al seu desenvolupament.",
"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "La classificació d'avaluació es basa en el sistema de qualificació Elo i s'actualitza en temps real.",
"The LDAP attribute that maps to the mail that users use to sign in.": "",
"The LDAP attribute that maps to the mail that users use to sign in.": "L'atribut LDAP que s'associa al correu que els usuaris utilitzen per iniciar la sessió.",
"The LDAP attribute that maps to the username that users use to sign in.": "L'atribut LDAP que mapeja el nom d'usuari amb l'usuari que vol iniciar sessió",
"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "La classificació està actualment en versió beta i és possible que s'ajustin els càlculs de la puntuació a mesura que es perfeccioni l'algorisme.",
"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "La mida màxima del fitxer en MB. Si la mida del fitxer supera aquest límit, el fitxer no es carregarà.",
@@ -930,6 +943,7 @@
"This will delete all models including custom models and cannot be undone.": "Això eliminarà tots els models incloent els personalitzats i no es pot desfer",
"This will reset the knowledge base and sync all files. Do you wish to continue?": "Això restablirà la base de coneixement i sincronitzarà tots els fitxers. Vols continuar?",
"Thorough explanation": "Explicació en detall",
"Thought for {{DURATION}}": "",
"Tika": "Tika",
"Tika Server URL required.": "La URL del servidor Tika és obligatòria.",
"Tiktoken": "Tiktoken",
@@ -944,7 +958,7 @@
"To access the GGUF models available for downloading,": "Per accedir als models GGUF disponibles per descarregar,",
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Per accedir a la WebUI, poseu-vos en contacte amb l'administrador. Els administradors poden gestionar els estats dels usuaris des del tauler d'administració.",
"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "Per adjuntar la base de coneixement aquí, afegiu-la primer a l'espai de treball \"Coneixement\".",
"To learn more about available endpoints, visit our documentation.": "",
"To learn more about available endpoints, visit our documentation.": "Per obtenir més informació sobre els punts d'accés disponibles, visiteu la nostra documentació.",
"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "Per protegir la privadesa, només es comparteixen puntuacions, identificadors de models, etiquetes i metadades dels comentaris; els registres de xat romanen privats i no s'inclouen.",
"To select actions here, add them to the \"Functions\" workspace first.": "Per seleccionar accions aquí, afegeix-les primer a l'espai de treball \"Funcions\".",
"To select filters here, add them to the \"Functions\" workspace first.": "Per seleccionar filtres aquí, afegeix-los primer a l'espai de treball \"Funcions\".",
@@ -966,6 +980,7 @@
"Tools": "Eines",
"Tools Access": "Accés a les eines",
"Tools are a function calling system with arbitrary code execution": "Les eines són un sistema de crida a funcions amb execució de codi arbitrari",
"Tools Function Calling Prompt": "",
"Tools have a function calling system that allows arbitrary code execution": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari",
"Tools have a function calling system that allows arbitrary code execution.": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari.",
"Top K": "Top K",
@@ -1023,7 +1038,7 @@
"variable to have them replaced with clipboard content.": "variable per tenir-les reemplaçades amb el contingut del porta-retalls.",
"Version": "Versió",
"Version {{selectedVersion}} of {{totalVersions}}": "Versió {{selectedVersion}} de {{totalVersions}}",
"View Replies": "",
"View Replies": "Veure les respostes",
"Visibility": "Visibilitat",
"Voice": "Veu",
"Voice Input": "Entrada de veu",
@@ -1036,6 +1051,7 @@
"Web Loader Settings": "Preferències del carregador web",
"Web Search": "Cerca la web",
"Web Search Engine": "Motor de cerca de la web",
"Web Search in Chat": "",
"Web Search Query Generation": "Generació de consultes per a la cerca de la web",
"Webhook URL": "URL del webhook",
"WebUI Settings": "Preferències de WebUI",
@@ -1054,7 +1070,7 @@
"Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9)": "Funciona juntament amb top-k. Un valor més alt (p. ex., 0,95) donarà lloc a un text més divers, mentre que un valor més baix (p. ex., 0,5) generarà un text més concentrat i conservador. (Per defecte: 0,9)",
"Workspace": "Espai de treball",
"Workspace Permissions": "Permisos de l'espai de treball",
"Write": "",
"Write": "Escriure",
"Write a prompt suggestion (e.g. Who are you?)": "Escriu una suggerència d'indicació (p. ex. Qui ets?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "Escriu un resum en 50 paraules que resumeixi [tema o paraula clau].",
"Write something...": "Escriu quelcom...",
@@ -1064,7 +1080,8 @@
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Només pots xatejar amb un màxim de {{maxCount}} fitxers alhora.",
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Pots personalitzar les teves interaccions amb els models de llenguatge afegint memòries mitjançant el botó 'Gestiona' que hi ha a continuació, fent-les més útils i adaptades a tu.",
"You cannot upload an empty file.": "No es pot pujar un ariux buit.",
"You do not have permission to access this feature.": "",
"You do not have permission to access this feature.": "No tens permís per accedir a aquesta funcionalitat",
"You do not have permission to upload files": "",
"You do not have permission to upload files.": "No tens permisos per pujar arxius.",
"You have no archived conversations.": "No tens converses arxivades.",
"You have shared this chat": "Has compartit aquest xat",
+23 -6
View File
@@ -63,8 +63,11 @@
"Allowed Endpoints": "",
"Already have an account?": "Naa na kay account ?",
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
"Always": "",
"Amazing": "",
"an assistant": "usa ka katabang",
"Analyzed": "",
"Analyzing...": "",
"and": "Ug",
"and {{COUNT}} more": "",
"and create a new shared link.": "",
@@ -169,9 +172,11 @@
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
"Clone": "",
"Clone Chat": "",
"Clone of {{TITLE}}": "",
"Close": "Suod nga",
"Code execution": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Collection": "Koleksyon",
"Color": "",
"ComfyUI": "",
@@ -189,6 +194,7 @@
"Confirm your action": "",
"Confirm your new password": "",
"Connections": "Mga koneksyon",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
"Contact Admin for WebUI Access": "",
"Content": "Kontento",
"Content Extraction": "",
@@ -232,6 +238,7 @@
"Default": "Pinaagi sa default",
"Default (Open AI)": "",
"Default (SentenceTransformers)": "",
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
"Default Model": "",
"Default model updated": "Gi-update nga default template",
"Default Models": "",
@@ -342,6 +349,7 @@
"Enter Chunk Overlap": "Pagsulod sa block overlap",
"Enter Chunk Size": "Isulod ang block size",
"Enter description": "",
"Enter Exa API Key": "",
"Enter Github Raw URL": "",
"Enter Google PSE API Key": "",
"Enter Google PSE Engine Id": "",
@@ -354,6 +362,7 @@
"Enter Mojeek Search API Key": "",
"Enter Number of Steps (e.g. 50)": "Pagsulod sa gidaghanon sa mga lakang (e.g. 50)",
"Enter proxy URL (e.g. https://user:password@host:port)": "",
"Enter reasoning effort": "",
"Enter Sampler (e.g. Euler a)": "",
"Enter Scheduler (e.g. Karras)": "",
"Enter Score": "",
@@ -389,12 +398,14 @@
"Error accessing Google Drive: {{error}}": "",
"Error uploading file: {{error}}": "",
"Evaluations": "",
"Exa API Key": "",
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
"Example: ALL": "",
"Example: mail": "",
"Example: ou=users,dc=foo,dc=example": "",
"Example: sAMAccountName or uid or userPrincipalName": "",
"Exclude": "",
"Execute code for analysis": "",
"Experimental": "Eksperimento",
"Explore the cosmos": "",
"Export": "",
@@ -412,6 +423,7 @@
"External Models": "",
"Failed to add file.": "",
"Failed to create API Key.": "",
"Failed to fetch models": "",
"Failed to read clipboard contents": "Napakyas sa pagbasa sa sulod sa clipboard",
"Failed to save models configuration": "",
"Failed to update settings": "",
@@ -446,6 +458,7 @@
"Format your variables using brackets like this:": "",
"Frequency Penalty": "",
"Function": "",
"Function Calling": "",
"Function created successfully": "",
"Function deleted successfully": "",
"Function Description": "",
@@ -460,6 +473,7 @@
"Functions imported successfully": "",
"General": "Heneral",
"General Settings": "kinatibuk-ang mga setting",
"Generate an image": "",
"Generate Image": "",
"Generating search query": "",
"Get started": "",
@@ -475,7 +489,6 @@
"Group Name": "",
"Group updated successfully": "",
"Groups": "",
"h:mm a": "",
"Haptic Feedback": "",
"has no conversations.": "",
"Hello, {{name}}": "Maayong buntag, {{name}}",
@@ -493,7 +506,6 @@
"Ignite curiosity": "",
"Image": "",
"Image Compression": "",
"Image generation": "",
"Image Generation": "",
"Image Generation (Experimental)": "Pagmugna og hulagway (Eksperimento)",
"Image Generation Engine": "Makina sa paghimo og imahe",
@@ -592,9 +604,6 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"MMMM DD, YYYY HH:mm": "",
"MMMM DD, YYYY hh:mm:ss A": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "Ang modelo'{{modelName}}' malampuson nga na-download.",
"Model '{{modelTag}}' is already in queue for downloading.": "Ang modelo'{{modelTag}}' naa na sa pila para ma-download.",
@@ -621,6 +630,7 @@
"More": "",
"Name": "Ngalan",
"Name your knowledge base": "",
"Native": "",
"New Chat": "Bag-ong diskusyon",
"New Folder": "",
"New Password": "Bag-ong Password",
@@ -715,6 +725,7 @@
"Please enter a prompt": "",
"Please fill in all fields.": "",
"Please select a model first.": "",
"Please select a model.": "",
"Please select a reason": "",
"Port": "",
"Positive attitude": "",
@@ -723,6 +734,7 @@
"Previous 30 days": "",
"Previous 7 days": "",
"Profile Image": "",
"Prompt": "",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
"Prompt Content": "Ang sulod sa prompt",
"Prompt created successfully": "",
@@ -740,6 +752,7 @@
"Re-rank models by topic similarity": "",
"Read": "",
"Read Aloud": "",
"Reasoning Effort": "",
"Record voice": "Irekord ang tingog",
"Redirecting you to OpenWebUI Community": "Gi-redirect ka sa komunidad sa OpenWebUI",
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
@@ -797,7 +810,7 @@
"Search options": "",
"Search Prompts": "Pangitaa ang mga prompt",
"Search Result Count": "",
"Search the web": "",
"Search the internet": "",
"Search Tools": "",
"SearchApi API Key": "",
"SearchApi Engine": "",
@@ -930,6 +943,7 @@
"This will delete all models including custom models and cannot be undone.": "",
"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
"Thorough explanation": "",
"Thought for {{DURATION}}": "",
"Tika": "",
"Tika Server URL required.": "",
"Tiktoken": "",
@@ -966,6 +980,7 @@
"Tools": "",
"Tools Access": "",
"Tools are a function calling system with arbitrary code execution": "",
"Tools Function Calling Prompt": "",
"Tools have a function calling system that allows arbitrary code execution": "",
"Tools have a function calling system that allows arbitrary code execution.": "",
"Top K": "Top K",
@@ -1036,6 +1051,7 @@
"Web Loader Settings": "",
"Web Search": "",
"Web Search Engine": "",
"Web Search in Chat": "",
"Web Search Query Generation": "",
"Webhook URL": "",
"WebUI Settings": "Mga Setting sa WebUI",
@@ -1065,6 +1081,7 @@
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
"You cannot upload an empty file.": "",
"You do not have permission to access this feature.": "",
"You do not have permission to upload files": "",
"You do not have permission to upload files.": "",
"You have no archived conversations.": "",
"You have shared this chat": "",
+23 -6
View File
@@ -63,8 +63,11 @@
"Allowed Endpoints": "",
"Already have an account?": "Už máte účet?",
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
"Always": "",
"Amazing": "",
"an assistant": "asistent",
"Analyzed": "",
"Analyzing...": "",
"and": "a",
"and {{COUNT}} more": "a {{COUNT}} další/ch",
"and create a new shared link.": "a vytvořit nový sdílený odkaz.",
@@ -169,9 +172,11 @@
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Přístup k zápisu do schránky byl zamítnut. Prosím, zkontrolujte nastavení svého prohlížeče a udělte potřebný přístup.",
"Clone": "Klonovat",
"Clone Chat": "",
"Clone of {{TITLE}}": "",
"Close": "Zavřít",
"Code execution": "Provádění kódu",
"Code formatted successfully": "Kód byl úspěšně naformátován.",
"Code Interpreter": "",
"Collection": "",
"Color": "Barva",
"ComfyUI": "ComfyUI.",
@@ -189,6 +194,7 @@
"Confirm your action": "Potvrďte svoji akci",
"Confirm your new password": "",
"Connections": "Připojení",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
"Contact Admin for WebUI Access": "Kontaktujte administrátora pro přístup k webovému rozhraní.",
"Content": "Obsah",
"Content Extraction": "Extrahování obsahu",
@@ -232,6 +238,7 @@
"Default": "Výchozí hodnoty nebo nastavení.",
"Default (Open AI)": "Výchozí (Open AI)",
"Default (SentenceTransformers)": "Výchozí (SentenceTransformers)",
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
"Default Model": "Výchozí model",
"Default model updated": "Výchozí model aktualizován.",
"Default Models": "Výchozí modely",
@@ -342,6 +349,7 @@
"Enter Chunk Overlap": "Zadejte překryv části",
"Enter Chunk Size": "Zadejte velikost bloku",
"Enter description": "Zadejte popis",
"Enter Exa API Key": "",
"Enter Github Raw URL": "Zadejte URL adresu Github Raw",
"Enter Google PSE API Key": "Zadejte klíč rozhraní API Google PSE",
"Enter Google PSE Engine Id": "Zadejte ID vyhledávacího mechanismu Google PSE",
@@ -354,6 +362,7 @@
"Enter Mojeek Search API Key": "",
"Enter Number of Steps (e.g. 50)": "Zadejte počet kroků (např. 50)",
"Enter proxy URL (e.g. https://user:password@host:port)": "",
"Enter reasoning effort": "",
"Enter Sampler (e.g. Euler a)": "Zadejte vzorkovač (např. Euler a)",
"Enter Scheduler (e.g. Karras)": "Zadejte plánovač (např. Karras)",
"Enter Score": "Zadejte skóre",
@@ -389,12 +398,14 @@
"Error accessing Google Drive: {{error}}": "",
"Error uploading file: {{error}}": "",
"Evaluations": "Hodnocení",
"Exa API Key": "",
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
"Example: ALL": "",
"Example: mail": "",
"Example: ou=users,dc=foo,dc=example": "",
"Example: sAMAccountName or uid or userPrincipalName": "",
"Exclude": "Vyloučit",
"Execute code for analysis": "",
"Experimental": "Experimentální",
"Explore the cosmos": "",
"Export": "Exportovat",
@@ -412,6 +423,7 @@
"External Models": "Externí modely",
"Failed to add file.": "Nepodařilo se přidat soubor.",
"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 models configuration": "",
"Failed to update settings": "Nepodařilo se aktualizovat nastavení",
@@ -446,6 +458,7 @@
"Format your variables using brackets like this:": "Formátujte své proměnné pomocí závorek takto:",
"Frequency Penalty": "Penalizace frekvence",
"Function": "Funkce",
"Function Calling": "",
"Function created successfully": "Funkce byla úspěšně vytvořena.",
"Function deleted successfully": "Funkce byla úspěšně odstraněna",
"Function Description": "",
@@ -460,6 +473,7 @@
"Functions imported successfully": "Funkce byly úspěšně importovány",
"General": "Obecný",
"General Settings": "Obecná nastavení",
"Generate an image": "",
"Generate Image": "Vygenerovat obrázek",
"Generating search query": "Generování vyhledávacího dotazu",
"Get started": "",
@@ -475,7 +489,6 @@
"Group Name": "",
"Group updated successfully": "",
"Groups": "",
"h:mm a": "hh:mm dop./odp.",
"Haptic Feedback": "Haptická zpětná vazba",
"has no conversations.": "nemá žádné konverzace.",
"Hello, {{name}}": "Ahoj, {{name}}",
@@ -493,7 +506,6 @@
"Ignite curiosity": "",
"Image": "",
"Image Compression": "",
"Image generation": "",
"Image Generation": "",
"Image Generation (Experimental)": "Generování obrázků (experimentální)",
"Image Generation Engine": "Engine pro generování obrázků",
@@ -592,9 +604,6 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, RRRR",
"MMMM DD, YYYY HH:mm": "MMMM DD, RRRR HH:mm",
"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
"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í.",
@@ -621,6 +630,7 @@
"More": "Více",
"Name": "Jméno",
"Name your knowledge base": "",
"Native": "",
"New Chat": "Nový chat",
"New Folder": "",
"New Password": "Nové heslo",
@@ -715,6 +725,7 @@
"Please enter a prompt": "Prosím, zadejte zadání.",
"Please fill in all fields.": "Prosím, vyplňte všechna pole.",
"Please select a model first.": "",
"Please select a model.": "",
"Please select a reason": "Prosím vyberte důvod",
"Port": "",
"Positive attitude": "Pozitivní přístup",
@@ -723,6 +734,7 @@
"Previous 30 days": "Předchozích 30 dnů",
"Previous 7 days": "Předchozích 7 dní",
"Profile Image": "Profilový obrázek",
"Prompt": "",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (např. Řekni mi zábavný fakt o Římské říši)",
"Prompt Content": "Obsah promptu",
"Prompt created successfully": "",
@@ -740,6 +752,7 @@
"Re-rank models by topic similarity": "Znovu seřaďte modely podle podobnosti témat.",
"Read": "",
"Read Aloud": "Číst nahlas",
"Reasoning Effort": "",
"Record voice": "Nahrát hlas",
"Redirecting you to OpenWebUI Community": "Přesměrování na komunitu OpenWebUI",
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
@@ -797,7 +810,7 @@
"Search options": "",
"Search Prompts": "Vyhledávací dotazy",
"Search Result Count": "Počet výsledků hledání",
"Search the web": "",
"Search the internet": "",
"Search Tools": "Nástroje pro vyhledávání",
"SearchApi API Key": "Klíč API pro SearchApi",
"SearchApi Engine": "Vyhledávací engine API",
@@ -930,6 +943,7 @@
"This will delete all models including custom models and cannot be undone.": "",
"This will reset the knowledge base and sync all files. Do you wish to continue?": "Toto obnoví znalostní databázi a synchronizuje všechny soubory. Přejete si pokračovat?",
"Thorough explanation": "Obsáhlé vysvětlení",
"Thought for {{DURATION}}": "",
"Tika": "Tika",
"Tika Server URL required.": "Je vyžadována URL adresa serveru Tika.",
"Tiktoken": "Tiktoken",
@@ -966,6 +980,7 @@
"Tools": "Nástroje",
"Tools Access": "",
"Tools are a function calling system with arbitrary code execution": "Nástroje jsou systémem pro volání funkcí s vykonáváním libovolného kódu.",
"Tools Function Calling Prompt": "",
"Tools have a function calling system that allows arbitrary code execution": "Nástroje mají systém volání funkcí, který umožňuje libovolné spouštění kódu.",
"Tools have a function calling system that allows arbitrary code execution.": "Nástroje mají systém volání funkcí, který umožňuje spuštění libovolného kódu.",
"Top K": "Top K",
@@ -1036,6 +1051,7 @@
"Web Loader Settings": "Nastavení Web Loaderu",
"Web Search": "Vyhledávání na webu",
"Web Search Engine": "Webový vyhledávač",
"Web Search in Chat": "",
"Web Search Query Generation": "",
"Webhook URL": "Webhook URL",
"WebUI Settings": "Nastavení WebUI",
@@ -1065,6 +1081,7 @@
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Můžete personalizovat své interakce s LLM pomocí přidávání vzpomínek prostřednictvím tlačítka 'Spravovat' níže, což je učiní pro vás užitečnějšími a lépe přizpůsobenými.",
"You cannot upload an empty file.": "Nemůžete nahrát prázdný soubor.",
"You do not have permission to access this feature.": "",
"You do not have permission to upload files": "",
"You do not have permission to upload files.": "",
"You have no archived conversations.": "Nemáte žádné archivované konverzace.",
"You have shared this chat": "Sdíleli jste tento chat.",
+23 -6
View File
@@ -63,8 +63,11 @@
"Allowed Endpoints": "",
"Already have an account?": "Har du allerede en profil?",
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
"Always": "",
"Amazing": "",
"an assistant": "en assistent",
"Analyzed": "",
"Analyzing...": "",
"and": "og",
"and {{COUNT}} more": "",
"and create a new shared link.": "og lav et nyt link til deling",
@@ -169,9 +172,11 @@
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Skriveadgang til udklipsholderen ikke tilladt. Tjek venligst indstillingerne i din browser for at give adgang.",
"Clone": "Klon",
"Clone Chat": "",
"Clone of {{TITLE}}": "",
"Close": "Luk",
"Code execution": "",
"Code formatted successfully": "Kode formateret korrekt",
"Code Interpreter": "",
"Collection": "Samling",
"Color": "",
"ComfyUI": "ComfyUI",
@@ -189,6 +194,7 @@
"Confirm your action": "Bekræft din handling",
"Confirm your new password": "",
"Connections": "Forbindelser",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
"Contact Admin for WebUI Access": "Kontakt din administrator for adgang til WebUI",
"Content": "Indhold",
"Content Extraction": "Udtræk af indhold",
@@ -232,6 +238,7 @@
"Default": "Standard",
"Default (Open AI)": "Standard (Open AI)",
"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
"Default Model": "Standard model",
"Default model updated": "Standard model opdateret",
"Default Models": "",
@@ -342,6 +349,7 @@
"Enter Chunk Overlap": "Indtast overlapning af tekststykker",
"Enter Chunk Size": "Indtast størrelse af tekststykker",
"Enter description": "",
"Enter Exa API Key": "",
"Enter Github Raw URL": "Indtast Github Raw URL",
"Enter Google PSE API Key": "Indtast Google PSE API-nøgle",
"Enter Google PSE Engine Id": "Indtast Google PSE Engine ID",
@@ -354,6 +362,7 @@
"Enter Mojeek Search API Key": "",
"Enter Number of Steps (e.g. 50)": "Indtast antal trin (f.eks. 50)",
"Enter proxy URL (e.g. https://user:password@host:port)": "",
"Enter reasoning effort": "",
"Enter Sampler (e.g. Euler a)": "Indtast sampler (f.eks. Euler a)",
"Enter Scheduler (e.g. Karras)": "Indtast scheduler (f.eks. Karras)",
"Enter Score": "Indtast score",
@@ -389,12 +398,14 @@
"Error accessing Google Drive: {{error}}": "",
"Error uploading file: {{error}}": "",
"Evaluations": "",
"Exa API Key": "",
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
"Example: ALL": "",
"Example: mail": "",
"Example: ou=users,dc=foo,dc=example": "",
"Example: sAMAccountName or uid or userPrincipalName": "",
"Exclude": "",
"Execute code for analysis": "",
"Experimental": "Eksperimentel",
"Explore the cosmos": "",
"Export": "Eksportér",
@@ -412,6 +423,7 @@
"External Models": "Eksterne modeller",
"Failed to add file.": "",
"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 models configuration": "",
"Failed to update settings": "Kunne ikke opdatere indstillinger",
@@ -446,6 +458,7 @@
"Format your variables using brackets like this:": "",
"Frequency Penalty": "Hyppighedsstraf",
"Function": "",
"Function Calling": "",
"Function created successfully": "Funktion oprettet.",
"Function deleted successfully": "Funktion slettet.",
"Function Description": "",
@@ -460,6 +473,7 @@
"Functions imported successfully": "Funktioner importeret.",
"General": "Generelt",
"General Settings": "Generelle indstillinger",
"Generate an image": "",
"Generate Image": "Generer billede",
"Generating search query": "Genererer søgeforespørgsel",
"Get started": "",
@@ -475,7 +489,6 @@
"Group Name": "",
"Group updated successfully": "",
"Groups": "",
"h:mm a": "h:mm a",
"Haptic Feedback": "Haptisk feedback",
"has no conversations.": "har ingen samtaler.",
"Hello, {{name}}": "Hej {{name}}",
@@ -493,7 +506,6 @@
"Ignite curiosity": "",
"Image": "",
"Image Compression": "",
"Image generation": "",
"Image Generation": "",
"Image Generation (Experimental)": "Billedgenerering (eksperimentel)",
"Image Generation Engine": "Billedgenereringsengine",
@@ -592,9 +604,6 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' er blevet downloadet.",
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' er allerede i kø til download.",
@@ -621,6 +630,7 @@
"More": "Mere",
"Name": "Navn",
"Name your knowledge base": "",
"Native": "",
"New Chat": "Ny chat",
"New Folder": "",
"New Password": "Ny adgangskode",
@@ -715,6 +725,7 @@
"Please enter a prompt": "",
"Please fill in all fields.": "Udfyld alle felter.",
"Please select a model first.": "",
"Please select a model.": "",
"Please select a reason": "Vælg en årsag",
"Port": "",
"Positive attitude": "Positiv holdning",
@@ -723,6 +734,7 @@
"Previous 30 days": "Seneste 30 dage",
"Previous 7 days": "Seneste 7 dage",
"Profile Image": "Profilbillede",
"Prompt": "",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (f.eks. Fortæl mig en sjov kendsgerning om Romerriget)",
"Prompt Content": "Promptindhold",
"Prompt created successfully": "",
@@ -740,6 +752,7 @@
"Re-rank models by topic similarity": "",
"Read": "",
"Read Aloud": "Læs højt",
"Reasoning Effort": "",
"Record voice": "Optag stemme",
"Redirecting you to OpenWebUI Community": "Omdirigerer dig til OpenWebUI Community",
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
@@ -797,7 +810,7 @@
"Search options": "",
"Search Prompts": "Søg i prompts",
"Search Result Count": "Antal søgeresultater",
"Search the web": "",
"Search the internet": "",
"Search Tools": "Søg i værktøjer",
"SearchApi API Key": "SearchApi API-nøgle",
"SearchApi Engine": "SearchApi-engine",
@@ -930,6 +943,7 @@
"This will delete all models including custom models and cannot be undone.": "",
"This will reset the knowledge base and sync all files. Do you wish to continue?": "Dette vil nulstille vidensbasen og synkronisere alle filer. Vil du fortsætte?",
"Thorough explanation": "Grundig forklaring",
"Thought for {{DURATION}}": "",
"Tika": "Tika",
"Tika Server URL required.": "Tika-server-URL påkrævet.",
"Tiktoken": "",
@@ -966,6 +980,7 @@
"Tools": "Værktøjer",
"Tools Access": "",
"Tools are a function calling system with arbitrary code execution": "Værktøjer er et funktionkaldssystem med vilkårlig kodeudførelse",
"Tools Function Calling Prompt": "",
"Tools have a function calling system that allows arbitrary code execution": "Værktøjer har et funktionkaldssystem, der tillader vilkårlig kodeudførelse",
"Tools have a function calling system that allows arbitrary code execution.": "Værktøjer har et funktionkaldssystem, der tillader vilkårlig kodeudførelse.",
"Top K": "Top K",
@@ -1036,6 +1051,7 @@
"Web Loader Settings": "Web Loader-indstillinger",
"Web Search": "Websøgning",
"Web Search Engine": "Websøgemaskine",
"Web Search in Chat": "",
"Web Search Query Generation": "",
"Webhook URL": "Webhook-URL",
"WebUI Settings": "WebUI-indstillinger",
@@ -1065,6 +1081,7 @@
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Du kan personliggøre dine interaktioner med LLM'er ved at tilføje minder via knappen 'Administrer' nedenfor, hvilket gør dem mere nyttige og skræddersyet til dig.",
"You cannot upload an empty file.": "",
"You do not have permission to access this feature.": "",
"You do not have permission to upload files": "",
"You do not have permission to upload files.": "",
"You have no archived conversations.": "Du har ingen arkiverede samtaler.",
"You have shared this chat": "Du har delt denne chat",
+23 -6
View File
@@ -63,8 +63,11 @@
"Allowed Endpoints": "",
"Already have an account?": "Haben Sie bereits einen Account?",
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "Alternative zu top_p und zielt darauf ab, ein Gleichgewicht zwischen Qualität und Vielfalt zu gewährleisten. Der Parameter p repräsentiert die Mindestwahrscheinlichkeit für ein Token, um berücksichtigt zu werden, relativ zur Wahrscheinlichkeit des wahrscheinlichsten Tokens. Zum Beispiel, bei p=0.05 und das wahrscheinlichste Token hat eine Wahrscheinlichkeit von 0.9, werden Logits mit einem Wert von weniger als 0.045 herausgefiltert. (Standard: 0.0)",
"Always": "",
"Amazing": "Fantastisch",
"an assistant": "ein Assistent",
"Analyzed": "",
"Analyzing...": "",
"and": "und",
"and {{COUNT}} more": "und {{COUNT}} mehr",
"and create a new shared link.": "und erstellen Sie einen neuen freigegebenen Link.",
@@ -169,9 +172,11 @@
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Schreibberechtigung für die Zwischenablage verweigert. Bitte überprüfen Sie Ihre Browsereinstellungen, um den erforderlichen Zugriff zu erlauben.",
"Clone": "Klonen",
"Clone Chat": "",
"Clone of {{TITLE}}": "",
"Close": "Schließen",
"Code execution": "Codeausführung",
"Code formatted successfully": "Code erfolgreich formatiert",
"Code Interpreter": "",
"Collection": "Kollektion",
"Color": "Farbe",
"ComfyUI": "ComfyUI",
@@ -189,6 +194,7 @@
"Confirm your action": "Bestätigen Sie Ihre Aktion.",
"Confirm your new password": "",
"Connections": "Verbindungen",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
"Contact Admin for WebUI Access": "Kontaktieren Sie den Administrator für den Zugriff auf die Weboberfläche",
"Content": "Info",
"Content Extraction": "Inhaltsextraktion",
@@ -232,6 +238,7 @@
"Default": "Standard",
"Default (Open AI)": "Standard (Open AI)",
"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
"Default Model": "Standardmodell",
"Default model updated": "Standardmodell aktualisiert",
"Default Models": "",
@@ -342,6 +349,7 @@
"Enter Chunk Overlap": "Geben Sie die Blocküberlappung ein",
"Enter Chunk Size": "Geben Sie die Blockgröße ein",
"Enter description": "Geben Sie eine Beschreibung ein",
"Enter Exa API Key": "",
"Enter Github Raw URL": "Geben Sie die Github Raw-URL ein",
"Enter Google PSE API Key": "Geben Sie den Google PSE-API-Schlüssel ein",
"Enter Google PSE Engine Id": "Geben Sie die Google PSE-Engine-ID ein",
@@ -354,6 +362,7 @@
"Enter Mojeek Search API Key": "Geben Sie den Mojeek Search API-Schlüssel ein",
"Enter Number of Steps (e.g. 50)": "Geben Sie die Anzahl an Schritten ein (z. B. 50)",
"Enter proxy URL (e.g. https://user:password@host:port)": "",
"Enter reasoning effort": "",
"Enter Sampler (e.g. Euler a)": "Geben Sie den Sampler ein (z. B. Euler a)",
"Enter Scheduler (e.g. Karras)": "Geben Sie den Scheduler ein (z. B. Karras)",
"Enter Score": "Punktzahl eingeben",
@@ -389,12 +398,14 @@
"Error accessing Google Drive: {{error}}": "",
"Error uploading file: {{error}}": "",
"Evaluations": "Evaluationen",
"Exa API Key": "",
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Beispiel: (&(objectClass=inetOrgPerson)(uid=%s))",
"Example: ALL": "Beispiel: ALL",
"Example: mail": "",
"Example: ou=users,dc=foo,dc=example": "Beispiel: ou=users,dc=foo,dc=example",
"Example: sAMAccountName or uid or userPrincipalName": "Beispiel: sAMAccountName or uid or userPrincipalName",
"Exclude": "Ausschließen",
"Execute code for analysis": "",
"Experimental": "Experimentell",
"Explore the cosmos": "Erforschen Sie das Universum",
"Export": "Exportieren",
@@ -412,6 +423,7 @@
"External Models": "Externe Modelle",
"Failed to add file.": "Fehler beim Hinzufügen der Datei.",
"Failed to create API Key.": "Fehler beim Erstellen des API-Schlüssels.",
"Failed to fetch models": "",
"Failed to read clipboard contents": "Fehler beim Abruf der Zwischenablage",
"Failed to save models configuration": "",
"Failed to update settings": "Fehler beim Aktualisieren der Einstellungen",
@@ -446,6 +458,7 @@
"Format your variables using brackets like this:": "Formatieren Sie Ihre Variablen mit Klammern, wie hier:",
"Frequency Penalty": "Frequenzstrafe",
"Function": "Funktion",
"Function Calling": "",
"Function created successfully": "Funktion erfolgreich erstellt",
"Function deleted successfully": "Funktion erfolgreich gelöscht",
"Function Description": "Funktionsbeschreibung",
@@ -460,6 +473,7 @@
"Functions imported successfully": "Funktionen erfolgreich importiert",
"General": "Allgemein",
"General Settings": "Allgemeine Einstellungen",
"Generate an image": "",
"Generate Image": "Bild erzeugen",
"Generating search query": "Suchanfrage wird erstellt",
"Get started": "Loslegen",
@@ -475,7 +489,6 @@
"Group Name": "Gruppenname",
"Group updated successfully": "Gruppe erfolgreich aktualisiert",
"Groups": "Gruppen",
"h:mm a": "h:mm a",
"Haptic Feedback": "Haptisches Feedback",
"has no conversations.": "hat keine Unterhaltungen.",
"Hello, {{name}}": "Hallo, {{name}}",
@@ -493,7 +506,6 @@
"Ignite curiosity": "Neugier entfachen",
"Image": "",
"Image Compression": "",
"Image generation": "",
"Image Generation": "",
"Image Generation (Experimental)": "Bildgenerierung (experimentell)",
"Image Generation Engine": "Bildgenerierungs-Engine",
@@ -592,9 +604,6 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "DD MMMM YYYY",
"MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm",
"MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY HH:mm A",
"Model": "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.",
@@ -621,6 +630,7 @@
"More": "Mehr",
"Name": "Name",
"Name your knowledge base": "Benennen Sie Ihren Wissensspeicher",
"Native": "",
"New Chat": "Neue Unterhaltung",
"New Folder": "",
"New Password": "Neues Passwort",
@@ -715,6 +725,7 @@
"Please enter a prompt": "Bitte geben Sie einen Prompt ein",
"Please fill in all fields.": "Bitte füllen Sie alle Felder aus.",
"Please select a model first.": "",
"Please select a model.": "",
"Please select a reason": "Bitte wählen Sie einen Grund aus",
"Port": "Port",
"Positive attitude": "Positive Einstellung",
@@ -723,6 +734,7 @@
"Previous 30 days": "Vorherige 30 Tage",
"Previous 7 days": "Vorherige 7 Tage",
"Profile Image": "Profilbild",
"Prompt": "",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (z. B. \"Erzähle mir eine interessante Tatsache über das Römische Reich\")",
"Prompt Content": "Prompt-Inhalt",
"Prompt created successfully": "Prompt erfolgreich erstellt",
@@ -740,6 +752,7 @@
"Re-rank models by topic similarity": "Modelle nach thematischer Ähnlichkeit neu ordnen",
"Read": "",
"Read Aloud": "Vorlesen",
"Reasoning Effort": "",
"Record voice": "Stimme aufnehmen",
"Redirecting you to OpenWebUI Community": "Sie werden zur OpenWebUI-Community weitergeleitet",
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "Reduziert die Wahrscheinlichkeit, Unsinn zu generieren. Ein höherer Wert (z.B. 100) liefert vielfältigere Antworten, während ein niedrigerer Wert (z.B. 10) konservativer ist. (Standard: 40)",
@@ -797,7 +810,7 @@
"Search options": "Suchoptionen",
"Search Prompts": "Prompts durchsuchen...",
"Search Result Count": "Anzahl der Suchergebnisse",
"Search the web": "Im Web suchen",
"Search the internet": "",
"Search Tools": "Werkzeuge durchsuchen...",
"SearchApi API Key": "SearchApi-API-Schlüssel",
"SearchApi Engine": "SearchApi-Engine",
@@ -930,6 +943,7 @@
"This will delete all models including custom models and cannot be undone.": "Dies wird alle Modelle einschließlich benutzerdefinierter Modelle löschen und kann nicht rückgängig gemacht werden.",
"This will reset the knowledge base and sync all files. Do you wish to continue?": "Dadurch wird die Wissensdatenbank zurückgesetzt und alle Dateien synchronisiert. Möchten Sie fortfahren?",
"Thorough explanation": "Ausführliche Erklärung",
"Thought for {{DURATION}}": "",
"Tika": "Tika",
"Tika Server URL required.": "Tika-Server-URL erforderlich.",
"Tiktoken": "Tiktoken",
@@ -966,6 +980,7 @@
"Tools": "Werkzeuge",
"Tools Access": "Werkzeugzugriff",
"Tools are a function calling system with arbitrary code execution": "Wekzeuge sind ein Funktionssystem mit beliebiger Codeausführung",
"Tools Function Calling Prompt": "",
"Tools have a function calling system that allows arbitrary code execution": "Werkezuge verfügen über ein Funktionssystem, das die Ausführung beliebigen Codes ermöglicht",
"Tools have a function calling system that allows arbitrary code execution.": "Werkzeuge verfügen über ein Funktionssystem, das die Ausführung beliebigen Codes ermöglicht.",
"Top K": "Top K",
@@ -1036,6 +1051,7 @@
"Web Loader Settings": "Web Loader Einstellungen",
"Web Search": "Websuche",
"Web Search Engine": "Suchmaschine",
"Web Search in Chat": "",
"Web Search Query Generation": "",
"Webhook URL": "Webhook URL",
"WebUI Settings": "WebUI-Einstellungen",
@@ -1065,6 +1081,7 @@
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Personalisieren Sie Interaktionen mit LLMs, indem Sie über die Schaltfläche \"Verwalten\" Erinnerungen hinzufügen.",
"You cannot upload an empty file.": "Sie können keine leere Datei hochladen.",
"You do not have permission to access this feature.": "",
"You do not have permission to upload files": "",
"You do not have permission to upload files.": "Sie haben keine Berechtigung zum Hochladen von Dateien.",
"You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.",
"You have shared this chat": "Sie haben diese Unterhaltung geteilt",
+23 -6
View File
@@ -63,8 +63,11 @@
"Allowed Endpoints": "",
"Already have an account?": "Such account exists?",
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
"Always": "",
"Amazing": "",
"an assistant": "such assistant",
"Analyzed": "",
"Analyzing...": "",
"and": "and",
"and {{COUNT}} more": "",
"and create a new shared link.": "",
@@ -169,9 +172,11 @@
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
"Clone": "",
"Clone Chat": "",
"Clone of {{TITLE}}": "",
"Close": "Close",
"Code execution": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Collection": "Collection",
"Color": "",
"ComfyUI": "",
@@ -189,6 +194,7 @@
"Confirm your action": "",
"Confirm your new password": "",
"Connections": "Connections",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
"Contact Admin for WebUI Access": "",
"Content": "Content",
"Content Extraction": "",
@@ -232,6 +238,7 @@
"Default": "Default",
"Default (Open AI)": "",
"Default (SentenceTransformers)": "",
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
"Default Model": "",
"Default model updated": "Default model much updated",
"Default Models": "",
@@ -342,6 +349,7 @@
"Enter Chunk Overlap": "Enter Overlap of Chunks",
"Enter Chunk Size": "Enter Size of Chunk",
"Enter description": "",
"Enter Exa API Key": "",
"Enter Github Raw URL": "",
"Enter Google PSE API Key": "",
"Enter Google PSE Engine Id": "",
@@ -354,6 +362,7 @@
"Enter Mojeek Search API Key": "",
"Enter Number of Steps (e.g. 50)": "Enter Number of Steps (e.g. 50)",
"Enter proxy URL (e.g. https://user:password@host:port)": "",
"Enter reasoning effort": "",
"Enter Sampler (e.g. Euler a)": "",
"Enter Scheduler (e.g. Karras)": "",
"Enter Score": "",
@@ -389,12 +398,14 @@
"Error accessing Google Drive: {{error}}": "",
"Error uploading file: {{error}}": "",
"Evaluations": "",
"Exa API Key": "",
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
"Example: ALL": "",
"Example: mail": "",
"Example: ou=users,dc=foo,dc=example": "",
"Example: sAMAccountName or uid or userPrincipalName": "",
"Exclude": "",
"Execute code for analysis": "",
"Experimental": "Much Experiment",
"Explore the cosmos": "",
"Export": "",
@@ -412,6 +423,7 @@
"External Models": "",
"Failed to add file.": "",
"Failed to create API Key.": "",
"Failed to fetch models": "",
"Failed to read clipboard contents": "Failed to read clipboard borks",
"Failed to save models configuration": "",
"Failed to update settings": "",
@@ -446,6 +458,7 @@
"Format your variables using brackets like this:": "",
"Frequency Penalty": "",
"Function": "",
"Function Calling": "",
"Function created successfully": "",
"Function deleted successfully": "",
"Function Description": "",
@@ -460,6 +473,7 @@
"Functions imported successfully": "",
"General": "Woweral",
"General Settings": "General Doge Settings",
"Generate an image": "",
"Generate Image": "",
"Generating search query": "",
"Get started": "",
@@ -475,7 +489,6 @@
"Group Name": "",
"Group updated successfully": "",
"Groups": "",
"h:mm a": "",
"Haptic Feedback": "",
"has no conversations.": "",
"Hello, {{name}}": "Much helo, {{name}}",
@@ -493,7 +506,6 @@
"Ignite curiosity": "",
"Image": "",
"Image Compression": "",
"Image generation": "",
"Image Generation": "",
"Image Generation (Experimental)": "Image Wow (Much Experiment)",
"Image Generation Engine": "Image Engine",
@@ -592,9 +604,6 @@
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"MMMM DD, YYYY HH:mm": "",
"MMMM DD, YYYY hh:mm:ss A": "",
"Model": "",
"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' has been successfully downloaded.",
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' is already in queue for downloading.",
@@ -621,6 +630,7 @@
"More": "",
"Name": "Name",
"Name your knowledge base": "",
"Native": "",
"New Chat": "New Bark",
"New Folder": "",
"New Password": "New Barkword",
@@ -715,6 +725,7 @@
"Please enter a prompt": "",
"Please fill in all fields.": "",
"Please select a model first.": "",
"Please select a model.": "",
"Please select a reason": "",
"Port": "",
"Positive attitude": "",
@@ -723,6 +734,7 @@
"Previous 30 days": "",
"Previous 7 days": "",
"Profile Image": "",
"Prompt": "",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
"Prompt Content": "Prompt Content",
"Prompt created successfully": "",
@@ -740,6 +752,7 @@
"Re-rank models by topic similarity": "",
"Read": "",
"Read Aloud": "",
"Reasoning Effort": "",
"Record voice": "Record Bark",
"Redirecting you to OpenWebUI Community": "Redirecting you to OpenWebUI Community",
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
@@ -797,7 +810,7 @@
"Search options": "",
"Search Prompts": "Search Prompts much wow",
"Search Result Count": "",
"Search the web": "",
"Search the internet": "",
"Search Tools": "",
"SearchApi API Key": "",
"SearchApi Engine": "",
@@ -930,6 +943,7 @@
"This will delete all models including custom models and cannot be undone.": "",
"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
"Thorough explanation": "",
"Thought for {{DURATION}}": "",
"Tika": "",
"Tika Server URL required.": "",
"Tiktoken": "",
@@ -966,6 +980,7 @@
"Tools": "",
"Tools Access": "",
"Tools are a function calling system with arbitrary code execution": "",
"Tools Function Calling Prompt": "",
"Tools have a function calling system that allows arbitrary code execution": "",
"Tools have a function calling system that allows arbitrary code execution.": "",
"Top K": "Top K very top",
@@ -1036,6 +1051,7 @@
"Web Loader Settings": "",
"Web Search": "",
"Web Search Engine": "",
"Web Search in Chat": "",
"Web Search Query Generation": "",
"Webhook URL": "",
"WebUI Settings": "WebUI Settings much settings",
@@ -1065,6 +1081,7 @@
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
"You cannot upload an empty file.": "",
"You do not have permission to access this feature.": "",
"You do not have permission to upload files": "",
"You do not have permission to upload files.": "",
"You have no archived conversations.": "",
"You have shared this chat": "",

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