Compare commits

...

186 Commits

Author SHA1 Message Date
Timothy Jaeryang Baek
a9801147b8 Merge pull request #5051 from open-webui/dev
0.3.17
2024-09-04 18:40:16 +02:00
Timothy J. Baek
f3d488fb0c doc: changelog 2024-09-04 18:37:09 +02:00
Timothy J. Baek
28e3701187 Update release-pypi.yml 2024-09-04 18:00:38 +02:00
Timothy J. Baek
8f6369374d refac: error handling 2024-09-04 17:52:59 +02:00
Timothy J. Baek
e5cfa6501b dev6 2024-09-04 17:47:51 +02:00
Timothy J. Baek
175ffc5c66 dev5 2024-09-04 17:33:39 +02:00
Timothy J. Baek
c0441ab2b8 dev4 2024-09-04 17:14:24 +02:00
Timothy J. Baek
6d1bd3ab66 dev4 2024-09-04 17:02:10 +02:00
Timothy J. Baek
1779e6fecc chore: format 2024-09-04 16:58:28 +02:00
Timothy J. Baek
03d5a670f6 refac: mv backend files to /open_webui dir 2024-09-04 16:54:48 +02:00
Timothy J. Baek
76806a998f fix: automatic1111 model update issue 2024-09-04 15:25:31 +02:00
Timothy J. Baek
9bcbf5e9b3 Update config.py 2024-09-03 21:46:40 +02:00
Timothy J. Baek
6bbb755997 feat: import/export config 2024-09-03 21:16:07 +02:00
Timothy Jaeryang Baek
aec7cd572c Merge pull request #5127 from open-webui/dependabot/pip/pip-5bab50f10f
build(deps): bump the pip group across 2 directories with 1 update
2024-09-03 20:17:04 +02:00
dependabot[bot]
856759d350 build(deps): bump the pip group across 2 directories with 1 update
Bumps the pip group with 1 update in the / directory: [flask-cors](https://github.com/corydolphin/flask-cors).
Bumps the pip group with 1 update in the /backend directory: [flask-cors](https://github.com/corydolphin/flask-cors).


Updates `flask-cors` from 4.0.1 to 5.0.0
- [Release notes](https://github.com/corydolphin/flask-cors/releases)
- [Changelog](https://github.com/corydolphin/flask-cors/blob/main/CHANGELOG.md)
- [Commits](https://github.com/corydolphin/flask-cors/compare/4.0.1...5.0.0)

Updates `flask-cors` from 4.0.1 to 5.0.0
- [Release notes](https://github.com/corydolphin/flask-cors/releases)
- [Changelog](https://github.com/corydolphin/flask-cors/blob/main/CHANGELOG.md)
- [Commits](https://github.com/corydolphin/flask-cors/compare/4.0.1...5.0.0)

---
updated-dependencies:
- dependency-name: flask-cors
  dependency-type: direct:production
  dependency-group: pip
- dependency-name: flask-cors
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-03 15:32:52 +00:00
Timothy J. Baek
5f92f7e41f enh: prevent trailing / from comfyui base url
Co-Authored-By: qwertyoriuop <41389168+qwertyoriuop@users.noreply.github.com>
2024-09-03 16:19:34 +02:00
Timothy J. Baek
890824ced4 fix: advanced params not being applied to ollama 2024-09-03 15:43:26 +02:00
Timothy J. Baek
2cd1207e73 fix: mermaid error on rating 2024-09-03 15:39:09 +02:00
Timothy J. Baek
1803c7adeb refac 2024-09-03 15:24:58 +02:00
Timothy J. Baek
fa8bb51b81 refac 2024-09-03 15:23:38 +02:00
Timothy J. Baek
9bea1950dc feat: web search url search param 2024-09-03 15:15:46 +02:00
Timothy Jaeryang Baek
df0bb4b4b0 Merge pull request #5070 from open-webui/dependabot/pip/backend/dev/authlib-1.3.2
build(deps): bump authlib from 1.3.1 to 1.3.2 in /backend
2024-09-03 14:51:30 +02:00
Timothy Jaeryang Baek
fdfa96ba51 Merge pull request #5069 from open-webui/dependabot/pip/backend/dev/duckduckgo-search-approx-eq-6.2.11
build(deps): update duckduckgo-search requirement from ~=6.2.1 to ~=6.2.11 in /backend
2024-09-03 14:51:24 +02:00
Timothy Jaeryang Baek
ff94a8e57f Merge pull request #5068 from open-webui/dependabot/pip/backend/dev/aiohttp-3.10.5
build(deps): bump aiohttp from 3.10.2 to 3.10.5 in /backend
2024-09-03 14:51:17 +02:00
Timothy Jaeryang Baek
41369aefdf Merge pull request #5067 from open-webui/dependabot/pip/backend/dev/langchain-0.2.15
build(deps): bump langchain from 0.2.14 to 0.2.15 in /backend
2024-09-03 14:51:11 +02:00
Timothy Jaeryang Baek
d5ad5c5422 Merge pull request #5066 from open-webui/dependabot/pip/backend/dev/unstructured-0.15.9
build(deps): bump unstructured from 0.15.7 to 0.15.9 in /backend
2024-09-03 14:51:03 +02:00
Timothy Jaeryang Baek
1357237a3e Merge pull request #5076 from cheahjs/fix/websocket-error-object
fix: chat errors when websocket unavailable
2024-09-03 14:50:50 +02:00
Jun Siang Cheah
4946f08671 fix: force socket.io to try websockets first, and explicit fallback to polling 2024-09-01 12:59:49 +01:00
dependabot[bot]
665c851f66 build(deps): bump authlib from 1.3.1 to 1.3.2 in /backend
Bumps [authlib](https://github.com/lepture/authlib) from 1.3.1 to 1.3.2.
- [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.1...v1.3.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 02:56:51 +00:00
dependabot[bot]
115503098e build(deps): update duckduckgo-search requirement in /backend
Updates the requirements on [duckduckgo-search](https://github.com/deedy5/duckduckgo_search) to permit the latest version.
- [Release notes](https://github.com/deedy5/duckduckgo_search/releases)
- [Commits](https://github.com/deedy5/duckduckgo_search/compare/v6.2.1...v6.2.11)

---
updated-dependencies:
- dependency-name: duckduckgo-search
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 02:56:47 +00:00
dependabot[bot]
b7cda48a03 build(deps): bump aiohttp from 3.10.2 to 3.10.5 in /backend
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.2 to 3.10.5.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.2...v3.10.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 02:56:42 +00:00
dependabot[bot]
7780640938 build(deps): bump langchain from 0.2.14 to 0.2.15 in /backend
Bumps [langchain](https://github.com/langchain-ai/langchain) from 0.2.14 to 0.2.15.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain==0.2.14...langchain==0.2.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 02:56:33 +00:00
dependabot[bot]
92488c254d build(deps): bump unstructured from 0.15.7 to 0.15.9 in /backend
Bumps [unstructured](https://github.com/Unstructured-IO/unstructured) from 0.15.7 to 0.15.9.
- [Release notes](https://github.com/Unstructured-IO/unstructured/releases)
- [Changelog](https://github.com/Unstructured-IO/unstructured/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Unstructured-IO/unstructured/compare/0.15.7...0.15.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 02:56:28 +00:00
Timothy Jaeryang Baek
bccc6e08cc Merge pull request #4957 from pascallim/fix/imports
fix: sort and fix backend imports
2024-08-31 20:09:04 +02:00
Timothy Jaeryang Baek
a8c15e1b54 Merge pull request #5060 from cheahjs/fix/missing-chat-completion-response-headers
fix: return proxied response headers during chat completion
2024-08-31 20:08:22 +02:00
Jun Siang Cheah
953beb369c fix: return proxied response headers during chat completion 2024-08-31 15:15:21 +01:00
Pascal Lim
9232e03102 fix imports 2024-08-30 22:29:45 +02:00
Pascal Lim
c386d0b1a5 sort and fix backend imports 2024-08-30 22:26:22 +02:00
Timothy J. Baek
08efabc696 refac 2024-08-30 10:56:31 -07:00
Timothy Jaeryang Baek
560b9228fd Merge pull request #4973 from sebdanielsson/ally-aria-labels
A11y: Add missing aria labels for buttons
2024-08-30 19:43:35 +02:00
Timothy Jaeryang Baek
d3bcfd4d5f Merge pull request #4974 from SearchApi/add-search-api
feat: Add support for SearchApi as alternative to WebSearch
2024-08-30 19:42:36 +02:00
Timothy Jaeryang Baek
3b21547c83 Merge pull request #4971 from sebdanielsson/lighthouse-seo-score
Lighthouse seo score and error fixes
2024-08-30 19:39:29 +02:00
Timothy Jaeryang Baek
62c475f6b4 Merge pull request #4976 from aleixdorca/dev
i18n: Update Catalan Translation
2024-08-30 19:38:58 +02:00
Timothy Jaeryang Baek
5367d5ec3b Merge pull request #5030 from Peter-De-Ath/fix-comfy-ui-403
fix: add useragent default headers comfyui
2024-08-30 19:38:50 +02:00
Timothy Jaeryang Baek
01a5569ee9 Merge pull request #5037 from KarlLee830/translate
i18n: Update Chinese translation
2024-08-30 19:37:35 +02:00
Timothy Jaeryang Baek
f75908ebe3 Merge pull request #4975 from sebdanielsson/automatic-theme-update
fix: Update color theme on system change
2024-08-30 19:37:24 +02:00
Timothy Jaeryang Baek
928326103c Merge pull request #4970 from daniel-code/feat/support-literal-type-in-tools
feat: support Literal type in Tools
2024-08-30 19:34:19 +02:00
Timothy Jaeryang Baek
8a11f12ffd Merge pull request #4945 from OriginalSimon/dev
i18n: Update Ukrainian translation
2024-08-30 19:34:03 +02:00
Karl Lee
a6b5bf8df2 i18n: Update Chinese translation 2024-08-30 18:23:27 +08:00
Peter De-Ath
a26f4306a4 fix: add useragent default headers comfyui 2024-08-29 23:38:37 +01:00
Su YR
45363a2abb fix: type_ == "literal" 2024-08-29 15:55:12 +08:00
Aleix Dorca
a7b6d5507f i18n: Update Catalan translation.json 2024-08-28 13:06:52 +02:00
Aleix Dorca
2706b8c59c i18n: Update Catalan translation.json 2024-08-28 13:05:19 +02:00
Sebastian
fd1612935d Update color theme on system change 2024-08-28 12:43:43 +02:00
Rajendra Kadam
7e1923fcfe Add searchapi as an alternative web search
Add config changes for SearchApi api key and engine

Add searchapi results json in testdata
2024-08-28 15:26:33 +05:30
Sebastian
22117e06b5 A11y: Buttons do not have an accessible name 2024-08-28 10:13:02 +02:00
Sebastian
718d69b148 Add meta description 2024-08-28 10:10:22 +02:00
Sebastian
30e03cbac4 Add robots.txt to fix validation error 2024-08-28 10:10:22 +02:00
Su YR
0525dd2bb5 feat: support Literal type in Tools 2024-08-28 15:16:56 +08:00
Timothy Jaeryang Baek
6589450bd6 Merge pull request #4954 from open-webui/main
dev
2024-08-27 22:45:44 +02:00
Timothy Jaeryang Baek
f4df49e600 Merge pull request #4953 from open-webui/dev
fix: support list in json schema to pydantic
2024-08-27 22:45:28 +02:00
Timothy Jaeryang Baek
bc6d06b49e Merge pull request #4952 from Peter-De-Ath/support-list-json-schema-pydantic
fix: support list in json schema pydantic
2024-08-27 22:43:49 +02:00
Peter De-Ath
025f0f390e refac: support list in json schema to pydantic 2024-08-27 21:42:22 +01:00
Timothy J. Baek
0c0a860538 revert: docker build workflow 2024-08-27 22:40:23 +02:00
Simon
8994728d8b Update translation.json 2024-08-27 19:09:42 +02:00
Timothy Jaeryang Baek
693dc3107a Merge pull request #4811 from open-webui/dev
0.3.16
2024-08-27 18:49:04 +02:00
Timothy J. Baek
63c0772135 Update uv.lock 2024-08-27 18:48:56 +02:00
Timothy J. Baek
fb9b7275ad doc: changelog 2024-08-27 18:45:35 +02:00
Timothy J. Baek
7a024fbe1e refac 2024-08-27 18:18:40 +02:00
Timothy Jaeryang Baek
9dade91ef5 Merge pull request #4917 from Yanyutin753/upload_files_limit
🤖 Limit the size and number of uploaded files
2024-08-27 17:09:52 +02:00
Timothy J. Baek
35fa278b37 chore: format 2024-08-27 17:07:47 +02:00
Timothy J. Baek
6a21a77ee9 refac 2024-08-27 17:05:24 +02:00
Timothy J. Baek
628310b12b refac 2024-08-27 15:58:02 +02:00
Timothy J. Baek
ef28330c1a refac: do NOT change default behaviour in a PR 2024-08-27 15:56:47 +02:00
Timothy J. Baek
69c4687a53 refac 2024-08-27 15:53:29 +02:00
Timothy J. Baek
09cba5b87a refac: rm sub standard code 2024-08-27 15:51:40 +02:00
Timothy J. Baek
600409682e refac: do not change default behaviour 2024-08-27 15:30:57 +02:00
Timothy J. Baek
689b05a73d enh: add content-type: application/x-ndjson to ollama /api/chat 2024-08-27 14:06:58 +02:00
Timothy J. Baek
062649e483 refac: endpoints regarding db operations 2024-08-27 14:01:00 +02:00
Timothy Jaeryang Baek
bbeed7cd85 Merge pull request #4938 from KarlLee830/translate
i18n: Update Chinese translation
2024-08-27 13:30:42 +02:00
Karl Lee
6c49a15e20 i18n: Update Chinese translation 2024-08-27 19:12:47 +08:00
Timothy Jaeryang Baek
18a6b00083 Merge pull request #4929 from jannikstdl/improve-rag-status
fix: reset status on error + styling
2024-08-27 13:08:46 +02:00
Timothy Jaeryang Baek
7c841c9f63 Merge pull request #4924 from Peter-De-Ath/fix-duplicate-system-prompt
fix: stop system prompt being duplicated
2024-08-27 13:07:26 +02:00
Jannik Streidl
97d73d3d33 fix: reset status on error + styling 2024-08-27 11:24:05 +02:00
Peter De-Ath
b2e682e263 fix: stop system prompt being duplicated 2024-08-26 21:25:57 +01:00
Clivia
29cbdbcadd 💄Fix format 2024-08-26 23:59:53 +08:00
Clivia
ebca735f5e 💄Fix format 2024-08-26 23:53:51 +08:00
Clivia
775478534a 👀 Fix Common users cannot upload files
💄Fix format

💄Fix format i18

 Feat paste upload files and make restrictions

 Feat paste upload files and make restrictions
2024-08-26 23:36:13 +08:00
Clivia
b01d72ade3 💄Fix format
💄Fix format
2024-08-26 23:36:13 +08:00
Clivia
b6da4baa97 💄 Limit the size and number of uploaded files
💄 Limit the size and number of uploaded files
2024-08-26 23:36:13 +08:00
Timothy J. Baek
7fa9f381e1 chore: format 2024-08-26 15:38:42 +02:00
Timothy J. Baek
a9673c793a fix 2024-08-26 15:37:11 +02:00
Timothy Jaeryang Baek
b148865ee8 Merge pull request #4886 from kiosion/dev
feat: Add control for how message content is split for TTS generation requests
2024-08-26 15:02:30 +02:00
Timothy J. Baek
f4f7adb377 refac 2024-08-26 15:01:29 +02:00
Timothy J. Baek
b96239fb0b enh: block api user with model filter 2024-08-26 14:24:56 +02:00
Timothy J. Baek
7fc049a513 enh: codespan click to copy content 2024-08-26 14:17:33 +02:00
Timothy J. Baek
faeabfb3d4 fix: include __files__ param to pipe function 2024-08-26 12:36:04 +02:00
Timothy J. Baek
de6b5a7bbe refac 2024-08-26 12:27:00 +02:00
Timothy J. Baek
efd4b03f78 enh: repopulate tools & functions from db if non existent 2024-08-26 12:18:12 +02:00
Timothy J. Baek
e98a20fce9 fix: .md processing issue 2024-08-26 12:08:55 +02:00
Timothy J. Baek
5a0e1c5f75 refac: disable signups when ENABLE_LOGIN_FORM is set to false 2024-08-26 11:54:55 +02:00
Timothy Jaeryang Baek
c224f1105e Merge pull request #4859 from 0xThresh/main
Enhancement: Update Docker image metadata
2024-08-26 11:38:16 +02:00
Timothy Jaeryang Baek
95185aaaec Merge pull request #4904 from que-nguyen/dev
Fix and update Vietnamese translations for better accuracy.
2024-08-26 11:35:01 +02:00
Que Nguyen
cb7ee6212e Fix and update Vietnamese translations for better accuracy. 2024-08-26 14:14:00 +07:00
kiosion
d78c35c9ba refac: Tidy Chat.svelte 2024-08-25 20:27:50 -04:00
kiosion
73998a70cc i18n: Add new strings for en, fr locales 2024-08-25 20:03:58 -04:00
kiosion
3967c34261 feat: Add control for how message content is split for TTS generation reqs 2024-08-25 20:03:21 -04:00
kiosion
f30428754f fix: Safely retrieve settings from LocalStorage 2024-08-25 20:00:57 -04:00
James W.
56c9552ab3 Reset package.json to correct version 2024-08-25 14:46:21 -07:00
James W.
e414ba2d8c Merge pull request #28 from 0xThresh/dev
Test updating version with all images
2024-08-25 15:34:37 -06:00
0xThresh.eth
b1355e16bc Test updating version with all images 2024-08-25 15:33:08 -06:00
James W.
882b76cefa Merge pull request #27 from 0xThresh/dev
Add image updates to cuda and ollama image jobs
2024-08-25 15:28:51 -06:00
0xThresh.eth
63d82dbece Add image updates to cuda and ollama image jobs 2024-08-25 15:28:09 -06:00
Timothy J. Baek
f568389235 refac 2024-08-25 18:42:27 +02:00
Timothy Jaeryang Baek
814473878f Merge pull request #4899 from open-webui/config-db-migration
feat: config.json db migration
2024-08-25 17:59:01 +02:00
Timothy J. Baek
a44bae2d3a fix 2024-08-25 17:54:51 +02:00
Timothy J. Baek
fd0370d801 fix 2024-08-25 16:57:01 +02:00
Timothy J. Baek
58cf1be20c feat: config.json db migration 2024-08-25 16:52:36 +02:00
Timothy Jaeryang Baek
072945c40b Merge pull request #4867 from El-Tatane/improve-fr-translations
i18n: Improve French translations
2024-08-25 14:50:24 +02:00
El-Tatane
7c7d407f34 i18n: Improve French translations 2024-08-23 22:00:05 +02:00
James W.
c63a2dfbcd Merge pull request #26 from 0xThresh/dev
Test bumping the Open WebUI version
2024-08-23 12:11:24 -06:00
0xThresh.eth
e1022b3a28 Test bumping the Open WebUI version 2024-08-23 12:10:39 -06:00
James W.
0022902e8d Merge pull request #25 from 0xThresh/dev
Try removing push by digest
2024-08-23 12:03:42 -06:00
0xThresh.eth
9be7c8b969 Try removing push by digest 2024-08-23 12:03:06 -06:00
James W.
913b454fc3 Merge pull request #24 from 0xThresh/dev
Move version tags to latest when no new release is created
2024-08-23 11:54:09 -06:00
0xThresh.eth
ff1ea70dfa Only add version tag to main 2024-08-23 11:46:30 -06:00
0xThresh.eth
550386f52a Test moving tags in Actions 2024-08-23 11:40:02 -06:00
Timothy J. Baek
7a1fecbdb3 fix 2024-08-23 18:22:50 +02:00
Timothy J. Baek
8a99eaa68f fix 2024-08-23 18:21:19 +02:00
Timothy J. Baek
e442b3b169 fix: async image gen automatic1111 2024-08-23 16:51:34 +02:00
Timothy J. Baek
553293f4d5 refac: styling 2024-08-23 16:45:07 +02:00
Timothy J. Baek
48503573c1 refac: call overlay styling 2024-08-23 16:42:36 +02:00
Timothy J. Baek
4519ddd0e9 refac: files rbac 2024-08-23 16:19:04 +02:00
Timothy J. Baek
8b3d5e8b80 chore: format 2024-08-23 15:56:50 +02:00
Timothy J. Baek
0a5a2e67e8 fix: uploaded files leaking to other user chats issue
#4601
2024-08-23 15:02:23 +02:00
Timothy J. Baek
7b91be21b4 refac 2024-08-23 14:43:32 +02:00
Timothy J. Baek
591962d906 refac: input commands 2024-08-23 14:31:39 +02:00
Timothy Jaeryang Baek
64c0157271 Merge pull request #4842 from KarlLee830/translate
i18n: Update Chinese translation
2024-08-23 14:08:48 +02:00
Timothy Jaeryang Baek
f5c0e670aa Merge pull request #4847 from michaelpoluektov/fix-tools-param
fix: `__tools__` param
2024-08-23 14:08:32 +02:00
Michael Poluektov
83de28bac2 fix: tools param 2024-08-23 12:58:43 +01:00
Karl Lee
c6885a8bcf i18n: Update Chinese translation 2024-08-23 18:01:26 +08:00
Timothy J. Baek
5d8dbff486 refac 2024-08-22 17:37:47 +02:00
Timothy J. Baek
0939cb5ed6 refac: styling 2024-08-22 17:34:28 +02:00
Timothy J. Baek
153a2876b4 fix: mediaStream not stopping after exiting call mode 2024-08-22 17:27:22 +02:00
Timothy J. Baek
61503a654c enh: call=true url search param 2024-08-22 17:17:32 +02:00
Timothy J. Baek
c268a4e217 refac: only activate wakelock in call mode 2024-08-22 17:12:31 +02:00
Timothy J. Baek
70ab7735ba chore: format 2024-08-22 16:38:48 +02:00
Timothy J. Baek
25de3e753d fix 2024-08-22 16:34:12 +02:00
Timothy Jaeryang Baek
670672c067 Merge pull request #4813 from jannikstdl/rag-knowledge-status
feat: show rag status when using models with knowledge collections
2024-08-22 16:31:42 +02:00
Timothy J. Baek
85f8a80389 refac 2024-08-22 16:31:33 +02:00
Timothy J. Baek
673b893a8a refac 2024-08-22 16:11:19 +02:00
Timothy J. Baek
d8d2f3529f refac 2024-08-22 16:08:03 +02:00
Timothy J. Baek
63ba8145b9 refac 2024-08-22 16:02:29 +02:00
Timothy Jaeryang Baek
99db82a161 Merge pull request #4815 from michaelpoluektov/fix-user-valves
fix: Fix user valves
2024-08-22 15:25:45 +02:00
Timothy J. Baek
a3089e0472 fix 2024-08-22 15:24:48 +02:00
Timothy Jaeryang Baek
9186abf9c1 Merge branch 'dev' into fix-user-valves 2024-08-22 15:23:59 +02:00
Timothy J. Baek
97808ba1c3 refac 2024-08-22 15:23:32 +02:00
Timothy J. Baek
589420c208 refac 2024-08-22 15:20:19 +02:00
Timothy J. Baek
3bf7d569c6 refac 2024-08-22 15:11:31 +02:00
Timothy J. Baek
14187b027d refac 2024-08-22 15:09:06 +02:00
Timothy J. Baek
06f067fda9 refacfix 2024-08-22 15:03:39 +02:00
Michael Poluektov
9b5dfe64b7 fix: is not None 2024-08-22 13:58:10 +01:00
Timothy J. Baek
1c89c91f07 refac 2024-08-22 14:53:14 +02:00
Timothy J. Baek
ed5761f18f refac: rye -> uv 2024-08-22 14:38:05 +02:00
Michael Poluektov
16ec25d296 fix user valves 2024-08-22 13:34:35 +01:00
Jannik Streidl
abe17ab4b5 feat: show rag status when using models with knowledge collections 2024-08-22 14:30:11 +02:00
Timothy Jaeryang Baek
14f0e6a2ba Merge pull request #4783 from open-webui/dependabot/pip/backend/dev/langchain-community-0.2.12
chore(deps): bump langchain-community from 0.2.10 to 0.2.12 in /backend
2024-08-22 14:26:41 +02:00
Timothy Jaeryang Baek
f1f2f034f8 Merge pull request #4785 from open-webui/dependabot/pip/backend/dev/markdown-3.7
chore(deps): bump markdown from 3.6 to 3.7 in /backend
2024-08-22 14:26:33 +02:00
Timothy J. Baek
63418a583a fix: latex rendering issue 2024-08-22 14:24:49 +02:00
dependabot[bot]
4031cb9eda chore(deps): bump langchain-community from 0.2.10 to 0.2.12 in /backend
Bumps [langchain-community](https://github.com/langchain-ai/langchain) from 0.2.10 to 0.2.12.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain-community==0.2.10...langchain-community==0.2.12)

---
updated-dependencies:
- dependency-name: langchain-community
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-22 12:10:43 +00:00
dependabot[bot]
ee6b8c5b72 chore(deps): bump markdown from 3.6 to 3.7 in /backend
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.6 to 3.7.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.6...3.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-22 12:07:04 +00:00
Timothy Jaeryang Baek
11aecbe79a Merge pull request #4768 from zhaofengli/db-url-fixes
fix: Fix parameter handling for PostgreSQL URLs
2024-08-22 14:01:28 +02:00
Timothy Jaeryang Baek
a9bace0f97 Merge pull request #4784 from open-webui/dependabot/pip/backend/dev/langchain-0.2.14
chore(deps): bump langchain from 0.2.12 to 0.2.14 in /backend
2024-08-22 13:59:27 +02:00
Timothy Jaeryang Baek
7e4fed2451 Merge pull request #4786 from open-webui/dependabot/pip/backend/dev/unstructured-0.15.7
chore(deps): bump unstructured from 0.15.5 to 0.15.7 in /backend
2024-08-22 13:59:03 +02:00
Timothy Jaeryang Baek
ac8a16ec5b Merge pull request #4787 from open-webui/dependabot/pip/backend/dev/langfuse-2.44.0
chore(deps): bump langfuse from 2.43.3 to 2.44.0 in /backend
2024-08-22 13:58:48 +02:00
Timothy Jaeryang Baek
95d016dea4 Merge pull request #4800 from aleixdorca/dev
i18n: Update catalan translation.json
2024-08-22 13:58:14 +02:00
Timothy Jaeryang Baek
abe36a3e67 Merge pull request #4803 from CJDaniel96/dev
fix: DeprecationWarning for datetime.utcnow() by using datetime.now(UTC)
2024-08-22 13:57:57 +02:00
USIGLOBAL\daniel_tsai
89ebbed67b fix: DeprecationWarning for datetime.utcnow() by using datetime.now(UTC) 2024-08-22 15:12:40 +08:00
Aleix Dorca
ec075e2612 Update catalan translation.json 2024-08-22 07:12:42 +02:00
Timothy Jaeryang Baek
e2b7296786 Merge pull request #4798 from open-webui/dev
fix: filter compatibility issue
2024-08-22 01:14:34 +02:00
Timothy J. Baek
6fcd40d4d8 fix: filter compatibility issue 2024-08-22 01:08:59 +02:00
dependabot[bot]
f5994e3a44 chore(deps): bump langfuse from 2.43.3 to 2.44.0 in /backend
Bumps [langfuse](https://github.com/langfuse/langfuse) from 2.43.3 to 2.44.0.
- [Release notes](https://github.com/langfuse/langfuse/releases)
- [Commits](https://github.com/langfuse/langfuse/commits/v2.44.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-21 15:38:07 +00:00
dependabot[bot]
ccf5bd1492 chore(deps): bump unstructured from 0.15.5 to 0.15.7 in /backend
Bumps [unstructured](https://github.com/Unstructured-IO/unstructured) from 0.15.5 to 0.15.7.
- [Release notes](https://github.com/Unstructured-IO/unstructured/releases)
- [Changelog](https://github.com/Unstructured-IO/unstructured/blob/0.15.7/CHANGELOG.md)
- [Commits](https://github.com/Unstructured-IO/unstructured/compare/0.15.5...0.15.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-21 15:38:05 +00:00
dependabot[bot]
510a0f3a2f chore(deps): bump langchain from 0.2.12 to 0.2.14 in /backend
Bumps [langchain](https://github.com/langchain-ai/langchain) from 0.2.12 to 0.2.14.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain==0.2.12...langchain==0.2.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-21 15:37:56 +00:00
Zhaofeng Li
e63d5778a8 fix: Decode URL-encoded characters in passwords
This enables using passwords containing special characters.
2024-08-12 08:52:16 -06:00
Zhaofeng Li
a53c2a8c6b fix: Pass all parsed options to ReconnectingPostgresqlDatabase 2024-08-12 08:52:16 -06:00
205 changed files with 8954 additions and 4359 deletions

View File

@@ -312,7 +312,7 @@ jobs:
merge-main-images:
runs-on: ubuntu-latest
needs: [ build-main-image ]
needs: [build-main-image]
steps:
# GitHub Packages requires the entire repository name to be in lowercase
# although the repository owner has a lowercase username, this prevents some people from running actions after forking
@@ -364,10 +364,9 @@ jobs:
run: |
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }}
merge-cuda-images:
runs-on: ubuntu-latest
needs: [ build-cuda-image ]
needs: [build-cuda-image]
steps:
# GitHub Packages requires the entire repository name to be in lowercase
# although the repository owner has a lowercase username, this prevents some people from running actions after forking
@@ -423,7 +422,7 @@ jobs:
merge-ollama-images:
runs-on: ubuntu-latest
needs: [ build-ollama-image ]
needs: [build-ollama-image]
steps:
# GitHub Packages requires the entire repository name to be in lowercase
# although the repository owner has a lowercase username, this prevents some people from running actions after forking

View File

@@ -4,6 +4,7 @@ on:
push:
branches:
- main # or whatever branch you want to use
- pypi-release
jobs:
release:

View File

@@ -5,6 +5,57 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.17] - 2024-09-04
### Added
- **🔄 Import/Export Configuration**: Users can now import and export webui configurations from admin settings > Database, simplifying setup replication across systems.
- **🌍 Web Search via URL Parameter**: Added support for activating web search directly through URL by setting 'web-search=true'.
- **🌐 SearchApi Integration**: Added support for SearchApi as an alternative web search provider, enhancing search capabilities within the platform.
- **🔍 Literal Type Support in Tools**: Tools now support the Literal type.
- **🌍 Updated Translations**: Improved translations for Chinese, Ukrainian, and Catalan.
### Fixed
- **🔧 Pip Install Issue**: Resolved the issue where pip install failed due to missing 'alembic.ini', ensuring smoother installation processes.
- **🌃 Automatic Theme Update**: Fixed an issue where the color theme did not update dynamically with system changes.
- **🛠️ User Agent in ComfyUI**: Added default headers in ComfyUI to fix access issues, improving reliability in network communications.
- **🔄 Missing Chat Completion Response Headers**: Ensured proper return of proxied response headers during chat completion, improving API reliability.
- **🔗 Websocket Connection Prioritization**: Modified socket.io configuration to prefer websockets and more reliably fallback to polling, enhancing connection stability.
- **🎭 Accessibility Enhancements**: Added missing ARIA labels for buttons, improving accessibility for visually impaired users.
- **⚖️ Advanced Parameter**: Fixed an issue ensuring that advanced parameters are correctly applied in all scenarios, ensuring consistent behavior of user-defined settings.
### Changed
- **🔁 Namespace Reorganization**: Reorganized all Python files under the 'open_webui' namespace to streamline the project structure and improve maintainability. Tools and functions importing from 'utils' should now use 'open_webui.utils'.
- **🚧 Dependency Updates**: Updated several backend dependencies like 'aiohttp', 'authlib', 'duckduckgo-search', 'flask-cors', and 'langchain' to their latest versions, enhancing performance and security.
## [0.3.16] - 2024-08-27
### Added
- **🚀 Config DB Migration**: Migrated configuration handling from config.json to the database, enabling high-availability setups and load balancing across multiple Open WebUI instances.
- **🔗 Call Mode Activation via URL**: Added a 'call=true' URL search parameter enabling direct shortcuts to activate call mode, enhancing user interaction on mobile devices.
- **✨ TTS Content Control**: Added functionality to control how message content is segmented for Text-to-Speech (TTS) generation requests, allowing for more flexible speech output options.
- **😄 Show Knowledge Search Status**: Enhanced model usage transparency by displaying status when working with knowledge-augmented models, helping users understand the system's state during queries.
- **👆 Click-to-Copy for Codespan**: Enhanced interactive experience in the WebUI by allowing users to click to copy content from code spans directly.
- **🚫 API User Blocking via Model Filter**: Introduced the ability to block API users based on customized model filters, enhancing security and control over API access.
- **🎬 Call Overlay Styling**: Adjusted call overlay styling on large screens to not cover the entire interface, but only the chat control area, for a more unobtrusive interaction experience.
### Fixed
- **🔧 LaTeX Rendering Issue**: Addressed an issue that affected the correct rendering of LaTeX.
- **📁 File Leak Prevention**: Resolved the issue of uploaded files mistakenly being accessible across user chats.
- **🔧 Pipe Functions with '**files**' Param**: Fixed issues with '**files**' parameter not functioning correctly in pipe functions.
- **📝 Markdown Processing for RAG**: Fixed issues with processing Markdown in files.
- **🚫 Duplicate System Prompts**: Fixed bugs causing system prompts to duplicate.
### Changed
- **🔋 Wakelock Permission**: Optimized the activation of wakelock to only engage during call mode, conserving device resources and improving battery performance during idle periods.
- **🔍 Content-Type for Ollama Chats**: Added 'application/x-ndjson' content-type to '/api/chat' endpoint responses to match raw Ollama responses.
- **✋ Disable Signups Conditionally**: Implemented conditional logic to disable sign-ups when 'ENABLE_LOGIN_FORM' is set to false.
## [0.3.15] - 2024-08-21
### Added

View File

@@ -37,7 +37,7 @@ Open WebUI is an [extensible](https://github.com/open-webui/pipelines), feature-
- 📚 **Local RAG Integration**: Dive into the future of chat interactions with groundbreaking Retrieval Augmented Generation (RAG) support. This feature seamlessly integrates document interactions into your chat experience. You can load documents directly into the chat or add files to your document library, effortlessly accessing them using the `#` command before a query.
- 🔍 **Web Search for RAG**: Perform web searches using providers like `SearXNG`, `Google PSE`, `Brave Search`, `serpstack`, `serper`, `Serply`, `DuckDuckGo` and `TavilySearch` and inject the results directly into your chat experience.
- 🔍 **Web Search for RAG**: Perform web searches using providers like `SearXNG`, `Google PSE`, `Brave Search`, `serpstack`, `serper`, `Serply`, `DuckDuckGo`, `TavilySearch` and `SearchApi` and inject the results directly into your chat experience.
- 🌐 **Web Browsing Capability**: Seamlessly integrate websites into your chat experience using the `#` command followed by a URL. This feature allows you to incorporate web content directly into your conversations, enhancing the richness and depth of your interactions.

View File

@@ -1,36 +0,0 @@
{
"version": 0,
"ui": {
"default_locale": "",
"prompt_suggestions": [
{
"title": ["Help me study", "vocabulary for a college entrance exam"],
"content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option."
},
{
"title": ["Give me ideas", "for what to do with my kids' art"],
"content": "What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter."
},
{
"title": ["Tell me a fun fact", "about the Roman Empire"],
"content": "Tell me a random fun fact about the Roman Empire"
},
{
"title": ["Show me a code snippet", "of a website's sticky header"],
"content": "Show me a code snippet of a website's sticky header in CSS and JavaScript."
},
{
"title": ["Explain options trading", "if I'm familiar with buying and selling stocks"],
"content": "Explain options trading in simple terms if I'm familiar with buying and selling stocks."
},
{
"title": ["Overcome procrastination", "give me tips"],
"content": "Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?"
},
{
"title": ["Grammar check", "rewrite it for better readability "],
"content": "Check the following sentence for grammar and clarity: \"[sentence]\". Rewrite it for better readability while maintaining its original meaning."
}
]
}
}

View File

@@ -1,4 +0,0 @@
general_settings: {}
litellm_settings: {}
model_list: []
router_settings: {}

View File

@@ -1 +1 @@
dir for backend files (db, documents, etc.)
docker dir for backend files (db, documents, etc.)

View File

@@ -1,2 +1,2 @@
PORT="${PORT:-8080}"
uvicorn main:app --port $PORT --host 0.0.0.0 --forwarded-allow-ips '*' --reload
uvicorn open_webui.main:app --port $PORT --host 0.0.0.0 --forwarded-allow-ips '*' --reload

View File

@@ -9,8 +9,6 @@ import uvicorn
app = typer.Typer()
KEY_FILE = Path.cwd() / ".webui_secret_key"
if (frontend_build_dir := Path(__file__).parent / "frontend").exists():
os.environ["FRONTEND_BUILD_DIR"] = str(frontend_build_dir)
@app.command()
@@ -40,9 +38,9 @@ def serve(
"/usr/local/lib/python3.11/site-packages/nvidia/cudnn/lib",
]
)
import main # we need set environment variables before importing main
import open_webui.main # we need set environment variables before importing main
uvicorn.run(main.app, host=host, port=port, forwarded_allow_ips="*")
uvicorn.run(open_webui.main.app, host=host, port=port, forwarded_allow_ips="*")
@app.command()
@@ -52,7 +50,11 @@ def dev(
reload: bool = True,
):
uvicorn.run(
"main:app", host=host, port=port, reload=reload, forwarded_allow_ips="*"
"open_webui.main:app",
host=host,
port=port,
reload=reload,
forwarded_allow_ips="*",
)

View File

@@ -7,45 +7,33 @@ from functools import lru_cache
from pathlib import Path
import requests
from fastapi import (
FastAPI,
Request,
Depends,
HTTPException,
status,
UploadFile,
File,
from open_webui.config import (
AUDIO_STT_ENGINE,
AUDIO_STT_MODEL,
AUDIO_STT_OPENAI_API_BASE_URL,
AUDIO_STT_OPENAI_API_KEY,
AUDIO_TTS_API_KEY,
AUDIO_TTS_ENGINE,
AUDIO_TTS_MODEL,
AUDIO_TTS_OPENAI_API_BASE_URL,
AUDIO_TTS_OPENAI_API_KEY,
AUDIO_TTS_SPLIT_ON,
AUDIO_TTS_VOICE,
CACHE_DIR,
CORS_ALLOW_ORIGIN,
DEVICE_TYPE,
WHISPER_MODEL,
WHISPER_MODEL_AUTO_UPDATE,
WHISPER_MODEL_DIR,
AppConfig,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import Depends, FastAPI, File, HTTPException, Request, UploadFile, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from pydantic import BaseModel
from config import (
SRC_LOG_LEVELS,
CACHE_DIR,
WHISPER_MODEL,
WHISPER_MODEL_DIR,
WHISPER_MODEL_AUTO_UPDATE,
DEVICE_TYPE,
AUDIO_STT_OPENAI_API_BASE_URL,
AUDIO_STT_OPENAI_API_KEY,
AUDIO_TTS_OPENAI_API_BASE_URL,
AUDIO_TTS_OPENAI_API_KEY,
AUDIO_TTS_API_KEY,
AUDIO_STT_ENGINE,
AUDIO_STT_MODEL,
AUDIO_TTS_ENGINE,
AUDIO_TTS_MODEL,
AUDIO_TTS_VOICE,
AppConfig,
CORS_ALLOW_ORIGIN,
)
from constants import ERROR_MESSAGES
from utils.utils import (
get_current_user,
get_verified_user,
get_admin_user,
)
from open_webui.utils.utils import get_admin_user, get_current_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["AUDIO"])
@@ -72,6 +60,7 @@ app.state.config.TTS_ENGINE = AUDIO_TTS_ENGINE
app.state.config.TTS_MODEL = AUDIO_TTS_MODEL
app.state.config.TTS_VOICE = AUDIO_TTS_VOICE
app.state.config.TTS_API_KEY = AUDIO_TTS_API_KEY
app.state.config.TTS_SPLIT_ON = AUDIO_TTS_SPLIT_ON
# setting device type for whisper model
whisper_device_type = DEVICE_TYPE if DEVICE_TYPE and DEVICE_TYPE == "cuda" else "cpu"
@@ -88,6 +77,7 @@ class TTSConfigForm(BaseModel):
ENGINE: str
MODEL: str
VOICE: str
SPLIT_ON: str
class STTConfigForm(BaseModel):
@@ -139,6 +129,7 @@ async def get_audio_config(user=Depends(get_admin_user)):
"ENGINE": app.state.config.TTS_ENGINE,
"MODEL": app.state.config.TTS_MODEL,
"VOICE": app.state.config.TTS_VOICE,
"SPLIT_ON": app.state.config.TTS_SPLIT_ON,
},
"stt": {
"OPENAI_API_BASE_URL": app.state.config.STT_OPENAI_API_BASE_URL,
@@ -159,6 +150,7 @@ async def update_audio_config(
app.state.config.TTS_ENGINE = form_data.tts.ENGINE
app.state.config.TTS_MODEL = form_data.tts.MODEL
app.state.config.TTS_VOICE = form_data.tts.VOICE
app.state.config.TTS_SPLIT_ON = form_data.tts.SPLIT_ON
app.state.config.STT_OPENAI_API_BASE_URL = form_data.stt.OPENAI_API_BASE_URL
app.state.config.STT_OPENAI_API_KEY = form_data.stt.OPENAI_API_KEY
@@ -173,6 +165,7 @@ async def update_audio_config(
"ENGINE": app.state.config.TTS_ENGINE,
"MODEL": app.state.config.TTS_MODEL,
"VOICE": app.state.config.TTS_VOICE,
"SPLIT_ON": app.state.config.TTS_SPLIT_ON,
},
"stt": {
"OPENAI_API_BASE_URL": app.state.config.STT_OPENAI_API_BASE_URL,
@@ -205,7 +198,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
body = json.loads(body)
body["model"] = app.state.config.TTS_MODEL
body = json.dumps(body).encode("utf-8")
except Exception as e:
except Exception:
pass
r = None
@@ -482,7 +475,7 @@ def get_available_voices() -> dict:
elif app.state.config.TTS_ENGINE == "elevenlabs":
try:
ret = get_elevenlabs_voices()
except Exception as e:
except Exception:
# Avoided @lru_cache with exception
pass

View File

@@ -1,51 +1,42 @@
from fastapi import (
FastAPI,
Request,
Depends,
HTTPException,
)
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional
from pydantic import BaseModel
from pathlib import Path
import mimetypes
import uuid
import asyncio
import base64
import json
import logging
import mimetypes
import re
import uuid
from pathlib import Path
from typing import Optional
import requests
from utils.utils import (
get_verified_user,
get_admin_user,
)
from apps.images.utils.comfyui import (
ComfyUIWorkflow,
from open_webui.apps.images.utils.comfyui import (
ComfyUIGenerateImageForm,
ComfyUIWorkflow,
comfyui_generate_image,
)
from constants import ERROR_MESSAGES
from config import (
SRC_LOG_LEVELS,
CACHE_DIR,
IMAGE_GENERATION_ENGINE,
ENABLE_IMAGE_GENERATION,
AUTOMATIC1111_BASE_URL,
from open_webui.config import (
AUTOMATIC1111_API_AUTH,
AUTOMATIC1111_BASE_URL,
CACHE_DIR,
COMFYUI_BASE_URL,
COMFYUI_WORKFLOW,
COMFYUI_WORKFLOW_NODES,
IMAGES_OPENAI_API_BASE_URL,
IMAGES_OPENAI_API_KEY,
CORS_ALLOW_ORIGIN,
ENABLE_IMAGE_GENERATION,
IMAGE_GENERATION_ENGINE,
IMAGE_GENERATION_MODEL,
IMAGE_SIZE,
IMAGE_STEPS,
CORS_ALLOW_ORIGIN,
IMAGES_OPENAI_API_BASE_URL,
IMAGES_OPENAI_API_KEY,
AppConfig,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from open_webui.utils.utils import get_admin_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["IMAGES"])
@@ -142,7 +133,7 @@ async def update_config(form_data: ConfigForm, user=Depends(get_admin_user)):
form_data.automatic1111.AUTOMATIC1111_API_AUTH
)
app.state.config.COMFYUI_BASE_URL = form_data.comfyui.COMFYUI_BASE_URL
app.state.config.COMFYUI_BASE_URL = form_data.comfyui.COMFYUI_BASE_URL.strip("/")
app.state.config.COMFYUI_WORKFLOW = form_data.comfyui.COMFYUI_WORKFLOW
app.state.config.COMFYUI_WORKFLOW_NODES = form_data.comfyui.COMFYUI_WORKFLOW_NODES
@@ -185,7 +176,7 @@ async def verify_url(user=Depends(get_admin_user)):
)
r.raise_for_status()
return True
except Exception as e:
except Exception:
app.state.config.ENABLED = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
elif app.state.config.ENGINE == "comfyui":
@@ -193,7 +184,7 @@ async def verify_url(user=Depends(get_admin_user)):
r = requests.get(url=f"{app.state.config.COMFYUI_BASE_URL}/object_info")
r.raise_for_status()
return True
except Exception as e:
except Exception:
app.state.config.ENABLED = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
else:
@@ -201,6 +192,7 @@ async def verify_url(user=Depends(get_admin_user)):
def set_image_model(model: str):
log.info(f"Setting image model to {model}")
app.state.config.MODEL = model
if app.state.config.ENGINE in ["", "automatic1111"]:
api_auth = get_automatic1111_api_auth()
@@ -254,7 +246,8 @@ async def get_image_config(user=Depends(get_admin_user)):
@app.post("/image/config/update")
async def update_image_config(form_data: ImageConfigForm, user=Depends(get_admin_user)):
app.state.config.MODEL = form_data.MODEL
set_image_model(form_data.MODEL)
pattern = r"^\d+x\d+$"
if re.match(pattern, form_data.IMAGE_SIZE):
@@ -396,7 +389,6 @@ def save_url_image(url):
r = requests.get(url)
r.raise_for_status()
if r.headers["content-type"].split("/")[0] == "image":
mime_type = r.headers["content-type"]
image_format = mimetypes.guess_extension(mime_type)
@@ -411,7 +403,7 @@ def save_url_image(url):
image_file.write(chunk)
return image_filename
else:
log.error(f"Url does not point to an image.")
log.error("Url does not point to an image.")
return None
except Exception as e:
@@ -429,7 +421,6 @@ async def image_generations(
r = None
try:
if app.state.config.ENGINE == "openai":
headers = {}
headers["Authorization"] = f"Bearer {app.state.config.OPENAI_API_KEY}"
headers["Content-Type"] = "application/json"
@@ -533,7 +524,9 @@ async def image_generations(
if form_data.negative_prompt is not None:
data["negative_prompt"] = form_data.negative_prompt
r = requests.post(
# Use asyncio.to_thread for the requests.post call
r = await asyncio.to_thread(
requests.post,
url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/txt2img",
json=data,
headers={"authorization": get_automatic1111_api_auth()},
@@ -553,7 +546,6 @@ async def image_generations(
json.dump({**data, "info": res["info"]}, f)
return images
except Exception as e:
error = e
if r != None:

View File

@@ -1,34 +1,45 @@
import asyncio
import websocket # NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
import json
import urllib.request
import urllib.parse
import random
import logging
import random
import urllib.parse
import urllib.request
from typing import Optional
from config import SRC_LOG_LEVELS
import websocket # NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["COMFYUI"])
from pydantic import BaseModel
from typing import Optional
default_headers = {"User-Agent": "Mozilla/5.0"}
def queue_prompt(prompt, client_id, base_url):
log.info("queue_prompt")
p = {"prompt": prompt, "client_id": client_id}
data = json.dumps(p).encode("utf-8")
req = urllib.request.Request(f"{base_url}/prompt", data=data)
return json.loads(urllib.request.urlopen(req).read())
log.debug(f"queue_prompt data: {data}")
try:
req = urllib.request.Request(
f"{base_url}/prompt", data=data, headers=default_headers
)
response = urllib.request.urlopen(req).read()
return json.loads(response)
except Exception as e:
log.exception(f"Error while queuing prompt: {e}")
raise e
def get_image(filename, subfolder, folder_type, base_url):
log.info("get_image")
data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
url_values = urllib.parse.urlencode(data)
with urllib.request.urlopen(f"{base_url}/view?{url_values}") as response:
req = urllib.request.Request(
f"{base_url}/view?{url_values}", headers=default_headers
)
with urllib.request.urlopen(req) as response:
return response.read()
@@ -41,7 +52,11 @@ def get_image_url(filename, subfolder, folder_type, base_url):
def get_history(prompt_id, base_url):
log.info("get_history")
with urllib.request.urlopen(f"{base_url}/history/{prompt_id}") as response:
req = urllib.request.Request(
f"{base_url}/history/{prompt_id}", headers=default_headers
)
with urllib.request.urlopen(req) as response:
return json.loads(response.read())

View File

@@ -1,54 +1,40 @@
from fastapi import (
FastAPI,
Request,
HTTPException,
Depends,
UploadFile,
File,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, ConfigDict
import os
import re
import random
import requests
import json
import aiohttp
import asyncio
import json
import logging
import os
import random
import re
import time
from urllib.parse import urlparse
from typing import Optional, Union
from urllib.parse import urlparse
from starlette.background import BackgroundTask
from apps.webui.models.models import Models
from constants import ERROR_MESSAGES
from utils.utils import (
get_verified_user,
get_admin_user,
)
from config import (
SRC_LOG_LEVELS,
OLLAMA_BASE_URLS,
ENABLE_OLLAMA_API,
import aiohttp
import requests
from open_webui.apps.webui.models.models import Models
from open_webui.config import (
AIOHTTP_CLIENT_TIMEOUT,
CORS_ALLOW_ORIGIN,
ENABLE_MODEL_FILTER,
ENABLE_OLLAMA_API,
MODEL_FILTER_LIST,
OLLAMA_BASE_URLS,
UPLOAD_DIR,
AppConfig,
CORS_ALLOW_ORIGIN,
)
from utils.misc import (
calculate_sha256,
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import Depends, FastAPI, File, HTTPException, Request, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, ConfigDict
from starlette.background import BackgroundTask
from open_webui.utils.misc import (
apply_model_params_to_body_ollama,
apply_model_params_to_body_openai,
apply_model_system_prompt_to_body,
calculate_sha256,
)
from open_webui.utils.utils import get_admin_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["OLLAMA"])
@@ -148,7 +134,9 @@ async def cleanup_response(
await session.close()
async def post_streaming_url(url: str, payload: Union[str, bytes], stream: bool = True):
async def post_streaming_url(
url: str, payload: Union[str, bytes], stream: bool = True, content_type=None
):
r = None
try:
session = aiohttp.ClientSession(
@@ -162,10 +150,13 @@ async def post_streaming_url(url: str, payload: Union[str, bytes], stream: bool
r.raise_for_status()
if stream:
headers = dict(r.headers)
if content_type:
headers["Content-Type"] = content_type
return StreamingResponse(
r.content,
status_code=r.status,
headers=dict(r.headers),
headers=headers,
background=BackgroundTask(
cleanup_response, response=r, session=session
),
@@ -737,6 +728,14 @@ async def generate_chat_completion(
del payload["metadata"]
model_id = form_data.model
if app.state.config.ENABLE_MODEL_FILTER:
if user.role == "user" and model_id not in app.state.config.MODEL_FILTER_LIST:
raise HTTPException(
status_code=403,
detail="Model not found",
)
model_info = Models.get_model_by_id(model_id)
if model_info:
@@ -761,7 +760,9 @@ async def generate_chat_completion(
log.info(f"url: {url}")
log.debug(payload)
return await post_streaming_url(f"{url}/api/chat", json.dumps(payload))
return await post_streaming_url(
f"{url}/api/chat", json.dumps(payload), content_type="application/x-ndjson"
)
# TODO: we should update this part once Ollama supports other types
@@ -797,6 +798,14 @@ async def generate_openai_chat_completion(
del payload["metadata"]
model_id = completion_form.model
if app.state.config.ENABLE_MODEL_FILTER:
if user.role == "user" and model_id not in app.state.config.MODEL_FILTER_LIST:
raise HTTPException(
status_code=403,
detail="Model not found",
)
model_info = Models.get_model_by_id(model_id)
if model_info:

View File

@@ -1,44 +1,36 @@
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, FileResponse
import requests
import aiohttp
import asyncio
import hashlib
import json
import logging
from pathlib import Path
from typing import Literal, Optional, overload
import aiohttp
import requests
from open_webui.apps.webui.models.models import Models
from open_webui.config import (
AIOHTTP_CLIENT_TIMEOUT,
CACHE_DIR,
CORS_ALLOW_ORIGIN,
ENABLE_MODEL_FILTER,
ENABLE_OPENAI_API,
MODEL_FILTER_LIST,
OPENAI_API_BASE_URLS,
OPENAI_API_KEYS,
AppConfig,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, StreamingResponse
from pydantic import BaseModel
from starlette.background import BackgroundTask
from apps.webui.models.models import Models
from constants import ERROR_MESSAGES
from utils.utils import (
get_verified_user,
get_admin_user,
)
from utils.misc import (
from open_webui.utils.misc import (
apply_model_params_to_body_openai,
apply_model_system_prompt_to_body,
)
from config import (
SRC_LOG_LEVELS,
ENABLE_OPENAI_API,
AIOHTTP_CLIENT_TIMEOUT,
OPENAI_API_BASE_URLS,
OPENAI_API_KEYS,
CACHE_DIR,
ENABLE_MODEL_FILTER,
MODEL_FILTER_LIST,
AppConfig,
CORS_ALLOW_ORIGIN,
)
from typing import Optional, Literal, overload
import hashlib
from pathlib import Path
from open_webui.utils.utils import get_admin_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["OPENAI"])

View File

@@ -1,138 +1,118 @@
from fastapi import (
FastAPI,
Depends,
HTTPException,
status,
UploadFile,
File,
Form,
)
from fastapi.middleware.cors import CORSMiddleware
import requests
import os, shutil, logging, re
from datetime import datetime
from pathlib import Path
from typing import Union, Sequence, Iterator, Any
from chromadb.utils.batch_utils import create_batches
from langchain_core.documents import Document
from langchain_community.document_loaders import (
WebBaseLoader,
TextLoader,
PyPDFLoader,
CSVLoader,
BSHTMLLoader,
Docx2txtLoader,
UnstructuredEPubLoader,
UnstructuredWordDocumentLoader,
UnstructuredMarkdownLoader,
UnstructuredXMLLoader,
UnstructuredRSTLoader,
UnstructuredExcelLoader,
UnstructuredPowerPointLoader,
YoutubeLoader,
OutlookMessageLoader,
)
from langchain.text_splitter import RecursiveCharacterTextSplitter
import validators
import urllib.parse
import socket
from pydantic import BaseModel
from typing import Optional
import mimetypes
import uuid
import json
import logging
import mimetypes
import os
import shutil
import socket
import urllib.parse
import uuid
from datetime import datetime
from pathlib import Path
from typing import Iterator, Optional, Sequence, Union
from apps.webui.models.documents import (
Documents,
DocumentForm,
DocumentResponse,
)
from apps.webui.models.files import (
Files,
)
from apps.rag.utils import (
get_model_path,
import requests
import validators
from open_webui.apps.rag.search.brave import search_brave
from open_webui.apps.rag.search.duckduckgo import search_duckduckgo
from open_webui.apps.rag.search.google_pse import search_google_pse
from open_webui.apps.rag.search.jina_search import search_jina
from open_webui.apps.rag.search.main import SearchResult
from open_webui.apps.rag.search.searchapi import search_searchapi
from open_webui.apps.rag.search.searxng import search_searxng
from open_webui.apps.rag.search.serper import search_serper
from open_webui.apps.rag.search.serply import search_serply
from open_webui.apps.rag.search.serpstack import search_serpstack
from open_webui.apps.rag.search.tavily import search_tavily
from open_webui.apps.rag.utils import (
get_embedding_function,
query_doc,
query_doc_with_hybrid_search,
get_model_path,
query_collection,
query_collection_with_hybrid_search,
query_doc,
query_doc_with_hybrid_search,
)
from apps.rag.search.brave import search_brave
from apps.rag.search.google_pse import search_google_pse
from apps.rag.search.main import SearchResult
from apps.rag.search.searxng import search_searxng
from apps.rag.search.serper import search_serper
from apps.rag.search.serpstack import search_serpstack
from apps.rag.search.serply import search_serply
from apps.rag.search.duckduckgo import search_duckduckgo
from apps.rag.search.tavily import search_tavily
from apps.rag.search.jina_search import search_jina
from utils.misc import (
calculate_sha256,
calculate_sha256_string,
sanitize_filename,
extract_folders_after_data_docs,
)
from utils.utils import get_verified_user, get_admin_user
from config import (
AppConfig,
ENV,
SRC_LOG_LEVELS,
UPLOAD_DIR,
DOCS_DIR,
from open_webui.apps.webui.models.documents import DocumentForm, Documents
from open_webui.apps.webui.models.files import Files
from chromadb.utils.batch_utils import create_batches
from open_webui.config import (
BRAVE_SEARCH_API_KEY,
CHROMA_CLIENT,
CHUNK_OVERLAP,
CHUNK_SIZE,
CONTENT_EXTRACTION_ENGINE,
TIKA_SERVER_URL,
RAG_TOP_K,
RAG_RELEVANCE_THRESHOLD,
CORS_ALLOW_ORIGIN,
DEVICE_TYPE,
DOCS_DIR,
ENABLE_RAG_HYBRID_SEARCH,
ENABLE_RAG_LOCAL_WEB_FETCH,
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
ENABLE_RAG_WEB_SEARCH,
ENV,
GOOGLE_PSE_API_KEY,
GOOGLE_PSE_ENGINE_ID,
PDF_EXTRACT_IMAGES,
RAG_EMBEDDING_ENGINE,
RAG_EMBEDDING_MODEL,
RAG_EMBEDDING_MODEL_AUTO_UPDATE,
RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE,
ENABLE_RAG_HYBRID_SEARCH,
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
RAG_RERANKING_MODEL,
PDF_EXTRACT_IMAGES,
RAG_RERANKING_MODEL_AUTO_UPDATE,
RAG_RERANKING_MODEL_TRUST_REMOTE_CODE,
RAG_EMBEDDING_OPENAI_BATCH_SIZE,
RAG_FILE_MAX_COUNT,
RAG_FILE_MAX_SIZE,
RAG_OPENAI_API_BASE_URL,
RAG_OPENAI_API_KEY,
DEVICE_TYPE,
CHROMA_CLIENT,
CHUNK_SIZE,
CHUNK_OVERLAP,
RAG_RELEVANCE_THRESHOLD,
RAG_RERANKING_MODEL,
RAG_RERANKING_MODEL_AUTO_UPDATE,
RAG_RERANKING_MODEL_TRUST_REMOTE_CODE,
RAG_TEMPLATE,
ENABLE_RAG_LOCAL_WEB_FETCH,
YOUTUBE_LOADER_LANGUAGE,
ENABLE_RAG_WEB_SEARCH,
RAG_WEB_SEARCH_ENGINE,
RAG_TOP_K,
RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
RAG_WEB_SEARCH_ENGINE,
RAG_WEB_SEARCH_RESULT_COUNT,
SEARCHAPI_API_KEY,
SEARCHAPI_ENGINE,
SEARXNG_QUERY_URL,
GOOGLE_PSE_API_KEY,
GOOGLE_PSE_ENGINE_ID,
BRAVE_SEARCH_API_KEY,
SERPSTACK_API_KEY,
SERPSTACK_HTTPS,
SERPER_API_KEY,
SERPLY_API_KEY,
SERPSTACK_API_KEY,
SERPSTACK_HTTPS,
TAVILY_API_KEY,
RAG_WEB_SEARCH_RESULT_COUNT,
RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
RAG_EMBEDDING_OPENAI_BATCH_SIZE,
CORS_ALLOW_ORIGIN,
TIKA_SERVER_URL,
UPLOAD_DIR,
YOUTUBE_LOADER_LANGUAGE,
AppConfig,
)
from constants import ERROR_MESSAGES
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import Depends, FastAPI, File, Form, HTTPException, UploadFile, status
from fastapi.middleware.cors import CORSMiddleware
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import (
BSHTMLLoader,
CSVLoader,
Docx2txtLoader,
OutlookMessageLoader,
PyPDFLoader,
TextLoader,
UnstructuredEPubLoader,
UnstructuredExcelLoader,
UnstructuredMarkdownLoader,
UnstructuredPowerPointLoader,
UnstructuredRSTLoader,
UnstructuredXMLLoader,
WebBaseLoader,
YoutubeLoader,
)
from langchain_core.documents import Document
from pydantic import BaseModel
from open_webui.utils.misc import (
calculate_sha256,
calculate_sha256_string,
extract_folders_after_data_docs,
sanitize_filename,
)
from open_webui.utils.utils import get_admin_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -143,6 +123,8 @@ app.state.config = AppConfig()
app.state.config.TOP_K = RAG_TOP_K
app.state.config.RELEVANCE_THRESHOLD = RAG_RELEVANCE_THRESHOLD
app.state.config.FILE_MAX_SIZE = RAG_FILE_MAX_SIZE
app.state.config.FILE_MAX_COUNT = RAG_FILE_MAX_COUNT
app.state.config.ENABLE_RAG_HYBRID_SEARCH = ENABLE_RAG_HYBRID_SEARCH
app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = (
@@ -185,6 +167,8 @@ app.state.config.SERPSTACK_HTTPS = SERPSTACK_HTTPS
app.state.config.SERPER_API_KEY = SERPER_API_KEY
app.state.config.SERPLY_API_KEY = SERPLY_API_KEY
app.state.config.TAVILY_API_KEY = TAVILY_API_KEY
app.state.config.SEARCHAPI_API_KEY = SEARCHAPI_API_KEY
app.state.config.SEARCHAPI_ENGINE = SEARCHAPI_ENGINE
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = RAG_WEB_SEARCH_RESULT_COUNT
app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
@@ -393,6 +377,10 @@ async def get_rag_config(user=Depends(get_admin_user)):
return {
"status": True,
"pdf_extract_images": app.state.config.PDF_EXTRACT_IMAGES,
"file": {
"max_size": app.state.config.FILE_MAX_SIZE,
"max_count": app.state.config.FILE_MAX_COUNT,
},
"content_extraction": {
"engine": app.state.config.CONTENT_EXTRACTION_ENGINE,
"tika_server_url": app.state.config.TIKA_SERVER_URL,
@@ -419,6 +407,8 @@ async def get_rag_config(user=Depends(get_admin_user)):
"serper_api_key": app.state.config.SERPER_API_KEY,
"serply_api_key": app.state.config.SERPLY_API_KEY,
"tavily_api_key": app.state.config.TAVILY_API_KEY,
"searchapi_api_key": app.state.config.SEARCHAPI_API_KEY,
"seaarchapi_engine": app.state.config.SEARCHAPI_ENGINE,
"result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
"concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
},
@@ -426,6 +416,11 @@ async def get_rag_config(user=Depends(get_admin_user)):
}
class FileConfig(BaseModel):
max_size: Optional[int] = None
max_count: Optional[int] = None
class ContentExtractionConfig(BaseModel):
engine: str = ""
tika_server_url: Optional[str] = None
@@ -453,6 +448,8 @@ class WebSearchConfig(BaseModel):
serper_api_key: Optional[str] = None
serply_api_key: Optional[str] = None
tavily_api_key: Optional[str] = None
searchapi_api_key: Optional[str] = None
searchapi_engine: Optional[str] = None
result_count: Optional[int] = None
concurrent_requests: Optional[int] = None
@@ -464,6 +461,7 @@ class WebConfig(BaseModel):
class ConfigUpdateForm(BaseModel):
pdf_extract_images: Optional[bool] = None
file: Optional[FileConfig] = None
content_extraction: Optional[ContentExtractionConfig] = None
chunk: Optional[ChunkParamUpdateForm] = None
youtube: Optional[YoutubeLoaderConfig] = None
@@ -478,6 +476,10 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_
else app.state.config.PDF_EXTRACT_IMAGES
)
if form_data.file is not None:
app.state.config.FILE_MAX_SIZE = form_data.file.max_size
app.state.config.FILE_MAX_COUNT = form_data.file.max_count
if form_data.content_extraction is not None:
log.info(f"Updating text settings: {form_data.content_extraction}")
app.state.config.CONTENT_EXTRACTION_ENGINE = form_data.content_extraction.engine
@@ -511,6 +513,8 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_
app.state.config.SERPER_API_KEY = form_data.web.search.serper_api_key
app.state.config.SERPLY_API_KEY = form_data.web.search.serply_api_key
app.state.config.TAVILY_API_KEY = form_data.web.search.tavily_api_key
app.state.config.SEARCHAPI_API_KEY = form_data.web.search.searchapi_api_key
app.state.config.SEARCHAPI_ENGINE = form_data.web.search.searchapi_engine
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = form_data.web.search.result_count
app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = (
form_data.web.search.concurrent_requests
@@ -519,6 +523,10 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_
return {
"status": True,
"pdf_extract_images": app.state.config.PDF_EXTRACT_IMAGES,
"file": {
"max_size": app.state.config.FILE_MAX_SIZE,
"max_count": app.state.config.FILE_MAX_COUNT,
},
"content_extraction": {
"engine": app.state.config.CONTENT_EXTRACTION_ENGINE,
"tika_server_url": app.state.config.TIKA_SERVER_URL,
@@ -544,6 +552,8 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_
"serpstack_https": app.state.config.SERPSTACK_HTTPS,
"serper_api_key": app.state.config.SERPER_API_KEY,
"serply_api_key": app.state.config.SERPLY_API_KEY,
"serachapi_api_key": app.state.config.SEARCHAPI_API_KEY,
"searchapi_engine": app.state.config.SEARCHAPI_ENGINE,
"tavily_api_key": app.state.config.TAVILY_API_KEY,
"result_count": app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
"concurrent_requests": app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
@@ -590,6 +600,7 @@ async def update_query_settings(
app.state.config.ENABLE_RAG_HYBRID_SEARCH = (
form_data.hybrid if form_data.hybrid else False
)
return {
"status": True,
"template": app.state.config.RAG_TEMPLATE,
@@ -794,6 +805,7 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
- SERPER_API_KEY
- SERPLY_API_KEY
- TAVILY_API_KEY
- SEARCHAPI_API_KEY + SEARCHAPI_ENGINE (by default `google`)
Args:
query (str): The query to search for
"""
@@ -881,6 +893,17 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
)
else:
raise Exception("No TAVILY_API_KEY found in environment variables")
elif engine == "searchapi":
if app.state.config.SEARCHAPI_API_KEY:
return search_searchapi(
app.state.config.SEARCHAPI_API_KEY,
app.state.config.SEARCHAPI_ENGINE,
query,
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
)
else:
raise Exception("No SEARCHAPI_API_KEY found in environment variables")
elif engine == "jina":
return search_jina(query, app.state.config.RAG_WEB_SEARCH_RESULT_COUNT)
else:
@@ -931,7 +954,6 @@ def store_web_search(form_data: SearchForm, user=Depends(get_verified_user)):
def store_data_in_vector_db(
data, collection_name, metadata: Optional[dict] = None, overwrite: bool = False
) -> bool:
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=app.state.config.CHUNK_SIZE,
chunk_overlap=app.state.config.CHUNK_OVERLAP,
@@ -1292,7 +1314,6 @@ def store_text(
form_data: TextRAGForm,
user=Depends(get_verified_user),
):
collection_name = form_data.collection_name
if collection_name is None:
collection_name = calculate_sha256_string(form_data.content)
@@ -1373,12 +1394,12 @@ def scan_docs_dir(user=Depends(get_admin_user)):
return True
@app.get("/reset/db")
@app.post("/reset/db")
def reset_vector_db(user=Depends(get_admin_user)):
CHROMA_CLIENT.reset()
@app.get("/reset/uploads")
@app.post("/reset/uploads")
def reset_upload_dir(user=Depends(get_admin_user)) -> bool:
folder = f"{UPLOAD_DIR}"
try:
@@ -1402,7 +1423,7 @@ def reset_upload_dir(user=Depends(get_admin_user)) -> bool:
return True
@app.get("/reset")
@app.post("/reset")
def reset(user=Depends(get_admin_user)) -> bool:
folder = f"{UPLOAD_DIR}"
for filename in os.listdir(folder):

View File

@@ -1,9 +1,9 @@
import logging
from typing import Optional
import requests
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])

View File

@@ -1,8 +1,9 @@
import logging
from typing import Optional
from apps.rag.search.main import SearchResult, get_filtered_results
from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
from duckduckgo_search import DDGS
from config import SRC_LOG_LEVELS
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])

View File

@@ -1,10 +1,9 @@
import json
import logging
from typing import Optional
import requests
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])

View File

@@ -1,9 +1,9 @@
import logging
import requests
from yarl import URL
from apps.rag.search.main import SearchResult
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.rag.search.main import SearchResult
from open_webui.env import SRC_LOG_LEVELS
from yarl import URL
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])

View File

@@ -1,5 +1,6 @@
from typing import Optional
from urllib.parse import urlparse
from pydantic import BaseModel
@@ -8,7 +9,8 @@ def get_filtered_results(results, filter_list):
return results
filtered_results = []
for result in results:
domain = urlparse(result["url"]).netloc
url = result.get("url") or result.get("link", "")
domain = urlparse(url).netloc
if any(domain.endswith(filtered_domain) for filtered_domain in filter_list):
filtered_results.append(result)
return filtered_results

View File

@@ -0,0 +1,48 @@
import logging
from typing import Optional
from urllib.parse import urlencode
import requests
from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
def search_searchapi(
api_key: str,
engine: str,
query: str,
count: int,
filter_list: Optional[list[str]] = None,
) -> list[SearchResult]:
"""Search using searchapi.io's API and return the results as a list of SearchResult objects.
Args:
api_key (str): A searchapi.io API key
query (str): The query to search for
"""
url = "https://www.searchapi.io/api/v1/search"
engine = engine or "google"
payload = {"engine": engine, "q": query, "api_key": api_key}
url = f"{url}?{urlencode(payload)}"
response = requests.request("GET", url)
json_response = response.json()
log.info(f"results from searchapi search: {json_response}")
results = sorted(
json_response.get("organic_results", []), key=lambda x: x.get("position", 0)
)
if filter_list:
results = get_filtered_results(results, filter_list)
return [
SearchResult(
link=result["link"], title=result["title"], snippet=result["snippet"]
)
for result in results[:count]
]

View File

@@ -1,10 +1,9 @@
import logging
import requests
from typing import Optional
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])

View File

@@ -1,10 +1,10 @@
import json
import logging
from typing import Optional
import requests
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])

View File

@@ -1,11 +1,10 @@
import json
import logging
from typing import Optional
import requests
from urllib.parse import urlencode
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])

View File

@@ -1,10 +1,9 @@
import json
import logging
from typing import Optional
import requests
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])

View File

@@ -1,9 +1,8 @@
import logging
import requests
from apps.rag.search.main import SearchResult
from config import SRC_LOG_LEVELS
from open_webui.apps.rag.search.main import SearchResult
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])

File diff suppressed because one or more lines are too long

View File

@@ -1,27 +1,19 @@
import os
import logging
import os
from typing import Optional, Union
import requests
from typing import Union
from apps.ollama.main import (
generate_ollama_embeddings,
from open_webui.apps.ollama.main import (
GenerateEmbeddingsForm,
generate_ollama_embeddings,
)
from open_webui.config import CHROMA_CLIENT
from open_webui.env import SRC_LOG_LEVELS
from huggingface_hub import snapshot_download
from langchain_core.documents import Document
from langchain.retrievers import ContextualCompressionRetriever, EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import (
ContextualCompressionRetriever,
EnsembleRetriever,
)
from typing import Optional
from utils.misc import get_last_user_message, add_or_update_system_message
from config import SRC_LOG_LEVELS, CHROMA_CLIENT
from langchain_core.documents import Document
from open_webui.utils.misc import get_last_user_message
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -149,16 +141,20 @@ def query_collection(
):
results = []
for collection_name in collection_names:
try:
result = query_doc(
collection_name=collection_name,
query=query,
k=k,
embedding_function=embedding_function,
)
results.append(result)
except Exception:
if collection_name:
try:
result = query_doc(
collection_name=collection_name,
query=query,
k=k,
embedding_function=embedding_function,
)
results.append(result)
except Exception:
pass
else:
pass
return merge_and_sort_query_results(results, k=k)
@@ -257,7 +253,7 @@ def get_rag_context(
collection_names = (
file["collection_names"]
if file["type"] == "collection"
else [file["collection_name"]]
else [file["collection_name"]] if file["collection_name"] else []
)
collection_names = set(collection_names).difference(extracted_collections)
@@ -397,8 +393,8 @@ def generate_openai_batch_embeddings(
from typing import Any
from langchain_core.retrievers import BaseRetriever
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.retrievers import BaseRetriever
class ChromaRetriever(BaseRetriever):
@@ -435,11 +431,10 @@ class ChromaRetriever(BaseRetriever):
import operator
from typing import Optional, Sequence
from langchain_core.documents import BaseDocumentCompressor, Document
from langchain_core.callbacks import Callbacks
from langchain_core.documents import BaseDocumentCompressor, Document
from langchain_core.pydantic_v1 import Extra

View File

@@ -1,9 +1,8 @@
import socketio
import asyncio
from apps.webui.models.users import Users
from utils.utils import decode_token
import socketio
from open_webui.apps.webui.models.users import Users
from open_webui.utils.utils import decode_token
sio = socketio.AsyncServer(cors_allowed_origins=[], async_mode="asgi")
app = socketio.ASGIApp(sio, socketio_path="/ws/socket.io")

View File

@@ -1,20 +1,16 @@
import os
import logging
import json
import logging
from contextlib import contextmanager
from typing import Any, Optional
from open_webui.apps.webui.internal.wrappers import register_connection
from open_webui.env import OPEN_WEBUI_DIR, DATABASE_URL, SRC_LOG_LEVELS
from peewee_migrate import Router
from apps.webui.internal.wrappers import register_connection
from typing import Optional, Any
from typing_extensions import Self
from sqlalchemy import create_engine, types, Dialect
from sqlalchemy import Dialect, create_engine, types
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.sql.type_api import _T
from config import SRC_LOG_LEVELS, DATA_DIR, DATABASE_URL, BACKEND_DIR
from typing_extensions import Self
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["DB"])
@@ -42,34 +38,21 @@ class JSONField(types.TypeDecorator):
return json.loads(value)
# Check if the file exists
if os.path.exists(f"{DATA_DIR}/ollama.db"):
# Rename the file
os.rename(f"{DATA_DIR}/ollama.db", f"{DATA_DIR}/webui.db")
log.info("Database migrated from Ollama-WebUI successfully.")
else:
pass
# Workaround to handle the peewee migration
# This is required to ensure the peewee migration is handled before the alembic migration
def handle_peewee_migration(DATABASE_URL):
# db = None
try:
# Replace the postgresql:// with postgres:// and %40 with @ in the DATABASE_URL
db = register_connection(
DATABASE_URL.replace("postgresql://", "postgres://").replace("%40", "@")
)
migrate_dir = BACKEND_DIR / "apps" / "webui" / "internal" / "migrations"
# Replace the postgresql:// with postgres:// to handle the peewee migration
db = register_connection(DATABASE_URL.replace("postgresql://", "postgres://"))
migrate_dir = OPEN_WEBUI_DIR / "apps" / "webui" / "internal" / "migrations"
router = Router(db, logger=log, migrate_dir=migrate_dir)
router.run()
db.close()
# check if db connection has been closed
except Exception as e:
log.error(f"Failed to initialize the database connection: {e}")
raise
finally:
# Properly closing the database connection
if db and not db.is_closed():
@@ -98,7 +81,6 @@ Base = declarative_base()
Session = scoped_session(SessionLocal)
# Dependency
def get_session():
db = SessionLocal()
try:

View File

@@ -30,7 +30,7 @@ import peewee as pw
from peewee_migrate import Migrator
import json
from utils.misc import parse_ollama_modelfile
from open_webui.utils.misc import parse_ollama_modelfile
with suppress(ImportError):
import playhouse.postgres_ext as pw_pext

View File

@@ -1,13 +1,13 @@
from contextvars import ContextVar
from peewee import *
from peewee import PostgresqlDatabase, InterfaceError as PeeWeeInterfaceError
import logging
from contextvars import ContextVar
from open_webui.env import SRC_LOG_LEVELS
from peewee import *
from peewee import InterfaceError as PeeWeeInterfaceError
from peewee import PostgresqlDatabase
from playhouse.db_url import connect, parse
from playhouse.shortcuts import ReconnectMixin
from config import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["DB"])
@@ -43,7 +43,7 @@ class ReconnectingPostgresqlDatabase(CustomReconnectMixin, PostgresqlDatabase):
def register_connection(db_url):
db = connect(db_url)
db = connect(db_url, unquote_password=True)
if isinstance(db, PostgresqlDatabase):
# Enable autoconnect for SQLite databases, managed by Peewee
db.autoconnect = True
@@ -51,16 +51,10 @@ def register_connection(db_url):
log.info("Connected to PostgreSQL database")
# Get the connection details
connection = parse(db_url)
connection = parse(db_url, unquote_password=True)
# Use our custom database class that supports reconnection
db = ReconnectingPostgresqlDatabase(
connection["database"],
user=connection["user"],
password=connection["password"],
host=connection["host"],
port=connection["port"],
)
db = ReconnectingPostgresqlDatabase(**connection)
db.connect(reuse_if_open=True)
elif isinstance(db, SqliteDatabase):
# Enable autoconnect for SQLite databases, managed by Peewee

View File

@@ -1,67 +1,67 @@
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from apps.webui.routers import (
import inspect
import json
import logging
from typing import AsyncGenerator, Generator, Iterator
from open_webui.apps.socket.main import get_event_call, get_event_emitter
from open_webui.apps.webui.models.functions import Functions
from open_webui.apps.webui.models.models import Models
from open_webui.apps.webui.routers import (
auths,
users,
chats,
documents,
tools,
models,
prompts,
configs,
memories,
utils,
documents,
files,
functions,
memories,
models,
prompts,
tools,
users,
utils,
)
from apps.webui.models.functions import Functions
from apps.webui.models.models import Models
from apps.webui.utils import load_function_module_by_id
from utils.misc import (
openai_chat_chunk_message_template,
openai_chat_completion_message_template,
apply_model_params_to_body_openai,
apply_model_system_prompt_to_body,
)
from utils.tools import get_tools
from config import (
SHOW_ADMIN_DETAILS,
from open_webui.apps.webui.utils import load_function_module_by_id
from open_webui.config import (
ADMIN_EMAIL,
WEBUI_AUTH,
CORS_ALLOW_ORIGIN,
DEFAULT_MODELS,
DEFAULT_PROMPT_SUGGESTIONS,
DEFAULT_USER_ROLE,
ENABLE_SIGNUP,
ENABLE_COMMUNITY_SHARING,
ENABLE_LOGIN_FORM,
ENABLE_MESSAGE_RATING,
ENABLE_SIGNUP,
JWT_EXPIRES_IN,
OAUTH_EMAIL_CLAIM,
OAUTH_PICTURE_CLAIM,
OAUTH_USERNAME_CLAIM,
SHOW_ADMIN_DETAILS,
USER_PERMISSIONS,
WEBHOOK_URL,
WEBUI_AUTH,
WEBUI_BANNERS,
AppConfig,
)
from open_webui.env import (
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
WEBUI_AUTH_TRUSTED_NAME_HEADER,
JWT_EXPIRES_IN,
WEBUI_BANNERS,
ENABLE_COMMUNITY_SHARING,
ENABLE_MESSAGE_RATING,
AppConfig,
OAUTH_USERNAME_CLAIM,
OAUTH_PICTURE_CLAIM,
OAUTH_EMAIL_CLAIM,
CORS_ALLOW_ORIGIN,
)
from apps.socket.main import get_event_call, get_event_emitter
import inspect
import json
from typing import Iterator, Generator, AsyncGenerator
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from open_webui.utils.misc import (
apply_model_params_to_body_openai,
apply_model_system_prompt_to_body,
openai_chat_chunk_message_template,
openai_chat_completion_message_template,
)
from open_webui.utils.tools import get_tools
app = FastAPI()
log = logging.getLogger(__name__)
app.state.config = AppConfig()
app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP
@@ -243,43 +243,37 @@ def get_pipe_id(form_data: dict) -> str:
return pipe_id
def get_function_params(function_module, form_data, user, extra_params={}):
def get_function_params(function_module, form_data, user, extra_params=None):
if extra_params is None:
extra_params = {}
pipe_id = get_pipe_id(form_data)
# Get the signature of the function
sig = inspect.signature(function_module.pipe)
params = {"body": form_data}
for key, value in extra_params.items():
if key in sig.parameters:
params[key] = value
if "__user__" in sig.parameters:
__user__ = {
"id": user.id,
"email": user.email,
"name": user.name,
"role": user.role,
}
params = {"body": form_data} | {
k: v for k, v in extra_params.items() if k in sig.parameters
}
if "__user__" in params and hasattr(function_module, "UserValves"):
user_valves = Functions.get_user_valves_by_id_and_user_id(pipe_id, user.id)
try:
if hasattr(function_module, "UserValves"):
__user__["valves"] = function_module.UserValves(
**Functions.get_user_valves_by_id_and_user_id(pipe_id, user.id)
)
params["__user__"]["valves"] = function_module.UserValves(**user_valves)
except Exception as e:
print(e)
log.exception(e)
params["__user__"]["valves"] = function_module.UserValves()
params["__user__"] = __user__
return params
async def generate_function_chat_completion(form_data, user):
model_id = form_data.get("model")
model_info = Models.get_model_by_id(model_id)
metadata = form_data.pop("metadata", {})
files = metadata.get("files", [])
tool_ids = metadata.get("tool_ids", [])
# Check if tool_ids is None
if tool_ids is None:
tool_ids = []
@@ -298,16 +292,25 @@ async def generate_function_chat_completion(form_data, user):
"__event_emitter__": __event_emitter__,
"__event_call__": __event_call__,
"__task__": __task__,
}
tools_params = {
**extra_params,
"__model__": app.state.MODELS[form_data["model"]],
"__messages__": form_data["messages"],
"__files__": files,
"__user__": {
"id": user.id,
"email": user.email,
"name": user.name,
"role": user.role,
},
}
tools = get_tools(app, tool_ids, user, tools_params)
extra_params["__tools__"] = tools
extra_params["__tools__"] = get_tools(
app,
tool_ids,
user,
{
**extra_params,
"__model__": app.state.MODELS[form_data["model"]],
"__messages__": form_data["messages"],
"__files__": files,
},
)
if model_info:
if model_info.base_model_id:

View File

@@ -1,15 +1,13 @@
from pydantic import BaseModel
from typing import Optional
import uuid
import logging
from sqlalchemy import String, Column, Boolean, Text
import uuid
from typing import Optional
from apps.webui.models.users import UserModel, Users
from utils.utils import verify_password
from apps.webui.internal.db import Base, get_db
from config import SRC_LOG_LEVELS
from open_webui.apps.webui.internal.db import Base, get_db
from open_webui.apps.webui.models.users import UserModel, Users
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel
from sqlalchemy import Boolean, Column, String, Text
from open_webui.utils.utils import verify_password
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -92,7 +90,6 @@ class AddUserForm(SignupForm):
class AuthsTable:
def insert_new_auth(
self,
email: str,
@@ -103,7 +100,6 @@ class AuthsTable:
oauth_sub: Optional[str] = None,
) -> Optional[UserModel]:
with get_db() as db:
log.info("insert_new_auth")
id = str(uuid.uuid4())
@@ -130,7 +126,6 @@ class AuthsTable:
log.info(f"authenticate_user: {email}")
try:
with get_db() as db:
auth = db.query(Auth).filter_by(email=email, active=True).first()
if auth:
if verify_password(password, auth.password):
@@ -189,7 +184,6 @@ class AuthsTable:
def delete_auth_by_id(self, id: str) -> bool:
try:
with get_db() as db:
# Delete User
result = Users.delete_user_by_id(id)

View File

@@ -1,14 +1,11 @@
from pydantic import BaseModel, ConfigDict
from typing import Union, Optional
import json
import uuid
import time
import uuid
from typing import Optional
from sqlalchemy import Column, String, BigInteger, Boolean, Text
from apps.webui.internal.db import Base, get_db
from open_webui.apps.webui.internal.db import Base, get_db
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Boolean, Column, String, Text
####################
# Chat DB Schema
@@ -77,10 +74,8 @@ class ChatTitleIdResponse(BaseModel):
class ChatTable:
def insert_new_chat(self, user_id: str, form_data: ChatForm) -> Optional[ChatModel]:
with get_db() as db:
id = str(uuid.uuid4())
chat = ChatModel(
**{
@@ -106,7 +101,6 @@ class ChatTable:
def update_chat_by_id(self, id: str, chat: dict) -> Optional[ChatModel]:
try:
with get_db() as db:
chat_obj = db.get(Chat, id)
chat_obj.chat = json.dumps(chat)
chat_obj.title = chat["title"] if "title" in chat else "New Chat"
@@ -115,12 +109,11 @@ class ChatTable:
db.refresh(chat_obj)
return ChatModel.model_validate(chat_obj)
except Exception as e:
except Exception:
return None
def insert_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]:
with get_db() as db:
# Get the existing chat to share
chat = db.get(Chat, chat_id)
# Check if the chat is already shared
@@ -154,7 +147,6 @@ class ChatTable:
def update_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]:
try:
with get_db() as db:
print("update_shared_chat_by_id")
chat = db.get(Chat, chat_id)
print(chat)
@@ -170,7 +162,6 @@ class ChatTable:
def delete_shared_chat_by_chat_id(self, chat_id: str) -> bool:
try:
with get_db() as db:
db.query(Chat).filter_by(user_id=f"shared-{chat_id}").delete()
db.commit()
@@ -183,7 +174,6 @@ class ChatTable:
) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.get(Chat, id)
chat.share_id = share_id
db.commit()
@@ -195,7 +185,6 @@ class ChatTable:
def toggle_chat_archive_by_id(self, id: str) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.get(Chat, id)
chat.archived = not chat.archived
db.commit()
@@ -217,7 +206,6 @@ class ChatTable:
self, user_id: str, skip: int = 0, limit: int = 50
) -> list[ChatModel]:
with get_db() as db:
all_chats = (
db.query(Chat)
.filter_by(user_id=user_id, archived=True)
@@ -249,22 +237,25 @@ class ChatTable:
self,
user_id: str,
include_archived: bool = False,
skip: int = 0,
limit: int = -1,
skip: Optional[int] = None,
limit: Optional[int] = None,
) -> list[ChatTitleIdResponse]:
with get_db() as db:
query = db.query(Chat).filter_by(user_id=user_id)
if not include_archived:
query = query.filter_by(archived=False)
all_chats = (
query.order_by(Chat.updated_at.desc())
# limit cols
.with_entities(Chat.id, Chat.title, Chat.updated_at, Chat.created_at)
.limit(limit)
.offset(skip)
.all()
query = query.order_by(Chat.updated_at.desc()).with_entities(
Chat.id, Chat.title, Chat.updated_at, Chat.created_at
)
if limit:
query = query.limit(limit)
if skip:
query = query.offset(skip)
all_chats = query.all()
# result has to be destrctured from sqlalchemy `row` and mapped to a dict since the `ChatModel`is not the returned dataclass.
return [
ChatTitleIdResponse.model_validate(
@@ -294,7 +285,6 @@ class ChatTable:
def get_chat_by_id(self, id: str) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.get(Chat, id)
return ChatModel.model_validate(chat)
except Exception:
@@ -303,20 +293,18 @@ class ChatTable:
def get_chat_by_share_id(self, id: str) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.query(Chat).filter_by(share_id=id).first()
if chat:
return self.get_chat_by_id(id)
else:
return None
except Exception as e:
except Exception:
return None
def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.query(Chat).filter_by(id=id, user_id=user_id).first()
return ChatModel.model_validate(chat)
except Exception:
@@ -324,7 +312,6 @@ class ChatTable:
def get_chats(self, skip: int = 0, limit: int = 50) -> list[ChatModel]:
with get_db() as db:
all_chats = (
db.query(Chat)
# .limit(limit).offset(skip)
@@ -334,7 +321,6 @@ class ChatTable:
def get_chats_by_user_id(self, user_id: str) -> list[ChatModel]:
with get_db() as db:
all_chats = (
db.query(Chat)
.filter_by(user_id=user_id)
@@ -344,7 +330,6 @@ class ChatTable:
def get_archived_chats_by_user_id(self, user_id: str) -> list[ChatModel]:
with get_db() as db:
all_chats = (
db.query(Chat)
.filter_by(user_id=user_id, archived=True)
@@ -355,7 +340,6 @@ class ChatTable:
def delete_chat_by_id(self, id: str) -> bool:
try:
with get_db() as db:
db.query(Chat).filter_by(id=id).delete()
db.commit()
@@ -366,7 +350,6 @@ class ChatTable:
def delete_chat_by_id_and_user_id(self, id: str, user_id: str) -> bool:
try:
with get_db() as db:
db.query(Chat).filter_by(id=id, user_id=user_id).delete()
db.commit()
@@ -376,9 +359,7 @@ class ChatTable:
def delete_chats_by_user_id(self, user_id: str) -> bool:
try:
with get_db() as db:
self.delete_shared_chats_by_user_id(user_id)
db.query(Chat).filter_by(user_id=user_id).delete()
@@ -390,9 +371,7 @@ class ChatTable:
def delete_shared_chats_by_user_id(self, user_id: str) -> bool:
try:
with get_db() as db:
chats_by_user = db.query(Chat).filter_by(user_id=user_id).all()
shared_chat_ids = [f"shared-{chat.id}" for chat in chats_by_user]

View File

@@ -1,15 +1,12 @@
from pydantic import BaseModel, ConfigDict
from typing import Optional
import time
import logging
from sqlalchemy import String, Column, BigInteger, Text
from apps.webui.internal.db import Base, get_db
import json
import logging
import time
from typing import Optional
from config import SRC_LOG_LEVELS
from open_webui.apps.webui.internal.db import Base, get_db
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -70,12 +67,10 @@ class DocumentForm(DocumentUpdateForm):
class DocumentsTable:
def insert_new_doc(
self, user_id: str, form_data: DocumentForm
) -> Optional[DocumentModel]:
with get_db() as db:
document = DocumentModel(
**{
**form_data.model_dump(),
@@ -99,7 +94,6 @@ class DocumentsTable:
def get_doc_by_name(self, name: str) -> Optional[DocumentModel]:
try:
with get_db() as db:
document = db.query(Document).filter_by(name=name).first()
return DocumentModel.model_validate(document) if document else None
except Exception:
@@ -107,7 +101,6 @@ class DocumentsTable:
def get_docs(self) -> list[DocumentModel]:
with get_db() as db:
return [
DocumentModel.model_validate(doc) for doc in db.query(Document).all()
]
@@ -117,7 +110,6 @@ class DocumentsTable:
) -> Optional[DocumentModel]:
try:
with get_db() as db:
db.query(Document).filter_by(name=name).update(
{
"title": form_data.title,
@@ -140,7 +132,6 @@ class DocumentsTable:
doc_content = {**doc_content, **updated}
with get_db() as db:
db.query(Document).filter_by(name=name).update(
{
"content": json.dumps(doc_content),
@@ -156,7 +147,6 @@ class DocumentsTable:
def delete_doc_by_name(self, name: str) -> bool:
try:
with get_db() as db:
db.query(Document).filter_by(name=name).delete()
db.commit()
return True

View File

@@ -1,15 +1,11 @@
from pydantic import BaseModel, ConfigDict
from typing import Union, Optional
import time
import logging
import time
from typing import Optional
from sqlalchemy import Column, String, BigInteger, Text
from apps.webui.internal.db import JSONField, Base, get_db
import json
from config import SRC_LOG_LEVELS
from open_webui.apps.webui.internal.db import Base, JSONField, get_db
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -59,10 +55,8 @@ class FileForm(BaseModel):
class FilesTable:
def insert_new_file(self, user_id: str, form_data: FileForm) -> Optional[FileModel]:
with get_db() as db:
file = FileModel(
**{
**form_data.model_dump(),
@@ -86,7 +80,6 @@ class FilesTable:
def get_file_by_id(self, id: str) -> Optional[FileModel]:
with get_db() as db:
try:
file = db.get(File, id)
return FileModel.model_validate(file)
@@ -95,13 +88,17 @@ class FilesTable:
def get_files(self) -> list[FileModel]:
with get_db() as db:
return [FileModel.model_validate(file) for file in db.query(File).all()]
def delete_file_by_id(self, id: str) -> bool:
def get_files_by_user_id(self, user_id: str) -> list[FileModel]:
with get_db() as db:
return [
FileModel.model_validate(file)
for file in db.query(File).filter_by(user_id=user_id).all()
]
def delete_file_by_id(self, id: str) -> bool:
with get_db() as db:
try:
db.query(File).filter_by(id=id).delete()
db.commit()
@@ -111,9 +108,7 @@ class FilesTable:
return False
def delete_all_files(self) -> bool:
with get_db() as db:
try:
db.query(File).delete()
db.commit()

View File

@@ -1,18 +1,12 @@
from pydantic import BaseModel, ConfigDict
from typing import Union, Optional
import time
import logging
import time
from typing import Optional
from sqlalchemy import Column, String, Text, BigInteger, Boolean
from apps.webui.internal.db import JSONField, Base, get_db
from apps.webui.models.users import Users
import json
import copy
from config import SRC_LOG_LEVELS
from open_webui.apps.webui.internal.db import Base, JSONField, get_db
from open_webui.apps.webui.models.users import Users
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Boolean, Column, String, Text
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -87,11 +81,9 @@ class FunctionValves(BaseModel):
class FunctionsTable:
def insert_new_function(
self, user_id: str, type: str, form_data: FunctionForm
) -> Optional[FunctionModel]:
function = FunctionModel(
**{
**form_data.model_dump(),
@@ -119,7 +111,6 @@ class FunctionsTable:
def get_function_by_id(self, id: str) -> Optional[FunctionModel]:
try:
with get_db() as db:
function = db.get(Function, id)
return FunctionModel.model_validate(function)
except Exception:
@@ -127,7 +118,6 @@ class FunctionsTable:
def get_functions(self, active_only=False) -> list[FunctionModel]:
with get_db() as db:
if active_only:
return [
FunctionModel.model_validate(function)
@@ -143,7 +133,6 @@ class FunctionsTable:
self, type: str, active_only=False
) -> list[FunctionModel]:
with get_db() as db:
if active_only:
return [
FunctionModel.model_validate(function)
@@ -159,7 +148,6 @@ class FunctionsTable:
def get_global_filter_functions(self) -> list[FunctionModel]:
with get_db() as db:
return [
FunctionModel.model_validate(function)
for function in db.query(Function)
@@ -178,7 +166,6 @@ class FunctionsTable:
def get_function_valves_by_id(self, id: str) -> Optional[dict]:
with get_db() as db:
try:
function = db.get(Function, id)
return function.valves if function.valves else {}
@@ -190,7 +177,6 @@ class FunctionsTable:
self, id: str, valves: dict
) -> Optional[FunctionValves]:
with get_db() as db:
try:
function = db.get(Function, id)
function.valves = valves
@@ -204,7 +190,6 @@ class FunctionsTable:
def get_user_valves_by_id_and_user_id(
self, id: str, user_id: str
) -> Optional[dict]:
try:
user = Users.get_user_by_id(user_id)
user_settings = user.settings.model_dump() if user.settings else {}
@@ -223,7 +208,6 @@ class FunctionsTable:
def update_user_valves_by_id_and_user_id(
self, id: str, user_id: str, valves: dict
) -> Optional[dict]:
try:
user = Users.get_user_by_id(user_id)
user_settings = user.settings.model_dump() if user.settings else {}
@@ -246,7 +230,6 @@ class FunctionsTable:
def update_function_by_id(self, id: str, updated: dict) -> Optional[FunctionModel]:
with get_db() as db:
try:
db.query(Function).filter_by(id=id).update(
{
@@ -261,7 +244,6 @@ class FunctionsTable:
def deactivate_all_functions(self) -> Optional[bool]:
with get_db() as db:
try:
db.query(Function).update(
{

View File

@@ -1,12 +1,10 @@
from pydantic import BaseModel, ConfigDict
from typing import Union, Optional
from sqlalchemy import Column, String, BigInteger, Text
from apps.webui.internal.db import Base, get_db
import time
import uuid
from typing import Optional
from open_webui.apps.webui.internal.db import Base, get_db
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
####################
# Memory DB Schema
@@ -39,13 +37,11 @@ class MemoryModel(BaseModel):
class MemoriesTable:
def insert_new_memory(
self,
user_id: str,
content: str,
) -> Optional[MemoryModel]:
with get_db() as db:
id = str(uuid.uuid4())
@@ -73,7 +69,6 @@ class MemoriesTable:
content: str,
) -> Optional[MemoryModel]:
with get_db() as db:
try:
db.query(Memory).filter_by(id=id).update(
{"content": content, "updated_at": int(time.time())}
@@ -85,7 +80,6 @@ class MemoriesTable:
def get_memories(self) -> list[MemoryModel]:
with get_db() as db:
try:
memories = db.query(Memory).all()
return [MemoryModel.model_validate(memory) for memory in memories]
@@ -94,7 +88,6 @@ class MemoriesTable:
def get_memories_by_user_id(self, user_id: str) -> list[MemoryModel]:
with get_db() as db:
try:
memories = db.query(Memory).filter_by(user_id=user_id).all()
return [MemoryModel.model_validate(memory) for memory in memories]
@@ -103,7 +96,6 @@ class MemoriesTable:
def get_memory_by_id(self, id: str) -> Optional[MemoryModel]:
with get_db() as db:
try:
memory = db.get(Memory, id)
return MemoryModel.model_validate(memory)
@@ -112,7 +104,6 @@ class MemoriesTable:
def delete_memory_by_id(self, id: str) -> bool:
with get_db() as db:
try:
db.query(Memory).filter_by(id=id).delete()
db.commit()
@@ -124,7 +115,6 @@ class MemoriesTable:
def delete_memories_by_user_id(self, user_id: str) -> bool:
with get_db() as db:
try:
db.query(Memory).filter_by(user_id=user_id).delete()
db.commit()
@@ -135,7 +125,6 @@ class MemoriesTable:
def delete_memory_by_id_and_user_id(self, id: str, user_id: str) -> bool:
with get_db() as db:
try:
db.query(Memory).filter_by(id=id, user_id=user_id).delete()
db.commit()

View File

@@ -1,14 +1,11 @@
import logging
from typing import Optional, List
from pydantic import BaseModel, ConfigDict
from sqlalchemy import Column, BigInteger, Text
from apps.webui.internal.db import Base, JSONField, get_db
from config import SRC_LOG_LEVELS
import time
from typing import Optional
from open_webui.apps.webui.internal.db import Base, JSONField, get_db
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, Text
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])

View File

@@ -1,12 +1,9 @@
from pydantic import BaseModel, ConfigDict
from typing import Optional
import time
from typing import Optional
from sqlalchemy import String, Column, BigInteger, Text
from apps.webui.internal.db import Base, get_db
import json
from open_webui.apps.webui.internal.db import Base, get_db
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
####################
# Prompts DB Schema
@@ -45,7 +42,6 @@ class PromptForm(BaseModel):
class PromptsTable:
def insert_new_prompt(
self, user_id: str, form_data: PromptForm
) -> Optional[PromptModel]:
@@ -61,7 +57,6 @@ class PromptsTable:
try:
with get_db() as db:
result = Prompt(**prompt.dict())
db.add(result)
db.commit()
@@ -70,13 +65,12 @@ class PromptsTable:
return PromptModel.model_validate(result)
else:
return None
except Exception as e:
except Exception:
return None
def get_prompt_by_command(self, command: str) -> Optional[PromptModel]:
try:
with get_db() as db:
prompt = db.query(Prompt).filter_by(command=command).first()
return PromptModel.model_validate(prompt)
except Exception:
@@ -84,7 +78,6 @@ class PromptsTable:
def get_prompts(self) -> list[PromptModel]:
with get_db() as db:
return [
PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all()
]
@@ -94,7 +87,6 @@ class PromptsTable:
) -> Optional[PromptModel]:
try:
with get_db() as db:
prompt = db.query(Prompt).filter_by(command=command).first()
prompt.title = form_data.title
prompt.content = form_data.content
@@ -107,7 +99,6 @@ class PromptsTable:
def delete_prompt_by_command(self, command: str) -> bool:
try:
with get_db() as db:
db.query(Prompt).filter_by(command=command).delete()
db.commit()

View File

@@ -1,16 +1,12 @@
from pydantic import BaseModel, ConfigDict
import logging
import time
import uuid
from typing import Optional
import json
import uuid
import time
import logging
from sqlalchemy import String, Column, BigInteger, Text
from apps.webui.internal.db import Base, get_db
from config import SRC_LOG_LEVELS
from open_webui.apps.webui.internal.db import Base, get_db
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -77,10 +73,8 @@ class ChatTagsResponse(BaseModel):
class TagTable:
def insert_new_tag(self, name: str, user_id: str) -> Optional[TagModel]:
with get_db() as db:
id = str(uuid.uuid4())
tag = TagModel(**{"id": id, "user_id": user_id, "name": name})
try:
@@ -92,7 +86,7 @@ class TagTable:
return TagModel.model_validate(result)
else:
return None
except Exception as e:
except Exception:
return None
def get_tag_by_name_and_user_id(
@@ -102,7 +96,7 @@ class TagTable:
with get_db() as db:
tag = db.query(Tag).filter_by(name=name, user_id=user_id).first()
return TagModel.model_validate(tag)
except Exception as e:
except Exception:
return None
def add_tag_to_chat(
@@ -161,7 +155,6 @@ class TagTable:
self, chat_id: str, user_id: str
) -> list[TagModel]:
with get_db() as db:
tag_names = [
chat_id_tag.tag_name
for chat_id_tag in (
@@ -186,7 +179,6 @@ class TagTable:
self, tag_name: str, user_id: str
) -> list[ChatIdTagModel]:
with get_db() as db:
return [
ChatIdTagModel.model_validate(chat_id_tag)
for chat_id_tag in (
@@ -201,7 +193,6 @@ class TagTable:
self, tag_name: str, user_id: str
) -> int:
with get_db() as db:
return (
db.query(ChatIdTag)
.filter_by(tag_name=tag_name, user_id=user_id)
@@ -236,7 +227,6 @@ class TagTable:
) -> bool:
try:
with get_db() as db:
res = (
db.query(ChatIdTag)
.filter_by(tag_name=tag_name, chat_id=chat_id, user_id=user_id)

View File

@@ -1,17 +1,12 @@
from pydantic import BaseModel, ConfigDict
from typing import Optional
import time
import logging
from sqlalchemy import String, Column, BigInteger, Text
import time
from typing import Optional
from apps.webui.internal.db import Base, JSONField, get_db
from apps.webui.models.users import Users
import json
import copy
from config import SRC_LOG_LEVELS
from open_webui.apps.webui.internal.db import Base, JSONField, get_db
from open_webui.apps.webui.models.users import Users
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -79,13 +74,10 @@ class ToolValves(BaseModel):
class ToolsTable:
def insert_new_tool(
self, user_id: str, form_data: ToolForm, specs: list[dict]
) -> Optional[ToolModel]:
with get_db() as db:
tool = ToolModel(
**{
**form_data.model_dump(),
@@ -112,7 +104,6 @@ class ToolsTable:
def get_tool_by_id(self, id: str) -> Optional[ToolModel]:
try:
with get_db() as db:
tool = db.get(Tool, id)
return ToolModel.model_validate(tool)
except Exception:
@@ -125,7 +116,6 @@ class ToolsTable:
def get_tool_valves_by_id(self, id: str) -> Optional[dict]:
try:
with get_db() as db:
tool = db.get(Tool, id)
return tool.valves if tool.valves else {}
except Exception as e:
@@ -135,7 +125,6 @@ class ToolsTable:
def update_tool_valves_by_id(self, id: str, valves: dict) -> Optional[ToolValves]:
try:
with get_db() as db:
db.query(Tool).filter_by(id=id).update(
{"valves": valves, "updated_at": int(time.time())}
)

View File

@@ -1,11 +1,10 @@
from pydantic import BaseModel, ConfigDict
from typing import Optional
import time
from typing import Optional
from sqlalchemy import String, Column, BigInteger, Text
from apps.webui.internal.db import Base, JSONField, get_db
from apps.webui.models.chats import Chats
from open_webui.apps.webui.internal.db import Base, JSONField, get_db
from open_webui.apps.webui.models.chats import Chats
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
####################
# User DB Schema
@@ -113,7 +112,7 @@ class UsersTable:
with get_db() as db:
user = db.query(User).filter_by(id=id).first()
return UserModel.model_validate(user)
except Exception as e:
except Exception:
return None
def get_user_by_api_key(self, api_key: str) -> Optional[UserModel]:
@@ -221,7 +220,7 @@ class UsersTable:
user = db.query(User).filter_by(id=id).first()
return UserModel.model_validate(user)
# return UserModel(**user.dict())
except Exception as e:
except Exception:
return None
def delete_user_by_id(self, id: str) -> bool:
@@ -255,7 +254,7 @@ class UsersTable:
with get_db() as db:
user = db.query(User).filter_by(id=id).first()
return user.api_key
except Exception as e:
except Exception:
return None

View File

@@ -1,43 +1,36 @@
import logging
from fastapi import Request, UploadFile, File
from fastapi import Depends, HTTPException, status
from fastapi.responses import Response
from fastapi import APIRouter
from pydantic import BaseModel
import re
import uuid
import csv
from apps.webui.models.auths import (
SigninForm,
SignupForm,
from open_webui.apps.webui.models.auths import (
AddUserForm,
UpdateProfileForm,
UpdatePasswordForm,
UserResponse,
SigninResponse,
Auths,
ApiKey,
Auths,
SigninForm,
SigninResponse,
SignupForm,
UpdatePasswordForm,
UpdateProfileForm,
UserResponse,
)
from apps.webui.models.users import Users
from utils.utils import (
get_password_hash,
get_current_user,
get_admin_user,
create_token,
create_api_key,
)
from utils.misc import parse_duration, validate_email_format
from utils.webhook import post_webhook
from constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
from config import (
WEBUI_AUTH,
from open_webui.apps.webui.models.users import Users
from open_webui.config import WEBUI_AUTH
from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
from open_webui.env import (
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
WEBUI_AUTH_TRUSTED_NAME_HEADER,
)
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import Response
from pydantic import BaseModel
from open_webui.utils.misc import parse_duration, validate_email_format
from open_webui.utils.utils import (
create_api_key,
create_token,
get_admin_user,
get_current_user,
get_password_hash,
)
from open_webui.utils.webhook import post_webhook
router = APIRouter()
@@ -195,7 +188,11 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
@router.post("/signup", response_model=SigninResponse)
async def signup(request: Request, response: Response, form_data: SignupForm):
if not request.app.state.config.ENABLE_SIGNUP and WEBUI_AUTH:
if (
not request.app.state.config.ENABLE_SIGNUP
and request.app.state.config.ENABLE_LOGIN_FORM
and WEBUI_AUTH
):
raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
)
@@ -228,7 +225,6 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
data={"id": user.id},
expires_delta=parse_duration(request.app.state.config.JWT_EXPIRES_IN),
)
# response.set_cookie(key='token', value=token, httponly=True)
# Set the cookie token
response.set_cookie(
@@ -270,7 +266,6 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
@router.post("/add", response_model=SigninResponse)
async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)):
if not validate_email_format(form_data.email.lower()):
raise HTTPException(
status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
@@ -280,7 +275,6 @@ async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)):
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
try:
print(form_data)
hashed = get_password_hash(form_data.password)
user = Auths.insert_new_auth(

View File

@@ -1,34 +1,25 @@
from fastapi import Depends, Request, HTTPException, status
from datetime import datetime, timedelta
from typing import Union, Optional
from utils.utils import get_verified_user, get_admin_user
from fastapi import APIRouter
from pydantic import BaseModel
import json
import logging
from typing import Optional
from apps.webui.models.users import Users
from apps.webui.models.chats import (
ChatModel,
ChatResponse,
ChatTitleForm,
from open_webui.apps.webui.models.chats import (
ChatForm,
ChatTitleIdResponse,
ChatResponse,
Chats,
ChatTitleIdResponse,
)
from apps.webui.models.tags import (
TagModel,
ChatIdTagModel,
from open_webui.apps.webui.models.tags import (
ChatIdTagForm,
ChatTagsResponse,
ChatIdTagModel,
TagModel,
Tags,
)
from constants import ERROR_MESSAGES
from config import SRC_LOG_LEVELS, ENABLE_ADMIN_EXPORT, ENABLE_ADMIN_CHAT_ACCESS
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
from open_webui.utils.utils import get_admin_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -61,7 +52,6 @@ async def get_session_user_chat_list(
@router.delete("/", response_model=bool)
async def delete_all_user_chats(request: Request, user=Depends(get_verified_user)):
if (
user.role == "user"
and not request.app.state.config.USER_PERMISSIONS["chat"]["deletion"]
@@ -220,7 +210,6 @@ class TagNameForm(BaseModel):
async def get_user_chat_list_by_tag_name(
form_data: TagNameForm, user=Depends(get_verified_user)
):
chat_ids = [
chat_id_tag.chat_id
for chat_id_tag in Tags.get_chat_ids_by_tag_name_and_user_id(
@@ -299,7 +288,6 @@ async def update_chat_by_id(
@router.delete("/{id}", response_model=bool)
async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified_user)):
if user.role == "admin":
result = Chats.delete_chat_by_id(id)
return result
@@ -323,7 +311,6 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified
async def clone_chat_by_id(id: str, user=Depends(get_verified_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
chat_body = json.loads(chat.chat)
updated_chat = {
**chat_body,

View File

@@ -1,29 +1,39 @@
from fastapi import Response, Request
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import Union
from fastapi import APIRouter
from open_webui.config import BannerModel
from fastapi import APIRouter, Depends, Request
from pydantic import BaseModel
import time
import uuid
from open_webui.utils.utils import get_admin_user, get_verified_user
from config import BannerModel
from apps.webui.models.users import Users
from utils.utils import (
get_password_hash,
get_verified_user,
get_admin_user,
create_token,
)
from utils.misc import get_gravatar_url, validate_email_format
from constants import ERROR_MESSAGES
from open_webui.config import get_config, save_config
router = APIRouter()
############################
# ImportConfig
############################
class ImportConfigForm(BaseModel):
config: dict
@router.post("/import", response_model=dict)
async def import_config(form_data: ImportConfigForm, user=Depends(get_admin_user)):
save_config(form_data.config)
return get_config()
############################
# ExportConfig
############################
@router.get("/export", response_model=dict)
async def export_config(user=Depends(get_admin_user)):
return get_config()
class SetDefaultModelsForm(BaseModel):
models: str

View File

@@ -1,21 +1,16 @@
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import Union, Optional
from fastapi import APIRouter
from pydantic import BaseModel
import json
from typing import Optional
from apps.webui.models.documents import (
Documents,
from open_webui.apps.webui.models.documents import (
DocumentForm,
DocumentUpdateForm,
DocumentModel,
DocumentResponse,
Documents,
DocumentUpdateForm,
)
from utils.utils import get_verified_user, get_admin_user
from constants import ERROR_MESSAGES
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from open_webui.utils.utils import get_admin_user, get_verified_user
router = APIRouter()

View File

@@ -1,42 +1,17 @@
from fastapi import (
Depends,
FastAPI,
HTTPException,
status,
Request,
UploadFile,
File,
Form,
)
from datetime import datetime, timedelta
from typing import Union, Optional
from pathlib import Path
from fastapi import APIRouter
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
from pydantic import BaseModel
import json
from apps.webui.models.files import (
Files,
FileForm,
FileModel,
FileModelResponse,
)
from utils.utils import get_verified_user, get_admin_user
from constants import ERROR_MESSAGES
from importlib import util
import logging
import os
import shutil
import uuid
import os, shutil, logging, re
from config import SRC_LOG_LEVELS, UPLOAD_DIR
from pathlib import Path
from typing import Optional
from open_webui.apps.webui.models.files import FileForm, FileModel, Files
from open_webui.config import UPLOAD_DIR
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from fastapi.responses import FileResponse
from open_webui.utils.utils import get_admin_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -106,7 +81,10 @@ def upload_file(file: UploadFile = File(...), user=Depends(get_verified_user)):
@router.get("/", response_model=list[FileModel])
async def list_files(user=Depends(get_verified_user)):
files = Files.get_files()
if user.role == "admin":
files = Files.get_files()
else:
files = Files.get_files_by_user_id(user.id)
return files
@@ -156,7 +134,7 @@ async def delete_all_files(user=Depends(get_admin_user)):
async def get_file_by_id(id: str, user=Depends(get_verified_user)):
file = Files.get_file_by_id(id)
if file:
if file and (file.user_id == user.id or user.role == "admin"):
return file
else:
raise HTTPException(
@@ -174,7 +152,7 @@ async def get_file_by_id(id: str, user=Depends(get_verified_user)):
async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
file = Files.get_file_by_id(id)
if file:
if file and (file.user_id == user.id or user.role == "admin"):
file_path = Path(file.meta["path"])
# Check if the file already exists in the cache
@@ -197,7 +175,7 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
file = Files.get_file_by_id(id)
if file:
if file and (file.user_id == user.id or user.role == "admin"):
file_path = Path(file.meta["path"])
# Check if the file already exists in the cache
@@ -224,8 +202,7 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
@router.delete("/{id}")
async def delete_file_by_id(id: str, user=Depends(get_verified_user)):
file = Files.get_file_by_id(id)
if file:
if file and (file.user_id == user.id or user.role == "admin"):
result = Files.delete_file_by_id(id)
if result:
return {"message": "File deleted successfully"}

View File

@@ -1,27 +1,18 @@
from fastapi import Depends, FastAPI, HTTPException, status, Request
from datetime import datetime, timedelta
from typing import Union, Optional
import os
from pathlib import Path
from typing import Optional
from fastapi import APIRouter
from pydantic import BaseModel
import json
from apps.webui.models.functions import (
Functions,
from open_webui.apps.webui.models.functions import (
FunctionForm,
FunctionModel,
FunctionResponse,
Functions,
)
from apps.webui.utils import load_function_module_by_id
from utils.utils import get_verified_user, get_admin_user
from constants import ERROR_MESSAGES
from importlib import util
import os
from pathlib import Path
from config import DATA_DIR, CACHE_DIR, FUNCTIONS_DIR
from open_webui.apps.webui.utils import load_function_module_by_id
from open_webui.config import CACHE_DIR, FUNCTIONS_DIR
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.utils.utils import get_admin_user, get_verified_user
router = APIRouter()
@@ -304,7 +295,6 @@ async def update_function_valves_by_id(
):
function = Functions.get_function_by_id(id)
if function:
if id in request.app.state.FUNCTIONS:
function_module = request.app.state.FUNCTIONS[id]
else:

View File

@@ -1,18 +1,12 @@
from fastapi import Response, Request
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import Union, Optional
from fastapi import APIRouter
from pydantic import BaseModel
import logging
from typing import Optional
from apps.webui.models.memories import Memories, MemoryModel
from utils.utils import get_verified_user
from constants import ERROR_MESSAGES
from config import SRC_LOG_LEVELS, CHROMA_CLIENT
from open_webui.apps.webui.models.memories import Memories, MemoryModel
from open_webui.config import CHROMA_CLIENT
from open_webui.env import SRC_LOG_LEVELS
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from open_webui.utils.utils import get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -68,6 +62,76 @@ async def add_memory(
return memory
############################
# QueryMemory
############################
class QueryMemoryForm(BaseModel):
content: str
k: Optional[int] = 1
@router.post("/query")
async def query_memory(
request: Request, form_data: QueryMemoryForm, user=Depends(get_verified_user)
):
query_embedding = request.app.state.EMBEDDING_FUNCTION(form_data.content)
collection = CHROMA_CLIENT.get_or_create_collection(name=f"user-memory-{user.id}")
results = collection.query(
query_embeddings=[query_embedding],
n_results=form_data.k, # how many results to return
)
return results
############################
# ResetMemoryFromVectorDB
############################
@router.post("/reset", response_model=bool)
async def reset_memory_from_vector_db(
request: Request, user=Depends(get_verified_user)
):
CHROMA_CLIENT.delete_collection(f"user-memory-{user.id}")
collection = CHROMA_CLIENT.get_or_create_collection(name=f"user-memory-{user.id}")
memories = Memories.get_memories_by_user_id(user.id)
for memory in memories:
memory_embedding = request.app.state.EMBEDDING_FUNCTION(memory.content)
collection.upsert(
documents=[memory.content],
ids=[memory.id],
embeddings=[memory_embedding],
)
return True
############################
# DeleteMemoriesByUserId
############################
@router.delete("/delete/user", response_model=bool)
async def delete_memory_by_user_id(user=Depends(get_verified_user)):
result = Memories.delete_memories_by_user_id(user.id)
if result:
try:
CHROMA_CLIENT.delete_collection(f"user-memory-{user.id}")
except Exception as e:
log.error(e)
return True
return False
############################
# UpdateMemoryById
############################
@router.post("/{memory_id}/update", response_model=Optional[MemoryModel])
async def update_memory_by_id(
memory_id: str,
@@ -96,71 +160,6 @@ async def update_memory_by_id(
return memory
############################
# QueryMemory
############################
class QueryMemoryForm(BaseModel):
content: str
k: Optional[int] = 1
@router.post("/query")
async def query_memory(
request: Request, form_data: QueryMemoryForm, user=Depends(get_verified_user)
):
query_embedding = request.app.state.EMBEDDING_FUNCTION(form_data.content)
collection = CHROMA_CLIENT.get_or_create_collection(name=f"user-memory-{user.id}")
results = collection.query(
query_embeddings=[query_embedding],
n_results=form_data.k, # how many results to return
)
return results
############################
# ResetMemoryFromVectorDB
############################
@router.get("/reset", response_model=bool)
async def reset_memory_from_vector_db(
request: Request, user=Depends(get_verified_user)
):
CHROMA_CLIENT.delete_collection(f"user-memory-{user.id}")
collection = CHROMA_CLIENT.get_or_create_collection(name=f"user-memory-{user.id}")
memories = Memories.get_memories_by_user_id(user.id)
for memory in memories:
memory_embedding = request.app.state.EMBEDDING_FUNCTION(memory.content)
collection.upsert(
documents=[memory.content],
ids=[memory.id],
embeddings=[memory_embedding],
)
return True
############################
# DeleteMemoriesByUserId
############################
@router.delete("/user", response_model=bool)
async def delete_memory_by_user_id(user=Depends(get_verified_user)):
result = Memories.delete_memories_by_user_id(user.id)
if result:
try:
CHROMA_CLIENT.delete_collection(f"user-memory-{user.id}")
except Exception as e:
log.error(e)
return True
return False
############################
# DeleteMemoryById
############################

View File

@@ -1,15 +1,14 @@
from fastapi import Depends, FastAPI, HTTPException, status, Request
from datetime import datetime, timedelta
from typing import Union, Optional
from typing import Optional
from fastapi import APIRouter
from pydantic import BaseModel
import json
from apps.webui.models.models import Models, ModelModel, ModelForm, ModelResponse
from utils.utils import get_verified_user, get_admin_user
from constants import ERROR_MESSAGES
from open_webui.apps.webui.models.models import (
ModelForm,
ModelModel,
ModelResponse,
Models,
)
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.utils.utils import get_admin_user, get_verified_user
router = APIRouter()

View File

@@ -1,15 +1,9 @@
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import Union, Optional
from typing import Optional
from fastapi import APIRouter
from pydantic import BaseModel
import json
from apps.webui.models.prompts import Prompts, PromptForm, PromptModel
from utils.utils import get_verified_user, get_admin_user
from constants import ERROR_MESSAGES
from open_webui.apps.webui.models.prompts import PromptForm, PromptModel, Prompts
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, status
from open_webui.utils.utils import get_admin_user, get_verified_user
router = APIRouter()

View File

@@ -1,20 +1,14 @@
from fastapi import Depends, HTTPException, status, Request
from typing import Optional
from fastapi import APIRouter
from apps.webui.models.tools import Tools, ToolForm, ToolModel, ToolResponse
from apps.webui.utils import load_toolkit_module_by_id
from utils.utils import get_admin_user, get_verified_user
from utils.tools import get_tools_specs
from constants import ERROR_MESSAGES
import os
from pathlib import Path
from typing import Optional
from config import DATA_DIR, CACHE_DIR
from open_webui.apps.webui.models.tools import ToolForm, ToolModel, ToolResponse, Tools
from open_webui.apps.webui.utils import load_toolkit_module_by_id
from open_webui.config import CACHE_DIR, DATA_DIR
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.utils.tools import get_tools_specs
from open_webui.utils.utils import get_admin_user, get_verified_user
TOOLS_DIR = f"{DATA_DIR}/tools"
os.makedirs(TOOLS_DIR, exist_ok=True)

View File

@@ -1,33 +1,20 @@
from fastapi import Response, Request
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import Union, Optional
from fastapi import APIRouter
from pydantic import BaseModel
import time
import uuid
import logging
from typing import Optional
from apps.webui.models.users import (
from open_webui.apps.webui.models.auths import Auths
from open_webui.apps.webui.models.chats import Chats
from open_webui.apps.webui.models.users import (
UserModel,
UserUpdateForm,
UserRoleUpdateForm,
UserSettings,
Users,
UserSettings,
UserUpdateForm,
)
from apps.webui.models.auths import Auths
from apps.webui.models.chats import Chats
from utils.utils import (
get_verified_user,
get_password_hash,
get_current_user,
get_admin_user,
)
from constants import ERROR_MESSAGES
from config import SRC_LOG_LEVELS
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
from open_webui.utils.utils import get_admin_user, get_password_hash, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -69,7 +56,6 @@ async def update_user_permissions(
@router.post("/update/role", response_model=Optional[UserModel])
async def update_user_role(form_data: UserRoleUpdateForm, user=Depends(get_admin_user)):
if user.id != form_data.id and form_data.id != Users.get_first_user().id:
return Users.update_user_role_by_id(form_data.id, form_data.role)
@@ -173,7 +159,6 @@ class UserResponse(BaseModel):
@router.get("/{user_id}", response_model=UserResponse)
async def get_user_by_id(user_id: str, user=Depends(get_verified_user)):
# Check if user_id is a shared chat
# If it is, get the user_id from the chat
if user_id.startswith("shared-"):

View File

@@ -1,23 +1,16 @@
from pathlib import Path
import site
from pathlib import Path
from fastapi import APIRouter, UploadFile, File, Response
from fastapi import Depends, HTTPException, status
from starlette.responses import StreamingResponse, FileResponse
from pydantic import BaseModel
from fpdf import FPDF
import markdown
import black
from utils.utils import get_admin_user
from utils.misc import calculate_sha256, get_gravatar_url
from config import OLLAMA_BASE_URLS, DATA_DIR, UPLOAD_DIR, ENABLE_ADMIN_EXPORT
from constants import ERROR_MESSAGES
import markdown
from open_webui.config import DATA_DIR, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Response, status
from fpdf import FPDF
from pydantic import BaseModel
from starlette.responses import FileResponse
from open_webui.utils.misc import get_gravatar_url
from open_webui.utils.utils import get_admin_user
router = APIRouter()
@@ -115,7 +108,7 @@ async def download_chat_as_pdf(
return Response(
content=bytes(pdf_bytes),
media_type="application/pdf",
headers={"Content-Disposition": f"attachment;filename=chat.pdf"},
headers={"Content-Disposition": "attachment;filename=chat.pdf"},
)
@@ -126,7 +119,7 @@ async def download_db(user=Depends(get_admin_user)):
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
from apps.webui.internal.db import engine
from open_webui.apps.webui.internal.db import engine
if engine.name != "sqlite":
raise HTTPException(

View File

@@ -1,10 +1,12 @@
from importlib import util
import os
import re
import sys
import subprocess
import sys
from importlib import util
from config import TOOLS_DIR, FUNCTIONS_DIR
from open_webui.apps.webui.models.functions import Functions
from open_webui.apps.webui.models.tools import Tools
from open_webui.config import FUNCTIONS_DIR, TOOLS_DIR
def extract_frontmatter(file_path):
@@ -49,6 +51,15 @@ def extract_frontmatter(file_path):
def load_toolkit_module_by_id(toolkit_id):
toolkit_path = os.path.join(TOOLS_DIR, f"{toolkit_id}.py")
if not os.path.exists(toolkit_path):
tool = Tools.get_tool_by_id(toolkit_id)
if tool:
with open(toolkit_path, "w") as file:
file.write(tool.content)
else:
raise Exception(f"Toolkit not found: {toolkit_id}")
spec = util.spec_from_file_location(toolkit_id, toolkit_path)
module = util.module_from_spec(spec)
frontmatter = extract_frontmatter(toolkit_path)
@@ -71,6 +82,14 @@ def load_toolkit_module_by_id(toolkit_id):
def load_function_module_by_id(function_id):
function_path = os.path.join(FUNCTIONS_DIR, f"{function_id}.py")
if not os.path.exists(function_path):
function = Functions.get_function_by_id(function_id)
if function:
with open(function_path, "w") as file:
file.write(function.content)
else:
raise Exception(f"Function not found: {function_id}")
spec = util.spec_from_file_location(function_id, function_path)
module = util.module_from_spec(spec)
frontmatter = extract_frontmatter(function_path)

View File

@@ -1,83 +1,29 @@
import os
import sys
import json
import logging
import importlib.metadata
import pkgutil
import os
import shutil
from datetime import datetime
from pathlib import Path
from typing import Generic, Optional, TypeVar
from urllib.parse import urlparse
import chromadb
from chromadb import Settings
from bs4 import BeautifulSoup
from typing import TypeVar, Generic
from pydantic import BaseModel
from typing import Optional
from pathlib import Path
import json
import yaml
import markdown
import requests
import shutil
from constants import ERROR_MESSAGES
####################################
# Load .env file
####################################
BACKEND_DIR = Path(__file__).parent # the path containing this file
BASE_DIR = BACKEND_DIR.parent # the path containing the backend/
print(BASE_DIR)
try:
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
except ImportError:
print("dotenv not installed, skipping...")
####################################
# LOGGING
####################################
log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
if GLOBAL_LOG_LEVEL in log_levels:
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
else:
GLOBAL_LOG_LEVEL = "INFO"
log = logging.getLogger(__name__)
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
log_sources = [
"AUDIO",
"COMFYUI",
"CONFIG",
"DB",
"IMAGES",
"MAIN",
"MODELS",
"OLLAMA",
"OPENAI",
"RAG",
"WEBHOOK",
]
SRC_LOG_LEVELS = {}
for source in log_sources:
log_env_var = source + "_LOG_LEVEL"
SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
if SRC_LOG_LEVELS[source] not in log_levels:
SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
log.setLevel(SRC_LOG_LEVELS["CONFIG"])
import yaml
from open_webui.apps.webui.internal.db import Base, get_db
from chromadb import Settings
from open_webui.env import (
OPEN_WEBUI_DIR,
DATA_DIR,
ENV,
FRONTEND_BUILD_DIR,
WEBUI_AUTH,
WEBUI_FAVICON_URL,
WEBUI_NAME,
log,
)
from pydantic import BaseModel
from sqlalchemy import JSON, Column, DateTime, Integer, func
class EndpointFilter(logging.Filter):
@@ -88,141 +34,126 @@ class EndpointFilter(logging.Filter):
# Filter out /endpoint
logging.getLogger("uvicorn.access").addFilter(EndpointFilter())
WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
if WEBUI_NAME != "Open WebUI":
WEBUI_NAME += " (Open WebUI)"
WEBUI_URL = os.environ.get("WEBUI_URL", "http://localhost:3000")
WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
####################################
# ENV (dev,test,prod)
####################################
ENV = os.environ.get("ENV", "dev")
try:
PACKAGE_DATA = json.loads((BASE_DIR / "package.json").read_text())
except Exception:
try:
PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
except importlib.metadata.PackageNotFoundError:
PACKAGE_DATA = {"version": "0.0.0"}
VERSION = PACKAGE_DATA["version"]
# Function to parse each section
def parse_section(section):
items = []
for li in section.find_all("li"):
# Extract raw HTML string
raw_html = str(li)
# Extract text without HTML tags
text = li.get_text(separator=" ", strip=True)
# Split into title and content
parts = text.split(": ", 1)
title = parts[0].strip() if len(parts) > 1 else ""
content = parts[1].strip() if len(parts) > 1 else text
items.append({"title": title, "content": content, "raw": raw_html})
return items
try:
changelog_path = BASE_DIR / "CHANGELOG.md"
with open(str(changelog_path.absolute()), "r", encoding="utf8") as file:
changelog_content = file.read()
except Exception:
changelog_content = (pkgutil.get_data("open_webui", "CHANGELOG.md") or b"").decode()
# Convert markdown content to HTML
html_content = markdown.markdown(changelog_content)
# Parse the HTML content
soup = BeautifulSoup(html_content, "html.parser")
# Initialize JSON structure
changelog_json = {}
# Iterate over each version
for version in soup.find_all("h2"):
version_number = version.get_text().strip().split(" - ")[0][1:-1] # Remove brackets
date = version.get_text().strip().split(" - ")[1]
version_data = {"date": date}
# Find the next sibling that is a h3 tag (section title)
current = version.find_next_sibling()
while current and current.name != "h2":
if current.name == "h3":
section_title = current.get_text().lower() # e.g., "added", "fixed"
section_items = parse_section(current.find_next_sibling("ul"))
version_data[section_title] = section_items
# Move to the next element
current = current.find_next_sibling()
changelog_json[version_number] = version_data
CHANGELOG = changelog_json
####################################
# SAFE_MODE
####################################
SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true"
####################################
# WEBUI_BUILD_HASH
####################################
WEBUI_BUILD_HASH = os.environ.get("WEBUI_BUILD_HASH", "dev-build")
####################################
# DATA/FRONTEND BUILD DIR
####################################
DATA_DIR = Path(os.getenv("DATA_DIR", BACKEND_DIR / "data")).resolve()
FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
RESET_CONFIG_ON_START = (
os.environ.get("RESET_CONFIG_ON_START", "False").lower() == "true"
)
if RESET_CONFIG_ON_START:
try:
os.remove(f"{DATA_DIR}/config.json")
with open(f"{DATA_DIR}/config.json", "w") as f:
f.write("{}")
except Exception:
pass
try:
CONFIG_DATA = json.loads((DATA_DIR / "config.json").read_text())
except Exception:
CONFIG_DATA = {}
####################################
# Config helpers
####################################
def save_config():
# Function to run the alembic migrations
def run_migrations():
print("Running migrations")
try:
with open(f"{DATA_DIR}/config.json", "w") as f:
json.dump(CONFIG_DATA, f, indent="\t")
from alembic import command
from alembic.config import Config
alembic_cfg = Config(OPEN_WEBUI_DIR / "alembic.ini")
# Set the script location dynamically
migrations_path = OPEN_WEBUI_DIR / "migrations"
alembic_cfg.set_main_option("script_location", str(migrations_path))
command.upgrade(alembic_cfg, "head")
except Exception as e:
log.exception(e)
print(f"Error: {e}")
run_migrations()
class Config(Base):
__tablename__ = "config"
id = Column(Integer, primary_key=True)
data = Column(JSON, nullable=False)
version = Column(Integer, nullable=False, default=0)
created_at = Column(DateTime, nullable=False, server_default=func.now())
updated_at = Column(DateTime, nullable=True, onupdate=func.now())
def load_json_config():
with open(f"{DATA_DIR}/config.json", "r") as file:
return json.load(file)
def save_to_db(data):
with get_db() as db:
existing_config = db.query(Config).first()
if not existing_config:
new_config = Config(data=data, version=0)
db.add(new_config)
else:
existing_config.data = data
existing_config.updated_at = datetime.now()
db.add(existing_config)
db.commit()
# When initializing, check if config.json exists and migrate it to the database
if os.path.exists(f"{DATA_DIR}/config.json"):
data = load_json_config()
save_to_db(data)
os.rename(f"{DATA_DIR}/config.json", f"{DATA_DIR}/old_config.json")
DEFAULT_CONFIG = {
"version": 0,
"ui": {
"default_locale": "",
"prompt_suggestions": [
{
"title": [
"Help me study",
"vocabulary for a college entrance exam",
],
"content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.",
},
{
"title": [
"Give me ideas",
"for what to do with my kids' art",
],
"content": "What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.",
},
{
"title": ["Tell me a fun fact", "about the Roman Empire"],
"content": "Tell me a random fun fact about the Roman Empire",
},
{
"title": [
"Show me a code snippet",
"of a website's sticky header",
],
"content": "Show me a code snippet of a website's sticky header in CSS and JavaScript.",
},
{
"title": [
"Explain options trading",
"if I'm familiar with buying and selling stocks",
],
"content": "Explain options trading in simple terms if I'm familiar with buying and selling stocks.",
},
{
"title": ["Overcome procrastination", "give me tips"],
"content": "Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?",
},
{
"title": [
"Grammar check",
"rewrite it for better readability ",
],
"content": 'Check the following sentence for grammar and clarity: "[sentence]". Rewrite it for better readability while maintaining its original meaning.',
},
],
},
}
def get_config():
with get_db() as db:
config_entry = db.query(Config).order_by(Config.id.desc()).first()
return config_entry.data if config_entry else DEFAULT_CONFIG
CONFIG_DATA = get_config()
def get_config_value(config_path: str):
@@ -236,6 +167,25 @@ def get_config_value(config_path: str):
return cur_config
PERSISTENT_CONFIG_REGISTRY = []
def save_config(config):
global CONFIG_DATA
global PERSISTENT_CONFIG_REGISTRY
try:
save_to_db(config)
CONFIG_DATA = config
# Trigger updates on all registered PersistentConfig entries
for config_item in PERSISTENT_CONFIG_REGISTRY:
config_item.update()
except Exception as e:
log.exception(e)
return False
return True
T = TypeVar("T")
@@ -246,11 +196,13 @@ class PersistentConfig(Generic[T]):
self.env_value = env_value
self.config_value = get_config_value(config_path)
if self.config_value is not None:
log.info(f"'{env_name}' loaded from config.json")
log.info(f"'{env_name}' loaded from the latest database entry")
self.value = self.config_value
else:
self.value = env_value
PERSISTENT_CONFIG_REGISTRY.append(self)
def __str__(self):
return str(self.value)
@@ -267,20 +219,22 @@ class PersistentConfig(Generic[T]):
)
return super().__getattribute__(item)
def update(self):
new_value = get_config_value(self.config_path)
if new_value is not None:
self.value = new_value
log.info(f"Updated {self.env_name} to new value {self.value}")
def save(self):
# Don't save if the value is the same as the env value and the config value
if self.env_value == self.value:
if self.config_value == self.value:
return
log.info(f"Saving '{self.env_name}' to config.json")
log.info(f"Saving '{self.env_name}' to the database")
path_parts = self.config_path.split(".")
config = CONFIG_DATA
sub_config = CONFIG_DATA
for key in path_parts[:-1]:
if key not in config:
config[key] = {}
config = config[key]
config[path_parts[-1]] = self.value
save_config()
if key not in sub_config:
sub_config[key] = {}
sub_config = sub_config[key]
sub_config[path_parts[-1]] = self.value
save_to_db(CONFIG_DATA)
self.config_value = self.value
@@ -305,11 +259,6 @@ class AppConfig:
# WEBUI_AUTH (Required for security)
####################################
WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
"WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
)
WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
JWT_EXPIRES_IN = PersistentConfig(
"JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1")
)
@@ -486,7 +435,7 @@ load_oauth_providers()
# Static DIR
####################################
STATIC_DIR = Path(os.getenv("STATIC_DIR", BACKEND_DIR / "static")).resolve()
STATIC_DIR = Path(os.getenv("STATIC_DIR", OPEN_WEBUI_DIR / "static")).resolve()
frontend_favicon = FRONTEND_BUILD_DIR / "static" / "favicon.png"
@@ -999,30 +948,6 @@ TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = PersistentConfig(
)
####################################
# WEBUI_SECRET_KEY
####################################
WEBUI_SECRET_KEY = os.environ.get(
"WEBUI_SECRET_KEY",
os.environ.get(
"WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
), # DEPRECATED: remove at next major version
)
WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get(
"WEBUI_SESSION_COOKIE_SAME_SITE",
os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax"),
)
WEBUI_SESSION_COOKIE_SECURE = os.environ.get(
"WEBUI_SESSION_COOKIE_SECURE",
os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true",
)
if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
####################################
# RAG document content extraction
####################################
@@ -1074,6 +999,26 @@ ENABLE_RAG_HYBRID_SEARCH = PersistentConfig(
os.environ.get("ENABLE_RAG_HYBRID_SEARCH", "").lower() == "true",
)
RAG_FILE_MAX_COUNT = PersistentConfig(
"RAG_FILE_MAX_COUNT",
"rag.file.max_count",
(
int(os.environ.get("RAG_FILE_MAX_COUNT"))
if os.environ.get("RAG_FILE_MAX_COUNT")
else None
),
)
RAG_FILE_MAX_SIZE = PersistentConfig(
"RAG_FILE_MAX_SIZE",
"rag.file.max_size",
(
int(os.environ.get("RAG_FILE_MAX_SIZE"))
if os.environ.get("RAG_FILE_MAX_SIZE")
else None
),
)
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = PersistentConfig(
"ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION",
"rag.enable_web_loader_ssl_verification",
@@ -1286,6 +1231,18 @@ TAVILY_API_KEY = PersistentConfig(
os.getenv("TAVILY_API_KEY", ""),
)
SEARCHAPI_API_KEY = PersistentConfig(
"SEARCHAPI_API_KEY",
"rag.web.search.searchapi_api_key",
os.getenv("SEARCHAPI_API_KEY", ""),
)
SEARCHAPI_ENGINE = PersistentConfig(
"SEARCHAPI_ENGINE",
"rag.web.search.searchapi_engine",
os.getenv("SEARCHAPI_ENGINE", ""),
)
RAG_WEB_SEARCH_RESULT_COUNT = PersistentConfig(
"RAG_WEB_SEARCH_RESULT_COUNT",
"rag.web.search.result_count",
@@ -1554,13 +1511,8 @@ AUDIO_TTS_VOICE = PersistentConfig(
os.getenv("AUDIO_TTS_VOICE", "alloy"), # OpenAI default voice
)
####################################
# Database
####################################
DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")
# Replace the postgres:// with postgresql://
if "postgres://" in DATABASE_URL:
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")
AUDIO_TTS_SPLIT_ON = PersistentConfig(
"AUDIO_TTS_SPLIT_ON",
"audio.tts.split_on",
os.getenv("AUDIO_TTS_SPLIT_ON", "punctuation"),
)

View File

@@ -0,0 +1 @@
pip install dir for backend files (db, documents, etc.)

278
backend/open_webui/env.py Normal file
View File

@@ -0,0 +1,278 @@
import importlib.metadata
import json
import logging
import os
import pkgutil
import sys
import shutil
from pathlib import Path
import markdown
from bs4 import BeautifulSoup
from open_webui.constants import ERROR_MESSAGES
####################################
# Load .env file
####################################
OPEN_WEBUI_DIR = Path(__file__).parent # the path containing this file
print(OPEN_WEBUI_DIR)
BACKEND_DIR = OPEN_WEBUI_DIR.parent # the path containing this file
BASE_DIR = BACKEND_DIR.parent # the path containing the backend/
print(BACKEND_DIR)
print(BASE_DIR)
try:
from dotenv import find_dotenv, load_dotenv
load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
except ImportError:
print("dotenv not installed, skipping...")
####################################
# LOGGING
####################################
log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
if GLOBAL_LOG_LEVEL in log_levels:
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
else:
GLOBAL_LOG_LEVEL = "INFO"
log = logging.getLogger(__name__)
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
log_sources = [
"AUDIO",
"COMFYUI",
"CONFIG",
"DB",
"IMAGES",
"MAIN",
"MODELS",
"OLLAMA",
"OPENAI",
"RAG",
"WEBHOOK",
]
SRC_LOG_LEVELS = {}
for source in log_sources:
log_env_var = source + "_LOG_LEVEL"
SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
if SRC_LOG_LEVELS[source] not in log_levels:
SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
log.setLevel(SRC_LOG_LEVELS["CONFIG"])
WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
if WEBUI_NAME != "Open WebUI":
WEBUI_NAME += " (Open WebUI)"
WEBUI_URL = os.environ.get("WEBUI_URL", "http://localhost:3000")
WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
####################################
# ENV (dev,test,prod)
####################################
ENV = os.environ.get("ENV", "dev")
PIP_INSTALL = False
try:
importlib.metadata.version("open-webui")
PIP_INSTALL = True
except importlib.metadata.PackageNotFoundError:
pass
if PIP_INSTALL:
PACKAGE_DATA = {"version": importlib.metadata.version("open-webui")}
else:
try:
PACKAGE_DATA = json.loads((BASE_DIR / "package.json").read_text())
except Exception:
PACKAGE_DATA = {"version": "0.0.0"}
VERSION = PACKAGE_DATA["version"]
# Function to parse each section
def parse_section(section):
items = []
for li in section.find_all("li"):
# Extract raw HTML string
raw_html = str(li)
# Extract text without HTML tags
text = li.get_text(separator=" ", strip=True)
# Split into title and content
parts = text.split(": ", 1)
title = parts[0].strip() if len(parts) > 1 else ""
content = parts[1].strip() if len(parts) > 1 else text
items.append({"title": title, "content": content, "raw": raw_html})
return items
try:
changelog_path = BASE_DIR / "CHANGELOG.md"
with open(str(changelog_path.absolute()), "r", encoding="utf8") as file:
changelog_content = file.read()
except Exception:
changelog_content = (pkgutil.get_data("open_webui", "CHANGELOG.md") or b"").decode()
# Convert markdown content to HTML
html_content = markdown.markdown(changelog_content)
# Parse the HTML content
soup = BeautifulSoup(html_content, "html.parser")
# Initialize JSON structure
changelog_json = {}
# Iterate over each version
for version in soup.find_all("h2"):
version_number = version.get_text().strip().split(" - ")[0][1:-1] # Remove brackets
date = version.get_text().strip().split(" - ")[1]
version_data = {"date": date}
# Find the next sibling that is a h3 tag (section title)
current = version.find_next_sibling()
while current and current.name != "h2":
if current.name == "h3":
section_title = current.get_text().lower() # e.g., "added", "fixed"
section_items = parse_section(current.find_next_sibling("ul"))
version_data[section_title] = section_items
# Move to the next element
current = current.find_next_sibling()
changelog_json[version_number] = version_data
CHANGELOG = changelog_json
####################################
# SAFE_MODE
####################################
SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true"
####################################
# WEBUI_BUILD_HASH
####################################
WEBUI_BUILD_HASH = os.environ.get("WEBUI_BUILD_HASH", "dev-build")
####################################
# DATA/FRONTEND BUILD DIR
####################################
DATA_DIR = Path(os.getenv("DATA_DIR", BACKEND_DIR / "data")).resolve()
if PIP_INSTALL:
NEW_DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data")).resolve()
NEW_DATA_DIR.mkdir(parents=True, exist_ok=True)
# Check if the data directory exists in the package directory
if DATA_DIR.exists():
log.info(f"Moving {DATA_DIR} to {NEW_DATA_DIR}")
for item in DATA_DIR.iterdir():
dest = NEW_DATA_DIR / item.name
if item.is_dir():
shutil.copytree(item, dest, dirs_exist_ok=True)
else:
shutil.copy2(item, dest)
DATA_DIR = OPEN_WEBUI_DIR / "data"
FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
if PIP_INSTALL:
FRONTEND_BUILD_DIR = Path(
os.getenv("FRONTEND_BUILD_DIR", OPEN_WEBUI_DIR / "frontend")
).resolve()
RESET_CONFIG_ON_START = (
os.environ.get("RESET_CONFIG_ON_START", "False").lower() == "true"
)
if RESET_CONFIG_ON_START:
try:
os.remove(f"{DATA_DIR}/config.json")
with open(f"{DATA_DIR}/config.json", "w") as f:
f.write("{}")
except Exception:
pass
####################################
# Database
####################################
# Check if the file exists
if os.path.exists(f"{DATA_DIR}/ollama.db"):
# Rename the file
os.rename(f"{DATA_DIR}/ollama.db", f"{DATA_DIR}/webui.db")
log.info("Database migrated from Ollama-WebUI successfully.")
else:
pass
DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite:///{DATA_DIR}/webui.db")
# Replace the postgres:// with postgresql://
if "postgres://" in DATABASE_URL:
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://")
####################################
# WEBUI_AUTH (Required for security)
####################################
WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
"WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
)
WEBUI_AUTH_TRUSTED_NAME_HEADER = os.environ.get("WEBUI_AUTH_TRUSTED_NAME_HEADER", None)
####################################
# WEBUI_SECRET_KEY
####################################
WEBUI_SECRET_KEY = os.environ.get(
"WEBUI_SECRET_KEY",
os.environ.get(
"WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
), # DEPRECATED: remove at next major version
)
WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get(
"WEBUI_SESSION_COOKIE_SAME_SITE",
os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax"),
)
WEBUI_SESSION_COOKIE_SECURE = os.environ.get(
"WEBUI_SESSION_COOKIE_SECURE",
os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true",
)
if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)

View File

@@ -1,131 +1,138 @@
import base64
import inspect
import json
import logging
import mimetypes
import os
import shutil
import sys
import time
import uuid
from contextlib import asynccontextmanager
from typing import Optional
import aiohttp
import requests
from open_webui.apps.audio.main import app as audio_app
from open_webui.apps.images.main import app as images_app
from open_webui.apps.ollama.main import app as ollama_app
from open_webui.apps.ollama.main import (
generate_openai_chat_completion as generate_ollama_chat_completion,
)
from open_webui.apps.ollama.main import get_all_models as get_ollama_models
from open_webui.apps.openai.main import app as openai_app
from open_webui.apps.openai.main import (
generate_chat_completion as generate_openai_chat_completion,
)
from open_webui.apps.openai.main import get_all_models as get_openai_models
from open_webui.apps.rag.main import app as rag_app
from open_webui.apps.rag.utils import get_rag_context, rag_template
from open_webui.apps.socket.main import app as socket_app
from open_webui.apps.socket.main import get_event_call, get_event_emitter
from open_webui.apps.webui.internal.db import Session
from open_webui.apps.webui.main import app as webui_app
from open_webui.apps.webui.main import (
generate_function_chat_completion,
get_pipe_models,
)
from open_webui.apps.webui.models.auths import Auths
from open_webui.apps.webui.models.functions import Functions
from open_webui.apps.webui.models.models import Models
from open_webui.apps.webui.models.users import UserModel, Users
from open_webui.apps.webui.utils import load_function_module_by_id
from authlib.integrations.starlette_client import OAuth
from authlib.oidc.core import UserInfo
import json
import time
import os
import sys
import logging
import aiohttp
import requests
import mimetypes
import shutil
import inspect
from typing import Optional
from fastapi import FastAPI, Request, Depends, status, UploadFile, File, Form
from fastapi.staticfiles import StaticFiles
from fastapi.responses import JSONResponse
from fastapi import HTTPException
from open_webui.config import (
CACHE_DIR,
CORS_ALLOW_ORIGIN,
DEFAULT_LOCALE,
ENABLE_ADMIN_CHAT_ACCESS,
ENABLE_ADMIN_EXPORT,
ENABLE_MODEL_FILTER,
ENABLE_OAUTH_SIGNUP,
ENABLE_OLLAMA_API,
ENABLE_OPENAI_API,
ENV,
FRONTEND_BUILD_DIR,
MODEL_FILTER_LIST,
OAUTH_MERGE_ACCOUNTS_BY_EMAIL,
OAUTH_PROVIDERS,
SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE,
SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD,
STATIC_DIR,
TASK_MODEL,
TASK_MODEL_EXTERNAL,
TITLE_GENERATION_PROMPT_TEMPLATE,
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
WEBHOOK_URL,
WEBUI_AUTH,
WEBUI_NAME,
AppConfig,
run_migrations,
)
from open_webui.constants import ERROR_MESSAGES, TASKS, WEBHOOK_MESSAGES
from open_webui.env import (
CHANGELOG,
GLOBAL_LOG_LEVEL,
SAFE_MODE,
SRC_LOG_LEVELS,
VERSION,
WEBUI_BUILD_HASH,
WEBUI_SECRET_KEY,
WEBUI_SESSION_COOKIE_SAME_SITE,
WEBUI_SESSION_COOKIE_SECURE,
WEBUI_URL,
)
from fastapi import (
Depends,
FastAPI,
File,
Form,
HTTPException,
Request,
UploadFile,
status,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from sqlalchemy import text
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import StreamingResponse, Response, RedirectResponse
from starlette.responses import RedirectResponse, Response, StreamingResponse
from apps.socket.main import app as socket_app, get_event_emitter, get_event_call
from apps.ollama.main import (
app as ollama_app,
get_all_models as get_ollama_models,
generate_openai_chat_completion as generate_ollama_chat_completion,
from open_webui.utils.misc import (
add_or_update_system_message,
get_last_user_message,
parse_duration,
prepend_to_first_user_message_content,
)
from apps.openai.main import (
app as openai_app,
get_all_models as get_openai_models,
generate_chat_completion as generate_openai_chat_completion,
from open_webui.utils.task import (
moa_response_generation_template,
search_query_generation_template,
title_generation_template,
tools_function_calling_generation_template,
)
from apps.audio.main import app as audio_app
from apps.images.main import app as images_app
from apps.rag.main import app as rag_app
from apps.webui.main import (
app as webui_app,
get_pipe_models,
generate_function_chat_completion,
)
from apps.webui.internal.db import Session
from pydantic import BaseModel
from apps.webui.models.auths import Auths
from apps.webui.models.models import Models
from apps.webui.models.functions import Functions
from apps.webui.models.users import Users, UserModel
from apps.webui.utils import load_function_module_by_id
from utils.utils import (
from open_webui.utils.tools import get_tools
from open_webui.utils.utils import (
create_token,
decode_token,
get_admin_user,
get_verified_user,
get_current_user,
get_http_authorization_cred,
get_password_hash,
create_token,
decode_token,
get_verified_user,
)
from utils.task import (
title_generation_template,
search_query_generation_template,
tools_function_calling_generation_template,
moa_response_generation_template,
)
from utils.tools import get_tools
from utils.misc import (
get_last_user_message,
add_or_update_system_message,
prepend_to_first_user_message_content,
parse_duration,
)
from apps.rag.utils import get_rag_context, rag_template
from config import (
WEBUI_NAME,
WEBUI_URL,
WEBUI_AUTH,
ENV,
VERSION,
CHANGELOG,
FRONTEND_BUILD_DIR,
CACHE_DIR,
STATIC_DIR,
DEFAULT_LOCALE,
ENABLE_OPENAI_API,
ENABLE_OLLAMA_API,
ENABLE_MODEL_FILTER,
MODEL_FILTER_LIST,
GLOBAL_LOG_LEVEL,
SRC_LOG_LEVELS,
WEBHOOK_URL,
ENABLE_ADMIN_EXPORT,
WEBUI_BUILD_HASH,
TASK_MODEL,
TASK_MODEL_EXTERNAL,
TITLE_GENERATION_PROMPT_TEMPLATE,
SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE,
SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD,
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
SAFE_MODE,
OAUTH_PROVIDERS,
ENABLE_OAUTH_SIGNUP,
OAUTH_MERGE_ACCOUNTS_BY_EMAIL,
WEBUI_SECRET_KEY,
WEBUI_SESSION_COOKIE_SAME_SITE,
WEBUI_SESSION_COOKIE_SECURE,
ENABLE_ADMIN_CHAT_ACCESS,
AppConfig,
CORS_ALLOW_ORIGIN,
)
from constants import ERROR_MESSAGES, WEBHOOK_MESSAGES, TASKS
from utils.webhook import post_webhook
from open_webui.utils.webhook import post_webhook
if SAFE_MODE:
print("SAFE MODE ENABLED")
@@ -165,17 +172,6 @@ https://github.com/open-webui/open-webui
)
def run_migrations():
try:
from alembic.config import Config
from alembic import command
alembic_cfg = Config("alembic.ini")
command.upgrade(alembic_cfg, "head")
except Exception as e:
print(f"Error: {e}")
@asynccontextmanager
async def lifespan(app: FastAPI):
run_migrations()
@@ -299,24 +295,26 @@ async def chat_completion_filter_functions_handler(body, model, extra_params):
# Get the signature of the function
sig = inspect.signature(inlet)
params = {"body": body}
params = {"body": body} | {
k: v
for k, v in {
**extra_params,
"__model__": model,
"__id__": filter_id,
}.items()
if k in sig.parameters
}
# Extra parameters to be passed to the function
custom_params = {**extra_params, "__model__": model, "__id__": filter_id}
if hasattr(function_module, "UserValves") and "__user__" in sig.parameters:
if "__user__" in params and hasattr(function_module, "UserValves"):
try:
uid = custom_params["__user__"]["id"]
custom_params["__user__"]["valves"] = function_module.UserValves(
**Functions.get_user_valves_by_id_and_user_id(filter_id, uid)
params["__user__"]["valves"] = function_module.UserValves(
**Functions.get_user_valves_by_id_and_user_id(
filter_id, params["__user__"]["id"]
)
)
except Exception as e:
print(e)
# Add extra params in contained in function signature
for key, value in custom_params.items():
if key in sig.parameters:
params[key] = value
if inspect.iscoroutinefunction(inlet):
body = await inlet(**params)
else:
@@ -372,7 +370,9 @@ async def chat_completion_tools_handler(
) -> tuple[dict, dict]:
# If tool_ids field is present, call the functions
metadata = body.get("metadata", {})
tool_ids = metadata.get("tool_ids", None)
log.debug(f"{tool_ids=}")
if not tool_ids:
return body, {}
@@ -381,16 +381,17 @@ async def chat_completion_tools_handler(
citations = []
task_model_id = get_task_model_id(body["model"])
log.debug(f"{tool_ids=}")
custom_params = {
**extra_params,
"__model__": app.state.MODELS[task_model_id],
"__messages__": body["messages"],
"__files__": metadata.get("files", []),
}
tools = get_tools(webui_app, tool_ids, user, custom_params)
tools = get_tools(
webui_app,
tool_ids,
user,
{
**extra_params,
"__model__": app.state.MODELS[task_model_id],
"__messages__": body["messages"],
"__files__": metadata.get("files", []),
},
)
log.info(f"{tools=}")
specs = [tool["spec"] for tool in tools.values()]
@@ -525,23 +526,20 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware):
"chat_id": body.pop("chat_id", None),
"message_id": body.pop("id", None),
"session_id": body.pop("session_id", None),
"valves": body.pop("valves", None),
"tool_ids": body.pop("tool_ids", None),
"files": body.pop("files", None),
"tool_ids": body.get("tool_ids", None),
"files": body.get("files", None),
}
body["metadata"] = metadata
__user__ = {
"id": user.id,
"email": user.email,
"name": user.name,
"role": user.role,
}
extra_params = {
"__user__": __user__,
"__event_emitter__": get_event_emitter(metadata),
"__event_call__": get_event_call(metadata),
"__user__": {
"id": user.id,
"email": user.email,
"name": user.name,
"role": user.role,
},
}
# Initialize data_items to store additional data to be sent to the client
@@ -560,6 +558,13 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware):
content={"detail": str(e)},
)
metadata = {
**metadata,
"tool_ids": body.pop("tool_ids", None),
"files": body.pop("files", None),
}
body["metadata"] = metadata
try:
body, flags = await chat_completion_tools_handler(body, user, extra_params)
contexts.extend(flags.get("contexts", []))
@@ -630,7 +635,10 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware):
async for data in original_generator:
yield data
return StreamingResponse(stream_wrapper(response.body_iterator, data_items))
return StreamingResponse(
stream_wrapper(response.body_iterator, data_items),
headers=dict(response.headers),
)
async def _receive(self, body: bytes):
return {"type": "http.request", "body": body, "more_body": False}
@@ -731,10 +739,16 @@ class PipelineMiddleware(BaseHTTPMiddleware):
try:
data = filter_pipeline(data, user)
except Exception as e:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
if len(e.args) > 1:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
else:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": str(e)},
)
modified_body_bytes = json.dumps(data).encode("utf-8")
# Replace the request body with the modified one
@@ -983,11 +997,20 @@ async def get_models(user=Depends(get_verified_user)):
@app.post("/api/chat/completions")
async def generate_chat_completions(form_data: dict, user=Depends(get_verified_user)):
model_id = form_data["model"]
if model_id not in app.state.MODELS:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Model not found",
)
if app.state.config.ENABLE_MODEL_FILTER:
if user.role == "user" and model_id not in app.state.config.MODEL_FILTER_LIST:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Model not found",
)
model = app.state.MODELS[model_id]
if model.get("pipe"):
return await generate_function_chat_completion(form_data, user=user)
@@ -1373,10 +1396,16 @@ async def generate_title(form_data: dict, user=Depends(get_verified_user)):
try:
payload = filter_pipeline(payload, user)
except Exception as e:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
if len(e.args) > 1:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
else:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": str(e)},
)
if "chat_id" in payload:
del payload["chat_id"]
@@ -1426,10 +1455,16 @@ async def generate_search_query(form_data: dict, user=Depends(get_verified_user)
try:
payload = filter_pipeline(payload, user)
except Exception as e:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
if len(e.args) > 1:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
else:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": str(e)},
)
if "chat_id" in payload:
del payload["chat_id"]
@@ -1483,10 +1518,16 @@ Message: """{{prompt}}"""
try:
payload = filter_pipeline(payload, user)
except Exception as e:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
if len(e.args) > 1:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
else:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": str(e)},
)
if "chat_id" in payload:
del payload["chat_id"]
@@ -1535,10 +1576,16 @@ Responses from models: {{responses}}"""
try:
payload = filter_pipeline(payload, user)
except Exception as e:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
if len(e.args) > 1:
return JSONResponse(
status_code=e.args[0],
content={"detail": e.args[1]},
)
else:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": str(e)},
)
if "chat_id" in payload:
del payload["chat_id"]
@@ -1926,11 +1973,16 @@ async def get_app_config(request: Request):
"tts": {
"engine": audio_app.state.config.TTS_ENGINE,
"voice": audio_app.state.config.TTS_VOICE,
"split_on": audio_app.state.config.TTS_SPLIT_ON,
},
"stt": {
"engine": audio_app.state.config.STT_ENGINE,
},
},
"file": {
"max_size": rag_app.state.config.FILE_MAX_SIZE,
"max_count": rag_app.state.config.FILE_MAX_COUNT,
},
"permissions": {**webui_app.state.config.USER_PERMISSIONS},
}
if user is not None

View File

@@ -1,24 +1,9 @@
import os
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
from apps.webui.models.auths import Auth
from apps.webui.models.chats import Chat
from apps.webui.models.documents import Document
from apps.webui.models.memories import Memory
from apps.webui.models.models import Model
from apps.webui.models.prompts import Prompt
from apps.webui.models.tags import Tag, ChatIdTag
from apps.webui.models.tools import Tool
from apps.webui.models.users import User
from apps.webui.models.files import File
from apps.webui.models.functions import Function
from config import DATABASE_URL
from open_webui.apps.webui.models.auths import Auth
from open_webui.env import DATABASE_URL
from sqlalchemy import engine_from_config, pool
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.

View File

@@ -9,7 +9,7 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import apps.webui.internal.db
import open_webui.apps.webui.internal.db
${imports if imports else ""}
# revision identifiers, used by Alembic.

View File

@@ -1,17 +1,19 @@
"""init
Revision ID: 7e5b5dc7342b
Revises:
Revises:
Create Date: 2024-06-24 13:15:33.808998
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import apps.webui.internal.db
from migrations.util import get_existing_tables
from alembic import op
import open_webui.apps.webui.internal.db
from open_webui.migrations.util import get_existing_tables
# revision identifiers, used by Alembic.
revision: str = "7e5b5dc7342b"

View File

@@ -0,0 +1,41 @@
"""Add config table
Revision ID: ca81bd47c050
Revises: 7e5b5dc7342b
Create Date: 2024-08-25 15:26:35.241684
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "ca81bd47c050"
down_revision: Union[str, None] = "7e5b5dc7342b"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade():
op.create_table(
"config",
sa.Column("id", sa.Integer, primary_key=True),
sa.Column("data", sa.JSON(), nullable=False),
sa.Column("version", sa.Integer, nullable=False),
sa.Column(
"created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()
),
sa.Column(
"updated_at",
sa.DateTime(),
nullable=True,
server_default=sa.func.now(),
onupdate=sa.func.now(),
),
)
def downgrade():
op.drop_table("config")

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

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