Compare commits

..

1208 Commits

Author SHA1 Message Date
Timothy Jaeryang Baek 99dd7fb5a8 Merge pull request #6002 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.33
2024-10-24 13:36:19 -07:00
Timothy J. Baek e9e49babf8 enh: remove token cookie on signout 2024-10-24 13:35:29 -07:00
Timothy J. Baek 6b620d93a9 refac 2024-10-24 03:13:35 -07:00
Timothy J. Baek c5d974606f refac 2024-10-24 03:10:17 -07:00
Timothy J. Baek 587dab23f0 fix 2024-10-24 02:35:17 -07:00
Timothy J. Baek a4f29e1e55 chore: format 2024-10-24 02:26:52 -07:00
Timothy J. Baek 205402e096 refac 2024-10-24 01:01:00 -07:00
Timothy J. Baek 42a58da487 refac 2024-10-24 00:56:24 -07:00
Timothy J. Baek d160d0351f refac 2024-10-24 00:48:34 -07:00
Timothy J. Baek 744139d2ce refac 2024-10-24 00:35:27 -07:00
Timothy J. Baek a28ef8acc5 refac 2024-10-24 00:34:44 -07:00
Timothy J. Baek 9c2d592c73 enh: include message_turn metadata 2024-10-24 00:23:31 -07:00
Timothy J. Baek 160e63e509 refac: feedback history share support 2024-10-24 00:00:49 -07:00
Timothy J. Baek 056950b4f9 enh: leaderboard notice 2024-10-23 23:07:44 -07:00
Timothy J. Baek d7151fe6fa refac: similarity operation 2024-10-23 22:38:58 -07:00
Timothy J. Baek cde33002c7 feat: topic leaderboard 2024-10-23 22:35:12 -07:00
Timothy J. Baek 0f4b6cdb67 refac 2024-10-23 20:49:18 -07:00
Timothy J. Baek d0662e2518 refac: do not use arena model for tasks 2024-10-23 20:33:08 -07:00
Timothy J. Baek ec37d801be refac: feedback optimisation 2024-10-23 20:27:32 -07:00
Timothy J. Baek b5d7e57d3c refac 2024-10-23 20:19:42 -07:00
Timothy J. Baek 076f9fd9c0 enh: auto tag feedback 2024-10-23 20:18:51 -07:00
Timothy J. Baek 73ab84efe6 fix: leaderboard
Co-Authored-By: silentoplayz <50341825+silentoplayz@users.noreply.github.com>
2024-10-23 18:54:49 -07:00
Timothy J. Baek 0c6e9e3e86 refac: rating 2024-10-23 16:59:54 -07:00
Timothy J. Baek c1a054de4a fix 2024-10-23 15:35:40 -07:00
Timothy J. Baek 51265b4683 refac 2024-10-23 15:33:13 -07:00
Timothy J. Baek d4c42dcfa9 fix 2024-10-23 15:09:20 -07:00
Timothy J. Baek d85480b4d6 fix: bypass_filter for arena models 2024-10-23 15:05:43 -07:00
Timothy J. Baek ee2f8d3552 fix: folder migration 2024-10-23 14:58:28 -07:00
Timothy J. Baek 0e56ef20cb fix: selectedModels 2024-10-23 14:51:25 -07:00
Timothy J. Baek b16c4bc1df refac 2024-10-23 12:41:41 -07:00
Timothy J. Baek 68a47f6a9f enh: arena model filter 2024-10-23 12:40:47 -07:00
Timothy J. Baek affb0e5c37 refac 2024-10-23 12:33:00 -07:00
Timothy Jaeryang Baek 15c3bdf7af Merge pull request #6361 from KarlLee830/translate
i18n: Update Chinese translation
2024-10-23 10:03:45 -07:00
KarlLee830 42914a162f i18n: Update Chinese translation 2024-10-23 21:46:28 +08:00
Timothy Jaeryang Baek 928a5e747c Merge pull request #6358 from OriginalSimon/dev
18n: Update Ukrainian translation
2024-10-23 05:04:48 -07:00
Simon a494ef8a99 Update translation.json 2024-10-23 13:50:56 +02:00
Timothy J. Baek 8d2089e5b8 refac: styling 2024-10-23 02:17:10 -07:00
Timothy J. Baek 4557f4d165 chore: version bump 2024-10-23 02:15:11 -07:00
Timothy J. Baek b9af35be4c refac 2024-10-23 01:48:26 -07:00
Timothy J. Baek a41a5cc0c8 refac 2024-10-23 01:27:32 -07:00
Timothy J. Baek 4a1bf6d6c4 refac 2024-10-23 01:23:49 -07:00
Timothy J. Baek 0d924a1cba enh: feedback delete support 2024-10-23 01:05:45 -07:00
Timothy J. Baek 01651ee0a6 enh: feedback history pagination 2024-10-23 00:51:27 -07:00
Timothy J. Baek 3531e009ce chore: format 2024-10-23 00:42:13 -07:00
Timothy Jaeryang Baek 6edbbde2eb Merge pull request #6348 from MadsLang/main
Adding Danish language
2024-10-23 00:39:31 -07:00
Timothy J. Baek 8514ed148a refac: show changelog behaviour 2024-10-23 00:38:59 -07:00
Timothy J. Baek 2897c25b99 refac 2024-10-22 23:55:36 -07:00
Timothy J. Baek 58261a7b83 refac: styling 2024-10-22 23:54:37 -07:00
Timothy Jaeryang Baek 40d5082b9f Merge pull request #6345 from open-webui/leaderboard
feat: leaderboard
2024-10-22 23:45:16 -07:00
Timothy J. Baek 6536c5b7f7 enh: leaderboard percentage 2024-10-22 23:44:13 -07:00
Timothy J. Baek 139136e700 refac: styling 2024-10-22 23:31:51 -07:00
Timothy J. Baek 5101b440a8 refac: styling 2024-10-22 23:28:41 -07:00
Timothy J. Baek 75980010a9 refac: styling 2024-10-22 23:27:07 -07:00
Timothy J. Baek f6893edcc2 feat: leaderboard rating 2024-10-22 23:24:49 -07:00
Timothy J. Baek ce85400817 refac: feedback 2024-10-22 22:55:34 -07:00
Timothy J. Baek bc95e62600 feat: leaderboard 2024-10-22 20:14:10 -07:00
Timothy J. Baek 4c691c0edb refac: rich text input behaviour 2024-10-22 18:34:07 -07:00
Timothy J. Baek 5499b5acc8 enh: arena models toggle tooltip
Co-Authored-By: silentoplayz <50341825+silentoplayz@users.noreply.github.com>
2024-10-22 18:28:08 -07:00
Timothy J. Baek 0a765744b8 refac 2024-10-22 18:12:08 -07:00
Timothy J. Baek 00e9362c2c enh: arena model rate to reveal 2024-10-22 18:05:30 -07:00
Timothy J. Baek 6d52f913d2 enh: arena model send selected model id 2024-10-22 17:43:39 -07:00
Timothy J. Baek 2d99f275a3 refac: multi response scroll behaviour 2024-10-22 17:34:53 -07:00
Timothy Jaeryang Baek 170ec2f9d0 Merge pull request #6339 from Cyb4Black/fix-not-rely-on-id-token-for-user-info
fix: get userinfo from endpoint, not only from token
2024-10-22 13:36:30 -07:00
Willnow, Patrick 1b5ac834ef fix: get userinfo from endpoint, not only from token
as was suggested by @alvarolopez in #6262
2024-10-22 21:55:12 +02:00
Timothy Jaeryang Baek c57ef980fb Merge pull request #6337 from open-webui/dependabot/npm_and_yarn/npm_and_yarn-dd9e89fd33
build(deps): bump mermaid from 10.9.1 to 10.9.3 in the npm_and_yarn group across 1 directory
2024-10-22 12:30:31 -07:00
Timothy J. Baek 04b324996c fix 2024-10-22 12:05:48 -07:00
Timothy J. Baek b31d314638 refac 2024-10-22 12:04:45 -07:00
Timothy J. Baek 424062d75f fix: emoji generation 2024-10-22 11:23:38 -07:00
dependabot[bot] 00b2038efb build(deps): bump mermaid in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [mermaid](https://github.com/mermaid-js/mermaid).


Updates `mermaid` from 10.9.1 to 10.9.3
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Changelog](https://github.com/mermaid-js/mermaid/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/mermaid-js/mermaid/compare/v10.9.1...v10.9.3)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-22 18:23:33 +00:00
Timothy J. Baek 24e7ae8767 refac: multi response styling 2024-10-22 03:46:24 -07:00
Timothy J. Baek 8d38af8ddd refac 2024-10-22 03:44:54 -07:00
Timothy J. Baek a0692a434d refac 2024-10-22 03:40:03 -07:00
Timothy J. Baek a89d29db9c fix 2024-10-22 03:38:28 -07:00
Timothy Jaeryang Baek b759f488b4 Merge pull request #6324 from RocketRider/patch-2
feat: Allow URL in citations to be different from the displayed name
2024-10-22 03:27:49 -07:00
Timothy Jaeryang Baek 645657e307 Merge pull request #6327 from aleixdorca/dev
i18n: Update catalan translation.json
2024-10-22 03:27:23 -07:00
Timothy J. Baek 3c8944cb12 refac 2024-10-22 03:22:45 -07:00
Timothy J. Baek 9f285fb2fb feat: arena models 2024-10-22 03:16:48 -07:00
MadsLang 8497db9f43 added Danish language 2024-10-22 11:57:28 +02:00
Aleix Dorca ac09d59a3f Update translation.json 2024-10-22 11:17:06 +02:00
Timothy J. Baek ee16177924 refac: playground styling 2024-10-21 21:50:34 -07:00
Michael Möbius 9153525c84 use url if available as document link 2024-10-22 06:35:01 +02:00
Timothy J. Baek a7aa669038 refac: styling 2024-10-21 21:32:45 -07:00
Michael Möbius 41bd16acbe don't overwrite name, keep url separate 2024-10-22 06:32:08 +02:00
Timothy J. Baek ae44445464 refac: styling 2024-10-21 16:10:29 -07:00
Timothy J. Baek 09f7cdc272 fix 2024-10-21 15:43:15 -07:00
Timothy J. Baek b11c48ac16 refac: playground 2024-10-21 15:37:50 -07:00
Timothy J. Baek e0b7d95429 refac 2024-10-21 15:30:30 -07:00
Timothy J. Baek c03cccfd14 refac: playground 2024-10-21 15:24:59 -07:00
Timothy Jaeryang Baek 39e3afd55d Merge pull request #6309 from OriginalSimon/dev
18n: Update Ukrainian translation
2024-10-21 13:58:59 -07:00
Timothy J. Baek fc71f441c4 refac 2024-10-21 13:45:28 -07:00
Simon 117bdfcb70 Update translation.json 2024-10-21 15:02:14 +02:00
Timothy J. Baek 3dd10f2ca0 refac: playground 2024-10-21 05:09:28 -07:00
Timothy J. Baek e2d4a69750 refac: title generation 2024-10-21 04:24:17 -07:00
Timothy J. Baek 24885a2e38 refac 2024-10-21 04:14:49 -07:00
Timothy J. Baek b4e73c7f19 refac: convert_payload_openai_to_ollama 2024-10-21 04:10:28 -07:00
Timothy J. Baek f99facf383 fix: message input issue 2024-10-21 03:17:30 -07:00
Timothy J. Baek d105be9ca2 refac: styling 2024-10-21 02:02:33 -07:00
Timothy J. Baek e1baa9cc3f refac 2024-10-21 02:00:22 -07:00
Timothy J. Baek ec17bbc867 refac: styling 2024-10-21 01:57:19 -07:00
Timothy J. Baek f1b0d7eb41 refac: styling 2024-10-21 01:54:43 -07:00
Timothy J. Baek fc6dc43a19 refac: tagging behaviour 2024-10-21 01:30:22 -07:00
Timothy J. Baek 8d71323009 refac: model workspace styling 2024-10-21 00:50:08 -07:00
Timothy J. Baek ded22b3204 refac: do not check for empty keys 2024-10-21 00:40:01 -07:00
Timothy J. Baek e3b1b717be refac: styling 2024-10-21 00:35:51 -07:00
Timothy J. Baek 2bb82f258a refac: styling 2024-10-21 00:33:21 -07:00
Timothy J. Baek 6cb41a38a6 enh: option to disable update toast 2024-10-21 00:30:29 -07:00
Timothy J. Baek 47e377967e refac: styling 2024-10-21 00:05:27 -07:00
Timothy J. Baek f27c0f7dcf refac: styling 2024-10-20 23:53:36 -07:00
Timothy J. Baek cd8271eb95 fix 2024-10-20 23:48:28 -07:00
Timothy J. Baek e43e91edd3 refac 2024-10-20 23:45:15 -07:00
Timothy J. Baek 7984980619 feat: s3 support 2024-10-20 23:38:26 -07:00
Timothy J. Baek cb86e09005 feat: s3 support (cherry-picked from #6117)
Co-Authored-By: ssnnow <ssnnow@gmail.com>
2024-10-20 22:53:45 -07:00
Timothy J. Baek 822024f44e refac: artifacts panel behaviour 2024-10-20 22:04:21 -07:00
Timothy J. Baek f02ef01e0c refac: wording 2024-10-20 21:43:19 -07:00
Timothy J. Baek d5c1c2f0a7 enh: faster whisper custom model support 2024-10-20 21:34:36 -07:00
Timothy J. Baek 145a7bbda5 refac: wording 2024-10-20 20:43:10 -07:00
Timothy Jaeryang Baek de1e29eed0 Merge pull request #6109 from JoeyShapiro/dev
fix: disallow empty files
2024-10-20 20:41:56 -07:00
Timothy J. Baek 438b277be0 refac: styling 2024-10-20 19:04:30 -07:00
Timothy J. Baek 2f5c65bd1f refac: styling 2024-10-20 18:54:08 -07:00
Timothy J. Baek 4b357a7b62 refac: styling 2024-10-20 18:49:30 -07:00
Timothy J. Baek 3f598ee5c9 chore: format 2024-10-20 18:42:34 -07:00
Timothy Jaeryang Baek c023afac8c Merge pull request #6238 from Cyb4Black/dev
feat: oauth based role management
2024-10-20 18:40:21 -07:00
Timothy J. Baek 9936583477 chore: format 2024-10-20 18:38:06 -07:00
Timothy Jaeryang Baek 768b7e139c Merge branch 'dev' into dev 2024-10-20 18:37:20 -07:00
Timothy J. Baek b529212a2d refac: styling 2024-10-20 18:35:19 -07:00
Timothy J. Baek b38ed4221a refac: styling 2024-10-20 18:31:24 -07:00
Timothy J. Baek 254c6ca709 refac: tools & functions styling 2024-10-20 18:24:50 -07:00
Timothy J. Baek e378f70f34 refac: tag restoration 2024-10-20 18:04:46 -07:00
Timothy J. Baek d23252b8a9 enh: restore tags from chat import 2024-10-20 18:02:41 -07:00
Timothy J. Baek 30eb43f5b8 fix 2024-10-20 17:50:30 -07:00
Timothy J. Baek 4d46bfe03b refac: file table migration 2024-10-20 17:45:37 -07:00
Timothy J. Baek c5787a2b55 refac: placeholder 2024-10-20 17:44:57 -07:00
Timothy J. Baek 67647c8747 refac 2024-10-20 14:45:47 -07:00
Patrick Willnow d14443a4ba Update Dockerfile
Removed as requested in PR
2024-10-20 14:44:31 +02:00
Timothy J. Baek da9a6c1078 refac: styling 2024-10-20 02:10:15 -07:00
Timothy J. Baek e8591b57b4 enh: allow file download from collection view 2024-10-20 01:58:27 -07:00
Timothy Jaeryang Baek 15cbb66e5e Merge pull request #6292 from KarlLee830/translate
i18n: Update Chinese translation
2024-10-20 01:57:59 -07:00
Timothy J. Baek 05c18cd664 enh: voice input support for knowledge text content 2024-10-20 01:49:44 -07:00
Timothy J. Baek b9b670fcfb refac: styling 2024-10-20 01:24:31 -07:00
Timothy J. Baek a0b58e244e refac: styling 2024-10-20 01:22:20 -07:00
Timothy J. Baek 875e75b8c2 refac 2024-10-20 01:20:27 -07:00
KarlLee830 a850a60a23 i18n: Update Chinese translation 2024-10-20 16:14:15 +08:00
KarlLee830 8102bdbed1 i18n: Update Chinese translation 2024-10-20 16:12:27 +08:00
Timothy J. Baek 97b6fb4766 refac: styling 2024-10-20 00:56:01 -07:00
Timothy J. Baek 146a9d7f05 refac 2024-10-20 00:51:01 -07:00
Timothy J. Baek c5952d62ad refac 2024-10-20 00:47:43 -07:00
Timothy J. Baek a922f4b2e7 refac 2024-10-20 00:42:45 -07:00
Timothy J. Baek 03282da45c refac: collection styling 2024-10-20 00:36:43 -07:00
Timothy J. Baek e8c629a2e2 refac: styling 2024-10-19 23:17:47 -07:00
Timothy J. Baek e530914328 enh: custom tags generation prompt support 2024-10-19 21:27:10 -07:00
Timothy J. Baek 7476bcaa2b enh: filter by untagged chat 2024-10-19 21:16:59 -07:00
Timothy J. Baek 86999157de refac: disable 'none' tag 2024-10-19 21:04:56 -07:00
Timothy J. Baek 54b843c367 refac 2024-10-19 20:58:19 -07:00
Timothy J. Baek 8330fcdb5c refac 2024-10-19 20:47:30 -07:00
Timothy J. Baek d795940ced feat: chat auto tag 2024-10-19 20:34:17 -07:00
Timothy J. Baek 2db0f58dcb refac: user message edit 2024-10-19 18:42:26 -07:00
Timothy J. Baek 3069452210 refac 2024-10-19 17:55:57 -07:00
Timothy J. Baek 7ce51f2b4c refac 2024-10-19 17:49:28 -07:00
Timothy J. Baek 335b6b6c7a refac 2024-10-19 17:29:58 -07:00
Timothy J. Baek 953a8285f7 refac 2024-10-19 16:18:14 -07:00
Timothy J. Baek 73b33c3781 fix: rich text input width styling 2024-10-19 15:03:20 -07:00
Timothy J. Baek 60dd3230ae fix: rich input space behaviour 2024-10-19 14:56:30 -07:00
Timothy J. Baek 3436523b79 fix: input commands 2024-10-19 13:40:20 -07:00
Timothy J. Baek 211c41843c fix: sidebar search 2024-10-19 13:28:27 -07:00
Timothy J. Baek e252ca1dc4 refac: rich text input styling 2024-10-19 03:15:40 -07:00
Timothy J. Baek 6d25d23326 refac: styling 2024-10-19 03:04:12 -07:00
Timothy J. Baek 82edd0e3d9 refac: hide pipelines save button if not present 2024-10-19 02:53:50 -07:00
Timothy J. Baek f6e7af346e refac: unescape rich text input 2024-10-19 02:50:49 -07:00
Timothy J. Baek 7d322a7238 enh: folder export 2024-10-19 02:42:12 -07:00
Timothy J. Baek 3c1afa97af refac: styling 2024-10-19 02:23:10 -07:00
Timothy J. Baek 545f2a0fe2 enh: hide made by community 2024-10-19 02:12:34 -07:00
Timothy J. Baek e153108b51 refac 2024-10-19 01:45:17 -07:00
Timothy J. Baek 971e54d7b4 refac: selector styling 2024-10-19 01:44:45 -07:00
Timothy J. Baek c993cc5515 refac: styling 2024-10-19 01:42:07 -07:00
Timothy J. Baek fb84fc8e77 refac: selector styling 2024-10-19 01:34:03 -07:00
Timothy J. Baek ce917eba81 enh: model url search param 2024-10-19 01:30:49 -07:00
Timothy J. Baek 81f62d5bff refac 2024-10-19 01:07:30 -07:00
Timothy J. Baek 0556479cbe enh: ctrl+shift+delete chat delete support 2024-10-19 01:06:30 -07:00
Timothy J. Baek 99f70cac8b fix: model w/ custom name delete issue 2024-10-19 00:50:00 -07:00
Timothy J. Baek 94c4aa8ddb refac: styling 2024-10-19 00:48:16 -07:00
Timothy J. Baek ca6a624534 refac: styling 2024-10-19 00:45:29 -07:00
Timothy J. Baek 28d6425f87 refac: styling 2024-10-19 00:41:58 -07:00
Timothy J. Baek 6b43cc97d2 chore: format 2024-10-19 00:37:21 -07:00
Timothy J. Baek 6c0d3ce736 refac: tab text variable select 2024-10-19 00:23:59 -07:00
Timothy J. Baek 0107a70343 refac: styling 2024-10-18 23:54:41 -07:00
Timothy J. Baek f46b95300b feat: rich text input for chat 2024-10-18 23:54:35 -07:00
Timothy J. Baek 5e96922eba refac: styling 2024-10-18 22:56:04 -07:00
Timothy J. Baek a3728e6957 refac 2024-10-18 22:40:15 -07:00
Timothy J. Baek 353dc83542 enh: rich text input space behaviour 2024-10-18 22:16:18 -07:00
Timothy Jaeryang Baek 2fc8fa1869 Merge pull request #6274 from open-webui/main
dev
2024-10-18 21:33:54 -07:00
Timothy Jaeryang Baek c2d1a3164e Merge pull request #6261 from monotykamary/main
fix: default to False if stream is unavailable
2024-10-18 21:33:38 -07:00
Timothy J. Baek 79cb9383d9 refac 2024-10-18 21:30:55 -07:00
Timothy J. Baek 670441f548 feat: rich text input 2024-10-18 14:55:39 -07:00
Timothy J. Baek 988a5e2b8d refac: folder/pinned behaviour 2024-10-18 14:18:13 -07:00
Timothy J. Baek 8648a330f2 refac 2024-10-18 14:11:13 -07:00
Timothy J. Baek cad31f6f2b fix 2024-10-18 14:08:43 -07:00
Timothy Jaeryang Baek 8ce5c2eaf0 Merge pull request #6263 from aleixdorca/dev
i18n: Update catalan translation
2024-10-18 14:06:42 -07:00
Aleix Dorca 001788b6e4 Minor Update to translation.json 2024-10-18 12:35:38 +02:00
Aleix Dorca f0fbc586c8 Update catalan translation.json 2024-10-18 12:33:29 +02:00
Tom X Nguyen 11588af34c fix: default to False if stream is unavailable 2024-10-18 11:33:08 +07:00
Timothy J. Baek 2962aa9f06 fix 2024-10-17 20:29:31 -07:00
Timothy J. Baek f821de9470 enh: drag and drop import to folders 2024-10-17 20:13:28 -07:00
Timothy J. Baek 590dc0895f refac 2024-10-17 19:46:37 -07:00
Timothy J. Baek e6f4da2bfc refac 2024-10-17 19:45:18 -07:00
Timothy J. Baek c171e624eb enh: export chat from chat item 2024-10-17 19:30:20 -07:00
Timothy J. Baek b877bc0086 enh: folder delete confirmation
Co-Authored-By: silentoplayz <50341825+silentoplayz@users.noreply.github.com>
2024-10-17 19:19:26 -07:00
Timothy J. Baek 351c29917b refac 2024-10-17 19:09:01 -07:00
Timothy J. Baek 6e4820cad8 refac 2024-10-17 18:42:36 -07:00
Timothy J. Baek 7ffa3cb022 refac: folder deletion 2024-10-17 18:24:58 -07:00
Timothy J. Baek 42d048741c fix: disable file item click during upload 2024-10-17 18:15:59 -07:00
Timothy J. Baek dff9254e34 chore: format 2024-10-17 13:30:21 -07:00
Timothy Jaeryang Baek e6fea74b60 Merge pull request #5989 from jannikstdl/dev
enh: citations show relevance score - compact citation view
2024-10-17 13:26:20 -07:00
Timothy Jaeryang Baek c9c79852a5 Merge branch 'dev' into dev 2024-10-17 13:25:39 -07:00
Timothy J. Baek 186923210b refac: styling 2024-10-17 13:17:37 -07:00
Timothy J. Baek 6336d34b59 fix: web attachment issue 2024-10-17 13:08:10 -07:00
Timothy J. Baek f54ab7e551 fix 2024-10-17 00:55:53 -07:00
Timothy J. Baek 4dcbf1af07 enh: share rating to community 2024-10-17 00:51:46 -07:00
Timothy J. Baek 86adcf33b0 refac: styling 2024-10-17 00:33:53 -07:00
Timothy Jaeryang Baek 87d2738864 Merge pull request #6247 from open-webui/folders
feat: folders
2024-10-17 00:18:18 -07:00
Timothy J. Baek 3578c85e39 feat: folder menu 2024-10-17 00:17:50 -07:00
Timothy J. Baek b402061546 enh: open folder on drop 2024-10-16 23:50:06 -07:00
Timothy J. Baek d8b513023c feat: chat folder drag and drop support 2024-10-16 23:45:50 -07:00
Timothy J. Baek 36a541d6b0 enh: drag and drop folders 2024-10-16 23:06:53 -07:00
Timothy J. Baek d5bf32f240 enh: save isExpanded state 2024-10-16 22:58:28 -07:00
Timothy J. Baek e421be5759 refac: ui optimisation 2024-10-16 22:41:16 -07:00
Timothy J. Baek 29c39d44e1 fix: collapsible space toggle issue 2024-10-16 22:36:44 -07:00
Timothy J. Baek 7b97d7a718 enh: disable drag event listener if parent dragged 2024-10-16 22:04:21 -07:00
Timothy J. Baek 9df9f4a990 refac 2024-10-16 21:55:25 -07:00
Timothy J. Baek dea12360f4 refac: folder id -> uuid 2024-10-16 21:49:22 -07:00
Timothy J. Baek a942c30ca8 feat: folder ui 2024-10-16 21:05:03 -07:00
Timothy J. Baek ede71740d2 feat: folders table 2024-10-16 15:13:38 -07:00
Patrick Willnow 8e807bcf8a Merge branch 'open-webui:dev' into dev 2024-10-16 21:43:30 +02:00
Willnow, Patrick ea070e34f9 Remove pending from allowed roles 2024-10-16 21:41:47 +02:00
Timothy Jaeryang Baek de442792ca Merge pull request #6235 from tklk-forks/add-python-dep
chore: add googleapis-common-protos dep
2024-10-16 12:13:21 -07:00
Willnow, Patrick 9ad07ad0ce Add WEBUI_SESSION_COOKIE-settings missing from merge conflict 2024-10-16 20:30:35 +02:00
Willnow, Patrick b888ee17ff Merge branch 'main' into dev
# Conflicts:
#	backend/open_webui/main.py
2024-10-16 20:16:05 +02:00
techknowlogick e6802c7e98 chore: add googleapis-common-protos dep 2024-10-16 13:38:48 -04:00
Willnow, Patrick b1554be3f2 Fix imports 2024-10-16 16:58:03 +02:00
Willnow, Patrick 57d54160d3 Merge branch 'implement-oauth-role-management'
# Conflicts:
#	backend/open_webui/main.py
2024-10-16 16:54:43 +02:00
Willnow, Patrick 8eebd6bce1 Finish reorganizing oauth code 2024-10-16 16:32:57 +02:00
Timothy J. Baek c797c2e18b refac 2024-10-16 03:35:26 -07:00
Timothy J. Baek dedb26fd5c feat: folders db migration 2024-10-16 02:53:16 -07:00
Willnow, Patrick 08ff494754 WIP
- refactoring oauth functions to enable refresh functionality
2024-10-16 09:42:47 +02:00
Timothy J. Baek eef9045dcc chore: format 2024-10-15 09:22:03 -07:00
Timothy Jaeryang Baek 2f2af94d73 Merge pull request #6194 from OriginalSimon/dev
18n: Update Ukrainian translation
2024-10-15 09:17:41 -07:00
Simon 2af9727bd3 Merge branch 'open-webui:dev' into dev 2024-10-15 14:17:30 +02:00
Simon b5f46023d4 Update translation.json 2024-10-15 14:17:09 +02:00
Timothy J. Baek 0131afe667 refac: styling 2024-10-15 05:12:56 -07:00
Timothy J. Baek 69d0472898 refac: styling 2024-10-15 05:00:36 -07:00
Timothy J. Baek 86c961e342 fix 2024-10-15 03:30:02 -07:00
Timothy J. Baek bf0043881a refac 2024-10-15 03:11:03 -07:00
Timothy J. Baek 98ba3f8428 refac: styling 2024-10-15 03:02:56 -07:00
Timothy J. Baek 24f149f686 refac: pinned chat behaviour 2024-10-15 02:52:56 -07:00
Timothy J. Baek b4cd503a02 refac 2024-10-15 02:51:08 -07:00
Timothy J. Baek 97bb9d41a6 refac 2024-10-15 02:48:41 -07:00
Timothy J. Baek 2f4c04055c enh: drag and drop chat to pin 2024-10-15 02:12:39 -07:00
Timothy J. Baek b01f9e8ec3 refac: folder 2024-10-15 00:35:14 -07:00
Timothy J. Baek 73a251fc49 enh: drag ghost 2024-10-14 23:55:50 -07:00
Timothy J. Baek cf24a65caa fix: duplicate tags 2024-10-14 23:04:10 -07:00
Timothy J. Baek 9d2d53bfb5 fix 2024-10-14 22:59:17 -07:00
Timothy J. Baek 6703cacb99 fix: tag unarchive/archive issue 2024-10-14 22:57:11 -07:00
Timothy J. Baek d8a30bd6ae refac: sidebar tag add behaviour 2024-10-14 21:21:45 -07:00
Timothy J. Baek 4c7651c113 refac: pinned collapsible styling 2024-10-14 21:15:36 -07:00
Timothy J. Baek 2afc5f3339 refac 2024-10-14 21:10:04 -07:00
Timothy J. Baek 93dab86e8d refac: dragged overlay behaviour to only activate for files 2024-10-14 21:03:55 -07:00
Timothy J. Baek f8bb77324d refac: styling 2024-10-14 20:36:16 -07:00
Timothy J. Baek 29e8e2d938 refac: rating submit -> save 2024-10-14 20:35:06 -07:00
Timothy J. Baek 90b7754cd6 refac: collapsible pinned chat list 2024-10-14 19:33:32 -07:00
Timothy J. Baek bc75870289 fix: tag search 2024-10-14 19:02:08 -07:00
Timothy J. Baek effa77379e refac 2024-10-14 18:53:29 -07:00
Timothy J. Baek 8eb45acf10 refac 2024-10-14 18:47:41 -07:00
Timothy J. Baek 1db1ef7c26 refac: tag search 2024-10-14 17:31:52 -07:00
Timothy J. Baek 0b2c7046cd fix: archived chats 2024-10-14 15:29:43 -07:00
Timothy J. Baek 466eea1a4c fix: voice note mic not stopping issue 2024-10-14 14:53:01 -07:00
Timothy J. Baek f0179270e2 fix: tag migration 2024-10-14 13:45:09 -07:00
Timothy J. Baek 86bc3023b3 refac: styling 2024-10-14 12:52:20 -07:00
Timothy J. Baek 6f07afc79b refac: styling 2024-10-14 12:46:05 -07:00
Timothy Jaeryang Baek 7a0933a72f Merge pull request #6182 from aleixdorca/dev
i18n: Update catalan translation.json
2024-10-14 12:34:30 -07:00
Aleix Dorca c5921dea58 Update catalan translation.json 2024-10-14 11:38:33 +02:00
Jannik Streidl 79c834d0e4 fix: do not show relevances it you have mixed collections (cosine + l2) 2024-10-14 11:18:13 +02:00
Jannik Streidl 33c3dbd9fa fix 2024-10-14 10:37:54 +02:00
Jannik Streidl f0f4de59eb Merge branch 'upstream-dev' into dev 2024-10-14 09:50:40 +02:00
Timothy Jaeryang Baek 6233494ed1 Merge pull request #6179 from shirayu/fix_typos
fix: Fix typos
2024-10-14 00:29:59 -07:00
Timothy J. Baek dce91a8557 fix: custom model action button issue 2024-10-14 00:28:21 -07:00
Yuta Hayashibe 12516c8a45 fix: Fix typos 2024-10-14 16:22:07 +09:00
Timothy J. Baek 1294ba9d61 refac: styling 2024-10-13 23:53:53 -07:00
Timothy J. Baek ee079df8ed refac: code execution styling 2024-10-13 23:49:32 -07:00
Timothy J. Baek e0e249c1b9 refac: convention 2024-10-13 23:16:51 -07:00
Timothy J. Baek a4a5614de5 refac 2024-10-13 23:07:32 -07:00
Timothy J. Baek 55f14e37ea refac: code_execution.uuid -> id
convention
2024-10-13 23:04:04 -07:00
Timothy Jaeryang Baek adb1bfcaa8 Merge pull request #5955 from EtiennePerot/code-execution-message
feat: add code execution status to chat messages.
2024-10-13 22:58:59 -07:00
Timothy Jaeryang Baek 67885c71dc Merge pull request #6176 from luochen1990/feat-openai-model-list-timeout
add: OPENAI_MODEL_LIST_TIMEOUT
2024-10-13 22:56:59 -07:00
Timothy J. Baek ee8b8220f0 refac: OPENAI_MODEL_LIST_TIMEOUT -> AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST 2024-10-13 22:56:33 -07:00
LuoChen ebec6cd426 add: OPENAI_MODEL_LIST_TIMEOUT 2024-10-14 09:00:09 +08:00
Timothy J. Baek 6f3f5ff922 refac: styling 2024-10-13 04:36:27 -07:00
Timothy Jaeryang Baek 5c45643028 Merge pull request #6160 from shirayu/improve_translation_ja-JP
i18n: Update Japanese translation
2024-10-13 04:29:56 -07:00
Timothy J. Baek 586e005f0f enh: token text splitter support 2024-10-13 04:24:13 -07:00
Yuta Hayashibe 28892fe2d7 i18n: Update Japanese translation 2024-10-13 20:18:06 +09:00
Timothy J. Baek 8a0da6d376 enh: include file name to context 2024-10-13 03:58:51 -07:00
Timothy J. Baek 797afd0b72 fix: embedding metadata issue 2024-10-13 03:25:11 -07:00
Timothy J. Baek 92605fd59f refac 2024-10-13 03:16:18 -07:00
Timothy J. Baek 5ffd216fca refac 2024-10-13 03:02:02 -07:00
Timothy J. Baek dff3732fcd enh: tiktoken/token splitter support 2024-10-13 02:07:50 -07:00
Timothy J. Baek 8ae605ec4b fix: multi-user tags issue 2024-10-13 01:00:38 -07:00
Timothy J. Baek 5273dc4535 refac 2024-10-13 00:21:06 -07:00
Timothy J. Baek 112cbdccbb revert: pdf gen 2024-10-13 00:05:28 -07:00
Timothy J. Baek 5dc05eac67 chore: pyproject bump 2024-10-12 23:07:36 -07:00
Timothy Jaeryang Baek 9c1820f785 Merge pull request #6156 from noczero/add-pdf-generator
Feat: Enhance PDF Downloader to Export Chat
2024-10-12 22:46:50 -07:00
noczero c41261e72b feat: PDF generator to export chat 2024-10-13 11:20:09 +07:00
Timothy J. Baek bb97535cad refac: sentence transformers bump 2024-10-12 18:32:04 -07:00
Timothy J. Baek 333317a7ce refac: add embedding metadata 2024-10-12 18:30:21 -07:00
Etienne Perot 9fbff16a08 feat: add code execution status to chat messages.
This adds `code_executions` as an array of code execution statuses to
chat messages. The intent of this data is to be displayed in a similar
manner as citations: at the bottom of the message, with buttons that open
a modal for more info. However, code execution data doesn't fit well in
citation modals, because they fundamentally differ in their formatting.
Code execution status includes the code that was run (which benefits from
being syntax-highlighted), and the output and generated files. This
differs from citations which are just list of document names and links.

Additionally, code execution is a process, whereas citations are only
emitted once. This is why code execution data uses an ID-based approach,
where each code execution instance is identified by a unique ID and can
be updated by emitting a new `code_execution` message with the same ID.
This allows the code execution status to be updated as code runs.
2024-10-12 16:14:12 -07:00
Jannik Streidl f47c9c69e3 Merge branch 'upstream-dev' into dev 2024-10-12 15:18:59 +02:00
Jannik Streidl 0bebc898c8 finalizing fixes & additions 2024-10-12 15:18:56 +02:00
Jannik Streidl 9d4d96429f only show relevance pertentage score if distances are in cosinus silimarity range 2024-10-12 13:51:27 +02:00
Timothy J. Baek 5c48fce382 fix: async-timeout 2024-10-12 02:42:31 -07:00
Timothy J. Baek e576ddce67 enh: tooltip 2024-10-12 02:37:09 -07:00
Timothy J. Baek c5b670cccd refac: styling 2024-10-12 02:32:15 -07:00
Timothy J. Baek 5a96fcbeaf enh: enable selecting individual files from collection 2024-10-12 02:31:10 -07:00
Timothy J. Baek 0a08495fec refac: styling 2024-10-12 01:27:55 -07:00
Timothy J. Baek 5837dcbfd4 refac: chat controls resize behaviour 2024-10-12 01:10:10 -07:00
Timothy J. Baek 11d7aec61d fix 2024-10-11 16:21:56 -07:00
Timothy J. Baek 30d3d28b9f refac 2024-10-11 15:37:46 -07:00
Timothy J. Baek 1cad157071 fix 2024-10-11 15:29:58 -07:00
Timothy J. Baek 428fd202c5 fix: custom comfyui prompt issue 2024-10-11 13:11:17 -07:00
Timothy Jaeryang Baek db35ea0ae1 Merge pull request #6129 from open-webui/main
refac
2024-10-11 12:11:47 -07:00
Willnow, Patrick 1c5b6987e2 add missing env mapping 2024-10-11 14:08:11 +02:00
Timothy J. Baek 5867ebb1ee refac 2024-10-11 00:28:26 -07:00
Timothy Jaeryang Baek 11afce7895 Merge pull request #6112 from djismgaming/dev
**i18n** Spanish translation update
2024-10-11 00:14:23 -07:00
Timothy J. Baek 63d297756a enh: mermaid copy content button 2024-10-11 00:10:00 -07:00
Timothy J. Baek ba2df1c33a refac 2024-10-11 00:00:13 -07:00
Timothy J. Baek 9658c2559a refac 2024-10-10 23:43:08 -07:00
Timothy J. Baek acb5dcf30a refac: tags 2024-10-10 23:22:53 -07:00
Ismael 7f586f1c3d Added missing new lines on two other translations (non-Spanish) to fix some Format & Build Frontend action issues for them. 2024-10-10 22:25:56 -04:00
Ismael b2c492ce1f Spanish translation update
Updated the new keys added and some minor fixes based on text context.
2024-10-10 21:58:11 -04:00
Timothy Jaeryang Baek 4adc57fd34 Merge pull request #6104 from Peter-De-Ath/hotfix/ollama-embed
fix: ollama embed support list[str] or str on input
2024-10-10 15:10:39 -07:00
Peter De-Ath c748d33192 support list[str] | str as input 2024-10-10 22:41:57 +01:00
Willnow, Patrick edc15d0d7c rewrite oauth role management logic to allow any custom roles to be used for oauth role to open webui role mapping 2024-10-10 23:00:05 +02:00
JoeyShapiro df824db042 fix: disallow empty files 2024-10-10 15:33:27 -05:00
Timothy J. Baek e8babe62bc refac 2024-10-10 12:51:00 -07:00
Jannik Streidl 741230bcdb fix 2024-10-10 17:20:50 +02:00
Jannik Streidl 89c77f05a8 chromadb switch to cosine similarity 2024-10-10 16:46:14 +02:00
Timothy J. Baek ad31bd51fd refac 2024-10-09 12:36:52 -07:00
Timothy Jaeryang Baek 7e253df175 Merge pull request #6054 from jeeteshchel/bugfix/secure-cookie
fix: set token cookie secure and samesite per config
2024-10-09 12:15:19 -07:00
Timothy Jaeryang Baek d7a71f3b34 Merge pull request #6050 from RobinBially/main
feat: add qdrant vector db connector
2024-10-09 12:12:48 -07:00
Timothy Jaeryang Baek 36c9371227 Merge pull request #6062 from KarlLee830/translate
i18n: Update Chinese translation
2024-10-09 12:11:15 -07:00
Timothy J. Baek c157004e07 refac 2024-10-09 12:05:16 -07:00
Timothy J. Baek 451f1bae15 refac: embeddings function 2024-10-09 11:41:35 -07:00
Robin Bially 34150fc3ed add NO_LIMIT var 2024-10-09 18:34:04 +02:00
Robin Bially 54dc94317c improvements 2024-10-09 18:29:14 +02:00
KarlLee830 ce5143c535 i18n: Update Chinese translation 2024-10-09 23:14:14 +08:00
Jeetesh Chellani a2e889c8bb fix: set oauth token secure and samesite per config 2024-10-09 14:50:48 +03:00
Robin Bially 2c59f2dcaf fix critical bug 2024-10-09 13:23:04 +02:00
Robin Bially b56f77ed47 improvements 2024-10-09 13:10:23 +02:00
Robin Bially b185524a8c Merge branch 'open-webui:main' into main 2024-10-09 12:52:13 +02:00
Robin Bially 878a570a2c add qdrant as vector db 2024-10-09 12:51:43 +02:00
Timothy J. Baek b38e2fab32 enh: drag and drop chat exports 2024-10-09 00:13:54 -07:00
Timothy J. Baek 3516eea189 refac 2024-10-08 23:57:36 -07:00
Timothy J. Baek a3f2b7045c refac: dropzone body -> chat-container 2024-10-08 23:55:38 -07:00
Timothy J. Baek 37fdb0ea2e refac: move search to backend 2024-10-08 23:37:37 -07:00
Timothy J. Baek e66619262a refac 2024-10-08 22:34:17 -07:00
Timothy J. Baek d7a00af576 refac: convert chat.chat to json data type 2024-10-08 22:02:48 -07:00
Timothy Jaeryang Baek a04f22d55f Merge pull request #6044 from open-webui/main
dev
2024-10-08 20:30:00 -07:00
Timothy J. Baek caeca9d300 refac: styling 2024-10-08 20:29:33 -07:00
Timothy J. Baek 0da0e12096 refac: file download 2024-10-08 20:16:17 -07:00
Timothy J. Baek d48f54f7b2 enh: show both model id and hash 2024-10-08 19:32:53 -07:00
Timothy J. Baek 3391a855f0 fix 2024-10-08 17:03:42 -07:00
Timothy J. Baek a4870f4c91 fix 2024-10-08 16:35:21 -07:00
Timothy J. Baek 7f37b9340d enh: artifacts, overview back button
Co-Authored-By: Thomas Nordentoft <60035638+nordwestt@users.noreply.github.com>
2024-10-08 15:35:35 -07:00
Timothy Jaeryang Baek 187ea38beb Merge pull request #6020 from PeterDaveHello/Update-locale-zh-TW
i18n: Update and improve zh-TW Traditional Chinese translation
2024-10-08 15:21:20 -07:00
Timothy J. Baek 07a556df55 refac: landing page styling 2024-10-08 13:45:34 -07:00
Timothy J. Baek 0feaf6f649 refac 2024-10-08 13:39:27 -07:00
Timothy J. Baek 595cf191fb chore: rm documents 2024-10-08 13:38:16 -07:00
Timothy J. Baek dcab991d44 fix: model dump issue 2024-10-08 11:05:57 -07:00
Timothy J. Baek 457360dae7 refac 2024-10-07 22:53:36 -07:00
Timothy J. Baek 9bd5567552 enh: 'execute' event type 2024-10-07 22:50:57 -07:00
Timothy Jaeryang Baek d2a1f4a93d Merge pull request #6005 from Kazu-zamasu/dev
Up to date ja-JP translation.
2024-10-07 22:46:27 -07:00
Kazumichi Aoki 69bf610b17 Up to date ja-JP translation. 2024-10-08 14:42:04 +09:00
Timothy Jaeryang Baek 89a29653b8 Merge pull request #6003 from saravanan30erd/dev
add offline_mode and disable version check
2024-10-07 22:18:07 -07:00
Timothy J. Baek 345090c769 chore: i18n 2024-10-07 22:14:37 -07:00
Saravanan Palanisamy 16dd352524 add offline_mode and disable version check 2024-10-08 09:13:49 +04:00
Timothy J. Baek 8c677ff7a1 chore: i18n 2024-10-07 22:13:45 -07:00
Timothy Jaeryang Baek 929e665c5e Merge pull request #5998 from KarlLee830/translate
i18n: Update Chinese translation
2024-10-07 20:14:45 -07:00
Karl Lee 4d45083b5d i18n: Update Chinese translation 2024-10-08 10:39:45 +08:00
Karl Lee 3b88ba8812 i18n: Update Chinese translation 2024-10-08 10:36:39 +08:00
Timothy J. Baek 133ff406d7 i18n: french update
Co-Authored-By: Moguiy <101116631+moblangeois@users.noreply.github.com>
2024-10-07 18:33:14 -07:00
Timothy Jaeryang Baek d05c2f56ea Merge pull request #4715 from Peter-De-Ath/ollama-batch-embeddings
feat: support ollama batch processing embeddings
2024-10-07 18:32:08 -07:00
Timothy J. Baek 3c5b216612 fix 2024-10-07 18:26:03 -07:00
Timothy J. Baek f099b277c8 enh: youtube watch param support 2024-10-07 18:19:13 -07:00
Timothy J. Baek e7ae9a2107 refac 2024-10-07 17:39:06 -07:00
Peter De-Ath b242460874 formatting 2024-10-08 00:04:35 +01:00
Peter De-Ath 3fcdca2f28 backwards comp: use RAG_EMBEDDING_OPENAI_BATCH_SIZE 2024-10-08 00:04:35 +01:00
Peter De-Ath 885b9f1ece refactor: Update GenerateEmbeddingsForm to support batch processing
refactor: Update embedding batch size handling in RAG configuration

refactor: add query_doc query caching

refactor: update logging statements in generate_chat_completion function

change embedding_batch_size to Optional
2024-10-08 00:04:35 +01:00
Timothy J. Baek 09f34a7561 refac 2024-10-07 14:57:56 -07:00
Timothy J. Baek cf544b9e60 refac: styling 2024-10-07 14:51:41 -07:00
Timothy J. Baek 5a78f1d915 refac: collection styling 2024-10-07 14:49:26 -07:00
Timothy J. Baek 854024cdf8 refac: show chat menu in temp chat 2024-10-07 14:28:28 -07:00
Timothy Jaeryang Baek fe872aa3fc Merge pull request #5963 from aleixdorca/dev
i18n: Update catalan translation.json
2024-10-07 14:07:26 -07:00
Timothy J. Baek d9ccef8150 refac 2024-10-07 14:05:45 -07:00
Timothy J. Baek 958d882ff9 refac: knowledge file handling ui behaviour 2024-10-07 14:04:06 -07:00
Timothy J. Baek 48e7f47558 refac 2024-10-07 14:03:42 -07:00
Jannik Streidl 73c291193b fix: do not toggle collapsible if you click on a citation 2024-10-07 21:22:36 +02:00
Jannik Streidl 209948af6f styling 2024-10-07 21:15:10 +02:00
Jannik Streidl 9fc813cfa6 fix: only append if distances are available 2024-10-07 21:13:13 +02:00
Jannik Streidl b105efa05f enh: append citations with distance scores 2024-10-07 21:11:04 +02:00
Jannik Streidl 86caca495b enh: show source documents vector distance + cleaner source view 2024-10-07 21:04:06 +02:00
Peter Dave Hello 4eb47716e2 i18n: Update and improve zh-TW Traditional Chinese translation 2024-10-08 01:51:50 +08:00
Timothy Jaeryang Baek 3f892583c3 Merge pull request #5965 from open-webui/dev
fix: update info toast dismiss issue
2024-10-07 08:42:55 +02:00
Timothy J. Baek ed9fbe153b fix: update info toast dismiss issue 2024-10-06 23:41:50 -07:00
Aleix Dorca d255f5cf8d Update catalan translation.json 2024-10-07 08:20:36 +02:00
Timothy Jaeryang Baek ad7bc624c2 Merge pull request #5961 from open-webui/dev
refac: floating buttons
2024-10-07 07:55:39 +02:00
Timothy J. Baek 6e8e0454e4 refac: floating buttons 2024-10-06 22:55:10 -07:00
Timothy Jaeryang Baek 7cebcf064a Merge pull request #5958 from open-webui/dev
refac: floating buttons behaviour
2024-10-07 07:10:54 +02:00
Timothy J. Baek 1fc1bf5f0d refac: floating buttons behaviour 2024-10-06 22:09:58 -07:00
Timothy Jaeryang Baek bc29d5d3c3 Merge pull request #5956 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.32
2024-10-07 07:06:48 +02:00
Timothy J. Baek fe18aebdd9 doc: changelog 2024-10-06 22:06:24 -07:00
Timothy J. Baek 57df49274c refac: tools url param handling 2024-10-06 22:00:23 -07:00
Timothy J. Baek 9193c9bd7d refac: update info url 2024-10-06 21:52:13 -07:00
Timothy J. Baek 656e75372c enh: workspace models, prompts, tools, functions count 2024-10-06 21:51:03 -07:00
Timothy J. Baek 6f9080dfe0 fix: call mode not working in landing page 2024-10-06 21:42:47 -07:00
Timothy J. Baek 86f822fd9a chore: pyproject bumps 2024-10-06 21:06:47 -07:00
Timothy Jaeryang Baek 8b3972c6df Merge pull request #5954 from open-webui/dev
refac: url param tool_ids -> tools/tool-ids
2024-10-07 05:43:56 +02:00
Timothy J. Baek 58ca5e42c2 refac: url param tool_ids -> tools/tool-ids 2024-10-06 20:41:56 -07:00
Timothy Jaeryang Baek cd62d89dd9 Merge pull request #5953 from open-webui/dev
enh: floating buttons styling
2024-10-07 05:31:44 +02:00
Timothy J. Baek 3686cf7365 enh: esc to close floating buttons 2024-10-06 20:29:30 -07:00
Timothy J. Baek aaa4350eb4 refac: floating buttons styling 2024-10-06 20:26:35 -07:00
Timothy Jaeryang Baek 2113c2f57f Merge pull request #5952 from open-webui/dev
fix: web, youtube attachment issue
2024-10-07 04:44:45 +02:00
Timothy J. Baek ee22ba9676 fix: web, youtube attachment issue 2024-10-06 19:44:02 -07:00
Timothy Jaeryang Baek 466974f344 Merge pull request #5950 from open-webui/dev
refac: styling
2024-10-07 04:09:51 +02:00
Timothy J. Baek 95616e92d7 refac: styling 2024-10-06 19:09:27 -07:00
Timothy Jaeryang Baek c8c41e07e9 Merge pull request #5744 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.31
2024-10-07 03:50:06 +02:00
Timothy J. Baek d195b83c9e doc: changelog 2024-10-06 18:48:56 -07:00
Timothy J. Baek 0bff6d38ee doc: changelog 2024-10-06 18:35:04 -07:00
Timothy J. Baek b0bf6b5d31 doc: changelog 2024-10-06 18:29:57 -07:00
Timothy J. Baek 7abdf8e342 refac: wording 2024-10-06 18:28:41 -07:00
Timothy J. Baek cfb7357103 doc: changelog 2024-10-06 18:27:49 -07:00
Timothy J. Baek 05c15b017d fix: milvus 2024-10-06 17:58:09 -07:00
Timothy J. Baek c8e609c3d1 chore: format 2024-10-06 16:51:07 -07:00
Timothy J. Baek 4623fdfce2 refac: styling 2024-10-06 16:40:27 -07:00
Timothy J. Baek 1c1e6a7172 refac 2024-10-06 16:39:46 -07:00
Timothy J. Baek 297835628a refac 2024-10-06 16:32:44 -07:00
Timothy J. Baek 7b6723a955 refac: styling 2024-10-06 16:22:19 -07:00
Timothy J. Baek a40eb87c31 refac: styling 2024-10-06 16:17:38 -07:00
Timothy J. Baek 53954e4c05 feat: ask floating button 2024-10-06 16:14:12 -07:00
Timothy J. Baek 50b1a3471c refac 2024-10-06 15:30:16 -07:00
Timothy J. Baek dc6e68bd60 refac 2024-10-06 15:27:19 -07:00
Timothy J. Baek da1e88a427 enh: __task_body__ param 2024-10-06 14:56:49 -07:00
Timothy J. Baek d23f05fe0a refac 2024-10-06 14:52:12 -07:00
Timothy J. Baek 90effa925e fix 2024-10-06 13:14:35 -07:00
Timothy J. Baek 8a3c9b9a3e refac 2024-10-06 13:07:41 -07:00
Timothy J. Baek a7f543d737 refac 2024-10-06 13:00:53 -07:00
Timothy J. Baek 4b00036dbf refac: styling 2024-10-06 12:54:20 -07:00
Timothy J. Baek babfc97c90 enh: svg zoom pan 2024-10-06 12:51:29 -07:00
Timothy J. Baek 913620ff0c refac 2024-10-06 12:07:45 -07:00
Timothy J. Baek e6265897c8 refac: styling 2024-10-06 11:54:57 -07:00
Timothy J. Baek 91e1b548a9 refac: styling 2024-10-06 11:52:19 -07:00
Timothy J. Baek 8ad44cd690 enh: chat landing ui option 2024-10-06 11:45:13 -07:00
Timothy J. Baek 5f74cfaa51 enh: artifacts fullscreen 2024-10-06 11:26:02 -07:00
Timothy Jaeryang Baek 9ef3fb0bc7 Merge pull request #5940 from JEleniel/voice_i18n_sort
fix: sort the list of TTS voices and reformat the names
2024-10-06 20:12:47 +02:00
Timothy Jaeryang Baek 7d1890c7d1 Merge pull request #5938 from Nowheresly/fix5930
feat: db pool config #5935
2024-10-06 20:11:50 +02:00
JEleniel eb2aaea6cb fix, i18n - sort the list of TTS voices and reformat the names returned by the built in speech syntehsis 2024-10-06 12:56:30 -04:00
Sylvere Richard 9706b76b36 feat: db pool config #5935 2024-10-06 17:20:10 +02:00
Timothy J. Baek 059ac466e0 refac: styling 2024-10-06 02:49:16 -07:00
Timothy J. Baek 75780256d1 refac: styling 2024-10-06 02:04:41 -07:00
Timothy J. Baek b5efe15f73 refac 2024-10-06 01:58:33 -07:00
Timothy J. Baek be657f45e4 refac: styling 2024-10-06 01:50:06 -07:00
Timothy J. Baek f295e26f25 chore: bump 2024-10-06 01:39:22 -07:00
Timothy J. Baek 0591cd82d4 chore: format 2024-10-06 01:35:58 -07:00
Timothy J. Baek 70ca6527f6 enh: copy artifacts 2024-10-06 01:27:21 -07:00
Timothy J. Baek cdd024db81 refac: close artifacts if html codeblock is not found 2024-10-06 01:20:10 -07:00
Timothy J. Baek 3082d0de7a enh: apply edit to artifacts 2024-10-06 01:12:30 -07:00
Timothy J. Baek f5e10704c0 refac 2024-10-06 00:50:38 -07:00
Timothy J. Baek c90cd6019a refac 2024-10-06 00:46:43 -07:00
Timothy J. Baek c26d7ff463 refac 2024-10-06 00:42:24 -07:00
Timothy J. Baek b21af908a7 refac 2024-10-06 00:33:01 -07:00
Timothy J. Baek 097e90f1de refac 2024-10-06 00:30:50 -07:00
Timothy J. Baek ed47e24494 refac 2024-10-06 00:28:33 -07:00
Timothy J. Baek aeaf761ecd enh: automatic artifacts toggle 2024-10-06 00:14:05 -07:00
Timothy J. Baek 6d2eaaf602 refac 2024-10-06 00:09:37 -07:00
Timothy J. Baek 1815cfe99d refac 2024-10-06 00:03:58 -07:00
Timothy J. Baek 539e95a206 refac 2024-10-06 00:00:39 -07:00
Timothy J. Baek de59ecf8a3 feat: artifacts
Co-Authored-By: Andrew Tait Gehrhardt <134739775+atgehrhardt@users.noreply.github.com>
2024-10-05 23:58:02 -07:00
Timothy J. Baek 2981212dfa refac 2024-10-05 23:55:44 -07:00
Timothy J. Baek 7ee07df26a refac 2024-10-05 22:23:06 -07:00
Timothy J. Baek 008febb6d7 refac: styling 2024-10-05 22:18:05 -07:00
Timothy J. Baek 6417ccef00 chore: format 2024-10-05 21:29:08 -07:00
Timothy J. Baek 198749aed1 refac: styling 2024-10-05 21:27:47 -07:00
Timothy J. Baek 46bd97c100 refac: styling 2024-10-05 21:25:04 -07:00
Timothy J. Baek 1086a0e662 refac: styling 2024-10-05 21:16:20 -07:00
Timothy J. Baek 9586749314 refac: styling 2024-10-05 21:14:35 -07:00
Timothy J. Baek 54552c3d13 refac 2024-10-05 21:02:37 -07:00
Timothy J. Baek ed337b94e1 refac: styling 2024-10-05 20:50:58 -07:00
Timothy J. Baek 0f287c8a09 enh: floating codeblock buttons 2024-10-05 20:48:55 -07:00
Timothy J. Baek dc2e75017d refac 2024-10-05 20:16:46 -07:00
Timothy J. Baek c3862bc387 refac: code rendering 2024-10-05 19:58:27 -07:00
Timothy J. Baek 6fdfa62845 refac 2024-10-05 19:53:19 -07:00
Timothy J. Baek e0fc3e89a7 refac: i18n 2024-10-05 19:42:04 -07:00
Timothy Jaeryang Baek ed5669410b Merge pull request #5927 from KarlLee830/translate
i18n: Update Chinese translation
2024-10-06 04:38:24 +02:00
Timothy Jaeryang Baek 6d3d80b347 Merge pull request #5926 from Nowheresly/useNode22
Update to node 22
2024-10-06 04:37:42 +02:00
Karl Lee d84368a8f6 i18n: Update Chinese translation 2024-10-06 10:29:43 +08:00
Timothy J. Baek 5adf9ed445 refac 2024-10-05 19:03:39 -07:00
Timothy J. Baek 49e9275c5b refac: styling 2024-10-05 18:57:19 -07:00
Timothy J. Baek c746fd94cb refac 2024-10-05 18:43:17 -07:00
Timothy J. Baek 0bfe28711a enh: dark theme scrollbar 2024-10-05 18:28:40 -07:00
Timothy J. Baek c09e51c1bf fix: banner styling 2024-10-05 17:09:49 -07:00
Timothy J. Baek 3dd1d1bc4a fix 2024-10-05 17:05:26 -07:00
Timothy J. Baek a860e98ab9 refac: remember sidebar status 2024-10-05 17:00:56 -07:00
Timothy J. Baek ed1a2ab5e8 refac: explain button behaviour 2024-10-05 16:50:06 -07:00
Timothy J. Baek bbbd94f69c refac 2024-10-05 16:16:51 -07:00
Timothy J. Baek 2e067b0541 refac 2024-10-05 16:00:25 -07:00
Timothy J. Baek a0a79fbc5b refac: styling 2024-10-05 15:55:30 -07:00
Timothy J. Baek ecd6a135cd refac: styling 2024-10-05 15:47:51 -07:00
Timothy J. Baek 9c18cf204d refac 2024-10-05 15:10:55 -07:00
Timothy J. Baek a571ae4ec2 refac: only show resizer when controls is open 2024-10-05 14:54:15 -07:00
Timothy J. Baek 1558f64c48 refac: only show floating buttons when message.done 2024-10-05 14:51:28 -07:00
Timothy J. Baek 1739313c13 refac 2024-10-05 14:49:39 -07:00
Timothy J. Baek 1b8acc42b4 refac 2024-10-05 14:27:16 -07:00
Timothy J. Baek 1397bfa84c refac: navbar styling 2024-10-05 14:26:54 -07:00
Sylvere Richard c09af435ac WIP node 22
https://github.com/cypress-io/github-action/pull/1189
2024-10-05 22:39:24 +02:00
Timothy J. Baek f5004fd9d4 refac: styling 2024-10-05 13:26:52 -07:00
Timothy J. Baek fb58e842b7 refac: styling 2024-10-05 13:25:30 -07:00
Timothy Jaeryang Baek dcf8d9c7eb Merge pull request #5923 from Nowheresly/fixIntegTests
fix: fix cypress test on CI
2024-10-05 21:46:02 +02:00
Timothy Jaeryang Baek a61c54531c Merge pull request #5925 from Nowheresly/pythonActionUpd
Update to actions/setup-python@v5
2024-10-05 21:45:43 +02:00
Timothy J. Baek e184a65dea refac 2024-10-05 12:43:58 -07:00
Sylvere Richard fafeec3d86 Update to actions/setup-python@v5 2024-10-05 21:42:01 +02:00
Timothy J. Baek 55d0ecc879 refac 2024-10-05 12:41:18 -07:00
Timothy J. Baek 614f3d5f80 fix 2024-10-05 12:40:13 -07:00
Timothy J. Baek 46328333c7 refac 2024-10-05 12:38:44 -07:00
Sylvere Richard 582036fb3f fix: fix cypress test on CI 2024-10-05 21:38:20 +02:00
Timothy J. Baek 28602b12ea refac: dynamic lang support 2024-10-05 12:38:09 -07:00
Timothy J. Baek f5b6785e53 refac 2024-10-05 12:07:45 -07:00
Timothy J. Baek 81440460f2 feat: editable code block 2024-10-05 12:04:36 -07:00
Timothy Jaeryang Baek 82ac2e4edb Merge pull request #5920 from Nowheresly/baseImgDockerUpd
fix: ensure Dockerfile and github actions use the same nodejs version
2024-10-05 19:54:27 +02:00
Sylvere Richard fc44924256 fix: ensure Dockerfile and github actions use the same nodejs version 2024-10-05 19:48:56 +02:00
Timothy J. Baek 9ca2350a13 fix: call tts issue 2024-10-05 10:42:06 -07:00
Timothy J. Baek 12a6216477 refac: styling 2024-10-05 10:23:47 -07:00
Timothy J. Baek fb5fb27ebe fix 2024-10-05 10:20:11 -07:00
Timothy J. Baek cb0f759420 refac: knowledge collection uploading indicator 2024-10-05 10:18:43 -07:00
Timothy J. Baek 378223aedb refac: comments 2024-10-05 10:08:48 -07:00
Timothy J. Baek 61b147441c refac 2024-10-05 10:05:12 -07:00
Timothy J. Baek 1f9b5b6456 refac: retain metadata for collection 2024-10-05 09:58:46 -07:00
Timothy Jaeryang Baek 4ca870bf6d Merge pull request #5919 from Nowheresly/dockerwarn
refac: remove docker warnings during image build
2024-10-05 18:33:11 +02:00
Sylvere Richard 52a3ab5333 refac: remove docker warnings during image build 2024-10-05 12:30:47 +02:00
Timothy J. Baek ff22e2156d refac: styling 2024-10-05 03:29:55 -07:00
Timothy J. Baek e0c775f6e3 refac 2024-10-05 03:24:00 -07:00
Timothy J. Baek 7036166535 refac 2024-10-05 03:22:17 -07:00
Timothy J. Baek 2e231982a3 refac 2024-10-05 03:21:22 -07:00
Timothy J. Baek fe7e7abf15 refac 2024-10-05 03:19:55 -07:00
Timothy J. Baek 3e7a163660 refac: styling 2024-10-05 03:15:39 -07:00
Timothy J. Baek 0319e63999 enh: new landing page 2024-10-05 03:07:56 -07:00
Timothy J. Baek 0bd88090bb refac: DOCS_DIR deprecated 2024-10-05 01:45:22 -07:00
Timothy J. Baek ed3e1397ca refac 2024-10-05 01:40:10 -07:00
Timothy J. Baek 0ad35ffad9 feat: text select quick actions 2024-10-05 01:37:39 -07:00
Timothy J. Baek 1f488a0072 enh: sync confirm 2024-10-04 19:38:24 -07:00
Timothy J. Baek 493745a70b refac 2024-10-04 19:32:33 -07:00
Timothy J. Baek c400f40663 enh: knowledge search in model editor 2024-10-04 19:24:36 -07:00
Timothy J. Baek bc6113f4ba refac 2024-10-04 19:10:52 -07:00
Timothy J. Baek b6703be859 chore: format 2024-10-04 19:00:48 -07:00
Timothy Jaeryang Baek 62f30ce098 Merge pull request #5917 from KarlLee830/translate
i18n: Update Chinese translation
2024-10-05 03:59:17 +02:00
Timothy J. Baek 9f812e7022 enh: support non chrome browsers 2024-10-04 18:54:40 -07:00
Timothy J. Baek a909aa1c20 enh: sync directory 2024-10-04 18:44:57 -07:00
Timothy J. Baek e3889522d6 refac 2024-10-04 18:28:48 -07:00
Timothy J. Baek 79c005a041 refac: deprecate docs_dir 2024-10-04 18:22:55 -07:00
Timothy J. Baek a6c797d4c2 refac: process docs dir 2024-10-04 17:22:00 -07:00
Karl Lee 0e50c8bb31 i18n: Update Chinese translation 2024-10-05 08:12:14 +08:00
Timothy J. Baek 9ad5ffb8c1 chore: endpoint naming 2024-10-04 17:03:56 -07:00
Timothy J. Baek f052010d8e refac 2024-10-04 16:58:45 -07:00
Timothy J. Baek ee6e41b144 refac: legacy support 2024-10-04 16:56:20 -07:00
Timothy J. Baek 2e267b420a fix: model knowledge 2024-10-04 16:53:25 -07:00
Timothy J. Baek 0c618b8145 refac: legacy detection 2024-10-04 16:43:41 -07:00
Timothy J. Baek 8f41db2f2e refac: styling 2024-10-04 16:43:25 -07:00
Timothy J. Baek a53537ccde fix: disable empty fields 2024-10-04 16:18:44 -07:00
Timothy J. Baek 5017ca90ff refac 2024-10-04 16:16:16 -07:00
Timothy J. Baek f3ee07a8a2 enh: knowledge collection search 2024-10-04 12:35:06 -07:00
Timothy Jaeryang Baek 263cc71dd3 Merge pull request #5913 from calvarez8922/fr-translation
i18n: fix fr year translation on date
2024-10-04 21:02:39 +02:00
Timothy J. Baek 8f5e426a13 enh: important note 2024-10-04 11:26:06 -07:00
Timothy Jaeryang Baek 87700d02a7 Merge pull request #5911 from aleixdorca/dev
i18n: Updated Catalan Translation
2024-10-04 20:18:47 +02:00
Timothy J. Baek 8f22e911e0 fix: empty knowledge delete issue 2024-10-04 11:11:53 -07:00
Aleix Dorca 1647cbef21 Update catalan translation.json 2024-10-04 15:06:06 +02:00
Aleix Dorca 528ac51ba9 Update translation.json 2024-10-04 15:00:49 +02:00
Willnow, Patrick f751d22a20 Refinement 2024-10-04 13:26:49 +02:00
Timothy J. Baek 650ca95784 fix 2024-10-04 01:21:33 -07:00
Patrick Willnow 6ddd8c7241 fix logic 2024-10-04 10:14:20 +02:00
Timothy J. Baek d1b8af6220 chore: format 2024-10-04 01:04:04 -07:00
Timothy Jaeryang Baek 332ef045ee Merge pull request #5870 from KarlLee830/translate
i18n: Update Chinese translation
2024-10-04 10:01:56 +02:00
Timothy Jaeryang Baek 0876c9b5ef Merge pull request #5829 from jannikstdl/query-embedding-perf-fix
fix:  performance issues on large collections
2024-10-04 10:01:17 +02:00
Timothy Jaeryang Baek ebc7da6f82 Merge pull request #5861 from open-webui/projects
feat: knowledge/projects
2024-10-04 10:00:47 +02:00
Timothy J. Baek 1fe1c27220 refac: legacy support 2024-10-04 00:59:19 -07:00
Timothy J. Baek 8013c152d0 feat: edit file content support 2024-10-04 00:46:32 -07:00
Timothy J. Baek 630a78cead fix 2024-10-04 00:24:51 -07:00
Timothy J. Baek 17c772831d refac 2024-10-04 00:23:14 -07:00
Timothy J. Baek 6147f41589 refac: styling 2024-10-03 23:45:20 -07:00
Timothy J. Baek 5e70afc054 refac: styling 2024-10-03 23:43:41 -07:00
Timothy J. Baek 75fae69def refac: styling 2024-10-03 23:41:17 -07:00
Timothy J. Baek 90ef9f751a fix 2024-10-03 23:32:20 -07:00
Timothy J. Baek 544251a7fd feat: add text content 2024-10-03 23:24:44 -07:00
Timothy J. Baek 05970157f6 refac 2024-10-03 23:06:47 -07:00
Timothy J. Baek d834bd2a18 refac 2024-10-03 22:35:43 -07:00
Timothy J. Baek e9b68524e8 refac 2024-10-03 22:24:05 -07:00
Timothy J. Baek b291271df3 refac 2024-10-03 22:22:22 -07:00
Timothy J. Baek 9dd76b72b4 refac: styling 2024-10-03 21:34:53 -07:00
Timothy J. Baek 12977e07f3 feat: collection file content editor 2024-10-03 21:31:42 -07:00
Timothy J. Baek 29467a7057 fix 2024-10-03 21:12:35 -07:00
Timothy J. Baek b862dff185 refac 2024-10-03 21:10:33 -07:00
Timothy J. Baek 6747478f67 refac 2024-10-03 21:05:55 -07:00
Timothy J. Baek 124a17e826 refac 2024-10-03 20:58:56 -07:00
Timothy J. Baek 57360b7a61 refac 2024-10-03 20:51:21 -07:00
jose.c.alvarez.diaz 2d40efe9d6 bug: fix fr year translation 2024-10-03 20:27:30 -06:00
Willnow, Patrick 79b9c8a677 handling no claim received when nested expected 2024-10-04 00:05:36 +02:00
Willnow, Patrick 8e4776ada1 add handling nested claims... 2024-10-03 23:25:00 +02:00
Willnow, Patrick 5b2e1ca7cd add more logging 2024-10-03 23:06:05 +02:00
Willnow, Patrick 0a7373dae1 add pending as role fallback
add logging to determine correct handling of oauth roles
2024-10-03 22:56:52 +02:00
Willnow, Patrick c9d948f284 Remove copy pasta error of calling value on bool 2024-10-03 21:38:56 +02:00
Willnow, Patrick dc92178641 Fix missing key mapping 2024-10-03 20:55:32 +02:00
Timothy J. Baek 2fc07fd6a2 enh: vector db hash collision check 2024-10-03 06:53:21 -07:00
Timothy J. Baek 78413d0c2e enh: add/remove file from knowledge 2024-10-03 06:46:20 -07:00
Timothy J. Baek 1c01e52f7c refac: fileItem styling 2024-10-03 06:45:29 -07:00
Timothy J. Baek 15a3c6b171 refac 2024-10-03 06:45:15 -07:00
Timothy J. Baek d394f8b7be enh: add to vector db support 2024-10-03 06:44:17 -07:00
Timothy J. Baek 325ca98773 enh: vector db delete filter support 2024-10-03 06:43:50 -07:00
Patrick Willnow 9a691c0387 Add oauth role mapping
also add node env to allow local build to succeed
2024-10-03 11:12:14 +02:00
Timothy J. Baek 351b1dbf31 refac 2024-10-02 21:14:58 -07:00
Timothy J. Baek a2eadb30f5 refac 2024-10-02 20:42:10 -07:00
Timothy Jaeryang Baek 2e7e346e19 Merge pull request #5871 from tcgumus/dev
fix: Unsupported JSON schema type {type_}
2024-10-03 05:07:30 +02:00
Timothy Jaeryang Baek 7c145ce5d5 Merge pull request #5879 from cheahjs/feat/arbitrary-usage-info
feat: show the user the entirety of the usage response
2024-10-03 05:06:56 +02:00
Timothy Jaeryang Baek f19b61307d Merge pull request #5878 from cheahjs/feat/lazy-load-new-imports-for-mem
perf: lazy load big dependencies to reduce min memory usage
2024-10-03 00:15:03 +02:00
Jun Siang Cheah a8ec73d01e feat: show the user the entirety of the usage response 2024-10-02 23:14:08 +01:00
Jun Siang Cheah 318f61161e refac: lazy load big dependencies to reduce min memory usage 2024-10-02 22:18:42 +01:00
Timothy J. Baek 1a26e67611 refac: styling 2024-10-02 07:44:07 -07:00
Timothy J. Baek 6974cb248c refac 2024-10-02 07:37:01 -07:00
Timothy J. Baek dd5da10d2a feat: knowledge page 2024-10-02 07:35:43 -07:00
Timothy J. Baek 9c4b55c86a refac: knowledge fuzzy search 2024-10-02 06:21:16 -07:00
Timothy J. Baek 4aca1e86ad refac 2024-10-02 06:19:09 -07:00
Tuna Çağlar Gümüş b2f09e4623 Merge pull request #1 from tcgumus/dev-fix-unsupported-json-error
fix: Unsupported JSON schema type {type_}
2024-10-02 15:40:22 +03:00
t bdb2ce9448 fix: Unsupported JSON schema type {type_} 2024-10-02 15:34:48 +03:00
Karl Lee 973c08babd i18n: Update Chinese translation 2024-10-02 17:51:45 +08:00
Timothy J. Baek 7f51ef1838 refac 2024-10-01 23:21:33 -07:00
Timothy J. Baek 08969ecf89 refac: rename projects -> knowledge 2024-10-01 22:45:04 -07:00
Timothy J. Baek 3e012f0219 refac 2024-10-01 21:48:15 -07:00
Timothy J. Baek 5933d7a216 enh: sort by descending order 2024-10-01 21:35:18 -07:00
Timothy J. Baek 1b7d363d32 refac 2024-10-01 21:32:59 -07:00
Timothy J. Baek c2732a0990 refac 2024-10-01 17:46:56 -07:00
Timothy J. Baek c5eb0a9732 refac: documents -> projects 2024-10-01 17:35:35 -07:00
Timothy J. Baek bf57dd808e feat: project migration 2024-10-01 17:35:10 -07:00
Timothy Jaeryang Baek d353ea449a Merge pull request #5864 from sebdanielsson/remove-bun
build: Remove bun
2024-10-02 00:42:50 +02:00
Sebastian c85cdbde46 Delete bun.lockb 2024-10-02 00:00:46 +02:00
Timothy J. Baek fb083237cd refac 2024-10-01 14:00:19 -07:00
Timothy J. Baek a0fb4a9b84 refac 2024-10-01 13:13:39 -07:00
Timothy J. Baek 5c9dd25459 refac: files migration 2024-10-01 11:01:26 -07:00
Timothy J. Baek 0907c32e10 refac 2024-10-01 09:51:58 -07:00
Timothy J. Baek 4752df9bd8 refac 2024-10-01 09:40:08 -07:00
Timothy J. Baek 7b9b29253f Merge branch 'dev' of https://github.com/open-webui/open-webui into dev 2024-10-01 09:18:15 -07:00
Timothy Jaeryang Baek 126aff7a7d Merge pull request #5839 from open-webui/dependabot/pip/backend/dev/langchain-chroma-0.1.4
chore(deps): bump langchain-chroma from 0.1.2 to 0.1.4 in /backend
2024-10-01 17:58:14 +02:00
Timothy Jaeryang Baek 6ca1de3601 Merge pull request #5838 from open-webui/dependabot/pip/backend/dev/duckduckgo-search-approx-eq-6.2.13
chore(deps): update duckduckgo-search requirement from ~=6.2.11 to ~=6.2.13 in /backend
2024-10-01 17:57:59 +02:00
Timothy Jaeryang Baek fb8af637e1 Merge pull request #5837 from open-webui/dependabot/pip/backend/dev/aiohttp-3.10.8
chore(deps): bump aiohttp from 3.10.5 to 3.10.8 in /backend
2024-10-01 17:57:51 +02:00
Timothy Jaeryang Baek ab983887cc Merge pull request #5836 from open-webui/dependabot/pip/backend/dev/pandas-2.2.3
chore(deps): bump pandas from 2.2.2 to 2.2.3 in /backend
2024-10-01 17:57:40 +02:00
Timothy Jaeryang Baek 75c1f47500 Merge pull request #5835 from open-webui/dependabot/pip/backend/dev/pydantic-2.9.2
chore(deps): bump pydantic from 2.8.2 to 2.9.2 in /backend
2024-10-01 17:57:29 +02:00
Timothy Jaeryang Baek 3da981d9cd Merge pull request #5831 from aindriu80/main
Added Irish language support
2024-10-01 17:57:11 +02:00
Timothy Jaeryang Baek 8a0e30a901 Merge pull request #5830 from FINNSEEFLY/task/adjust-ru-localization
i18n: Update ru-RU localization
2024-10-01 17:56:46 +02:00
dependabot[bot] 736cbb961f chore(deps): bump langchain-chroma from 0.1.2 to 0.1.4 in /backend
Bumps [langchain-chroma](https://github.com/langchain-ai/langchain) from 0.1.2 to 0.1.4.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain-chroma==0.1.2...langchain-chroma==0.1.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 02:22:25 +00:00
dependabot[bot] 8d8c2752dc chore(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.12...v6.2.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 02:22:21 +00:00
dependabot[bot] d78a7d431d chore(deps): bump aiohttp from 3.10.5 to 3.10.8 in /backend
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.5 to 3.10.8.
- [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.5...v3.10.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 02:22:18 +00:00
dependabot[bot] 91ca5c424b chore(deps): bump pandas from 2.2.2 to 2.2.3 in /backend
Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.2.2 to 2.2.3.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Commits](https://github.com/pandas-dev/pandas/compare/v2.2.2...v2.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 02:22:08 +00:00
dependabot[bot] 7e1c55d2ed chore(deps): bump pydantic from 2.8.2 to 2.9.2 in /backend
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.8.2 to 2.9.2.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.8.2...v2.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 02:22:04 +00:00
Aindriú Mac Giolla Eoin 3a2c241450 Added Irish language support 2024-09-30 17:47:01 +01:00
FINNSEEFLY 13b6b43cea Adjust ru-RU localization 2024-09-30 18:36:02 +03:00
Timothy J. Baek c7a0e45bea refac 2024-09-30 16:32:38 +02:00
Jannik Streidl 6bff5a4d09 fix: do not embed the query every single time 2024-09-30 16:18:02 +02:00
Timothy J. Baek 37e0d47082 refac 2024-09-30 13:13:19 +02:00
Timothy J. Baek e6b91036e1 refac 2024-09-30 13:12:41 +02:00
Timothy J. Baek 79a83adc89 refac 2024-09-30 13:03:47 +02:00
Timothy J. Baek ffd598c5d7 enh: summary tag support 2024-09-30 12:50:53 +02:00
Timothy Jaeryang Baek d255251e5f Merge pull request #5813 from res0Nanz/patch-1
feat: add `apple-touch-icon`
2024-09-30 12:17:06 +02:00
Eman Lear 49fe04a627 frontend: add apple-touch-icon
With `apple-touch-icon`, mobile devices of particular OS can properly show icon when bookmarking the web page as a home screen application.
2024-09-30 15:20:57 +08:00
Timothy J. Baek 21c919988d refac 2024-09-30 01:01:39 +02:00
Timothy J. Baek 1c4b6b9cd9 refac 2024-09-30 01:00:13 +02:00
Timothy J. Baek 3899405864 refac 2024-09-30 00:39:30 +02:00
Timothy J. Baek 209828c7c3 refac: styling 2024-09-30 00:37:31 +02:00
Timothy J. Baek 7152af949b feat: compress audio
Co-Authored-By: Beck Bekmyradov <47065940+bekmuradov@users.noreply.github.com>
2024-09-30 00:30:12 +02:00
Timothy J. Baek 8206c47a47 refac 2024-09-29 23:20:37 +02:00
Timothy J. Baek f7aba20d79 refac 2024-09-29 23:11:22 +02:00
Timothy J. Baek 6afc686e17 refac 2024-09-29 23:08:55 +02:00
Timothy J. Baek 677c36c3aa refac 2024-09-29 22:55:53 +02:00
Timothy J. Baek 6d764ee55e feat: retrieval whole document mode 2024-09-29 22:52:27 +02:00
Timothy J. Baek 1d8b3b8c51 refac 2024-09-29 22:11:50 +02:00
Timothy J. Baek 92dd173b27 refac 2024-09-29 18:55:26 +02:00
Timothy J. Baek f2ec020b64 refac: styling 2024-09-29 18:41:27 +02:00
Timothy J. Baek d784d5c367 refac 2024-09-29 18:33:16 +02:00
Timothy J. Baek b3517c63e8 fix: multi model chat infinite loop issue 2024-09-29 18:29:50 +02:00
Timothy J. Baek 550075bba4 fix: action button not working 2024-09-29 18:24:44 +02:00
Timothy J. Baek c93a10388b refac 2024-09-28 19:51:28 +02:00
Timothy J. Baek 5a168ecc2a refac 2024-09-28 19:25:41 +02:00
Timothy J. Baek 276ce3374d refac 2024-09-28 19:17:21 +02:00
Timothy J. Baek e77c3ab043 refac 2024-09-28 19:16:52 +02:00
Timothy J. Baek d2e2e535dd refac 2024-09-28 10:55:52 +02:00
Timothy J. Baek 90ec458c4c enh: show extracted file content 2024-09-28 10:53:25 +02:00
Timothy Jaeryang Baek 9636913de0 Merge pull request #5777 from smonux/dev
Fix: sanitize function calling json
2024-09-28 10:07:52 +02:00
smonux e039b4ec54 Merge branch 'open-webui:dev' into dev 2024-09-28 06:01:26 +02:00
Timothy J. Baek 9d2ed3d2be refac 2024-09-28 02:56:56 +02:00
Timothy J. Baek b8b994a820 refac 2024-09-28 02:49:18 +02:00
Timothy J. Baek 00eb022450 refac 2024-09-28 02:38:59 +02:00
Timothy J. Baek 2428878f42 refac 2024-09-28 02:29:08 +02:00
Timothy J. Baek af57a2c153 refac 2024-09-28 02:23:09 +02:00
Timothy J. Baek 1b349016ff refac 2024-09-28 01:36:35 +02:00
Timothy J. Baek c1b4fbf5c2 refac 2024-09-28 01:35:52 +02:00
Timothy J. Baek a52e8cd537 refac 2024-09-28 01:35:31 +02:00
Timothy J. Baek 5b7cf88915 refac 2024-09-28 01:28:45 +02:00
Timothy J. Baek e1103305f5 refac: "rag" endpoints renamed to "retrieval" 2024-09-28 01:27:46 +02:00
Timothy Jaeryang Baek 6e9db3e3c8 Merge pull request #5773 from HaldiH/main
fix: Chat completion 401 when no Authorization header
2024-09-27 21:34:58 +02:00
Hugo Haldi eab30781e0 Chat completion 401 when no Authorization header
When we send a request to `/api/chat/completions` without the `Authorization` header, the server just crashes and creates a stack trace, returning "Internal Server Error" to the calling client. With this fix, the server sends a 401 to the client with the content `{"detail": "Not authenticated"}`.
2024-09-27 20:04:45 +02:00
Timothy Jaeryang Baek c30c876659 Merge pull request #5758 from kivvi3412/fix_o1_system_message
Fix: O1 does not support the system parameter
2024-09-27 19:44:16 +02:00
Timothy J. Baek 4ead3c5b80 chore: format 2024-09-27 19:43:40 +02:00
Timothy Jaeryang Baek f0f176b80f Merge pull request #5765 from aleixdorca/dev
Update catalan translation.json
2024-09-27 19:42:18 +02:00
kivvi 5b3ee30ca9 Merge remote-tracking branch 'origin/fix_o1_system_message' into fix_o1_system_message
# Conflicts:
#	backend/open_webui/apps/openai/main.py
2024-09-27 22:48:36 +08:00
kivvi e13614e11b Fix: O1 does not support the system parameter 2024-09-27 22:47:24 +08:00
Aleix Dorca 464b6a329e Update catalan translation.json 2024-09-27 16:11:52 +02:00
Timothy J. Baek 44d768ecf3 refac: do not wait for update check 2024-09-27 14:41:29 +02:00
Timothy J. Baek 0bd9d59c78 refac: update check timeout 2024-09-27 14:38:56 +02:00
kivvi be74a4c9c1 Fix: O1 does not support the system parameter 2024-09-27 20:18:13 +08:00
Timothy J. Baek 719f4da1dc fix: milvus collection creation issue 2024-09-26 22:59:09 +02:00
Timothy Jaeryang Baek fbc9634ffc Merge pull request #5742 from not-a-ethan/corrected-link
FIX: Migration link updated
2024-09-26 22:41:04 +02:00
Ethan 619c81472b Migration link updated 2024-09-26 16:27:49 -04:00
smonux d8f71e1d7f Some models produce almost correct json during function calling, but with additional data before of after it. This solves it. 2024-09-26 22:02:56 +02:00
Timothy J. Baek 1715446b13 fix: call mode persisting after width change issue 2024-09-26 21:45:19 +02:00
Timothy Jaeryang Baek 8204f06485 Merge pull request #5741 from open-webui/main
dev
2024-09-26 21:34:16 +02:00
Timothy J. Baek 4c92a0f571 chore: chromadb, pymilvus bump 2024-09-26 21:33:37 +02:00
Timothy J. Baek 1d225dd804 fix: chat pdf, txt export issue 2024-09-26 20:59:25 +02:00
Timothy J. Baek 15bd5ebd7b Update README.md
Co-Authored-By: Aam Surganda <74174455+aamsur-933@users.noreply.github.com>
2024-09-26 15:57:43 +02:00
Timothy Jaeryang Baek 3af50f08bd Merge pull request #5728 from sp301415/dev
fix: Fix OpenAI batch embedding
2024-09-26 14:08:11 +02:00
Hwang In Tak 4fe1f2487d fix: Fix OpenAI batch embedding 2024-09-26 20:48:14 +09:00
Hwang In Tak a4bc0b2829 fix: Fix OpenAI batch embedding 2024-09-26 20:39:40 +09:00
Timothy Jaeryang Baek 7b8f923981 Merge pull request #5711 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.30
2024-09-26 04:13:54 +02:00
Timothy J. Baek be41994d40 Update CHANGELOG.md 2024-09-26 04:12:19 +02:00
Timothy J. Baek 5361896411 doc: changelog 2024-09-26 04:11:13 +02:00
Timothy J. Baek c754c53906 refac 2024-09-26 04:05:28 +02:00
Timothy J. Baek 78d6647885 refac 2024-09-26 03:25:44 +02:00
Timothy J. Baek 4eeb669ac3 refac 2024-09-26 03:21:37 +02:00
Timothy J. Baek 26465f3e92 chore: bump 2024-09-26 03:14:06 +02:00
Timothy J. Baek 9d25207b83 refac 2024-09-26 03:13:38 +02:00
Timothy Jaeryang Baek cc19b8049a Merge pull request #5710 from kivvi3412/fix_o1_max_tokens
Fix: o1 input parameter must be max_completion_tokens
2024-09-26 03:03:15 +02:00
Timothy J. Baek 3a163b6392 fix: pip install database save issue 2024-09-26 03:02:08 +02:00
kivvi 405d0561df Fix: o1 input parameter must be max_completion_tokens 2024-09-26 08:49:59 +08:00
Timothy J. Baek ee33b4e2a3 fix: ollama /embed form_data 2024-09-25 22:34:02 +02:00
Timothy J. Baek 888479aaf0 refac: update toast dismiss behaviour 2024-09-25 20:47:04 +02:00
Timothy Jaeryang Baek 82cda6e522 Merge pull request #5699 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.29
2024-09-25 15:46:39 +02:00
Timothy J. Baek 119a7f1933 doc: changelog 2024-09-25 15:45:36 +02:00
Timothy J. Baek 6c4445d545 fix: filter outlet issue 2024-09-25 13:44:06 +02:00
Timothy J. Baek 92b1acd6fb fix: RESET_CONFIG_ON_START not working 2024-09-25 01:06:11 +02:00
Timothy J. Baek 1767b64135 fix: 'call' url search param not working 2024-09-25 01:02:48 +02:00
Timothy Jaeryang Baek e030f261d1 Merge pull request #5675 from sp301415/dev
fix: Fix KaTeX Rendering (Followup)
2024-09-24 20:58:22 +02:00
Hwang In Tak d501ece247 fix: Fix KaTeX corner cases 2024-09-25 02:15:53 +09:00
Timothy Jaeryang Baek 534e4c90ca Merge pull request #5674 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.28
2024-09-24 18:52:23 +02:00
Timothy J. Baek 40f2c3521b doc: changelog 2024-09-24 18:50:47 +02:00
Timothy J. Baek 07b1327708 fix 2024-09-24 18:32:14 +02:00
Timothy J. Baek 525095b3de fix: websearch not working issue
#5672
2024-09-24 18:25:21 +02:00
Timothy Jaeryang Baek f84513e856 Merge pull request #5673 from open-webui/dev
refac: readme
2024-09-24 18:19:09 +02:00
Timothy J. Baek bf423b8577 refac: readme 2024-09-24 18:18:32 +02:00
Timothy Jaeryang Baek ba20c71963 Merge pull request #5661 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.27
2024-09-24 18:13:08 +02:00
Timothy J. Baek 7bbc57f225 doc: changelog 2024-09-24 18:12:47 +02:00
Timothy J. Baek e703e172e2 chore: format 2024-09-24 18:10:14 +02:00
Timothy Jaeryang Baek 3ff52fd1ad Merge pull request #5655 from sp301415/dev
fix: Fix KaTeX Rendering
2024-09-24 17:44:08 +02:00
Timothy J. Baek e19406cdd7 fix 2024-09-24 17:43:43 +02:00
Hwang In Tak 30e65b33f6 fix: Add comments 2024-09-25 00:41:08 +09:00
Hwang In Tak 3f1255b39e fix: Change inline and block delimiters 2024-09-25 00:10:49 +09:00
Timothy J. Baek 71743b25fe chore: format 2024-09-24 16:42:42 +02:00
Timothy Jaeryang Baek c7e93b32c5 Merge pull request #5660 from open-webui/pypi-release
fix: open-webui serve
2024-09-24 16:36:37 +02:00
Timothy J. Baek 3cee507687 fix: dev2 2024-09-24 16:19:24 +02:00
Timothy J. Baek ff651ddc36 fix: dev1 2024-09-24 16:07:49 +02:00
Timothy Jaeryang Baek c0738cef26 Merge pull request #5659 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.26
2024-09-24 15:41:42 +02:00
Timothy J. Baek a44e9a8dda refac 2024-09-24 15:41:23 +02:00
Timothy J. Baek 38b9a63fa5 refac 2024-09-24 15:40:37 +02:00
Timothy J. Baek 2d60e42258 doc: changelog 2024-09-24 15:40:01 +02:00
Timothy Jaeryang Baek 60ac69eb27 Merge pull request #5656 from OriginalSimon/dev
i18n: Update Ukrainian translation
2024-09-24 15:33:15 +02:00
Timothy J. Baek 504d910557 fix: no running event loop issue 2024-09-24 15:31:55 +02:00
Hwang In Tak e48d66f918 fix: Remove unnecessary logging 2024-09-24 22:11:05 +09:00
Hwang In Tak 0bfbace9aa fix: Simplify regex 2024-09-24 22:00:01 +09:00
Timothy Jaeryang Baek 019cf8199f Merge pull request #5657 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.25
2024-09-24 14:07:23 +02:00
Timothy J. Baek c5f85eed92 doc: changelog 2024-09-24 14:07:13 +02:00
Timothy J. Baek 21719ccdf1 chore: format 2024-09-24 14:05:44 +02:00
Timothy J. Baek 299b3d72cf fix: rate responses 2024-09-24 14:02:41 +02:00
Timothy J. Baek 85e9e231ed fix: image generation 2024-09-24 13:54:34 +02:00
Simon f382a78e31 Update translation.json 2024-09-24 13:47:51 +02:00
Hwang In Tak 377cc427b6 fix: Remove unnecessary logging 2024-09-24 20:40:50 +09:00
Timothy Jaeryang Baek 7ec72679f0 Merge pull request #5599 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.24
2024-09-24 13:32:00 +02:00
Timothy J. Baek 31b311c3c9 refac
Co-Authored-By: MicroDev <70126934+microdev1@users.noreply.github.com>
2024-09-24 13:28:00 +02:00
Timothy J. Baek e24ab4c6d2 doc: changelog 2024-09-24 13:23:34 +02:00
Timothy J. Baek 6739983cf1 refac: deprecate interface "stream response" settings for advanced params 2024-09-24 12:49:35 +02:00
Timothy J. Baek ff00815b61 chore: format 2024-09-24 12:41:44 +02:00
Timothy J. Baek 8f6a927be3 enh: update info toast 2024-09-24 12:40:13 +02:00
Timothy J. Baek f6add92702 chore: format 2024-09-24 11:35:51 +02:00
Timothy Jaeryang Baek deedfdceae Merge pull request #5632 from jannikstdl/dev
enh: open PDF citations on the associated page
2024-09-24 11:27:53 +02:00
Timothy J. Baek e268ee5675 enh: audio/x-m4a support 2024-09-24 11:00:47 +02:00
Timothy J. Baek 33d8d818bd fix 2024-09-24 10:54:49 +02:00
Timothy Jaeryang Baek 9a81a37008 Merge pull request #5645 from kivvi3412/main
[feat] Set whether to stream individually for the model
2024-09-24 10:52:00 +02:00
Timothy Jaeryang Baek 8e620b0c2c Merge pull request #5649 from EtiennePerot/temp-file-close
fix: close temporary file after creating it
2024-09-24 10:49:38 +02:00
Hwang In Tak 214546399a fix: fix katex rendering 2024-09-24 16:58:15 +09:00
Etienne Perot fdd27aa321 fix: close temporary file after creating it.
This fixes "The process cannot access the file
because it is being used by another process"
errors on Windows.

The file is still automatically deleted by the
`os.unlink` call later in the function.

Updates #5606
Fixes #5642
2024-09-23 23:22:47 -07:00
kivvi 4ebff2c5ce Merge remote-tracking branch 'origin/main' 2024-09-24 12:29:36 +08:00
kivvi 8266d343bf Merge remote-tracking branch 'origin/main' 2024-09-24 12:29:11 +08:00
kivvi 36ddb19023 Merge remote-tracking branch 'origin/main' 2024-09-24 12:17:40 +08:00
kivvi ebc410d8d4 Merge remote-tracking branch 'origin/main' 2024-09-24 12:17:33 +08:00
kivvi 1772db4712 Merge remote-tracking branch 'origin/main' 2024-09-24 12:14:58 +08:00
kivvi 40d7d7d6dd Set whether to stream responses for this model individually, overriding the global setting 2024-09-24 12:14:51 +08:00
kivvi 0d01ea5f2f Set whether to stream responses for this model individually, overriding the global setting 2024-09-24 11:51:47 +08:00
Timothy J. Baek 822c47c171 fix 2024-09-24 01:35:34 +02:00
Timothy J. Baek fdf02c4e86 fix 2024-09-24 01:12:49 +02:00
Timothy J. Baek 398bc96b1a refac 2024-09-24 01:05:46 +02:00
Timothy J. Baek ad82eae6a9 feat: create new message pair on cmd+shift+enter 2024-09-24 00:57:28 +02:00
Timothy J. Baek d7b64ff447 enh: infinite scroll messages 2024-09-24 00:27:22 +02:00
Timothy J. Baek 93cb2be35d refac 2024-09-23 23:55:18 +02:00
Timothy J. Baek 8f1b9bdf8a refac 2024-09-23 23:52:45 +02:00
Jannik S. f69956bda8 fix: only append link with page attribute if metatada.page is available 2024-09-23 23:43:50 +02:00
Timothy J. Baek e06667ead8 refac 2024-09-23 23:39:33 +02:00
Timothy Jaeryang Baek 35f64cc53f Merge pull request #5640 from open-webui/messages-render-optimisation
refac: messages render optimisation
2024-09-23 23:27:08 +02:00
Timothy J. Baek 12f95555fc fix 2024-09-23 23:23:08 +02:00
Timothy J. Baek 84c1810b6e refac 2024-09-23 23:20:27 +02:00
Timothy J. Baek ba39f9bf56 refac 2024-09-23 17:40:24 +02:00
Jannik Streidl 8eb82265d0 Remove animation import - unsure if animations are desired 2024-09-23 15:50:46 +02:00
Jannik Streidl ac277e8e9e enh: CitationsModal 2024-09-23 15:48:12 +02:00
Timothy J. Baek b1521cacad refac: WIP 2024-09-23 14:24:50 +02:00
Timothy J. Baek 5978e7c9a6 refac: wip 2024-09-23 01:36:46 +02:00
Timothy J. Baek fd5e8b4fcf refac: deprecate messages for history 2024-09-23 00:55:13 +02:00
Timothy Jaeryang Baek 291b6dd744 Merge pull request #5613 from cevheri/dev
Turkish Language Support has been updated
2024-09-22 20:33:29 +02:00
Timothy J. Baek 73ff524a8f refac 2024-09-22 20:31:53 +02:00
cevheri c2b5bf2130 Turkish Language Support has been fixed. 2024-09-22 21:20:43 +03:00
Timothy J. Baek 8532f9da03 fix: many model chat save as copy issue 2024-09-22 20:16:45 +02:00
Timothy Jaeryang Baek 2cfe6830df Merge pull request #5595 from sebdanielsson/dev
feat: Add more variables to prompts
2024-09-22 16:47:51 +02:00
Timothy J. Baek 44355a4bdc enh: 'model' url search param added 2024-09-22 14:52:30 +02:00
Timothy J. Baek 47a858393b fix: hide mobile sidebar after menu button click 2024-09-22 14:50:24 +02:00
Timothy J. Baek d3e80f515d enh: hide pinned chats when searching 2024-09-22 14:49:53 +02:00
Timothy Jaeryang Baek c5b93ca631 Merge pull request #5601 from JamesClarke7283/extend-num-predict-param
fix: Extend num predict param
2024-09-22 14:28:00 +02:00
Timothy J. Baek 3335eee1b9 refac: do not set max for input 2024-09-22 14:27:52 +02:00
Sebastian 29175405a6 Add weekday, timezone, language variables to system prompts 2024-09-22 12:24:05 +02:00
Sebastian d53062a9b0 Add more variables to prompts 2024-09-22 11:49:18 +02:00
James Clarke 96ef15362a Increased max_tokens (num_predict) to 131072, fixes #5600 2024-09-22 05:43:04 +01:00
Timothy J. Baek d6d6098378 refac 2024-09-22 02:57:51 +02:00
Timothy J. Baek 620e629edc refac 2024-09-22 02:57:38 +02:00
Timothy J. Baek c292fd89f9 refac 2024-09-22 02:55:45 +02:00
Timothy J. Baek c346130774 feat: overview favourite response 2024-09-22 02:53:38 +02:00
Timothy J. Baek 768717aaf9 refac 2024-09-22 02:35:25 +02:00
Timothy J. Baek d055e1f888 refac 2024-09-22 02:14:59 +02:00
Timothy J. Baek 5f84145a2d enh: socket full redis support 2024-09-22 02:12:55 +02:00
Timothy J. Baek 47a9395a22 refac: styling 2024-09-22 01:42:18 +02:00
Timothy J. Baek 8dbac0f7e3 enh: show model hash and modified ts 2024-09-21 22:12:41 +02:00
Timothy Jaeryang Baek 6b463164f4 Merge pull request #5584 from open-webui/dev
fix: node tooltip xss issue
2024-09-21 21:44:25 +02:00
Timothy J. Baek 5f15e9ee68 fix: node tooltip xss issue
Co-Authored-By: Valentino Stillhardt <4715129+Fusseldieb@users.noreply.github.com>
2024-09-21 21:43:54 +02:00
Timothy Jaeryang Baek 54d63ece6f Merge pull request #5583 from open-webui/dev
refac
2024-09-21 21:35:54 +02:00
Timothy J. Baek e35883ca9c fix: openai payload issue 2024-09-21 21:35:00 +02:00
Timothy Jaeryang Baek 19e49e43cb Merge pull request #5578 from aleixdorca/dev
i18n: Update catalan translation.json
2024-09-21 17:37:41 +02:00
Aleix Dorca e429b5548c Update catalan translation.json 2024-09-21 17:27:32 +02:00
Timothy J. Baek 839dacc4a4 refac 2024-09-21 15:53:29 +02:00
Timothy J. Baek 4225591a26 fix 2024-09-21 15:48:52 +02:00
Timothy J. Baek b4adffc3af fix 2024-09-21 15:48:16 +02:00
Timothy J. Baek 8fc178ae91 refac: chat controls size 2024-09-21 15:46:39 +02:00
Timothy J. Baek 580ad46036 refac: speedRate -> playbackRate 2024-09-21 15:42:39 +02:00
Timothy Jaeryang Baek f47dffe6e1 Merge pull request #5576 from open-webui/dev
fix: WEBUI_AUTH=False not working issue
2024-09-21 15:35:55 +02:00
Timothy J. Baek 00f6b4bf09 refac 2024-09-21 15:35:35 +02:00
Timothy J. Baek 9126ceac08 fix: WEBUI_AUTH=False not working issue 2024-09-21 15:33:34 +02:00
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
427 changed files with 37304 additions and 13005 deletions
+4
View File
@@ -8,6 +8,10 @@ assignees: ''
# Bug Report
**Important: Before submitting a bug report, please check whether a similar issue or feature request has already been posted in the Issues or Discussions section. It's likely we're already tracking it. In case of uncertainty, initiate a discussion post first. This helps us all to efficiently focus on improving the project.**
**Let's collaborate respectfully. If you bring negativity, please understand our capacity to engage may be limited. If you're open to learning and communicating constructively, we're more than happy to assist you. Remember, Open WebUI is a volunteer-driven project maintained by a single maintainer, supported by our amazing contributors who also manage full-time jobs. We respect your time; please respect ours. If you have an issue, We highly encourage you to submit a pull request or to fork the project. We actively work to prevent contributor burnout to preserve the quality and continuity of Open WebUI.**
## Installation Method
[Describe the method you used to install the project, e.g., git clone, Docker, pip, etc.]
@@ -6,6 +6,12 @@ labels: ''
assignees: ''
---
# Feature Request
**Important: Before submitting a feature request, please check whether a similar issue or feature request has already been posted in the Issues or Discussions section. It's likely we're already tracking it. In case of uncertainty, initiate a discussion post first. This helps us all to efficiently focus on improving the project.**
**Let's collaborate respectfully. If you bring negativity, please understand our capacity to engage may be limited. If you're open to learning and communicating constructively, we're more than happy to assist you. Remember, Open WebUI is a volunteer-driven project maintained by a single maintainer, supported by our amazing contributors who also manage full-time jobs. We respect your time; please respect ours. If you have an issue, We highly encourage you to submit a pull request or to fork the project. We actively work to prevent contributor burnout to preserve the quality and continuity of Open WebUI.**
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+31 -48
View File
@@ -56,25 +56,19 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get version number from package.json
id: get_version
run: |
VERSION=$(jq -r '.version' package.json)
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Extract metadata for Docker images
- name: Extract metadata for Docker images (default latest tag)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.FULL_IMAGE_NAME }}
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=${{ steps.get_version.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }}
type=ref,event=branch
type=ref,event=tag
type=sha,prefix=git-
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
flavor: |
latest=${{ github.ref == 'refs/heads/main' }}
- name: Extract metadata for Docker cache
id: cache-meta
@@ -88,7 +82,7 @@ jobs:
prefix=cache-${{ matrix.platform }}-
latest=false
- name: Build Docker image
- name: Build Docker image (latest)
uses: docker/build-push-action@v5
id: build
with:
@@ -96,8 +90,7 @@ jobs:
push: true
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push=true
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }}
cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
build-args: |
@@ -160,25 +153,21 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get version number from package.json
id: get_version
run: |
VERSION=$(jq -r '.version' package.json)
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Extract metadata for Docker images
- name: Extract metadata for Docker images (cuda tag)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.FULL_IMAGE_NAME }}
tags: |
type=raw,value=latest-cuda,enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=${{ steps.get_version.outputs.version }}-cuda,enable=${{ github.ref == 'refs/heads/main' }}
type=ref,event=branch,suffix=-cuda
type=ref,event=tag,suffix=-cuda
type=sha,prefix=git-,suffix=-cuda
type=semver,pattern={{version}},suffix=-cuda
type=semver,pattern={{major}}.{{minor}},suffix=-cuda
type=ref,event=branch
type=ref,event=tag
type=sha,prefix=git-
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda
flavor: |
latest=${{ github.ref == 'refs/heads/main' }}
suffix=-cuda,onlatest=true
- name: Extract metadata for Docker cache
id: cache-meta
@@ -200,8 +189,7 @@ jobs:
push: true
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push=true
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }}
cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
build-args: |
@@ -265,25 +253,21 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get version number from package.json
id: get_version
run: |
VERSION=$(jq -r '.version' package.json)
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Extract metadata for Docker images
- name: Extract metadata for Docker images (ollama tag)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.FULL_IMAGE_NAME }}
tags: |
type=raw,value=latest-ollama,enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=${{ steps.get_version.outputs.version }}-ollama,enable=${{ github.ref == 'refs/heads/main' }}
type=ref,event=branch,suffix=-ollama
type=ref,event=tag,suffix=-ollama
type=sha,prefix=git-,suffix=-ollama
type=semver,pattern={{version}},suffix=-ollama
type=semver,pattern={{major}}.{{minor}},suffix=-ollama
type=ref,event=branch
type=ref,event=tag
type=sha,prefix=git-
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=ollama
flavor: |
latest=${{ github.ref == 'refs/heads/main' }}
suffix=-ollama,onlatest=true
- name: Extract metadata for Docker cache
id: cache-meta
@@ -305,8 +289,7 @@ jobs:
push: true
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push=true
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }}
cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
build-args: |
@@ -326,9 +309,10 @@ jobs:
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
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
@@ -380,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
@@ -439,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
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
+2 -2
View File
@@ -21,7 +21,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20' # Or specify any other version you want to use
node-version: '22' # Or specify any other version you want to use
- name: Install Dependencies
run: npm install
@@ -48,7 +48,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
- name: Install Dependencies
run: npm ci
+7 -4
View File
@@ -85,7 +85,7 @@ jobs:
# - uses: actions/checkout@v4
# - name: Set up Python
# uses: actions/setup-python@v4
# uses: actions/setup-python@v5
# with:
# python-version: ${{ matrix.python-version }}
@@ -157,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
@@ -182,9 +182,12 @@ jobs:
WEBUI_SECRET_KEY: secret-key
GLOBAL_LOG_LEVEL: debug
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
DATABASE_POOL_SIZE: 10
DATABASE_POOL_MAX_OVERFLOW: 10
DATABASE_POOL_TIMEOUT: 30
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
@@ -230,7 +233,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 -1
View File
@@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Use Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
- name: Use Bun
uses: oven-sh/setup-bun@v1
- name: Install dependencies
+1
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main # or whatever branch you want to use
- pypi-release
jobs:
release:
+298
View File
@@ -5,6 +5,304 @@ 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.33] - 2024-10-24
### Added
- **🏆 Evaluation Leaderboard**: Easily track your performance through a new leaderboard system where your ratings contribute to a real-time ranking based on the Elo system. Sibling responses (regenerations, many model chats) are required for your ratings to count in the leaderboard. Additionally, you can opt-in to share your feedback history and be part of the community-wide leaderboard. Expect further improvements as we refine the algorithm—help us build the best community leaderboard!
- **⚔️ Arena Model Evaluation**: Enable blind A/B testing of models directly from Admin Settings > Evaluation for a true side-by-side comparison. Ideal for pinpointing the best model for your needs.
- **🎯 Topic-Based Leaderboard**: Discover more accurate rankings with experimental topic-based reranking, which adjusts leaderboard standings based on tag similarity in feedback. Get more relevant insights based on specific topics!
- **📁 Folders Support for Chats**: Organize your chats better by grouping them into folders. Drag and drop chats between folders and export them seamlessly for easy sharing or analysis.
- **📤 Easy Chat Import via Drag & Drop**: Save time by simply dragging and dropping chat exports (JSON) directly onto the sidebar to import them into your workspace—streamlined, efficient, and intuitive!
- **📚 Enhanced Knowledge Collection**: Now, you can reference individual files from a knowledge collection—ideal for more precise Retrieval-Augmented Generations (RAG) queries and document analysis.
- **🏷️ Enhanced Tagging System**: Tags now take up less space! Utilize the new 'tag:' query system to manage, search, and organize your conversations more effectively without cluttering the interface.
- **🧠 Auto-Tagging for Chats**: Your conversations are now automatically tagged for improved organization, mirroring the efficiency of auto-generated titles.
- **🔍 Backend Chat Query System**: Chat filtering has become more efficient, now handled through the backend\*\* instead of your browser, improving search performance and accuracy.
- **🎮 Revamped Playground**: Experience a refreshed and optimized Playground for smoother testing, tweaks, and experimentation of your models and tools.
- **🧩 Token-Based Text Splitter**: Introducing token-based text splitting (tiktoken), giving you more precise control over how text is processed. Previously, only character-based splitting was available.
- **🔢 Ollama Batch Embeddings**: Leverage new batch embedding support for improved efficiency and performance with Ollama embedding models.
- **🔍 Enhanced Add Text Content Modal**: Enjoy a cleaner, more intuitive workflow for adding and curating knowledge content with an upgraded input modal from our Knowledge workspace.
- **🖋️ Rich Text Input for Chats**: Make your chat inputs more dynamic with support for rich text formatting. Your conversations just got a lot more polished and professional.
- **⚡ Faster Whisper Model Configurability**: Customize your local faster whisper model directly from the WebUI.
- **☁️ Experimental S3 Support**: Enable stateless WebUI instances with S3 support, greatly enhancing scalability and balancing heavy workloads.
- **🔕 Disable Update Toast**: Now you can streamline your workspace even further—choose to disable update notifications for a more focused experience.
- **🌟 RAG Citation Relevance Percentage**: Easily assess citation accuracy with the addition of relevance percentages in RAG results.
- **⚙️ Mermaid Copy Button**: Mermaid diagrams now come with a handy copy button, simplifying the extraction and use of diagram contents directly in your workflow.
- **🎨 UI Redesign**: Major interface redesign that will make navigation smoother, keep your focus where it matters, and ensure a modern look.
### Fixed
- **🎙️ Voice Note Mic Stopping Issue**: Fixed the issue where the microphone stayed active after ending a voice note recording, ensuring your audio workflow runs smoothly.
### Removed
- **👋 Goodbye Sidebar Tags**: Sidebar tag clutter is gone. Weve shifted tag buttons to more effective query-based tag filtering for a sleeker, more agile interface.
## [0.3.32] - 2024-10-06
### Added
- **🔢 Workspace Enhancements**: Added a display count for models, prompts, tools, and functions in the workspace, providing a clear overview and easier management.
### Fixed
- **🖥️ Web and YouTube Attachment Fix**: Resolved an issue where attaching web links and YouTube videos was malfunctioning, ensuring seamless integration and display within chats.
- **📞 Call Mode Activation on Landing Page**: Fixed a bug where call mode was not operational from the landing page.
### Changed
- **🔄 URL Parameter Refinement**: Updated the 'tool_ids' URL parameter to 'tools' or 'tool-ids' for more intuitive and consistent user experience.
- **🎨 Floating Buttons Styling Update**: Refactored the styling of floating buttons to intelligently adjust to the left side when there isn't enough room on the right, improving interface usability and aesthetic.
- **🔧 Enhanced Accessibility for Floating Buttons**: Implemented the ability to close floating buttons with the 'Esc' key, making workflow smoother and more efficient for users navigating via keyboard.
- **🖇️ Updated Information URL**: Information URLs now direct users to a general release page rather than a version-specific URL, ensuring access to the latest and relevant details all in one place.
- **📦 Library Dependencies Update**: Upgraded dependencies to ensure compatibility and performance optimization for pip installs.
## [0.3.31] - 2024-10-06
### Added
- **📚 Knowledge Feature**: Reimagined documents feature, now more performant with a better UI for enhanced organization; includes streamlined API integration for Retrieval-Augmented Generation (RAG). Detailed documentation forthcoming: https://docs.openwebui.com/
- **🌐 New Landing Page**: Freshly designed landing page; toggle between the new UI and the classic chat UI from Settings > Interface for a personalized experience.
- **📁 Full Document Retrieval Mode**: Toggle between full document retrieval or traditional snippets by clicking on the file item. This mode enhances document capabilities and supports comprehensive tasks like summarization by utilizing the entire content instead of RAG.
- **📄 Extracted File Content Display**: View extracted content directly by clicking on the file item, simplifying file analysis.
- **🎨 Artifacts Feature**: Render web content and SVGs directly in the interface, supporting quick iterations and live changes.
- **🖊️ Editable Code Blocks**: Supercharged code blocks now allow live editing directly in the LLM response, with live reloads supported by artifacts.
- **🔧 Code Block Enhancements**: Introduced a floating copy button in code blocks to facilitate easier code copying without scrolling.
- **🔍 SVG Pan/Zoom**: Enhanced interaction with SVG images, including Mermaid diagrams, via new pan and zoom capabilities.
- **🔍 Text Select Quick Actions**: New floating buttons appear when text is highlighted in LLM responses, offering deeper interactions like "Ask a Question" or "Explain".
- **🗃️ Database Pool Configuration**: Enhanced database handling to support scalable user growth.
- **🔊 Experimental Audio Compression**: Compress audio files to navigate around the 25MB limit for OpenAI's speech-to-text processing.
- **🔍 Query Embedding**: Adjusted embedding behavior to enhance system performance by not repeating query embedding.
- **💾 Lazy Load Optimizations**: Implemented lazy loading of large dependencies to minimize initial memory usage, boosting performance.
- **🍏 Apple Touch Icon Support**: Optimizes the display of icons for web bookmarks on Apple mobile devices.
- **🔽 Expandable Content Markdown Support**: Introducing 'details', 'summary' tag support for creating expandable content sections in markdown, facilitating cleaner, organized documentation and interactive content display.
### Fixed
- **🔘 Action Button Issue**: Resolved a bug where action buttons were not functioning, enhancing UI reliability.
- **🔄 Multi-Model Chat Loop**: Fixed an infinite loop issue in multi-model chat environments, ensuring smoother chat operations.
- **📄 Chat PDF/TXT Export Issue**: Resolved problems with exporting chat logs to PDF and TXT formats.
- **🔊 Call to Text-to-Speech Issues**: Rectified problems with text-to-speech functions to improve audio interactions.
### Changed
- **⚙️ Endpoint Renaming**: Renamed 'rag' endpoints to 'retrieval' for clearer function description.
- **🎨 Styling and Interface Updates**: Multiple refinements across the platform to enhance visual appeal and user interaction.
### Removed
- **🗑️ Deprecated 'DOCS_DIR'**: Removed the outdated 'docs_dir' variable in favor of more direct file management solutions, with direct file directory syncing and API uploads for a more integrated experience.
## [0.3.30] - 2024-09-26
### Fixed
- **🍞 Update Available Toast Dismissal**: Enhanced user experience by ensuring that once the update available notification is dismissed, it won't reappear for 24 hours.
- **📋 Ollama /embed Form Data**: Adjusted the integration inaccuracies in the /embed form data to ensure it perfectly matches with Ollama's specifications.
- **🔧 O1 Max Completion Tokens Issue**: Resolved compatibility issues with OpenAI's o1 models max_completion_tokens param to ensure smooth operation.
- **🔄 Pip Install Database Issue**: Fixed a critical issue where database changes during pip installations were reverting and not saving chat logs, now ensuring data persistence and reliability in chat operations.
- **🏷️ Chat Rename Tab Update**: Fixed the functionality to change the web browser's tab title simultaneously when a chat is renamed, keeping tab titles consistent.
## [0.3.29] - 2023-09-25
### Fixed
- **🔧 KaTeX Rendering Improvement**: Resolved specific corner cases in KaTeX rendering to enhance the display of complex mathematical notation.
- **📞 'Call' URL Parameter Fix**: Corrected functionality for 'call' URL search parameter ensuring reliable activation of voice calls through URL triggers.
- **🔄 Configuration Reset Fix**: Fixed the RESET_CONFIG_ON_START to ensure settings revert to default correctly upon each startup, improving reliability in configuration management.
- **🌍 Filter Outlet Hook Fix**: Addressed issues in the filter outlet hook, ensuring all filter functions operate as intended.
## [0.3.28] - 2024-09-24
### Fixed
- **🔍 Web Search Functionality**: Corrected an issue where the web search option was not functioning properly.
## [0.3.27] - 2024-09-24
### Fixed
- **🔄 Periodic Cleanup Error Resolved**: Fixed a critical RuntimeError related to the 'periodic_usage_pool_cleanup' coroutine, ensuring smooth and efficient performance post-pip install, correcting a persisting issue from version 0.3.26.
- **📊 Enhanced LaTeX Rendering**: Improved rendering for LaTeX content, enhancing clarity and visual presentation in documents and mathematical models.
## [0.3.26] - 2024-09-24
### Fixed
- **🔄 Event Loop Error Resolution**: Addressed a critical error where a missing running event loop caused 'periodic_usage_pool_cleanup' to fail with pip installs. This fix ensures smoother and more reliable updates and installations, enhancing overall system stability.
## [0.3.25] - 2024-09-24
### Fixed
- **🖼️ Image Generation Functionality**: Resolved an issue where image generation was not functioning, restoring full capability for visual content creation.
- **⚖️ Rate Response Corrections**: Addressed a problem where rate responses were not working, ensuring reliable feedback mechanisms are operational.
## [0.3.24] - 2024-09-24
### Added
- **🚀 Rendering Optimization**: Significantly improved message rendering performance, enhancing user experience and webui responsiveness.
- **💖 Favorite Response Feature in Chat Overview**: Users can now mark responses as favorite directly from the chat overview, enhancing ease of retrieval and organization of preferred responses.
- **💬 Create Message Pairs with Shortcut**: Implemented creation of new message pairs using Cmd/Ctrl+Shift+Enter, making conversation editing faster and more intuitive.
- **🌍 Expanded User Prompt Variables**: Added weekday, timezone, and language information variables to user prompts to match system prompt variables.
- **🎵 Enhanced Audio Support**: Now includes support for 'audio/x-m4a' files, broadening compatibility with audio content within the platform.
- **🔏 Model URL Search Parameter**: Added an ability to select a model directly via URL parameters, streamlining navigation and model access.
- **📄 Enhanced PDF Citations**: PDF citations now open at the associated page, streamlining reference checks and document handling.
- **🔧Use of Redis in Sockets**: Enhanced socket implementation to fully support Redis, enabling effective stateless instances suitable for scalable load balancing.
- **🌍 Stream Individual Model Responses**: Allows specific models to have individualized streaming settings, enhancing performance and customization.
- **🕒 Display Model Hash and Last Modified Timestamp for Ollama Models**: Provides critical model details directly in the Models workspace for enhanced tracking.
- **❗ Update Info Notification for Admins**: Ensures administrators receive immediate updates upon login, keeping them informed of the latest changes and system statuses.
### Fixed
- **🗑️ Temporary File Handling On Windows**: Fixed an issue causing errors when accessing a temporary file being used by another process, Tools & Functions should now work as intended.
- **🔓 Authentication Toggle Issue**: Resolved the malfunction where setting 'WEBUI_AUTH=False' did not appropriately disable authentication, ensuring that user experience and system security settings function as configured.
- **🔧 Save As Copy Issue for Many Model Chats**: Resolved an error preventing users from save messages as copies in many model chats.
- **🔒 Sidebar Closure on Mobile**: Resolved an issue where the mobile sidebar remained open after menu engagement, improving user interface responsivity and comfort.
- **🛡️ Tooltip XSS Vulnerability**: Resolved a cross-site scripting (XSS) issue within tooltips, ensuring enhanced security and data integrity during user interactions.
### Changed
- **↩️ Deprecated Interface Stream Response Settings**: Moved to advanced parameters to streamline interface settings and enhance user clarity.
- **⚙️ Renamed 'speedRate' to 'playbackRate'**: Standardizes terminology, improving usability and understanding in media settings.
## [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
+21 -6
View File
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
# Initialize device type args
# use build args in the docker build commmand with --build-arg="BUILDARG=true"
# use build args in the docker build command with --build-arg="BUILDARG=true"
ARG USE_CUDA=false
ARG USE_OLLAMA=false
# Tested with cu117 for CUDA 11 and cu121 for CUDA 12 (default)
@@ -11,13 +11,17 @@ ARG USE_CUDA_VER=cu121
# IMPORTANT: If you change the embedding model (sentence-transformers/all-MiniLM-L6-v2) and vice versa, you aren't able to use RAG Chat with your previous documents loaded in the WebUI! You need to re-embed them.
ARG USE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
ARG USE_RERANKING_MODEL=""
# Tiktoken encoding name; models to use can be found at https://huggingface.co/models?library=tiktoken
ARG USE_TIKTOKEN_ENCODING_NAME="cl100k_base"
ARG BUILD_HASH=dev-build
# Override at your own risk - non-root configurations are untested
ARG UID=0
ARG GID=0
######## WebUI frontend ########
FROM --platform=$BUILDPLATFORM node:21-alpine3.19 as build
FROM --platform=$BUILDPLATFORM node:22-alpine3.20 AS build
ARG BUILD_HASH
WORKDIR /app
@@ -30,7 +34,7 @@ ENV APP_BUILD_HASH=${BUILD_HASH}
RUN npm run build
######## WebUI backend ########
FROM python:3.11-slim-bookworm as base
FROM python:3.11-slim-bookworm AS base
# Use args
ARG USE_CUDA
@@ -72,13 +76,21 @@ ENV RAG_EMBEDDING_MODEL="$USE_EMBEDDING_MODEL_DOCKER" \
RAG_RERANKING_MODEL="$USE_RERANKING_MODEL_DOCKER" \
SENTENCE_TRANSFORMERS_HOME="/app/backend/data/cache/embedding/models"
## Tiktoken model settings ##
ENV TIKTOKEN_ENCODING_NAME="$USE_TIKTOKEN_ENCODING_NAME" \
TIKTOKEN_CACHE_DIR="/app/backend/data/cache/tiktoken"
## 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
ENV HOME /root
ENV HOME=/root
# Create user and group if not root
RUN if [ $UID -ne 0 ]; then \
if [ $GID -ne 0 ]; then \
@@ -96,7 +108,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 +121,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 && \
@@ -127,11 +139,13 @@ RUN pip3 install uv && \
uv pip install --system -r requirements.txt --no-cache-dir && \
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
else \
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
uv pip install --system -r requirements.txt --no-cache-dir && \
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
fi; \
chown -R $UID:$GID /app/backend/data/
@@ -157,5 +171,6 @@ USER $UID:$GID
ARG BUILD_HASH
ENV WEBUI_BUILD_VERSION=${BUILD_HASH}
ENV DOCKER=true
CMD [ "bash", "start.sh"]
+27 -7
View File
@@ -1,4 +1,4 @@
# Open WebUI (Formerly Ollama WebUI) 👋
# Open WebUI 👋
![GitHub stars](https://img.shields.io/github/stars/open-webui/open-webui?style=social)
![GitHub forks](https://img.shields.io/github/forks/open-webui/open-webui?style=social)
@@ -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.
@@ -59,11 +59,31 @@ Don't forget to explore our sibling project, [Open WebUI Community](https://open
## How to Install 🚀
> [!NOTE]
> Please note that for certain Docker environments, additional configurations might be needed. If you encounter any connection issues, our detailed guide on [Open WebUI Documentation](https://docs.openwebui.com/) is ready to assist you.
### Installation via Python pip 🐍
Open WebUI can be installed using pip, the Python package installer. Before proceeding, ensure you're using **Python 3.11** to avoid compatibility issues.
1. **Install Open WebUI**:
Open your terminal and run the following command to install Open WebUI:
```bash
pip install open-webui
```
2. **Running Open WebUI**:
After installation, you can start Open WebUI by executing:
```bash
open-webui serve
```
This will start the Open WebUI server, which you can access at [http://localhost:8080](http://localhost:8080)
### Quick Start with Docker 🐳
> [!NOTE]
> Please note that for certain Docker environments, additional configurations might be needed. If you encounter any connection issues, our detailed guide on [Open WebUI Documentation](https://docs.openwebui.com/) is ready to assist you.
> [!WARNING]
> When using Docker to install Open WebUI, make sure to include the `-v open-webui:/app/backend/data` in your Docker command. This step is crucial as it ensures your database is properly mounted and prevents any loss of data.
@@ -86,7 +106,7 @@ Don't forget to explore our sibling project, [Open WebUI Community](https://open
docker run -d -p 3000:8080 -e OLLAMA_BASE_URL=https://example.com -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
```
- **To run Open WebUI with Nvidia GPU support**, use this command:
- **To run Open WebUI with Nvidia GPU support**, use this command:
```bash
docker run -d -p 3000:8080 --gpus all --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:cuda
@@ -150,7 +170,7 @@ docker run --rm --volume /var/run/docker.sock:/var/run/docker.sock containrrr/wa
In the last part of the command, replace `open-webui` with your container name if it is different.
Check our Migration Guide available in our [Open WebUI Documentation](https://docs.openwebui.com/migration/).
Check our Migration Guide available in our [Open WebUI Documentation](https://docs.openwebui.com/tutorials/migration/).
### Using the Dev Branch 🌙
@@ -200,4 +220,4 @@ If you have any questions, suggestions, or need assistance, please open an issue
---
Created by [Timothy J. Baek](https://github.com/tjbck) - Let's make Open WebUI even more amazing together! 💪
Created by [Timothy Jaeryang Baek](https://github.com/tjbck) - Let's make Open WebUI even more amazing together! 💪
+1 -1
View File
@@ -18,7 +18,7 @@ If you're experiencing connection issues, its often due to the WebUI docker c
docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_BASE_URL=http://127.0.0.1:11434 --name open-webui --restart always ghcr.io/open-webui/open-webui:main
```
### Error on Slow Reponses for Ollama
### Error on Slow Responses for Ollama
Open WebUI has a default timeout of 5 minutes for Ollama to finish generating the response. If needed, this can be adjusted via the environment variable AIOHTTP_CLIENT_TIMEOUT, which sets the timeout in seconds.
+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
-171
View File
@@ -1,171 +0,0 @@
import socketio
import asyncio
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
SESSION_POOL = {}
USER_POOL = {}
USAGE_POOL = {}
# Timeout duration in seconds
TIMEOUT_DURATION = 3
@sio.event
async def connect(sid, environ, auth):
user = None
if auth and "token" in auth:
data = decode_token(auth["token"])
if data is not None and "id" in data:
user = Users.get_user_by_id(data["id"])
if user:
SESSION_POOL[sid] = user.id
if user.id in USER_POOL:
USER_POOL[user.id].append(sid)
else:
USER_POOL[user.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()})
@sio.on("user-join")
async def 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:
return
data = decode_token(auth["token"])
if data is None or "id" not in data:
return
user = Users.get_user_by_id(data["id"])
if not user:
return
SESSION_POOL[sid] = user.id
if user.id in USER_POOL:
USER_POOL[user.id].append(sid)
else:
USER_POOL[user.id] = [sid]
print(f"user {user.name}({user.id}) connected with session ID {sid}")
await sio.emit("user-count", {"count": len(set(USER_POOL))})
@sio.on("user-count")
async def user_count(sid):
await sio.emit("user-count", {"count": len(set(USER_POOL))})
def get_models_in_use():
# Aggregate all models in use
models_in_use = []
for model_id, data in USAGE_POOL.items():
models_in_use.append(model_id)
return models_in_use
@sio.on("usage")
async def usage(sid, data):
model_id = data["model"]
# Cancel previous callback if there is one
if model_id in USAGE_POOL:
USAGE_POOL[model_id]["callback"].cancel()
# Store the new usage data and task
if model_id in USAGE_POOL:
USAGE_POOL[model_id]["sids"].append(sid)
USAGE_POOL[model_id]["sids"] = list(set(USAGE_POOL[model_id]["sids"]))
else:
USAGE_POOL[model_id] = {"sids": [sid]}
# Schedule a task to remove the usage data after TIMEOUT_DURATION
USAGE_POOL[model_id]["callback"] = asyncio.create_task(
remove_after_timeout(sid, model_id)
)
# Broadcast the usage data to all clients
await sio.emit("usage", {"models": get_models_in_use()})
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"])
USAGE_POOL[model_id]["sids"].remove(sid)
USAGE_POOL[model_id]["sids"] = list(set(USAGE_POOL[model_id]["sids"]))
if len(USAGE_POOL[model_id]["sids"]) == 0:
del USAGE_POOL[model_id]
# Broadcast the usage data to all clients
await sio.emit("usage", {"models": get_models_in_use()})
except asyncio.CancelledError:
# Task was cancelled due to new 'usage' event
pass
@sio.event
async def disconnect(sid):
if sid in SESSION_POOL:
user_id = SESSION_POOL[sid]
del SESSION_POOL[sid]
USER_POOL[user_id].remove(sid)
if len(USER_POOL[user_id]) == 0:
del USER_POOL[user_id]
await sio.emit("user-count", {"count": len(USER_POOL)})
else:
print(f"Unknown session ID {sid} disconnected")
def get_event_emitter(request_info):
async def __event_emitter__(event_data):
await sio.emit(
"chat-events",
{
"chat_id": request_info["chat_id"],
"message_id": request_info["message_id"],
"data": event_data,
},
to=request_info["session_id"],
)
return __event_emitter__
def get_event_call(request_info):
async def __event_call__(event_data):
response = await sio.call(
"chat-events",
{
"chat_id": request_info["chat_id"],
"message_id": request_info["message_id"],
"data": event_data,
},
to=request_info["session_id"],
)
return response
return __event_call__
-410
View File
@@ -1,410 +0,0 @@
from pydantic import BaseModel, ConfigDict
from typing import Union, Optional
import json
import uuid
import time
from sqlalchemy import Column, String, BigInteger, Boolean, Text
from apps.webui.internal.db import Base, get_db
####################
# Chat DB Schema
####################
class Chat(Base):
__tablename__ = "chat"
id = Column(String, primary_key=True)
user_id = Column(String)
title = Column(Text)
chat = Column(Text) # Save Chat JSON as Text
created_at = Column(BigInteger)
updated_at = Column(BigInteger)
share_id = Column(Text, unique=True, nullable=True)
archived = Column(Boolean, default=False)
class ChatModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
user_id: str
title: str
chat: str
created_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
share_id: Optional[str] = None
archived: bool = False
####################
# Forms
####################
class ChatForm(BaseModel):
chat: dict
class ChatTitleForm(BaseModel):
title: str
class ChatResponse(BaseModel):
id: str
user_id: str
title: str
chat: dict
updated_at: int # timestamp in epoch
created_at: int # timestamp in epoch
share_id: Optional[str] = None # id of the chat to be shared
archived: bool
class ChatTitleIdResponse(BaseModel):
id: str
title: str
updated_at: int
created_at: int
class ChatTable:
def insert_new_chat(self, user_id: str, form_data: ChatForm) -> Optional[ChatModel]:
with get_db() as db:
id = str(uuid.uuid4())
chat = ChatModel(
**{
"id": id,
"user_id": user_id,
"title": (
form_data.chat["title"]
if "title" in form_data.chat
else "New Chat"
),
"chat": json.dumps(form_data.chat),
"created_at": int(time.time()),
"updated_at": int(time.time()),
}
)
result = Chat(**chat.model_dump())
db.add(result)
db.commit()
db.refresh(result)
return ChatModel.model_validate(result) if result else None
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"
chat_obj.updated_at = int(time.time())
db.commit()
db.refresh(chat_obj)
return ChatModel.model_validate(chat_obj)
except Exception as e:
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
if chat.share_id:
return self.get_chat_by_id_and_user_id(chat.share_id, "shared")
# Create a new chat with the same data, but with a new ID
shared_chat = ChatModel(
**{
"id": str(uuid.uuid4()),
"user_id": f"shared-{chat_id}",
"title": chat.title,
"chat": chat.chat,
"created_at": chat.created_at,
"updated_at": int(time.time()),
}
)
shared_result = Chat(**shared_chat.model_dump())
db.add(shared_result)
db.commit()
db.refresh(shared_result)
# Update the original chat with the share_id
result = (
db.query(Chat)
.filter_by(id=chat_id)
.update({"share_id": shared_chat.id})
)
db.commit()
return shared_chat if (shared_result and result) else None
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)
chat.title = chat.title
chat.chat = chat.chat
db.commit()
db.refresh(chat)
return self.get_chat_by_id(chat.share_id)
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 Exception:
return False
def update_chat_share_id_by_id(
self, id: str, share_id: Optional[str]
) -> 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 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 Exception:
return None
def archive_all_chats_by_user_id(self, user_id: str) -> bool:
try:
with get_db() as db:
db.query(Chat).filter_by(user_id=user_id).update({"archived": True})
db.commit()
return True
except Exception:
return False
def get_archived_chat_list_by_user_id(
self, user_id: str, skip: int = 0, limit: int = 50
) -> list[ChatModel]:
with get_db() as db:
all_chats = (
db.query(Chat)
.filter_by(user_id=user_id, archived=True)
.order_by(Chat.updated_at.desc())
# .limit(limit).offset(skip)
.all()
)
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_chat_list_by_user_id(
self,
user_id: str,
include_archived: bool = False,
skip: int = 0,
limit: int = 50,
) -> list[ChatModel]:
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(limit).offset(skip)
.all()
)
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_chat_title_id_list_by_user_id(
self,
user_id: str,
include_archived: bool = False,
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)
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(
{
"id": chat[0],
"title": chat[1],
"updated_at": chat[2],
"created_at": chat[3],
}
)
for chat in all_chats
]
def get_chat_list_by_chat_ids(
self, chat_ids: list[str], skip: int = 0, limit: int = 50
) -> list[ChatModel]:
with get_db() as db:
all_chats = (
db.query(Chat)
.filter(Chat.id.in_(chat_ids))
.filter_by(archived=False)
.order_by(Chat.updated_at.desc())
.all()
)
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_chat_by_id(self, id: str) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.get(Chat, id)
return ChatModel.model_validate(chat)
except Exception:
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:
return None
def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.query(Chat).filter_by(id=id, user_id=user_id).first()
return ChatModel.model_validate(chat)
except Exception:
return None
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)
.order_by(Chat.updated_at.desc())
)
return [ChatModel.model_validate(chat) for chat in all_chats]
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)
.order_by(Chat.updated_at.desc())
)
return [ChatModel.model_validate(chat) for chat in all_chats]
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)
.order_by(Chat.updated_at.desc())
)
return [ChatModel.model_validate(chat) for chat in all_chats]
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 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 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 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]
db.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete()
db.commit()
return True
except Exception:
return False
Chats = ChatTable()
-133
View File
@@ -1,133 +0,0 @@
from pydantic import BaseModel, ConfigDict
from typing import Union, Optional
import time
import logging
from sqlalchemy import Column, String, BigInteger, Text
from apps.webui.internal.db import JSONField, Base, get_db
import json
from env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
####################
# Files DB Schema
####################
class File(Base):
__tablename__ = "file"
id = Column(String, primary_key=True)
user_id = Column(String)
filename = Column(Text)
meta = Column(JSONField)
created_at = Column(BigInteger)
class FileModel(BaseModel):
id: str
user_id: str
filename: str
meta: dict
created_at: int # timestamp in epoch
model_config = ConfigDict(from_attributes=True)
####################
# Forms
####################
class FileModelResponse(BaseModel):
id: str
user_id: str
filename: str
meta: dict
created_at: int # timestamp in epoch
class FileForm(BaseModel):
id: str
filename: str
meta: dict = {}
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(),
"user_id": user_id,
"created_at": int(time.time()),
}
)
try:
result = File(**file.model_dump())
db.add(result)
db.commit()
db.refresh(result)
if result:
return FileModel.model_validate(result)
else:
return None
except Exception as e:
print(f"Error creating tool: {e}")
return None
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 Exception:
return None
def get_files(self) -> list[FileModel]:
with get_db() as db:
return [FileModel.model_validate(file) for file in db.query(File).all()]
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 Exception:
return False
def delete_all_files(self) -> bool:
with get_db() as db:
try:
db.query(File).delete()
db.commit()
return True
except Exception:
return False
Files = FilesTable()
-272
View File
@@ -1,272 +0,0 @@
from pydantic import BaseModel, ConfigDict
from typing import Optional
import json
import uuid
import time
import logging
from sqlalchemy import String, Column, BigInteger, Text
from apps.webui.internal.db import Base, get_db
from env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
####################
# Tag DB Schema
####################
class Tag(Base):
__tablename__ = "tag"
id = Column(String, primary_key=True)
name = Column(String)
user_id = Column(String)
data = Column(Text, nullable=True)
class ChatIdTag(Base):
__tablename__ = "chatidtag"
id = Column(String, primary_key=True)
tag_name = Column(String)
chat_id = Column(String)
user_id = Column(String)
timestamp = Column(BigInteger)
class TagModel(BaseModel):
id: str
name: str
user_id: str
data: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class ChatIdTagModel(BaseModel):
id: str
tag_name: str
chat_id: str
user_id: str
timestamp: int
model_config = ConfigDict(from_attributes=True)
####################
# Forms
####################
class ChatIdTagForm(BaseModel):
tag_name: str
chat_id: str
class TagChatIdsResponse(BaseModel):
chat_ids: list[str]
class ChatTagsResponse(BaseModel):
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:
result = Tag(**tag.model_dump())
db.add(result)
db.commit()
db.refresh(result)
if result:
return TagModel.model_validate(result)
else:
return None
except Exception as e:
return None
def get_tag_by_name_and_user_id(
self, name: str, user_id: str
) -> Optional[TagModel]:
try:
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:
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 is None:
tag = self.insert_new_tag(form_data.tag_name, user_id)
id = str(uuid.uuid4())
chatIdTag = ChatIdTagModel(
**{
"id": id,
"user_id": user_id,
"chat_id": form_data.chat_id,
"tag_name": tag.name,
"timestamp": int(time.time()),
}
)
try:
with get_db() as db:
result = ChatIdTag(**chatIdTag.model_dump())
db.add(result)
db.commit()
db.refresh(result)
if result:
return ChatIdTagModel.model_validate(result)
else:
return None
except Exception:
return None
def get_tags_by_user_id(self, user_id: str) -> list[TagModel]:
with get_db() as db:
tag_names = [
chat_id_tag.tag_name
for chat_id_tag in (
db.query(ChatIdTag)
.filter_by(user_id=user_id)
.order_by(ChatIdTag.timestamp.desc())
.all()
)
]
return [
TagModel.model_validate(tag)
for tag in (
db.query(Tag)
.filter_by(user_id=user_id)
.filter(Tag.name.in_(tag_names))
.all()
)
]
def get_tags_by_chat_id_and_user_id(
self, chat_id: str, user_id: str
) -> list[TagModel]:
with get_db() as db:
tag_names = [
chat_id_tag.tag_name
for chat_id_tag in (
db.query(ChatIdTag)
.filter_by(user_id=user_id, chat_id=chat_id)
.order_by(ChatIdTag.timestamp.desc())
.all()
)
]
return [
TagModel.model_validate(tag)
for tag in (
db.query(Tag)
.filter_by(user_id=user_id)
.filter(Tag.name.in_(tag_names))
.all()
)
]
def get_chat_ids_by_tag_name_and_user_id(
self, tag_name: str, user_id: str
) -> list[ChatIdTagModel]:
with get_db() as db:
return [
ChatIdTagModel.model_validate(chat_id_tag)
for chat_id_tag in (
db.query(ChatIdTag)
.filter_by(user_id=user_id, tag_name=tag_name)
.order_by(ChatIdTag.timestamp.desc())
.all()
)
]
def count_chat_ids_by_tag_name_and_user_id(
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)
.count()
)
def delete_tag_by_tag_name_and_user_id(self, tag_name: str, user_id: str) -> bool:
try:
with get_db() as db:
res = (
db.query(ChatIdTag)
.filter_by(tag_name=tag_name, user_id=user_id)
.delete()
)
log.debug(f"res: {res}")
db.commit()
tag_count = self.count_chat_ids_by_tag_name_and_user_id(
tag_name, user_id
)
if tag_count == 0:
# Remove tag item from Tag col as well
db.query(Tag).filter_by(name=tag_name, user_id=user_id).delete()
db.commit()
return True
except Exception as e:
log.error(f"delete_tag: {e}")
return False
def delete_tag_by_tag_name_and_chat_id_and_user_id(
self, tag_name: str, chat_id: str, user_id: str
) -> 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)
.delete()
)
log.debug(f"res: {res}")
db.commit()
tag_count = self.count_chat_ids_by_tag_name_and_user_id(
tag_name, user_id
)
if tag_count == 0:
# Remove tag item from Tag col as well
db.query(Tag).filter_by(name=tag_name, user_id=user_id).delete()
db.commit()
return True
except Exception as e:
log.error(f"delete_tag: {e}")
return False
def delete_tags_by_chat_id_and_user_id(self, chat_id: str, user_id: str) -> bool:
tags = self.get_tags_by_chat_id_and_user_id(chat_id, user_id)
for tag in tags:
self.delete_tag_by_tag_name_and_chat_id_and_user_id(
tag.tag_name, chat_id, user_id
)
return True
Tags = TagTable()
-243
View File
@@ -1,243 +0,0 @@
from fastapi import (
Depends,
FastAPI,
HTTPException,
status,
Request,
UploadFile,
File,
Form,
)
from datetime import datetime, timedelta
from typing import Union, Optional
from pathlib import Path
from fastapi import APIRouter
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
from pydantic import BaseModel
import json
from apps.webui.models.files import (
Files,
FileForm,
FileModel,
FileModelResponse,
)
from utils.utils import get_verified_user, get_admin_user
from constants import ERROR_MESSAGES
from importlib import util
import os
import uuid
import os, shutil, logging, re
from config import SRC_LOG_LEVELS, UPLOAD_DIR
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
router = APIRouter()
############################
# Upload File
############################
@router.post("/")
def upload_file(file: UploadFile = File(...), user=Depends(get_verified_user)):
log.info(f"file.content_type: {file.content_type}")
try:
unsanitized_filename = file.filename
filename = os.path.basename(unsanitized_filename)
# replace filename with uuid
id = str(uuid.uuid4())
name = filename
filename = f"{id}_{filename}"
file_path = f"{UPLOAD_DIR}/{filename}"
contents = file.file.read()
with open(file_path, "wb") as f:
f.write(contents)
f.close()
file = Files.insert_new_file(
user.id,
FileForm(
**{
"id": id,
"filename": filename,
"meta": {
"name": name,
"content_type": file.content_type,
"size": len(contents),
"path": file_path,
},
}
),
)
if file:
return file
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT("Error uploading file"),
)
except Exception as e:
log.exception(e)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT(e),
)
############################
# List Files
############################
@router.get("/", response_model=list[FileModel])
async def list_files(user=Depends(get_verified_user)):
if user.role == "admin":
files = Files.get_files()
else:
files = Files.get_files_by_user_id(user.id)
return files
############################
# Delete All Files
############################
@router.delete("/all")
async def delete_all_files(user=Depends(get_admin_user)):
result = Files.delete_all_files()
if result:
folder = f"{UPLOAD_DIR}"
try:
# Check if the directory exists
if os.path.exists(folder):
# Iterate over all the files and directories in the specified directory
for filename in os.listdir(folder):
file_path = os.path.join(folder, filename)
try:
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path) # Remove the file or link
elif os.path.isdir(file_path):
shutil.rmtree(file_path) # Remove the directory
except Exception as e:
print(f"Failed to delete {file_path}. Reason: {e}")
else:
print(f"The directory {folder} does not exist")
except Exception as e:
print(f"Failed to process the directory {folder}. Reason: {e}")
return {"message": "All files deleted successfully"}
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT("Error deleting files"),
)
############################
# Get File By Id
############################
@router.get("/{id}", response_model=Optional[FileModel])
async def get_file_by_id(id: str, user=Depends(get_verified_user)):
file = Files.get_file_by_id(id)
if file and (file.user_id == user.id or user.role == "admin"):
return file
else:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
############################
# Get File Content By Id
############################
@router.get("/{id}/content", response_model=Optional[FileModel])
async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
file = Files.get_file_by_id(id)
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
if file_path.is_file():
print(f"file_path: {file_path}")
return FileResponse(file_path)
else:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
else:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
@router.get("/{id}/content/{file_name}", response_model=Optional[FileModel])
async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
file = Files.get_file_by_id(id)
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
if file_path.is_file():
print(f"file_path: {file_path}")
return FileResponse(file_path)
else:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
else:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
############################
# Delete File By Id
############################
@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 and (file.user_id == user.id or user.role == "admin"):
result = Files.delete_file_by_id(id)
if result:
return {"message": "File deleted successfully"}
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT("Error deleting file"),
)
else:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
-149
View File
@@ -1,149 +0,0 @@
from pathlib import Path
import site
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
router = APIRouter()
@router.get("/gravatar")
async def get_gravatar(
email: str,
):
return get_gravatar_url(email)
class CodeFormatRequest(BaseModel):
code: str
@router.post("/code/format")
async def format_code(request: CodeFormatRequest):
try:
formatted_code = black.format_str(request.code, mode=black.Mode())
return {"code": formatted_code}
except black.NothingChanged:
return {"code": request.code}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
class MarkdownForm(BaseModel):
md: str
@router.post("/markdown")
async def get_html_from_markdown(
form_data: MarkdownForm,
):
return {"html": markdown.markdown(form_data.md)}
class ChatForm(BaseModel):
title: str
messages: list[dict]
@router.post("/pdf")
async def download_chat_as_pdf(
form_data: ChatForm,
):
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"
# When running using `pip install -e .` the static directory is in the site packages.
# This path only works if `open-webui serve` is run from the root of this project.
if not FONTS_DIR.exists():
FONTS_DIR = Path("./backend/static/fonts")
pdf.add_font("NotoSans", "", f"{FONTS_DIR}/NotoSans-Regular.ttf")
pdf.add_font("NotoSans", "b", f"{FONTS_DIR}/NotoSans-Bold.ttf")
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", "NotoSansSC"])
pdf.set_auto_page_break(auto=True, margin=15)
# Adjust the effective page width for multi_cell
effective_page_width = (
pdf.w - 2 * pdf.l_margin - 10
) # Subtracted an additional 10 for extra padding
# Add chat messages
for message in form_data.messages:
role = message["role"]
content = message["content"]
pdf.set_font("NotoSans", "B", size=14) # Bold for the role
pdf.multi_cell(effective_page_width, 10, f"{role.upper()}", 0, "L")
pdf.ln(1) # Extra space between messages
pdf.set_font("NotoSans", size=10) # Regular for content
pdf.multi_cell(effective_page_width, 6, content, 0, "L")
pdf.ln(1.5) # Extra space between messages
# Save the pdf with name .pdf
pdf_bytes = pdf.output()
return Response(
content=bytes(pdf_bytes),
media_type="application/pdf",
headers={"Content-Disposition": f"attachment;filename=chat.pdf"},
)
@router.get("/db/download")
async def download_db(user=Depends(get_admin_user)):
if not ENABLE_ADMIN_EXPORT:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
from apps.webui.internal.db import engine
if engine.name != "sqlite":
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DB_NOT_SQLITE,
)
return FileResponse(
engine.url.database,
media_type="application/octet-stream",
filename="webui.db",
)
@router.get("/litellm/config")
async def download_litellm_config_yaml(user=Depends(get_admin_user)):
return FileResponse(
f"{DATA_DIR}/litellm/config.yaml",
media_type="application/octet-stream",
filename="config.yaml",
)
-124
View File
@@ -1,124 +0,0 @@
from importlib import util
import os
import re
import sys
import subprocess
from apps.webui.models.tools import Tools
from apps.webui.models.functions import Functions
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")
if not os.path.exists(toolkit_path):
tool = Tools.get_tool_by_id(toolkit_id)
if tool:
with open(toolkit_path, "w") as file:
file.write(tool.content)
else:
raise Exception(f"Toolkit not found: {toolkit_id}")
spec = util.spec_from_file_location(toolkit_id, toolkit_path)
module = util.module_from_spec(spec)
frontmatter = extract_frontmatter(toolkit_path)
try:
install_frontmatter_requirements(frontmatter.get("requirements", ""))
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")
if not os.path.exists(function_path):
function = Functions.get_function_by_id(function_id)
if function:
with open(function_path, "w") as file:
file.write(function.content)
else:
raise Exception(f"Function not found: {function_id}")
spec = util.spec_from_file_location(function_id, function_path)
module = util.module_from_spec(spec)
frontmatter = extract_frontmatter(function_path)
try:
install_frontmatter_requirements(frontmatter.get("requirements", ""))
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
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.")
-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
+22 -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,23 @@ 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 +65,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="*",
)
@@ -5,48 +5,44 @@ import os
import uuid
from functools import lru_cache
from pathlib import Path
from pydub import AudioSegment
from pydub.silence import split_on_silence
import requests
from fastapi import (
FastAPI,
Request,
Depends,
HTTPException,
status,
UploadFile,
File,
from open_webui.config import (
AUDIO_STT_ENGINE,
AUDIO_STT_MODEL,
AUDIO_STT_OPENAI_API_BASE_URL,
AUDIO_STT_OPENAI_API_KEY,
AUDIO_TTS_API_KEY,
AUDIO_TTS_ENGINE,
AUDIO_TTS_MODEL,
AUDIO_TTS_OPENAI_API_BASE_URL,
AUDIO_TTS_OPENAI_API_KEY,
AUDIO_TTS_SPLIT_ON,
AUDIO_TTS_VOICE,
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_verified_user
# Constants
MAX_FILE_SIZE_MB = 25
MAX_FILE_SIZE = MAX_FILE_SIZE_MB * 1024 * 1024 # Convert MB to bytes
from config import (
SRC_LOG_LEVELS,
CACHE_DIR,
WHISPER_MODEL,
WHISPER_MODEL_DIR,
WHISPER_MODEL_AUTO_UPDATE,
DEVICE_TYPE,
AUDIO_STT_OPENAI_API_BASE_URL,
AUDIO_STT_OPENAI_API_KEY,
AUDIO_TTS_OPENAI_API_BASE_URL,
AUDIO_TTS_OPENAI_API_KEY,
AUDIO_TTS_API_KEY,
AUDIO_STT_ENGINE,
AUDIO_STT_MODEL,
AUDIO_TTS_ENGINE,
AUDIO_TTS_MODEL,
AUDIO_TTS_VOICE,
AUDIO_TTS_SPLIT_ON,
AppConfig,
CORS_ALLOW_ORIGIN,
)
from constants import ERROR_MESSAGES
from utils.utils import (
get_current_user,
get_verified_user,
get_admin_user,
)
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["AUDIO"])
@@ -67,6 +63,9 @@ app.state.config.STT_OPENAI_API_KEY = AUDIO_STT_OPENAI_API_KEY
app.state.config.STT_ENGINE = AUDIO_STT_ENGINE
app.state.config.STT_MODEL = AUDIO_STT_MODEL
app.state.config.WHISPER_MODEL = WHISPER_MODEL
app.state.faster_whisper_model = None
app.state.config.TTS_OPENAI_API_BASE_URL = AUDIO_TTS_OPENAI_API_BASE_URL
app.state.config.TTS_OPENAI_API_KEY = AUDIO_TTS_OPENAI_API_KEY
app.state.config.TTS_ENGINE = AUDIO_TTS_ENGINE
@@ -75,6 +74,9 @@ 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"
log.info(f"whisper_device_type: {whisper_device_type}")
@@ -83,6 +85,31 @@ SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
SPEECH_CACHE_DIR.mkdir(parents=True, exist_ok=True)
def set_faster_whisper_model(model: str, auto_update: bool = False):
if model and app.state.config.STT_ENGINE == "":
from faster_whisper import WhisperModel
faster_whisper_kwargs = {
"model_size_or_path": model,
"device": whisper_device_type,
"compute_type": "int8",
"download_root": WHISPER_MODEL_DIR,
"local_files_only": not auto_update,
}
try:
app.state.faster_whisper_model = WhisperModel(**faster_whisper_kwargs)
except Exception:
log.warning(
"WhisperModel initialization failed, attempting download with local_files_only=False"
)
faster_whisper_kwargs["local_files_only"] = False
app.state.faster_whisper_model = WhisperModel(**faster_whisper_kwargs)
else:
app.state.faster_whisper_model = None
class TTSConfigForm(BaseModel):
OPENAI_API_BASE_URL: str
OPENAI_API_KEY: str
@@ -91,6 +118,8 @@ class TTSConfigForm(BaseModel):
MODEL: str
VOICE: str
SPLIT_ON: str
AZURE_SPEECH_REGION: str
AZURE_SPEECH_OUTPUT_FORMAT: str
class STTConfigForm(BaseModel):
@@ -98,6 +127,7 @@ class STTConfigForm(BaseModel):
OPENAI_API_KEY: str
ENGINE: str
MODEL: str
WHISPER_MODEL: str
class AudioConfigUpdateForm(BaseModel):
@@ -143,12 +173,15 @@ async def get_audio_config(user=Depends(get_admin_user)):
"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,
"OPENAI_API_KEY": app.state.config.STT_OPENAI_API_KEY,
"ENGINE": app.state.config.STT_ENGINE,
"MODEL": app.state.config.STT_MODEL,
"WHISPER_MODEL": app.state.config.WHISPER_MODEL,
},
}
@@ -164,11 +197,17 @@ async def update_audio_config(
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
app.state.config.STT_ENGINE = form_data.stt.ENGINE
app.state.config.STT_MODEL = form_data.stt.MODEL
app.state.config.WHISPER_MODEL = form_data.stt.WHISPER_MODEL
set_faster_whisper_model(form_data.stt.WHISPER_MODEL, WHISPER_MODEL_AUTO_UPDATE)
return {
"tts": {
@@ -179,12 +218,15 @@ async def update_audio_config(
"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,
"OPENAI_API_KEY": app.state.config.STT_OPENAI_API_KEY,
"ENGINE": app.state.config.STT_ENGINE,
"MODEL": app.state.config.STT_MODEL,
"WHISPER_MODEL": app.state.config.WHISPER_MODEL,
},
}
@@ -211,7 +253,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
@@ -314,68 +356,96 @@ 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")
@app.post("/transcriptions")
def transcribe(
file: UploadFile = File(...),
user=Depends(get_current_user),
):
log.info(f"file.content_type: {file.content_type}")
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"
if file.content_type not in ["audio/mpeg", "audio/wav"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.FILE_NOT_SUPPORTED,
)
headers = {
"Ocp-Apim-Subscription-Key": app.state.config.TTS_API_KEY,
"Content-Type": "application/ssml+xml",
"X-Microsoft-OutputFormat": output_format,
}
try:
ext = file.filename.split(".")[-1]
data = f"""<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="{locale}">
<voice name="{language}">{payload["input"]}</voice>
</speak>"""
id = uuid.uuid4()
filename = f"{id}.{ext}"
response = requests.post(url, headers=headers, data=data)
file_dir = f"{CACHE_DIR}/audio/transcriptions"
os.makedirs(file_dir, exist_ok=True)
file_path = f"{file_dir}/{filename}"
print(filename)
contents = file.file.read()
with open(file_path, "wb") as f:
f.write(contents)
f.close()
if app.state.config.STT_ENGINE == "":
from faster_whisper import WhisperModel
whisper_kwargs = {
"model_size_or_path": WHISPER_MODEL,
"device": whisper_device_type,
"compute_type": "int8",
"download_root": WHISPER_MODEL_DIR,
"local_files_only": not WHISPER_MODEL_AUTO_UPDATE,
}
log.debug(f"whisper_kwargs: {whisper_kwargs}")
try:
model = WhisperModel(**whisper_kwargs)
except Exception:
log.warning(
"WhisperModel initialization failed, attempting download with local_files_only=False"
)
whisper_kwargs["local_files_only"] = False
model = WhisperModel(**whisper_kwargs)
segments, info = model.transcribe(file_path, beam_size=5)
log.info(
"Detected language '%s' with probability %f"
% (info.language, info.language_probability)
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}"
)
transcript = "".join([segment.text for segment in list(segments)])
data = {"text": transcript.strip()}
def transcribe(file_path):
print("transcribe", file_path)
filename = os.path.basename(file_path)
file_dir = os.path.dirname(file_path)
id = filename.split(".")[0]
if app.state.config.STT_ENGINE == "":
if app.state.faster_whisper_model is None:
set_faster_whisper_model(app.state.config.WHISPER_MODEL)
model = app.state.faster_whisper_model
segments, info = model.transcribe(file_path, beam_size=5)
log.info(
"Detected language '%s' with probability %f"
% (info.language, info.language_probability)
)
transcript = "".join([segment.text for segment in list(segments)])
data = {"text": transcript.strip()}
# save the transcript to a json file
transcript_file = f"{file_dir}/{id}.json"
with open(transcript_file, "w") as f:
json.dump(data, f)
log.debug(data)
return data
elif app.state.config.STT_ENGINE == "openai":
if is_mp4_audio(file_path):
print("is_mp4_audio")
os.rename(file_path, file_path.replace(".wav", ".mp4"))
# Convert MP4 audio file to WAV format
convert_mp4_to_wav(file_path.replace(".wav", ".mp4"), file_path)
headers = {"Authorization": f"Bearer {app.state.config.STT_OPENAI_API_KEY}"}
files = {"file": (filename, open(file_path, "rb"))}
data = {"model": app.state.config.STT_MODEL}
log.debug(files, data)
r = None
try:
r = requests.post(
url=f"{app.state.config.STT_OPENAI_API_BASE_URL}/audio/transcriptions",
headers=headers,
files=files,
data=data,
)
r.raise_for_status()
data = r.json()
# save the transcript to a json file
transcript_file = f"{file_dir}/{id}.json"
@@ -383,58 +453,82 @@ def transcribe(
json.dump(data, f)
print(data)
return data
except Exception as e:
log.exception(e)
error_detail = "Open WebUI: Server Connection Error"
if r is not None:
try:
res = r.json()
if "error" in res:
error_detail = f"External: {res['error']['message']}"
except Exception:
error_detail = f"External: {e}"
raise Exception(error_detail)
@app.post("/transcriptions")
def transcription(
file: UploadFile = File(...),
user=Depends(get_verified_user),
):
log.info(f"file.content_type: {file.content_type}")
if file.content_type not in ["audio/mpeg", "audio/wav", "audio/ogg", "audio/x-m4a"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.FILE_NOT_SUPPORTED,
)
try:
ext = file.filename.split(".")[-1]
id = uuid.uuid4()
filename = f"{id}.{ext}"
contents = file.file.read()
file_dir = f"{CACHE_DIR}/audio/transcriptions"
os.makedirs(file_dir, exist_ok=True)
file_path = f"{file_dir}/{filename}"
with open(file_path, "wb") as f:
f.write(contents)
try:
if os.path.getsize(file_path) > MAX_FILE_SIZE: # file is bigger than 25MB
log.debug(f"File size is larger than {MAX_FILE_SIZE_MB}MB")
audio = AudioSegment.from_file(file_path)
audio = audio.set_frame_rate(16000).set_channels(1) # Compress audio
compressed_path = f"{file_dir}/{id}_compressed.opus"
audio.export(compressed_path, format="opus", bitrate="32k")
log.debug(f"Compressed audio to {compressed_path}")
file_path = compressed_path
if (
os.path.getsize(file_path) > MAX_FILE_SIZE
): # Still larger than 25MB after compression
log.debug(
f"Compressed file size is still larger than {MAX_FILE_SIZE_MB}MB: {os.path.getsize(file_path)}"
)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.FILE_TOO_LARGE(
size=f"{MAX_FILE_SIZE_MB}MB"
),
)
data = transcribe(file_path)
else:
data = transcribe(file_path)
return data
elif app.state.config.STT_ENGINE == "openai":
if is_mp4_audio(file_path):
print("is_mp4_audio")
os.rename(file_path, file_path.replace(".wav", ".mp4"))
# Convert MP4 audio file to WAV format
convert_mp4_to_wav(file_path.replace(".wav", ".mp4"), file_path)
headers = {"Authorization": f"Bearer {app.state.config.STT_OPENAI_API_KEY}"}
files = {"file": (filename, open(file_path, "rb"))}
data = {"model": app.state.config.STT_MODEL}
print(files, data)
r = None
try:
r = requests.post(
url=f"{app.state.config.STT_OPENAI_API_BASE_URL}/audio/transcriptions",
headers=headers,
files=files,
data=data,
)
r.raise_for_status()
data = r.json()
# save the transcript to a json file
transcript_file = f"{file_dir}/{id}.json"
with open(transcript_file, "w") as f:
json.dump(data, f)
print(data)
return data
except Exception as e:
log.exception(e)
error_detail = "Open WebUI: Server Connection Error"
if r is not None:
try:
res = r.json()
if "error" in res:
error_detail = f"External: {res['error']['message']}"
except Exception:
error_detail = f"External: {e}"
raise HTTPException(
status_code=r.status_code if r != None else 500,
detail=error_detail,
)
except Exception as e:
log.exception(e)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT(e),
)
except Exception as e:
log.exception(e)
@@ -456,7 +550,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()
@@ -488,9 +582,24 @@ def get_available_voices() -> dict:
elif app.state.config.TTS_ENGINE == "elevenlabs":
try:
ret = get_elevenlabs_voices()
except Exception as e:
except Exception:
# Avoided @lru_cache with exception
pass
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}
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 ret
@@ -1,52 +1,45 @@
from fastapi import (
FastAPI,
Request,
Depends,
HTTPException,
)
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional
from pydantic import BaseModel
from pathlib import Path
import mimetypes
import uuid
import asyncio
import base64
import json
import logging
import mimetypes
import re
import uuid
from pathlib import Path
from typing import Optional
import requests
import asyncio
from utils.utils import (
get_verified_user,
get_admin_user,
)
from apps.images.utils.comfyui import (
ComfyUIWorkflow,
from open_webui.apps.images.utils.comfyui import (
ComfyUIGenerateImageForm,
ComfyUIWorkflow,
comfyui_generate_image,
)
from constants import ERROR_MESSAGES
from config import (
SRC_LOG_LEVELS,
CACHE_DIR,
IMAGE_GENERATION_ENGINE,
ENABLE_IMAGE_GENERATION,
AUTOMATIC1111_BASE_URL,
from open_webui.config import (
AUTOMATIC1111_API_AUTH,
AUTOMATIC1111_BASE_URL,
AUTOMATIC1111_CFG_SCALE,
AUTOMATIC1111_SAMPLER,
AUTOMATIC1111_SCHEDULER,
CACHE_DIR,
COMFYUI_BASE_URL,
COMFYUI_WORKFLOW,
COMFYUI_WORKFLOW_NODES,
IMAGES_OPENAI_API_BASE_URL,
IMAGES_OPENAI_API_KEY,
CORS_ALLOW_ORIGIN,
ENABLE_IMAGE_GENERATION,
IMAGE_GENERATION_ENGINE,
IMAGE_GENERATION_MODEL,
IMAGE_SIZE,
IMAGE_STEPS,
CORS_ALLOW_ORIGIN,
IMAGES_OPENAI_API_BASE_URL,
IMAGES_OPENAI_API_KEY,
AppConfig,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from open_webui.utils.utils import get_admin_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["IMAGES"])
@@ -75,6 +68,9 @@ 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
@@ -95,6 +91,9 @@ async def get_config(request: Request, user=Depends(get_admin_user)):
"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,
@@ -112,6 +111,9 @@ class OpenAIConfigForm(BaseModel):
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):
@@ -143,7 +145,23 @@ async def update_config(form_data: ConfigForm, user=Depends(get_admin_user)):
form_data.automatic1111.AUTOMATIC1111_API_AUTH
)
app.state.config.COMFYUI_BASE_URL = form_data.comfyui.COMFYUI_BASE_URL
app.state.config.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
@@ -157,6 +175,9 @@ async def update_config(form_data: ConfigForm, user=Depends(get_admin_user)):
"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,
@@ -186,7 +207,7 @@ async def verify_url(user=Depends(get_admin_user)):
)
r.raise_for_status()
return True
except Exception as e:
except Exception:
app.state.config.ENABLED = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
elif app.state.config.ENGINE == "comfyui":
@@ -194,7 +215,7 @@ async def verify_url(user=Depends(get_admin_user)):
r = requests.get(url=f"{app.state.config.COMFYUI_BASE_URL}/object_info")
r.raise_for_status()
return True
except Exception as e:
except Exception:
app.state.config.ENABLED = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
else:
@@ -202,6 +223,7 @@ async def verify_url(user=Depends(get_admin_user)):
def set_image_model(model: str):
log.info(f"Setting image model to {model}")
app.state.config.MODEL = model
if app.state.config.ENGINE in ["", "automatic1111"]:
api_auth = get_automatic1111_api_auth()
@@ -255,7 +277,8 @@ async def get_image_config(user=Depends(get_admin_user)):
@app.post("/image/config/update")
async def update_image_config(form_data: ImageConfigForm, user=Depends(get_admin_user)):
app.state.config.MODEL = form_data.MODEL
set_image_model(form_data.MODEL)
pattern = r"^\d+x\d+$"
if re.match(pattern, form_data.IMAGE_SIZE):
@@ -397,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)
@@ -412,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:
@@ -430,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"
@@ -449,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,
@@ -534,6 +557,15 @@ async def image_generations(
if form_data.negative_prompt is not None:
data["negative_prompt"] = form_data.negative_prompt
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,
@@ -1,34 +1,45 @@
import asyncio
import websocket # NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
import json
import urllib.request
import urllib.parse
import random
import logging
import random
import urllib.parse
import urllib.request
from typing import Optional
from config import SRC_LOG_LEVELS
import websocket # NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["COMFYUI"])
from pydantic import BaseModel
from typing import Optional
default_headers = {"User-Agent": "Mozilla/5.0"}
def queue_prompt(prompt, client_id, base_url):
log.info("queue_prompt")
p = {"prompt": prompt, "client_id": client_id}
data = json.dumps(p).encode("utf-8")
req = urllib.request.Request(f"{base_url}/prompt", data=data)
return json.loads(urllib.request.urlopen(req).read())
log.debug(f"queue_prompt data: {data}")
try:
req = urllib.request.Request(
f"{base_url}/prompt", data=data, headers=default_headers
)
response = urllib.request.urlopen(req).read()
return json.loads(response)
except Exception as e:
log.exception(f"Error while queuing prompt: {e}")
raise e
def get_image(filename, subfolder, folder_type, base_url):
log.info("get_image")
data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
url_values = urllib.parse.urlencode(data)
with urllib.request.urlopen(f"{base_url}/view?{url_values}") as response:
req = urllib.request.Request(
f"{base_url}/view?{url_values}", headers=default_headers
)
with urllib.request.urlopen(req) as response:
return response.read()
@@ -41,7 +52,11 @@ def get_image_url(filename, subfolder, folder_type, base_url):
def get_history(prompt_id, base_url):
log.info("get_history")
with urllib.request.urlopen(f"{base_url}/history/{prompt_id}") as response:
req = urllib.request.Request(
f"{base_url}/history/{prompt_id}", headers=default_headers
)
with urllib.request.urlopen(req) as response:
return json.loads(response.read())
@@ -110,22 +125,34 @@ async def comfyui_generate_image(
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
workflow[node_id]["inputs"][
node.key if node.key else "text"
] = payload.prompt
elif node.type == "negative_prompt":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["text"] = payload.negative_prompt
workflow[node_id]["inputs"][
node.key if node.key else "text"
] = payload.negative_prompt
elif node.type == "width":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["width"] = payload.width
workflow[node_id]["inputs"][
node.key if node.key else "width"
] = payload.width
elif node.type == "height":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["height"] = payload.height
workflow[node_id]["inputs"][
node.key if node.key else "height"
] = payload.height
elif node.type == "n":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["batch_size"] = payload.n
workflow[node_id]["inputs"][
node.key if node.key else "batch_size"
] = payload.n
elif node.type == "steps":
for node_id in node.node_ids:
workflow[node_id]["inputs"]["steps"] = payload.steps
workflow[node_id]["inputs"][
node.key if node.key else "steps"
] = payload.steps
elif node.type == "seed":
seed = (
payload.seed
@@ -1,54 +1,46 @@
from fastapi import (
FastAPI,
Request,
HTTPException,
Depends,
UploadFile,
File,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, ConfigDict
import os
import re
import random
import requests
import json
import aiohttp
import asyncio
import json
import logging
import os
import random
import re
import time
from urllib.parse import urlparse
from typing import Optional, Union
from urllib.parse import urlparse
from starlette.background import BackgroundTask
from apps.webui.models.models import Models
from constants import ERROR_MESSAGES
from utils.utils import (
get_verified_user,
get_admin_user,
)
from config import (
SRC_LOG_LEVELS,
OLLAMA_BASE_URLS,
ENABLE_OLLAMA_API,
AIOHTTP_CLIENT_TIMEOUT,
import aiohttp
import requests
from open_webui.apps.webui.models.models import Models
from open_webui.config import (
CORS_ALLOW_ORIGIN,
ENABLE_MODEL_FILTER,
ENABLE_OLLAMA_API,
MODEL_FILTER_LIST,
OLLAMA_BASE_URLS,
UPLOAD_DIR,
AppConfig,
CORS_ALLOW_ORIGIN,
)
from utils.misc import (
from open_webui.env import AIOHTTP_CLIENT_TIMEOUT
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"])
@@ -127,7 +119,7 @@ async def update_ollama_api_url(form_data: UrlUpdateForm, user=Depends(get_admin
async def fetch_url(url):
timeout = aiohttp.ClientTimeout(total=5)
timeout = aiohttp.ClientTimeout(total=3)
try:
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
async with session.get(url) as response:
@@ -553,6 +545,24 @@ class GenerateEmbeddingsForm(BaseModel):
keep_alive: Optional[Union[int, str]] = None
class GenerateEmbedForm(BaseModel):
model: str
input: list[str] | str
truncate: Optional[bool] = None
options: Optional[dict] = None
keep_alive: Optional[Union[int, str]] = None
@app.post("/api/embed")
@app.post("/api/embed/{url_idx}")
async def generate_embeddings(
form_data: GenerateEmbedForm,
url_idx: Optional[int] = None,
user=Depends(get_verified_user),
):
return generate_ollama_batch_embeddings(form_data, url_idx)
@app.post("/api/embeddings")
@app.post("/api/embeddings/{url_idx}")
async def generate_embeddings(
@@ -560,48 +570,7 @@ async def generate_embeddings(
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(
status_code=r.status_code if r else 500,
detail=error_detail,
)
return generate_ollama_embeddings(form_data=form_data, url_idx=url_idx)
def generate_ollama_embeddings(
@@ -641,7 +610,64 @@ def generate_ollama_embeddings(
log.info(f"generate_ollama_embeddings {data}")
if "embedding" in data:
return data["embedding"]
return data
else:
raise Exception("Something went wrong :/")
except Exception as e:
log.exception(e)
error_detail = "Open WebUI: Server Connection Error"
if r is not None:
try:
res = r.json()
if "error" in res:
error_detail = f"Ollama: {res['error']}"
except Exception:
error_detail = f"Ollama: {e}"
raise HTTPException(
status_code=r.status_code if r else 500,
detail=error_detail,
)
def generate_ollama_batch_embeddings(
form_data: GenerateEmbedForm,
url_idx: Optional[int] = None,
):
log.info(f"generate_ollama_batch_embeddings {form_data}")
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/embed",
headers={"Content-Type": "application/json"},
data=form_data.model_dump_json(exclude_none=True).encode(),
)
try:
r.raise_for_status()
data = r.json()
log.info(f"generate_ollama_batch_embeddings {data}")
if "embeddings" in data:
return data
else:
raise Exception("Something went wrong :/")
except Exception as e:
@@ -737,7 +763,7 @@ async def generate_chat_completion(
user=Depends(get_verified_user),
):
payload = {**form_data.model_dump(exclude_none=True)}
log.debug(f"{payload = }")
log.debug(f"generate_chat_completion() - 1.payload = {payload}")
if "metadata" in payload:
del payload["metadata"]
@@ -772,10 +798,13 @@ async def generate_chat_completion(
url = get_ollama_url(url_idx, payload["model"])
log.info(f"url: {url}")
log.debug(payload)
log.debug(f"generate_chat_completion() - 2.payload = {payload}")
return await post_streaming_url(
f"{url}/api/chat", json.dumps(payload), content_type="application/x-ndjson"
f"{url}/api/chat",
json.dumps(payload),
stream=form_data.stream,
content_type="application/x-ndjson",
)
@@ -1,44 +1,42 @@
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 (
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.env import (
AIOHTTP_CLIENT_TIMEOUT,
AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, StreamingResponse
from pydantic import BaseModel
from starlette.background import BackgroundTask
from apps.webui.models.models import Models
from constants import ERROR_MESSAGES
from utils.utils import (
get_verified_user,
get_admin_user,
)
from utils.misc import (
from open_webui.utils.payload import (
apply_model_params_to_body_openai,
apply_model_system_prompt_to_body,
)
from config import (
SRC_LOG_LEVELS,
ENABLE_OPENAI_API,
AIOHTTP_CLIENT_TIMEOUT,
OPENAI_API_BASE_URLS,
OPENAI_API_KEYS,
CACHE_DIR,
ENABLE_MODEL_FILTER,
MODEL_FILTER_LIST,
AppConfig,
CORS_ALLOW_ORIGIN,
)
from typing import Optional, Literal, overload
import hashlib
from pathlib import Path
from open_webui.utils.utils import get_admin_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["OPENAI"])
@@ -52,7 +50,6 @@ app.add_middleware(
allow_headers=["*"],
)
app.state.config = AppConfig()
app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
@@ -185,7 +182,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
async def fetch_url(url, key):
timeout = aiohttp.ClientTimeout(total=5)
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST)
try:
headers = {"Authorization": f"Bearer {key}"}
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
@@ -225,7 +222,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",
]
)
]
)
@@ -233,9 +240,7 @@ def merge_models_lists(model_lists):
def is_openai_api_disabled():
api_keys = app.state.config.OPENAI_API_KEYS
no_keys = len(api_keys) == 1 and api_keys[0] == ""
return no_keys or not app.state.config.ENABLE_OPENAI_API
return not app.state.config.ENABLE_OPENAI_API
async def get_all_models_raw() -> list:
@@ -328,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:
@@ -386,14 +405,32 @@ 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]
is_o1 = payload["model"].lower().startswith("o1-")
# Change max_completion_tokens to max_tokens (Backward compatible)
if "api.openai.com" not in url and not is_o1:
if "max_completion_tokens" in payload:
# Remove "max_completion_tokens" from the payload
payload["max_tokens"] = payload["max_completion_tokens"]
del payload["max_completion_tokens"]
else:
if is_o1 and "max_tokens" in payload:
payload["max_completion_tokens"] = payload["max_tokens"]
del payload["max_tokens"]
if "max_tokens" in payload and "max_completion_tokens" in payload:
del payload["max_tokens"]
# Fix: O1 does not support the "system" parameter, Modify "system" to "user"
if is_o1 and payload["messages"][0]["role"] == "system":
payload["messages"][0]["role"] = "user"
# 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"
@@ -404,6 +441,7 @@ async def generate_chat_completion(
r = None
session = None
streaming = False
response = None
try:
session = aiohttp.ClientSession(
@@ -416,8 +454,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
@@ -430,19 +466,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:
@@ -0,0 +1,190 @@
import requests
import logging
import ftfy
from langchain_community.document_loaders import (
BSHTMLLoader,
CSVLoader,
Docx2txtLoader,
OutlookMessageLoader,
PyPDFLoader,
TextLoader,
UnstructuredEPubLoader,
UnstructuredExcelLoader,
UnstructuredMarkdownLoader,
UnstructuredPowerPointLoader,
UnstructuredRSTLoader,
UnstructuredXMLLoader,
YoutubeLoader,
)
from langchain_core.documents import Document
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
known_source_ext = [
"go",
"py",
"java",
"sh",
"bat",
"ps1",
"cmd",
"js",
"ts",
"css",
"cpp",
"hpp",
"h",
"c",
"cs",
"sql",
"log",
"ini",
"pl",
"pm",
"r",
"dart",
"dockerfile",
"env",
"php",
"hs",
"hsc",
"lua",
"nginxconf",
"conf",
"m",
"mm",
"plsql",
"perl",
"rb",
"rs",
"db2",
"scala",
"bash",
"swift",
"vue",
"svelte",
"msg",
"ex",
"exs",
"erl",
"tsx",
"jsx",
"hs",
"lhs",
]
class TikaLoader:
def __init__(self, url, file_path, mime_type=None):
self.url = url
self.file_path = file_path
self.mime_type = mime_type
def load(self) -> list[Document]:
with open(self.file_path, "rb") as f:
data = f.read()
if self.mime_type is not None:
headers = {"Content-Type": self.mime_type}
else:
headers = {}
endpoint = self.url
if not endpoint.endswith("/"):
endpoint += "/"
endpoint += "tika/text"
r = requests.put(endpoint, data=data, headers=headers)
if r.ok:
raw_metadata = r.json()
text = raw_metadata.get("X-TIKA:content", "<No text content found>")
if "Content-Type" in raw_metadata:
headers["Content-Type"] = raw_metadata["Content-Type"]
log.info("Tika extracted text: %s", text)
return [Document(page_content=text, metadata=headers)]
else:
raise Exception(f"Error calling Tika: {r.reason}")
class Loader:
def __init__(self, engine: str = "", **kwargs):
self.engine = engine
self.kwargs = kwargs
def load(
self, filename: str, file_content_type: str, file_path: str
) -> list[Document]:
loader = self._get_loader(filename, file_content_type, file_path)
docs = loader.load()
return [
Document(
page_content=ftfy.fix_text(doc.page_content), metadata=doc.metadata
)
for doc in docs
]
def _get_loader(self, filename: str, file_content_type: str, file_path: str):
file_ext = filename.split(".")[-1].lower()
if self.engine == "tika" and self.kwargs.get("TIKA_SERVER_URL"):
if file_ext in known_source_ext or (
file_content_type and file_content_type.find("text/") >= 0
):
loader = TextLoader(file_path, autodetect_encoding=True)
else:
loader = TikaLoader(
url=self.kwargs.get("TIKA_SERVER_URL"),
file_path=file_path,
mime_type=file_content_type,
)
else:
if file_ext == "pdf":
loader = PyPDFLoader(
file_path, extract_images=self.kwargs.get("PDF_EXTRACT_IMAGES")
)
elif file_ext == "csv":
loader = CSVLoader(file_path)
elif file_ext == "rst":
loader = UnstructuredRSTLoader(file_path, mode="elements")
elif file_ext == "xml":
loader = UnstructuredXMLLoader(file_path)
elif file_ext in ["htm", "html"]:
loader = BSHTMLLoader(file_path, open_encoding="unicode_escape")
elif file_ext == "md":
loader = UnstructuredMarkdownLoader(file_path)
elif file_content_type == "application/epub+zip":
loader = UnstructuredEPubLoader(file_path)
elif (
file_content_type
== "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
or file_ext == "docx"
):
loader = Docx2txtLoader(file_path)
elif file_content_type in [
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
] or file_ext in ["xls", "xlsx"]:
loader = UnstructuredExcelLoader(file_path)
elif file_content_type in [
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
] or file_ext in ["ppt", "pptx"]:
loader = UnstructuredPowerPointLoader(file_path)
elif file_ext == "msg":
loader = OutlookMessageLoader(file_path)
elif file_ext in known_source_ext or (
file_content_type and file_content_type.find("text/") >= 0
):
loader = TextLoader(file_path, autodetect_encoding=True)
else:
loader = TextLoader(file_path, autodetect_encoding=True)
return loader
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
import os
import torch
import numpy as np
from colbert.infra import ColBERTConfig
from colbert.modeling.checkpoint import Checkpoint
class ColBERT:
def __init__(self, name, **kwargs) -> None:
print("ColBERT: Loading model", name)
self.device = "cuda" if torch.cuda.is_available() else "cpu"
DOCKER = kwargs.get("env") == "docker"
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
@@ -1,50 +1,85 @@
import os
import logging
import os
import uuid
from typing import Optional, Union
import requests
from typing import 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 (
GenerateEmbedForm,
generate_ollama_batch_embeddings,
)
from open_webui.apps.retrieval.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 open_webui.config import DEFAULT_RAG_TEMPLATE
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,
embedding_function,
query_embedding: list[float],
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=[query_embedding],
limit=k,
)
log.info(f"query_doc:result {result}")
return result
except Exception as e:
print(e)
raise e
@@ -55,27 +90,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 +133,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 = []
@@ -146,20 +181,23 @@ def query_collection(
query: str,
embedding_function,
k: int,
):
) -> dict:
results = []
query_embedding = embedding_function(query)
for collection_name in collection_names:
if collection_name:
try:
result = query_doc(
collection_name=collection_name,
query=query,
k=k,
embedding_function=embedding_function,
query_embedding=query_embedding,
)
results.append(result)
except Exception:
pass
if result is not None:
results.append(result.model_dump())
except Exception as e:
log.exception(f"Error when querying the collection: {e}")
else:
pass
@@ -173,8 +211,9 @@ def query_collection_with_hybrid_search(
k: int,
reranking_function,
r: float,
):
) -> dict:
results = []
error = False
for collection_name in collection_names:
try:
result = query_doc_with_hybrid_search(
@@ -186,14 +225,55 @@ def query_collection_with_hybrid_search(
r=r,
)
results.append(result)
except Exception:
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):
if template == "":
template = DEFAULT_RAG_TEMPLATE
if "[context]" not in template and "{{CONTEXT}}" not in template:
log.debug(
"WARNING: The RAG template does not contain the '[context]' or '{{CONTEXT}}' placeholder."
)
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."
)
query_placeholders = []
if "[query]" in context:
query_placeholder = "{{QUERY" + str(uuid.uuid4()) + "}}"
template = template.replace("[query]", query_placeholder)
query_placeholders.append(query_placeholder)
if "{{QUERY}}" in context:
query_placeholder = "{{QUERY" + str(uuid.uuid4()) + "}}"
template = template.replace("{{QUERY}}", query_placeholder)
query_placeholders.append(query_placeholder)
template = template.replace("[context]", context)
template = template.replace("{{CONTEXT}}", context)
template = template.replace("[query]", query)
template = template.replace("{{QUERY}}", query)
for query_placeholder in query_placeholders:
template = template.replace(query_placeholder, query)
return template
@@ -203,39 +283,27 @@ def get_embedding_function(
embedding_function,
openai_key,
openai_url,
batch_size,
embedding_batch_size,
):
if embedding_engine == "":
return lambda query: embedding_function.encode(query).tolist()
elif embedding_engine in ["ollama", "openai"]:
if embedding_engine == "ollama":
func = lambda query: generate_ollama_embeddings(
GenerateEmbeddingsForm(
**{
"model": embedding_model,
"prompt": query,
}
)
)
elif embedding_engine == "openai":
func = lambda query: generate_openai_embeddings(
model=embedding_model,
text=query,
key=openai_key,
url=openai_url,
)
func = lambda query: generate_embeddings(
engine=embedding_engine,
model=embedding_model,
text=query,
key=openai_key if embedding_engine == "openai" else "",
url=openai_url if embedding_engine == "openai" else "",
)
def generate_multiple(query, f):
def generate_multiple(query, func):
if isinstance(query, list):
if embedding_engine == "openai":
embeddings = []
for i in range(0, len(query), batch_size):
embeddings.extend(f(query[i : i + batch_size]))
return embeddings
else:
return [f(q) for q in query]
embeddings = []
for i in range(0, len(query), embedding_batch_size):
embeddings.extend(func(query[i : i + embedding_batch_size]))
return embeddings
else:
return f(query)
return func(query)
return lambda query: generate_multiple(query, func)
@@ -256,71 +324,107 @@ def get_rag_context(
relevant_contexts = []
for file in files:
context = None
collection_names = (
file["collection_names"]
if file["type"] == "collection"
else [file["collection_name"]] if file["collection_name"] else []
)
collection_names = set(collection_names).difference(extracted_collections)
if not collection_names:
log.debug(f"skipping {file} as it has already been extracted")
continue
try:
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:
context = query_collection(
collection_names=collection_names,
query=query,
embedding_function=embedding_function,
k=k,
)
except Exception as e:
log.exception(e)
if file.get("context") == "full":
context = {
"documents": [[file.get("file").get("data", {}).get("content")]],
"metadatas": [[{"file_id": file.get("id"), "name": file.get("name")}]],
}
else:
context = None
if context:
relevant_contexts.append({**context, "source": file})
collection_names = []
if file.get("type") == "collection":
if file.get("legacy"):
collection_names = file.get("collection_names", [])
else:
collection_names.append(file["id"])
elif file.get("collection_name"):
collection_names.append(file["collection_name"])
elif file.get("id"):
if file.get("legacy"):
collection_names.append(f"{file['id']}")
else:
collection_names.append(f"file-{file['id']}")
extracted_collections.extend(collection_names)
collection_names = set(collection_names).difference(extracted_collections)
if not collection_names:
log.debug(f"skipping {file} as it has already been extracted")
continue
try:
context = None
if file.get("type") == "text":
context = file["content"]
else:
if hybrid_search:
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,
embedding_function=embedding_function,
k=k,
)
except Exception as e:
log.exception(e)
extracted_collections.extend(collection_names)
if context:
if "data" in file:
del file["data"]
relevant_contexts.append({**context, "file": file})
contexts = []
citations = []
for context in relevant_contexts:
try:
if "documents" in context:
file_names = list(
set(
[
metadata["name"]
for metadata in context["metadatas"][0]
if metadata is not None and "name" in metadata
]
)
)
contexts.append(
"\n\n".join(
((", ".join(file_names) + ":\n\n") if file_names else "")
+ "\n\n".join(
[text for text in context["documents"][0] if text is not None]
)
)
if "metadatas" in context:
citations.append(
{
"source": context["source"],
"document": context["documents"][0],
"metadata": context["metadatas"][0],
}
)
citation = {
"source": context["file"],
"document": context["documents"][0],
"metadata": context["metadatas"][0],
}
if "distances" in context and context["distances"]:
citation["distances"] = context["distances"][0]
citations.append(citation)
except Exception as e:
log.exception(e)
print("contexts", contexts)
print("citations", citations)
return contexts, citations
@@ -362,20 +466,6 @@ def get_model_path(model: str, update_model: bool = False):
return model
def generate_openai_embeddings(
model: str,
text: Union[str, list[str]],
key: str,
url: str = "https://api.openai.com/v1",
):
if isinstance(text, list):
embeddings = generate_openai_batch_embeddings(model, text, key, url)
else:
embeddings = generate_openai_batch_embeddings(model, [text], key, url)
return embeddings[0] if isinstance(text, str) else embeddings
def generate_openai_batch_embeddings(
model: str, texts: list[str], key: str, url: str = "https://api.openai.com/v1"
) -> Optional[list[list[float]]]:
@@ -399,52 +489,38 @@ 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],
)
def generate_embeddings(engine: str, model: str, text: Union[str, list[str]], **kwargs):
if engine == "ollama":
if isinstance(text, list):
embeddings = generate_ollama_batch_embeddings(
GenerateEmbedForm(**{"model": model, "input": text})
)
return results
else:
embeddings = generate_ollama_batch_embeddings(
GenerateEmbedForm(**{"model": model, "input": [text]})
)
return (
embeddings["embeddings"][0]
if isinstance(text, str)
else embeddings["embeddings"]
)
elif engine == "openai":
key = kwargs.get("key", "")
url = kwargs.get("url", "https://api.openai.com/v1")
if isinstance(text, list):
embeddings = generate_openai_batch_embeddings(model, text, key, url)
else:
embeddings = generate_openai_batch_embeddings(model, [text], key, url)
return embeddings[0] if isinstance(text, str) else embeddings
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):
@@ -454,7 +530,7 @@ class RerankCompressor(BaseDocumentCompressor):
r_score: float
class Config:
extra = Extra.forbid
extra = "forbid"
arbitrary_types_allowed = True
def compress_documents(
@@ -0,0 +1,14 @@
from open_webui.config import VECTOR_DB
if VECTOR_DB == "milvus":
from open_webui.apps.retrieval.vector.dbs.milvus import MilvusClient
VECTOR_DB_CLIENT = MilvusClient()
elif VECTOR_DB == "qdrant":
from open_webui.apps.retrieval.vector.dbs.qdrant import QdrantClient
VECTOR_DB_CLIENT = QdrantClient()
else:
from open_webui.apps.retrieval.vector.dbs.chroma import ChromaClient
VECTOR_DB_CLIENT = ChromaClient()
@@ -0,0 +1,161 @@
import chromadb
from chromadb import Settings
from chromadb.utils.batch_utils import create_batches
from typing import Optional
from open_webui.apps.retrieval.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.
try:
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
except Exception as e:
return None
def query(
self, collection_name: str, filter: dict, limit: Optional[int] = None
) -> Optional[GetResult]:
# Query the items from the collection based on the filter.
try:
collection = self.client.get_collection(name=collection_name)
if collection:
result = collection.get(
where=filter,
limit=limit,
)
return GetResult(
**{
"ids": [result["ids"]],
"documents": [result["documents"]],
"metadatas": [result["metadatas"]],
}
)
return None
except Exception as e:
print(e)
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, metadata={"hnsw:space": "cosine"}
)
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, metadata={"hnsw:space": "cosine"}
)
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: Optional[list[str]] = None,
filter: Optional[dict] = None,
):
# Delete the items from the collection based on the ids.
collection = self.client.get_collection(name=collection_name)
if collection:
if ids:
collection.delete(ids=ids)
elif filter:
collection.delete(where=filter)
def reset(self):
# Resets the database. This will delete all collections and item entries.
return self.client.reset()
@@ -0,0 +1,286 @@
from pymilvus import MilvusClient as Client
from pymilvus import FieldSchema, DataType
import json
from typing import Optional
from open_webui.apps.retrieval.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:
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:
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={"M": 16, "efConstruction": 100},
)
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.
collection_name = collection_name.replace("-", "_")
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.
collection_name = collection_name.replace("-", "_")
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.
collection_name = collection_name.replace("-", "_")
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 query(self, collection_name: str, filter: dict, limit: Optional[int] = None):
# Construct the filter string for querying
collection_name = collection_name.replace("-", "_")
if not self.has_collection(collection_name):
return None
filter_string = " && ".join(
[
f'metadata["{key}"] == {json.dumps(value)}'
for key, value in filter.items()
]
)
max_limit = 16383 # The maximum number of records per request
all_results = []
if limit is None:
limit = float("inf") # Use infinity as a placeholder for no limit
# Initialize offset and remaining to handle pagination
offset = 0
remaining = limit
try:
# Loop until there are no more items to fetch or the desired limit is reached
while remaining > 0:
print("remaining", remaining)
current_fetch = min(
max_limit, remaining
) # Determine how many items to fetch in this iteration
results = self.client.query(
collection_name=f"{self.collection_prefix}_{collection_name}",
filter=filter_string,
output_fields=["*"],
limit=current_fetch,
offset=offset,
)
if not results:
break
all_results.extend(results)
results_count = len(results)
remaining -= (
results_count # Decrease remaining by the number of items fetched
)
offset += results_count
# Break the loop if the results returned are less than the requested fetch count
if results_count < current_fetch:
break
print(all_results)
return self._result_to_get_result([all_results])
except Exception as e:
print(e)
return None
def get(self, collection_name: str) -> Optional[GetResult]:
# Get all the items in the collection.
collection_name = collection_name.replace("-", "_")
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.
collection_name = collection_name.replace("-", "_")
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.
collection_name = collection_name.replace("-", "_")
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: Optional[list[str]] = None,
filter: Optional[dict] = None,
):
# Delete the items from the collection based on the ids.
collection_name = collection_name.replace("-", "_")
if ids:
return self.client.delete(
collection_name=f"{self.collection_prefix}_{collection_name}",
ids=ids,
)
elif filter:
# Convert the filter dictionary to a string using JSON_CONTAINS.
filter_string = " && ".join(
[
f'metadata["{key}"] == {json.dumps(value)}'
for key, value in filter.items()
]
)
return self.client.delete(
collection_name=f"{self.collection_prefix}_{collection_name}",
filter=filter_string,
)
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,179 @@
from typing import Optional
from qdrant_client import QdrantClient as Qclient
from qdrant_client.http.models import PointStruct
from qdrant_client.models import models
from open_webui.apps.retrieval.vector.main import VectorItem, SearchResult, GetResult
from open_webui.config import QDRANT_URI
NO_LIMIT = 999999999
class QdrantClient:
def __init__(self):
self.collection_prefix = "open-webui"
self.QDRANT_URI = QDRANT_URI
self.client = Qclient(url=self.QDRANT_URI) if self.QDRANT_URI else None
def _result_to_get_result(self, points) -> GetResult:
ids = []
documents = []
metadatas = []
for point in points:
payload = point.payload
ids.append(point.id)
documents.append(payload["text"])
metadatas.append(payload["metadata"])
return GetResult(
**{
"ids": [ids],
"documents": [documents],
"metadatas": [metadatas],
}
)
def _create_collection(self, collection_name: str, dimension: int):
collection_name_with_prefix = f"{self.collection_prefix}_{collection_name}"
self.client.create_collection(
collection_name=collection_name_with_prefix,
vectors_config=models.VectorParams(
size=dimension, distance=models.Distance.COSINE
),
)
print(f"collection {collection_name_with_prefix} successfully created!")
def _create_collection_if_not_exists(self, collection_name, dimension):
if not self.has_collection(collection_name=collection_name):
self._create_collection(
collection_name=collection_name, dimension=dimension
)
def _create_points(self, items: list[VectorItem]):
return [
PointStruct(
id=item["id"],
vector=item["vector"],
payload={"text": item["text"], "metadata": item["metadata"]},
)
for item in items
]
def has_collection(self, collection_name: str) -> bool:
return self.client.collection_exists(
f"{self.collection_prefix}_{collection_name}"
)
def delete_collection(self, collection_name: str):
return self.client.delete_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.
if limit is None:
limit = NO_LIMIT # otherwise qdrant would set limit to 10!
query_response = self.client.query_points(
collection_name=f"{self.collection_prefix}_{collection_name}",
query=vectors[0],
limit=limit,
)
get_result = self._result_to_get_result(query_response.points)
return SearchResult(
ids=get_result.ids,
documents=get_result.documents,
metadatas=get_result.metadatas,
distances=[[point.score for point in query_response.points]],
)
def query(self, collection_name: str, filter: dict, limit: Optional[int] = None):
# Construct the filter string for querying
if not self.has_collection(collection_name):
return None
try:
if limit is None:
limit = NO_LIMIT # otherwise qdrant would set limit to 10!
field_conditions = []
for key, value in filter.items():
field_conditions.append(
models.FieldCondition(
key=f"metadata.{key}", match=models.MatchValue(value=value)
)
)
points = self.client.query_points(
collection_name=f"{self.collection_prefix}_{collection_name}",
query_filter=models.Filter(should=field_conditions),
limit=limit,
)
return self._result_to_get_result(points.points)
except Exception as e:
print(e)
return None
def get(self, collection_name: str) -> Optional[GetResult]:
# Get all the items in the collection.
points = self.client.query_points(
collection_name=f"{self.collection_prefix}_{collection_name}",
limit=NO_LIMIT, # otherwise qdrant would set limit to 10!
)
return self._result_to_get_result(points.points)
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.
self._create_collection_if_not_exists(collection_name, len(items[0]["vector"]))
points = self._create_points(items)
self.client.upload_points(f"{self.collection_prefix}_{collection_name}", points)
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.
self._create_collection_if_not_exists(collection_name, len(items[0]["vector"]))
points = self._create_points(items)
return self.client.upsert(f"{self.collection_prefix}_{collection_name}", points)
def delete(
self,
collection_name: str,
ids: Optional[list[str]] = None,
filter: Optional[dict] = None,
):
# Delete the items from the collection based on the ids.
field_conditions = []
if ids:
for id_value in ids:
field_conditions.append(
models.FieldCondition(
key="metadata.id",
match=models.MatchValue(value=id_value),
),
),
elif filter:
for key, value in filter.items():
field_conditions.append(
models.FieldCondition(
key=f"metadata.{key}",
match=models.MatchValue(value=value),
),
),
return self.client.delete(
collection_name=f"{self.collection_prefix}_{collection_name}",
points_selector=models.FilterSelector(
filter=models.Filter(must=field_conditions)
),
)
def reset(self):
# Resets the database. This will delete all collections and item entries.
collection_names = self.client.get_collections().collections
for collection_name in collection_names:
if collection_name.name.startswith(self.collection_prefix):
self.client.delete_collection(collection_name=collection_name.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,9 +1,9 @@
import logging
from typing import Optional
import requests
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -1,8 +1,9 @@
import logging
from typing import Optional
from apps.rag.search.main import SearchResult, get_filtered_results
from open_webui.apps.retrieval.web.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"])
@@ -1,10 +1,9 @@
import json
import logging
from typing import Optional
import requests
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -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.retrieval.web.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"])
@@ -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.retrieval.web.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
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.retrieval.web.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -1,10 +1,10 @@
import json
import logging
from typing import Optional
import requests
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -1,11 +1,10 @@
import json
import logging
from typing import Optional
import requests
from urllib.parse import urlencode
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -1,10 +1,9 @@
import json
import logging
from typing import Optional
import requests
from apps.rag.search.main import SearchResult, get_filtered_results
from config import SRC_LOG_LEVELS
import requests
from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -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.retrieval.web.main import SearchResult
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
File diff suppressed because one or more lines are too long
@@ -0,0 +1,97 @@
import socket
import urllib.parse
import validators
from typing import Union, Sequence, Iterator
from langchain_community.document_loaders import (
WebBaseLoader,
)
from langchain_core.documents import Document
from open_webui.constants import ERROR_MESSAGES
from open_webui.config import ENABLE_RAG_LOCAL_WEB_FETCH
from open_webui.env import SRC_LOG_LEVELS
import logging
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
def validate_url(url: Union[str, Sequence[str]]):
if isinstance(url, str):
if isinstance(validators.url(url), validators.ValidationError):
raise ValueError(ERROR_MESSAGES.INVALID_URL)
if not ENABLE_RAG_LOCAL_WEB_FETCH:
# Local web fetch is disabled, filter out any URLs that resolve to private IP addresses
parsed_url = urllib.parse.urlparse(url)
# Get IPv4 and IPv6 addresses
ipv4_addresses, ipv6_addresses = resolve_hostname(parsed_url.hostname)
# Check if any of the resolved addresses are private
# This is technically still vulnerable to DNS rebinding attacks, as we don't control WebBaseLoader
for ip in ipv4_addresses:
if validators.ipv4(ip, private=True):
raise ValueError(ERROR_MESSAGES.INVALID_URL)
for ip in ipv6_addresses:
if validators.ipv6(ip, private=True):
raise ValueError(ERROR_MESSAGES.INVALID_URL)
return True
elif isinstance(url, Sequence):
return all(validate_url(u) for u in url)
else:
return False
def resolve_hostname(hostname):
# Get address information
addr_info = socket.getaddrinfo(hostname, None)
# Extract IP addresses from address information
ipv4_addresses = [info[4][0] for info in addr_info if info[0] == socket.AF_INET]
ipv6_addresses = [info[4][0] for info in addr_info if info[0] == socket.AF_INET6]
return ipv4_addresses, ipv6_addresses
class SafeWebBaseLoader(WebBaseLoader):
"""WebBaseLoader with enhanced error handling for URLs."""
def lazy_load(self) -> Iterator[Document]:
"""Lazy load text from the url(s) in web_path with error handling."""
for path in self.web_paths:
try:
soup = self._scrape(path, bs_kwargs=self.bs_kwargs)
text = soup.get_text(**self.bs_get_text_kwargs)
# Build metadata
metadata = {"source": path}
if title := soup.find("title"):
metadata["title"] = title.get_text()
if description := soup.find("meta", attrs={"name": "description"}):
metadata["description"] = description.get(
"content", "No description found."
)
if html := soup.find("html"):
metadata["language"] = html.get("lang", "No language found.")
yield Document(page_content=text, metadata=metadata)
except Exception as e:
# Log the error and continue with the next URL
log.error(f"Error loading {path}: {e}")
def get_web_loader(
url: Union[str, Sequence[str]],
verify_ssl: bool = True,
requests_per_second: int = 2,
):
# Check if the URL is valid
if not validate_url(url):
raise ValueError(ERROR_MESSAGES.INVALID_URL)
return SafeWebBaseLoader(
url,
verify_ssl=verify_ssl,
requests_per_second=requests_per_second,
continue_on_failure=True,
)
+219
View File
@@ -0,0 +1,219 @@
import asyncio
import socketio
import logging
import sys
import time
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
from open_webui.apps.socket.utils import RedisDict
from open_webui.env import (
GLOBAL_LOG_LEVEL,
SRC_LOG_LEVELS,
)
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["SOCKET"])
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,
)
# Dictionary to maintain the user pool
if WEBSOCKET_MANAGER == "redis":
SESSION_POOL = RedisDict("open-webui:session_pool", redis_url=WEBSOCKET_REDIS_URL)
USER_POOL = RedisDict("open-webui:user_pool", redis_url=WEBSOCKET_REDIS_URL)
USAGE_POOL = RedisDict("open-webui:usage_pool", redis_url=WEBSOCKET_REDIS_URL)
else:
SESSION_POOL = {}
USER_POOL = {}
USAGE_POOL = {}
# Timeout duration in seconds
TIMEOUT_DURATION = 3
async def periodic_usage_pool_cleanup():
while True:
now = int(time.time())
for model_id, connections in list(USAGE_POOL.items()):
# Creating a list of sids to remove if they have timed out
expired_sids = [
sid
for sid, details in connections.items()
if now - details["updated_at"] > TIMEOUT_DURATION
]
for sid in expired_sids:
del connections[sid]
if not connections:
log.debug(f"Cleaning up model {model_id} from usage pool")
del USAGE_POOL[model_id]
else:
USAGE_POOL[model_id] = connections
# Emit updated usage information after cleaning
await sio.emit("usage", {"models": get_models_in_use()})
await asyncio.sleep(TIMEOUT_DURATION)
app = socketio.ASGIApp(
sio,
socketio_path="/ws/socket.io",
)
def get_models_in_use():
# List models that are currently in use
models_in_use = list(USAGE_POOL.keys())
return models_in_use
@sio.on("usage")
async def usage(sid, data):
model_id = data["model"]
# Record the timestamp for the last update
current_time = int(time.time())
# Store the new usage data and task
USAGE_POOL[model_id] = {
**(USAGE_POOL[model_id] if model_id in USAGE_POOL else {}),
sid: {"updated_at": current_time},
}
# Broadcast the usage data to all clients
await sio.emit("usage", {"models": get_models_in_use()})
@sio.event
async def connect(sid, environ, auth):
user = None
if auth and "token" in auth:
data = decode_token(auth["token"])
if data is not None and "id" in data:
user = Users.get_user_by_id(data["id"])
if user:
SESSION_POOL[sid] = user.id
if user.id in USER_POOL:
USER_POOL[user.id].append(sid)
else:
USER_POOL[user.id] = [sid]
# print(f"user {user.name}({user.id}) connected with session ID {sid}")
await sio.emit("user-count", {"count": len(USER_POOL.items())})
await sio.emit("usage", {"models": get_models_in_use()})
@sio.on("user-join")
async def 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:
return
data = decode_token(auth["token"])
if data is None or "id" not in data:
return
user = Users.get_user_by_id(data["id"])
if not user:
return
SESSION_POOL[sid] = user.id
if user.id in USER_POOL:
USER_POOL[user.id].append(sid)
else:
USER_POOL[user.id] = [sid]
# print(f"user {user.name}({user.id}) connected with session ID {sid}")
await sio.emit("user-count", {"count": len(USER_POOL.items())})
@sio.on("user-count")
async def user_count(sid):
await sio.emit("user-count", {"count": len(USER_POOL.items())})
@sio.event
async def disconnect(sid):
if sid in SESSION_POOL:
user_id = SESSION_POOL[sid]
del SESSION_POOL[sid]
USER_POOL[user_id] = [_sid for _sid in USER_POOL[user_id] if _sid != sid]
if len(USER_POOL[user_id]) == 0:
del USER_POOL[user_id]
await sio.emit("user-count", {"count": len(USER_POOL)})
else:
pass
# print(f"Unknown session ID {sid} disconnected")
def get_event_emitter(request_info):
async def __event_emitter__(event_data):
await sio.emit(
"chat-events",
{
"chat_id": request_info["chat_id"],
"message_id": request_info["message_id"],
"data": event_data,
},
to=request_info["session_id"],
)
return __event_emitter__
def get_event_call(request_info):
async def __event_call__(event_data):
response = await sio.call(
"chat-events",
{
"chat_id": request_info["chat_id"],
"message_id": request_info["message_id"],
"data": event_data,
},
to=request_info["session_id"],
)
return response
return __event_call__
+59
View File
@@ -0,0 +1,59 @@
import json
import redis
class RedisDict:
def __init__(self, name, redis_url):
self.name = name
self.redis = redis.Redis.from_url(redis_url, decode_responses=True)
def __setitem__(self, key, value):
serialized_value = json.dumps(value)
self.redis.hset(self.name, key, serialized_value)
def __getitem__(self, key):
value = self.redis.hget(self.name, key)
if value is None:
raise KeyError(key)
return json.loads(value)
def __delitem__(self, key):
result = self.redis.hdel(self.name, key)
if result == 0:
raise KeyError(key)
def __contains__(self, key):
return self.redis.hexists(self.name, key)
def __len__(self):
return self.redis.hlen(self.name)
def keys(self):
return self.redis.hkeys(self.name)
def values(self):
return [json.loads(v) for v in self.redis.hvals(self.name)]
def items(self):
return [(k, json.loads(v)) for k, v in self.redis.hgetall(self.name).items()]
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def clear(self):
self.redis.delete(self.name)
def update(self, other=None, **kwargs):
if other is not None:
for k, v in other.items() if hasattr(other, "items") else other:
self[k] = v
for k, v in kwargs.items():
self[k] = v
def setdefault(self, key, default=None):
if key not in self:
self[key] = default
return self[key]
@@ -1,21 +1,25 @@
import os
import logging
import json
import logging
from contextlib import contextmanager
from typing import Any, Optional
from typing import Optional, Any
from typing_extensions import Self
from sqlalchemy import create_engine, types, Dialect
from sqlalchemy.sql.type_api import _T
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
from open_webui.apps.webui.internal.wrappers import register_connection
from open_webui.env import (
OPEN_WEBUI_DIR,
DATABASE_URL,
SRC_LOG_LEVELS,
DATABASE_POOL_MAX_OVERFLOW,
DATABASE_POOL_RECYCLE,
DATABASE_POOL_SIZE,
DATABASE_POOL_TIMEOUT,
)
from peewee_migrate import Router
from apps.webui.internal.wrappers import register_connection
from env import SRC_LOG_LEVELS, BACKEND_DIR, DATABASE_URL
from sqlalchemy import Dialect, create_engine, types
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.pool import QueuePool, NullPool
from sqlalchemy.sql.type_api import _T
from typing_extensions import Self
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["DB"])
@@ -50,7 +54,7 @@ def handle_peewee_migration(DATABASE_URL):
try:
# Replace the postgresql:// with postgres:// to handle the peewee migration
db = register_connection(DATABASE_URL.replace("postgresql://", "postgres://"))
migrate_dir = BACKEND_DIR / "apps" / "webui" / "internal" / "migrations"
migrate_dir = OPEN_WEBUI_DIR / "apps" / "webui" / "internal" / "migrations"
router = Router(db, logger=log, migrate_dir=migrate_dir)
router.run()
db.close()
@@ -76,7 +80,20 @@ if "sqlite" in SQLALCHEMY_DATABASE_URL:
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
else:
engine = create_engine(SQLALCHEMY_DATABASE_URL, pool_pre_ping=True)
if DATABASE_POOL_SIZE > 0:
engine = create_engine(
SQLALCHEMY_DATABASE_URL,
pool_size=DATABASE_POOL_SIZE,
max_overflow=DATABASE_POOL_MAX_OVERFLOW,
pool_timeout=DATABASE_POOL_TIMEOUT,
pool_recycle=DATABASE_POOL_RECYCLE,
pool_pre_ping=True,
poolclass=QueuePool,
)
else:
engine = create_engine(
SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, poolclass=NullPool
)
SessionLocal = sessionmaker(
@@ -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 env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["DB"])
@@ -1,65 +1,76 @@
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
import time
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,
folders,
configs,
memories,
utils,
files,
functions,
memories,
models,
knowledge,
prompts,
evaluations,
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,
ENABLE_EVALUATION_ARENA_MODELS,
EVALUATION_ARENA_MODELS,
DEFAULT_ARENA_MODEL,
JWT_EXPIRES_IN,
ENABLE_OAUTH_ROLE_MANAGEMENT,
OAUTH_ROLES_CLAIM,
OAUTH_EMAIL_CLAIM,
OAUTH_PICTURE_CLAIM,
OAUTH_USERNAME_CLAIM,
OAUTH_ALLOWED_ROLES,
OAUTH_ADMIN_ROLES,
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,
)
from open_webui.utils.payload import (
apply_model_params_to_body_openai,
apply_model_system_prompt_to_body,
)
from utils.tools import get_tools
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,
ENABLE_MESSAGE_RATING,
AppConfig,
OAUTH_USERNAME_CLAIM,
OAUTH_PICTURE_CLAIM,
OAUTH_EMAIL_CLAIM,
CORS_ALLOW_ORIGIN,
)
from apps.socket.main import get_event_call, get_event_emitter
import inspect
import json
import logging
from typing import Iterator, Generator, AsyncGenerator
from pydantic import BaseModel
from open_webui.utils.tools import get_tools
app = FastAPI()
@@ -88,10 +99,18 @@ 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.ENABLE_EVALUATION_ARENA_MODELS = ENABLE_EVALUATION_ARENA_MODELS
app.state.config.EVALUATION_ARENA_MODELS = EVALUATION_ARENA_MODELS
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.config.ENABLE_OAUTH_ROLE_MANAGEMENT = ENABLE_OAUTH_ROLE_MANAGEMENT
app.state.config.OAUTH_ROLES_CLAIM = OAUTH_ROLES_CLAIM
app.state.config.OAUTH_ALLOWED_ROLES = OAUTH_ALLOWED_ROLES
app.state.config.OAUTH_ADMIN_ROLES = OAUTH_ADMIN_ROLES
app.state.MODELS = {}
app.state.TOOLS = {}
app.state.FUNCTIONS = {}
@@ -106,19 +125,24 @@ app.add_middleware(
app.include_router(configs.router, prefix="/configs", tags=["configs"])
app.include_router(auths.router, prefix="/auths", tags=["auths"])
app.include_router(users.router, prefix="/users", tags=["users"])
app.include_router(chats.router, prefix="/chats", tags=["chats"])
app.include_router(documents.router, prefix="/documents", tags=["documents"])
app.include_router(models.router, prefix="/models", tags=["models"])
app.include_router(knowledge.router, prefix="/knowledge", tags=["knowledge"])
app.include_router(prompts.router, prefix="/prompts", tags=["prompts"])
app.include_router(memories.router, prefix="/memories", tags=["memories"])
app.include_router(files.router, prefix="/files", tags=["files"])
app.include_router(tools.router, prefix="/tools", tags=["tools"])
app.include_router(functions.router, prefix="/functions", tags=["functions"])
app.include_router(memories.router, prefix="/memories", tags=["memories"])
app.include_router(evaluations.router, prefix="/evaluations", tags=["evaluations"])
app.include_router(folders.router, prefix="/folders", tags=["folders"])
app.include_router(files.router, prefix="/files", tags=["files"])
app.include_router(utils.router, prefix="/utils", tags=["utils"])
@@ -132,6 +156,47 @@ async def get_status():
}
async def get_all_models():
models = []
pipe_models = await get_pipe_models()
models = models + pipe_models
if app.state.config.ENABLE_EVALUATION_ARENA_MODELS:
arena_models = []
if len(app.state.config.EVALUATION_ARENA_MODELS) > 0:
arena_models = [
{
"id": model["id"],
"name": model["name"],
"info": {
"meta": model["meta"],
},
"object": "model",
"created": int(time.time()),
"owned_by": "arena",
"arena": True,
}
for model in app.state.config.EVALUATION_ARENA_MODELS
]
else:
# Add default arena model
arena_models = [
{
"id": DEFAULT_ARENA_MODEL["id"],
"name": DEFAULT_ARENA_MODEL["name"],
"info": {
"meta": DEFAULT_ARENA_MODEL["meta"],
},
"object": "model",
"created": int(time.time()),
"owned_by": "arena",
"arena": True,
}
]
models = models + arena_models
return models
def get_function_module(pipe_id: str):
# Check if function is already loaded
if pipe_id not in app.state.FUNCTIONS:
@@ -155,29 +220,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",
@@ -186,8 +255,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(
{
@@ -284,17 +351,20 @@ async def generate_function_chat_completion(form_data, user):
__event_emitter__ = None
__event_call__ = None
__task__ = None
__task_body__ = None
if metadata:
if all(k in metadata for k in ("session_id", "chat_id", "message_id")):
__event_emitter__ = get_event_emitter(metadata)
__event_call__ = get_event_call(metadata)
__task__ = metadata.get("task", None)
__task_body__ = metadata.get("task_body", None)
extra_params = {
"__event_emitter__": __event_emitter__,
"__event_call__": __event_call__,
"__task__": __task__,
"__task_body__": __task_body__,
"__files__": files,
"__user__": {
"id": user.id,
@@ -329,7 +399,7 @@ async def generate_function_chat_completion(form_data, user):
pipe = function_module.pipe
params = get_function_params(function_module, form_data, user, extra_params)
if form_data["stream"]:
if form_data.get("stream", False):
async def stream_content():
try:
@@ -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 utils.utils import verify_password
from apps.webui.models.users import UserModel, Users
from apps.webui.internal.db import Base, get_db
from env import SRC_LOG_LEVELS
from open_webui.apps.webui.internal.db import Base, get_db
from open_webui.apps.webui.models.users import UserModel, Users
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel
from sqlalchemy import Boolean, Column, String, Text
from open_webui.utils.utils import verify_password
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -92,7 +90,6 @@ class AddUserForm(SignupForm):
class AuthsTable:
def insert_new_auth(
self,
email: str,
@@ -103,7 +100,6 @@ class AuthsTable:
oauth_sub: Optional[str] = None,
) -> Optional[UserModel]:
with get_db() as db:
log.info("insert_new_auth")
id = str(uuid.uuid4())
@@ -130,7 +126,6 @@ class AuthsTable:
log.info(f"authenticate_user: {email}")
try:
with get_db() as db:
auth = db.query(Auth).filter_by(email=email, active=True).first()
if auth:
if verify_password(password, auth.password):
@@ -189,7 +184,6 @@ class AuthsTable:
def delete_auth_by_id(self, id: str) -> bool:
try:
with get_db() as db:
# Delete User
result = Users.delete_user_by_id(id)
@@ -0,0 +1,805 @@
import json
import time
import uuid
from typing import Optional
from open_webui.apps.webui.internal.db import Base, get_db
from open_webui.apps.webui.models.tags import TagModel, Tag, Tags
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Boolean, Column, String, Text, JSON
from sqlalchemy import or_, func, select, and_, text
from sqlalchemy.sql import exists
####################
# Chat DB Schema
####################
class Chat(Base):
__tablename__ = "chat"
id = Column(String, primary_key=True)
user_id = Column(String)
title = Column(Text)
chat = Column(JSON)
created_at = Column(BigInteger)
updated_at = Column(BigInteger)
share_id = Column(Text, unique=True, nullable=True)
archived = Column(Boolean, default=False)
pinned = Column(Boolean, default=False, nullable=True)
meta = Column(JSON, server_default="{}")
folder_id = Column(Text, nullable=True)
class ChatModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
user_id: str
title: str
chat: dict
created_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
share_id: Optional[str] = None
archived: bool = False
pinned: Optional[bool] = False
meta: dict = {}
folder_id: Optional[str] = None
####################
# Forms
####################
class ChatForm(BaseModel):
chat: dict
class ChatImportForm(ChatForm):
meta: Optional[dict] = {}
pinned: Optional[bool] = False
folder_id: Optional[str] = None
class ChatTitleMessagesForm(BaseModel):
title: str
messages: list[dict]
class ChatTitleForm(BaseModel):
title: str
class ChatResponse(BaseModel):
id: str
user_id: str
title: str
chat: dict
updated_at: int # timestamp in epoch
created_at: int # timestamp in epoch
share_id: Optional[str] = None # id of the chat to be shared
archived: bool
pinned: Optional[bool] = False
meta: dict = {}
folder_id: Optional[str] = None
class ChatTitleIdResponse(BaseModel):
id: str
title: str
updated_at: int
created_at: int
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(
**{
"id": id,
"user_id": user_id,
"title": (
form_data.chat["title"]
if "title" in form_data.chat
else "New Chat"
),
"chat": form_data.chat,
"created_at": int(time.time()),
"updated_at": int(time.time()),
}
)
result = Chat(**chat.model_dump())
db.add(result)
db.commit()
db.refresh(result)
return ChatModel.model_validate(result) if result else None
def import_chat(
self, user_id: str, form_data: ChatImportForm
) -> Optional[ChatModel]:
with get_db() as db:
id = str(uuid.uuid4())
chat = ChatModel(
**{
"id": id,
"user_id": user_id,
"title": (
form_data.chat["title"]
if "title" in form_data.chat
else "New Chat"
),
"chat": form_data.chat,
"meta": form_data.meta,
"pinned": form_data.pinned,
"folder_id": form_data.folder_id,
"created_at": int(time.time()),
"updated_at": int(time.time()),
}
)
result = Chat(**chat.model_dump())
db.add(result)
db.commit()
db.refresh(result)
return ChatModel.model_validate(result) if result else None
def update_chat_by_id(self, id: str, chat: dict) -> Optional[ChatModel]:
try:
with get_db() as db:
chat_item = db.get(Chat, id)
chat_item.chat = chat
chat_item.title = chat["title"] if "title" in chat else "New Chat"
chat_item.updated_at = int(time.time())
db.commit()
db.refresh(chat_item)
return ChatModel.model_validate(chat_item)
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
if chat.share_id:
return self.get_chat_by_id_and_user_id(chat.share_id, "shared")
# Create a new chat with the same data, but with a new ID
shared_chat = ChatModel(
**{
"id": str(uuid.uuid4()),
"user_id": f"shared-{chat_id}",
"title": chat.title,
"chat": chat.chat,
"created_at": chat.created_at,
"updated_at": int(time.time()),
}
)
shared_result = Chat(**shared_chat.model_dump())
db.add(shared_result)
db.commit()
db.refresh(shared_result)
# Update the original chat with the share_id
result = (
db.query(Chat)
.filter_by(id=chat_id)
.update({"share_id": shared_chat.id})
)
db.commit()
return shared_chat if (shared_result and result) else None
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)
chat.title = chat.title
chat.chat = chat.chat
db.commit()
db.refresh(chat)
return self.get_chat_by_id(chat.share_id)
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 Exception:
return False
def update_chat_share_id_by_id(
self, id: str, share_id: Optional[str]
) -> 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 Exception:
return None
def toggle_chat_pinned_by_id(self, id: str) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.get(Chat, id)
chat.pinned = not chat.pinned
chat.updated_at = int(time.time())
db.commit()
db.refresh(chat)
return ChatModel.model_validate(chat)
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
chat.updated_at = int(time.time())
db.commit()
db.refresh(chat)
return ChatModel.model_validate(chat)
except Exception:
return None
def archive_all_chats_by_user_id(self, user_id: str) -> bool:
try:
with get_db() as db:
db.query(Chat).filter_by(user_id=user_id).update({"archived": True})
db.commit()
return True
except Exception:
return False
def get_archived_chat_list_by_user_id(
self, user_id: str, skip: int = 0, limit: int = 50
) -> list[ChatModel]:
with get_db() as db:
all_chats = (
db.query(Chat)
.filter_by(user_id=user_id, archived=True)
.order_by(Chat.updated_at.desc())
# .limit(limit).offset(skip)
.all()
)
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_chat_list_by_user_id(
self,
user_id: str,
include_archived: bool = False,
skip: int = 0,
limit: int = 50,
) -> list[ChatModel]:
with get_db() as db:
query = db.query(Chat).filter_by(user_id=user_id).filter_by(folder_id=None)
if not include_archived:
query = query.filter_by(archived=False)
query = query.order_by(Chat.updated_at.desc())
if skip:
query = query.offset(skip)
if limit:
query = query.limit(limit)
all_chats = query.all()
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_chat_title_id_list_by_user_id(
self,
user_id: str,
include_archived: bool = False,
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).filter_by(folder_id=None)
query = query.filter(or_(Chat.pinned == False, Chat.pinned == None))
if not include_archived:
query = query.filter_by(archived=False)
query = query.order_by(Chat.updated_at.desc()).with_entities(
Chat.id, Chat.title, Chat.updated_at, Chat.created_at
)
if skip:
query = query.offset(skip)
if limit:
query = query.limit(limit)
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(
{
"id": chat[0],
"title": chat[1],
"updated_at": chat[2],
"created_at": chat[3],
}
)
for chat in all_chats
]
def get_chat_list_by_chat_ids(
self, chat_ids: list[str], skip: int = 0, limit: int = 50
) -> list[ChatModel]:
with get_db() as db:
all_chats = (
db.query(Chat)
.filter(Chat.id.in_(chat_ids))
.filter_by(archived=False)
.order_by(Chat.updated_at.desc())
.all()
)
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_chat_by_id(self, id: str) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.get(Chat, id)
return ChatModel.model_validate(chat)
except Exception:
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:
return None
def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.query(Chat).filter_by(id=id, user_id=user_id).first()
return ChatModel.model_validate(chat)
except Exception:
return None
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)
.order_by(Chat.updated_at.desc())
)
return [ChatModel.model_validate(chat) for chat in all_chats]
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)
.order_by(Chat.updated_at.desc())
)
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_pinned_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, pinned=True, archived=False)
.order_by(Chat.updated_at.desc())
)
return [ChatModel.model_validate(chat) for chat in all_chats]
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)
.order_by(Chat.updated_at.desc())
)
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_chats_by_user_id_and_search_text(
self,
user_id: str,
search_text: str,
include_archived: bool = False,
skip: int = 0,
limit: int = 60,
) -> list[ChatModel]:
"""
Filters chats based on a search query using Python, allowing pagination using skip and limit.
"""
search_text = search_text.lower().strip()
if not search_text:
return self.get_chat_list_by_user_id(user_id, include_archived, skip, limit)
search_text_words = search_text.split(" ")
# search_text might contain 'tag:tag_name' format so we need to extract the tag_name, split the search_text and remove the tags
tag_ids = [
word.replace("tag:", "").replace(" ", "_").lower()
for word in search_text_words
if word.startswith("tag:")
]
search_text_words = [
word for word in search_text_words if not word.startswith("tag:")
]
search_text = " ".join(search_text_words)
with get_db() as db:
query = db.query(Chat).filter(Chat.user_id == user_id)
if not include_archived:
query = query.filter(Chat.archived == False)
query = query.order_by(Chat.updated_at.desc())
# Check if the database dialect is either 'sqlite' or 'postgresql'
dialect_name = db.bind.dialect.name
if dialect_name == "sqlite":
# SQLite case: using JSON1 extension for JSON searching
query = query.filter(
(
Chat.title.ilike(
f"%{search_text}%"
) # Case-insensitive search in title
| text(
"""
EXISTS (
SELECT 1
FROM json_each(Chat.chat, '$.messages') AS message
WHERE LOWER(message.value->>'content') LIKE '%' || :search_text || '%'
)
"""
)
).params(search_text=search_text)
)
# Check if there are any tags to filter, it should have all the tags
if "none" in tag_ids:
query = query.filter(
text(
"""
NOT EXISTS (
SELECT 1
FROM json_each(Chat.meta, '$.tags') AS tag
)
"""
)
)
elif tag_ids:
query = query.filter(
and_(
*[
text(
f"""
EXISTS (
SELECT 1
FROM json_each(Chat.meta, '$.tags') AS tag
WHERE tag.value = :tag_id_{tag_idx}
)
"""
).params(**{f"tag_id_{tag_idx}": tag_id})
for tag_idx, tag_id in enumerate(tag_ids)
]
)
)
elif dialect_name == "postgresql":
# PostgreSQL relies on proper JSON query for search
query = query.filter(
(
Chat.title.ilike(
f"%{search_text}%"
) # Case-insensitive search in title
| text(
"""
EXISTS (
SELECT 1
FROM json_array_elements(Chat.chat->'messages') AS message
WHERE LOWER(message->>'content') LIKE '%' || :search_text || '%'
)
"""
)
).params(search_text=search_text)
)
# Check if there are any tags to filter, it should have all the tags
if "none" in tag_ids:
query = query.filter(
text(
"""
NOT EXISTS (
SELECT 1
FROM json_array_elements_text(Chat.meta->'tags') AS tag
)
"""
)
)
elif tag_ids:
query = query.filter(
and_(
*[
text(
f"""
EXISTS (
SELECT 1
FROM json_array_elements_text(Chat.meta->'tags') AS tag
WHERE tag = :tag_id_{tag_idx}
)
"""
).params(**{f"tag_id_{tag_idx}": tag_id})
for tag_idx, tag_id in enumerate(tag_ids)
]
)
)
else:
raise NotImplementedError(
f"Unsupported dialect: {db.bind.dialect.name}"
)
# Perform pagination at the SQL level
all_chats = query.offset(skip).limit(limit).all()
print(len(all_chats))
# Validate and return chats
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_chats_by_folder_id_and_user_id(
self, folder_id: str, user_id: str
) -> list[ChatModel]:
with get_db() as db:
query = db.query(Chat).filter_by(folder_id=folder_id, user_id=user_id)
query = query.filter(or_(Chat.pinned == False, Chat.pinned == None))
query = query.filter_by(archived=False)
query = query.order_by(Chat.updated_at.desc())
all_chats = query.all()
return [ChatModel.model_validate(chat) for chat in all_chats]
def get_chats_by_folder_ids_and_user_id(
self, folder_ids: list[str], user_id: str
) -> list[ChatModel]:
with get_db() as db:
query = db.query(Chat).filter(
Chat.folder_id.in_(folder_ids), Chat.user_id == user_id
)
query = query.filter(or_(Chat.pinned == False, Chat.pinned == None))
query = query.filter_by(archived=False)
query = query.order_by(Chat.updated_at.desc())
all_chats = query.all()
return [ChatModel.model_validate(chat) for chat in all_chats]
def update_chat_folder_id_by_id_and_user_id(
self, id: str, user_id: str, folder_id: str
) -> Optional[ChatModel]:
try:
with get_db() as db:
chat = db.get(Chat, id)
chat.folder_id = folder_id
chat.updated_at = int(time.time())
chat.pinned = False
db.commit()
db.refresh(chat)
return ChatModel.model_validate(chat)
except Exception:
return None
def get_chat_tags_by_id_and_user_id(self, id: str, user_id: str) -> list[TagModel]:
with get_db() as db:
chat = db.get(Chat, id)
tags = chat.meta.get("tags", [])
return [Tags.get_tag_by_name_and_user_id(tag, user_id) for tag in tags]
def get_chat_list_by_user_id_and_tag_name(
self, user_id: str, tag_name: str, skip: int = 0, limit: int = 50
) -> list[ChatModel]:
with get_db() as db:
query = db.query(Chat).filter_by(user_id=user_id)
tag_id = tag_name.replace(" ", "_").lower()
print(db.bind.dialect.name)
if db.bind.dialect.name == "sqlite":
# SQLite JSON1 querying for tags within the meta JSON field
query = query.filter(
text(
f"EXISTS (SELECT 1 FROM json_each(Chat.meta, '$.tags') WHERE json_each.value = :tag_id)"
)
).params(tag_id=tag_id)
elif db.bind.dialect.name == "postgresql":
# PostgreSQL JSON query for tags within the meta JSON field (for `json` type)
query = query.filter(
text(
"EXISTS (SELECT 1 FROM json_array_elements_text(Chat.meta->'tags') elem WHERE elem = :tag_id)"
)
).params(tag_id=tag_id)
else:
raise NotImplementedError(
f"Unsupported dialect: {db.bind.dialect.name}"
)
all_chats = query.all()
print("all_chats", all_chats)
return [ChatModel.model_validate(chat) for chat in all_chats]
def add_chat_tag_by_id_and_user_id_and_tag_name(
self, id: str, user_id: str, tag_name: str
) -> Optional[ChatModel]:
tag = Tags.get_tag_by_name_and_user_id(tag_name, user_id)
if tag is None:
tag = Tags.insert_new_tag(tag_name, user_id)
try:
with get_db() as db:
chat = db.get(Chat, id)
tag_id = tag.id
if tag_id not in chat.meta.get("tags", []):
chat.meta = {
**chat.meta,
"tags": list(set(chat.meta.get("tags", []) + [tag_id])),
}
db.commit()
db.refresh(chat)
return ChatModel.model_validate(chat)
except Exception:
return None
def count_chats_by_tag_name_and_user_id(self, tag_name: str, user_id: str) -> int:
with get_db() as db: # Assuming `get_db()` returns a session object
query = db.query(Chat).filter_by(user_id=user_id, archived=False)
# Normalize the tag_name for consistency
tag_id = tag_name.replace(" ", "_").lower()
if db.bind.dialect.name == "sqlite":
# SQLite JSON1 support for querying the tags inside the `meta` JSON field
query = query.filter(
text(
f"EXISTS (SELECT 1 FROM json_each(Chat.meta, '$.tags') WHERE json_each.value = :tag_id)"
)
).params(tag_id=tag_id)
elif db.bind.dialect.name == "postgresql":
# PostgreSQL JSONB support for querying the tags inside the `meta` JSON field
query = query.filter(
text(
"EXISTS (SELECT 1 FROM json_array_elements_text(Chat.meta->'tags') elem WHERE elem = :tag_id)"
)
).params(tag_id=tag_id)
else:
raise NotImplementedError(
f"Unsupported dialect: {db.bind.dialect.name}"
)
# Get the count of matching records
count = query.count()
# Debugging output for inspection
print(f"Count of chats for tag '{tag_name}':", count)
return count
def delete_tag_by_id_and_user_id_and_tag_name(
self, id: str, user_id: str, tag_name: str
) -> bool:
try:
with get_db() as db:
chat = db.get(Chat, id)
tags = chat.meta.get("tags", [])
tag_id = tag_name.replace(" ", "_").lower()
tags = [tag for tag in tags if tag != tag_id]
chat.meta = {
**chat.meta,
"tags": list(set(tags)),
}
db.commit()
return True
except Exception:
return False
def delete_all_tags_by_id_and_user_id(self, id: str, user_id: str) -> bool:
try:
with get_db() as db:
chat = db.get(Chat, id)
chat.meta = {
**chat.meta,
"tags": [],
}
db.commit()
return True
except Exception:
return False
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 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 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 Exception:
return False
def delete_chats_by_user_id_and_folder_id(
self, user_id: str, folder_id: str
) -> bool:
try:
with get_db() as db:
db.query(Chat).filter_by(user_id=user_id, folder_id=folder_id).delete()
db.commit()
return True
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]
db.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete()
db.commit()
return True
except Exception:
return False
Chats = ChatTable()
@@ -1,15 +1,12 @@
from pydantic import BaseModel, ConfigDict
from typing import Optional
import time
import logging
from sqlalchemy import String, Column, BigInteger, Text
from apps.webui.internal.db import Base, get_db
import json
import logging
import time
from typing import Optional
from env import SRC_LOG_LEVELS
from open_webui.apps.webui.internal.db import Base, get_db
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -70,12 +67,10 @@ class DocumentForm(DocumentUpdateForm):
class DocumentsTable:
def insert_new_doc(
self, user_id: str, form_data: DocumentForm
) -> Optional[DocumentModel]:
with get_db() as db:
document = DocumentModel(
**{
**form_data.model_dump(),
@@ -99,7 +94,6 @@ class DocumentsTable:
def get_doc_by_name(self, name: str) -> Optional[DocumentModel]:
try:
with get_db() as db:
document = db.query(Document).filter_by(name=name).first()
return DocumentModel.model_validate(document) if document else None
except Exception:
@@ -107,7 +101,6 @@ class DocumentsTable:
def get_docs(self) -> list[DocumentModel]:
with get_db() as db:
return [
DocumentModel.model_validate(doc) for doc in db.query(Document).all()
]
@@ -117,7 +110,6 @@ class DocumentsTable:
) -> Optional[DocumentModel]:
try:
with get_db() as db:
db.query(Document).filter_by(name=name).update(
{
"title": form_data.title,
@@ -140,7 +132,6 @@ class DocumentsTable:
doc_content = {**doc_content, **updated}
with get_db() as db:
db.query(Document).filter_by(name=name).update(
{
"content": json.dumps(doc_content),
@@ -156,7 +147,6 @@ class DocumentsTable:
def delete_doc_by_name(self, name: str) -> bool:
try:
with get_db() as db:
db.query(Document).filter_by(name=name).delete()
db.commit()
return True
@@ -0,0 +1,254 @@
import logging
import time
import uuid
from typing import Optional
from open_webui.apps.webui.internal.db import Base, get_db
from open_webui.apps.webui.models.chats import Chats
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, Text, JSON, Boolean
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
####################
# Feedback DB Schema
####################
class Feedback(Base):
__tablename__ = "feedback"
id = Column(Text, primary_key=True)
user_id = Column(Text)
version = Column(BigInteger, default=0)
type = Column(Text)
data = Column(JSON, nullable=True)
meta = Column(JSON, nullable=True)
snapshot = Column(JSON, nullable=True)
created_at = Column(BigInteger)
updated_at = Column(BigInteger)
class FeedbackModel(BaseModel):
id: str
user_id: str
version: int
type: str
data: Optional[dict] = None
meta: Optional[dict] = None
snapshot: Optional[dict] = None
created_at: int
updated_at: int
model_config = ConfigDict(from_attributes=True)
####################
# Forms
####################
class FeedbackResponse(BaseModel):
id: str
user_id: str
version: int
type: str
data: Optional[dict] = None
meta: Optional[dict] = None
created_at: int
updated_at: int
class RatingData(BaseModel):
rating: Optional[str | int] = None
model_id: Optional[str] = None
sibling_model_ids: Optional[list[str]] = None
reason: Optional[str] = None
comment: Optional[str] = None
model_config = ConfigDict(extra="allow", protected_namespaces=())
class MetaData(BaseModel):
arena: Optional[bool] = None
chat_id: Optional[str] = None
message_id: Optional[str] = None
tags: Optional[list[str]] = None
model_config = ConfigDict(extra="allow")
class SnapshotData(BaseModel):
chat: Optional[dict] = None
model_config = ConfigDict(extra="allow")
class FeedbackForm(BaseModel):
type: str
data: Optional[RatingData] = None
meta: Optional[dict] = None
snapshot: Optional[SnapshotData] = None
model_config = ConfigDict(extra="allow")
class FeedbackTable:
def insert_new_feedback(
self, user_id: str, form_data: FeedbackForm
) -> Optional[FeedbackModel]:
with get_db() as db:
id = str(uuid.uuid4())
feedback = FeedbackModel(
**{
"id": id,
"user_id": user_id,
"version": 0,
**form_data.model_dump(),
"created_at": int(time.time()),
"updated_at": int(time.time()),
}
)
try:
result = Feedback(**feedback.model_dump())
db.add(result)
db.commit()
db.refresh(result)
if result:
return FeedbackModel.model_validate(result)
else:
return None
except Exception as e:
print(e)
return None
def get_feedback_by_id(self, id: str) -> Optional[FeedbackModel]:
try:
with get_db() as db:
feedback = db.query(Feedback).filter_by(id=id).first()
if not feedback:
return None
return FeedbackModel.model_validate(feedback)
except Exception:
return None
def get_feedback_by_id_and_user_id(
self, id: str, user_id: str
) -> Optional[FeedbackModel]:
try:
with get_db() as db:
feedback = db.query(Feedback).filter_by(id=id, user_id=user_id).first()
if not feedback:
return None
return FeedbackModel.model_validate(feedback)
except Exception:
return None
def get_all_feedbacks(self) -> list[FeedbackModel]:
with get_db() as db:
return [
FeedbackModel.model_validate(feedback)
for feedback in db.query(Feedback)
.order_by(Feedback.updated_at.desc())
.all()
]
def get_feedbacks_by_type(self, type: str) -> list[FeedbackModel]:
with get_db() as db:
return [
FeedbackModel.model_validate(feedback)
for feedback in db.query(Feedback)
.filter_by(type=type)
.order_by(Feedback.updated_at.desc())
.all()
]
def get_feedbacks_by_user_id(self, user_id: str) -> list[FeedbackModel]:
with get_db() as db:
return [
FeedbackModel.model_validate(feedback)
for feedback in db.query(Feedback)
.filter_by(user_id=user_id)
.order_by(Feedback.updated_at.desc())
.all()
]
def update_feedback_by_id(
self, id: str, form_data: FeedbackForm
) -> Optional[FeedbackModel]:
with get_db() as db:
feedback = db.query(Feedback).filter_by(id=id).first()
if not feedback:
return None
if form_data.data:
feedback.data = form_data.data.model_dump()
if form_data.meta:
feedback.meta = form_data.meta
if form_data.snapshot:
feedback.snapshot = form_data.snapshot.model_dump()
feedback.updated_at = int(time.time())
db.commit()
return FeedbackModel.model_validate(feedback)
def update_feedback_by_id_and_user_id(
self, id: str, user_id: str, form_data: FeedbackForm
) -> Optional[FeedbackModel]:
with get_db() as db:
feedback = db.query(Feedback).filter_by(id=id, user_id=user_id).first()
if not feedback:
return None
if form_data.data:
feedback.data = form_data.data.model_dump()
if form_data.meta:
feedback.meta = form_data.meta
if form_data.snapshot:
feedback.snapshot = form_data.snapshot.model_dump()
feedback.updated_at = int(time.time())
db.commit()
return FeedbackModel.model_validate(feedback)
def delete_feedback_by_id(self, id: str) -> bool:
with get_db() as db:
feedback = db.query(Feedback).filter_by(id=id).first()
if not feedback:
return False
db.delete(feedback)
db.commit()
return True
def delete_feedback_by_id_and_user_id(self, id: str, user_id: str) -> bool:
with get_db() as db:
feedback = db.query(Feedback).filter_by(id=id, user_id=user_id).first()
if not feedback:
return False
db.delete(feedback)
db.commit()
return True
def delete_feedbacks_by_user_id(self, user_id: str) -> bool:
with get_db() as db:
feedbacks = db.query(Feedback).filter_by(user_id=user_id).all()
if not feedbacks:
return False
for feedback in feedbacks:
db.delete(feedback)
db.commit()
return True
def delete_all_feedbacks(self) -> bool:
with get_db() as db:
feedbacks = db.query(Feedback).all()
if not feedbacks:
return False
for feedback in feedbacks:
db.delete(feedback)
db.commit()
return True
Feedbacks = FeedbackTable()
@@ -0,0 +1,228 @@
import logging
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, String, Text, JSON
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
####################
# Files DB Schema
####################
class File(Base):
__tablename__ = "file"
id = Column(String, primary_key=True)
user_id = Column(String)
hash = Column(Text, nullable=True)
filename = Column(Text)
path = Column(Text, nullable=True)
data = Column(JSON, nullable=True)
meta = Column(JSON, nullable=True)
created_at = Column(BigInteger)
updated_at = Column(BigInteger)
class FileModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
user_id: str
hash: Optional[str] = None
filename: str
path: Optional[str] = None
data: Optional[dict] = None
meta: Optional[dict] = None
created_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
####################
# Forms
####################
class FileMeta(BaseModel):
name: Optional[str] = None
content_type: Optional[str] = None
size: Optional[int] = None
model_config = ConfigDict(extra="allow")
class FileModelResponse(BaseModel):
id: str
user_id: str
hash: Optional[str] = None
filename: str
data: Optional[dict] = None
meta: FileMeta
created_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
class FileMetadataResponse(BaseModel):
id: str
meta: dict
created_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
class FileForm(BaseModel):
id: str
hash: Optional[str] = None
filename: str
path: str
data: dict = {}
meta: dict = {}
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(),
"user_id": user_id,
"created_at": int(time.time()),
"updated_at": int(time.time()),
}
)
try:
result = File(**file.model_dump())
db.add(result)
db.commit()
db.refresh(result)
if result:
return FileModel.model_validate(result)
else:
return None
except Exception as e:
print(f"Error creating tool: {e}")
return None
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 Exception:
return None
def get_file_metadata_by_id(self, id: str) -> Optional[FileMetadataResponse]:
with get_db() as db:
try:
file = db.get(File, id)
return FileMetadataResponse(
id=file.id,
meta=file.meta,
created_at=file.created_at,
updated_at=file.updated_at,
)
except Exception:
return None
def get_files(self) -> list[FileModel]:
with get_db() as db:
return [FileModel.model_validate(file) for file in db.query(File).all()]
def get_files_by_ids(self, ids: list[str]) -> list[FileModel]:
with get_db() as db:
return [
FileModel.model_validate(file)
for file in db.query(File)
.filter(File.id.in_(ids))
.order_by(File.updated_at.desc())
.all()
]
def get_file_metadatas_by_ids(self, ids: list[str]) -> list[FileMetadataResponse]:
with get_db() as db:
return [
FileMetadataResponse(
id=file.id,
meta=file.meta,
created_at=file.created_at,
updated_at=file.updated_at,
)
for file in db.query(File)
.filter(File.id.in_(ids))
.order_by(File.updated_at.desc())
.all()
]
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 update_file_hash_by_id(self, id: str, hash: str) -> Optional[FileModel]:
with get_db() as db:
try:
file = db.query(File).filter_by(id=id).first()
file.hash = hash
db.commit()
return FileModel.model_validate(file)
except Exception:
return None
def update_file_data_by_id(self, id: str, data: dict) -> Optional[FileModel]:
with get_db() as db:
try:
file = db.query(File).filter_by(id=id).first()
file.data = {**(file.data if file.data else {}), **data}
db.commit()
return FileModel.model_validate(file)
except Exception as e:
return None
def update_file_metadata_by_id(self, id: str, meta: dict) -> Optional[FileModel]:
with get_db() as db:
try:
file = db.query(File).filter_by(id=id).first()
file.meta = {**(file.meta if file.meta else {}), **meta}
db.commit()
return FileModel.model_validate(file)
except Exception:
return None
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 Exception:
return False
def delete_all_files(self) -> bool:
with get_db() as db:
try:
db.query(File).delete()
db.commit()
return True
except Exception:
return False
Files = FilesTable()
@@ -0,0 +1,271 @@
import logging
import time
import uuid
from typing import Optional
from open_webui.apps.webui.internal.db import Base, get_db
from open_webui.apps.webui.models.chats import Chats
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, Text, JSON, Boolean
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
####################
# Folder DB Schema
####################
class Folder(Base):
__tablename__ = "folder"
id = Column(Text, primary_key=True)
parent_id = Column(Text, nullable=True)
user_id = Column(Text)
name = Column(Text)
items = Column(JSON, nullable=True)
meta = Column(JSON, nullable=True)
is_expanded = Column(Boolean, default=False)
created_at = Column(BigInteger)
updated_at = Column(BigInteger)
class FolderModel(BaseModel):
id: str
parent_id: Optional[str] = None
user_id: str
name: str
items: Optional[dict] = None
meta: Optional[dict] = None
is_expanded: bool = False
created_at: int
updated_at: int
model_config = ConfigDict(from_attributes=True)
####################
# Forms
####################
class FolderForm(BaseModel):
name: str
model_config = ConfigDict(extra="allow")
class FolderTable:
def insert_new_folder(
self, user_id: str, name: str, parent_id: Optional[str] = None
) -> Optional[FolderModel]:
with get_db() as db:
id = str(uuid.uuid4())
folder = FolderModel(
**{
"id": id,
"user_id": user_id,
"name": name,
"parent_id": parent_id,
"created_at": int(time.time()),
"updated_at": int(time.time()),
}
)
try:
result = Folder(**folder.model_dump())
db.add(result)
db.commit()
db.refresh(result)
if result:
return FolderModel.model_validate(result)
else:
return None
except Exception as e:
print(e)
return None
def get_folder_by_id_and_user_id(
self, id: str, user_id: str
) -> Optional[FolderModel]:
try:
with get_db() as db:
folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
if not folder:
return None
return FolderModel.model_validate(folder)
except Exception:
return None
def get_children_folders_by_id_and_user_id(
self, id: str, user_id: str
) -> Optional[FolderModel]:
try:
with get_db() as db:
folders = []
def get_children(folder):
children = self.get_folders_by_parent_id_and_user_id(
folder.id, user_id
)
for child in children:
get_children(child)
folders.append(child)
folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
if not folder:
return None
get_children(folder)
return folders
except Exception:
return None
def get_folders_by_user_id(self, user_id: str) -> list[FolderModel]:
with get_db() as db:
return [
FolderModel.model_validate(folder)
for folder in db.query(Folder).filter_by(user_id=user_id).all()
]
def get_folder_by_parent_id_and_user_id_and_name(
self, parent_id: Optional[str], user_id: str, name: str
) -> Optional[FolderModel]:
try:
with get_db() as db:
# Check if folder exists
folder = (
db.query(Folder)
.filter_by(parent_id=parent_id, user_id=user_id)
.filter(Folder.name.ilike(name))
.first()
)
if not folder:
return None
return FolderModel.model_validate(folder)
except Exception as e:
log.error(f"get_folder_by_parent_id_and_user_id_and_name: {e}")
return None
def get_folders_by_parent_id_and_user_id(
self, parent_id: Optional[str], user_id: str
) -> list[FolderModel]:
with get_db() as db:
return [
FolderModel.model_validate(folder)
for folder in db.query(Folder)
.filter_by(parent_id=parent_id, user_id=user_id)
.all()
]
def update_folder_parent_id_by_id_and_user_id(
self,
id: str,
user_id: str,
parent_id: str,
) -> Optional[FolderModel]:
try:
with get_db() as db:
folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
if not folder:
return None
folder.parent_id = parent_id
folder.updated_at = int(time.time())
db.commit()
return FolderModel.model_validate(folder)
except Exception as e:
log.error(f"update_folder: {e}")
return
def update_folder_name_by_id_and_user_id(
self, id: str, user_id: str, name: str
) -> Optional[FolderModel]:
try:
with get_db() as db:
folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
if not folder:
return None
existing_folder = (
db.query(Folder)
.filter_by(name=name, parent_id=folder.parent_id, user_id=user_id)
.first()
)
if existing_folder:
return None
folder.name = name
folder.updated_at = int(time.time())
db.commit()
return FolderModel.model_validate(folder)
except Exception as e:
log.error(f"update_folder: {e}")
return
def update_folder_is_expanded_by_id_and_user_id(
self, id: str, user_id: str, is_expanded: bool
) -> Optional[FolderModel]:
try:
with get_db() as db:
folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
if not folder:
return None
folder.is_expanded = is_expanded
folder.updated_at = int(time.time())
db.commit()
return FolderModel.model_validate(folder)
except Exception as e:
log.error(f"update_folder: {e}")
return
def delete_folder_by_id_and_user_id(self, id: str, user_id: str) -> bool:
try:
with get_db() as db:
folder = db.query(Folder).filter_by(id=id, user_id=user_id).first()
if not folder:
return False
# Delete all chats in the folder
Chats.delete_chats_by_user_id_and_folder_id(user_id, folder.id)
# Delete all children folders
def delete_children(folder):
folder_children = self.get_folders_by_parent_id_and_user_id(
folder.id, user_id
)
for folder_child in folder_children:
Chats.delete_chats_by_user_id_and_folder_id(
user_id, folder_child.id
)
delete_children(folder_child)
folder = db.query(Folder).filter_by(id=folder_child.id).first()
db.delete(folder)
db.commit()
delete_children(folder)
db.delete(folder)
db.commit()
return True
except Exception as e:
log.error(f"delete_folder: {e}")
return False
Folders = FolderTable()
@@ -1,18 +1,12 @@
from pydantic import BaseModel, ConfigDict
from typing import Union, Optional
import time
import logging
import time
from typing import Optional
from sqlalchemy import Column, String, Text, BigInteger, Boolean
from apps.webui.internal.db import JSONField, Base, get_db
from apps.webui.models.users import Users
import json
import copy
from env import SRC_LOG_LEVELS
from open_webui.apps.webui.internal.db import Base, JSONField, get_db
from open_webui.apps.webui.models.users import Users
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Boolean, Column, String, Text
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -87,11 +81,9 @@ class FunctionValves(BaseModel):
class FunctionsTable:
def insert_new_function(
self, user_id: str, type: str, form_data: FunctionForm
) -> Optional[FunctionModel]:
function = FunctionModel(
**{
**form_data.model_dump(),
@@ -119,7 +111,6 @@ class FunctionsTable:
def get_function_by_id(self, id: str) -> Optional[FunctionModel]:
try:
with get_db() as db:
function = db.get(Function, id)
return FunctionModel.model_validate(function)
except Exception:
@@ -127,7 +118,6 @@ class FunctionsTable:
def get_functions(self, active_only=False) -> list[FunctionModel]:
with get_db() as db:
if active_only:
return [
FunctionModel.model_validate(function)
@@ -143,7 +133,6 @@ class FunctionsTable:
self, type: str, active_only=False
) -> list[FunctionModel]:
with get_db() as db:
if active_only:
return [
FunctionModel.model_validate(function)
@@ -159,7 +148,6 @@ class FunctionsTable:
def get_global_filter_functions(self) -> list[FunctionModel]:
with get_db() as db:
return [
FunctionModel.model_validate(function)
for function in db.query(Function)
@@ -178,7 +166,6 @@ class FunctionsTable:
def get_function_valves_by_id(self, id: str) -> Optional[dict]:
with get_db() as db:
try:
function = db.get(Function, id)
return function.valves if function.valves else {}
@@ -190,7 +177,6 @@ class FunctionsTable:
self, id: str, valves: dict
) -> Optional[FunctionValves]:
with get_db() as db:
try:
function = db.get(Function, id)
function.valves = valves
@@ -204,7 +190,6 @@ class FunctionsTable:
def get_user_valves_by_id_and_user_id(
self, id: str, user_id: str
) -> Optional[dict]:
try:
user = Users.get_user_by_id(user_id)
user_settings = user.settings.model_dump() if user.settings else {}
@@ -223,7 +208,6 @@ class FunctionsTable:
def update_user_valves_by_id_and_user_id(
self, id: str, user_id: str, valves: dict
) -> Optional[dict]:
try:
user = Users.get_user_by_id(user_id)
user_settings = user.settings.model_dump() if user.settings else {}
@@ -246,7 +230,6 @@ class FunctionsTable:
def update_function_by_id(self, id: str, updated: dict) -> Optional[FunctionModel]:
with get_db() as db:
try:
db.query(Function).filter_by(id=id).update(
{
@@ -261,7 +244,6 @@ class FunctionsTable:
def deactivate_all_functions(self) -> Optional[bool]:
with get_db() as db:
try:
db.query(Function).update(
{
@@ -0,0 +1,168 @@
import json
import logging
import time
from typing import Optional
import uuid
from open_webui.apps.webui.internal.db import Base, get_db
from open_webui.env import SRC_LOG_LEVELS
from open_webui.apps.webui.models.files import FileMetadataResponse
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text, JSON
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
####################
# Knowledge DB Schema
####################
class Knowledge(Base):
__tablename__ = "knowledge"
id = Column(Text, unique=True, primary_key=True)
user_id = Column(Text)
name = Column(Text)
description = Column(Text)
data = Column(JSON, nullable=True)
meta = Column(JSON, nullable=True)
created_at = Column(BigInteger)
updated_at = Column(BigInteger)
class KnowledgeModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
user_id: str
name: str
description: str
data: Optional[dict] = None
meta: Optional[dict] = None
created_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
####################
# Forms
####################
class KnowledgeResponse(BaseModel):
id: str
name: str
description: str
data: Optional[dict] = None
meta: Optional[dict] = None
created_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
files: Optional[list[FileMetadataResponse | dict]] = None
class KnowledgeForm(BaseModel):
name: str
description: str
data: Optional[dict] = None
class KnowledgeUpdateForm(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
data: Optional[dict] = None
class KnowledgeTable:
def insert_new_knowledge(
self, user_id: str, form_data: KnowledgeForm
) -> Optional[KnowledgeModel]:
with get_db() as db:
knowledge = KnowledgeModel(
**{
**form_data.model_dump(),
"id": str(uuid.uuid4()),
"user_id": user_id,
"created_at": int(time.time()),
"updated_at": int(time.time()),
}
)
try:
result = Knowledge(**knowledge.model_dump())
db.add(result)
db.commit()
db.refresh(result)
if result:
return KnowledgeModel.model_validate(result)
else:
return None
except Exception:
return None
def get_knowledge_items(self) -> list[KnowledgeModel]:
with get_db() as db:
return [
KnowledgeModel.model_validate(knowledge)
for knowledge in db.query(Knowledge)
.order_by(Knowledge.updated_at.desc())
.all()
]
def get_knowledge_by_id(self, id: str) -> Optional[KnowledgeModel]:
try:
with get_db() as db:
knowledge = db.query(Knowledge).filter_by(id=id).first()
return KnowledgeModel.model_validate(knowledge) if knowledge else None
except Exception:
return None
def update_knowledge_by_id(
self, id: str, form_data: KnowledgeUpdateForm, overwrite: bool = False
) -> Optional[KnowledgeModel]:
try:
with get_db() as db:
knowledge = self.get_knowledge_by_id(id=id)
db.query(Knowledge).filter_by(id=id).update(
{
**form_data.model_dump(exclude_none=True),
"updated_at": int(time.time()),
}
)
db.commit()
return self.get_knowledge_by_id(id=id)
except Exception as e:
log.exception(e)
return None
def delete_knowledge_by_id(self, id: str) -> bool:
try:
with get_db() as db:
db.query(Knowledge).filter_by(id=id).delete()
db.commit()
return True
except Exception:
return False
def delete_all_knowledge(self) -> bool:
with get_db() as db:
try:
db.query(Knowledge).delete()
db.commit()
return True
except Exception:
return False
Knowledges = KnowledgeTable()
@@ -1,12 +1,10 @@
from pydantic import BaseModel, ConfigDict
from typing import Union, Optional
from sqlalchemy import Column, String, BigInteger, Text
from apps.webui.internal.db import Base, get_db
import time
import uuid
from typing import Optional
from open_webui.apps.webui.internal.db import Base, get_db
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
####################
# Memory DB Schema
@@ -39,13 +37,11 @@ class MemoryModel(BaseModel):
class MemoriesTable:
def insert_new_memory(
self,
user_id: str,
content: str,
) -> Optional[MemoryModel]:
with get_db() as db:
id = str(uuid.uuid4())
@@ -73,7 +69,6 @@ class MemoriesTable:
content: str,
) -> Optional[MemoryModel]:
with get_db() as db:
try:
db.query(Memory).filter_by(id=id).update(
{"content": content, "updated_at": int(time.time())}
@@ -85,7 +80,6 @@ class MemoriesTable:
def get_memories(self) -> list[MemoryModel]:
with get_db() as db:
try:
memories = db.query(Memory).all()
return [MemoryModel.model_validate(memory) for memory in memories]
@@ -94,7 +88,6 @@ class MemoriesTable:
def get_memories_by_user_id(self, user_id: str) -> list[MemoryModel]:
with get_db() as db:
try:
memories = db.query(Memory).filter_by(user_id=user_id).all()
return [MemoryModel.model_validate(memory) for memory in memories]
@@ -103,7 +96,6 @@ class MemoriesTable:
def get_memory_by_id(self, id: str) -> Optional[MemoryModel]:
with get_db() as db:
try:
memory = db.get(Memory, id)
return MemoryModel.model_validate(memory)
@@ -112,7 +104,6 @@ class MemoriesTable:
def delete_memory_by_id(self, id: str) -> bool:
with get_db() as db:
try:
db.query(Memory).filter_by(id=id).delete()
db.commit()
@@ -124,7 +115,6 @@ class MemoriesTable:
def delete_memories_by_user_id(self, user_id: str) -> bool:
with get_db() as db:
try:
db.query(Memory).filter_by(user_id=user_id).delete()
db.commit()
@@ -135,7 +125,6 @@ class MemoriesTable:
def delete_memory_by_id_and_user_id(self, id: str, user_id: str) -> bool:
with get_db() as db:
try:
db.query(Memory).filter_by(id=id, user_id=user_id).delete()
db.commit()
@@ -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 env 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"])
@@ -1,12 +1,9 @@
from pydantic import BaseModel, ConfigDict
from typing import Optional
import time
from typing import Optional
from sqlalchemy import String, Column, BigInteger, Text
from apps.webui.internal.db import Base, get_db
import json
from open_webui.apps.webui.internal.db import Base, get_db
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
####################
# Prompts DB Schema
@@ -45,7 +42,6 @@ class PromptForm(BaseModel):
class PromptsTable:
def insert_new_prompt(
self, user_id: str, form_data: PromptForm
) -> Optional[PromptModel]:
@@ -61,7 +57,6 @@ class PromptsTable:
try:
with get_db() as db:
result = Prompt(**prompt.dict())
db.add(result)
db.commit()
@@ -70,13 +65,12 @@ class PromptsTable:
return PromptModel.model_validate(result)
else:
return None
except Exception as e:
except Exception:
return None
def get_prompt_by_command(self, command: str) -> Optional[PromptModel]:
try:
with get_db() as db:
prompt = db.query(Prompt).filter_by(command=command).first()
return PromptModel.model_validate(prompt)
except Exception:
@@ -84,7 +78,6 @@ class PromptsTable:
def get_prompts(self) -> list[PromptModel]:
with get_db() as db:
return [
PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all()
]
@@ -94,7 +87,6 @@ class PromptsTable:
) -> Optional[PromptModel]:
try:
with get_db() as db:
prompt = db.query(Prompt).filter_by(command=command).first()
prompt.title = form_data.title
prompt.content = form_data.content
@@ -107,7 +99,6 @@ class PromptsTable:
def delete_prompt_by_command(self, command: str) -> bool:
try:
with get_db() as db:
db.query(Prompt).filter_by(command=command).delete()
db.commit()
@@ -0,0 +1,109 @@
import logging
import time
import uuid
from typing import Optional
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, JSON, PrimaryKeyConstraint
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
####################
# Tag DB Schema
####################
class Tag(Base):
__tablename__ = "tag"
id = Column(String)
name = Column(String)
user_id = Column(String)
meta = Column(JSON, nullable=True)
# Unique constraint ensuring (id, user_id) is unique, not just the `id` column
__table_args__ = (PrimaryKeyConstraint("id", "user_id", name="pk_id_user_id"),)
class TagModel(BaseModel):
id: str
name: str
user_id: str
meta: Optional[dict] = None
model_config = ConfigDict(from_attributes=True)
####################
# Forms
####################
class TagChatIdForm(BaseModel):
name: str
chat_id: str
class TagTable:
def insert_new_tag(self, name: str, user_id: str) -> Optional[TagModel]:
with get_db() as db:
id = name.replace(" ", "_").lower()
tag = TagModel(**{"id": id, "user_id": user_id, "name": name})
try:
result = Tag(**tag.model_dump())
db.add(result)
db.commit()
db.refresh(result)
if result:
return TagModel.model_validate(result)
else:
return None
except Exception as e:
print(e)
return None
def get_tag_by_name_and_user_id(
self, name: str, user_id: str
) -> Optional[TagModel]:
try:
id = name.replace(" ", "_").lower()
with get_db() as db:
tag = db.query(Tag).filter_by(id=id, user_id=user_id).first()
return TagModel.model_validate(tag)
except Exception:
return None
def get_tags_by_user_id(self, user_id: str) -> list[TagModel]:
with get_db() as db:
return [
TagModel.model_validate(tag)
for tag in (db.query(Tag).filter_by(user_id=user_id).all())
]
def get_tags_by_ids_and_user_id(
self, ids: list[str], user_id: str
) -> list[TagModel]:
with get_db() as db:
return [
TagModel.model_validate(tag)
for tag in (
db.query(Tag).filter(Tag.id.in_(ids), Tag.user_id == user_id).all()
)
]
def delete_tag_by_name_and_user_id(self, name: str, user_id: str) -> bool:
try:
with get_db() as db:
id = name.replace(" ", "_").lower()
res = db.query(Tag).filter_by(id=id, user_id=user_id).delete()
log.debug(f"res: {res}")
db.commit()
return True
except Exception as e:
log.error(f"delete_tag: {e}")
return False
Tags = TagTable()
@@ -1,17 +1,12 @@
from pydantic import BaseModel, ConfigDict
from typing import Optional
import time
import logging
from sqlalchemy import String, Column, BigInteger, Text
import time
from typing import Optional
from apps.webui.internal.db import Base, JSONField, get_db
from apps.webui.models.users import Users
import json
import copy
from env import SRC_LOG_LEVELS
from open_webui.apps.webui.internal.db import Base, JSONField, get_db
from open_webui.apps.webui.models.users import Users
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -79,13 +74,10 @@ class ToolValves(BaseModel):
class ToolsTable:
def insert_new_tool(
self, user_id: str, form_data: ToolForm, specs: list[dict]
) -> Optional[ToolModel]:
with get_db() as db:
tool = ToolModel(
**{
**form_data.model_dump(),
@@ -112,7 +104,6 @@ class ToolsTable:
def get_tool_by_id(self, id: str) -> Optional[ToolModel]:
try:
with get_db() as db:
tool = db.get(Tool, id)
return ToolModel.model_validate(tool)
except Exception:
@@ -125,7 +116,6 @@ class ToolsTable:
def get_tool_valves_by_id(self, id: str) -> Optional[dict]:
try:
with get_db() as db:
tool = db.get(Tool, id)
return tool.valves if tool.valves else {}
except Exception as e:
@@ -135,7 +125,6 @@ class ToolsTable:
def update_tool_valves_by_id(self, id: str, valves: dict) -> Optional[ToolValves]:
try:
with get_db() as db:
db.query(Tool).filter_by(id=id).update(
{"valves": valves, "updated_at": int(time.time())}
)
@@ -1,11 +1,10 @@
from pydantic import BaseModel, ConfigDict
from typing import Optional
import time
from typing import Optional
from sqlalchemy import String, Column, BigInteger, Text
from apps.webui.internal.db import Base, JSONField, get_db
from apps.webui.models.chats import Chats
from open_webui.apps.webui.internal.db import Base, JSONField, get_db
from open_webui.apps.webui.models.chats import Chats
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Column, String, Text
####################
# User DB Schema
@@ -113,7 +112,7 @@ class UsersTable:
with get_db() as db:
user = db.query(User).filter_by(id=id).first()
return UserModel.model_validate(user)
except Exception as e:
except Exception:
return None
def get_user_by_api_key(self, api_key: str) -> Optional[UserModel]:
@@ -221,7 +220,7 @@ class UsersTable:
user = db.query(User).filter_by(id=id).first()
return UserModel.model_validate(user)
# return UserModel(**user.dict())
except Exception as e:
except Exception:
return None
def delete_user_by_id(self, id: str) -> bool:
@@ -255,7 +254,7 @@ class UsersTable:
with get_db() as db:
user = db.query(User).filter_by(id=id).first()
return user.api_key
except Exception as e:
except Exception:
return None
@@ -1,43 +1,43 @@
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
import time
import datetime
from apps.webui.models.auths import (
SigninForm,
SignupForm,
from open_webui.apps.webui.models.auths import (
AddUserForm,
UpdateProfileForm,
UpdatePasswordForm,
UserResponse,
SigninResponse,
Auths,
ApiKey,
Auths,
Token,
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,
WEBUI_SESSION_COOKIE_SAME_SITE,
WEBUI_SESSION_COOKIE_SECURE,
)
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_verified_user,
get_current_user,
get_password_hash,
)
from open_webui.utils.webhook import post_webhook
from typing import Optional
router = APIRouter()
@@ -46,23 +46,44 @@ router = APIRouter()
############################
@router.get("/", response_model=UserResponse)
class SessionUserResponse(Token, UserResponse):
expires_at: Optional[int] = None
@router.get("/", response_model=SessionUserResponse)
async def get_session_user(
request: Request, response: Response, user=Depends(get_current_user)
):
expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
expires_at = None
if expires_delta:
expires_at = int(time.time()) + int(expires_delta.total_seconds())
token = create_token(
data={"id": user.id},
expires_delta=parse_duration(request.app.state.config.JWT_EXPIRES_IN),
expires_delta=expires_delta,
)
datetime_expires_at = (
datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc)
if expires_at
else None
)
# Set the cookie token
response.set_cookie(
key="token",
value=token,
expires=datetime_expires_at,
httponly=True, # Ensures the cookie is not accessible via JavaScript
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
secure=WEBUI_SESSION_COOKIE_SECURE,
)
return {
"token": token,
"token_type": "Bearer",
"expires_at": expires_at,
"id": user.id,
"email": user.email,
"name": user.name,
@@ -78,7 +99,7 @@ async def get_session_user(
@router.post("/update/profile", response_model=UserResponse)
async def update_profile(
form_data: UpdateProfileForm, session_user=Depends(get_current_user)
form_data: UpdateProfileForm, session_user=Depends(get_verified_user)
):
if session_user:
user = Users.update_user_by_id(
@@ -121,7 +142,7 @@ async def update_password(
############################
@router.post("/signin", response_model=SigninResponse)
@router.post("/signin", response_model=SessionUserResponse)
async def signin(request: Request, response: Response, form_data: SigninForm):
if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
if WEBUI_AUTH_TRUSTED_EMAIL_HEADER not in request.headers:
@@ -163,21 +184,37 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
user = Auths.authenticate_user(form_data.email.lower(), form_data.password)
if user:
expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
expires_at = None
if expires_delta:
expires_at = int(time.time()) + int(expires_delta.total_seconds())
token = create_token(
data={"id": user.id},
expires_delta=parse_duration(request.app.state.config.JWT_EXPIRES_IN),
expires_delta=expires_delta,
)
datetime_expires_at = (
datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc)
if expires_at
else None
)
# Set the cookie token
response.set_cookie(
key="token",
value=token,
expires=datetime_expires_at,
httponly=True, # Ensures the cookie is not accessible via JavaScript
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
secure=WEBUI_SESSION_COOKIE_SECURE,
)
return {
"token": token,
"token_type": "Bearer",
"expires_at": expires_at,
"id": user.id,
"email": user.email,
"name": user.name,
@@ -193,16 +230,21 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
############################
@router.post("/signup", response_model=SigninResponse)
@router.post("/signup", response_model=SessionUserResponse)
async def signup(request: Request, response: Response, form_data: SignupForm):
if (
not request.app.state.config.ENABLE_SIGNUP
and request.app.state.config.ENABLE_LOGIN_FORM
and WEBUI_AUTH
):
raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
)
if WEBUI_AUTH:
if (
not request.app.state.config.ENABLE_SIGNUP
or not request.app.state.config.ENABLE_LOGIN_FORM
):
raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
)
else:
if Users.get_num_users() != 0:
raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
)
if not validate_email_format(form_data.email.lower()):
raise HTTPException(
@@ -228,16 +270,30 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
)
if user:
expires_delta = parse_duration(request.app.state.config.JWT_EXPIRES_IN)
expires_at = None
if expires_delta:
expires_at = int(time.time()) + int(expires_delta.total_seconds())
token = create_token(
data={"id": user.id},
expires_delta=parse_duration(request.app.state.config.JWT_EXPIRES_IN),
expires_delta=expires_delta,
)
datetime_expires_at = (
datetime.datetime.fromtimestamp(expires_at, datetime.timezone.utc)
if expires_at
else None
)
# Set the cookie token
response.set_cookie(
key="token",
value=token,
expires=datetime_expires_at,
httponly=True, # Ensures the cookie is not accessible via JavaScript
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
secure=WEBUI_SESSION_COOKIE_SECURE,
)
if request.app.state.config.WEBHOOK_URL:
@@ -254,6 +310,7 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
return {
"token": token,
"token_type": "Bearer",
"expires_at": expires_at,
"id": user.id,
"email": user.email,
"name": user.name,
@@ -266,6 +323,12 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
@router.get("/signout")
async def signout(response: Response):
response.delete_cookie("token")
return {"status": True}
############################
# AddUser
############################
@@ -273,7 +336,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
@@ -283,7 +345,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(
@@ -1,34 +1,23 @@
from fastapi import Depends, Request, HTTPException, status
from datetime import datetime, timedelta
from typing import Union, Optional
from utils.utils import get_verified_user, get_admin_user
from fastapi import APIRouter
from pydantic import BaseModel
import json
import logging
from typing import Optional
from apps.webui.models.users import Users
from apps.webui.models.chats import (
ChatModel,
ChatResponse,
ChatTitleForm,
from open_webui.apps.webui.models.chats import (
ChatForm,
ChatTitleIdResponse,
ChatImportForm,
ChatResponse,
Chats,
ChatTitleIdResponse,
)
from open_webui.apps.webui.models.tags import TagModel, Tags
from open_webui.apps.webui.models.folders import Folders
from apps.webui.models.tags import (
TagModel,
ChatIdTagModel,
ChatIdTagForm,
ChatTagsResponse,
Tags,
)
from constants import ERROR_MESSAGES
from config import SRC_LOG_LEVELS, ENABLE_ADMIN_EXPORT, ENABLE_ADMIN_CHAT_ACCESS
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import SRC_LOG_LEVELS
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
from open_webui.utils.utils import get_admin_user, get_verified_user
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@@ -61,11 +50,9 @@ 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"]
):
if user.role == "user" and not request.app.state.config.USER_PERMISSIONS.get(
"chat", {}
).get("deletion", {}):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -106,7 +93,35 @@ async def get_user_chat_list_by_user_id(
async def create_new_chat(form_data: ChatForm, user=Depends(get_verified_user)):
try:
chat = Chats.insert_new_chat(user.id, form_data)
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
return ChatResponse(**chat.model_dump())
except Exception as e:
log.exception(e)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
)
############################
# ImportChat
############################
@router.post("/import", response_model=Optional[ChatResponse])
async def import_chat(form_data: ChatImportForm, user=Depends(get_verified_user)):
try:
chat = Chats.import_chat(user.id, form_data)
if chat:
tags = chat.meta.get("tags", [])
for tag_id in tags:
tag_id = tag_id.replace(" ", "_").lower()
tag_name = " ".join([word.capitalize() for word in tag_id.split("_")])
if (
tag_id != "none"
and Tags.get_tag_by_name_and_user_id(tag_name, user.id) is None
):
Tags.insert_new_tag(tag_name, user.id)
return ChatResponse(**chat.model_dump())
except Exception as e:
log.exception(e)
raise HTTPException(
@@ -119,10 +134,77 @@ async def create_new_chat(form_data: ChatForm, user=Depends(get_verified_user)):
############################
@router.get("/search", response_model=list[ChatTitleIdResponse])
async def search_user_chats(
text: str, page: Optional[int] = None, user=Depends(get_verified_user)
):
if page is None:
page = 1
limit = 60
skip = (page - 1) * limit
chat_list = [
ChatTitleIdResponse(**chat.model_dump())
for chat in Chats.get_chats_by_user_id_and_search_text(
user.id, text, skip=skip, limit=limit
)
]
# Delete tag if no chat is found
words = text.strip().split(" ")
if page == 1 and len(words) == 1 and words[0].startswith("tag:"):
tag_id = words[0].replace("tag:", "")
if len(chat_list) == 0:
if Tags.get_tag_by_name_and_user_id(tag_id, user.id):
log.debug(f"deleting tag: {tag_id}")
Tags.delete_tag_by_name_and_user_id(tag_id, user.id)
return chat_list
############################
# GetChatsByFolderId
############################
@router.get("/folder/{folder_id}", response_model=list[ChatResponse])
async def get_chats_by_folder_id(folder_id: str, user=Depends(get_verified_user)):
folder_ids = [folder_id]
children_folders = Folders.get_children_folders_by_id_and_user_id(
folder_id, user.id
)
if children_folders:
folder_ids.extend([folder.id for folder in children_folders])
return [
ChatResponse(**chat.model_dump())
for chat in Chats.get_chats_by_folder_ids_and_user_id(folder_ids, user.id)
]
############################
# GetPinnedChats
############################
@router.get("/pinned", response_model=list[ChatResponse])
async def get_user_pinned_chats(user=Depends(get_verified_user)):
return [
ChatResponse(**chat.model_dump())
for chat in Chats.get_pinned_chats_by_user_id(user.id)
]
############################
# GetChats
############################
@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)})
ChatResponse(**chat.model_dump())
for chat in Chats.get_chats_by_user_id(user.id)
]
@@ -135,11 +217,28 @@ async def get_user_chats(user=Depends(get_verified_user)):
@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)})
ChatResponse(**chat.model_dump())
for chat in Chats.get_archived_chats_by_user_id(user.id)
]
############################
# GetAllTags
############################
@router.get("/all/tags", response_model=list[TagModel])
async def get_all_user_tags(user=Depends(get_verified_user)):
try:
tags = Tags.get_tags_by_user_id(user.id)
return tags
except Exception as e:
log.exception(e)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
)
############################
# GetAllChatsInDB
############################
@@ -152,10 +251,7 @@ async def get_all_user_chats_in_db(user=Depends(get_admin_user)):
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
return [
ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
for chat in Chats.get_chats()
]
return [ChatResponse(**chat.model_dump()) for chat in Chats.get_chats()]
############################
@@ -198,7 +294,8 @@ async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user)):
chat = Chats.get_chat_by_id(share_id)
if chat:
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
@@ -210,49 +307,28 @@ async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user)):
############################
class TagNameForm(BaseModel):
class TagForm(BaseModel):
name: str
class TagFilterForm(TagForm):
skip: Optional[int] = 0
limit: Optional[int] = 50
@router.post("/tags", response_model=list[ChatTitleIdResponse])
async def get_user_chat_list_by_tag_name(
form_data: TagNameForm, user=Depends(get_verified_user)
form_data: TagFilterForm, 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(
form_data.name, user.id
)
]
chats = Chats.get_chat_list_by_chat_ids(chat_ids, form_data.skip, form_data.limit)
chats = Chats.get_chat_list_by_user_id_and_tag_name(
user.id, form_data.name, form_data.skip, form_data.limit
)
if len(chats) == 0:
Tags.delete_tag_by_tag_name_and_user_id(form_data.name, user.id)
Tags.delete_tag_by_name_and_user_id(form_data.name, user.id)
return chats
############################
# GetAllTags
############################
@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)
return tags
except Exception as e:
log.exception(e)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
)
############################
# GetChatById
############################
@@ -263,7 +339,8 @@ async def get_chat_by_id(id: str, user=Depends(get_verified_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
@@ -281,10 +358,9 @@ async def update_chat_by_id(
):
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
updated_chat = {**json.loads(chat.chat), **form_data.chat}
updated_chat = {**chat.chat, **form_data.chat}
chat = Chats.update_chat_by_id(id, updated_chat)
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -299,41 +375,84 @@ 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":
chat = Chats.get_chat_by_id(id)
for tag in chat.meta.get("tags", []):
if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 1:
Tags.delete_tag_by_name_and_user_id(tag, user.id)
result = Chats.delete_chat_by_id(id)
return result
else:
if not request.app.state.config.USER_PERMISSIONS["chat"]["deletion"]:
if not request.app.state.config.USER_PERMISSIONS.get("chat", {}).get(
"deletion", {}
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
chat = Chats.get_chat_by_id(id)
for tag in chat.meta.get("tags", []):
if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 1:
Tags.delete_tag_by_name_and_user_id(tag, user.id)
result = Chats.delete_chat_by_id_and_user_id(id, user.id)
return result
############################
# GetPinnedStatusById
############################
@router.get("/{id}/pinned", response_model=Optional[bool])
async def get_pinned_status_by_id(id: str, user=Depends(get_verified_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
return chat.pinned
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
)
############################
# PinChatById
############################
@router.post("/{id}/pin", response_model=Optional[ChatResponse])
async def pin_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 = Chats.toggle_chat_pinned_by_id(id)
return chat
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
)
############################
# CloneChat
############################
@router.get("/{id}/clone", response_model=Optional[ChatResponse])
@router.post("/{id}/clone", response_model=Optional[ChatResponse])
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,
**chat.chat,
"originalChatId": chat.id,
"branchPointMessageId": chat_body["history"]["currentId"],
"branchPointMessageId": chat.chat["history"]["currentId"],
"title": f"Clone of {chat.title}",
}
chat = Chats.insert_new_chat(user.id, ChatForm(**{"chat": updated_chat}))
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
@@ -345,12 +464,26 @@ async def clone_chat_by_id(id: str, user=Depends(get_verified_user)):
############################
@router.get("/{id}/archive", response_model=Optional[ChatResponse])
@router.post("/{id}/archive", response_model=Optional[ChatResponse])
async def archive_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 = Chats.toggle_chat_archive_by_id(id)
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
# Delete tags if chat is archived
if chat.archived:
for tag_id in chat.meta.get("tags", []):
if Chats.count_chats_by_tag_name_and_user_id(tag_id, user.id) == 0:
log.debug(f"deleting tag: {tag_id}")
Tags.delete_tag_by_name_and_user_id(tag_id, user.id)
else:
for tag_id in chat.meta.get("tags", []):
tag = Tags.get_tag_by_name_and_user_id(tag_id, user.id)
if tag is None:
log.debug(f"inserting tag: {tag_id}")
tag = Tags.insert_new_tag(tag_id, user.id)
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
@@ -368,9 +501,7 @@ async def share_chat_by_id(id: str, user=Depends(get_verified_user)):
if chat:
if chat.share_id:
shared_chat = Chats.update_shared_chat_by_chat_id(chat.id)
return ChatResponse(
**{**shared_chat.model_dump(), "chat": json.loads(shared_chat.chat)}
)
return ChatResponse(**shared_chat.model_dump())
shared_chat = Chats.insert_shared_chat_by_chat_id(chat.id)
if not shared_chat:
@@ -378,10 +509,8 @@ async def share_chat_by_id(id: str, user=Depends(get_verified_user)):
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=ERROR_MESSAGES.DEFAULT(),
)
return ChatResponse(**shared_chat.model_dump())
return ChatResponse(
**{**shared_chat.model_dump(), "chat": json.loads(shared_chat.chat)}
)
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -412,6 +541,31 @@ async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user)):
)
############################
# UpdateChatFolderIdById
############################
class ChatFolderIdForm(BaseModel):
folder_id: Optional[str] = None
@router.post("/{id}/folder", response_model=Optional[ChatResponse])
async def update_chat_folder_id_by_id(
id: str, form_data: ChatFolderIdForm, user=Depends(get_verified_user)
):
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
chat = Chats.update_chat_folder_id_by_id_and_user_id(
id, user.id, form_data.folder_id
)
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
)
############################
# GetChatTagsById
############################
@@ -419,10 +573,10 @@ async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user)):
@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)
if tags != None:
return tags
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
tags = chat.meta.get("tags", [])
return Tags.get_tags_by_ids_and_user_id(tags, user.id)
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
@@ -434,22 +588,30 @@ async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user)):
############################
@router.post("/{id}/tags", response_model=Optional[ChatIdTagModel])
async def add_chat_tag_by_id(
id: str, form_data: ChatIdTagForm, user=Depends(get_verified_user)
@router.post("/{id}/tags", response_model=list[TagModel])
async def add_tag_by_id_and_tag_name(
id: str, form_data: TagForm, user=Depends(get_verified_user)
):
tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id)
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
tags = chat.meta.get("tags", [])
tag_id = form_data.name.replace(" ", "_").lower()
if form_data.tag_name not in tags:
tag = Tags.add_tag_to_chat(user.id, form_data)
if tag:
return tag
else:
if tag_id == "none":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.NOT_FOUND,
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT("Tag name cannot be 'None'"),
)
print(tags, tag_id)
if tag_id not in tags:
Chats.add_chat_tag_by_id_and_user_id_and_tag_name(
id, user.id, form_data.name
)
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
tags = chat.meta.get("tags", [])
return Tags.get_tags_by_ids_and_user_id(tags, user.id)
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
@@ -461,16 +623,20 @@ async def add_chat_tag_by_id(
############################
@router.delete("/{id}/tags", response_model=Optional[bool])
async def delete_chat_tag_by_id(
id: str, form_data: ChatIdTagForm, user=Depends(get_verified_user)
@router.delete("/{id}/tags", response_model=list[TagModel])
async def delete_tag_by_id_and_tag_name(
id: str, form_data: TagForm, user=Depends(get_verified_user)
):
result = Tags.delete_tag_by_tag_name_and_chat_id_and_user_id(
form_data.tag_name, id, user.id
)
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
Chats.delete_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name)
if result:
return result
if Chats.count_chats_by_tag_name_and_user_id(form_data.name, user.id) == 0:
Tags.delete_tag_by_name_and_user_id(form_data.name, user.id)
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
tags = chat.meta.get("tags", [])
return Tags.get_tags_by_ids_and_user_id(tags, user.id)
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
@@ -478,16 +644,21 @@ async def delete_chat_tag_by_id(
############################
# DeleteAllChatTagsById
# DeleteAllTagsById
############################
@router.delete("/{id}/tags/all", response_model=Optional[bool])
async def delete_all_chat_tags_by_id(id: str, user=Depends(get_verified_user)):
result = Tags.delete_tags_by_chat_id_and_user_id(id, user.id)
async def delete_all_tags_by_id(id: str, user=Depends(get_verified_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
Chats.delete_all_tags_by_id_and_user_id(id, user.id)
if result:
return result
for tag in chat.meta.get("tags", []):
if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 0:
Tags.delete_tag_by_name_and_user_id(tag, user.id)
return True
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
@@ -1,29 +1,39 @@
from fastapi import Response, Request
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import Union
from fastapi import APIRouter
from open_webui.config import BannerModel
from fastapi import APIRouter, Depends, Request
from pydantic import BaseModel
import time
import uuid
from open_webui.utils.utils import get_admin_user, get_verified_user
from config import BannerModel
from apps.webui.models.users import Users
from utils.utils import (
get_password_hash,
get_verified_user,
get_admin_user,
create_token,
)
from utils.misc import get_gravatar_url, validate_email_format
from constants import ERROR_MESSAGES
from open_webui.config import get_config, save_config
router = APIRouter()
############################
# ImportConfig
############################
class ImportConfigForm(BaseModel):
config: dict
@router.post("/import", response_model=dict)
async def import_config(form_data: ImportConfigForm, user=Depends(get_admin_user)):
save_config(form_data.config)
return get_config()
############################
# ExportConfig
############################
@router.get("/export", response_model=dict)
async def export_config(user=Depends(get_admin_user)):
return get_config()
class SetDefaultModelsForm(BaseModel):
models: str
@@ -1,21 +1,16 @@
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import Union, Optional
from fastapi import APIRouter
from pydantic import BaseModel
import json
from typing import Optional
from apps.webui.models.documents import (
Documents,
from open_webui.apps.webui.models.documents import (
DocumentForm,
DocumentUpdateForm,
DocumentModel,
DocumentResponse,
Documents,
DocumentUpdateForm,
)
from utils.utils import get_verified_user, get_admin_user
from constants import ERROR_MESSAGES
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from open_webui.utils.utils import get_admin_user, get_verified_user
router = APIRouter()
@@ -0,0 +1,147 @@
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Request
from pydantic import BaseModel
from open_webui.apps.webui.models.users import Users, UserModel
from open_webui.apps.webui.models.feedbacks import (
FeedbackModel,
FeedbackForm,
Feedbacks,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.utils.utils import get_admin_user, get_verified_user
router = APIRouter()
############################
# GetConfig
############################
@router.get("/config")
async def get_config(request: Request, user=Depends(get_admin_user)):
return {
"ENABLE_EVALUATION_ARENA_MODELS": request.app.state.config.ENABLE_EVALUATION_ARENA_MODELS,
"EVALUATION_ARENA_MODELS": request.app.state.config.EVALUATION_ARENA_MODELS,
}
############################
# UpdateConfig
############################
class UpdateConfigForm(BaseModel):
ENABLE_EVALUATION_ARENA_MODELS: Optional[bool] = None
EVALUATION_ARENA_MODELS: Optional[list[dict]] = None
@router.post("/config")
async def update_config(
request: Request,
form_data: UpdateConfigForm,
user=Depends(get_admin_user),
):
config = request.app.state.config
if form_data.ENABLE_EVALUATION_ARENA_MODELS is not None:
config.ENABLE_EVALUATION_ARENA_MODELS = form_data.ENABLE_EVALUATION_ARENA_MODELS
if form_data.EVALUATION_ARENA_MODELS is not None:
config.EVALUATION_ARENA_MODELS = form_data.EVALUATION_ARENA_MODELS
return {
"ENABLE_EVALUATION_ARENA_MODELS": config.ENABLE_EVALUATION_ARENA_MODELS,
"EVALUATION_ARENA_MODELS": config.EVALUATION_ARENA_MODELS,
}
@router.get("/feedbacks", response_model=list[FeedbackModel])
async def get_feedbacks(user=Depends(get_verified_user)):
feedbacks = Feedbacks.get_feedbacks_by_user_id(user.id)
return feedbacks
@router.delete("/feedbacks", response_model=bool)
async def delete_feedbacks(user=Depends(get_verified_user)):
success = Feedbacks.delete_feedbacks_by_user_id(user.id)
return success
class FeedbackUserModel(FeedbackModel):
user: Optional[UserModel] = None
@router.get("/feedbacks/all", response_model=list[FeedbackUserModel])
async def get_all_feedbacks(user=Depends(get_admin_user)):
feedbacks = Feedbacks.get_all_feedbacks()
return [
FeedbackUserModel(
**feedback.model_dump(), user=Users.get_user_by_id(feedback.user_id)
)
for feedback in feedbacks
]
@router.delete("/feedbacks/all")
async def delete_all_feedbacks(user=Depends(get_admin_user)):
success = Feedbacks.delete_all_feedbacks()
return success
@router.post("/feedback", response_model=FeedbackModel)
async def create_feedback(
request: Request,
form_data: FeedbackForm,
user=Depends(get_verified_user),
):
feedback = Feedbacks.insert_new_feedback(user_id=user.id, form_data=form_data)
if not feedback:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT(),
)
return feedback
@router.get("/feedback/{id}", response_model=FeedbackModel)
async def get_feedback_by_id(id: str, user=Depends(get_verified_user)):
feedback = Feedbacks.get_feedback_by_id_and_user_id(id=id, user_id=user.id)
if not feedback:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
)
return feedback
@router.post("/feedback/{id}", response_model=FeedbackModel)
async def update_feedback_by_id(
id: str, form_data: FeedbackForm, user=Depends(get_verified_user)
):
feedback = Feedbacks.update_feedback_by_id_and_user_id(
id=id, user_id=user.id, form_data=form_data
)
if not feedback:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
)
return feedback
@router.delete("/feedback/{id}")
async def delete_feedback_by_id(id: str, user=Depends(get_verified_user)):
if user.role == "admin":
success = Feedbacks.delete_feedback_by_id(id=id)
else:
success = Feedbacks.delete_feedback_by_id_and_user_id(id=id, user_id=user.id)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
)
return success

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