Compare commits

..

765 Commits

Author SHA1 Message Date
Timothy Jaeryang Baek ff8a2da751 Merge pull request #5565 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.23
2024-09-21 04:55:29 +02:00
Timothy J. Baek 2f028b45fe refac 2024-09-21 04:53:44 +02:00
Timothy J. Baek d3ef9d980b refac 2024-09-21 04:36:18 +02:00
Timothy J. Baek cb81dfe4ba fix: voice recording not stopping 2024-09-21 04:27:18 +02:00
Timothy J. Baek 426de76690 refac 2024-09-21 04:24:38 +02:00
Timothy J. Baek 16a767e04e doc: changelog 2024-09-21 04:14:10 +02:00
Timothy J. Baek 2adaf9ba3d refac 2024-09-21 04:10:24 +02:00
Timothy J. Baek f381850bb2 refac 2024-09-21 04:04:34 +02:00
Timothy Jaeryang Baek 273787fe78 Merge pull request #5541 from kivvi3412/fix-title-generate
[fix] Fix title generate
2024-09-21 03:58:28 +02:00
Timothy J. Baek 70d16c3904 refac: styling 2024-09-21 03:55:17 +02:00
Timothy J. Baek ee6b1376c3 fix: rag duplicate collection issue 2024-09-21 03:53:53 +02:00
Timothy J. Baek aed8e2156f refac 2024-09-21 03:44:44 +02:00
Timothy J. Baek 5d848ad130 refac 2024-09-21 03:39:30 +02:00
Timothy J. Baek 98928f6bd7 enh: save chat controls width 2024-09-21 03:37:28 +02:00
Timothy J. Baek 692f04d457 enh: width adjustable chat controls 2024-09-21 03:33:06 +02:00
kivvi e1ea0c23eb Fixed the issue of being unable to generate titles 2024-09-21 09:06:28 +08:00
Timothy J. Baek 657d443a3e fix 2024-09-21 02:24:30 +02:00
Timothy J. Baek 95985e7bbb refac 2024-09-21 02:03:58 +02:00
Timothy J. Baek 3a0a1aca11 refac: task ollama stream support 2024-09-21 01:07:57 +02:00
Timothy J. Baek 41926172d3 fix/refac: use ollama /api/chat endpoint for tasks 2024-09-21 00:30:13 +02:00
Timothy J. Baek 585b9eb84a refac 2024-09-20 23:48:03 +02:00
Timothy J. Baek ffd7d74f77 enh: websocket redis support 2024-09-20 23:43:22 +02:00
Timothy Jaeryang Baek 578d52b89d Merge pull request #5562 from open-webui/dev
fix: chat download as plain text issue
2024-09-20 23:19:51 +02:00
Timothy J. Baek e599d5db3c fix: chat download as plain text issue 2024-09-20 23:19:20 +02:00
Timothy Jaeryang Baek 79b5430a9e Merge pull request #5546 from itaybar/bugfix/downgrade_authlib
downgrade authlib to 1.3.1
2024-09-20 17:41:19 +02:00
Timothy J. Baek 9b05fe3c54 Update pyproject.toml 2024-09-20 17:40:30 +02:00
Timothy Jaeryang Baek 5fe2795db8 Merge pull request #5556 from open-webui/dev
refac
2024-09-20 15:44:31 +02:00
Timothy J. Baek 732f730213 refac: click to focus on mesage node 2024-09-20 15:43:18 +02:00
Timothy Jaeryang Baek c89bb01db9 Merge pull request #5554 from KarlLee830/translate
i18n: Update Chinese translation
2024-09-20 15:34:58 +02:00
Karl Lee 1cbbf75807 i18n: Update Chinese translation 2024-09-20 21:06:10 +08:00
itaybar bd81fc8bff downgrade authlib to 1.3.1 2024-09-20 15:13:03 +03:00
Timothy Jaeryang Baek d4df552076 Merge pull request #5537 from open-webui/dev
refac
2024-09-20 03:18:40 +02:00
Timothy J. Baek b7ad82757d refac 2024-09-20 03:18:14 +02:00
Timothy J. Baek d12b6cda4e refac: openai image async gen 2024-09-20 03:16:08 +02:00
Timothy Jaeryang Baek 83855b713b Merge pull request #5362 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.22
2024-09-20 00:25:27 +02:00
Timothy J. Baek bd09b6dbad doc: changelog 2024-09-20 00:25:09 +02:00
Timothy J. Baek eb9ad47ef8 refac: temp tools & functions files 2024-09-20 00:12:52 +02:00
Timothy J. Baek 5b46a252ff doc: changelog 2024-09-20 00:00:45 +02:00
Timothy J. Baek 39c57c0e94 refac 2024-09-19 22:40:06 +02:00
Timothy J. Baek 619dbbe9f5 refac 2024-09-19 22:21:35 +02:00
Timothy J. Baek 9be73ea94a refac 2024-09-19 22:17:32 +02:00
Timothy J. Baek 4e43663448 refac 2024-09-19 22:12:54 +02:00
Timothy J. Baek cd117f5b67 refac 2024-09-19 21:51:45 +02:00
Timothy J. Baek 2dad9b9432 refac 2024-09-19 20:56:13 +02:00
Timothy J. Baek b0bc36f2af fix 2024-09-19 18:47:02 +02:00
Timothy J. Baek 1688f5ecaf refac 2024-09-19 18:42:01 +02:00
Timothy J. Baek cba2d31175 refac 2024-09-19 18:40:23 +02:00
Timothy J. Baek a2b77fd072 refac 2024-09-19 18:08:52 +02:00
Timothy J. Baek 9b83e57372 refac 2024-09-19 17:46:11 +02:00
Timothy J. Baek 687cae9b79 refac 2024-09-19 17:31:59 +02:00
Timothy J. Baek f8fffdd288 refac: max_tokens -> max_completion_tokens 2024-09-19 17:19:31 +02:00
Timothy J. Baek 60d6279055 chore: format 2024-09-19 17:09:05 +02:00
Timothy J. Baek 7b330d1490 refac 2024-09-19 17:05:49 +02:00
Timothy J. Baek 7078af635c refac 2024-09-19 17:02:59 +02:00
Timothy J. Baek e1b57d80a4 chore: format 2024-09-19 16:57:34 +02:00
Timothy J. Baek 8426874426 fix 2024-09-19 16:54:34 +02:00
Timothy J. Baek 70dd790afc refac: default rag params 2024-09-19 16:44:33 +02:00
Timothy J. Baek e99cba53fe enh: stream=false support 2024-09-19 16:25:59 +02:00
Timothy J. Baek 628d7ae72d refac 2024-09-19 16:20:07 +02:00
Timothy J. Baek ff737a9e25 enh: openai error message handling 2024-09-19 16:20:00 +02:00
Timothy J. Baek 5c16631ec5 refac 2024-09-19 15:35:01 +02:00
Timothy Jaeryang Baek d52eaf19d6 Merge pull request #5511 from EtiennePerot/import-file
fix: restore `__file__` variable for imported toolkits and functions
2024-09-19 15:29:34 +02:00
Timothy J. Baek 27dd6ef14e refac 2024-09-19 05:15:06 +02:00
Timothy J. Baek ed75f72358 refac 2024-09-19 03:53:07 +02:00
Timothy J. Baek 276d629a14 fix: message delete 2024-09-19 03:49:35 +02:00
Timothy J. Baek aedd77b81d refac 2024-09-19 03:27:54 +02:00
Timothy J. Baek 1053863175 chore: format 2024-09-19 03:24:39 +02:00
Timothy J. Baek f448341211 chore: format 2024-09-19 03:24:34 +02:00
Timothy Jaeryang Baek 9fc6b999d0 Merge pull request #5509 from open-webui/dev-playback-controls
feat: playback controls
2024-09-19 03:23:16 +02:00
Timothy J. Baek ff2fff857a refac: styling 2024-09-19 03:22:55 +02:00
Timothy Jaeryang Baek dd4cf102cc Merge pull request #5313 from zabirauf/u/zabirauf/speech-speed
feat: Added speech playback speed control for Call mode
2024-09-19 02:44:27 +02:00
Timothy Jaeryang Baek 5d3a89dd25 Merge pull request #5496 from pawel-ochman/azure-tts
Integrate Azure Speech service for TTS
2024-09-19 02:42:45 +02:00
Timothy J. Baek b4f1a0b5a6 refac 2024-09-19 02:42:24 +02:00
Timothy J. Baek afa42dd2e4 refac 2024-09-19 02:40:54 +02:00
Timothy J. Baek be44af4680 enh: focus on current message 2024-09-19 01:25:46 +02:00
Timothy J. Baek e6b6f42139 refac: display error content on node 2024-09-18 16:46:13 +02:00
Pawel Ochman 4d9677e808 Update configuration page, expose all Azure settings through ENV variables 2024-09-18 14:13:42 +01:00
Pawel Ochman eacb69074e remove dependency and migrate to raw rest calls 2024-09-18 12:24:55 +01:00
Etienne Perot 6477bf37fe fix: restore __file__ variable for imported toolkits and functions
Commit cf86ba7786 changed the way toolkits
and functions were imported to use `exec`, whereas they previously were
written to files and `import`ed. The use of `exec` means that
module-global variables such as `__file__` are no longer defined.

This breaks https://github.com/EtiennePerot/open-webui-code-execution
(code execution tool for Open WebUI), as the module needs to re-execute
its own code in a subprocess in order to properly sandbox itself. This
is done using `__file__` in order to know where the module's code is
located.

This PR creates a temporary in-memory file that contains the imported
toolkit or function's code and exists only during the import process.
Then it injects the path to this in-memory file as the `__file__`
variable in the `exec` context. This restores the ability for the
toolkit or function being imported to rely on `__file__`.
2024-09-18 01:08:30 -07:00
Timothy J. Baek 1743d3c6c1 fix 2024-09-18 03:47:04 +02:00
Timothy J. Baek e723b2a4c6 refac 2024-09-18 03:19:32 +02:00
Timothy J. Baek 67f704c98d enh: scroll to message from overview 2024-09-18 03:13:37 +02:00
Timothy J. Baek 8d92093570 enh: node show content as tooltip 2024-09-18 02:55:25 +02:00
Timothy J. Baek 1ccac9111b refac: only animate active edges 2024-09-18 02:42:19 +02:00
Timothy J. Baek 98984166f9 refac: styling 2024-09-18 02:11:25 +02:00
Timothy J. Baek d1bf18eeb0 fix 2024-09-18 01:57:27 +02:00
Timothy J. Baek 2018a6c000 fix: styling 2024-09-18 01:53:08 +02:00
Timothy J. Baek 56152230f8 refac 2024-09-18 01:39:22 +02:00
Timothy J. Baek 7e8cf5504a refac: styling 2024-09-18 01:35:22 +02:00
Timothy J. Baek 3c03d5069d refac: styling 2024-09-18 01:31:49 +02:00
Timothy J. Baek 705508a674 refac: allow chat menu in mobile 2024-09-18 01:30:09 +02:00
Timothy J. Baek 771482c6e6 refac 2024-09-18 01:25:28 +02:00
Timothy J. Baek d25ccfba5f refac: styling 2024-09-18 01:21:32 +02:00
Timothy J. Baek 614c219010 enh: prepend image filename with prompt 2024-09-18 01:04:20 +02:00
Timothy J. Baek 63b3076d64 refac 2024-09-18 00:52:06 +02:00
Timothy J. Baek 8e3ad45ce4 enh: model move to top 2024-09-18 00:49:27 +02:00
Timothy J. Baek 984e0c533e refac 2024-09-18 00:18:47 +02:00
Timothy J. Baek 5065291f72 feat: save as new response message 2024-09-17 23:36:48 +02:00
Timothy J. Baek 8c273ba58a refac 2024-09-17 23:23:38 +02:00
Timothy J. Baek 7a9c0946a4 refac 2024-09-17 23:13:51 +02:00
Timothy J. Baek 7e0a26ef4e refac 2024-09-17 23:07:04 +02:00
Timothy J. Baek 65b7c9898b fix 2024-09-17 23:02:41 +02:00
Timothy J. Baek 67f95ddfdc refac 2024-09-17 22:58:06 +02:00
Timothy J. Baek d1dbb9a3be feat: chat overview 2024-09-17 22:05:19 +02:00
Timothy J. Baek bb087a5989 enh: chat copy button 2024-09-17 21:32:39 +02:00
Timothy Jaeryang Baek a0d24105fc Merge pull request #5473 from aleixdorca/dev
i18n: Update Catalan Translation
2024-09-17 17:18:40 +02:00
Timothy Jaeryang Baek a5c9160b7d Merge pull request #5466 from phil-ogb/add-security-response-headers
feat: security response headers
2024-09-17 17:18:28 +02:00
Aleix Dorca 0e2cdbe9fb Minor typo 2024-09-17 16:49:05 +02:00
Aleix Dorca 3c55a46484 i18n: Update Catalan translation.json 2024-09-17 16:46:52 +02:00
Pawel Ochman d6b68f405e added azure speech service support 2024-09-17 09:13:10 +01:00
Pawel Ochman 351bbdb36c Added Azure speach service option (UI) 2024-09-17 08:47:30 +01:00
Phil Ogbonna 896baf021b update comment block 2024-09-16 22:02:55 -03:00
Phil Ogbonna 499e5e4f60 feat: security response headers 2024-09-16 21:53:30 -03:00
Timothy J. Baek 3ad003bccb fix 2024-09-16 16:42:18 +02:00
Timothy J. Baek af92184c93 chore: colbert-ai requirement 2024-09-16 16:19:40 +02:00
Timothy J. Baek 06debb322b refac: colbert cuda support 2024-09-16 12:42:48 +02:00
Timothy J. Baek b7f0759485 refac 2024-09-16 12:36:43 +02:00
Timothy J. Baek bc6f23f82f fix 2024-09-16 12:33:55 +02:00
Timothy J. Baek cb9e76c7f9 refac: default rag template 2024-09-16 12:01:04 +02:00
Timothy J. Baek b38986a0aa enh: colbert rerank support 2024-09-16 11:46:39 +02:00
Timothy J. Baek db0c576f48 refac 2024-09-16 07:14:17 +02:00
Timothy Jaeryang Baek 40d7e5089d Merge pull request #5436 from khanh-alice/fix-transparent-icons
fix: model transparent icons
2024-09-16 07:05:17 +02:00
Timothy Jaeryang Baek 9b8d42c670 Merge pull request #5440 from cheahjs/feat/capabilities-help
feat: add help text for capabilities
2024-09-16 05:17:32 +02:00
Jun Siang Cheah bed3b71860 feat: add help text for capabilities 2024-09-15 14:14:09 +01:00
Khanh Le 253791b92c save model icon as png 2024-09-15 16:43:42 +07:00
Timothy J. Baek 5dd6ae6ec4 chore: npm 2024-09-14 23:30:56 +01:00
Timothy J. Baek 902f30c123 enh: table view allow overflow 2024-09-14 23:23:52 +01:00
Timothy J. Baek fa8d7bd9c6 enh: allow new lines in default prompts 2024-09-14 23:17:58 +01:00
Timothy J. Baek d0df2cbe53 fix: disable "enable new sign ups" not working issue 2024-09-14 23:13:52 +01:00
Timothy Jaeryang Baek 27baa00afb Merge pull request #5419 from cheahjs/fix/remove-doc-support
fix: remove unsupported .doc file ingest
2024-09-14 22:52:40 +01:00
Timothy J. Baek 869063c743 refac: better reranking model error handling 2024-09-14 20:58:42 +01:00
Timothy Jaeryang Baek 2f9f568dd9 Merge pull request #5400 from thiswillbeyourgithub/fix_fallback_cuda
fix: if cuda is not available fallback to cpu
2024-09-14 20:57:14 +01:00
Timothy Jaeryang Baek 937ce5f797 Merge pull request #5411 from KarlLee830/translate
i18n: Update Chinese translation
2024-09-14 20:56:13 +01:00
Timothy Jaeryang Baek 1b612209a3 Merge pull request #5429 from OriginalSimon/dev
i18n: Update Ukrainian translation
2024-09-14 20:56:03 +01:00
Simon d00d3341af Update translation.json 2024-09-14 20:32:04 +02:00
Jun Siang Cheah 3199e26500 fix: remove unsupported .doc file ingest 2024-09-14 12:44:51 +01:00
Karl Lee bc06e7a282 i18n: Update Chinese translation 2024-09-14 10:10:15 +08:00
Timothy Jaeryang Baek 1eec9e2b59 Merge pull request #5407 from Peter-De-Ath/citations-page-no
feat: add page no to citations
2024-09-13 21:55:45 +01:00
Peter De-Ath d46f652f7e enh: show page number in citation if known 2024-09-13 21:05:28 +01:00
Timothy Jaeryang Baek d75d638b9a Merge pull request #5406 from open-webui/main
refac
2024-09-13 19:26:51 +01:00
Timothy J. Baek b64c9d966a refac 2024-09-13 14:26:32 -04:00
Timothy Jaeryang Baek 8a928e5356 Merge pull request #5405 from open-webui/main
refac: filter non chat completions models
2024-09-13 19:23:02 +01:00
Timothy J. Baek adf958559b refac: filter non chat completions models 2024-09-13 14:20:27 -04:00
thiswillbeyourgithub 82b35492af pep8 2024-09-13 17:18:44 +02:00
thiswillbeyourgithub a7ea07036e fix: if cuda is not available fallback to cpu 2024-09-13 17:12:00 +02:00
Timothy Jaeryang Baek c1d3481c41 Merge pull request #5399 from open-webui/main
fix: allow o1
2024-09-13 15:01:48 +01:00
Timothy J. Baek bc0baa35e6 fix: allow o1 2024-09-13 09:59:41 -04:00
Timothy J. Baek f549cb1f87 fix 2024-09-13 01:30:30 -04:00
Timothy J. Baek 823093eea6 fix: hybrid search 2024-09-13 01:21:47 -04:00
Timothy J. Baek 939bfd153e refac 2024-09-13 01:18:20 -04:00
Timothy J. Baek b943b7d337 refac 2024-09-13 01:08:02 -04:00
Timothy J. Baek a1f3ece528 refac 2024-09-13 01:07:03 -04:00
Timothy J. Baek 8a4b3e6bc9 refac: allow multiple [context] in prompt 2024-09-13 01:06:51 -04:00
Timothy J. Baek a9c497612b refac: rm assert 2024-09-13 00:56:50 -04:00
Timothy J. Baek 7df997c992 chore: format 2024-09-13 00:49:23 -04:00
Timothy J. Baek bebc0d2073 chore: format 2024-09-13 00:48:54 -04:00
Timothy Jaeryang Baek 857fc3ce99 Merge pull request #5276 from sebdanielsson/add-theme-color
fix: Set theme colors to fix white top bar for PWA
2024-09-13 05:43:25 +01:00
Timothy J. Baek 47e9c12fc2 refac 2024-09-13 00:41:20 -04:00
Timothy Jaeryang Baek 5970dadead Merge pull request #5256 from Bazsalanszky/main
feat: More options for AUTOMATIC1111
2024-09-13 05:40:09 +01:00
Timothy Jaeryang Baek 7dc4cb30b2 Merge pull request #5378 from thiswillbeyourgithub/fix_RAG_and_web
fix: RAG and Web Search + RAG enhancements
2024-09-13 05:38:53 +01:00
Timothy Jaeryang Baek fb122e67d0 Merge pull request #5382 from Peter-De-Ath/hotfix/chroma-metadata-s
fix: change metadata to metadatas
2024-09-12 22:30:08 +01:00
Peter De-Ath ca9874757f fix: change metadata to metadatas 2024-09-12 20:14:14 +01:00
thiswillbeyourgithub 65d5545cf0 added a few type hints 2024-09-12 17:22:22 +02:00
thiswillbeyourgithub e872f5dc78 log: added a debug log if detecting a potential prompt injection attack 2024-09-12 17:22:22 +02:00
thiswillbeyourgithub b4ad64586a fix: add check that the context for RAG is not empty if the threshold is 0 2024-09-12 17:22:22 +02:00
thiswillbeyourgithub 9661fee554 fix: handle case where [query] happens in the RAG context 2024-09-12 17:22:22 +02:00
thiswillbeyourgithub adf26789b8 logs: crash if rag_template would be wrong 2024-09-12 17:22:22 +02:00
thiswillbeyourgithub 209e246e6f fix: much improved RAG template 2024-09-12 17:22:22 +02:00
thiswillbeyourgithub ed2a1e7db9 enh: use non hybrid search as fallback if hybrid search failed 2024-09-12 17:22:22 +02:00
Timothy J. Baek 143ac08c35 refac 2024-09-12 10:56:16 -04:00
thiswillbeyourgithub 53f03f6556 fix: log exception when issues of collection querying 2024-09-12 16:51:37 +02:00
Timothy J. Baek 62f1933e3c refac: audio input (audio/ogg support) 2024-09-12 09:18:20 -04:00
Timothy J. Baek 0b30dc357c refac: remove prints 2024-09-12 09:13:21 -04:00
Timothy J. Baek d5b595a842 fix: ol numbering 2024-09-12 09:11:45 -04:00
Balazs Toldi d05ba042c0 Make the optional AUTOMATIC1111 values nullable
This commit makes the optional AUTOMATIC1111 options default to None, and if the value is removed, it resets to None.

Signed-off-by: Balazs Toldi <balazs@toldi.eu>
2024-09-12 14:00:24 +02:00
Timothy J. Baek eed2d735a1 refac 2024-09-12 02:06:02 -04:00
Timothy Jaeryang Baek 675403d26d Merge pull request #5364 from open-webui/multiple-vector-dbs
refac: vector db clients
2024-09-12 07:00:55 +01:00
Timothy J. Baek 8be6e16513 refac: vector db clients 2024-09-12 02:00:31 -04:00
Timothy Jaeryang Baek c7fc17da69 Merge pull request #5312 from open-webui/multiple-vector-dbs
feat: various vector db support
2024-09-12 06:53:47 +01:00
Timothy J. Baek 4775fe43d8 feat: milvus support 2024-09-12 01:52:19 -04:00
Sebastian 2a04dd0f9b Set theme colors to fix white top bar for PWA 2024-09-11 01:46:39 +02:00
Timothy Jaeryang Baek 61ee4bd629 Merge pull request #5319 from Liuzhch1/fix-openai_timeout
fix: get OpenAI models only if OpenAI API Enabled; timeout for querying OpenAI models
2024-09-10 23:03:20 +01:00
Timothy Jaeryang Baek dbd661a417 Merge pull request #5318 from moblangeois/french-translation
i18n: Update French (fr-FR) translation
2024-09-10 23:02:34 +01:00
Liuzhch1 26700ac4ac fix: get OpenAI models only if OpenAI Enabled;timeout for query OpenAI models 2024-09-10 19:22:05 +08:00
Morgan Blangeois b0defe5524 i18n: Update French (fr-FR) translation 2024-09-10 12:54:47 +02:00
Zohaib Rauf 91cd8c7608 Undid a unnecessary change 2024-09-09 21:59:13 -07:00
Zohaib Rauf 62b01c5f8e Cleaned up 2024-09-09 21:57:53 -07:00
Zohaib Rauf b33ab6c5fd Updated to use Dropdown menu which is being used elsewhere in app 2024-09-09 21:33:48 -07:00
Timothy J. Baek 0886b3a0a4 refac: comments 2024-09-10 04:46:40 +01:00
Timothy J. Baek 522afbb0a0 refac 2024-09-10 04:37:06 +01:00
Timothy Jaeryang Baek d5f13dd9e0 Merge pull request #5311 from open-webui/dev
dev
2024-09-10 02:30:02 +01:00
Timothy Jaeryang Baek 3d6d8c91dd Merge branch 'multiple-vector-dbs' into dev 2024-09-10 02:29:55 +01:00
Timothy J. Baek 4354f270ce refac 2024-09-10 02:27:50 +01:00
Timothy J. Baek 28087ccf40 refac 2024-09-10 01:37:36 +01:00
Timothy J. Baek eb0e683b47 refac 2024-09-10 01:34:27 +01:00
Timothy Jaeryang Baek 1023ff8454 Merge pull request #5270 from cheahjs/fix/websocket-take-2
fix: socket.io connections failing when websockets are not available
2024-09-09 23:19:05 +01:00
Jun Siang Cheah 9401f6c821 fix: workaround socketio upstream bug when websockets are not available 2024-09-09 23:17:34 +01:00
Jun Siang Cheah 827c419251 feat: add ENABLE_WEBSOCKET_SUPPORT to force socket.io to ignore websocket upgrades 2024-09-09 23:17:17 +01:00
Timothy Jaeryang Baek 82db64a700 Merge pull request #5279 from afritzler/enh/kustomization
feat: Improve `kustomization` usage
2024-09-09 23:04:29 +01:00
Timothy J. Baek f1fae805a2 fix: separate /embed and /embedding ollama endpoint 2024-09-09 23:02:26 +01:00
Timothy Jaeryang Baek 272e2386dd Merge pull request #5300 from FINNSEEFLY/task/adjust-ru-localization
i18n: Update ru-RU localization
2024-09-09 22:59:44 +01:00
FINNSEEFLY 67e1c0a10b Adjust ru-RU localization 2024-09-09 22:49:55 +03:00
Zohaib Rauf f88a86f9b0 Fixed build error 2024-09-08 22:57:33 -07:00
Timothy J. Baek 601982f52b fix: lengthy chat title delete ui issue 2024-09-09 04:52:12 +01:00
Timothy Jaeryang Baek fc839011f6 Merge pull request #5275 from sebdanielsson/safe-area
fix: Add padding to compensate for iPhone nav bar
2024-09-09 04:42:45 +01:00
Timothy Jaeryang Baek b407f24950 Merge pull request #5277 from msurma/dev
fix: incorrect casting of top_p and frequency_penalty
2024-09-09 04:36:22 +01:00
Timothy Jaeryang Baek d15f57cfff Merge pull request #5266 from KarlLee830/translate
i18n: Update Chinese translation
2024-09-09 04:34:47 +01:00
Andreas Fritzler 82fbfd69a5 Improve kustomization usage
Ensure that the `manifest/base` folder can be used as a standalone
kustomization resource.

Add a new subfolder `manifest/gpu` which uses `manifest/base` with
additional GPU related patches.
2024-09-08 20:43:00 +02:00
Michał Surmaczewski 8e6ea49e0e fix: incorrect casting of top_p and frequency_penalty 2024-09-08 17:52:58 +02:00
Sebastian 83a3e53d8d Add padding to compensate for iPhone nav bar 2024-09-08 17:52:10 +02:00
Jun Siang Cheah 698976add0 feat: add ENABLE_WEBSOCKET_SUPPORT to force socket.io to ignore websocket upgrades 2024-09-08 12:00:36 +01:00
Karl Lee cc1d3c48e0 i18n: Update Chinese translation 2024-09-08 16:51:14 +08:00
Timothy Jaeryang Baek e510c8b11f Merge pull request #5263 from open-webui/dev
refac
2024-09-08 01:52:27 +01:00
Timothy J. Baek 666086a806 refac 2024-09-08 01:17:02 +01:00
Timothy J. Baek 9bdbe88bda refac: default search query prompt 2024-09-08 01:04:57 +01:00
Timothy Jaeryang Baek 50db51ebe0 Merge pull request #5262 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.21
2024-09-08 00:59:40 +01:00
Timothy J. Baek 0beaab51ae doc: changelog 2024-09-08 00:57:59 +01:00
Timothy Jaeryang Baek 0e30b0f9b4 Merge pull request #5010 from jannikstdl/show-total-docs
feat: show total number of documents
2024-09-08 00:52:45 +01:00
Timothy J. Baek 214722d39e enh: search query generation prompt 2024-09-08 00:51:12 +01:00
Balazs Toldi 7f6dae41f0 More options for AUTOMATIC1111
This commit adds 3 new options to the AUTOMATIC1111 settings:
- CFG Scale
- Sampler
- Scheduler

These options allow users to configure these parameters directly through the admin settings, without needing to modify the source code, which was previously required to change the default values in  AUTOMATIC1111.

Signed-off-by: Balazs Toldi <balazs@toldi.eu>
2024-09-07 17:21:17 +02:00
Jannik S. 9aa8eff44e styling 2024-09-07 15:08:25 +02:00
Timothy Jaeryang Baek 2544f7eaf0 Merge pull request #5249 from open-webui/dev
refac: default search generation prompt
2024-09-07 06:42:12 +02:00
Timothy J. Baek 1a6ce1d5d9 refac: default search generation prompt 2024-09-07 05:41:55 +01:00
Timothy Jaeryang Baek 98eaec22d4 Merge pull request #5248 from open-webui/dev
fix
2024-09-07 06:20:35 +02:00
Timothy J. Baek 2e40719f4e fix 2024-09-07 05:18:52 +01:00
Timothy Jaeryang Baek 71a2bd2fea Merge pull request #5247 from open-webui/dev
refac: enable /api/embed
2024-09-07 06:13:02 +02:00
Timothy J. Baek 1c20db775c refac: enable /api/embed 2024-09-07 05:12:46 +01:00
Timothy Jaeryang Baek e2ef36b582 Merge pull request #5238 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.20
2024-09-07 06:08:23 +02:00
Timothy J. Baek 90a064972c fix 2024-09-07 05:07:37 +01:00
Timothy J. Baek f4a0b845be doc: wording 2024-09-07 05:06:32 +01:00
Timothy J. Baek 1f22aa99d8 doc: changelog 2024-09-07 05:05:54 +01:00
Timothy J. Baek 4c9ea084d5 chore: format 2024-09-07 05:03:26 +01:00
Timothy J. Baek a3094bcd1b fix: all response messages re-rendering on new message 2024-09-07 04:54:48 +01:00
Timothy J. Baek 5c8fb4b3d5 refac: web search 2024-09-07 04:50:29 +01:00
Timothy J. Baek ff46fe2b4a refac 2024-09-07 03:09:57 +01:00
Timothy J. Baek 8d6a424604 fix: markdown proper spacing 2024-09-07 02:56:58 +01:00
Timothy J. Baek 8dfbdbd883 fix: mic kept alive after call issue 2024-09-07 01:28:07 +01:00
Timothy J. Baek f533c9750b fix: close chat controls after call ends 2024-09-07 01:12:43 +01:00
Timothy J. Baek 2f841f9f5a fix: enable inline link image rendering 2024-09-06 23:40:35 +02:00
Timothy J. Baek 02d5bca44d fix: tools & function not installing requirements 2024-09-06 18:35:43 +02:00
Timothy J. Baek 14eda1bf5b fix: pdf download FONTS_DIR issue 2024-09-06 15:52:23 +02:00
Timothy Jaeryang Baek 2d4cdc5be9 Merge pull request #5209 from aleixdorca/dev
i18n: Update Catalan translation.json
2024-09-06 14:52:55 +02:00
Aleix Dorca d038e831dc i18n: Update Catalan translation.json 2024-09-06 07:20:21 +02:00
Timothy J. Baek 9fe62fc80d refac 2024-09-06 05:30:16 +02:00
Timothy J. Baek bfb12a7851 refac 2024-09-06 04:59:20 +02:00
Timothy Jaeryang Baek 4617f3a4e2 Merge pull request #5200 from open-webui/dev
fix
2024-09-05 21:01:12 +02:00
Timothy J. Baek 97e331632c fix: call overlay issue 2024-09-05 21:00:53 +02:00
Timothy J. Baek 4cf777ab5a refac: styling 2024-09-05 20:52:27 +02:00
Timothy Jaeryang Baek 05c0423d6e Merge pull request #5197 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.19
2024-09-05 20:47:33 +02:00
Timothy J. Baek 377efc8a0c doc: changelog 2024-09-05 20:46:32 +02:00
Timothy J. Baek b35bbaade2 fix: extract_frontmatter issue 2024-09-05 20:35:58 +02:00
Timothy J. Baek c9107fa87f refac 2024-09-05 19:11:27 +02:00
Timothy J. Baek 0c1fab09ff refac 2024-09-05 18:55:31 +02:00
Timothy J. Baek 8b67959695 refac: styling 2024-09-05 18:44:52 +02:00
Timothy J. Baek 21e81d78d9 refac 2024-09-05 17:25:24 +02:00
Timothy J. Baek 31623ff330 refac: styling 2024-09-05 17:23:59 +02:00
Timothy J. Baek 7edfffdcc5 refac: styling 2024-09-05 17:11:55 +02:00
Timothy Jaeryang Baek 3b868be77b Merge pull request #5185 from open-webui/main
refac: styling
2024-09-05 17:04:10 +02:00
Timothy J. Baek c3271e84ef refac: styling 2024-09-05 17:03:09 +02:00
Timothy Jaeryang Baek 1cac9fce46 Merge pull request #5182 from open-webui/main
dev
2024-09-05 16:26:15 +02:00
Timothy J. Baek d0869bbfbc fix: migrations 2024-09-05 16:23:19 +02:00
Timothy J. Baek ca4beb413b refac: styling 2024-09-05 16:21:34 +02:00
Timothy Jaeryang Baek 7b31ef60fb Merge pull request #5161 from KarlLee830/translate
i18n: Update Chinese translation
2024-09-05 16:15:46 +02:00
Timothy Jaeryang Baek 73576dfcaf Merge pull request #5180 from open-webui/main
dev
2024-09-05 16:14:11 +02:00
Timothy J. Baek 8a411decac fix 2024-09-05 16:13:40 +02:00
Timothy Jaeryang Baek fa786c8e05 Merge pull request #5160 from vikrantrathore/main
fix: avoid overriding DATA_DIR and prevent errors when directories are the same
2024-09-05 16:11:42 +02:00
Timothy J. Baek 85fc35492d refac 2024-09-05 16:11:07 +02:00
Karl Lee 1c94f4dd71 i18n: Update Chinese translation 2024-09-05 15:28:13 +08:00
vikrantrathore 74169b0320 fix: avoid overriding DATA_DIR and prevent errors when directories are the same
Previously, the `DATA_DIR` environment variable was always overridden by defaulting to `OPEN_WEBUI_DIR / "data"`, which ignored user-defined `DATA_DIR` values. Additionally, when `DATA_DIR` and `NEW_DATA_DIR` were the same, the script attempted to copy files into themselves, leading to errors or redundant operations.

This commit ensures that:
1. The `DATA_DIR` environment variable is respected and not overridden.
2. Copy operations between `DATA_DIR` and `NEW_DATA_DIR` are only performed if the directories are different, preventing errors when they point to the same location.

These changes resolve potential file copy errors and preserve user configurations.
2024-09-05 14:17:58 +08:00
Timothy Jaeryang Baek 83574ccf6c Merge pull request #5159 from drupol/let-customize-pip-install
feat: add `OVERRIDE_PIP_INSTALL` env. variable
2024-09-04 23:01:01 +02:00
Timothy J. Baek a0be3822bf refac 2024-09-04 23:00:01 +02:00
Pol Dellaiera 004700c125 feat: add OVERRIDE_PIP_INSTALL environment variable 2024-09-04 22:44:59 +02:00
Timothy Jaeryang Baek 19ee110b7d Merge pull request #5158 from pascallim/fix/start_scripts
fix scripts to point to correct path
2024-09-04 20:29:58 +02:00
Pascal Lim 998616c0fd fix scripts to point to correct path 2024-09-04 20:27:30 +02:00
Timothy Jaeryang Baek 9204498420 Merge pull request #5157 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.18
2024-09-04 20:09:58 +02:00
Timothy J. Baek b1957e5cfe doc: changelog 2024-09-04 20:08:14 +02:00
Timothy J. Baek 92a88df484 refac 2024-09-04 20:00:47 +02:00
Timothy J. Baek f2f713023d refac 2024-09-04 19:57:41 +02:00
Timothy J. Baek cf86ba7786 refac: tools & functions 2024-09-04 19:55:20 +02:00
Timothy J. Baek 94502d6494 fix: styling 2024-09-04 19:55:10 +02:00
Timothy Jaeryang Baek 5e3f9ec757 Merge pull request #5156 from open-webui/dev
fix
2024-09-04 19:04:03 +02:00
Timothy J. Baek d6fc0ccf65 fix 2024-09-04 19:03:51 +02:00
Timothy Jaeryang Baek a988c53949 Merge pull request #5155 from open-webui/dev
fix: tools & functions import
2024-09-04 19:00:52 +02:00
Timothy J. Baek 3afd66d50f fix: tools & functions import 2024-09-04 18:59:50 +02:00
Timothy Jaeryang Baek a9801147b8 Merge pull request #5051 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.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
Jannik Streidl 0ead3ed37a feat: show total number of documents 2024-08-29 16:30:34 +02: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
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.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
Timothy Jaeryang Baek 847ca66001 Merge pull request #4795 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.15
2024-08-22 00:28:30 +02:00
Timothy J. Baek 85b4129219 fix 2024-08-22 00:25:43 +02:00
Timothy J. Baek c36f83df5b doc: changelog 2024-08-22 00:23:40 +02:00
Timothy J. Baek bb026cdd9c fix: many model chat backward compatibility 2024-08-22 00:22:40 +02:00
Timothy J. Baek 8843898a8c fix: older many model chat compatibility 2024-08-22 00:03:58 +02:00
Timothy J. Baek f036aa0a48 doc: changelog 2024-08-21 23:47:33 +02:00
Timothy J. Baek 1e928e463f chore: format 2024-08-21 23:35:54 +02:00
Timothy J. Baek 36e895c135 fix 2024-08-21 23:33:32 +02:00
Timothy J. Baek 3447f233fa fix 2024-08-21 23:27:23 +02:00
Timothy J. Baek f3e8dd1f2e enh: comfyui logs 2024-08-21 23:22:12 +02:00
Timothy J. Baek 3f0b7b29db fix 2024-08-21 23:11:12 +02:00
Timothy J. Baek 95cf90d787 enh: comfyui seed node support 2024-08-21 23:05:20 +02:00
Timothy J. Baek f3f6941205 fix: many model chat actions not working 2024-08-21 22:54:56 +02:00
Timothy J. Baek 4a21c5c5e7 refac 2024-08-21 22:42:25 +02:00
Timothy J. Baek 180f2b9a83 fix: functions 2024-08-21 22:39:36 +02:00
Timothy J. Baek 94eb91063c fix 2024-08-21 22:27:21 +02:00
Timothy J. Baek dc4c6b3b14 enh: temporary-chat url search param 2024-08-21 18:55:12 +02:00
Timothy Jaeryang Baek 50d53c6f8d Merge pull request #4790 from open-webui/dev
fix
2024-08-21 18:33:57 +02:00
Timothy J. Baek a93b0ac143 fix 2024-08-21 18:33:31 +02:00
Timothy Jaeryang Baek ed92205d7d Merge pull request #4789 from open-webui/dev
fix
2024-08-21 18:30:10 +02:00
Timothy J. Baek acaf135a2f fix 2024-08-21 18:29:52 +02:00
Timothy Jaeryang Baek bf6c6afb21 Merge pull request #4788 from open-webui/main
dev
2024-08-21 17:41:15 +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
Timothy Jaeryang Baek 8a620cab44 Merge pull request #4623 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.14
2024-08-21 17:36:58 +02:00
Timothy J. Baek fcffb0adf2 fix 2024-08-21 17:28:09 +02:00
Timothy J. Baek efcb56f0dc refac 2024-08-21 17:27:39 +02:00
Timothy J. Baek 86ee19178e doc: changelog 2024-08-21 17:09:18 +02:00
Timothy J. Baek 1fc306acd0 chore: backend bump 2024-08-21 15:47:03 +02:00
Timothy J. Baek da1d5ad917 fix 2024-08-21 15:42:33 +02:00
Timothy J. Baek b45db22a17 chore: format 2024-08-21 15:16:37 +02:00
Timothy J. Baek 19455ab04e fix 2024-08-21 15:15:29 +02:00
Timothy J. Baek 71b1661f00 fix 2024-08-21 14:49:54 +02:00
Timothy J. Baek 99764bfd29 fix 2024-08-21 14:49:17 +02:00
Timothy J. Baek 063e006446 feat: custom comfyui workflow
Co-Authored-By: John Karabudak <hello@johnthenerd.com>
2024-08-21 14:44:47 +02:00
Timothy J. Baek 436f009dab refac 2024-08-21 14:20:52 +02:00
Timothy J. Baek 71a66ed4cf refac 2024-08-21 14:18:47 +02:00
Timothy J. Baek 29bef261be refac 2024-08-21 14:16:33 +02:00
Timothy J. Baek e2291f7148 refac 2024-08-21 01:39:30 +02:00
Timothy J. Baek 8b5aed7a2b refac 2024-08-21 01:21:03 +02:00
Timothy J. Baek 95057d2368 refac: image gen 2024-08-21 00:35:42 +02:00
Timothy Jaeryang Baek 20dadf9b5a Merge pull request #4760 from michaelpoluektov/tools-refac-2.1
Fix: tools filter
2024-08-20 18:51:28 +02:00
Michael Poluektov 3d6ac3a7db Merge branch 'dev' of https://github.com/open-webui/open-webui into tools-refac-2.1 2024-08-20 17:42:25 +01:00
Michael Poluektov bd47bbbce9 fix tools filter 2024-08-20 17:41:51 +01:00
Timothy Jaeryang Baek ddebfc7413 Merge pull request #4757 from OriginalSimon/dev
i18n: Update of the Ukrainian translation
2024-08-20 18:17:40 +02:00
Timothy J. Baek c5310e84db feat: custom COMFYUI_WORKFLOW
deprecates several comfyui env vars
2024-08-20 18:17:15 +02:00
Simon 0c8301e79c Update translation.json 2024-08-20 17:44:20 +02:00
Timothy J. Baek 73faa8dc80 refac: styling 2024-08-20 17:03:20 +02:00
Timothy Jaeryang Baek ee526b4b07 Merge pull request #4724 from michaelpoluektov/tools-refac-2.1
feat: Add `__tools__` optional param for function pipes
2024-08-20 17:01:13 +02:00
Michael Poluektov 454f59d59a undo frontend change 2024-08-20 15:48:14 +01:00
Michael Poluektov 2e3146263c put tool_ids and files in metadata 2024-08-20 15:41:49 +01:00
Michael Poluektov bcbcd5fde9 Merge branch 'dev' of https://github.com/open-webui/open-webui into tools-refac-2.1 2024-08-20 14:56:47 +01:00
Timothy J. Baek 83a596612a fix 2024-08-20 14:28:19 +02:00
Timothy J. Baek 2b896989b8 refac 2024-08-20 14:27:14 +02:00
Timothy Jaeryang Baek 27109d22e4 Merge pull request #4743 from 5E-324/add-num_gpu
fix: Changes to num_gpu made in Settings > General won't be saved
2024-08-20 13:38:11 +02:00
Timothy Jaeryang Baek 22d8f8f1ef Merge pull request #4746 from 5E-324/fix-type-errors
chore: Fix type errors.
2024-08-20 13:37:08 +02:00
Zhuoran fd26e5635d Fix type errors. 2024-08-20 08:44:09 +08:00
Zhuoran b350b0023f Add num_gpu in General.svelte 2024-08-20 07:47:20 +08:00
Timothy J. Baek 330eb0fbb1 enh: user message edit 2024-08-19 21:51:10 +02:00
Timothy J. Baek d79d3f1352 enh: mermaid dark theme 2024-08-19 19:42:31 +02:00
Michael Poluektov 44966db505 avoid ugly exception 2024-08-19 17:04:57 +01:00
Timothy J. Baek 1e8abea753 refac 2024-08-19 17:58:54 +02:00
Timothy J. Baek 21d8ff61bb refac 2024-08-19 17:57:47 +02:00
Michael Poluektov 9652c8f8af dont delete files and tool_ids 2024-08-19 16:57:29 +01:00
Timothy J. Baek de8f5b9c13 enh: default title gen prompt 2024-08-19 17:54:34 +02:00
Michael Poluektov c89df923c5 fix import error 2024-08-19 16:52:42 +01:00
Michael Poluektov 556bc8669a remove config options for now 2024-08-19 16:50:52 +01:00
Timothy J. Baek 89ba98e927 enh: better pdf CJK language support 2024-08-19 17:50:42 +02:00
Timothy J. Baek 6c8a15fae2 refac 2024-08-19 17:47:28 +02:00
Timothy J. Baek 68d8fd69c0 fix 2024-08-19 17:44:14 +02:00
Timothy J. Baek b31de299e4 fix: mermaid rendering issue 2024-08-19 17:28:38 +02:00
Michael Poluektov 9d7037b730 add pydantic model from json 2024-08-19 16:27:38 +01:00
Michael Poluektov 5edc211392 pass docstring to function 2024-08-19 16:27:21 +01:00
Timothy J. Baek 1329eea5e5 refac 2024-08-19 17:00:48 +02:00
Timothy J. Baek 28022b056b chore: format 2024-08-19 16:51:17 +02:00
Timothy J. Baek cbadf39d7d enh: user chat edit permission 2024-08-19 16:49:40 +02:00
Timothy Jaeryang Baek ec99ac7121 Update SECURITY.md 2024-08-19 09:18:40 -05:00
Timothy J. Baek dfa5041b6f refac: styling 2024-08-19 15:30:41 +02:00
Timothy J. Baek 0fa85c5c64 enh: enable message rating setting 2024-08-19 15:16:49 +02:00
Timothy J. Baek 5abe1076ed enh: model workspace shiftKey quick actions 2024-08-19 14:58:15 +02:00
Timothy Jaeryang Baek 7feda56e7b Merge pull request #4718 from open-webui/dependabot/pip/backend/dev/bcrypt-4.2.0
chore(deps): bump bcrypt from 4.1.3 to 4.2.0 in /backend
2024-08-19 14:17:00 +02:00
dependabot[bot] a54d3ad512 chore(deps): bump bcrypt from 4.1.3 to 4.2.0 in /backend
Bumps [bcrypt](https://github.com/pyca/bcrypt) from 4.1.3 to 4.2.0.
- [Changelog](https://github.com/pyca/bcrypt/blob/main/release.py)
- [Commits](https://github.com/pyca/bcrypt/compare/4.1.3...4.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 12:05:37 +00:00
Timothy Jaeryang Baek cdf9f5a4ec Merge pull request #4720 from open-webui/dependabot/pip/backend/dev/boto3-1.35.0
chore(deps): bump boto3 from 1.34.153 to 1.35.0 in /backend
2024-08-19 14:04:41 +02:00
Timothy Jaeryang Baek a472bfe6dc Merge pull request #4719 from open-webui/dependabot/pip/backend/dev/faster-whisper-1.0.3
chore(deps): bump faster-whisper from 1.0.2 to 1.0.3 in /backend
2024-08-19 14:04:31 +02:00
Timothy Jaeryang Baek f88d994c35 Merge pull request #4717 from open-webui/dependabot/pip/backend/dev/uvicorn-standard--0.30.6
chore(deps): bump uvicorn[standard] from 0.22.0 to 0.30.6 in /backend
2024-08-19 14:04:22 +02:00
Timothy Jaeryang Baek ad92aded84 Merge pull request #4716 from open-webui/dependabot/pip/backend/dev/sqlalchemy-2.0.32
chore(deps): bump sqlalchemy from 2.0.31 to 2.0.32 in /backend
2024-08-19 14:03:43 +02:00
Timothy J. Baek b4de0a52f8 fix
Co-Authored-By: elad_pt <124190990+elad-pticha@users.noreply.github.com>
2024-08-19 14:01:21 +02:00
Timothy Jaeryang Baek bd23dac92e Merge pull request #4714 from crizCraig/set-cors
sec: Allow setting CORS origin
2024-08-19 13:55:28 +02:00
Michael Poluektov 528df12bf1 fix: nonetype error 2024-08-19 11:15:22 +01:00
Michael Poluektov a933319adb import error? 2024-08-19 11:11:00 +01:00
Michael Poluektov 13c03bfd7d add __tools__ custom param 2024-08-19 11:08:27 +01:00
Michael Poluektov 18965dcdac delete keys if envvars are set 2024-08-19 11:03:55 +01:00
Michael Poluektov a4a7d678f9 move tools utils to utils.tools 2024-08-19 10:53:12 +01:00
Michael Poluektov fd422d2e3c use filters envvars 2024-08-19 10:46:52 +01:00
Michael Poluektov ce7a1a73ac remove more nesting 2024-08-19 10:34:44 +01:00
Michael Poluektov 32874a816d add filter toggle envvars 2024-08-19 10:26:16 +01:00
Michael Poluektov 3164354c0b refactor into single wrapper 2024-08-19 10:23:40 +01:00
dependabot[bot] f96aaf1177 chore(deps): bump boto3 from 1.34.153 to 1.35.0 in /backend
Bumps [boto3](https://github.com/boto/boto3) from 1.34.153 to 1.35.0.
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.34.153...1.35.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:07:09 +00:00
dependabot[bot] 589c79f3c2 chore(deps): bump faster-whisper from 1.0.2 to 1.0.3 in /backend
Bumps [faster-whisper](https://github.com/SYSTRAN/faster-whisper) from 1.0.2 to 1.0.3.
- [Release notes](https://github.com/SYSTRAN/faster-whisper/releases)
- [Commits](https://github.com/SYSTRAN/faster-whisper/compare/v1.0.2...v1.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:07:03 +00:00
dependabot[bot] a0f6864216 chore(deps): bump uvicorn[standard] from 0.22.0 to 0.30.6 in /backend
Bumps [uvicorn[standard]](https://github.com/encode/uvicorn) from 0.22.0 to 0.30.6.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.22.0...0.30.6)

---
updated-dependencies:
- dependency-name: uvicorn[standard]
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:06:54 +00:00
dependabot[bot] a4cc2c5c48 chore(deps): bump sqlalchemy from 2.0.31 to 2.0.32 in /backend
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.31 to 2.0.32.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:06:51 +00:00
Craig Quiter 0550d12106 Black format 2024-08-18 15:04:01 -07:00
Craig Quiter 845628c100 Fix tab format causing build failure 2024-08-18 15:00:49 -07:00
Craig Quiter 7bcdc10539 Optimize imports 2024-08-18 14:19:29 -07:00
Craig Quiter d2f10d50bf Allow seting CORS origin 2024-08-18 14:19:29 -07:00
Timothy Jaeryang Baek 446b2a334a Merge pull request #4713 from FINNSEEFLY/task/adjust-localization
i18n: Update ru-RU and uk-UA localizations
2024-08-18 22:57:34 +02:00
FINNSEEFLY b0fd90ada9 Adjust controls to ensure proper display of localized strings 2024-08-18 22:42:58 +03:00
FINNSEEFLY 6bfd48e412 Adjust uk localization 2024-08-18 22:40:18 +03:00
FINNSEEFLY 07ffd6e231 Adjust ru localization 2024-08-18 22:39:36 +03:00
Timothy J. Baek c675beeda4 refac 2024-08-18 21:14:22 +02:00
Timothy J. Baek fe0cf9506c refac 2024-08-18 21:11:59 +02:00
Timothy J. Baek 7c81509804 feat: merge responses 2024-08-18 20:59:59 +02:00
Timothy J. Baek 65923006a8 chore: format 2024-08-18 18:29:30 +02:00
Timothy J. Baek b02f6db475 refac 2024-08-18 18:28:58 +02:00
Timothy J. Baek 7f394f3f00 refac 2024-08-18 18:21:59 +02:00
Timothy J. Baek fc8524bbfd refac: styling 2024-08-18 18:20:36 +02:00
Timothy J. Baek c9505531fd refac: styling 2024-08-18 17:40:26 +02:00
Timothy J. Baek 1b809fe42e refac: styling 2024-08-18 17:16:22 +02:00
Timothy J. Baek a1b4df1b85 fix: cloned many model chat freezing issue 2024-08-18 16:47:12 +02:00
Timothy Jaeryang Baek 079f37a2d6 Merge pull request #4701 from ndrsfel/fix-rag-embedding-openai-batch-size-environment-variable
fix: RAG with OpenAI embedding models and batch_size environment variable fails silently
2024-08-18 13:24:18 +02:00
Andreas Feldl 0980066363 fix conversion 2024-08-18 12:46:47 +02:00
Timothy Jaeryang Baek a3b6654cbb Merge pull request #4688 from amirsubhi/dev
i18n: Updated ms-MY Translation
2024-08-17 21:57:52 +02:00
amirsubhi f4328325be Update i18n ms-MY Translation
Update Bahasa Malaysia Translation to reflect new update, and some spelling error
2024-08-18 02:17:51 +08:00
Timothy J. Baek 7badff49d8 refac 2024-08-17 19:43:04 +02:00
Timothy J. Baek 176c689f8d fix: unstructured md file parsing issue 2024-08-17 17:20:35 +02:00
Timothy J. Baek fa20b1dc09 chore: format 2024-08-17 17:17:45 +02:00
Timothy J. Baek 2ca9989d20 refac 2024-08-17 17:15:44 +02:00
Timothy J. Baek 3420818c52 refac 2024-08-17 17:11:58 +02:00
Timothy J. Baek 0ae6ca608c refac 2024-08-17 17:01:35 +02:00
Timothy J. Baek 536b40890a refac 2024-08-17 16:57:27 +02:00
Timothy J. Baek d5337917db refac 2024-08-17 16:46:04 +02:00
Timothy J. Baek 15f3ebba93 refac 2024-08-17 16:41:34 +02:00
Timothy J. Baek e71f55e58f refac 2024-08-17 16:32:39 +02:00
Timothy J. Baek fe747382c1 refac 2024-08-17 16:27:11 +02:00
Timothy J. Baek c4946d42e0 refac 2024-08-17 16:24:11 +02:00
Timothy J. Baek c1823b4b73 refac 2024-08-17 16:02:46 +02:00
Timothy J. Baek 862a30842c refac: chat_completion_inlets_handler -> chat_completion_filter_functions_handler 2024-08-17 16:00:18 +02:00
Timothy Jaeryang Baek cbb0940ff8 Merge pull request #4602 from michaelpoluektov/tools-refac-1
refactor, perf: Tools refactor (progress PR 1)
2024-08-17 15:50:40 +02:00
Timothy Jaeryang Baek bd8df3583d Merge pull request #4674 from crizCraig/sanitize-11labs-voiceid
sec: Sanitize 11labs voice id to address semgrep security issue: tainted-path-traversal-stdlib-fastapi
2024-08-17 15:49:56 +02:00
Craig Quiter 5f36807dbe Note tts defaults are from openai 2024-08-16 15:42:15 -07:00
Craig Quiter 442f50303a Sanitize voice_id 2024-08-16 15:10:53 -07:00
Craig Quiter 4560f3b1ae Return a dict from get_available_voices 2024-08-16 15:10:51 -07:00
Craig Quiter 59d2c670ba Optimize imports 2024-08-16 15:10:47 -07:00
Craig Quiter 02577f6a45 Cache elevenlabs voice call (can take 1s) 2024-08-16 15:10:41 -07:00
Timothy J. Baek 094fdd8943 fix 2024-08-16 19:23:36 +02:00
Timothy J. Baek 17169dff1f fix 2024-08-16 19:00:10 +02:00
Timothy J. Baek 28e3e6e8cb refac: many model chat 2024-08-16 18:54:30 +02:00
Timothy J. Baek 4f47053e93 refac 2024-08-16 17:51:50 +02:00
Timothy J. Baek 9025e9dbb1 fix: pseudo html rendering issue 2024-08-16 15:44:18 +02:00
Timothy J. Baek eee1dad217 refac: styling 2024-08-16 15:37:17 +02:00
Timothy J. Baek 769df698be fix: visible backtick in codespan 2024-08-16 15:37:11 +02:00
Timothy J. Baek 4ef042e966 refac 2024-08-16 15:33:14 +02:00
Timothy J. Baek 92062ff722 refac 2024-08-16 15:19:47 +02:00
Timothy J. Baek 623aa08b9e refac 2024-08-16 15:15:06 +02:00
Timothy J. Baek a529343b2b refac 2024-08-16 15:10:21 +02:00
Timothy J. Baek 2161903163 refac 2024-08-16 14:42:51 +02:00
Timothy Jaeryang Baek 3de9a1a130 Merge pull request #4651 from KarlLee830/translate
i18n: Update Chinese translation
2024-08-16 14:12:47 +02:00
Karl Lee 587c1a3ca2 i18n: Update Chinese translation 2024-08-16 16:37:02 +08:00
Timothy J. Baek d224566957 refac: styling 2024-08-16 00:21:57 +02:00
Timothy J. Baek 8ea1a10525 enh: action __event_emitter__ support 2024-08-15 23:55:31 +02:00
Michael Poluektov b6d6094018 Merge branch 'dev' into tools-refac-1 2024-08-15 21:35:31 +01:00
Timothy J. Baek e5e1bac242 chore: format 2024-08-15 17:31:47 +02:00
Timothy J. Baek 8c2ba7f7ea enh: Actions __webui__ flag support 2024-08-15 17:28:43 +02:00
Timothy J. Baek dc6ca61548 enh: temp chat
deprecates chat history setting and introduces temp chat from model selector
2024-08-15 16:54:16 +02:00
Timothy Jaeryang Baek 723caf2a09 Merge pull request #4621 from nthe/main
feat: Set content-type header in Ollama backend
2024-08-15 15:45:51 +02:00
Timothy J. Baek 439cb66672 refac: fuzzy search threshold 2024-08-15 15:44:38 +02:00
Timothy J. Baek dbd5b4c9f1 enh: render markdown user message 2024-08-15 15:41:02 +02:00
Timothy Jaeryang Baek 4dd404ac3b Merge pull request #4614 from sebdanielsson/pwa-maskable-icon
fix: Make PWA icon maskable
2024-08-15 13:25:00 +02:00
Timothy J. Baek ba370438b2 refac: "any maskable" is discouraged 2024-08-15 13:24:47 +02:00
Juraj Onuska f73a60d96c fix: set content-type header in ollama backend 2024-08-15 13:15:12 +02:00
Sebastian afe1f13c5b Make PWA icon maskable 2024-08-15 00:46:22 +02:00
Timothy J. Baek 0554cc6128 enh: shiftkey quick delete for tools and functions 2024-08-15 00:44:23 +02:00
Timothy J. Baek 5a6ece9513 refac: enhanced response content sanitisation
'<' and '>' can be correctly displayed now
2024-08-15 00:08:15 +02:00
Michael Poluektov 4042219b3e minor refac 2024-08-14 21:40:00 +01:00
Michael Poluektov fdc89cbcee tool calling refactor 2024-08-14 21:40:00 +01:00
Michael Poluektov 6df6170c44 add get_configured_tools 2024-08-14 21:40:00 +01:00
Michael Poluektov d598d4bb93 typing and tweaks 2024-08-14 21:40:00 +01:00
Michael Poluektov 790bdcf9fc rename tool calling helpers to use 'tool' instead of 'function' 2024-08-14 21:40:00 +01:00
Michael Poluektov 2efcda837c add try: except back 2024-08-14 21:40:00 +01:00
Michael Poluektov e86688284a factor out get_function_calling_payload 2024-08-14 21:40:00 +01:00
Michael Poluektov ff9d899f9c fix more LSP errors 2024-08-14 21:40:00 +01:00
Michael Poluektov a68b918cbb refactor get_function_call_response 2024-08-14 21:40:00 +01:00
Michael Poluektov 9fb70969d7 factor out get_content_from_response 2024-08-14 21:40:00 +01:00
Michael Poluektov 0c9119d619 move task to metadata 2024-08-14 21:40:00 +01:00
Michael Poluektov 556141cdd8 refactor task 2024-08-14 21:40:00 +01:00
Michael Poluektov 60003c976a rename to chat_completions_inlet_handler for clarity 2024-08-14 21:40:00 +01:00
Michael Poluektov 23f1bee7bd cleanup 2024-08-14 21:40:00 +01:00
Michael Poluektov 589efcdc5f is_chat_completion_request helper, remove nesting 2024-08-14 21:40:00 +01:00
Michael Poluektov 3befadb29f remove unnecessary nesting, remove unused endpoint 2024-08-14 21:40:00 +01:00
Timothy Jaeryang Baek 13b0e7d64a Merge pull request #4434 from open-webui/dev
Create and publish Docker images with specific build args / build-main-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-main-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-cuda-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/amd64) (push) Waiting to run
Create and publish Docker images with specific build args / build-ollama-image (linux/arm64) (push) Waiting to run
Create and publish Docker images with specific build args / merge-main-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-cuda-images (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge-ollama-images (push) Blocked by required conditions
0.3.13
2024-08-14 21:45:19 +02:00
Timothy J. Baek c8badfe21f refac 2024-08-14 21:42:43 +02:00
Timothy J. Baek 2a1b9cae91 doc: changelog 2024-08-14 21:39:11 +02:00
Timothy J. Baek 5ba7bbdd98 refac 2024-08-14 21:34:08 +02:00
Timothy J. Baek 9435d2044a refac: more robust mermaid chart rendering 2024-08-14 21:16:07 +02:00
Timothy J. Baek 609a42c29c refac: mermaid chart rendering 2024-08-14 17:34:44 +02:00
Timothy J. Baek 6a1e7ab038 chore: version bump 2024-08-14 17:13:37 +02:00
Timothy J. Baek 53daa15b9a chore: format 2024-08-14 17:07:16 +02:00
Timothy J. Baek 55097410f6 feat: haptic feedback on support devices (android)
Co-Authored-By: Danny Liu <dannyjialiliu@gmail.com>
2024-08-14 17:05:43 +02:00
Timothy J. Baek 04e2b6e2bd chore: format 2024-08-14 16:39:02 +02:00
Timothy Jaeryang Baek 9f0c9d973c Merge pull request #4597 from michaelpoluektov/cleanup
refactor: search and replace-able cleanup
2024-08-14 16:30:27 +02:00
Timothy J. Baek 1597e33a74 refac: mermaid chart rendering 2024-08-14 16:27:55 +02:00
Timothy Jaeryang Baek 0b218bbb72 Merge pull request #4598 from michaelpoluektov/cleanup-frontend
refactor: Remove unused frontend functions migrated to backend
2024-08-14 16:13:39 +02:00
Timothy J. Baek 7b21b718fe refac 2024-08-14 16:09:16 +02:00
Timothy J. Baek 6e5b557a1f refac 2024-08-14 16:08:23 +02:00
Timothy J. Baek 6aefc79807 refac: latex 2024-08-14 16:07:39 +02:00
Michael Poluektov ec9e0dadea remove frontend functions migrated to backend 2024-08-14 14:33:20 +01:00
Michael Poluektov 0470146d7b replace Tuple with tuple 2024-08-14 13:58:37 +01:00
Michael Poluektov a518d50477 format backend 2024-08-14 13:49:18 +01:00
Michael Poluektov 29f904db45 remove List imports 2024-08-14 13:46:31 +01:00
Michael Poluektov 038fc48ac0 replace == None with is None 2024-08-14 13:39:53 +01:00
Michael Poluektov 6f72def1ac replace except: with except Exception: 2024-08-14 13:38:19 +01:00
Timothy J. Baek 0ec1f9e331 fix: ENABLE_ADMIN_CHAT_ACCESS disabling user deletion 2024-08-14 12:23:24 +02:00
Timothy Jaeryang Baek 9682806476 Merge pull request #4372 from JTHesse/main
build: Adding ability to install requirements from frontmatter for tools and functions
2024-08-13 18:18:26 +02:00
Timothy Jaeryang Baek fc6fa7887b Merge pull request #4579 from alexandregodard/main
Update main.py
2024-08-13 18:17:27 +02:00
Timothy J. Baek e1e69cfbcb refac: sft -> default to safetensors 2024-08-13 17:15:20 +01:00
Alexandre GODARD 7a8f8960c5 Update main.py
Fix typo in update_reranking_model
2024-08-13 17:51:25 +02:00
Timothy Jaeryang Baek 30c44d431b Merge pull request #4538 from open-webui/dependabot/pip/backend/dev/chromadb-0.5.5
chore(deps): bump chromadb from 0.5.4 to 0.5.5 in /backend
2024-08-13 12:22:56 +02:00
Timothy Jaeryang Baek b177976f29 Merge pull request #4539 from open-webui/dependabot/pip/backend/dev/langchain-0.2.12
chore(deps): bump langchain from 0.2.11 to 0.2.12 in /backend
2024-08-13 12:22:48 +02:00
Timothy J. Baek af6b92a6fa chore: format 2024-08-13 11:22:08 +01:00
Timothy J. Baek a1888b3757 refac 2024-08-13 11:21:17 +01:00
Timothy Jaeryang Baek c75e77cdde Merge pull request #4554 from 5E-324/add-num_gpu
feat: Add advanced parameter num_gpu (Ollama)
2024-08-13 12:15:44 +02:00
Timothy Jaeryang Baek 6447b484c1 Merge pull request #4555 from 5E-324/ollama-params-fix
fix: Ollama parameters not shown in Chat Controls even if the current user is admin.
2024-08-13 12:13:40 +02:00
Timothy Jaeryang Baek 0c5236ac27 Merge pull request #4562 from simonaszilinskas/patch-5
Update Lithuanian translations
2024-08-13 12:13:11 +02:00
Timothy J. Baek 7ef5aa520c chore: format 2024-08-13 11:12:35 +01:00
Simonas d620bcf516 Update Lithuanian translations 2024-08-13 03:10:08 -05:00
Zhuoran b0c2e607b8 Set property "admin" of AdvancedParams in Controls 2024-08-13 10:06:51 +08:00
Zhuoran 56e2d579f2 Add advanced parameter num_gpu (Ollama) 2024-08-13 09:46:38 +08: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
Timothy J. Baek 70f580ec45 fix 2024-08-12 15:10:08 +01:00
Timothy J. Baek eae35dddc2 refac 2024-08-12 12:47:54 +01:00
Timothy Jaeryang Baek bb979c9a78 Merge pull request #4540 from open-webui/dependabot/pip/backend/dev/pytest-approx-eq-8.3.2
chore(deps): update pytest requirement from ~=8.2.2 to ~=8.3.2 in /backend
2024-08-12 13:31:02 +02:00
Timothy Jaeryang Baek 4f959c31de Merge pull request #4537 from open-webui/dependabot/pip/backend/dev/langfuse-2.43.3
chore(deps): bump langfuse from 2.39.2 to 2.43.3 in /backend
2024-08-12 13:30:52 +02:00
Timothy Jaeryang Baek 3bbe065db1 Merge pull request #4541 from open-webui/dependabot/pip/backend/dev/pyjwt-crypto--2.9.0
chore(deps): bump pyjwt[crypto] from 2.8.0 to 2.9.0 in /backend
2024-08-12 13:30:43 +02:00
dependabot[bot] 41460e1335 chore(deps): bump pyjwt[crypto] from 2.8.0 to 2.9.0 in /backend
Bumps [pyjwt[crypto]](https://github.com/jpadilla/pyjwt) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/jpadilla/pyjwt/releases)
- [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jpadilla/pyjwt/compare/2.8.0...2.9.0)

---
updated-dependencies:
- dependency-name: pyjwt[crypto]
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 03:15:42 +00:00
dependabot[bot] ffdb44f887 chore(deps): update pytest requirement in /backend
Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.2.2...8.3.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 03:15:40 +00:00
dependabot[bot] 549d3b4d10 chore(deps): bump langchain from 0.2.11 to 0.2.12 in /backend
Bumps [langchain](https://github.com/langchain-ai/langchain) from 0.2.11 to 0.2.12.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain==0.2.11...langchain==0.2.12)

---
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-12 03:15:35 +00:00
dependabot[bot] 879f85802a chore(deps): bump chromadb from 0.5.4 to 0.5.5 in /backend
Bumps [chromadb](https://github.com/chroma-core/chroma) from 0.5.4 to 0.5.5.
- [Release notes](https://github.com/chroma-core/chroma/releases)
- [Changelog](https://github.com/chroma-core/chroma/blob/main/RELEASE_PROCESS.md)
- [Commits](https://github.com/chroma-core/chroma/compare/0.5.4...0.5.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 03:15:30 +00:00
dependabot[bot] d652a1bbb1 chore(deps): bump langfuse from 2.39.2 to 2.43.3 in /backend
Bumps [langfuse](https://github.com/langfuse/langfuse) from 2.39.2 to 2.43.3.
- [Release notes](https://github.com/langfuse/langfuse/releases)
- [Commits](https://github.com/langfuse/langfuse/commits)

---
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-12 03:15:25 +00:00
Timothy Jaeryang Baek 9c2429ff97 Merge pull request #4402 from michaelpoluektov/remove-ollama
refactor: re-use utils in Ollama
2024-08-12 00:45:15 +02:00
Timothy Jaeryang Baek d0645d3c4f Merge pull request #4503 from open-webui/dependabot/pip/pip-621ce9c937
chore(deps): bump the pip group across 2 directories with 1 update
2024-08-12 00:40:46 +02:00
Timothy Jaeryang Baek 748be3e637 Merge pull request #4529 from cheahjs/fix/integration-tests
ci: fix cypress integration tests
2024-08-11 23:41:01 +02:00
Jun Siang Cheah 153ba168a0 tests: update share test to actually share 2024-08-11 22:30:25 +01:00
Jun Siang Cheah c70b18b2ef tests: change how cypress detects chat messages 2024-08-11 16:22:33 +01:00
Jun Siang Cheah af6420e06d ci: increase usable disk space for integration test 2024-08-11 14:17:32 +01:00
Michael Poluektov 547611b703 Merge branch 'dev' of https://github.com/open-webui/open-webui into remove-ollama 2024-08-10 11:47:20 +01:00
Timothy Jaeryang Baek 1b2ae7bb77 Merge pull request #4504 from OriginalSimon/dev
i18n: Update of the Ukrainian translation
2024-08-09 21:28:07 +02:00
Simon 5cd8011d53 Update translation.json 2024-08-09 21:03:19 +02:00
dependabot[bot] 208833d9f2 chore(deps): bump the pip group across 2 directories with 1 update
Bumps the pip group with 1 update in the / directory: [aiohttp](https://github.com/aio-libs/aiohttp).
Bumps the pip group with 1 update in the /backend directory: [aiohttp](https://github.com/aio-libs/aiohttp).


Updates `aiohttp` from 3.9.5 to 3.10.2
- [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.9.5...v3.10.2)

Updates `aiohttp` from 3.9.5 to 3.10.2
- [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.9.5...v3.10.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-09 18:21:50 +00:00
Timothy Jaeryang Baek 6863028540 Merge pull request #4500 from KarlLee830/translate
i18n: Update Chinese translation
2024-08-09 17:13:42 +02:00
Karl Lee 5e97a52cbe i18n: Update Chinese translation 2024-08-09 22:04:33 +08:00
Timothy Jaeryang Baek af865ab035 Merge pull request #4474 from aleixdorca/dev
i18n: Updated catalan translation
2024-08-09 12:01:43 +02:00
Timothy J. Baek 01f622866f fix 2024-08-09 00:14:41 +02:00
Timothy J. Baek e9d14601a1 refac 2024-08-09 00:08:20 +02:00
Timothy J. Baek 92e77d7b33 refac: katex 2024-08-09 00:01:38 +02:00
Timothy J. Baek 3b370bbcb3 fix 2024-08-08 20:46:39 +02:00
Aleix Dorca 20b1753a91 i18n: Update catalan translation.json 2024-08-08 17:18:59 +02:00
Aleix Dorca b2379c7104 i18n: Update catalan translation.json 2024-08-08 17:16:52 +02:00
Timothy J. Baek e491e96f88 fix: markdown '$$' rendering issue 2024-08-08 15:27:51 +02:00
Timothy J. Baek 8afc8c5714 chore: format 2024-08-08 15:07:59 +02:00
Timothy J. Baek d388c20373 enh: codeblock i18n 2024-08-08 15:07:24 +02:00
Timothy J. Baek 13403cd7dc enh: codeblock i18n 2024-08-08 14:24:47 +02:00
Timothy Jaeryang Baek 52fd701f2e Merge pull request #4465 from open-webui/main
dev
2024-08-08 14:23:33 +02:00
Michael Poluektov 204a4fbe7a fix: backend format test 2024-08-08 12:45:23 +01:00
Michael Poluektov 549817627f Merge branch 'dev' into remove-ollama 2024-08-08 12:42:37 +01:00
Michael Poluektov fa4d1d42a5 fix: backend format test 2024-08-08 12:41:41 +01:00
Michael Poluektov 309cd645f1 undo del 2024-08-08 12:30:07 +01:00
Michael Poluektov a725801e55 fix: formatting test errors, remove print, merge dev 2024-08-08 11:34:48 +01:00
Michael Poluektov 8cdf9814bd fix: name differences 2024-08-08 11:01:00 +01:00
Michael Poluektov e6bbce439d fix: repeat_penalty 2024-08-08 10:52:09 +01:00
Timothy Jaeryang Baek 8d257ed596 Merge commit from fork
SSRF Fix
2024-08-08 11:47:33 +02:00
Timothy Jaeryang Baek a7063a598d Merge pull request #4448 from JohnTheNerd/comfyui-async-fix
fix: ComfyUI generation no longer causes FastAPI to stall for all users
2024-08-08 11:36:19 +02:00
Jan-Timo Hesse 367fa039a0 added install_frontmatter_requirements 2024-08-08 09:46:14 +02:00
Jan-Timo Hesse 71d88fe35d revert 2024-08-08 09:45:52 +02:00
John Karabudak 958fe9639a fix: ComfyUI generation no longer causes FastAPI to stall for all users
as the get_images() function involves a `while True` loop while waiting for a response from ComfyUI and is not async, when image generation is running the entire UI becomes unresponsive for all users.

furthermore, when image generation takes too long, the Docker health check starts failing.

this is certainly a bad fix as it does not convert everything to async, but rather just puts the blocking loop in a separate thread. however, it works and it at least fixes the problem for now.
2024-08-07 22:24:55 -02:30
Timothy Jaeryang Baek 670f28d694 Merge branch 'dev' into remove-ollama 2024-08-07 23:06:11 +02:00
Timothy Jaeryang Baek 3715994c25 Merge pull request #4439 from cdgco/dev
feat: Add OAuth Email Claim Variable
2024-08-07 22:13:10 +02:00
Carter Roeser d72d5d0e8e feat: Add OAuth Email Claim Variable
Add an `OAUTH_EMAIL_CLAIM` variable to override the default "email" claim value.
2024-08-07 11:39:51 -07:00
Timothy J. Baek dbe463a53d fix 2024-08-07 17:39:54 +02:00
Timothy J. Baek 678dd780ee chore: package 2024-08-07 17:39:00 +02:00
Timothy Jaeryang Baek 44c870447f Merge pull request #4385 from candidosales/sveltekit-2v
build: Migrated to SvelteKit 2v
2024-08-07 17:37:08 +02:00
Timothy Jaeryang Baek 70a8f6e707 Merge pull request #4431 from Nowheresly/fix4158_reconnect
feat: #4158 allow reconnection when websocket is closed
2024-08-07 17:13:57 +02:00
Sylvere Richard 2fb4d3356c fix: #4158 allow reconnection when websocket is closed
log reconnection attempts
mark session_id as mandatory on client-side
2024-08-07 17:05:55 +02:00
root 590fd129c8 SSRF Fix Updated 2024-08-07 10:59:22 -04:00
Timothy Jaeryang Baek 99d10d1189 Merge pull request #4430 from open-webui/dev
fix
2024-08-07 15:53:11 +02:00
Timothy J. Baek ad9a7cb1e2 refac 2024-08-07 15:52:03 +02:00
Timothy J. Baek 8187922ef1 fix: "metadata" issue 2024-08-07 15:49:48 +02:00
Michael Poluektov f62281a0c7 Merge branch 'dev' of https://github.com/open-webui/open-webui into remove-ollama 2024-08-07 11:47:46 +01:00
root 1f8d08eaa2 SSRF Fix 2024-08-07 03:30:21 -04:00
Candido Sales Gomes 4105c9735e revert files 2024-08-06 09:38:42 -04:00
Michael Poluektov ed205d82e8 fix: pop 2024-08-06 12:25:00 +01:00
Michael Poluektov fc31267a54 refac: re-use utils.misc 2024-08-06 11:31:45 +01:00
Michael Poluektov 44c781f414 cleanup 2024-08-06 10:50:22 +01:00
Michael Poluektov 831fe9f509 cleanup 2024-08-06 10:15:29 +01:00
Candido Sales Gomes b289eef523 typo 2024-08-05 11:16:28 -04:00
Candido Sales Gomes 5315fb201d changelod updated 2024-08-05 11:10:31 -04:00
Candido Sales Gomes 6a012d290e migration to SvelteKit 2 2024-08-05 11:00:30 -04:00
Jan-Timo Hesse cec5fdd144 Added EXTRA_MODULES argument 2024-08-05 09:42:16 +02:00
307 changed files with 19754 additions and 11853 deletions
+2 -2
View File
@@ -3,10 +3,10 @@ updates:
- package-ecosystem: pip
directory: '/backend'
schedule:
interval: weekly
interval: monthly
target-branch: 'dev'
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
# Check for updates to GitHub Actions every week
interval: 'weekly'
interval: monthly
+55 -53
View File
@@ -10,61 +10,63 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v4
- name: Check for changes in package.json
run: |
git diff --cached --diff-filter=d package.json || {
echo "No changes to package.json"
exit 1
}
- name: Get version number from package.json
id: get_version
run: |
VERSION=$(jq -r '.version' package.json)
echo "::set-output name=version::$VERSION"
- name: Check for changes in package.json
run: |
git diff --cached --diff-filter=d package.json || {
echo "No changes to package.json"
exit 1
}
- name: Extract latest CHANGELOG entry
id: changelog
run: |
CHANGELOG_CONTENT=$(awk 'BEGIN {print_section=0;} /^## \[/ {if (print_section == 0) {print_section=1;} else {exit;}} print_section {print;}' CHANGELOG.md)
CHANGELOG_ESCAPED=$(echo "$CHANGELOG_CONTENT" | sed ':a;N;$!ba;s/\n/%0A/g')
echo "Extracted latest release notes from CHANGELOG.md:"
echo -e "$CHANGELOG_CONTENT"
echo "::set-output name=content::$CHANGELOG_ESCAPED"
- name: Get version number from package.json
id: get_version
run: |
VERSION=$(jq -r '.version' package.json)
echo "::set-output name=version::$VERSION"
- name: Create GitHub release
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const changelog = `${{ steps.changelog.outputs.content }}`;
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: `v${{ steps.get_version.outputs.version }}`,
name: `v${{ steps.get_version.outputs.version }}`,
body: changelog,
})
console.log(`Created release ${release.data.html_url}`)
- name: Extract latest CHANGELOG entry
id: changelog
run: |
CHANGELOG_CONTENT=$(awk 'BEGIN {print_section=0;} /^## \[/ {if (print_section == 0) {print_section=1;} else {exit;}} print_section {print;}' CHANGELOG.md)
CHANGELOG_ESCAPED=$(echo "$CHANGELOG_CONTENT" | sed ':a;N;$!ba;s/\n/%0A/g')
echo "Extracted latest release notes from CHANGELOG.md:"
echo -e "$CHANGELOG_CONTENT"
echo "::set-output name=content::$CHANGELOG_ESCAPED"
- name: Upload package to GitHub release
uses: actions/upload-artifact@v4
with:
name: package
path: .
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub release
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const changelog = `${{ steps.changelog.outputs.content }}`;
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: `v${{ steps.get_version.outputs.version }}`,
name: `v${{ steps.get_version.outputs.version }}`,
body: changelog,
})
console.log(`Created release ${release.data.html_url}`)
- name: Trigger Docker build workflow
uses: actions/github-script@v7
with:
script: |
github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'docker-build.yaml',
ref: 'v${{ steps.get_version.outputs.version }}',
})
- name: Upload package to GitHub release
uses: actions/upload-artifact@v4
with:
name: package
path: |
.
!.git
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Trigger Docker build workflow
uses: actions/github-script@v7
with:
script: |
github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'docker-build.yaml',
ref: 'v${{ steps.get_version.outputs.version }}',
})
+3 -4
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
+10 -3
View File
@@ -15,6 +15,13 @@ jobs:
name: Run Cypress Integration Tests
runs-on: ubuntu-latest
steps:
- name: Maximize build space
uses: AdityaGarg8/remove-unwanted-software@v4.1
with:
remove-android: 'true'
remove-haskell: 'true'
remove-codeql: 'true'
- name: Checkout Repository
uses: actions/checkout@v4
@@ -150,7 +157,7 @@ jobs:
GLOBAL_LOG_LEVEL: debug
run: |
cd backend
uvicorn main:app --port "8080" --forwarded-allow-ips '*' &
uvicorn open_webui.main:app --port "8080" --forwarded-allow-ips '*' &
UVICORN_PID=$!
# Wait up to 40 seconds for the server to start
for i in {1..40}; do
@@ -177,7 +184,7 @@ jobs:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
run: |
cd backend
uvicorn main:app --port "8081" --forwarded-allow-ips '*' &
uvicorn open_webui.main:app --port "8081" --forwarded-allow-ips '*' &
UVICORN_PID=$!
# Wait up to 20 seconds for the server to start
for i in {1..20}; do
@@ -223,7 +230,7 @@ jobs:
# DATABASE_URL: mysql://root:mysql@localhost:3306/mysql
# run: |
# cd backend
# uvicorn main:app --port "8083" --forwarded-allow-ips '*' &
# uvicorn open_webui.main:app --port "8083" --forwarded-allow-ips '*' &
# UVICORN_PID=$!
# # Wait up to 20 seconds for the server to start
# for i in {1..20}; do
+1
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main # or whatever branch you want to use
- pypi-release
jobs:
release:
+233
View File
@@ -5,6 +5,239 @@ 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.23] - 2024-09-21
### Added
- **🚀 WebSocket Redis Support**: Enhanced load balancing capabilities for multiple instance setups, promoting better performance and reliability in WebUI.
- **🔧 Adjustable Chat Controls**: Introduced width-adjustable chat controls, enabling a personalized and more comfortable user interface.
- **🌎 i18n Updates**: Improved and updated the Chinese translations.
### Fixed
- **🌐 Task Model Unloading Issue**: Modified task handling to use the Ollama /api/chat endpoint instead of OpenAI compatible endpoint, ensuring models stay loaded and ready with custom parameters, thus minimizing delays in task execution.
- **📝 Title Generation Fix for OpenAI Compatible APIs**: Resolved an issue preventing the generation of titles, enhancing consistency and reliability when using multiple API providers.
- **🗃️ RAG Duplicate Collection Issue**: Fixed a bug causing repeated processing of the same uploaded file. Now utilizes indexed files to prevent unnecessary duplications, optimizing resource usage.
- **🖼️ Image Generation Enhancement**: Refactored OpenAI image generation endpoint to be asynchronous, preventing the WebUI from becoming unresponsive during processing, thus enhancing user experience.
- **🔓 Downgrade Authlib**: Reverted Authlib to version 1.3.1 to address and resolve issues concerning OAuth functionality.
### Changed
- **🔍 Improved Message Interaction**: Enhanced the message node interface to allow for easier focus redirection with a simple click, streamlining user interaction.
- **✨ Styling Refactor**: Updated WebUI styling for a cleaner, more modern look, enhancing user experience across the platform.
## [0.3.22] - 2024-09-19
### Added
- **⭐ Chat Overview**: Introducing a node-based interactive messages diagram for improved visualization of conversation flows.
- **🔗 Multiple Vector DB Support**: Now supports multiple vector databases, including the newly added Milvus support. Community contributions for additional database support are highly encouraged!
- **📡 Experimental Non-Stream Chat Completion**: Experimental feature allowing the use of OpenAI o1 models, which do not support streaming, ensuring more versatile model deployment.
- **🔍 Experimental Colbert-AI Reranker Integration**: Added support for "jinaai/jina-colbert-v2" as a reranker, enhancing search relevance and accuracy. Note: it may not function at all on low-spec computers.
- **🕸️ ENABLE_WEBSOCKET_SUPPORT**: Added environment variable for instances to ignore websocket upgrades, stabilizing connections on platforms with websocket issues.
- **🔊 Azure Speech Service Integration**: Added support for Azure Speech services for Text-to-Speech (TTS).
- **🎚️ Customizable Playback Speed**: Playback speed control is now available in Call mode settings, allowing users to adjust audio playback speed to their preferences.
- **🧠 Enhanced Error Messaging**: System now displays helpful error messages directly to users during chat completion issues.
- **📂 Save Model as Transparent PNG**: Model profile images are now saved as PNGs, supporting transparency and improving visual integration.
- **📱 iPhone Compatibility Adjustments**: Added padding to accommodate the iPhone navigation bar, improving UI display on these devices.
- **🔗 Secure Response Headers**: Implemented security response headers, bolstering web application security.
- **🔧 Enhanced AUTOMATIC1111 Settings**: Users can now configure 'CFG Scale', 'Sampler', and 'Scheduler' parameters directly in the admin settings, enhancing workflow flexibility without source code modifications.
- **🌍 i18n Updates**: Enhanced translations for Chinese, Ukrainian, Russian, and French, fostering a better localized experience.
### Fixed
- **🛠️ Chat Message Deletion**: Resolved issues with chat message deletion, ensuring a smoother user interaction and system stability.
- **🔢 Ordered List Numbering**: Fixed the incorrect ordering in lists.
### Changed
- **🎨 Transparent Icon Handling**: Allowed model icons to be displayed on transparent backgrounds, improving UI aesthetics.
- **📝 Improved RAG Template**: Enhanced Retrieval-Augmented Generation template, optimizing context handling and error checking for more precise operation.
## [0.3.21] - 2024-09-08
### Added
- **📊 Document Count Display**: Now displays the total number of documents directly within the dashboard.
- **🚀 Ollama Embed API Endpoint**: Enabled /api/embed endpoint proxy support.
### Fixed
- **🐳 Docker Launch Issue**: Resolved the problem preventing Open-WebUI from launching correctly when using Docker.
### Changed
- **🔍 Enhanced Search Prompts**: Improved the search query generation prompts for better accuracy and user interaction, enhancing the overall search experience.
## [0.3.20] - 2024-09-07
### Added
- **🌐 Translation Update**: Updated Catalan translations to improve user experience for Catalan speakers.
### Fixed
- **📄 PDF Download**: Resolved a configuration issue with fonts directory, ensuring PDFs are now downloaded with the correct formatting.
- **🛠️ Installation of Tools & Functions Requirements**: Fixed a bug where necessary requirements for tools and functions were not properly installing.
- **🔗 Inline Image Link Rendering**: Enabled rendering of images directly from links in chat.
- **📞 Post-Call User Interface Cleanup**: Adjusted UI behavior to automatically close chat controls after a voice call ends, reducing screen clutter.
- **🎙️ Microphone Deactivation Post-Call**: Addressed an issue where the microphone remained active after calls.
- **✍️ Markdown Spacing Correction**: Corrected spacing in Markdown rendering, ensuring text appears neatly and as expected.
- **🔄 Message Re-rendering**: Fixed an issue causing all response messages to re-render with each new message, now improving chat performance.
### Changed
- **🌐 Refined Web Search Integration**: Deprecated the Search Query Generation Prompt threshold; introduced a toggle button for "Enable Web Search Query Generation" allowing users to opt-in to using web search more judiciously.
- **📝 Default Prompt Templates Update**: Emptied environment variable templates for search and title generation now default to the Open WebUI default prompt templates, simplifying configuration efforts.
## [0.3.19] - 2024-09-05
### Added
- **🌐 Translation Update**: Improved Chinese translations.
### Fixed
- **📂 DATA_DIR Overriding**: Fixed an issue to avoid overriding DATA_DIR, preventing errors when directories are set identically, ensuring smoother operation and data management.
- **🛠️ Frontmatter Extraction**: Fixed the extraction process for frontmatter in tools and functions.
### Changed
- **🎨 UI Styling**: Refined the user interface styling for enhanced visual coherence and user experience.
## [0.3.18] - 2024-09-04
### Added
- **🛠️ Direct Database Execution for Tools & Functions**: Enhanced the execution of Python files for tools and functions, now directly loading from the database for a more streamlined backend process.
### Fixed
- **🔄 Automatic Rewrite of Import Statements in Tools & Functions**: Tool and function scripts that import 'utils', 'apps', 'main', 'config' will now automatically rename these with 'open_webui.', ensuring compatibility and consistency across different modules.
- **🎨 Styling Adjustments**: Minor fixes in the visual styling to improve user experience and interface consistency.
## [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
- **🔗 Temporary Chat Activation**: Integrated a new URL parameter 'temporary-chat=true' to enable temporary chat sessions directly through the URL.
- **🌄 ComfyUI Seed Node Support**: Introduced seed node support in ComfyUI for image generation, allowing users to specify node IDs for randomized seed assignment.
### Fixed
- **🛠️ Tools and Functions**: Resolved a critical issue where Tools and Functions were not properly functioning, restoring full capability and reliability to these essential features.
- **🔘 Chat Action Button in Many Model Chat**: Fixed the malfunctioning of chat action buttons in many model chat environments, ensuring a smoother and more responsive user interaction.
- **⏪ Many Model Chat Compatibility**: Restored backward compatibility for many model chats.
## [0.3.14] - 2024-08-21
### Added
- **🛠️ Custom ComfyUI Workflow**: Deprecating several older environment variables, this enhancement introduces a new, customizable workflow for a more tailored user experience.
- **🔀 Merge Responses in Many Model Chat**: Enhances the dialogue by merging responses from multiple models into a single, coherent reply, improving the interaction quality in many model chats.
- **✅ Multiple Instances of Same Model in Chats**: Enhanced many model chat to support adding multiple instances of the same model.
- **🔧 Quick Actions in Model Workspace**: Enhanced Shift key quick actions for hiding/unhiding and deleting models, facilitating a smoother workflow.
- **🗨️ Markdown Rendering in User Messages**: User messages are now rendered in Markdown, enhancing readability and interaction.
- **💬 Temporary Chat Feature**: Introduced a temporary chat feature, deprecating the old chat history setting to enhance user interaction flexibility.
- **🖋️ User Message Editing**: Enhanced the user chat editing feature to allow saving changes without sending, providing more flexibility in message management.
- **🛡️ Security Enhancements**: Various security improvements implemented across the platform to ensure safer user experiences.
- **🌍 Updated Translations**: Enhanced translations for Chinese, Ukrainian, and Bahasa Malaysia, improving localization and user comprehension.
### Fixed
- **📑 Mermaid Rendering Issue**: Addressed issues with Mermaid chart rendering to ensure clean and clear visual data representation.
- **🎭 PWA Icon Maskability**: Fixed the Progressive Web App icon to be maskable, ensuring proper display on various device home screens.
- **🔀 Cloned Model Chat Freezing Issue**: Fixed a bug where cloning many model chats would cause freezing, enhancing stability and responsiveness.
- **🔍 Generic Error Handling and Refinements**: Various minor fixes and refinements to address previously untracked issues, ensuring smoother operations.
### Changed
- **🖼️ Image Generation Refactor**: Overhauled image generation processes for improved efficiency and quality.
- **🔨 Refactor Tool and Function Calling**: Refactored tool and function calling mechanisms for improved clarity and maintainability.
- **🌐 Backend Library Updates**: Updated critical backend libraries including SQLAlchemy, uvicorn[standard], faster-whisper, bcrypt, and boto3 for enhanced performance and security.
### Removed
- **🚫 Deprecated ComfyUI Environment Variables**: Removed several outdated environment variables related to ComfyUI settings, simplifying configuration management.
## [0.3.13] - 2024-08-14
### Added
- **🎨 Enhanced Markdown Rendering**: Significant improvements in rendering markdown, ensuring smooth and reliable display of LaTeX and Mermaid charts, enhancing user experience with more robust visual content.
- **🔄 Auto-Install Tools & Functions Python Dependencies**: For 'Tools' and 'Functions', Open WebUI now automatically install extra python requirements specified in the frontmatter, streamlining setup processes and customization.
- **🌀 OAuth Email Claim Customization**: Introduced an 'OAUTH_EMAIL_CLAIM' variable to allow customization of the default "email" claim within OAuth configurations, providing greater flexibility in authentication processes.
- **📶 Websocket Reconnection**: Enhanced reliability with the capability to automatically reconnect when a websocket is closed, ensuring consistent and stable communication.
- **🤳 Haptic Feedback on Support Devices**: Android devices now support haptic feedback for an immersive tactile experience during certain interactions.
### Fixed
- **🛠️ ComfyUI Performance Improvement**: Addressed an issue causing FastAPI to stall when ComfyUI image generation was active; now runs in a separate thread to prevent UI unresponsiveness.
- **🔀 Session Handling**: Fixed an issue mandating session_id on client-side to ensure smoother session management and transitions.
- **🖋️ Minor Bug Fixes and Format Corrections**: Various minor fixes including typo corrections, backend formatting improvements, and test amendments enhancing overall system stability and performance.
### Changed
- **🚀 Migration to SvelteKit 2**: Upgraded the underlying framework to SvelteKit version 2, offering enhanced speed, better code structure, and improved deployment capabilities.
- **🧹 General Cleanup and Refactoring**: Performed broad cleanup and refactoring across the platform, improving code efficiency and maintaining high standards of code health.
- **🚧 Integration Testing Improvements**: Modified how Cypress integration tests detect chat messages and updated sharing tests for better reliability and accuracy.
- **📁 Standardized '.safetensors' File Extension**: Renamed the '.sft' file extension to '.safetensors' for ComfyUI workflows, standardizing file formats across the platform.
### Removed
- **🗑️ Deprecated Frontend Functions**: Removed frontend functions that were migrated to backend to declutter the codebase and reduce redundancy.
## [0.3.12] - 2024-08-07
### Added
+7 -2
View File
@@ -74,6 +74,10 @@ ENV RAG_EMBEDDING_MODEL="$USE_EMBEDDING_MODEL_DOCKER" \
## Hugging Face download cache ##
ENV HF_HOME="/app/backend/data/cache/embedding/models"
## Torch Extensions ##
# ENV TORCH_EXTENSIONS_DIR="/.cache/torch_extensions"
#### Other models ##########################################################
WORKDIR /app/backend
@@ -96,7 +100,7 @@ RUN chown -R $UID:$GID /app $HOME
RUN if [ "$USE_OLLAMA" = "true" ]; then \
apt-get update && \
# Install pandoc and netcat
apt-get install -y --no-install-recommends pandoc netcat-openbsd curl && \
apt-get install -y --no-install-recommends git build-essential pandoc netcat-openbsd curl && \
apt-get install -y --no-install-recommends gcc python3-dev && \
# for RAG OCR
apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
@@ -109,7 +113,7 @@ RUN if [ "$USE_OLLAMA" = "true" ]; then \
else \
apt-get update && \
# Install pandoc, netcat and gcc
apt-get install -y --no-install-recommends pandoc gcc netcat-openbsd curl jq && \
apt-get install -y --no-install-recommends git build-essential pandoc gcc netcat-openbsd curl jq && \
apt-get install -y --no-install-recommends gcc python3-dev && \
# for RAG OCR
apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
@@ -157,5 +161,6 @@ USER $UID:$GID
ARG BUILD_HASH
ENV WEBUI_BUILD_VERSION=${BUILD_HASH}
ENV DOCKER true
CMD [ "bash", "start.sh"]
+1 -1
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.
+1 -5
View File
@@ -8,9 +8,5 @@ _test
Pipfile
!/data
/data/*
!/data/litellm
/data/litellm/*
!data/litellm/config.yaml
!data/config.json
/open_webui/data/*
.webui_secret_key
-407
View File
@@ -1,407 +0,0 @@
import websocket # NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
import uuid
import json
import urllib.request
import urllib.parse
import random
import logging
from config import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["COMFYUI"])
from pydantic import BaseModel
from typing import Optional
COMFYUI_DEFAULT_PROMPT = """
{
"3": {
"inputs": {
"seed": 0,
"steps": 20,
"cfg": 8,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": [
"4",
0
],
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"latent_image": [
"5",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "KSampler"
}
},
"4": {
"inputs": {
"ckpt_name": "model.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Load Checkpoint"
}
},
"5": {
"inputs": {
"width": 512,
"height": 512,
"batch_size": 1
},
"class_type": "EmptyLatentImage",
"_meta": {
"title": "Empty Latent Image"
}
},
"6": {
"inputs": {
"text": "Prompt",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"7": {
"inputs": {
"text": "Negative Prompt",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "Save Image"
}
}
}
"""
FLUX_DEFAULT_PROMPT = """
{
"5": {
"inputs": {
"width": 1024,
"height": 1024,
"batch_size": 1
},
"class_type": "EmptyLatentImage"
},
"6": {
"inputs": {
"text": "Input Text Here",
"clip": [
"11",
0
]
},
"class_type": "CLIPTextEncode"
},
"8": {
"inputs": {
"samples": [
"13",
0
],
"vae": [
"10",
0
]
},
"class_type": "VAEDecode"
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage"
},
"10": {
"inputs": {
"vae_name": "ae.sft"
},
"class_type": "VAELoader"
},
"11": {
"inputs": {
"clip_name1": "clip_l.safetensors",
"clip_name2": "t5xxl_fp16.safetensors",
"type": "flux"
},
"class_type": "DualCLIPLoader"
},
"12": {
"inputs": {
"unet_name": "flux1-dev.sft",
"weight_dtype": "default"
},
"class_type": "UNETLoader"
},
"13": {
"inputs": {
"noise": [
"25",
0
],
"guider": [
"22",
0
],
"sampler": [
"16",
0
],
"sigmas": [
"17",
0
],
"latent_image": [
"5",
0
]
},
"class_type": "SamplerCustomAdvanced"
},
"16": {
"inputs": {
"sampler_name": "euler"
},
"class_type": "KSamplerSelect"
},
"17": {
"inputs": {
"scheduler": "simple",
"steps": 20,
"denoise": 1,
"model": [
"12",
0
]
},
"class_type": "BasicScheduler"
},
"22": {
"inputs": {
"model": [
"12",
0
],
"conditioning": [
"6",
0
]
},
"class_type": "BasicGuider"
},
"25": {
"inputs": {
"noise_seed": 778937779713005
},
"class_type": "RandomNoise"
}
}
"""
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())
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:
return response.read()
def get_image_url(filename, subfolder, folder_type, base_url):
log.info("get_image")
data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
url_values = urllib.parse.urlencode(data)
return f"{base_url}/view?{url_values}"
def get_history(prompt_id, base_url):
log.info("get_history")
with urllib.request.urlopen(f"{base_url}/history/{prompt_id}") as response:
return json.loads(response.read())
def get_images(ws, prompt, client_id, base_url):
prompt_id = queue_prompt(prompt, client_id, base_url)["prompt_id"]
output_images = []
while True:
out = ws.recv()
if isinstance(out, str):
message = json.loads(out)
if message["type"] == "executing":
data = message["data"]
if data["node"] is None and data["prompt_id"] == prompt_id:
break # Execution is done
else:
continue # previews are binary data
history = get_history(prompt_id, base_url)[prompt_id]
for o in history["outputs"]:
for node_id in history["outputs"]:
node_output = history["outputs"][node_id]
if "images" in node_output:
for image in node_output["images"]:
url = get_image_url(
image["filename"], image["subfolder"], image["type"], base_url
)
output_images.append({"url": url})
return {"data": output_images}
class ImageGenerationPayload(BaseModel):
prompt: str
negative_prompt: Optional[str] = ""
steps: Optional[int] = None
seed: Optional[int] = None
width: int
height: int
n: int = 1
cfg_scale: Optional[float] = None
sampler: Optional[str] = None
scheduler: Optional[str] = None
sd3: Optional[bool] = None
flux: Optional[bool] = None
flux_weight_dtype: Optional[str] = None
flux_fp8_clip: Optional[bool] = None
def comfyui_generate_image(
model: str, payload: ImageGenerationPayload, client_id, base_url
):
ws_url = base_url.replace("http://", "ws://").replace("https://", "wss://")
comfyui_prompt = json.loads(COMFYUI_DEFAULT_PROMPT)
if payload.cfg_scale:
comfyui_prompt["3"]["inputs"]["cfg"] = payload.cfg_scale
if payload.sampler:
comfyui_prompt["3"]["inputs"]["sampler"] = payload.sampler
if payload.scheduler:
comfyui_prompt["3"]["inputs"]["scheduler"] = payload.scheduler
if payload.sd3:
comfyui_prompt["5"]["class_type"] = "EmptySD3LatentImage"
if payload.steps:
comfyui_prompt["3"]["inputs"]["steps"] = payload.steps
comfyui_prompt["4"]["inputs"]["ckpt_name"] = model
comfyui_prompt["7"]["inputs"]["text"] = payload.negative_prompt
comfyui_prompt["3"]["inputs"]["seed"] = (
payload.seed if payload.seed else random.randint(0, 18446744073709551614)
)
# as Flux uses a completely different workflow, we must treat it specially
if payload.flux:
comfyui_prompt = json.loads(FLUX_DEFAULT_PROMPT)
comfyui_prompt["12"]["inputs"]["unet_name"] = model
comfyui_prompt["25"]["inputs"]["noise_seed"] = (
payload.seed if payload.seed else random.randint(0, 18446744073709551614)
)
if payload.sampler:
comfyui_prompt["16"]["inputs"]["sampler_name"] = payload.sampler
if payload.steps:
comfyui_prompt["17"]["inputs"]["steps"] = payload.steps
if payload.scheduler:
comfyui_prompt["17"]["inputs"]["scheduler"] = payload.scheduler
if payload.flux_weight_dtype:
comfyui_prompt["12"]["inputs"]["weight_dtype"] = payload.flux_weight_dtype
if payload.flux_fp8_clip:
comfyui_prompt["11"]["inputs"][
"clip_name2"
] = "t5xxl_fp8_e4m3fn.safetensors"
comfyui_prompt["5"]["inputs"]["batch_size"] = payload.n
comfyui_prompt["5"]["inputs"]["width"] = payload.width
comfyui_prompt["5"]["inputs"]["height"] = payload.height
# set the text prompt for our positive CLIPTextEncode
comfyui_prompt["6"]["inputs"]["text"] = payload.prompt
try:
ws = websocket.WebSocket()
ws.connect(f"{ws_url}/ws?clientId={client_id}")
log.info("WebSocket connection established.")
except Exception as e:
log.exception(f"Failed to connect to WebSocket server: {e}")
return None
try:
images = get_images(ws, comfyui_prompt, client_id, base_url)
except Exception as e:
log.exception(f"Error while receiving images: {e}")
images = None
ws.close()
return images
-90
View File
@@ -1,90 +0,0 @@
from importlib import util
import os
import re
from config import TOOLS_DIR, FUNCTIONS_DIR
def extract_frontmatter(file_path):
"""
Extract frontmatter as a dictionary from the specified file path.
"""
frontmatter = {}
frontmatter_started = False
frontmatter_ended = False
frontmatter_pattern = re.compile(r"^\s*([a-z_]+):\s*(.*)\s*$", re.IGNORECASE)
try:
with open(file_path, "r", encoding="utf-8") as file:
first_line = file.readline()
if first_line.strip() != '"""':
# The file doesn't start with triple quotes
return {}
frontmatter_started = True
for line in file:
if '"""' in line:
if frontmatter_started:
frontmatter_ended = True
break
if frontmatter_started and not frontmatter_ended:
match = frontmatter_pattern.match(line)
if match:
key, value = match.groups()
frontmatter[key.strip()] = value.strip()
except FileNotFoundError:
print(f"Error: The file {file_path} does not exist.")
return {}
except Exception as e:
print(f"An error occurred: {e}")
return {}
return frontmatter
def load_toolkit_module_by_id(toolkit_id):
toolkit_path = os.path.join(TOOLS_DIR, f"{toolkit_id}.py")
spec = util.spec_from_file_location(toolkit_id, toolkit_path)
module = util.module_from_spec(spec)
frontmatter = extract_frontmatter(toolkit_path)
try:
spec.loader.exec_module(module)
print(f"Loaded module: {module.__name__}")
if hasattr(module, "Tools"):
return module.Tools(), frontmatter
else:
raise Exception("No Tools class found")
except Exception as e:
print(f"Error loading module: {toolkit_id}")
# Move the file to the error folder
os.rename(toolkit_path, f"{toolkit_path}.error")
raise e
def load_function_module_by_id(function_id):
function_path = os.path.join(FUNCTIONS_DIR, f"{function_id}.py")
spec = util.spec_from_file_location(function_id, function_path)
module = util.module_from_spec(spec)
frontmatter = extract_frontmatter(function_path)
try:
spec.loader.exec_module(module)
print(f"Loaded module: {module.__name__}")
if hasattr(module, "Pipe"):
return module.Pipe(), "pipe", frontmatter
elif hasattr(module, "Filter"):
return module.Filter(), "filter", frontmatter
elif hasattr(module, "Action"):
return module.Action(), "action", frontmatter
else:
raise Exception("No Function class found")
except Exception as e:
print(f"Error loading module: {function_id}")
# Move the file to the error folder
os.rename(function_path, f"{function_path}.error")
raise e
-36
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."
}
]
}
}
-4
View File
@@ -1,4 +0,0 @@
general_settings: {}
litellm_settings: {}
model_list: []
router_settings: {}
+1 -1
View File
@@ -1 +1 @@
dir for backend files (db, documents, etc.)
docker dir for backend files (db, documents, etc.)
+1 -1
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
+21 -5
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()
@@ -18,6 +16,7 @@ def serve(
host: str = "0.0.0.0",
port: int = 8080,
):
os.environ["FROM_INIT_PY"] = "true"
if os.getenv("WEBUI_SECRET_KEY") is None:
typer.echo(
"Loading WEBUI_SECRET_KEY from file, not provided as an environment variable."
@@ -40,9 +39,22 @@ def serve(
"/usr/local/lib/python3.11/site-packages/nvidia/cudnn/lib",
]
)
import main # we need set environment variables before importing main
try:
import torch
uvicorn.run(main.app, host=host, port=port, forwarded_allow_ips="*")
assert torch.cuda.is_available(), "CUDA not available"
typer.echo("CUDA seems to be working")
except Exception as e:
typer.echo(
"Error when testing CUDA but USE_CUDA_DOCKER is true. "
"Resetting USE_CUDA_DOCKER to false and removing "
f"LD_LIBRARY_PATH modifications: {e}"
)
os.environ["USE_CUDA_DOCKER"] = "false"
os.environ["LD_LIBRARY_PATH"] = ":".join(LD_LIBRARY_PATH)
import open_webui.main # we need set environment variables before importing main
uvicorn.run(open_webui.main.app, host=host, port=port, forwarded_allow_ips="*")
@app.command()
@@ -52,7 +64,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="*",
)
@@ -1,65 +1,49 @@
import os
import logging
from fastapi import (
FastAPI,
Request,
Depends,
HTTPException,
status,
UploadFile,
File,
Form,
)
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List
import uuid
import requests
import hashlib
from pathlib import Path
import json
import logging
import os
import uuid
from functools import lru_cache
from pathlib import Path
from constants import ERROR_MESSAGES
from utils.utils import (
decode_token,
get_current_user,
get_verified_user,
get_admin_user,
)
from utils.misc import calculate_sha256
from config import (
SRC_LOG_LEVELS,
CACHE_DIR,
UPLOAD_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,
import requests
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,
AUDIO_TTS_AZURE_SPEECH_REGION,
AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT,
CACHE_DIR,
CORS_ALLOW_ORIGIN,
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, DEVICE_TYPE
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 open_webui.utils.utils import get_admin_user, get_current_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["AUDIO"])
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=CORS_ALLOW_ORIGIN,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@@ -78,6 +62,10 @@ 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
app.state.config.TTS_AZURE_SPEECH_REGION = AUDIO_TTS_AZURE_SPEECH_REGION
app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT = AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT
# setting device type for whisper model
whisper_device_type = DEVICE_TYPE if DEVICE_TYPE and DEVICE_TYPE == "cuda" else "cpu"
@@ -94,6 +82,9 @@ class TTSConfigForm(BaseModel):
ENGINE: str
MODEL: str
VOICE: str
SPLIT_ON: str
AZURE_SPEECH_REGION: str
AZURE_SPEECH_OUTPUT_FORMAT: str
class STTConfigForm(BaseModel):
@@ -145,6 +136,9 @@ 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,
"AZURE_SPEECH_REGION": app.state.config.TTS_AZURE_SPEECH_REGION,
"AZURE_SPEECH_OUTPUT_FORMAT": app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT,
},
"stt": {
"OPENAI_API_BASE_URL": app.state.config.STT_OPENAI_API_BASE_URL,
@@ -165,6 +159,11 @@ 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.TTS_AZURE_SPEECH_REGION = form_data.tts.AZURE_SPEECH_REGION
app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT = (
form_data.tts.AZURE_SPEECH_OUTPUT_FORMAT
)
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
@@ -179,6 +178,9 @@ 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,
"AZURE_SPEECH_REGION": app.state.config.TTS_AZURE_SPEECH_REGION,
"AZURE_SPEECH_OUTPUT_FORMAT": app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT,
},
"stt": {
"OPENAI_API_BASE_URL": app.state.config.STT_OPENAI_API_BASE_URL,
@@ -211,7 +213,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
@@ -244,7 +246,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
res = r.json()
if "error" in res:
error_detail = f"External: {res['error']['message']}"
except:
except Exception:
error_detail = f"External: {e}"
raise HTTPException(
@@ -261,6 +263,13 @@ async def speech(request: Request, user=Depends(get_verified_user)):
raise HTTPException(status_code=400, detail="Invalid JSON payload")
voice_id = payload.get("voice", "")
if voice_id not in get_available_voices():
raise HTTPException(
status_code=400,
detail="Invalid voice id",
)
url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}"
headers = {
@@ -299,7 +308,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
res = r.json()
if "error" in res:
error_detail = f"External: {res['error']['message']}"
except:
except Exception:
error_detail = f"External: {e}"
raise HTTPException(
@@ -307,6 +316,42 @@ async def speech(request: Request, user=Depends(get_verified_user)):
detail=error_detail,
)
elif app.state.config.TTS_ENGINE == "azure":
payload = None
try:
payload = json.loads(body.decode("utf-8"))
except Exception as e:
log.exception(e)
raise HTTPException(status_code=400, detail="Invalid JSON payload")
region = app.state.config.TTS_AZURE_SPEECH_REGION
language = app.state.config.TTS_VOICE
locale = "-".join(app.state.config.TTS_VOICE.split("-")[:1])
output_format = app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT
url = f"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1"
headers = {
"Ocp-Apim-Subscription-Key": app.state.config.TTS_API_KEY,
"Content-Type": "application/ssml+xml",
"X-Microsoft-OutputFormat": output_format,
}
data = f"""<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="{locale}">
<voice name="{language}">{payload["input"]}</voice>
</speak>"""
response = requests.post(url, headers=headers, data=data)
if response.status_code == 200:
with open(file_path, "wb") as f:
f.write(response.content)
return FileResponse(file_path)
else:
log.error(f"Error synthesizing speech - {response.reason}")
raise HTTPException(
status_code=500, detail=f"Error synthesizing speech - {response.reason}"
)
@app.post("/transcriptions")
def transcribe(
@@ -315,7 +360,7 @@ def transcribe(
):
log.info(f"file.content_type: {file.content_type}")
if file.content_type not in ["audio/mpeg", "audio/wav"]:
if file.content_type not in ["audio/mpeg", "audio/wav", "audio/ogg"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.FILE_NOT_SUPPORTED,
@@ -353,7 +398,7 @@ def transcribe(
try:
model = WhisperModel(**whisper_kwargs)
except:
except Exception:
log.warning(
"WhisperModel initialization failed, attempting download with local_files_only=False"
)
@@ -421,7 +466,7 @@ def transcribe(
res = r.json()
if "error" in res:
error_detail = f"External: {res['error']['message']}"
except:
except Exception:
error_detail = f"External: {e}"
raise HTTPException(
@@ -438,7 +483,7 @@ def transcribe(
)
def get_available_models() -> List[dict]:
def get_available_models() -> list[dict]:
if app.state.config.TTS_ENGINE == "openai":
return [{"id": "tts-1"}, {"id": "tts-1-hd"}]
elif app.state.config.TTS_ENGINE == "elevenlabs":
@@ -449,7 +494,7 @@ def get_available_models() -> List[dict]:
try:
response = requests.get(
"https://api.elevenlabs.io/v1/models", headers=headers
"https://api.elevenlabs.io/v1/models", headers=headers, timeout=5
)
response.raise_for_status()
models = response.json()
@@ -466,39 +511,73 @@ async def get_models(user=Depends(get_verified_user)):
return {"models": get_available_models()}
def get_available_voices() -> List[dict]:
def get_available_voices() -> dict:
"""Returns {voice_id: voice_name} dict"""
ret = {}
if app.state.config.TTS_ENGINE == "openai":
return [
{"name": "alloy", "id": "alloy"},
{"name": "echo", "id": "echo"},
{"name": "fable", "id": "fable"},
{"name": "onyx", "id": "onyx"},
{"name": "nova", "id": "nova"},
{"name": "shimmer", "id": "shimmer"},
]
elif app.state.config.TTS_ENGINE == "elevenlabs":
headers = {
"xi-api-key": app.state.config.TTS_API_KEY,
"Content-Type": "application/json",
ret = {
"alloy": "alloy",
"echo": "echo",
"fable": "fable",
"onyx": "onyx",
"nova": "nova",
"shimmer": "shimmer",
}
elif app.state.config.TTS_ENGINE == "elevenlabs":
try:
response = requests.get(
"https://api.elevenlabs.io/v1/voices", headers=headers
)
response.raise_for_status()
voices_data = response.json()
ret = get_elevenlabs_voices()
except Exception:
# Avoided @lru_cache with exception
pass
elif app.state.config.TTS_ENGINE == "azure":
try:
region = app.state.config.TTS_AZURE_SPEECH_REGION
url = f"https://{region}.tts.speech.microsoft.com/cognitiveservices/voices/list"
headers = {"Ocp-Apim-Subscription-Key": app.state.config.TTS_API_KEY}
voices = []
for voice in voices_data.get("voices", []):
voices.append({"name": voice["name"], "id": voice["voice_id"]})
return voices
response = requests.get(url, headers=headers)
response.raise_for_status()
voices = response.json()
for voice in voices:
ret[voice["ShortName"]] = (
f"{voice['DisplayName']} ({voice['ShortName']})"
)
except requests.RequestException as e:
log.error(f"Error fetching voices: {str(e)}")
return []
return ret
@lru_cache
def get_elevenlabs_voices() -> dict:
"""
Note, set the following in your .env file to use Elevenlabs:
AUDIO_TTS_ENGINE=elevenlabs
AUDIO_TTS_API_KEY=sk_... # Your Elevenlabs API key
AUDIO_TTS_VOICE=EXAVITQu4vr4xnSDxMaL # From https://api.elevenlabs.io/v1/voices
AUDIO_TTS_MODEL=eleven_multilingual_v2
"""
headers = {
"xi-api-key": app.state.config.TTS_API_KEY,
"Content-Type": "application/json",
}
try:
# TODO: Add retries
response = requests.get("https://api.elevenlabs.io/v1/voices", headers=headers)
response.raise_for_status()
voices_data = response.json()
voices = {}
for voice in voices_data.get("voices", []):
voices[voice["voice_id"]] = voice["name"]
except requests.RequestException as e:
# Avoid @lru_cache with exception
log.error(f"Error fetching voices: {str(e)}")
raise RuntimeError(f"Error fetching voices: {str(e)}")
return voices
@app.get("/voices")
async def get_voices(user=Depends(get_verified_user)):
return {"voices": get_available_voices()}
return {"voices": [{"id": k, "name": v} for k, v in get_available_voices().items()]}
@@ -1,57 +1,45 @@
import re
import requests
import base64
from fastapi import (
FastAPI,
Request,
Depends,
HTTPException,
status,
UploadFile,
File,
Form,
)
from fastapi.middleware.cors import CORSMiddleware
from constants import ERROR_MESSAGES
from utils.utils import (
get_verified_user,
get_admin_user,
)
from apps.images.utils.comfyui import ImageGenerationPayload, comfyui_generate_image
from utils.misc import calculate_sha256
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
from config import (
SRC_LOG_LEVELS,
CACHE_DIR,
IMAGE_GENERATION_ENGINE,
ENABLE_IMAGE_GENERATION,
AUTOMATIC1111_BASE_URL,
import requests
from open_webui.apps.images.utils.comfyui import (
ComfyUIGenerateImageForm,
ComfyUIWorkflow,
comfyui_generate_image,
)
from open_webui.config import (
AUTOMATIC1111_API_AUTH,
AUTOMATIC1111_BASE_URL,
AUTOMATIC1111_CFG_SCALE,
AUTOMATIC1111_SAMPLER,
AUTOMATIC1111_SCHEDULER,
CACHE_DIR,
COMFYUI_BASE_URL,
COMFYUI_CFG_SCALE,
COMFYUI_SAMPLER,
COMFYUI_SCHEDULER,
COMFYUI_SD3,
COMFYUI_FLUX,
COMFYUI_FLUX_WEIGHT_DTYPE,
COMFYUI_FLUX_FP8_CLIP,
IMAGES_OPENAI_API_BASE_URL,
IMAGES_OPENAI_API_KEY,
COMFYUI_WORKFLOW,
COMFYUI_WORKFLOW_NODES,
CORS_ALLOW_ORIGIN,
ENABLE_IMAGE_GENERATION,
IMAGE_GENERATION_ENGINE,
IMAGE_GENERATION_MODEL,
IMAGE_SIZE,
IMAGE_STEPS,
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"])
@@ -62,7 +50,7 @@ IMAGE_CACHE_DIR.mkdir(parents=True, exist_ok=True)
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=CORS_ALLOW_ORIGIN,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@@ -80,21 +68,127 @@ app.state.config.MODEL = IMAGE_GENERATION_MODEL
app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
app.state.config.AUTOMATIC1111_API_AUTH = AUTOMATIC1111_API_AUTH
app.state.config.AUTOMATIC1111_CFG_SCALE = AUTOMATIC1111_CFG_SCALE
app.state.config.AUTOMATIC1111_SAMPLER = AUTOMATIC1111_SAMPLER
app.state.config.AUTOMATIC1111_SCHEDULER = AUTOMATIC1111_SCHEDULER
app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL
app.state.config.COMFYUI_WORKFLOW = COMFYUI_WORKFLOW
app.state.config.COMFYUI_WORKFLOW_NODES = COMFYUI_WORKFLOW_NODES
app.state.config.IMAGE_SIZE = IMAGE_SIZE
app.state.config.IMAGE_STEPS = IMAGE_STEPS
app.state.config.COMFYUI_CFG_SCALE = COMFYUI_CFG_SCALE
app.state.config.COMFYUI_SAMPLER = COMFYUI_SAMPLER
app.state.config.COMFYUI_SCHEDULER = COMFYUI_SCHEDULER
app.state.config.COMFYUI_SD3 = COMFYUI_SD3
app.state.config.COMFYUI_FLUX = COMFYUI_FLUX
app.state.config.COMFYUI_FLUX_WEIGHT_DTYPE = COMFYUI_FLUX_WEIGHT_DTYPE
app.state.config.COMFYUI_FLUX_FP8_CLIP = COMFYUI_FLUX_FP8_CLIP
@app.get("/config")
async def get_config(request: Request, user=Depends(get_admin_user)):
return {
"enabled": app.state.config.ENABLED,
"engine": app.state.config.ENGINE,
"openai": {
"OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
"OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
},
"automatic1111": {
"AUTOMATIC1111_BASE_URL": app.state.config.AUTOMATIC1111_BASE_URL,
"AUTOMATIC1111_API_AUTH": app.state.config.AUTOMATIC1111_API_AUTH,
"AUTOMATIC1111_CFG_SCALE": app.state.config.AUTOMATIC1111_CFG_SCALE,
"AUTOMATIC1111_SAMPLER": app.state.config.AUTOMATIC1111_SAMPLER,
"AUTOMATIC1111_SCHEDULER": app.state.config.AUTOMATIC1111_SCHEDULER,
},
"comfyui": {
"COMFYUI_BASE_URL": app.state.config.COMFYUI_BASE_URL,
"COMFYUI_WORKFLOW": app.state.config.COMFYUI_WORKFLOW,
"COMFYUI_WORKFLOW_NODES": app.state.config.COMFYUI_WORKFLOW_NODES,
},
}
class OpenAIConfigForm(BaseModel):
OPENAI_API_BASE_URL: str
OPENAI_API_KEY: str
class Automatic1111ConfigForm(BaseModel):
AUTOMATIC1111_BASE_URL: str
AUTOMATIC1111_API_AUTH: str
AUTOMATIC1111_CFG_SCALE: Optional[str]
AUTOMATIC1111_SAMPLER: Optional[str]
AUTOMATIC1111_SCHEDULER: Optional[str]
class ComfyUIConfigForm(BaseModel):
COMFYUI_BASE_URL: str
COMFYUI_WORKFLOW: str
COMFYUI_WORKFLOW_NODES: list[dict]
class ConfigForm(BaseModel):
enabled: bool
engine: str
openai: OpenAIConfigForm
automatic1111: Automatic1111ConfigForm
comfyui: ComfyUIConfigForm
@app.post("/config/update")
async def update_config(form_data: ConfigForm, user=Depends(get_admin_user)):
app.state.config.ENGINE = form_data.engine
app.state.config.ENABLED = form_data.enabled
app.state.config.OPENAI_API_BASE_URL = form_data.openai.OPENAI_API_BASE_URL
app.state.config.OPENAI_API_KEY = form_data.openai.OPENAI_API_KEY
app.state.config.AUTOMATIC1111_BASE_URL = (
form_data.automatic1111.AUTOMATIC1111_BASE_URL
)
app.state.config.AUTOMATIC1111_API_AUTH = (
form_data.automatic1111.AUTOMATIC1111_API_AUTH
)
app.state.config.AUTOMATIC1111_CFG_SCALE = (
float(form_data.automatic1111.AUTOMATIC1111_CFG_SCALE)
if form_data.automatic1111.AUTOMATIC1111_CFG_SCALE
else None
)
app.state.config.AUTOMATIC1111_SAMPLER = (
form_data.automatic1111.AUTOMATIC1111_SAMPLER
if form_data.automatic1111.AUTOMATIC1111_SAMPLER
else None
)
app.state.config.AUTOMATIC1111_SCHEDULER = (
form_data.automatic1111.AUTOMATIC1111_SCHEDULER
if form_data.automatic1111.AUTOMATIC1111_SCHEDULER
else None
)
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
return {
"enabled": app.state.config.ENABLED,
"engine": app.state.config.ENGINE,
"openai": {
"OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
"OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
},
"automatic1111": {
"AUTOMATIC1111_BASE_URL": app.state.config.AUTOMATIC1111_BASE_URL,
"AUTOMATIC1111_API_AUTH": app.state.config.AUTOMATIC1111_API_AUTH,
"AUTOMATIC1111_CFG_SCALE": app.state.config.AUTOMATIC1111_CFG_SCALE,
"AUTOMATIC1111_SAMPLER": app.state.config.AUTOMATIC1111_SAMPLER,
"AUTOMATIC1111_SCHEDULER": app.state.config.AUTOMATIC1111_SCHEDULER,
},
"comfyui": {
"COMFYUI_BASE_URL": app.state.config.COMFYUI_BASE_URL,
"COMFYUI_WORKFLOW": app.state.config.COMFYUI_WORKFLOW,
"COMFYUI_WORKFLOW_NODES": app.state.config.COMFYUI_WORKFLOW_NODES,
},
}
def get_automatic1111_api_auth():
if app.state.config.AUTOMATIC1111_API_AUTH == None:
if app.state.config.AUTOMATIC1111_API_AUTH is None:
return ""
else:
auth1111_byte_string = app.state.config.AUTOMATIC1111_API_AUTH.encode("utf-8")
@@ -103,164 +197,112 @@ def get_automatic1111_api_auth():
return f"Basic {auth1111_base64_encoded_string}"
@app.get("/config")
async def get_config(request: Request, user=Depends(get_admin_user)):
return {
"engine": app.state.config.ENGINE,
"enabled": app.state.config.ENABLED,
}
class ConfigUpdateForm(BaseModel):
engine: str
enabled: bool
@app.post("/config/update")
async def update_config(form_data: ConfigUpdateForm, user=Depends(get_admin_user)):
app.state.config.ENGINE = form_data.engine
app.state.config.ENABLED = form_data.enabled
return {
"engine": app.state.config.ENGINE,
"enabled": app.state.config.ENABLED,
}
class EngineUrlUpdateForm(BaseModel):
AUTOMATIC1111_BASE_URL: Optional[str] = None
AUTOMATIC1111_API_AUTH: Optional[str] = None
COMFYUI_BASE_URL: Optional[str] = None
@app.get("/url")
async def get_engine_url(user=Depends(get_admin_user)):
return {
"AUTOMATIC1111_BASE_URL": app.state.config.AUTOMATIC1111_BASE_URL,
"AUTOMATIC1111_API_AUTH": app.state.config.AUTOMATIC1111_API_AUTH,
"COMFYUI_BASE_URL": app.state.config.COMFYUI_BASE_URL,
}
@app.post("/url/update")
async def update_engine_url(
form_data: EngineUrlUpdateForm, user=Depends(get_admin_user)
):
if form_data.AUTOMATIC1111_BASE_URL == None:
app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
else:
url = form_data.AUTOMATIC1111_BASE_URL.strip("/")
@app.get("/config/url/verify")
async def verify_url(user=Depends(get_admin_user)):
if app.state.config.ENGINE == "automatic1111":
try:
r = requests.head(url)
app.state.config.AUTOMATIC1111_BASE_URL = url
r = requests.get(
url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
headers={"authorization": get_automatic1111_api_auth()},
)
r.raise_for_status()
return True
except Exception:
app.state.config.ENABLED = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
elif app.state.config.ENGINE == "comfyui":
try:
r = requests.get(url=f"{app.state.config.COMFYUI_BASE_URL}/object_info")
r.raise_for_status()
return True
except Exception:
app.state.config.ENABLED = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
else:
return True
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()
r = requests.get(
url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
headers={"authorization": api_auth},
)
options = r.json()
if model != options["sd_model_checkpoint"]:
options["sd_model_checkpoint"] = model
r = requests.post(
url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
json=options,
headers={"authorization": api_auth},
)
return app.state.config.MODEL
def get_image_model():
if app.state.config.ENGINE == "openai":
return app.state.config.MODEL if app.state.config.MODEL else "dall-e-2"
elif app.state.config.ENGINE == "comfyui":
return app.state.config.MODEL if app.state.config.MODEL else ""
elif app.state.config.ENGINE == "automatic1111" or app.state.config.ENGINE == "":
try:
r = requests.get(
url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
headers={"authorization": get_automatic1111_api_auth()},
)
options = r.json()
return options["sd_model_checkpoint"]
except Exception as e:
app.state.config.ENABLED = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
if form_data.COMFYUI_BASE_URL == None:
app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL
else:
url = form_data.COMFYUI_BASE_URL.strip("/")
try:
r = requests.head(url)
app.state.config.COMFYUI_BASE_URL = url
except Exception as e:
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
class ImageConfigForm(BaseModel):
MODEL: str
IMAGE_SIZE: str
IMAGE_STEPS: int
if form_data.AUTOMATIC1111_API_AUTH == None:
app.state.config.AUTOMATIC1111_API_AUTH = AUTOMATIC1111_API_AUTH
else:
app.state.config.AUTOMATIC1111_API_AUTH = form_data.AUTOMATIC1111_API_AUTH
@app.get("/image/config")
async def get_image_config(user=Depends(get_admin_user)):
return {
"AUTOMATIC1111_BASE_URL": app.state.config.AUTOMATIC1111_BASE_URL,
"AUTOMATIC1111_API_AUTH": app.state.config.AUTOMATIC1111_API_AUTH,
"COMFYUI_BASE_URL": app.state.config.COMFYUI_BASE_URL,
"status": True,
"MODEL": app.state.config.MODEL,
"IMAGE_SIZE": app.state.config.IMAGE_SIZE,
"IMAGE_STEPS": app.state.config.IMAGE_STEPS,
}
class OpenAIConfigUpdateForm(BaseModel):
url: str
key: str
@app.post("/image/config/update")
async def update_image_config(form_data: ImageConfigForm, user=Depends(get_admin_user)):
set_image_model(form_data.MODEL)
@app.get("/openai/config")
async def get_openai_config(user=Depends(get_admin_user)):
return {
"OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
"OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
}
@app.post("/openai/config/update")
async def update_openai_config(
form_data: OpenAIConfigUpdateForm, user=Depends(get_admin_user)
):
if form_data.key == "":
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)
app.state.config.OPENAI_API_BASE_URL = form_data.url
app.state.config.OPENAI_API_KEY = form_data.key
return {
"status": True,
"OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
"OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
}
class ImageSizeUpdateForm(BaseModel):
size: str
@app.get("/size")
async def get_image_size(user=Depends(get_admin_user)):
return {"IMAGE_SIZE": app.state.config.IMAGE_SIZE}
@app.post("/size/update")
async def update_image_size(
form_data: ImageSizeUpdateForm, user=Depends(get_admin_user)
):
pattern = r"^\d+x\d+$" # Regular expression pattern
if re.match(pattern, form_data.size):
app.state.config.IMAGE_SIZE = form_data.size
return {
"IMAGE_SIZE": app.state.config.IMAGE_SIZE,
"status": True,
}
pattern = r"^\d+x\d+$"
if re.match(pattern, form_data.IMAGE_SIZE):
app.state.config.IMAGE_SIZE = form_data.IMAGE_SIZE
else:
raise HTTPException(
status_code=400,
detail=ERROR_MESSAGES.INCORRECT_FORMAT(" (e.g., 512x512)."),
)
class ImageStepsUpdateForm(BaseModel):
steps: int
@app.get("/steps")
async def get_image_size(user=Depends(get_admin_user)):
return {"IMAGE_STEPS": app.state.config.IMAGE_STEPS}
@app.post("/steps/update")
async def update_image_size(
form_data: ImageStepsUpdateForm, user=Depends(get_admin_user)
):
if form_data.steps >= 0:
app.state.config.IMAGE_STEPS = form_data.steps
return {
"IMAGE_STEPS": app.state.config.IMAGE_STEPS,
"status": True,
}
if form_data.IMAGE_STEPS >= 0:
app.state.config.IMAGE_STEPS = form_data.IMAGE_STEPS
else:
raise HTTPException(
status_code=400,
detail=ERROR_MESSAGES.INCORRECT_FORMAT(" (e.g., 50)."),
)
return {
"MODEL": app.state.config.MODEL,
"IMAGE_SIZE": app.state.config.IMAGE_SIZE,
"IMAGE_STEPS": app.state.config.IMAGE_STEPS,
}
@app.get("/models")
def get_models(user=Depends(get_verified_user)):
@@ -271,18 +313,51 @@ def get_models(user=Depends(get_verified_user)):
{"id": "dall-e-3", "name": "DALL·E 3"},
]
elif app.state.config.ENGINE == "comfyui":
# TODO - get models from comfyui
r = requests.get(url=f"{app.state.config.COMFYUI_BASE_URL}/object_info")
info = r.json()
return list(
map(
lambda model: {"id": model, "name": model},
info["CheckpointLoaderSimple"]["input"]["required"]["ckpt_name"][0],
)
)
workflow = json.loads(app.state.config.COMFYUI_WORKFLOW)
model_node_id = None
else:
for node in app.state.config.COMFYUI_WORKFLOW_NODES:
if node["type"] == "model":
if node["node_ids"]:
model_node_id = node["node_ids"][0]
break
if model_node_id:
model_list_key = None
print(workflow[model_node_id]["class_type"])
for key in info[workflow[model_node_id]["class_type"]]["input"][
"required"
]:
if "_name" in key:
model_list_key = key
break
if model_list_key:
return list(
map(
lambda model: {"id": model, "name": model},
info[workflow[model_node_id]["class_type"]]["input"][
"required"
][model_list_key][0],
)
)
else:
return list(
map(
lambda model: {"id": model, "name": model},
info["CheckpointLoaderSimple"]["input"]["required"][
"ckpt_name"
][0],
)
)
elif (
app.state.config.ENGINE == "automatic1111" or app.state.config.ENGINE == ""
):
r = requests.get(
url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/sd-models",
headers={"authorization": get_automatic1111_api_auth()},
@@ -299,69 +374,11 @@ def get_models(user=Depends(get_verified_user)):
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
@app.get("/models/default")
async def get_default_model(user=Depends(get_admin_user)):
try:
if app.state.config.ENGINE == "openai":
return {
"model": (
app.state.config.MODEL if app.state.config.MODEL else "dall-e-2"
)
}
elif app.state.config.ENGINE == "comfyui":
return {"model": (app.state.config.MODEL if app.state.config.MODEL else "")}
else:
r = requests.get(
url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
headers={"authorization": get_automatic1111_api_auth()},
)
options = r.json()
return {"model": options["sd_model_checkpoint"]}
except Exception as e:
app.state.config.ENABLED = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
class UpdateModelForm(BaseModel):
model: str
def set_model_handler(model: str):
if app.state.config.ENGINE in ["openai", "comfyui"]:
app.state.config.MODEL = model
return app.state.config.MODEL
else:
api_auth = get_automatic1111_api_auth()
r = requests.get(
url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
headers={"authorization": api_auth},
)
options = r.json()
if model != options["sd_model_checkpoint"]:
options["sd_model_checkpoint"] = model
r = requests.post(
url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
json=options,
headers={"authorization": api_auth},
)
return options
@app.post("/models/default/update")
def update_default_model(
form_data: UpdateModelForm,
user=Depends(get_verified_user),
):
return set_model_handler(form_data.model)
class GenerateImageForm(BaseModel):
model: Optional[str] = None
prompt: str
n: int = 1
size: Optional[str] = None
n: int = 1
negative_prompt: Optional[str] = None
@@ -403,7 +420,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)
@@ -418,7 +434,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:
@@ -436,7 +452,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"
@@ -455,7 +470,9 @@ async def image_generations(
"response_format": "b64_json",
}
r = requests.post(
# Use asyncio.to_thread for the requests.post call
r = await asyncio.to_thread(
requests.post,
url=f"{app.state.config.OPENAI_API_BASE_URL}/images/generations",
json=data,
headers=headers,
@@ -477,7 +494,6 @@ async def image_generations(
return images
elif app.state.config.ENGINE == "comfyui":
data = {
"prompt": form_data.prompt,
"width": width,
@@ -491,32 +507,20 @@ async def image_generations(
if form_data.negative_prompt is not None:
data["negative_prompt"] = form_data.negative_prompt
if app.state.config.COMFYUI_CFG_SCALE:
data["cfg_scale"] = app.state.config.COMFYUI_CFG_SCALE
if app.state.config.COMFYUI_SAMPLER is not None:
data["sampler"] = app.state.config.COMFYUI_SAMPLER
if app.state.config.COMFYUI_SCHEDULER is not None:
data["scheduler"] = app.state.config.COMFYUI_SCHEDULER
if app.state.config.COMFYUI_SD3 is not None:
data["sd3"] = app.state.config.COMFYUI_SD3
if app.state.config.COMFYUI_FLUX is not None:
data["flux"] = app.state.config.COMFYUI_FLUX
if app.state.config.COMFYUI_FLUX_WEIGHT_DTYPE is not None:
data["flux_weight_dtype"] = app.state.config.COMFYUI_FLUX_WEIGHT_DTYPE
if app.state.config.COMFYUI_FLUX_FP8_CLIP is not None:
data["flux_fp8_clip"] = app.state.config.COMFYUI_FLUX_FP8_CLIP
data = ImageGenerationPayload(**data)
res = comfyui_generate_image(
form_data = ComfyUIGenerateImageForm(
**{
"workflow": ComfyUIWorkflow(
**{
"workflow": app.state.config.COMFYUI_WORKFLOW,
"nodes": app.state.config.COMFYUI_WORKFLOW_NODES,
}
),
**data,
}
)
res = await comfyui_generate_image(
app.state.config.MODEL,
data,
form_data,
user.id,
app.state.config.COMFYUI_BASE_URL,
)
@@ -530,13 +534,15 @@ async def image_generations(
file_body_path = IMAGE_CACHE_DIR.joinpath(f"{image_filename}.json")
with open(file_body_path, "w") as f:
json.dump(data.model_dump(exclude_none=True), f)
json.dump(form_data.model_dump(exclude_none=True), f)
log.debug(f"images: {images}")
return images
else:
elif (
app.state.config.ENGINE == "automatic1111" or app.state.config.ENGINE == ""
):
if form_data.model:
set_model_handler(form_data.model)
set_image_model(form_data.model)
data = {
"prompt": form_data.prompt,
@@ -551,14 +557,24 @@ async def image_generations(
if form_data.negative_prompt is not None:
data["negative_prompt"] = form_data.negative_prompt
r = requests.post(
if app.state.config.AUTOMATIC1111_CFG_SCALE:
data["cfg_scale"] = app.state.config.AUTOMATIC1111_CFG_SCALE
if app.state.config.AUTOMATIC1111_SAMPLER:
data["sampler_name"] = app.state.config.AUTOMATIC1111_SAMPLER
if app.state.config.AUTOMATIC1111_SCHEDULER:
data["scheduler"] = app.state.config.AUTOMATIC1111_SCHEDULER
# 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()},
)
res = r.json()
log.debug(f"res: {res}")
images = []
@@ -572,10 +588,8 @@ async def image_generations(
json.dump({**data, "info": res["info"]}, f)
return images
except Exception as e:
error = e
if r != None:
data = r.json()
if "error" in data:
@@ -0,0 +1,174 @@
import asyncio
import json
import logging
import random
import urllib.parse
import urllib.request
from typing import Optional
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"])
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")
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)
req = urllib.request.Request(
f"{base_url}/view?{url_values}", headers=default_headers
)
with urllib.request.urlopen(req) as response:
return response.read()
def get_image_url(filename, subfolder, folder_type, base_url):
log.info("get_image")
data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
url_values = urllib.parse.urlencode(data)
return f"{base_url}/view?{url_values}"
def get_history(prompt_id, base_url):
log.info("get_history")
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())
def get_images(ws, prompt, client_id, base_url):
prompt_id = queue_prompt(prompt, client_id, base_url)["prompt_id"]
output_images = []
while True:
out = ws.recv()
if isinstance(out, str):
message = json.loads(out)
if message["type"] == "executing":
data = message["data"]
if data["node"] is None and data["prompt_id"] == prompt_id:
break # Execution is done
else:
continue # previews are binary data
history = get_history(prompt_id, base_url)[prompt_id]
for o in history["outputs"]:
for node_id in history["outputs"]:
node_output = history["outputs"][node_id]
if "images" in node_output:
for image in node_output["images"]:
url = get_image_url(
image["filename"], image["subfolder"], image["type"], base_url
)
output_images.append({"url": url})
return {"data": output_images}
class ComfyUINodeInput(BaseModel):
type: Optional[str] = None
node_ids: list[str] = []
key: Optional[str] = "text"
value: Optional[str] = None
class ComfyUIWorkflow(BaseModel):
workflow: str
nodes: list[ComfyUINodeInput]
class ComfyUIGenerateImageForm(BaseModel):
workflow: ComfyUIWorkflow
prompt: str
negative_prompt: Optional[str] = None
width: int
height: int
n: int = 1
steps: Optional[int] = None
seed: Optional[int] = None
async def comfyui_generate_image(
model: str, payload: ComfyUIGenerateImageForm, client_id, base_url
):
ws_url = base_url.replace("http://", "ws://").replace("https://", "wss://")
workflow = json.loads(payload.workflow.workflow)
for node in payload.workflow.nodes:
if node.type:
if node.type == "model":
for node_id in node.node_ids:
workflow[node_id]["inputs"][node.key] = model
elif node.type == "prompt":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["text"] = payload.prompt
elif node.type == "negative_prompt":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["text"] = payload.negative_prompt
elif node.type == "width":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["width"] = payload.width
elif node.type == "height":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["height"] = payload.height
elif node.type == "n":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["batch_size"] = payload.n
elif node.type == "steps":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["steps"] = payload.steps
elif node.type == "seed":
seed = (
payload.seed
if payload.seed
else random.randint(0, 18446744073709551614)
)
for node_id in node.node_ids:
workflow[node_id]["inputs"][node.key] = seed
else:
for node_id in node.node_ids:
workflow[node_id]["inputs"][node.key] = node.value
try:
ws = websocket.WebSocket()
ws.connect(f"{ws_url}/ws?clientId={client_id}")
log.info("WebSocket connection established.")
except Exception as e:
log.exception(f"Failed to connect to WebSocket server: {e}")
return None
try:
log.info("Sending workflow to WebSocket server.")
log.info(f"Workflow: {workflow}")
images = await asyncio.to_thread(get_images, ws, workflow, client_id, base_url)
except Exception as e:
log.exception(f"Error while receiving images: {e}")
images = None
ws.close()
return images
@@ -1,59 +1,44 @@
from fastapi import (
FastAPI,
Request,
Response,
HTTPException,
Depends,
status,
UploadFile,
File,
BackgroundTasks,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from fastapi.concurrency import run_in_threadpool
from pydantic import BaseModel, ConfigDict
import os
import re
import copy
import random
import requests
import json
import uuid
import aiohttp
import asyncio
import json
import logging
import os
import random
import re
import time
from typing import Optional, Union
from urllib.parse import urlparse
from typing import Optional, List, Union
from starlette.background import BackgroundTask
from apps.webui.models.models import Models
from apps.webui.models.users import Users
from constants import ERROR_MESSAGES
from utils.utils import (
decode_token,
get_current_user,
get_verified_user,
get_admin_user,
)
from utils.task import prompt_template
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,
)
from utils.misc import calculate_sha256, add_or_update_system_message
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 (
calculate_sha256,
)
from open_webui.utils.payload import (
apply_model_params_to_body_ollama,
apply_model_params_to_body_openai,
apply_model_system_prompt_to_body,
)
from open_webui.utils.utils import get_admin_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["OLLAMA"])
@@ -61,7 +46,7 @@ log.setLevel(SRC_LOG_LEVELS["OLLAMA"])
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=CORS_ALLOW_ORIGIN,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@@ -120,7 +105,7 @@ async def get_ollama_api_urls(user=Depends(get_admin_user)):
class UrlUpdateForm(BaseModel):
urls: List[str]
urls: list[str]
@app.post("/urls/update")
@@ -153,20 +138,29 @@ async def cleanup_response(
await session.close()
async def post_streaming_url(url: str, payload: str, 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(
trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
)
r = await session.post(url, data=payload)
r = await session.post(
url,
data=payload,
headers={"Content-Type": "application/json"},
)
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
),
@@ -183,7 +177,7 @@ async def post_streaming_url(url: str, payload: str, stream: bool = True):
res = await r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except:
except Exception:
error_detail = f"Ollama: {e}"
raise HTTPException(
@@ -238,7 +232,7 @@ async def get_all_models():
async def get_ollama_tags(
url_idx: Optional[int] = None, user=Depends(get_verified_user)
):
if url_idx == None:
if url_idx is None:
models = await get_all_models()
if app.state.config.ENABLE_MODEL_FILTER:
@@ -269,7 +263,7 @@ async def get_ollama_tags(
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except:
except Exception:
error_detail = f"Ollama: {e}"
raise HTTPException(
@@ -282,8 +276,7 @@ async def get_ollama_tags(
@app.get("/api/version/{url_idx}")
async def get_ollama_versions(url_idx: Optional[int] = None):
if app.state.config.ENABLE_OLLAMA_API:
if url_idx == None:
if url_idx is None:
# returns lowest version
tasks = [
fetch_url(f"{url}/api/version")
@@ -323,7 +316,7 @@ async def get_ollama_versions(url_idx: Optional[int] = None):
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except:
except Exception:
error_detail = f"Ollama: {e}"
raise HTTPException(
@@ -346,8 +339,6 @@ async def pull_model(
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
log.info(f"url: {url}")
r = None
# Admin should be able to pull models from any source
payload = {**form_data.model_dump(exclude_none=True), "insecure": True}
@@ -367,7 +358,7 @@ async def push_model(
url_idx: Optional[int] = None,
user=Depends(get_admin_user),
):
if url_idx == None:
if url_idx is None:
if form_data.name in app.state.MODELS:
url_idx = app.state.MODELS[form_data.name]["urls"][0]
else:
@@ -417,7 +408,7 @@ async def copy_model(
url_idx: Optional[int] = None,
user=Depends(get_admin_user),
):
if url_idx == None:
if url_idx is None:
if form_data.source in app.state.MODELS:
url_idx = app.state.MODELS[form_data.source]["urls"][0]
else:
@@ -428,13 +419,14 @@ async def copy_model(
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
log.info(f"url: {url}")
r = requests.request(
method="POST",
url=f"{url}/api/copy",
headers={"Content-Type": "application/json"},
data=form_data.model_dump_json(exclude_none=True).encode(),
)
try:
r = requests.request(
method="POST",
url=f"{url}/api/copy",
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
log.debug(f"r.text: {r.text}")
@@ -448,7 +440,7 @@ async def copy_model(
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except:
except Exception:
error_detail = f"Ollama: {e}"
raise HTTPException(
@@ -464,7 +456,7 @@ async def delete_model(
url_idx: Optional[int] = None,
user=Depends(get_admin_user),
):
if url_idx == None:
if url_idx is None:
if form_data.name in app.state.MODELS:
url_idx = app.state.MODELS[form_data.name]["urls"][0]
else:
@@ -476,12 +468,13 @@ async def delete_model(
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
log.info(f"url: {url}")
r = requests.request(
method="DELETE",
url=f"{url}/api/delete",
headers={"Content-Type": "application/json"},
data=form_data.model_dump_json(exclude_none=True).encode(),
)
try:
r = requests.request(
method="DELETE",
url=f"{url}/api/delete",
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
log.debug(f"r.text: {r.text}")
@@ -495,7 +488,7 @@ async def delete_model(
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except:
except Exception:
error_detail = f"Ollama: {e}"
raise HTTPException(
@@ -516,12 +509,13 @@ async def show_model_info(form_data: ModelNameForm, user=Depends(get_verified_us
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
log.info(f"url: {url}")
r = requests.request(
method="POST",
url=f"{url}/api/show",
headers={"Content-Type": "application/json"},
data=form_data.model_dump_json(exclude_none=True).encode(),
)
try:
r = requests.request(
method="POST",
url=f"{url}/api/show",
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
return r.json()
@@ -533,7 +527,7 @@ async def show_model_info(form_data: ModelNameForm, user=Depends(get_verified_us
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except:
except Exception:
error_detail = f"Ollama: {e}"
raise HTTPException(
@@ -549,14 +543,14 @@ class GenerateEmbeddingsForm(BaseModel):
keep_alive: Optional[Union[int, str]] = None
@app.post("/api/embeddings")
@app.post("/api/embeddings/{url_idx}")
@app.post("/api/embed")
@app.post("/api/embed/{url_idx}")
async def generate_embeddings(
form_data: GenerateEmbeddingsForm,
url_idx: Optional[int] = None,
user=Depends(get_verified_user),
):
if url_idx == None:
if url_idx is None:
model = form_data.model
if ":" not in model:
@@ -573,12 +567,13 @@ async def generate_embeddings(
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
log.info(f"url: {url}")
r = requests.request(
method="POST",
url=f"{url}/api/embed",
headers={"Content-Type": "application/json"},
data=form_data.model_dump_json(exclude_none=True).encode(),
)
try:
r = requests.request(
method="POST",
url=f"{url}/api/embeddings",
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
return r.json()
@@ -590,7 +585,58 @@ async def generate_embeddings(
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except:
except Exception:
error_detail = f"Ollama: {e}"
raise HTTPException(
status_code=r.status_code if r else 500,
detail=error_detail,
)
@app.post("/api/embeddings")
@app.post("/api/embeddings/{url_idx}")
async def generate_embeddings(
form_data: GenerateEmbeddingsForm,
url_idx: Optional[int] = None,
user=Depends(get_verified_user),
):
if url_idx is None:
model = form_data.model
if ":" not in model:
model = f"{model}:latest"
if model in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[model]["urls"])
else:
raise HTTPException(
status_code=400,
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
)
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
log.info(f"url: {url}")
r = requests.request(
method="POST",
url=f"{url}/api/embeddings",
headers={"Content-Type": "application/json"},
data=form_data.model_dump_json(exclude_none=True).encode(),
)
try:
r.raise_for_status()
return r.json()
except Exception as e:
log.exception(e)
error_detail = "Open WebUI: Server Connection Error"
if r is not None:
try:
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except Exception:
error_detail = f"Ollama: {e}"
raise HTTPException(
@@ -603,10 +649,9 @@ def generate_ollama_embeddings(
form_data: GenerateEmbeddingsForm,
url_idx: Optional[int] = None,
):
log.info(f"generate_ollama_embeddings {form_data}")
if url_idx == None:
if url_idx is None:
model = form_data.model
if ":" not in model:
@@ -623,12 +668,13 @@ def generate_ollama_embeddings(
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
log.info(f"url: {url}")
r = requests.request(
method="POST",
url=f"{url}/api/embeddings",
headers={"Content-Type": "application/json"},
data=form_data.model_dump_json(exclude_none=True).encode(),
)
try:
r = requests.request(
method="POST",
url=f"{url}/api/embeddings",
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
data = r.json()
@@ -638,7 +684,7 @@ def generate_ollama_embeddings(
if "embedding" in data:
return data["embedding"]
else:
raise "Something went wrong :/"
raise Exception("Something went wrong :/")
except Exception as e:
log.exception(e)
error_detail = "Open WebUI: Server Connection Error"
@@ -647,16 +693,16 @@ def generate_ollama_embeddings(
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except:
except Exception:
error_detail = f"Ollama: {e}"
raise error_detail
raise Exception(error_detail)
class GenerateCompletionForm(BaseModel):
model: str
prompt: str
images: Optional[List[str]] = None
images: Optional[list[str]] = None
format: Optional[str] = None
options: Optional[dict] = None
system: Optional[str] = None
@@ -674,8 +720,7 @@ async def generate_completion(
url_idx: Optional[int] = None,
user=Depends(get_verified_user),
):
if url_idx == None:
if url_idx is None:
model = form_data.model
if ":" not in model:
@@ -700,12 +745,12 @@ async def generate_completion(
class ChatMessage(BaseModel):
role: str
content: str
images: Optional[List[str]] = None
images: Optional[list[str]] = None
class GenerateChatCompletionForm(BaseModel):
model: str
messages: List[ChatMessage]
messages: list[ChatMessage]
format: Optional[str] = None
options: Optional[dict] = None
template: Optional[str] = None
@@ -713,6 +758,18 @@ class GenerateChatCompletionForm(BaseModel):
keep_alive: Optional[Union[int, str]] = None
def get_ollama_url(url_idx: Optional[int], model: str):
if url_idx is None:
if model not in app.state.MODELS:
raise HTTPException(
status_code=400,
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(model),
)
url_idx = random.choice(app.state.MODELS[model]["urls"])
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
return url
@app.post("/api/chat")
@app.post("/api/chat/{url_idx}")
async def generate_chat_completion(
@@ -720,209 +777,50 @@ async def generate_chat_completion(
url_idx: Optional[int] = None,
user=Depends(get_verified_user),
):
log.debug(
"form_data.model_dump_json(exclude_none=True).encode(): {0} ".format(
form_data.model_dump_json(exclude_none=True).encode()
)
)
payload = {
**form_data.model_dump(exclude_none=True, exclude=["metadata"]),
}
payload = {**form_data.model_dump(exclude_none=True)}
log.debug(f"{payload = }")
if "metadata" in payload:
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:
if model_info.base_model_id:
payload["model"] = model_info.base_model_id
model_info.params = model_info.params.model_dump()
params = model_info.params.model_dump()
if model_info.params:
if params:
if payload.get("options") is None:
payload["options"] = {}
if (
model_info.params.get("mirostat", None)
and payload["options"].get("mirostat") is None
):
payload["options"]["mirostat"] = model_info.params.get("mirostat", None)
if (
model_info.params.get("mirostat_eta", None)
and payload["options"].get("mirostat_eta") is None
):
payload["options"]["mirostat_eta"] = model_info.params.get(
"mirostat_eta", None
)
if (
model_info.params.get("mirostat_tau", None)
and payload["options"].get("mirostat_tau") is None
):
payload["options"]["mirostat_tau"] = model_info.params.get(
"mirostat_tau", None
)
if (
model_info.params.get("num_ctx", None)
and payload["options"].get("num_ctx") is None
):
payload["options"]["num_ctx"] = model_info.params.get("num_ctx", None)
if (
model_info.params.get("num_batch", None)
and payload["options"].get("num_batch") is None
):
payload["options"]["num_batch"] = model_info.params.get(
"num_batch", None
)
if (
model_info.params.get("num_keep", None)
and payload["options"].get("num_keep") is None
):
payload["options"]["num_keep"] = model_info.params.get("num_keep", None)
if (
model_info.params.get("repeat_last_n", None)
and payload["options"].get("repeat_last_n") is None
):
payload["options"]["repeat_last_n"] = model_info.params.get(
"repeat_last_n", None
)
if (
model_info.params.get("frequency_penalty", None)
and payload["options"].get("frequency_penalty") is None
):
payload["options"]["repeat_penalty"] = model_info.params.get(
"frequency_penalty", None
)
if (
model_info.params.get("temperature", None) is not None
and payload["options"].get("temperature") is None
):
payload["options"]["temperature"] = model_info.params.get(
"temperature", None
)
if (
model_info.params.get("seed", None) is not None
and payload["options"].get("seed") is None
):
payload["options"]["seed"] = model_info.params.get("seed", None)
if (
model_info.params.get("stop", None)
and payload["options"].get("stop") is None
):
payload["options"]["stop"] = (
[
bytes(stop, "utf-8").decode("unicode_escape")
for stop in model_info.params["stop"]
]
if model_info.params.get("stop", None)
else None
)
if (
model_info.params.get("tfs_z", None)
and payload["options"].get("tfs_z") is None
):
payload["options"]["tfs_z"] = model_info.params.get("tfs_z", None)
if (
model_info.params.get("max_tokens", None)
and payload["options"].get("max_tokens") is None
):
payload["options"]["num_predict"] = model_info.params.get(
"max_tokens", None
)
if (
model_info.params.get("top_k", None)
and payload["options"].get("top_k") is None
):
payload["options"]["top_k"] = model_info.params.get("top_k", None)
if (
model_info.params.get("top_p", None)
and payload["options"].get("top_p") is None
):
payload["options"]["top_p"] = model_info.params.get("top_p", None)
if (
model_info.params.get("min_p", None)
and payload["options"].get("min_p") is None
):
payload["options"]["min_p"] = model_info.params.get("min_p", None)
if (
model_info.params.get("use_mmap", None)
and payload["options"].get("use_mmap") is None
):
payload["options"]["use_mmap"] = model_info.params.get("use_mmap", None)
if (
model_info.params.get("use_mlock", None)
and payload["options"].get("use_mlock") is None
):
payload["options"]["use_mlock"] = model_info.params.get(
"use_mlock", None
)
if (
model_info.params.get("num_thread", None)
and payload["options"].get("num_thread") is None
):
payload["options"]["num_thread"] = model_info.params.get(
"num_thread", None
)
system = model_info.params.get("system", None)
if system:
system = prompt_template(
system,
**(
{
"user_name": user.name,
"user_location": (
user.info.get("location") if user.info else None
),
}
if user
else {}
),
payload["options"] = apply_model_params_to_body_ollama(
params, payload["options"]
)
payload = apply_model_system_prompt_to_body(params, payload, user)
if payload.get("messages"):
payload["messages"] = add_or_update_system_message(
system, payload["messages"]
)
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
if url_idx == None:
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
if payload["model"] in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[payload["model"]]["urls"])
else:
raise HTTPException(
status_code=400,
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
)
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
url = get_ollama_url(url_idx, payload["model"])
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),
stream=form_data.stream,
content_type="application/x-ndjson",
)
# TODO: we should update this part once Ollama supports other types
@@ -940,7 +838,7 @@ class OpenAIChatMessage(BaseModel):
class OpenAIChatCompletionForm(BaseModel):
model: str
messages: List[OpenAIChatMessage]
messages: list[OpenAIChatMessage]
model_config = ConfigDict(extra="allow")
@@ -952,83 +850,36 @@ async def generate_openai_chat_completion(
url_idx: Optional[int] = None,
user=Depends(get_verified_user),
):
form_data = OpenAIChatCompletionForm(**form_data)
payload = {**form_data.model_dump(exclude_none=True, exclude=["metadata"])}
completion_form = OpenAIChatCompletionForm(**form_data)
payload = {**completion_form.model_dump(exclude_none=True, exclude=["metadata"])}
if "metadata" in payload:
del payload["metadata"]
model_id = form_data.model
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:
if model_info.base_model_id:
payload["model"] = model_info.base_model_id
model_info.params = model_info.params.model_dump()
params = model_info.params.model_dump()
if model_info.params:
payload["temperature"] = model_info.params.get("temperature", None)
payload["top_p"] = model_info.params.get("top_p", None)
payload["max_tokens"] = model_info.params.get("max_tokens", None)
payload["frequency_penalty"] = model_info.params.get(
"frequency_penalty", None
)
payload["seed"] = model_info.params.get("seed", None)
payload["stop"] = (
[
bytes(stop, "utf-8").decode("unicode_escape")
for stop in model_info.params["stop"]
]
if model_info.params.get("stop", None)
else None
)
if params:
payload = apply_model_params_to_body_openai(params, payload)
payload = apply_model_system_prompt_to_body(params, payload, user)
system = model_info.params.get("system", None)
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
if system:
system = prompt_template(
system,
**(
{
"user_name": user.name,
"user_location": (
user.info.get("location") if user.info else None
),
}
if user
else {}
),
)
# Check if the payload already has a system message
# If not, add a system message to the payload
if payload.get("messages"):
for message in payload["messages"]:
if message.get("role") == "system":
message["content"] = system + message["content"]
break
else:
payload["messages"].insert(
0,
{
"role": "system",
"content": system,
},
)
if url_idx == None:
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
if payload["model"] in app.state.MODELS:
url_idx = random.choice(app.state.MODELS[payload["model"]]["urls"])
else:
raise HTTPException(
status_code=400,
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
)
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
url = get_ollama_url(url_idx, payload["model"])
log.info(f"url: {url}")
return await post_streaming_url(
@@ -1044,7 +895,7 @@ async def get_openai_models(
url_idx: Optional[int] = None,
user=Depends(get_verified_user),
):
if url_idx == None:
if url_idx is None:
models = await get_all_models()
if app.state.config.ENABLE_MODEL_FILTER:
@@ -1099,7 +950,7 @@ async def get_openai_models(
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except:
except Exception:
error_detail = f"Ollama: {e}"
raise HTTPException(
@@ -1125,7 +976,6 @@ def parse_huggingface_url(hf_url):
path_components = parsed_url.path.split("/")
# Extract the desired output
user_repo = "/".join(path_components[1:3])
model_file = path_components[-1]
return model_file
@@ -1190,7 +1040,6 @@ async def download_model(
url_idx: Optional[int] = None,
user=Depends(get_admin_user),
):
allowed_hosts = ["https://huggingface.co/", "https://github.com/"]
if not any(form_data.url.startswith(host) for host in allowed_hosts):
@@ -1199,7 +1048,7 @@ async def download_model(
detail="Invalid file_url. Only URLs from allowed hosts are permitted.",
)
if url_idx == None:
if url_idx is None:
url_idx = 0
url = app.state.config.OLLAMA_BASE_URLS[url_idx]
@@ -1222,7 +1071,7 @@ def upload_model(
url_idx: Optional[int] = None,
user=Depends(get_admin_user),
):
if url_idx == None:
if url_idx is None:
url_idx = 0
ollama_url = app.state.config.OLLAMA_BASE_URLS[url_idx]
@@ -1,40 +1,39 @@
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 open_webui.utils.payload import (
apply_model_params_to_body_openai,
apply_model_system_prompt_to_body,
)
from utils.misc import apply_model_params_to_body, 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,
)
from typing import List, 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"])
@@ -42,7 +41,7 @@ log.setLevel(SRC_LOG_LEVELS["OPENAI"])
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=CORS_ALLOW_ORIGIN,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@@ -86,11 +85,11 @@ async def update_config(form_data: OpenAIConfigForm, user=Depends(get_admin_user
class UrlsUpdateForm(BaseModel):
urls: List[str]
urls: list[str]
class KeysUpdateForm(BaseModel):
keys: List[str]
keys: list[str]
@app.get("/urls")
@@ -221,7 +220,17 @@ def merge_models_lists(model_lists):
for model in models
if "api.openai.com"
not in app.state.config.OPENAI_API_BASE_URLS[idx]
or "gpt" in model["id"]
or not any(
name in model["id"]
for name in [
"babbage",
"dall-e",
"davinci",
"embedding",
"tts",
"whisper",
]
)
]
)
@@ -324,10 +333,24 @@ async def get_models(url_idx: Optional[int] = None, user=Depends(get_verified_us
r.raise_for_status()
response_data = r.json()
if "api.openai.com" in url:
response_data["data"] = list(
filter(lambda model: "gpt" in model["id"], response_data["data"])
)
# Filter the response data
response_data["data"] = [
model
for model in response_data["data"]
if not any(
name in model["id"]
for name in [
"babbage",
"dall-e",
"davinci",
"embedding",
"tts",
"whisper",
]
)
]
return response_data
except Exception as e:
@@ -356,7 +379,9 @@ async def generate_chat_completion(
):
idx = 0
payload = {**form_data}
payload.pop("metadata")
if "metadata" in payload:
del payload["metadata"]
model_id = form_data.get("model")
model_info = Models.get_model_by_id(model_id)
@@ -366,7 +391,7 @@ async def generate_chat_completion(
payload["model"] = model_info.base_model_id
params = model_info.params.model_dump()
payload = apply_model_params_to_body(params, payload)
payload = apply_model_params_to_body_openai(params, payload)
payload = apply_model_system_prompt_to_body(params, payload, user)
model = app.state.MODELS[payload.get("model")]
@@ -380,14 +405,19 @@ async def generate_chat_completion(
"role": user.role,
}
url = app.state.config.OPENAI_API_BASE_URLS[idx]
key = app.state.config.OPENAI_API_KEYS[idx]
# Change max_completion_tokens to max_tokens (Backward compatible)
if "api.openai.com" not in url and not payload["model"].lower().startswith("o1-"):
if "max_completion_tokens" in payload:
payload["max_tokens"] = payload.pop("max_completion_tokens")
# Convert the modified body back to JSON
payload = json.dumps(payload)
log.debug(payload)
url = app.state.config.OPENAI_API_BASE_URLS[idx]
key = app.state.config.OPENAI_API_KEYS[idx]
headers = {}
headers["Authorization"] = f"Bearer {key}"
headers["Content-Type"] = "application/json"
@@ -398,6 +428,7 @@ async def generate_chat_completion(
r = None
session = None
streaming = False
response = None
try:
session = aiohttp.ClientSession(
@@ -410,8 +441,6 @@ async def generate_chat_completion(
headers=headers,
)
r.raise_for_status()
# Check if response is SSE
if "text/event-stream" in r.headers.get("Content-Type", ""):
streaming = True
@@ -424,19 +453,23 @@ async def generate_chat_completion(
),
)
else:
response_data = await r.json()
return response_data
try:
response = await r.json()
except Exception as e:
log.error(e)
response = await r.text()
r.raise_for_status()
return response
except Exception as e:
log.exception(e)
error_detail = "Open WebUI: Server Connection Error"
if r is not None:
try:
res = await r.json()
print(res)
if "error" in res:
error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}"
except Exception:
error_detail = f"External: {e}"
if isinstance(response, dict):
if "error" in response:
error_detail = f"{response['error']['message'] if 'message' in response['error'] else response['error']}"
elif isinstance(response, str):
error_detail = response
raise HTTPException(status_code=r.status if r else 500, detail=error_detail)
finally:
if not streaming and session:
@@ -1,137 +1,125 @@
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 List, 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 numpy as np
import torch
import requests
import validators
from fastapi import Depends, FastAPI, File, Form, HTTPException, UploadFile, status
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from open_webui.apps.rag.search.main import SearchResult
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.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 open_webui.config import (
BRAVE_SEARCH_API_KEY,
CHUNK_OVERLAP,
CHUNK_SIZE,
CONTENT_EXTRACTION_ENGINE,
TIKA_SERVER_URL,
RAG_TOP_K,
RAG_RELEVANCE_THRESHOLD,
CORS_ALLOW_ORIGIN,
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,
DEFAULT_RAG_TEMPLATE,
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,
TIKA_SERVER_URL,
UPLOAD_DIR,
YOUTUBE_LOADER_LANGUAGE,
AppConfig,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS, DEVICE_TYPE, DOCKER
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
from open_webui.apps.rag.vector.connector import VECTOR_DB_CLIENT
from constants import ERROR_MESSAGES
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 colbert.infra import ColBERTConfig
from colbert.modeling.checkpoint import Checkpoint
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -142,6 +130,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 = (
@@ -160,13 +150,11 @@ app.state.config.RAG_EMBEDDING_OPENAI_BATCH_SIZE = RAG_EMBEDDING_OPENAI_BATCH_SI
app.state.config.RAG_RERANKING_MODEL = RAG_RERANKING_MODEL
app.state.config.RAG_TEMPLATE = RAG_TEMPLATE
app.state.config.OPENAI_API_BASE_URL = RAG_OPENAI_API_BASE_URL
app.state.config.OPENAI_API_KEY = RAG_OPENAI_API_KEY
app.state.config.PDF_EXTRACT_IMAGES = PDF_EXTRACT_IMAGES
app.state.config.YOUTUBE_LOADER_LANGUAGE = YOUTUBE_LOADER_LANGUAGE
app.state.YOUTUBE_LOADER_TRANSLATION = None
@@ -184,19 +172,21 @@ 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
def update_embedding_model(
embedding_model: str,
update_model: bool = False,
auto_update: bool = False,
):
if embedding_model and app.state.config.RAG_EMBEDDING_ENGINE == "":
import sentence_transformers
app.state.sentence_transformer_ef = sentence_transformers.SentenceTransformer(
get_model_path(embedding_model, update_model),
get_model_path(embedding_model, auto_update),
device=DEVICE_TYPE,
trust_remote_code=RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE,
)
@@ -206,16 +196,108 @@ def update_embedding_model(
def update_reranking_model(
reranking_model: str,
update_model: bool = False,
auto_update: bool = False,
):
if reranking_model:
import sentence_transformers
if any(model in reranking_model for model in ["jinaai/jina-colbert-v2"]):
app.state.sentence_transformer_rf = sentence_transformers.CrossEncoder(
get_model_path(reranking_model, update_model),
device=DEVICE_TYPE,
trust_remote_code=RAG_RERANKING_MODEL_TRUST_REMOTE_CODE,
)
class ColBERT:
def __init__(self, name) -> None:
print("ColBERT: Loading model", name)
self.device = "cuda" if torch.cuda.is_available() else "cpu"
if DOCKER:
# This is a workaround for the issue with the docker container
# where the torch extension is not loaded properly
# and the following error is thrown:
# /root/.cache/torch_extensions/py311_cpu/segmented_maxsim_cpp/segmented_maxsim_cpp.so: cannot open shared object file: No such file or directory
lock_file = "/root/.cache/torch_extensions/py311_cpu/segmented_maxsim_cpp/lock"
if os.path.exists(lock_file):
os.remove(lock_file)
self.ckpt = Checkpoint(
name,
colbert_config=ColBERTConfig(model_name=name),
).to(self.device)
pass
def calculate_similarity_scores(
self, query_embeddings, document_embeddings
):
query_embeddings = query_embeddings.to(self.device)
document_embeddings = document_embeddings.to(self.device)
# Validate dimensions to ensure compatibility
if query_embeddings.dim() != 3:
raise ValueError(
f"Expected query embeddings to have 3 dimensions, but got {query_embeddings.dim()}."
)
if document_embeddings.dim() != 3:
raise ValueError(
f"Expected document embeddings to have 3 dimensions, but got {document_embeddings.dim()}."
)
if query_embeddings.size(0) not in [1, document_embeddings.size(0)]:
raise ValueError(
"There should be either one query or queries equal to the number of documents."
)
# Transpose the query embeddings to align for matrix multiplication
transposed_query_embeddings = query_embeddings.permute(0, 2, 1)
# Compute similarity scores using batch matrix multiplication
computed_scores = torch.matmul(
document_embeddings, transposed_query_embeddings
)
# Apply max pooling to extract the highest semantic similarity across each document's sequence
maximum_scores = torch.max(computed_scores, dim=1).values
# Sum up the maximum scores across features to get the overall document relevance scores
final_scores = maximum_scores.sum(dim=1)
normalized_scores = torch.softmax(final_scores, dim=0)
return normalized_scores.detach().cpu().numpy().astype(np.float32)
def predict(self, sentences):
query = sentences[0][0]
docs = [i[1] for i in sentences]
# Embedding the documents
embedded_docs = self.ckpt.docFromText(docs, bsize=32)[0]
# Embedding the queries
embedded_queries = self.ckpt.queryFromText([query], bsize=32)
embedded_query = embedded_queries[0]
# Calculate retrieval scores for the query against all documents
scores = self.calculate_similarity_scores(
embedded_query.unsqueeze(0), embedded_docs
)
return scores
try:
app.state.sentence_transformer_rf = ColBERT(
get_model_path(reranking_model, auto_update)
)
except Exception as e:
log.error(f"ColBERT: {e}")
app.state.sentence_transformer_rf = None
app.state.config.ENABLE_RAG_HYBRID_SEARCH = False
else:
import sentence_transformers
try:
app.state.sentence_transformer_rf = sentence_transformers.CrossEncoder(
get_model_path(reranking_model, auto_update),
device=DEVICE_TYPE,
trust_remote_code=RAG_RERANKING_MODEL_TRUST_REMOTE_CODE,
)
except:
log.error("CrossEncoder error")
app.state.sentence_transformer_rf = None
app.state.config.ENABLE_RAG_HYBRID_SEARCH = False
else:
app.state.sentence_transformer_rf = None
@@ -240,12 +322,9 @@ app.state.EMBEDDING_FUNCTION = get_embedding_function(
app.state.config.RAG_EMBEDDING_OPENAI_BATCH_SIZE,
)
origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_origins=CORS_ALLOW_ORIGIN,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@@ -376,7 +455,7 @@ async def update_reranking_config(
try:
app.state.config.RAG_RERANKING_MODEL = form_data.reranking_model
update_reranking_model(app.state.config.RAG_RERANKING_MODEL), True
update_reranking_model(app.state.config.RAG_RERANKING_MODEL, True)
return {
"status": True,
@@ -395,6 +474,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,
@@ -421,6 +504,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,
},
@@ -428,6 +513,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
@@ -439,7 +529,7 @@ class ChunkParamUpdateForm(BaseModel):
class YoutubeLoaderConfig(BaseModel):
language: List[str]
language: list[str]
translation: Optional[str] = None
@@ -455,6 +545,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
@@ -466,6 +558,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
@@ -480,6 +573,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
@@ -513,6 +610,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
@@ -521,6 +620,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,
@@ -546,6 +649,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,
@@ -585,13 +690,14 @@ async def update_query_settings(
form_data: QuerySettingsForm, user=Depends(get_admin_user)
):
app.state.config.RAG_TEMPLATE = (
form_data.template if form_data.template else RAG_TEMPLATE
form_data.template if form_data.template != "" else DEFAULT_RAG_TEMPLATE
)
app.state.config.TOP_K = form_data.k if form_data.k else 4
app.state.config.RELEVANCE_THRESHOLD = form_data.r if form_data.r else 0.0
app.state.config.ENABLE_RAG_HYBRID_SEARCH = (
form_data.hybrid if form_data.hybrid else False
)
return {
"status": True,
"template": app.state.config.RAG_TEMPLATE,
@@ -642,7 +748,7 @@ def query_doc_handler(
class QueryCollectionsForm(BaseModel):
collection_names: List[str]
collection_names: list[str]
query: str
k: Optional[int] = None
r: Optional[float] = None
@@ -796,6 +902,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
"""
@@ -883,6 +990,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:
@@ -933,7 +1051,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,
@@ -978,41 +1095,39 @@ def store_docs_in_vector_db(
try:
if overwrite:
for collection in CHROMA_CLIENT.list_collections():
if collection_name == collection.name:
log.info(f"deleting existing collection {collection_name}")
CHROMA_CLIENT.delete_collection(name=collection_name)
if VECTOR_DB_CLIENT.has_collection(collection_name=collection_name):
log.info(f"deleting existing collection {collection_name}")
VECTOR_DB_CLIENT.delete_collection(collection_name=collection_name)
collection = CHROMA_CLIENT.create_collection(name=collection_name)
embedding_func = get_embedding_function(
app.state.config.RAG_EMBEDDING_ENGINE,
app.state.config.RAG_EMBEDDING_MODEL,
app.state.sentence_transformer_ef,
app.state.config.OPENAI_API_KEY,
app.state.config.OPENAI_API_BASE_URL,
app.state.config.RAG_EMBEDDING_OPENAI_BATCH_SIZE,
)
embedding_texts = list(map(lambda x: x.replace("\n", " "), texts))
embeddings = embedding_func(embedding_texts)
for batch in create_batches(
api=CHROMA_CLIENT,
ids=[str(uuid.uuid4()) for _ in texts],
metadatas=metadatas,
embeddings=embeddings,
documents=texts,
):
collection.add(*batch)
return True
except Exception as e:
if e.__class__.__name__ == "UniqueConstraintError":
if VECTOR_DB_CLIENT.has_collection(collection_name=collection_name):
log.info(f"collection {collection_name} already exists")
return True
else:
embedding_function = get_embedding_function(
app.state.config.RAG_EMBEDDING_ENGINE,
app.state.config.RAG_EMBEDDING_MODEL,
app.state.sentence_transformer_ef,
app.state.config.OPENAI_API_KEY,
app.state.config.OPENAI_API_BASE_URL,
app.state.config.RAG_EMBEDDING_OPENAI_BATCH_SIZE,
)
VECTOR_DB_CLIENT.insert(
collection_name=collection_name,
items=[
{
"id": str(uuid.uuid4()),
"text": text,
"vector": embedding_function(text.replace("\n", " ")),
"metadata": metadatas[idx],
}
for idx, text in enumerate(texts)
],
)
return True
except Exception as e:
log.exception(e)
return False
@@ -1021,7 +1136,7 @@ class TikaLoader:
self.file_path = file_path
self.mime_type = mime_type
def load(self) -> List[Document]:
def load(self) -> list[Document]:
with open(self.file_path, "rb") as f:
data = f.read()
@@ -1138,7 +1253,7 @@ def get_loader(filename: str, file_content_type: str, file_path: str):
elif (
file_content_type
== "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
or file_ext in ["doc", "docx"]
or file_ext == "docx"
):
loader = Docx2txtLoader(file_path)
elif file_content_type in [
@@ -1185,7 +1300,7 @@ def store_doc(
f.close()
f = open(file_path, "rb")
if collection_name == None:
if collection_name is None:
collection_name = calculate_sha256(f)[:63]
f.close()
@@ -1238,7 +1353,7 @@ def process_doc(
f = open(file_path, "rb")
collection_name = form_data.collection_name
if collection_name == None:
if collection_name is None:
collection_name = calculate_sha256(f)[:63]
f.close()
@@ -1294,9 +1409,8 @@ def store_text(
form_data: TextRAGForm,
user=Depends(get_verified_user),
):
collection_name = form_data.collection_name
if collection_name == None:
if collection_name is None:
collection_name = calculate_sha256_string(form_data.content)
result = store_text_in_vector_db(
@@ -1339,7 +1453,7 @@ def scan_docs_dir(user=Depends(get_admin_user)):
sanitized_filename = sanitize_filename(filename)
doc = Documents.get_doc_by_name(sanitized_filename)
if doc == None:
if doc is None:
doc = Documents.insert_new_doc(
user.id,
DocumentForm(
@@ -1375,12 +1489,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()
VECTOR_DB_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:
@@ -1404,7 +1518,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):
@@ -1418,7 +1532,7 @@ def reset(user=Depends(get_admin_user)) -> bool:
log.error("Failed to delete %s. Reason: %s" % (file_path, e))
try:
CHROMA_CLIENT.reset()
VECTOR_DB_CLIENT.reset()
except Exception as e:
log.exception(e)
@@ -1,16 +1,16 @@
import logging
from typing import List, Optional
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"])
def search_brave(
api_key: str, query: str, count: int, filter_list: Optional[List[str]] = None
api_key: str, query: str, count: int, filter_list: Optional[list[str]] = None
) -> list[SearchResult]:
"""Search using Brave's Search API and return the results as a list of SearchResult objects.
@@ -1,15 +1,16 @@
import logging
from typing import List, Optional
from apps.rag.search.main import SearchResult, get_filtered_results
from typing import Optional
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"])
def search_duckduckgo(
query: str, count: int, filter_list: Optional[List[str]] = None
query: str, count: int, filter_list: Optional[list[str]] = None
) -> list[SearchResult]:
"""
Search using DuckDuckGo's Search API and return the results as a list of SearchResult objects.
@@ -18,7 +19,7 @@ def search_duckduckgo(
count (int): The number of results to return
Returns:
List[SearchResult]: A list of search results
list[SearchResult]: A list of search results
"""
# Use the DDGS context manager to create a DDGS object
with DDGS() as ddgs:
@@ -1,10 +1,9 @@
import json
import logging
from typing import List, Optional
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"])
@@ -15,7 +14,7 @@ def search_google_pse(
search_engine_id: str,
query: str,
count: int,
filter_list: Optional[List[str]] = None,
filter_list: Optional[list[str]] = None,
) -> list[SearchResult]:
"""Search using Google's Programmable Search Engine API and return the results as a list of SearchResult objects.
@@ -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"])
@@ -17,7 +17,7 @@ def search_jina(query: str, count: int) -> list[SearchResult]:
count (int): The number of results to return
Returns:
List[SearchResult]: A list of search results
list[SearchResult]: A list of search results
"""
jina_search_endpoint = "https://s.jina.ai/"
headers = {
@@ -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
@@ -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]
]
@@ -1,10 +1,9 @@
import logging
from typing import Optional
import requests
from typing import List, Optional
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
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"])
@@ -14,9 +13,9 @@ def search_searxng(
query_url: str,
query: str,
count: int,
filter_list: Optional[List[str]] = None,
filter_list: Optional[list[str]] = None,
**kwargs,
) -> List[SearchResult]:
) -> list[SearchResult]:
"""
Search a SearXNG instance for a given query and return the results as a list of SearchResult objects.
@@ -31,10 +30,10 @@ def search_searxng(
language (str): Language filter for the search results; e.g., "en-US". Defaults to an empty string.
safesearch (int): Safe search filter for safer web results; 0 = off, 1 = moderate, 2 = strict. Defaults to 1 (moderate).
time_range (str): Time range for filtering results by date; e.g., "2023-04-05..today" or "all-time". Defaults to ''.
categories: (Optional[List[str]]): Specific categories within which the search should be performed, defaulting to an empty string if not provided.
categories: (Optional[list[str]]): Specific categories within which the search should be performed, defaulting to an empty string if not provided.
Returns:
List[SearchResult]: A list of SearchResults sorted by relevance score in descending order.
list[SearchResult]: A list of SearchResults sorted by relevance score in descending order.
Raise:
requests.exceptions.RequestException: If a request error occurs during the search process.
@@ -1,17 +1,17 @@
import json
import logging
from typing import List, Optional
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"])
def search_serper(
api_key: str, query: str, count: int, filter_list: Optional[List[str]] = None
api_key: str, query: str, count: int, filter_list: Optional[list[str]] = None
) -> list[SearchResult]:
"""Search using serper.dev's API and return the results as a list of SearchResult objects.
@@ -1,11 +1,10 @@
import json
import logging
from typing import List, Optional
import requests
from typing import Optional
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"])
@@ -19,7 +18,7 @@ def search_serply(
limit: int = 10,
device_type: str = "desktop",
proxy_location: str = "US",
filter_list: Optional[List[str]] = None,
filter_list: Optional[list[str]] = None,
) -> list[SearchResult]:
"""Search using serper.dev's API and return the results as a list of SearchResult objects.
@@ -1,10 +1,9 @@
import json
import logging
from typing import List, Optional
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"])
@@ -14,7 +13,7 @@ def search_serpstack(
api_key: str,
query: str,
count: int,
filter_list: Optional[List[str]] = None,
filter_list: Optional[list[str]] = None,
https_enabled: bool = True,
) -> list[SearchResult]:
"""Search using serpstack.com's and return the results as a list of SearchResult objects.
@@ -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"])
@@ -17,7 +16,7 @@ def search_tavily(api_key: str, query: str, count: int) -> list[SearchResult]:
query (str): The query to search for
Returns:
List[SearchResult]: A list of search results
list[SearchResult]: A list of search results
"""
url = "https://api.tavily.com/search"
data = {"query": query, "api_key": api_key}
File diff suppressed because one or more lines are too long
@@ -1,32 +1,68 @@
import os
import logging
import os
import uuid
from typing import Optional, Union
import requests
from typing import List, Union
from apps.ollama.main import (
generate_ollama_embeddings,
GenerateEmbeddingsForm,
)
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 langchain_core.documents import Document
from open_webui.apps.ollama.main import (
GenerateEmbeddingsForm,
generate_ollama_embeddings,
)
from open_webui.apps.rag.vector.connector import VECTOR_DB_CLIENT
from open_webui.utils.misc import get_last_user_message
from typing import Optional
from open_webui.env import SRC_LOG_LEVELS
from utils.misc import get_last_user_message, add_or_update_system_message
from config import SRC_LOG_LEVELS, CHROMA_CLIENT
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
from typing import Any
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.retrievers import BaseRetriever
class VectorSearchRetriever(BaseRetriever):
collection_name: Any
embedding_function: Any
top_k: int
def _get_relevant_documents(
self,
query: str,
*,
run_manager: CallbackManagerForRetrieverRun,
) -> list[Document]:
result = VECTOR_DB_CLIENT.search(
collection_name=self.collection_name,
vectors=[self.embedding_function(query)],
limit=self.top_k,
)
ids = result.ids[0]
metadatas = result.metadatas[0]
documents = result.documents[0]
results = []
for idx in range(len(ids)):
results.append(
Document(
metadata=metadatas[idx],
page_content=documents[idx],
)
)
return results
def query_doc(
collection_name: str,
query: str,
@@ -34,17 +70,18 @@ def query_doc(
k: int,
):
try:
collection = CHROMA_CLIENT.get_collection(name=collection_name)
query_embeddings = embedding_function(query)
result = collection.query(
query_embeddings=[query_embeddings],
n_results=k,
result = VECTOR_DB_CLIENT.search(
collection_name=collection_name,
vectors=[embedding_function(query)],
limit=k,
)
print("result", result)
log.info(f"query_doc:result {result}")
return result
except Exception as e:
print(e)
raise e
@@ -55,27 +92,25 @@ def query_doc_with_hybrid_search(
k: int,
reranking_function,
r: float,
):
) -> dict:
try:
collection = CHROMA_CLIENT.get_collection(name=collection_name)
documents = collection.get() # get all documents
result = VECTOR_DB_CLIENT.get(collection_name=collection_name)
bm25_retriever = BM25Retriever.from_texts(
texts=documents.get("documents"),
metadatas=documents.get("metadatas"),
texts=result.documents[0],
metadatas=result.metadatas[0],
)
bm25_retriever.k = k
chroma_retriever = ChromaRetriever(
collection=collection,
vector_search_retriever = VectorSearchRetriever(
collection_name=collection_name,
embedding_function=embedding_function,
top_n=k,
top_k=k,
)
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, chroma_retriever], weights=[0.5, 0.5]
retrievers=[bm25_retriever, vector_search_retriever], weights=[0.5, 0.5]
)
compressor = RerankCompressor(
embedding_function=embedding_function,
top_n=k,
@@ -100,7 +135,9 @@ def query_doc_with_hybrid_search(
raise e
def merge_and_sort_query_results(query_results, k, reverse=False):
def merge_and_sort_query_results(
query_results: list[dict], k: int, reverse: bool = False
) -> list[dict]:
# Initialize lists to store combined data
combined_distances = []
combined_documents = []
@@ -142,35 +179,40 @@ def merge_and_sort_query_results(query_results, k, reverse=False):
def query_collection(
collection_names: List[str],
collection_names: list[str],
query: str,
embedding_function,
k: int,
):
) -> dict:
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:
if collection_name:
try:
result = query_doc(
collection_name=collection_name,
query=query,
k=k,
embedding_function=embedding_function,
)
results.append(result.model_dump())
except Exception as e:
log.exception(f"Error when querying the collection: {e}")
else:
pass
return merge_and_sort_query_results(results, k=k)
def query_collection_with_hybrid_search(
collection_names: List[str],
collection_names: list[str],
query: str,
embedding_function,
k: int,
reranking_function,
r: float,
):
) -> dict:
results = []
error = False
for collection_name in collection_names:
try:
result = query_doc_with_hybrid_search(
@@ -182,14 +224,39 @@ def query_collection_with_hybrid_search(
r=r,
)
results.append(result)
except:
pass
except Exception as e:
log.exception(
"Error when querying the collection with " f"hybrid_search: {e}"
)
error = True
if error:
raise Exception(
"Hybrid search failed for all collections. Using Non hybrid search as fallback."
)
return merge_and_sort_query_results(results, k=k, reverse=True)
def rag_template(template: str, context: str, query: str):
template = template.replace("[context]", context)
template = template.replace("[query]", query)
count = template.count("[context]")
assert "[context]" in template, "RAG template does not contain '[context]'"
if "<context>" in context and "</context>" in context:
log.debug(
"WARNING: Potential prompt injection attack: the RAG "
"context contains '<context>' and '</context>'. This might be "
"nothing, or the user might be trying to hack something."
)
if "[query]" in context:
query_placeholder = f"[query-{str(uuid.uuid4())}]"
template = template.replace("[query]", query_placeholder)
template = template.replace("[context]", context)
template = template.replace(query_placeholder, query)
else:
template = template.replace("[context]", context)
template = template.replace("[query]", query)
return template
@@ -257,7 +324,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)
@@ -266,19 +333,27 @@ def get_rag_context(
continue
try:
context = None
if file["type"] == "text":
context = file["content"]
else:
if hybrid_search:
context = query_collection_with_hybrid_search(
collection_names=collection_names,
query=query,
embedding_function=embedding_function,
k=k,
reranking_function=reranking_function,
r=r,
)
else:
try:
context = query_collection_with_hybrid_search(
collection_names=collection_names,
query=query,
embedding_function=embedding_function,
k=k,
reranking_function=reranking_function,
r=r,
)
except Exception as e:
log.debug(
"Error when using hybrid search, using"
" non hybrid search as fallback."
)
if (not hybrid_search) or (context is None):
context = query_collection(
collection_names=collection_names,
query=query,
@@ -287,7 +362,6 @@ def get_rag_context(
)
except Exception as e:
log.exception(e)
context = None
if context:
relevant_contexts.append({**context, "source": file})
@@ -395,52 +469,11 @@ def generate_openai_batch_embeddings(
return None
from typing import Any
from langchain_core.retrievers import BaseRetriever
from langchain_core.callbacks import CallbackManagerForRetrieverRun
class ChromaRetriever(BaseRetriever):
collection: Any
embedding_function: Any
top_n: int
def _get_relevant_documents(
self,
query: str,
*,
run_manager: CallbackManagerForRetrieverRun,
) -> List[Document]:
query_embeddings = self.embedding_function(query)
results = self.collection.query(
query_embeddings=[query_embeddings],
n_results=self.top_n,
)
ids = results["ids"][0]
metadatas = results["metadatas"][0]
documents = results["documents"][0]
results = []
for idx in range(len(ids)):
results.append(
Document(
metadata=metadatas[idx],
page_content=documents[idx],
)
)
return results
import operator
from typing import Optional, Sequence
from langchain_core.documents import BaseDocumentCompressor, Document
from langchain_core.callbacks import Callbacks
from langchain_core.pydantic_v1 import Extra
from langchain_core.documents import BaseDocumentCompressor, Document
class RerankCompressor(BaseDocumentCompressor):
@@ -450,7 +483,7 @@ class RerankCompressor(BaseDocumentCompressor):
r_score: float
class Config:
extra = Extra.forbid
extra = "forbid"
arbitrary_types_allowed = True
def compress_documents(
@@ -0,0 +1,10 @@
from open_webui.apps.rag.vector.dbs.chroma import ChromaClient
from open_webui.apps.rag.vector.dbs.milvus import MilvusClient
from open_webui.config import VECTOR_DB
if VECTOR_DB == "milvus":
VECTOR_DB_CLIENT = MilvusClient()
else:
VECTOR_DB_CLIENT = ChromaClient()
@@ -0,0 +1,122 @@
import chromadb
from chromadb import Settings
from chromadb.utils.batch_utils import create_batches
from typing import Optional
from open_webui.apps.rag.vector.main import VectorItem, SearchResult, GetResult
from open_webui.config import (
CHROMA_DATA_PATH,
CHROMA_HTTP_HOST,
CHROMA_HTTP_PORT,
CHROMA_HTTP_HEADERS,
CHROMA_HTTP_SSL,
CHROMA_TENANT,
CHROMA_DATABASE,
)
class ChromaClient:
def __init__(self):
if CHROMA_HTTP_HOST != "":
self.client = chromadb.HttpClient(
host=CHROMA_HTTP_HOST,
port=CHROMA_HTTP_PORT,
headers=CHROMA_HTTP_HEADERS,
ssl=CHROMA_HTTP_SSL,
tenant=CHROMA_TENANT,
database=CHROMA_DATABASE,
settings=Settings(allow_reset=True, anonymized_telemetry=False),
)
else:
self.client = chromadb.PersistentClient(
path=CHROMA_DATA_PATH,
settings=Settings(allow_reset=True, anonymized_telemetry=False),
tenant=CHROMA_TENANT,
database=CHROMA_DATABASE,
)
def has_collection(self, collection_name: str) -> bool:
# Check if the collection exists based on the collection name.
collections = self.client.list_collections()
return collection_name in [collection.name for collection in collections]
def delete_collection(self, collection_name: str):
# Delete the collection based on the collection name.
return self.client.delete_collection(name=collection_name)
def search(
self, collection_name: str, vectors: list[list[float | int]], limit: int
) -> Optional[SearchResult]:
# Search for the nearest neighbor items based on the vectors and return 'limit' number of results.
collection = self.client.get_collection(name=collection_name)
if collection:
result = collection.query(
query_embeddings=vectors,
n_results=limit,
)
return SearchResult(
**{
"ids": result["ids"],
"distances": result["distances"],
"documents": result["documents"],
"metadatas": result["metadatas"],
}
)
return None
def get(self, collection_name: str) -> Optional[GetResult]:
# Get all the items in the collection.
collection = self.client.get_collection(name=collection_name)
if collection:
result = collection.get()
return GetResult(
**{
"ids": [result["ids"]],
"documents": [result["documents"]],
"metadatas": [result["metadatas"]],
}
)
return None
def insert(self, collection_name: str, items: list[VectorItem]):
# Insert the items into the collection, if the collection does not exist, it will be created.
collection = self.client.get_or_create_collection(name=collection_name)
ids = [item["id"] for item in items]
documents = [item["text"] for item in items]
embeddings = [item["vector"] for item in items]
metadatas = [item["metadata"] for item in items]
for batch in create_batches(
api=self.client,
documents=documents,
embeddings=embeddings,
ids=ids,
metadatas=metadatas,
):
collection.add(*batch)
def upsert(self, collection_name: str, items: list[VectorItem]):
# Update the items in the collection, if the items are not present, insert them. If the collection does not exist, it will be created.
collection = self.client.get_or_create_collection(name=collection_name)
ids = [item["id"] for item in items]
documents = [item["text"] for item in items]
embeddings = [item["vector"] for item in items]
metadatas = [item["metadata"] for item in items]
collection.upsert(
ids=ids, documents=documents, embeddings=embeddings, metadatas=metadatas
)
def delete(self, collection_name: str, ids: list[str]):
# Delete the items from the collection based on the ids.
collection = self.client.get_collection(name=collection_name)
if collection:
collection.delete(ids=ids)
def reset(self):
# Resets the database. This will delete all collections and item entries.
return self.client.reset()
@@ -0,0 +1,205 @@
from pymilvus import MilvusClient as Client
from pymilvus import FieldSchema, DataType
import json
from typing import Optional
from open_webui.apps.rag.vector.main import VectorItem, SearchResult, GetResult
from open_webui.config import (
MILVUS_URI,
)
class MilvusClient:
def __init__(self):
self.collection_prefix = "open_webui"
self.client = Client(uri=MILVUS_URI)
def _result_to_get_result(self, result) -> GetResult:
print(result)
ids = []
documents = []
metadatas = []
for match in result:
_ids = []
_documents = []
_metadatas = []
for item in match:
_ids.append(item.get("id"))
_documents.append(item.get("data", {}).get("text"))
_metadatas.append(item.get("metadata"))
ids.append(_ids)
documents.append(_documents)
metadatas.append(_metadatas)
return GetResult(
**{
"ids": ids,
"documents": documents,
"metadatas": metadatas,
}
)
def _result_to_search_result(self, result) -> SearchResult:
print(result)
ids = []
distances = []
documents = []
metadatas = []
for match in result:
_ids = []
_distances = []
_documents = []
_metadatas = []
for item in match:
_ids.append(item.get("id"))
_distances.append(item.get("distance"))
_documents.append(item.get("entity", {}).get("data", {}).get("text"))
_metadatas.append(item.get("entity", {}).get("metadata"))
ids.append(_ids)
distances.append(_distances)
documents.append(_documents)
metadatas.append(_metadatas)
return SearchResult(
**{
"ids": ids,
"distances": distances,
"documents": documents,
"metadatas": metadatas,
}
)
def _create_collection(self, collection_name: str, dimension: int):
schema = self.client.create_schema(
auto_id=False,
enable_dynamic_field=True,
)
schema.add_field(
field_name="id",
datatype=DataType.VARCHAR,
is_primary=True,
max_length=65535,
)
schema.add_field(
field_name="vector",
datatype=DataType.FLOAT_VECTOR,
dim=dimension,
description="vector",
)
schema.add_field(field_name="data", datatype=DataType.JSON, description="data")
schema.add_field(
field_name="metadata", datatype=DataType.JSON, description="metadata"
)
index_params = self.client.prepare_index_params()
index_params.add_index(
field_name="vector", index_type="HNSW", metric_type="COSINE", params={}
)
self.client.create_collection(
collection_name=f"{self.collection_prefix}_{collection_name}",
schema=schema,
index_params=index_params,
)
def has_collection(self, collection_name: str) -> bool:
# Check if the collection exists based on the collection name.
return self.client.has_collection(
collection_name=f"{self.collection_prefix}_{collection_name}"
)
def delete_collection(self, collection_name: str):
# Delete the collection based on the collection name.
return self.client.drop_collection(
collection_name=f"{self.collection_prefix}_{collection_name}"
)
def search(
self, collection_name: str, vectors: list[list[float | int]], limit: int
) -> Optional[SearchResult]:
# Search for the nearest neighbor items based on the vectors and return 'limit' number of results.
result = self.client.search(
collection_name=f"{self.collection_prefix}_{collection_name}",
data=vectors,
limit=limit,
output_fields=["data", "metadata"],
)
return self._result_to_search_result(result)
def get(self, collection_name: str) -> Optional[GetResult]:
# Get all the items in the collection.
result = self.client.query(
collection_name=f"{self.collection_prefix}_{collection_name}",
filter='id != ""',
)
return self._result_to_get_result([result])
def insert(self, collection_name: str, items: list[VectorItem]):
# Insert the items into the collection, if the collection does not exist, it will be created.
if not self.client.has_collection(
collection_name=f"{self.collection_prefix}_{collection_name}"
):
self._create_collection(
collection_name=collection_name, dimension=len(items[0]["vector"])
)
return self.client.insert(
collection_name=f"{self.collection_prefix}_{collection_name}",
data=[
{
"id": item["id"],
"vector": item["vector"],
"data": {"text": item["text"]},
"metadata": item["metadata"],
}
for item in items
],
)
def upsert(self, collection_name: str, items: list[VectorItem]):
# Update the items in the collection, if the items are not present, insert them. If the collection does not exist, it will be created.
if not self.client.has_collection(
collection_name=f"{self.collection_prefix}_{collection_name}"
):
self._create_collection(
collection_name=collection_name, dimension=len(items[0]["vector"])
)
return self.client.upsert(
collection_name=f"{self.collection_prefix}_{collection_name}",
data=[
{
"id": item["id"],
"vector": item["vector"],
"data": {"text": item["text"]},
"metadata": item["metadata"],
}
for item in items
],
)
def delete(self, collection_name: str, ids: list[str]):
# Delete the items from the collection based on the ids.
return self.client.delete(
collection_name=f"{self.collection_prefix}_{collection_name}",
ids=ids,
)
def reset(self):
# Resets the database. This will delete all collections and item entries.
collection_names = self.client.list_collections()
for collection_name in collection_names:
if collection_name.startswith(self.collection_prefix):
self.client.drop_collection(collection_name=collection_name)
@@ -0,0 +1,19 @@
from pydantic import BaseModel
from typing import Optional, List, Any
class VectorItem(BaseModel):
id: str
text: str
vector: List[float | int]
metadata: Any
class GetResult(BaseModel):
ids: Optional[List[List[str]]]
documents: Optional[List[List[str]]]
metadatas: Optional[List[List[Any]]]
class SearchResult(GetResult):
distances: Optional[List[List[float | int]]]
@@ -1,11 +1,39 @@
import socketio
import asyncio
import socketio
from open_webui.apps.webui.models.users import Users
from open_webui.env import (
ENABLE_WEBSOCKET_SUPPORT,
WEBSOCKET_MANAGER,
WEBSOCKET_REDIS_URL,
)
from open_webui.utils.utils import decode_token
if WEBSOCKET_MANAGER == "redis":
mgr = socketio.AsyncRedisManager(WEBSOCKET_REDIS_URL)
sio = socketio.AsyncServer(
cors_allowed_origins=[],
async_mode="asgi",
transports=(
["polling", "websocket"] if ENABLE_WEBSOCKET_SUPPORT else ["polling"]
),
allow_upgrades=ENABLE_WEBSOCKET_SUPPORT,
always_connect=True,
client_manager=mgr,
)
else:
sio = socketio.AsyncServer(
cors_allowed_origins=[],
async_mode="asgi",
transports=(
["polling", "websocket"] if ENABLE_WEBSOCKET_SUPPORT else ["polling"]
),
allow_upgrades=ENABLE_WEBSOCKET_SUPPORT,
always_connect=True,
)
from apps.webui.models.users import Users
from utils.utils import decode_token
sio = socketio.AsyncServer(cors_allowed_origins=[], async_mode="asgi")
app = socketio.ASGIApp(sio, socketio_path="/ws/socket.io")
# Dictionary to maintain the user pool
@@ -33,7 +61,7 @@ async def connect(sid, environ, auth):
else:
USER_POOL[user.id] = [sid]
print(f"user {user.name}({user.id}) connected with session ID {sid}")
# print(f"user {user.name}({user.id}) connected with session ID {sid}")
await sio.emit("user-count", {"count": len(set(USER_POOL))})
await sio.emit("usage", {"models": get_models_in_use()})
@@ -41,7 +69,7 @@ async def connect(sid, environ, auth):
@sio.on("user-join")
async def user_join(sid, data):
print("user-join", sid, data)
# print("user-join", sid, data)
auth = data["auth"] if "auth" in data else None
if not auth or "token" not in auth:
@@ -61,7 +89,7 @@ async def user_join(sid, data):
else:
USER_POOL[user.id] = [sid]
print(f"user {user.name}({user.id}) connected with session ID {sid}")
# print(f"user {user.name}({user.id}) connected with session ID {sid}")
await sio.emit("user-count", {"count": len(set(USER_POOL))})
@@ -110,7 +138,7 @@ async def remove_after_timeout(sid, model_id):
try:
await asyncio.sleep(TIMEOUT_DURATION)
if model_id in USAGE_POOL:
print(USAGE_POOL[model_id]["sids"])
# print(USAGE_POOL[model_id]["sids"])
USAGE_POOL[model_id]["sids"].remove(sid)
USAGE_POOL[model_id]["sids"] = list(set(USAGE_POOL[model_id]["sids"]))
@@ -137,7 +165,8 @@ async def disconnect(sid):
await sio.emit("user-count", {"count": len(USER_POOL)})
else:
print(f"Unknown session ID {sid} disconnected")
pass
# print(f"Unknown session ID {sid} disconnected")
def get_event_emitter(request_info):
@@ -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:
@@ -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
@@ -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
@@ -1,64 +1,70 @@
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 (
from open_webui.apps.webui.utils import load_function_module_by_id
from open_webui.config import (
ADMIN_EMAIL,
CORS_ALLOW_ORIGIN,
DEFAULT_MODELS,
DEFAULT_PROMPT_SUGGESTIONS,
DEFAULT_USER_ROLE,
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,
)
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 (
openai_chat_chunk_message_template,
openai_chat_completion_message_template,
apply_model_params_to_body,
)
from open_webui.utils.payload import (
apply_model_params_to_body_openai,
apply_model_system_prompt_to_body,
)
from config import (
SHOW_ADMIN_DETAILS,
ADMIN_EMAIL,
WEBUI_AUTH,
DEFAULT_MODELS,
DEFAULT_PROMPT_SUGGESTIONS,
DEFAULT_USER_ROLE,
ENABLE_SIGNUP,
ENABLE_LOGIN_FORM,
USER_PERMISSIONS,
WEBHOOK_URL,
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
WEBUI_AUTH_TRUSTED_NAME_HEADER,
JWT_EXPIRES_IN,
WEBUI_BANNERS,
ENABLE_COMMUNITY_SHARING,
AppConfig,
OAUTH_USERNAME_CLAIM,
OAUTH_PICTURE_CLAIM,
)
from apps.socket.main import get_event_call, get_event_emitter
import inspect
import json
from typing import Iterator, Generator, AsyncGenerator
from pydantic import BaseModel
from open_webui.utils.tools import get_tools
app = FastAPI()
origins = ["*"]
log = logging.getLogger(__name__)
app.state.config = AppConfig()
@@ -81,9 +87,11 @@ app.state.config.WEBHOOK_URL = WEBHOOK_URL
app.state.config.BANNERS = WEBUI_BANNERS
app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING
app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING
app.state.config.OAUTH_USERNAME_CLAIM = OAUTH_USERNAME_CLAIM
app.state.config.OAUTH_PICTURE_CLAIM = OAUTH_PICTURE_CLAIM
app.state.config.OAUTH_EMAIL_CLAIM = OAUTH_EMAIL_CLAIM
app.state.MODELS = {}
app.state.TOOLS = {}
@@ -91,7 +99,7 @@ app.state.FUNCTIONS = {}
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_origins=CORS_ALLOW_ORIGIN,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@@ -148,29 +156,33 @@ async def get_pipe_models():
# Check if function is a manifold
if hasattr(function_module, "pipes"):
manifold_pipes = []
sub_pipes = []
# Check if pipes is a function or a list
if callable(function_module.pipes):
manifold_pipes = function_module.pipes()
else:
manifold_pipes = function_module.pipes
for p in manifold_pipes:
manifold_pipe_id = f'{pipe.id}.{p["id"]}'
manifold_pipe_name = p["name"]
try:
if callable(function_module.pipes):
sub_pipes = function_module.pipes()
else:
sub_pipes = function_module.pipes
except Exception as e:
log.exception(e)
sub_pipes = []
print(sub_pipes)
for p in sub_pipes:
sub_pipe_id = f'{pipe.id}.{p["id"]}'
sub_pipe_name = p["name"]
if hasattr(function_module, "name"):
manifold_pipe_name = f"{function_module.name}{manifold_pipe_name}"
sub_pipe_name = f"{function_module.name}{sub_pipe_name}"
pipe_flag = {"type": pipe.type}
if hasattr(function_module, "ChatValves"):
pipe_flag["valves_spec"] = function_module.ChatValves.schema()
pipe_models.append(
{
"id": manifold_pipe_id,
"name": manifold_pipe_name,
"id": sub_pipe_id,
"name": sub_pipe_name,
"object": "model",
"created": pipe.created_at,
"owned_by": "openai",
@@ -179,8 +191,6 @@ async def get_pipe_models():
)
else:
pipe_flag = {"type": "pipe"}
if hasattr(function_module, "ChatValves"):
pipe_flag["valves_spec"] = function_module.ChatValves.schema()
pipe_models.append(
{
@@ -239,40 +249,40 @@ 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", None)
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 = []
__event_emitter__ = None
__event_call__ = None
@@ -284,28 +294,43 @@ async def generate_function_chat_completion(form_data, user):
__event_call__ = get_event_call(metadata)
__task__ = metadata.get("task", None)
extra_params = {
"__event_emitter__": __event_emitter__,
"__event_call__": __event_call__,
"__task__": __task__,
"__files__": files,
"__user__": {
"id": user.id,
"email": user.email,
"name": user.name,
"role": user.role,
},
}
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:
form_data["model"] = model_info.base_model_id
params = model_info.params.model_dump()
form_data = apply_model_params_to_body(params, form_data)
form_data = apply_model_params_to_body_openai(params, form_data)
form_data = apply_model_system_prompt_to_body(params, form_data, user)
pipe_id = get_pipe_id(form_data)
function_module = get_function_module(pipe_id)
pipe = function_module.pipe
params = get_function_params(
function_module,
form_data,
user,
{
"__event_emitter__": __event_emitter__,
"__event_call__": __event_call__,
"__task__": __task__,
},
)
params = get_function_params(function_module, form_data, user, extra_params)
if form_data["stream"]:
@@ -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):
@@ -140,7 +135,7 @@ class AuthsTable:
return None
else:
return None
except:
except Exception:
return None
def authenticate_user_by_api_key(self, api_key: str) -> Optional[UserModel]:
@@ -152,7 +147,7 @@ class AuthsTable:
try:
user = Users.get_user_by_api_key(api_key)
return user if user else None
except:
except Exception:
return False
def authenticate_user_by_trusted_header(self, email: str) -> Optional[UserModel]:
@@ -163,7 +158,7 @@ class AuthsTable:
if auth:
user = Users.get_user_by_id(auth.id)
return user
except:
except Exception:
return None
def update_user_password_by_id(self, id: str, new_password: str) -> bool:
@@ -174,7 +169,7 @@ class AuthsTable:
)
db.commit()
return True if result == 1 else False
except:
except Exception:
return False
def update_email_by_id(self, id: str, email: str) -> bool:
@@ -183,13 +178,12 @@ class AuthsTable:
result = db.query(Auth).filter_by(id=id).update({"email": email})
db.commit()
return True if result == 1 else False
except:
except Exception:
return False
def delete_auth_by_id(self, id: str) -> bool:
try:
with get_db() as db:
# Delete User
result = Users.delete_user_by_id(id)
@@ -200,7 +194,7 @@ class AuthsTable:
return True
else:
return False
except:
except Exception:
return False
@@ -1,14 +1,11 @@
from pydantic import BaseModel, ConfigDict
from typing import List, 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)
@@ -164,18 +156,17 @@ class ChatTable:
db.refresh(chat)
return self.get_chat_by_id(chat.share_id)
except:
except Exception:
return None
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()
return True
except:
except Exception:
return False
def update_chat_share_id_by_id(
@@ -183,25 +174,23 @@ class ChatTable:
) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.get(Chat, id)
chat.share_id = share_id
db.commit()
db.refresh(chat)
return ChatModel.model_validate(chat)
except:
except Exception:
return None
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()
db.refresh(chat)
return ChatModel.model_validate(chat)
except:
except Exception:
return None
def archive_all_chats_by_user_id(self, user_id: str) -> bool:
@@ -210,14 +199,13 @@ class ChatTable:
db.query(Chat).filter_by(user_id=user_id).update({"archived": True})
db.commit()
return True
except:
except Exception:
return False
def get_archived_chat_list_by_user_id(
self, user_id: str, skip: int = 0, limit: int = 50
) -> List[ChatModel]:
) -> list[ChatModel]:
with get_db() as db:
all_chats = (
db.query(Chat)
.filter_by(user_id=user_id, archived=True)
@@ -233,7 +221,7 @@ class ChatTable:
include_archived: bool = False,
skip: int = 0,
limit: int = 50,
) -> List[ChatModel]:
) -> list[ChatModel]:
with get_db() as db:
query = db.query(Chat).filter_by(user_id=user_id)
if not include_archived:
@@ -249,22 +237,25 @@ class ChatTable:
self,
user_id: str,
include_archived: bool = False,
skip: int = 0,
limit: int = -1,
) -> List[ChatTitleIdResponse]:
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(
@@ -279,8 +270,8 @@ class ChatTable:
]
def get_chat_list_by_chat_ids(
self, chat_ids: List[str], skip: int = 0, limit: int = 50
) -> List[ChatModel]:
self, chat_ids: list[str], skip: int = 0, limit: int = 50
) -> list[ChatModel]:
with get_db() as db:
all_chats = (
db.query(Chat)
@@ -294,37 +285,33 @@ 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:
except Exception:
return None
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:
except Exception:
return None
def get_chats(self, skip: int = 0, limit: int = 50) -> List[ChatModel]:
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)
@@ -332,9 +319,8 @@ class ChatTable:
)
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_chats_by_user_id(self, user_id: str) -> List[ChatModel]:
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)
@@ -342,9 +328,8 @@ class ChatTable:
)
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_archived_chats_by_user_id(self, user_id: str) -> List[ChatModel]:
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,44 +340,38 @@ 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()
return True and self.delete_shared_chat_by_chat_id(id)
except:
except Exception:
return False
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()
return True and self.delete_shared_chat_by_chat_id(id)
except:
except Exception:
return False
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()
db.commit()
return True
except:
except Exception:
return False
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]
@@ -400,7 +379,7 @@ class ChatTable:
db.commit()
return True
except:
except Exception:
return False
@@ -1,15 +1,12 @@
from pydantic import BaseModel, ConfigDict
from typing import List, 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(),
@@ -93,21 +88,19 @@ class DocumentsTable:
return DocumentModel.model_validate(result)
else:
return None
except:
except Exception:
return None
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:
except Exception:
return None
def get_docs(self) -> List[DocumentModel]:
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,11 +147,10 @@ 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
except:
except Exception:
return False
@@ -1,15 +1,11 @@
from pydantic import BaseModel, ConfigDict
from typing import List, 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,40 +80,41 @@ 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)
except:
except Exception:
return None
def get_files(self) -> List[FileModel]:
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()
return True
except:
except Exception:
return False
def delete_all_files(self) -> bool:
with get_db() as db:
try:
db.query(File).delete()
db.commit()
return True
except:
except Exception:
return False
@@ -1,18 +1,12 @@
from pydantic import BaseModel, ConfigDict
from typing import List, 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,15 +111,13 @@ 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:
except Exception:
return None
def get_functions(self, active_only=False) -> List[FunctionModel]:
def get_functions(self, active_only=False) -> list[FunctionModel]:
with get_db() as db:
if active_only:
return [
FunctionModel.model_validate(function)
@@ -141,9 +131,8 @@ class FunctionsTable:
def get_functions_by_type(
self, type: str, active_only=False
) -> List[FunctionModel]:
) -> list[FunctionModel]:
with get_db() as db:
if active_only:
return [
FunctionModel.model_validate(function)
@@ -157,9 +146,8 @@ class FunctionsTable:
for function in db.query(Function).filter_by(type=type).all()
]
def get_global_filter_functions(self) -> List[FunctionModel]:
def get_global_filter_functions(self) -> list[FunctionModel]:
with get_db() as db:
return [
FunctionModel.model_validate(function)
for function in db.query(Function)
@@ -167,7 +155,7 @@ class FunctionsTable:
.all()
]
def get_global_action_functions(self) -> List[FunctionModel]:
def get_global_action_functions(self) -> list[FunctionModel]:
with get_db() as db:
return [
FunctionModel.model_validate(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
@@ -198,13 +184,12 @@ class FunctionsTable:
db.commit()
db.refresh(function)
return self.get_function_by_id(id)
except:
except Exception:
return None
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(
{
@@ -256,12 +239,11 @@ class FunctionsTable:
)
db.commit()
return self.get_function_by_id(id)
except:
except Exception:
return None
def deactivate_all_functions(self) -> Optional[bool]:
with get_db() as db:
try:
db.query(Function).update(
{
@@ -271,7 +253,7 @@ class FunctionsTable:
)
db.commit()
return True
except:
except Exception:
return None
def delete_function_by_id(self, id: str) -> bool:
@@ -281,7 +263,7 @@ class FunctionsTable:
db.commit()
return True
except:
except Exception:
return False
@@ -1,12 +1,10 @@
from pydantic import BaseModel, ConfigDict
from typing import List, 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,75 +69,68 @@ 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())}
)
db.commit()
return self.get_memory_by_id(id)
except:
except Exception:
return None
def get_memories(self) -> List[MemoryModel]:
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]
except:
except Exception:
return None
def get_memories_by_user_id(self, user_id: str) -> List[MemoryModel]:
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]
except:
except Exception:
return None
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)
except:
except Exception:
return None
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()
return True
except:
except Exception:
return False
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()
return True
except:
except Exception:
return False
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()
return True
except:
except Exception:
return False
@@ -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"])
@@ -137,7 +134,7 @@ class ModelsTable:
print(e)
return None
def get_all_models(self) -> List[ModelModel]:
def get_all_models(self) -> list[ModelModel]:
with get_db() as db:
return [ModelModel.model_validate(model) for model in db.query(Model).all()]
@@ -146,7 +143,7 @@ class ModelsTable:
with get_db() as db:
model = db.get(Model, id)
return ModelModel.model_validate(model)
except:
except Exception:
return None
def update_model_by_id(self, id: str, model: ModelForm) -> Optional[ModelModel]:
@@ -175,7 +172,7 @@ class ModelsTable:
db.commit()
return True
except:
except Exception:
return False
@@ -1,12 +1,9 @@
from pydantic import BaseModel, ConfigDict
from typing import List, 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,21 +65,19 @@ 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:
except Exception:
return None
def get_prompts(self) -> List[PromptModel]:
def get_prompts(self) -> list[PromptModel]:
with get_db() as db:
return [
PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all()
]
@@ -94,25 +87,23 @@ 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
prompt.timestamp = int(time.time())
db.commit()
return PromptModel.model_validate(prompt)
except:
except Exception:
return None
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()
return True
except:
except Exception:
return False
@@ -1,16 +1,12 @@
from pydantic import BaseModel, ConfigDict
from typing import List, Optional
import json
import uuid
import time
import logging
import time
import uuid
from typing import Optional
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"])
@@ -69,18 +65,16 @@ class ChatIdTagForm(BaseModel):
class TagChatIdsResponse(BaseModel):
chat_ids: List[str]
chat_ids: list[str]
class ChatTagsResponse(BaseModel):
tags: List[str]
tags: list[str]
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,14 +96,14 @@ 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(
self, user_id: str, form_data: ChatIdTagForm
) -> Optional[ChatIdTagModel]:
tag = self.get_tag_by_name_and_user_id(form_data.tag_name, user_id)
if tag == None:
if tag is None:
tag = self.insert_new_tag(form_data.tag_name, user_id)
id = str(uuid.uuid4())
@@ -132,10 +126,10 @@ class TagTable:
return ChatIdTagModel.model_validate(result)
else:
return None
except:
except Exception:
return None
def get_tags_by_user_id(self, user_id: str) -> List[TagModel]:
def get_tags_by_user_id(self, user_id: str) -> list[TagModel]:
with get_db() as db:
tag_names = [
chat_id_tag.tag_name
@@ -159,9 +153,8 @@ class TagTable:
def get_tags_by_chat_id_and_user_id(
self, chat_id: str, user_id: str
) -> List[TagModel]:
) -> list[TagModel]:
with get_db() as db:
tag_names = [
chat_id_tag.tag_name
for chat_id_tag in (
@@ -184,9 +177,8 @@ class TagTable:
def get_chat_ids_by_tag_name_and_user_id(
self, tag_name: str, user_id: str
) -> List[ChatIdTagModel]:
) -> 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)
@@ -1,17 +1,12 @@
from pydantic import BaseModel, ConfigDict
from typing import List, 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"])
@@ -45,7 +40,7 @@ class ToolModel(BaseModel):
user_id: str
name: str
content: str
specs: List[dict]
specs: list[dict]
meta: ToolMeta
updated_at: int # timestamp in epoch
created_at: int # timestamp in epoch
@@ -79,13 +74,10 @@ class ToolValves(BaseModel):
class ToolsTable:
def insert_new_tool(
self, user_id: str, form_data: ToolForm, specs: List[dict]
self, user_id: str, form_data: ToolForm, specs: list[dict]
) -> Optional[ToolModel]:
with get_db() as db:
tool = ToolModel(
**{
**form_data.model_dump(),
@@ -112,20 +104,18 @@ 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:
except Exception:
return None
def get_tools(self) -> List[ToolModel]:
def get_tools(self) -> list[ToolModel]:
with get_db() as db:
return [ToolModel.model_validate(tool) for tool in db.query(Tool).all()]
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,13 +125,12 @@ 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())}
)
db.commit()
return self.get_tool_by_id(id)
except:
except Exception:
return None
def get_user_valves_by_id_and_user_id(
@@ -196,7 +185,7 @@ class ToolsTable:
tool = db.query(Tool).get(id)
db.refresh(tool)
return ToolModel.model_validate(tool)
except:
except Exception:
return None
def delete_tool_by_id(self, id: str) -> bool:
@@ -206,7 +195,7 @@ class ToolsTable:
db.commit()
return True
except:
except Exception:
return False
@@ -1,13 +1,10 @@
from pydantic import BaseModel, ConfigDict, parse_obj_as
from typing import List, Union, Optional
import time
from typing import Optional
from sqlalchemy import String, Column, BigInteger, Text
from utils.misc import get_gravatar_url
from apps.webui.internal.db import Base, JSONField, Session, 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
@@ -78,7 +75,6 @@ class UserUpdateForm(BaseModel):
class UsersTable:
def insert_new_user(
self,
id: str,
@@ -116,37 +112,34 @@ 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]:
try:
with get_db() as db:
user = db.query(User).filter_by(api_key=api_key).first()
return UserModel.model_validate(user)
except:
except Exception:
return None
def get_user_by_email(self, email: str) -> Optional[UserModel]:
try:
with get_db() as db:
user = db.query(User).filter_by(email=email).first()
return UserModel.model_validate(user)
except:
except Exception:
return None
def get_user_by_oauth_sub(self, sub: str) -> Optional[UserModel]:
try:
with get_db() as db:
user = db.query(User).filter_by(oauth_sub=sub).first()
return UserModel.model_validate(user)
except:
except Exception:
return None
def get_users(self, skip: int = 0, limit: int = 50) -> List[UserModel]:
def get_users(self, skip: int = 0, limit: int = 50) -> list[UserModel]:
with get_db() as db:
users = (
db.query(User)
@@ -164,7 +157,7 @@ class UsersTable:
with get_db() as db:
user = db.query(User).order_by(User.created_at).first()
return UserModel.model_validate(user)
except:
except Exception:
return None
def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]:
@@ -174,7 +167,7 @@ class UsersTable:
db.commit()
user = db.query(User).filter_by(id=id).first()
return UserModel.model_validate(user)
except:
except Exception:
return None
def update_user_profile_image_url_by_id(
@@ -189,13 +182,12 @@ class UsersTable:
user = db.query(User).filter_by(id=id).first()
return UserModel.model_validate(user)
except:
except Exception:
return None
def update_user_last_active_by_id(self, id: str) -> Optional[UserModel]:
try:
with get_db() as db:
db.query(User).filter_by(id=id).update(
{"last_active_at": int(time.time())}
)
@@ -203,7 +195,7 @@ class UsersTable:
user = db.query(User).filter_by(id=id).first()
return UserModel.model_validate(user)
except:
except Exception:
return None
def update_user_oauth_sub_by_id(
@@ -216,7 +208,7 @@ class UsersTable:
user = db.query(User).filter_by(id=id).first()
return UserModel.model_validate(user)
except:
except Exception:
return None
def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]:
@@ -228,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:
@@ -245,7 +237,7 @@ class UsersTable:
return True
else:
return False
except:
except Exception:
return False
def update_user_api_key_by_id(self, id: str, api_key: str) -> str:
@@ -254,7 +246,7 @@ class UsersTable:
result = db.query(User).filter_by(id=id).update({"api_key": api_key})
db.commit()
return True if result == 1 else False
except:
except Exception:
return False
def get_user_api_key_by_id(self, id: str) -> Optional[str]:
@@ -262,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
@@ -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
or not request.app.state.config.ENABLE_LOGIN_FORM
or not 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(
@@ -352,6 +346,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)):
"DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE,
"JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN,
"ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING,
"ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING,
}
@@ -361,6 +356,7 @@ class AdminConfig(BaseModel):
DEFAULT_USER_ROLE: str
JWT_EXPIRES_IN: str
ENABLE_COMMUNITY_SHARING: bool
ENABLE_MESSAGE_RATING: bool
@router.post("/admin/config")
@@ -382,6 +378,7 @@ async def update_admin_config(
request.app.state.config.ENABLE_COMMUNITY_SHARING = (
form_data.ENABLE_COMMUNITY_SHARING
)
request.app.state.config.ENABLE_MESSAGE_RATING = form_data.ENABLE_MESSAGE_RATING
return {
"SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS,
@@ -389,6 +386,7 @@ async def update_admin_config(
"DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE,
"JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN,
"ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING,
"ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING,
}
@@ -1,34 +1,25 @@
from fastapi import Depends, Request, HTTPException, status
from datetime import datetime, timedelta
from typing import List, 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"])
@@ -40,8 +31,8 @@ router = APIRouter()
############################
@router.get("/", response_model=List[ChatTitleIdResponse])
@router.get("/list", response_model=List[ChatTitleIdResponse])
@router.get("/", response_model=list[ChatTitleIdResponse])
@router.get("/list", response_model=list[ChatTitleIdResponse])
async def get_session_user_chat_list(
user=Depends(get_verified_user), page: Optional[int] = None
):
@@ -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"]
@@ -80,7 +70,7 @@ async def delete_all_user_chats(request: Request, user=Depends(get_verified_user
############################
@router.get("/list/user/{user_id}", response_model=List[ChatTitleIdResponse])
@router.get("/list/user/{user_id}", response_model=list[ChatTitleIdResponse])
async def get_user_chat_list_by_user_id(
user_id: str,
user=Depends(get_admin_user),
@@ -119,7 +109,7 @@ async def create_new_chat(form_data: ChatForm, user=Depends(get_verified_user)):
############################
@router.get("/all", response_model=List[ChatResponse])
@router.get("/all", response_model=list[ChatResponse])
async def get_user_chats(user=Depends(get_verified_user)):
return [
ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
@@ -132,7 +122,7 @@ async def get_user_chats(user=Depends(get_verified_user)):
############################
@router.get("/all/archived", response_model=List[ChatResponse])
@router.get("/all/archived", response_model=list[ChatResponse])
async def get_user_archived_chats(user=Depends(get_verified_user)):
return [
ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
@@ -145,7 +135,7 @@ async def get_user_archived_chats(user=Depends(get_verified_user)):
############################
@router.get("/all/db", response_model=List[ChatResponse])
@router.get("/all/db", response_model=list[ChatResponse])
async def get_all_user_chats_in_db(user=Depends(get_admin_user)):
if not ENABLE_ADMIN_EXPORT:
raise HTTPException(
@@ -163,7 +153,7 @@ async def get_all_user_chats_in_db(user=Depends(get_admin_user)):
############################
@router.get("/archived", response_model=List[ChatTitleIdResponse])
@router.get("/archived", response_model=list[ChatTitleIdResponse])
async def get_archived_session_user_chat_list(
user=Depends(get_verified_user), skip: int = 0, limit: int = 50
):
@@ -216,11 +206,10 @@ class TagNameForm(BaseModel):
limit: Optional[int] = 50
@router.post("/tags", response_model=List[ChatTitleIdResponse])
@router.post("/tags", response_model=list[ChatTitleIdResponse])
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(
@@ -241,7 +230,7 @@ async def get_user_chat_list_by_tag_name(
############################
@router.get("/tags/all", response_model=List[TagModel])
@router.get("/tags/all", response_model=list[TagModel])
async def get_all_tags(user=Depends(get_verified_user)):
try:
tags = Tags.get_tags_by_user_id(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,
@@ -417,7 +404,7 @@ async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user)):
############################
@router.get("/{id}/tags", response_model=List[TagModel])
@router.get("/{id}/tags", response_model=list[TagModel])
async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user)):
tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id)
@@ -1,40 +1,50 @@
from fastapi import Response, Request
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import List, 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
class PromptSuggestion(BaseModel):
title: List[str]
title: list[str]
content: str
class SetDefaultSuggestionsForm(BaseModel):
suggestions: List[PromptSuggestion]
suggestions: list[PromptSuggestion]
############################
@@ -50,7 +60,7 @@ async def set_global_default_models(
return request.app.state.config.DEFAULT_MODELS
@router.post("/default/suggestions", response_model=List[PromptSuggestion])
@router.post("/default/suggestions", response_model=list[PromptSuggestion])
async def set_global_default_suggestions(
request: Request,
form_data: SetDefaultSuggestionsForm,
@@ -67,10 +77,10 @@ async def set_global_default_suggestions(
class SetBannersForm(BaseModel):
banners: List[BannerModel]
banners: list[BannerModel]
@router.post("/banners", response_model=List[BannerModel])
@router.post("/banners", response_model=list[BannerModel])
async def set_banners(
request: Request,
form_data: SetBannersForm,
@@ -81,7 +91,7 @@ async def set_banners(
return request.app.state.config.BANNERS
@router.get("/banners", response_model=List[BannerModel])
@router.get("/banners", response_model=list[BannerModel])
async def get_banners(
request: Request,
user=Depends(get_verified_user),
@@ -1,21 +1,16 @@
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import List, 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()
@@ -24,7 +19,7 @@ router = APIRouter()
############################
@router.get("/", response_model=List[DocumentResponse])
@router.get("/", response_model=list[DocumentResponse])
async def get_documents(user=Depends(get_verified_user)):
docs = [
DocumentResponse(
@@ -46,7 +41,7 @@ async def get_documents(user=Depends(get_verified_user)):
@router.post("/create", response_model=Optional[DocumentResponse])
async def create_new_doc(form_data: DocumentForm, user=Depends(get_admin_user)):
doc = Documents.get_doc_by_name(form_data.name)
if doc == None:
if doc is None:
doc = Documents.insert_new_doc(user.id, form_data)
if doc:
@@ -102,7 +97,7 @@ class TagItem(BaseModel):
class TagDocumentForm(BaseModel):
name: str
tags: List[dict]
tags: list[dict]
@router.post("/doc/tags", response_model=Optional[DocumentResponse])
@@ -1,42 +1,17 @@
from fastapi import (
Depends,
FastAPI,
HTTPException,
status,
Request,
UploadFile,
File,
Form,
)
from datetime import datetime, timedelta
from typing import List, 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"])
@@ -104,9 +79,12 @@ def upload_file(file: UploadFile = File(...), user=Depends(get_verified_user)):
############################
@router.get("/", response_model=List[FileModel])
@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"}
@@ -1,27 +1,18 @@
from fastapi import Depends, FastAPI, HTTPException, status, Request
from datetime import datetime, timedelta
from typing import List, 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, replace_imports
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()
@@ -30,7 +21,7 @@ router = APIRouter()
############################
@router.get("/", response_model=List[FunctionResponse])
@router.get("/", response_model=list[FunctionResponse])
async def get_functions(user=Depends(get_verified_user)):
return Functions.get_functions()
@@ -40,7 +31,7 @@ async def get_functions(user=Depends(get_verified_user)):
############################
@router.get("/export", response_model=List[FunctionModel])
@router.get("/export", response_model=list[FunctionModel])
async def get_functions(user=Depends(get_admin_user)):
return Functions.get_functions()
@@ -63,14 +54,12 @@ async def create_new_function(
form_data.id = form_data.id.lower()
function = Functions.get_function_by_id(form_data.id)
if function == None:
function_path = os.path.join(FUNCTIONS_DIR, f"{form_data.id}.py")
if function is None:
try:
with open(function_path, "w") as function_file:
function_file.write(form_data.content)
form_data.content = replace_imports(form_data.content)
function_module, function_type, frontmatter = load_function_module_by_id(
form_data.id
form_data.id,
content=form_data.content,
)
form_data.meta.manifest = frontmatter
@@ -183,13 +172,11 @@ async def toggle_global_by_id(id: str, user=Depends(get_admin_user)):
async def update_function_by_id(
request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user)
):
function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py")
try:
with open(function_path, "w") as function_file:
function_file.write(form_data.content)
function_module, function_type, frontmatter = load_function_module_by_id(id)
form_data.content = replace_imports(form_data.content)
function_module, function_type, frontmatter = load_function_module_by_id(
id, content=form_data.content
)
form_data.meta.manifest = frontmatter
FUNCTIONS = request.app.state.FUNCTIONS
@@ -231,13 +218,6 @@ async def delete_function_by_id(
if id in FUNCTIONS:
del FUNCTIONS[id]
# delete the function file
function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py")
try:
os.remove(function_path)
except:
pass
return result
@@ -304,7 +284,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:
@@ -1,18 +1,13 @@
from fastapi import Response, Request
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import List, Union, Optional
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
import logging
from typing import Optional
from apps.webui.models.memories import Memories, MemoryModel
from open_webui.apps.webui.models.memories import Memories, MemoryModel
from open_webui.apps.rag.vector.connector import VECTOR_DB_CLIENT
from open_webui.utils.utils import get_verified_user
from open_webui.env import SRC_LOG_LEVELS
from utils.utils import get_verified_user
from constants import ERROR_MESSAGES
from config import SRC_LOG_LEVELS, CHROMA_CLIENT
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -30,7 +25,7 @@ async def get_embeddings(request: Request):
############################
@router.get("/", response_model=List[MemoryModel])
@router.get("/", response_model=list[MemoryModel])
async def get_memories(user=Depends(get_verified_user)):
return Memories.get_memories_by_user_id(user.id)
@@ -55,47 +50,22 @@ async def add_memory(
user=Depends(get_verified_user),
):
memory = Memories.insert_new_memory(user.id, form_data.content)
memory_embedding = request.app.state.EMBEDDING_FUNCTION(memory.content)
collection = CHROMA_CLIENT.get_or_create_collection(name=f"user-memory-{user.id}")
collection.upsert(
documents=[memory.content],
ids=[memory.id],
embeddings=[memory_embedding],
metadatas=[{"created_at": memory.created_at}],
VECTOR_DB_CLIENT.upsert(
collection_name=f"user-memory-{user.id}",
items=[
{
"id": memory.id,
"text": memory.content,
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
"metadata": {"created_at": memory.created_at},
}
],
)
return memory
@router.post("/{memory_id}/update", response_model=Optional[MemoryModel])
async def update_memory_by_id(
memory_id: str,
request: Request,
form_data: MemoryUpdateModel,
user=Depends(get_verified_user),
):
memory = Memories.update_memory_by_id(memory_id, form_data.content)
if memory is None:
raise HTTPException(status_code=404, detail="Memory not found")
if form_data.content is not None:
memory_embedding = request.app.state.EMBEDDING_FUNCTION(form_data.content)
collection = CHROMA_CLIENT.get_or_create_collection(
name=f"user-memory-{user.id}"
)
collection.upsert(
documents=[form_data.content],
ids=[memory.id],
embeddings=[memory_embedding],
metadatas=[
{"created_at": memory.created_at, "updated_at": memory.updated_at}
],
)
return memory
############################
# QueryMemory
############################
@@ -110,12 +80,10 @@ class QueryMemoryForm(BaseModel):
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
results = VECTOR_DB_CLIENT.search(
collection_name=f"user-memory-{user.id}",
vectors=[request.app.state.EMBEDDING_FUNCTION(form_data.content)],
limit=form_data.k,
)
return results
@@ -124,21 +92,29 @@ async def query_memory(
############################
# ResetMemoryFromVectorDB
############################
@router.get("/reset", response_model=bool)
@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}")
VECTOR_DB_CLIENT.delete_collection(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],
)
VECTOR_DB_CLIENT.upsert(
collection_name=f"user-memory-{user.id}",
items=[
{
"id": memory.id,
"text": memory.content,
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
"metadata": {
"created_at": memory.created_at,
"updated_at": memory.updated_at,
},
}
for memory in memories
],
)
return True
@@ -147,13 +123,13 @@ async def reset_memory_from_vector_db(
############################
@router.delete("/user", response_model=bool)
@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}")
VECTOR_DB_CLIENT.delete_collection(f"user-memory-{user.id}")
except Exception as e:
log.error(e)
return True
@@ -161,6 +137,41 @@ async def delete_memory_by_user_id(user=Depends(get_verified_user)):
return False
############################
# UpdateMemoryById
############################
@router.post("/{memory_id}/update", response_model=Optional[MemoryModel])
async def update_memory_by_id(
memory_id: str,
request: Request,
form_data: MemoryUpdateModel,
user=Depends(get_verified_user),
):
memory = Memories.update_memory_by_id(memory_id, form_data.content)
if memory is None:
raise HTTPException(status_code=404, detail="Memory not found")
if form_data.content is not None:
VECTOR_DB_CLIENT.upsert(
collection_name=f"user-memory-{user.id}",
items=[
{
"id": memory.id,
"text": memory.content,
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
"metadata": {
"created_at": memory.created_at,
"updated_at": memory.updated_at,
},
}
],
)
return memory
############################
# DeleteMemoryById
############################
@@ -171,10 +182,9 @@ async def delete_memory_by_id(memory_id: str, user=Depends(get_verified_user)):
result = Memories.delete_memory_by_id_and_user_id(memory_id, user.id)
if result:
collection = CHROMA_CLIENT.get_or_create_collection(
name=f"user-memory-{user.id}"
VECTOR_DB_CLIENT.delete(
collection_name=f"user-memory-{user.id}", ids=[memory_id]
)
collection.delete(ids=[memory_id])
return True
return False
@@ -1,15 +1,14 @@
from fastapi import Depends, FastAPI, HTTPException, status, Request
from datetime import datetime, timedelta
from typing import List, 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()
@@ -18,9 +17,19 @@ router = APIRouter()
###########################
@router.get("/", response_model=List[ModelResponse])
async def get_models(user=Depends(get_verified_user)):
return Models.get_all_models()
@router.get("/", response_model=list[ModelResponse])
async def get_models(id: Optional[str] = None, user=Depends(get_verified_user)):
if id:
model = Models.get_model_by_id(id)
if model:
return [model]
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.NOT_FOUND,
)
else:
return Models.get_all_models()
############################
@@ -51,24 +60,6 @@ async def add_new_model(
)
############################
# GetModelById
############################
@router.get("/", response_model=Optional[ModelModel])
async def get_model_by_id(id: str, user=Depends(get_verified_user)):
model = Models.get_model_by_id(id)
if model:
return model
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.NOT_FOUND,
)
############################
# UpdateModelById
############################
@@ -1,15 +1,9 @@
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import List, 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()
@@ -18,7 +12,7 @@ router = APIRouter()
############################
@router.get("/", response_model=List[PromptModel])
@router.get("/", response_model=list[PromptModel])
async def get_prompts(user=Depends(get_verified_user)):
return Prompts.get_prompts()
@@ -31,7 +25,7 @@ async def get_prompts(user=Depends(get_verified_user)):
@router.post("/create", response_model=Optional[PromptModel])
async def create_new_prompt(form_data: PromptForm, user=Depends(get_admin_user)):
prompt = Prompts.get_prompt_by_command(form_data.command)
if prompt == None:
if prompt is None:
prompt = Prompts.insert_new_prompt(user.id, form_data)
if prompt:
@@ -1,20 +1,14 @@
from fastapi import Depends, HTTPException, status, Request
from typing import List, 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, replace_imports
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)
@@ -27,7 +21,7 @@ router = APIRouter()
############################
@router.get("/", response_model=List[ToolResponse])
@router.get("/", response_model=list[ToolResponse])
async def get_toolkits(user=Depends(get_verified_user)):
toolkits = [toolkit for toolkit in Tools.get_tools()]
return toolkits
@@ -38,7 +32,7 @@ async def get_toolkits(user=Depends(get_verified_user)):
############################
@router.get("/export", response_model=List[ToolModel])
@router.get("/export", response_model=list[ToolModel])
async def get_toolkits(user=Depends(get_admin_user)):
toolkits = [toolkit for toolkit in Tools.get_tools()]
return toolkits
@@ -65,12 +59,11 @@ async def create_new_toolkit(
toolkit = Tools.get_tool_by_id(form_data.id)
if toolkit is None:
toolkit_path = os.path.join(TOOLS_DIR, f"{form_data.id}.py")
try:
with open(toolkit_path, "w") as tool_file:
tool_file.write(form_data.content)
toolkit_module, frontmatter = load_toolkit_module_by_id(form_data.id)
form_data.content = replace_imports(form_data.content)
toolkit_module, frontmatter = load_toolkit_module_by_id(
form_data.id, content=form_data.content
)
form_data.meta.manifest = frontmatter
TOOLS = request.app.state.TOOLS
@@ -132,13 +125,11 @@ async def update_toolkit_by_id(
form_data: ToolForm,
user=Depends(get_admin_user),
):
toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
try:
with open(toolkit_path, "w") as tool_file:
tool_file.write(form_data.content)
toolkit_module, frontmatter = load_toolkit_module_by_id(id)
form_data.content = replace_imports(form_data.content)
toolkit_module, frontmatter = load_toolkit_module_by_id(
id, content=form_data.content
)
form_data.meta.manifest = frontmatter
TOOLS = request.app.state.TOOLS
@@ -183,10 +174,6 @@ async def delete_toolkit_by_id(request: Request, id: str, user=Depends(get_admin
if id in TOOLS:
del TOOLS[id]
# delete the toolkit file
toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
os.remove(toolkit_path)
return result
@@ -1,33 +1,20 @@
from fastapi import Response, Request
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import List, 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"])
@@ -39,7 +26,7 @@ router = APIRouter()
############################
@router.get("/", response_model=List[UserModel])
@router.get("/", response_model=list[UserModel])
async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_admin_user)):
return Users.get_users(skip, limit)
@@ -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-"):
@@ -1,23 +1,17 @@
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
from typing import List
import markdown
from open_webui.config import DATA_DIR, ENABLE_ADMIN_EXPORT
from open_webui.env import FONTS_DIR
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()
@@ -57,21 +51,18 @@ async def get_html_from_markdown(
class ChatForm(BaseModel):
title: str
messages: List[dict]
messages: list[dict]
@router.post("/pdf")
async def download_chat_as_pdf(
form_data: ChatForm,
):
global FONTS_DIR
pdf = FPDF()
pdf.add_page()
# When running in docker, workdir is /app/backend, so fonts is in /app/backend/static/fonts
FONTS_DIR = Path("./static/fonts")
# Non Docker Installation
# When running using `pip install` the static directory is in the site packages.
if not FONTS_DIR.exists():
FONTS_DIR = Path(site.getsitepackages()[0]) / "static/fonts"
@@ -85,9 +76,10 @@ async def download_chat_as_pdf(
pdf.add_font("NotoSans", "i", f"{FONTS_DIR}/NotoSans-Italic.ttf")
pdf.add_font("NotoSansKR", "", f"{FONTS_DIR}/NotoSansKR-Regular.ttf")
pdf.add_font("NotoSansJP", "", f"{FONTS_DIR}/NotoSansJP-Regular.ttf")
pdf.add_font("NotoSansSC", "", f"{FONTS_DIR}/NotoSansSC-Regular.ttf")
pdf.set_font("NotoSans", size=12)
pdf.set_fallback_fonts(["NotoSansKR", "NotoSansJP"])
pdf.set_fallback_fonts(["NotoSansKR", "NotoSansJP", "NotoSansSC"])
pdf.set_auto_page_break(auto=True, margin=15)
@@ -114,7 +106,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"},
)
@@ -125,7 +117,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(
+170
View File
@@ -0,0 +1,170 @@
import os
import re
import subprocess
import sys
from importlib import util
import types
import tempfile
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(content):
"""
Extract frontmatter as a dictionary from the provided content string.
"""
frontmatter = {}
frontmatter_started = False
frontmatter_ended = False
frontmatter_pattern = re.compile(r"^\s*([a-z_]+):\s*(.*)\s*$", re.IGNORECASE)
try:
lines = content.splitlines()
if len(lines) < 1 or lines[0].strip() != '"""':
# The content doesn't start with triple quotes
return {}
frontmatter_started = True
for line in lines[1:]:
if '"""' in line:
if frontmatter_started:
frontmatter_ended = True
break
if frontmatter_started and not frontmatter_ended:
match = frontmatter_pattern.match(line)
if match:
key, value = match.groups()
frontmatter[key.strip()] = value.strip()
except Exception as e:
print(f"An error occurred: {e}")
return {}
return frontmatter
def replace_imports(content):
"""
Replace the import paths in the content.
"""
replacements = {
"from utils": "from open_webui.utils",
"from apps": "from open_webui.apps",
"from main": "from open_webui.main",
"from config": "from open_webui.config",
}
for old, new in replacements.items():
content = content.replace(old, new)
return content
def load_toolkit_module_by_id(toolkit_id, content=None):
if content is None:
tool = Tools.get_tool_by_id(toolkit_id)
if not tool:
raise Exception(f"Toolkit not found: {toolkit_id}")
content = tool.content
content = replace_imports(content)
Tools.update_tool_by_id(toolkit_id, {"content": content})
else:
frontmatter = extract_frontmatter(content)
# Install required packages found within the frontmatter
install_frontmatter_requirements(frontmatter.get("requirements", ""))
module_name = f"tool_{toolkit_id}"
module = types.ModuleType(module_name)
sys.modules[module_name] = module
# Create a temporary file and use it to define `__file__` so
# that it works as expected from the module's perspective.
temp_file = tempfile.NamedTemporaryFile(delete=False)
try:
with open(temp_file.name, "w", encoding="utf-8") as f:
f.write(content)
module.__dict__["__file__"] = temp_file.name
# Executing the modified content in the created module's namespace
exec(content, module.__dict__)
frontmatter = extract_frontmatter(content)
print(f"Loaded module: {module.__name__}")
# Create and return the object if the class 'Tools' is found in the module
if hasattr(module, "Tools"):
return module.Tools(), frontmatter
else:
raise Exception("No Tools class found in the module")
except Exception as e:
print(f"Error loading module: {toolkit_id}: {e}")
del sys.modules[module_name] # Clean up
raise e
finally:
os.unlink(temp_file.name)
def load_function_module_by_id(function_id, content=None):
if content is None:
function = Functions.get_function_by_id(function_id)
if not function:
raise Exception(f"Function not found: {function_id}")
content = function.content
content = replace_imports(content)
Functions.update_function_by_id(function_id, {"content": content})
else:
frontmatter = extract_frontmatter(content)
install_frontmatter_requirements(frontmatter.get("requirements", ""))
module_name = f"function_{function_id}"
module = types.ModuleType(module_name)
sys.modules[module_name] = module
# Create a temporary file and use it to define `__file__` so
# that it works as expected from the module's perspective.
temp_file = tempfile.NamedTemporaryFile(delete=False)
try:
with open(temp_file.name, "w", encoding="utf-8") as f:
f.write(content)
module.__dict__["__file__"] = temp_file.name
# Execute the modified content in the created module's namespace
exec(content, module.__dict__)
frontmatter = extract_frontmatter(content)
print(f"Loaded module: {module.__name__}")
# Create appropriate object based on available class type in the module
if hasattr(module, "Pipe"):
return module.Pipe(), "pipe", frontmatter
elif hasattr(module, "Filter"):
return module.Filter(), "filter", frontmatter
elif hasattr(module, "Action"):
return module.Action(), "action", frontmatter
else:
raise Exception("No Function class found in the module")
except Exception as e:
print(f"Error loading module: {function_id}: {e}")
del sys.modules[module_name] # Cleanup by removing the module in case of error
Functions.update_function_by_id(function_id, {"is_active": False})
raise e
finally:
os.unlink(temp_file.name)
def install_frontmatter_requirements(requirements):
if requirements:
req_list = [req.strip() for req in requirements.split(",")]
for req in req_list:
print(f"Installing requirement: {req}")
subprocess.check_call([sys.executable, "-m", "pip", "install", req])
else:
print("No requirements found in frontmatter.")
File diff suppressed because it is too large Load Diff
@@ -100,3 +100,4 @@ class TASKS(str, Enum):
EMOJI_GENERATION = "emoji_generation"
QUERY_GENERATION = "query_generation"
FUNCTION_CALLING = "function_calling"
MOA_RESPONSE_GENERATION = "moa_response_generation"
+1
View File
@@ -0,0 +1 @@
pip install dir for backend files (db, documents, etc.)
+308
View File
@@ -0,0 +1,308 @@
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...")
DOCKER = os.environ.get("DOCKER", "False").lower() == "true"
# device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")
if USE_CUDA.lower() == "true":
try:
import torch
assert torch.cuda.is_available(), "CUDA not available"
DEVICE_TYPE = "cuda"
except Exception as e:
cuda_error = (
"Error when testing CUDA but USE_CUDA_DOCKER is true. "
f"Resetting USE_CUDA_DOCKER to false: {e}"
)
os.environ["USE_CUDA_DOCKER"] = "false"
USE_CUDA = "false"
DEVICE_TYPE = "cpu"
else:
DEVICE_TYPE = "cpu"
####################################
# 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}")
if "cuda_error" in locals():
log.exception(cuda_error)
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")
FROM_INIT_PY = os.environ.get("FROM_INIT_PY", "False").lower() == "true"
if FROM_INIT_PY:
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 FROM_INIT_PY:
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() and DATA_DIR != NEW_DATA_DIR:
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 = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data"))
FONTS_DIR = Path(os.getenv("FONTS_DIR", OPEN_WEBUI_DIR / "static" / "fonts"))
FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "build")).resolve()
if FROM_INIT_PY:
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)
ENABLE_WEBSOCKET_SUPPORT = (
os.environ.get("ENABLE_WEBSOCKET_SUPPORT", "True").lower() == "true"
)
WEBSOCKET_MANAGER = os.environ.get("WEBSOCKET_MANAGER", "")
WEBSOCKET_REDIS_URL = os.environ.get("WEBSOCKET_REDIS_URL", "redis://localhost:6379/0")
File diff suppressed because it is too large Load Diff
@@ -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.
@@ -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.

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