mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-14 17:56:00 +08:00
Merge branch 'main' into feat/r2
This commit is contained in:
commit
360f8a3375
@ -34,4 +34,4 @@ if you see such error message when you open this project in codespaces:
|
|||||||

|

|
||||||
|
|
||||||
a simple workaround is change `/signin` endpoint into another one, then login with GitHub account and close the tab, then change it back to `/signin` endpoint. Then all things will be fine.
|
a simple workaround is change `/signin` endpoint into another one, then login with GitHub account and close the tab, then change it back to `/signin` endpoint. Then all things will be fine.
|
||||||
The reason is `signin` endpoint is not allowed in codespaces, details can be found [here](https://github.com/orgs/community/discussions/5204)
|
The reason is `signin` endpoint is not allowed in codespaces, details can be found [here](https://github.com/orgs/community/discussions/5204)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// README at: https://github.com/devcontainers/templates/tree/main/src/anaconda
|
// README at: https://github.com/devcontainers/templates/tree/main/src/anaconda
|
||||||
{
|
{
|
||||||
"name": "Python 3.12",
|
"name": "Python 3.12",
|
||||||
"build": {
|
"build": {
|
||||||
"context": "..",
|
"context": "..",
|
||||||
"dockerfile": "Dockerfile"
|
"dockerfile": "Dockerfile"
|
||||||
},
|
},
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
This file copied into the container along with environment.yml* from the parent
|
This file copied into the container along with environment.yml* from the parent
|
||||||
folder. This file is included to prevents the Dockerfile COPY instruction from
|
folder. This file is included to prevents the Dockerfile COPY instruction from
|
||||||
failing if no environment.yml is found.
|
failing if no environment.yml is found.
|
||||||
|
@ -5,18 +5,35 @@ root = true
|
|||||||
|
|
||||||
# Unix-style newlines with a newline ending every file
|
# Unix-style newlines with a newline ending every file
|
||||||
[*]
|
[*]
|
||||||
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.py]
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.toml]
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
# Markdown and MDX are whitespace sensitive languages.
|
||||||
|
# Do not remove trailing spaces.
|
||||||
|
[*.{md,mdx}]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
# Matches multiple files with brace expansion notation
|
# Matches multiple files with brace expansion notation
|
||||||
# Set default charset
|
# Set default charset
|
||||||
[*.{js,tsx}]
|
[*.{js,tsx}]
|
||||||
charset = utf-8
|
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
# Matches the exact files package.json
|
||||||
# Matches the exact files either package.json or .travis.yml
|
[package.json]
|
||||||
[{package.json,.travis.yml}]
|
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,5 +1,5 @@
|
|||||||
# Ensure that .sh scripts use LF as line separator, even if they are checked out
|
# Ensure that .sh scripts use LF as line separator, even if they are checked out
|
||||||
# to Windows(NTFS) file-system, by a user of Docker for Windows.
|
# to Windows(NTFS) file-system, by a user of Docker for Windows.
|
||||||
# These .sh scripts will be run from the Container after `docker compose up -d`.
|
# These .sh scripts will be run from the Container after `docker compose up -d`.
|
||||||
# If they appear to be CRLF style, Dash from the Container will fail to execute
|
# If they appear to be CRLF style, Dash from the Container will fail to execute
|
||||||
# them.
|
# them.
|
||||||
|
22
.github/linters/editorconfig-checker.json
vendored
Normal file
22
.github/linters/editorconfig-checker.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"Verbose": false,
|
||||||
|
"Debug": false,
|
||||||
|
"IgnoreDefaults": false,
|
||||||
|
"SpacesAfterTabs": false,
|
||||||
|
"NoColor": false,
|
||||||
|
"Exclude": [
|
||||||
|
"^web/public/vs/",
|
||||||
|
"^web/public/pdf.worker.min.mjs$",
|
||||||
|
"web/app/components/base/icons/src/vender/"
|
||||||
|
],
|
||||||
|
"AllowedContentTypes": [],
|
||||||
|
"PassedFiles": [],
|
||||||
|
"Disable": {
|
||||||
|
"EndOfLine": false,
|
||||||
|
"Indentation": false,
|
||||||
|
"IndentSize": true,
|
||||||
|
"InsertFinalNewline": false,
|
||||||
|
"TrimTrailingWhitespace": false,
|
||||||
|
"MaxLineLength": false
|
||||||
|
}
|
||||||
|
}
|
3
.github/workflows/api-tests.yml
vendored
3
.github/workflows/api-tests.yml
vendored
@ -88,3 +88,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Run Workflow
|
- name: Run Workflow
|
||||||
run: uv run --project api bash dev/pytest/pytest_workflow.sh
|
run: uv run --project api bash dev/pytest/pytest_workflow.sh
|
||||||
|
|
||||||
|
- name: Run Tool
|
||||||
|
run: uv run --project api bash dev/pytest/pytest_tools.sh
|
||||||
|
21
.github/workflows/style.yml
vendored
21
.github/workflows/style.yml
vendored
@ -9,6 +9,12 @@ concurrency:
|
|||||||
group: style-${{ github.head_ref || github.run_id }}
|
group: style-${{ github.head_ref || github.run_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
statuses: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
python-style:
|
python-style:
|
||||||
name: Python Style
|
name: Python Style
|
||||||
@ -43,8 +49,8 @@ jobs:
|
|||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
uv run --directory api ruff --version
|
uv run --directory api ruff --version
|
||||||
uv run --directory api ruff check ./
|
uv run --directory api ruff check --diff ./
|
||||||
uv run --directory api ruff format --check ./
|
uv run --directory api ruff format --check --diff ./
|
||||||
|
|
||||||
- name: Dotenv check
|
- name: Dotenv check
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
@ -163,3 +169,14 @@ jobs:
|
|||||||
VALIDATE_DOCKERFILE_HADOLINT: true
|
VALIDATE_DOCKERFILE_HADOLINT: true
|
||||||
VALIDATE_XML: true
|
VALIDATE_XML: true
|
||||||
VALIDATE_YAML: true
|
VALIDATE_YAML: true
|
||||||
|
|
||||||
|
- name: EditorConfig checks
|
||||||
|
uses: super-linter/super-linter/slim@v7
|
||||||
|
env:
|
||||||
|
DEFAULT_BRANCH: main
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
IGNORE_GENERATED_FILES: true
|
||||||
|
IGNORE_GITIGNORED_FILES: true
|
||||||
|
# EditorConfig validation
|
||||||
|
VALIDATE_EDITORCONFIG: true
|
||||||
|
EDITORCONFIG_FILE_NAME: editorconfig-checker.json
|
||||||
|
@ -90,4 +90,4 @@ Recomendamos revisar este documento cuidadosamente antes de proceder con la conf
|
|||||||
No dudes en contactarnos si encuentras algún problema durante el proceso de configuración.
|
No dudes en contactarnos si encuentras algún problema durante el proceso de configuración.
|
||||||
## Obteniendo Ayuda
|
## Obteniendo Ayuda
|
||||||
|
|
||||||
Si alguna vez te quedas atascado o tienes una pregunta urgente mientras contribuyes, simplemente envíanos tus consultas a través del issue relacionado de GitHub, o únete a nuestro [Discord](https://discord.gg/8Tpq4AcN9c) para una charla rápida.
|
Si alguna vez te quedas atascado o tienes una pregunta urgente mientras contribuyes, simplemente envíanos tus consultas a través del issue relacionado de GitHub, o únete a nuestro [Discord](https://discord.gg/8Tpq4AcN9c) para una charla rápida.
|
||||||
|
@ -90,4 +90,4 @@ Nous recommandons de revoir attentivement ce document avant de procéder à la c
|
|||||||
N'hésitez pas à nous contacter si vous rencontrez des problèmes pendant le processus de configuration.
|
N'hésitez pas à nous contacter si vous rencontrez des problèmes pendant le processus de configuration.
|
||||||
## Obtenir de l'aide
|
## Obtenir de l'aide
|
||||||
|
|
||||||
Si jamais vous êtes bloqué ou avez une question urgente en contribuant, envoyez-nous simplement vos questions via le problème GitHub concerné, ou rejoignez notre [Discord](https://discord.gg/8Tpq4AcN9c) pour une discussion rapide.
|
Si jamais vous êtes bloqué ou avez une question urgente en contribuant, envoyez-nous simplement vos questions via le problème GitHub concerné, ou rejoignez notre [Discord](https://discord.gg/8Tpq4AcN9c) pour une discussion rapide.
|
||||||
|
@ -90,4 +90,4 @@ PR 설명에 기존 이슈를 연결하거나 새 이슈를 여는 것을 잊지
|
|||||||
설정 과정에서 문제가 발생하면 언제든지 연락해 주세요.
|
설정 과정에서 문제가 발생하면 언제든지 연락해 주세요.
|
||||||
## 도움 받기
|
## 도움 받기
|
||||||
|
|
||||||
기여하는 동안 막히거나 긴급한 질문이 있으면, 관련 GitHub 이슈를 통해 질문을 보내거나, 빠른 대화를 위해 우리의 [Discord](https://discord.gg/8Tpq4AcN9c)에 참여하세요.
|
기여하는 동안 막히거나 긴급한 질문이 있으면, 관련 GitHub 이슈를 통해 질문을 보내거나, 빠른 대화를 위해 우리의 [Discord](https://discord.gg/8Tpq4AcN9c)에 참여하세요.
|
||||||
|
@ -90,4 +90,4 @@ Recomendamos revisar este documento cuidadosamente antes de prosseguir com a con
|
|||||||
Sinta-se à vontade para entrar em contato se encontrar quaisquer problemas durante o processo de configuração.
|
Sinta-se à vontade para entrar em contato se encontrar quaisquer problemas durante o processo de configuração.
|
||||||
## Obtendo Ajuda
|
## Obtendo Ajuda
|
||||||
|
|
||||||
Se você ficar preso ou tiver uma dúvida urgente enquanto contribui, simplesmente envie suas perguntas através do problema relacionado no GitHub, ou entre no nosso [Discord](https://discord.gg/8Tpq4AcN9c) para uma conversa rápida.
|
Se você ficar preso ou tiver uma dúvida urgente enquanto contribui, simplesmente envie suas perguntas através do problema relacionado no GitHub, ou entre no nosso [Discord](https://discord.gg/8Tpq4AcN9c) para uma conversa rápida.
|
||||||
|
@ -90,4 +90,4 @@ Kuruluma geçmeden önce bu belgeyi dikkatlice incelemenizi öneririz, çünkü
|
|||||||
Kurulum süreci sırasında herhangi bir sorunla karşılaşırsanız bizimle iletişime geçmekten çekinmeyin.
|
Kurulum süreci sırasında herhangi bir sorunla karşılaşırsanız bizimle iletişime geçmekten çekinmeyin.
|
||||||
## Yardım Almak
|
## Yardım Almak
|
||||||
|
|
||||||
Katkıda bulunurken takılırsanız veya yanıcı bir sorunuz olursa, sorularınızı ilgili GitHub sorunu aracılığıyla bize gönderin veya hızlı bir sohbet için [Discord'umuza](https://discord.gg/8Tpq4AcN9c) katılın.
|
Katkıda bulunurken takılırsanız veya yanıcı bir sorunuz olursa, sorularınızı ilgili GitHub sorunu aracılığıyla bize gönderin veya hızlı bir sohbet için [Discord'umuza](https://discord.gg/8Tpq4AcN9c) katılın.
|
||||||
|
518
README_SI.md
518
README_SI.md
@ -1,259 +1,259 @@
|
|||||||

|

|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
📌 <a href="https://dify.ai/blog/introducing-dify-workflow-file-upload-a-demo-on-ai-podcast">Predstavljamo nalaganje datotek Dify Workflow: znova ustvarite Google NotebookLM Podcast</a>
|
📌 <a href="https://dify.ai/blog/introducing-dify-workflow-file-upload-a-demo-on-ai-podcast">Predstavljamo nalaganje datotek Dify Workflow: znova ustvarite Google NotebookLM Podcast</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://cloud.dify.ai">Dify Cloud</a> ·
|
<a href="https://cloud.dify.ai">Dify Cloud</a> ·
|
||||||
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Samostojno gostovanje</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Samostojno gostovanje</a> ·
|
||||||
<a href="https://docs.dify.ai">Dokumentacija</a> ·
|
<a href="https://docs.dify.ai">Dokumentacija</a> ·
|
||||||
<a href="https://dify.ai/pricing">Pregled ponudb izdelkov Dify</a>
|
<a href="https://dify.ai/pricing">Pregled ponudb izdelkov Dify</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://dify.ai" target="_blank">
|
<a href="https://dify.ai" target="_blank">
|
||||||
<img alt="Static Badge" src="https://img.shields.io/badge/Product-F04438"></a>
|
<img alt="Static Badge" src="https://img.shields.io/badge/Product-F04438"></a>
|
||||||
<a href="https://dify.ai/pricing" target="_blank">
|
<a href="https://dify.ai/pricing" target="_blank">
|
||||||
<img alt="Static Badge" src="https://img.shields.io/badge/free-pricing?logo=free&color=%20%23155EEF&label=pricing&labelColor=%20%23528bff"></a>
|
<img alt="Static Badge" src="https://img.shields.io/badge/free-pricing?logo=free&color=%20%23155EEF&label=pricing&labelColor=%20%23528bff"></a>
|
||||||
<a href="https://discord.gg/FngNHpbcY7" target="_blank">
|
<a href="https://discord.gg/FngNHpbcY7" target="_blank">
|
||||||
<img src="https://img.shields.io/discord/1082486657678311454?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb"
|
<img src="https://img.shields.io/discord/1082486657678311454?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb"
|
||||||
alt="chat on Discord"></a>
|
alt="chat on Discord"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
||||||
alt="follow on LinkedIn"></a>
|
alt="follow on LinkedIn"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
<img alt="Commits last month" src="https://img.shields.io/github/commit-activity/m/langgenius/dify?labelColor=%20%2332b583&color=%20%2312b76a"></a>
|
<img alt="Commits last month" src="https://img.shields.io/github/commit-activity/m/langgenius/dify?labelColor=%20%2332b583&color=%20%2312b76a"></a>
|
||||||
<a href="https://github.com/langgenius/dify/" target="_blank">
|
<a href="https://github.com/langgenius/dify/" target="_blank">
|
||||||
<img alt="Issues closed" src="https://img.shields.io/github/issues-search?query=repo%3Alanggenius%2Fdify%20is%3Aclosed&label=issues%20closed&labelColor=%20%237d89b0&color=%20%235d6b98"></a>
|
<img alt="Issues closed" src="https://img.shields.io/github/issues-search?query=repo%3Alanggenius%2Fdify%20is%3Aclosed&label=issues%20closed&labelColor=%20%237d89b0&color=%20%235d6b98"></a>
|
||||||
<a href="https://github.com/langgenius/dify/discussions/" target="_blank">
|
<a href="https://github.com/langgenius/dify/discussions/" target="_blank">
|
||||||
<img alt="Discussion posts" src="https://img.shields.io/github/discussions/langgenius/dify?labelColor=%20%239b8afb&color=%20%237a5af8"></a>
|
<img alt="Discussion posts" src="https://img.shields.io/github/discussions/langgenius/dify?labelColor=%20%239b8afb&color=%20%237a5af8"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="./README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
|
<a href="./README.md"><img alt="README in English" src="https://img.shields.io/badge/English-d9d9d9"></a>
|
||||||
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
|
<a href="./README_CN.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
|
||||||
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
|
<a href="./README_JA.md"><img alt="日本語のREADME" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
|
||||||
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
|
<a href="./README_ES.md"><img alt="README en Español" src="https://img.shields.io/badge/Español-d9d9d9"></a>
|
||||||
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
|
<a href="./README_FR.md"><img alt="README en Français" src="https://img.shields.io/badge/Français-d9d9d9"></a>
|
||||||
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
|
<a href="./README_KL.md"><img alt="README tlhIngan Hol" src="https://img.shields.io/badge/Klingon-d9d9d9"></a>
|
||||||
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
<a href="./README_KR.md"><img alt="README in Korean" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
||||||
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
|
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
|
||||||
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
|
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
|
||||||
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
|
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
|
||||||
<a href="./README_SI.md"><img alt="README Slovenščina" src="https://img.shields.io/badge/Sloven%C5%A1%C4%8Dina-d9d9d9"></a>
|
<a href="./README_SI.md"><img alt="README Slovenščina" src="https://img.shields.io/badge/Sloven%C5%A1%C4%8Dina-d9d9d9"></a>
|
||||||
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
|
<a href="./README_BN.md"><img alt="README in বাংলা" src="https://img.shields.io/badge/বাংলা-d9d9d9"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
Dify je odprtokodna platforma za razvoj aplikacij LLM. Njegov intuitivni vmesnik združuje agentski potek dela z umetno inteligenco, cevovod RAG, zmogljivosti agentov, upravljanje modelov, funkcije opazovanja in več, kar vam omogoča hiter prehod od prototipa do proizvodnje.
|
Dify je odprtokodna platforma za razvoj aplikacij LLM. Njegov intuitivni vmesnik združuje agentski potek dela z umetno inteligenco, cevovod RAG, zmogljivosti agentov, upravljanje modelov, funkcije opazovanja in več, kar vam omogoča hiter prehod od prototipa do proizvodnje.
|
||||||
|
|
||||||
## Hitri začetek
|
## Hitri začetek
|
||||||
> Preden namestite Dify, se prepričajte, da vaša naprava izpolnjuje naslednje minimalne sistemske zahteve:
|
> Preden namestite Dify, se prepričajte, da vaša naprava izpolnjuje naslednje minimalne sistemske zahteve:
|
||||||
>
|
>
|
||||||
>- CPU >= 2 Core
|
>- CPU >= 2 Core
|
||||||
>- RAM >= 4 GiB
|
>- RAM >= 4 GiB
|
||||||
|
|
||||||
</br>
|
</br>
|
||||||
|
|
||||||
Najlažji način za zagon strežnika Dify je prek docker compose . Preden zaženete Dify z naslednjimi ukazi, se prepričajte, da sta Docker in Docker Compose nameščena na vašem računalniku:
|
Najlažji način za zagon strežnika Dify je prek docker compose . Preden zaženete Dify z naslednjimi ukazi, se prepričajte, da sta Docker in Docker Compose nameščena na vašem računalniku:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd dify
|
cd dify
|
||||||
cd docker
|
cd docker
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Po zagonu lahko dostopate do nadzorne plošče Dify v brskalniku na [http://localhost/install](http://localhost/install) in začnete postopek inicializacije.
|
Po zagonu lahko dostopate do nadzorne plošče Dify v brskalniku na [http://localhost/install](http://localhost/install) in začnete postopek inicializacije.
|
||||||
|
|
||||||
#### Iskanje pomoči
|
#### Iskanje pomoči
|
||||||
Prosimo, glejte naša pogosta vprašanja [FAQ](https://docs.dify.ai/getting-started/install-self-hosted/faqs) če naletite na težave pri nastavitvi Dify. Če imate še vedno težave, se obrnite na [skupnost ali nas](#community--contact).
|
Prosimo, glejte naša pogosta vprašanja [FAQ](https://docs.dify.ai/getting-started/install-self-hosted/faqs) če naletite na težave pri nastavitvi Dify. Če imate še vedno težave, se obrnite na [skupnost ali nas](#community--contact).
|
||||||
|
|
||||||
> Če želite prispevati k Difyju ali narediti dodaten razvoj, glejte naš vodnik za [uvajanje iz izvorne kode](https://docs.dify.ai/getting-started/install-self-hosted/local-source-code)
|
> Če želite prispevati k Difyju ali narediti dodaten razvoj, glejte naš vodnik za [uvajanje iz izvorne kode](https://docs.dify.ai/getting-started/install-self-hosted/local-source-code)
|
||||||
|
|
||||||
## Ključne značilnosti
|
## Ključne značilnosti
|
||||||
**1. Potek dela**:
|
**1. Potek dela**:
|
||||||
Zgradite in preizkusite zmogljive poteke dela AI na vizualnem platnu, pri čemer izkoristite vse naslednje funkcije in več.
|
Zgradite in preizkusite zmogljive poteke dela AI na vizualnem platnu, pri čemer izkoristite vse naslednje funkcije in več.
|
||||||
|
|
||||||
|
|
||||||
https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa
|
https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**2. Celovita podpora za modele**:
|
**2. Celovita podpora za modele**:
|
||||||
Brezhibna integracija s stotinami lastniških/odprtokodnih LLM-jev ducatov ponudnikov sklepanja in samostojnih rešitev, ki pokrivajo GPT, Mistral, Llama3 in vse modele, združljive z API-jem OpenAI. Celoten seznam podprtih ponudnikov modelov najdete [tukaj](https://docs.dify.ai/getting-started/readme/model-providers).
|
Brezhibna integracija s stotinami lastniških/odprtokodnih LLM-jev ducatov ponudnikov sklepanja in samostojnih rešitev, ki pokrivajo GPT, Mistral, Llama3 in vse modele, združljive z API-jem OpenAI. Celoten seznam podprtih ponudnikov modelov najdete [tukaj](https://docs.dify.ai/getting-started/readme/model-providers).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
**3. Prompt IDE**:
|
**3. Prompt IDE**:
|
||||||
intuitivni vmesnik za ustvarjanje pozivov, primerjavo zmogljivosti modela in dodajanje dodatnih funkcij, kot je pretvorba besedila v govor, aplikaciji, ki temelji na klepetu.
|
intuitivni vmesnik za ustvarjanje pozivov, primerjavo zmogljivosti modela in dodajanje dodatnih funkcij, kot je pretvorba besedila v govor, aplikaciji, ki temelji na klepetu.
|
||||||
|
|
||||||
**4. RAG Pipeline**:
|
**4. RAG Pipeline**:
|
||||||
E Obsežne zmogljivosti RAG, ki pokrivajo vse od vnosa dokumenta do priklica, s podporo za ekstrakcijo besedila iz datotek PDF, PPT in drugih običajnih formatov dokumentov.
|
E Obsežne zmogljivosti RAG, ki pokrivajo vse od vnosa dokumenta do priklica, s podporo za ekstrakcijo besedila iz datotek PDF, PPT in drugih običajnih formatov dokumentov.
|
||||||
|
|
||||||
**5. Agent capabilities**:
|
**5. Agent capabilities**:
|
||||||
definirate lahko agente, ki temeljijo na klicanju funkcij LLM ali ReAct, in dodate vnaprej izdelana orodja ali orodja po meri za agenta. Dify ponuja več kot 50 vgrajenih orodij za agente AI, kot so Google Search, DALL·E, Stable Diffusion in WolframAlpha.
|
definirate lahko agente, ki temeljijo na klicanju funkcij LLM ali ReAct, in dodate vnaprej izdelana orodja ali orodja po meri za agenta. Dify ponuja več kot 50 vgrajenih orodij za agente AI, kot so Google Search, DALL·E, Stable Diffusion in WolframAlpha.
|
||||||
|
|
||||||
**6. LLMOps**:
|
**6. LLMOps**:
|
||||||
Spremljajte in analizirajte dnevnike aplikacij in učinkovitost skozi čas. Pozive, nabore podatkov in modele lahko nenehno izboljšujete na podlagi proizvodnih podatkov in opomb.
|
Spremljajte in analizirajte dnevnike aplikacij in učinkovitost skozi čas. Pozive, nabore podatkov in modele lahko nenehno izboljšujete na podlagi proizvodnih podatkov in opomb.
|
||||||
|
|
||||||
**7. Backend-as-a-Service**:
|
**7. Backend-as-a-Service**:
|
||||||
AVse ponudbe Difyja so opremljene z ustreznimi API-ji, tako da lahko Dify brez težav integrirate v svojo poslovno logiko.
|
AVse ponudbe Difyja so opremljene z ustreznimi API-ji, tako da lahko Dify brez težav integrirate v svojo poslovno logiko.
|
||||||
|
|
||||||
## Primerjava Funkcij
|
## Primerjava Funkcij
|
||||||
|
|
||||||
<table style="width: 100%;">
|
<table style="width: 100%;">
|
||||||
<tr>
|
<tr>
|
||||||
<th align="center">Funkcija</th>
|
<th align="center">Funkcija</th>
|
||||||
<th align="center">Dify.AI</th>
|
<th align="center">Dify.AI</th>
|
||||||
<th align="center">LangChain</th>
|
<th align="center">LangChain</th>
|
||||||
<th align="center">Flowise</th>
|
<th align="center">Flowise</th>
|
||||||
<th align="center">OpenAI Assistants API</th>
|
<th align="center">OpenAI Assistants API</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">Programski pristop</td>
|
<td align="center">Programski pristop</td>
|
||||||
<td align="center">API + usmerjeno v aplikacije</td>
|
<td align="center">API + usmerjeno v aplikacije</td>
|
||||||
<td align="center">Python koda</td>
|
<td align="center">Python koda</td>
|
||||||
<td align="center">Usmerjeno v aplikacije</td>
|
<td align="center">Usmerjeno v aplikacije</td>
|
||||||
<td align="center">Usmerjeno v API</td>
|
<td align="center">Usmerjeno v API</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">Podprti LLM-ji</td>
|
<td align="center">Podprti LLM-ji</td>
|
||||||
<td align="center">Bogata izbira</td>
|
<td align="center">Bogata izbira</td>
|
||||||
<td align="center">Bogata izbira</td>
|
<td align="center">Bogata izbira</td>
|
||||||
<td align="center">Bogata izbira</td>
|
<td align="center">Bogata izbira</td>
|
||||||
<td align="center">Samo OpenAI</td>
|
<td align="center">Samo OpenAI</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">RAG pogon</td>
|
<td align="center">RAG pogon</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">Agent</td>
|
<td align="center">Agent</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">❌</td>
|
<td align="center">❌</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">Potek dela</td>
|
<td align="center">Potek dela</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">❌</td>
|
<td align="center">❌</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">❌</td>
|
<td align="center">❌</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">Spremljanje</td>
|
<td align="center">Spremljanje</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">❌</td>
|
<td align="center">❌</td>
|
||||||
<td align="center">❌</td>
|
<td align="center">❌</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">Funkcija za podjetja (SSO/nadzor dostopa)</td>
|
<td align="center">Funkcija za podjetja (SSO/nadzor dostopa)</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">❌</td>
|
<td align="center">❌</td>
|
||||||
<td align="center">❌</td>
|
<td align="center">❌</td>
|
||||||
<td align="center">❌</td>
|
<td align="center">❌</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">Lokalna namestitev</td>
|
<td align="center">Lokalna namestitev</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">✅</td>
|
<td align="center">✅</td>
|
||||||
<td align="center">❌</td>
|
<td align="center">❌</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Uporaba Dify
|
## Uporaba Dify
|
||||||
|
|
||||||
- **Cloud </br>**
|
- **Cloud </br>**
|
||||||
Gostimo storitev Dify Cloud za vsakogar, ki jo lahko preizkusite brez nastavitev. Zagotavlja vse zmožnosti različice za samostojno namestitev in vključuje 200 brezplačnih klicev GPT-4 v načrtu peskovnika.
|
Gostimo storitev Dify Cloud za vsakogar, ki jo lahko preizkusite brez nastavitev. Zagotavlja vse zmožnosti različice za samostojno namestitev in vključuje 200 brezplačnih klicev GPT-4 v načrtu peskovnika.
|
||||||
|
|
||||||
- **Self-hosting Dify Community Edition</br>**
|
- **Self-hosting Dify Community Edition</br>**
|
||||||
Hitro zaženite Dify v svojem okolju s tem [začetnim vodnikom](#quick-start) . Za dodatne reference in podrobnejša navodila uporabite našo [dokumentacijo](https://docs.dify.ai) .
|
Hitro zaženite Dify v svojem okolju s tem [začetnim vodnikom](#quick-start) . Za dodatne reference in podrobnejša navodila uporabite našo [dokumentacijo](https://docs.dify.ai) .
|
||||||
|
|
||||||
|
|
||||||
- **Dify za podjetja/organizacije</br>**
|
- **Dify za podjetja/organizacije</br>**
|
||||||
Ponujamo dodatne funkcije, osredotočene na podjetja. Zabeležite svoja vprašanja prek tega klepetalnega robota ali nam pošljite e-pošto, da se pogovorimo o potrebah podjetja. </br>
|
Ponujamo dodatne funkcije, osredotočene na podjetja. Zabeležite svoja vprašanja prek tega klepetalnega robota ali nam pošljite e-pošto, da se pogovorimo o potrebah podjetja. </br>
|
||||||
> Za novoustanovljena podjetja in mala podjetja, ki uporabljajo AWS, si oglejte Dify Premium na AWS Marketplace in ga z enim klikom uvedite v svoj AWS VPC. To je cenovno ugodna ponudba AMI z možnostjo ustvarjanja aplikacij z logotipom in blagovno znamko po meri.
|
> Za novoustanovljena podjetja in mala podjetja, ki uporabljajo AWS, si oglejte Dify Premium na AWS Marketplace in ga z enim klikom uvedite v svoj AWS VPC. To je cenovno ugodna ponudba AMI z možnostjo ustvarjanja aplikacij z logotipom in blagovno znamko po meri.
|
||||||
|
|
||||||
|
|
||||||
## Staying ahead
|
## Staying ahead
|
||||||
|
|
||||||
Star Dify on GitHub and be instantly notified of new releases.
|
Star Dify on GitHub and be instantly notified of new releases.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Napredne nastavitve
|
## Napredne nastavitve
|
||||||
|
|
||||||
Če morate prilagoditi konfiguracijo, si oglejte komentarje v naši datoteki .env.example in posodobite ustrezne vrednosti v svoji .env datoteki. Poleg tega boste morda morali prilagoditi docker-compose.yamlsamo datoteko, na primer spremeniti različice slike, preslikave vrat ali namestitve nosilca, glede na vaše specifično okolje in zahteve za uvajanje. Po kakršnih koli spremembah ponovno zaženite docker-compose up -d. Celoten seznam razpoložljivih spremenljivk okolja najdete tukaj .
|
Če morate prilagoditi konfiguracijo, si oglejte komentarje v naši datoteki .env.example in posodobite ustrezne vrednosti v svoji .env datoteki. Poleg tega boste morda morali prilagoditi docker-compose.yamlsamo datoteko, na primer spremeniti različice slike, preslikave vrat ali namestitve nosilca, glede na vaše specifično okolje in zahteve za uvajanje. Po kakršnih koli spremembah ponovno zaženite docker-compose up -d. Celoten seznam razpoložljivih spremenljivk okolja najdete tukaj .
|
||||||
|
|
||||||
Če želite konfigurirati visoko razpoložljivo nastavitev, so na voljo Helm Charts in datoteke YAML, ki jih prispeva skupnost, ki omogočajo uvedbo Difyja v Kubernetes.
|
Če želite konfigurirati visoko razpoložljivo nastavitev, so na voljo Helm Charts in datoteke YAML, ki jih prispeva skupnost, ki omogočajo uvedbo Difyja v Kubernetes.
|
||||||
|
|
||||||
- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
|
- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify)
|
||||||
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
|
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
|
||||||
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
|
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
|
||||||
- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s)
|
- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s)
|
||||||
|
|
||||||
#### Uporaba Terraform za uvajanje
|
#### Uporaba Terraform za uvajanje
|
||||||
|
|
||||||
namestite Dify v Cloud Platform z enim klikom z uporabo [terraform](https://www.terraform.io/)
|
namestite Dify v Cloud Platform z enim klikom z uporabo [terraform](https://www.terraform.io/)
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||||
|
|
||||||
##### Google Cloud
|
##### Google Cloud
|
||||||
- [Google Cloud Terraform by @sotazum](https://github.com/DeNA/dify-google-cloud-terraform)
|
- [Google Cloud Terraform by @sotazum](https://github.com/DeNA/dify-google-cloud-terraform)
|
||||||
|
|
||||||
#### Uporaba AWS CDK za uvajanje
|
#### Uporaba AWS CDK za uvajanje
|
||||||
|
|
||||||
Uvedite Dify v AWS z uporabo [CDK](https://aws.amazon.com/cdk/)
|
Uvedite Dify v AWS z uporabo [CDK](https://aws.amazon.com/cdk/)
|
||||||
|
|
||||||
##### AWS
|
##### AWS
|
||||||
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws)
|
||||||
|
|
||||||
## Prispevam
|
## Prispevam
|
||||||
|
|
||||||
Za tiste, ki bi radi prispevali kodo, si oglejte naš vodnik za prispevke . Hkrati vas prosimo, da podprete Dify tako, da ga delite na družbenih medijih ter na dogodkih in konferencah.
|
Za tiste, ki bi radi prispevali kodo, si oglejte naš vodnik za prispevke . Hkrati vas prosimo, da podprete Dify tako, da ga delite na družbenih medijih ter na dogodkih in konferencah.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
> Iščemo sodelavce za pomoč pri prevajanju Difyja v jezike, ki niso mandarinščina ali angleščina. Če želite pomagati, si oglejte i18n README za več informacij in nam pustite komentar v global-userskanalu našega strežnika skupnosti Discord .
|
> Iščemo sodelavce za pomoč pri prevajanju Difyja v jezike, ki niso mandarinščina ali angleščina. Če želite pomagati, si oglejte i18n README za več informacij in nam pustite komentar v global-userskanalu našega strežnika skupnosti Discord .
|
||||||
|
|
||||||
## Skupnost in stik
|
## Skupnost in stik
|
||||||
|
|
||||||
* [Github Discussion](https://github.com/langgenius/dify/discussions). Najboljše za: izmenjavo povratnih informacij in postavljanje vprašanj.
|
* [Github Discussion](https://github.com/langgenius/dify/discussions). Najboljše za: izmenjavo povratnih informacij in postavljanje vprašanj.
|
||||||
* [GitHub Issues](https://github.com/langgenius/dify/issues). Najboljše za: hrošče, na katere naletite pri uporabi Dify.AI, in predloge funkcij. Oglejte si naš [vodnik za prispevke](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
* [GitHub Issues](https://github.com/langgenius/dify/issues). Najboljše za: hrošče, na katere naletite pri uporabi Dify.AI, in predloge funkcij. Oglejte si naš [vodnik za prispevke](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
* [Discord](https://discord.gg/FngNHpbcY7). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo.
|
* [Discord](https://discord.gg/FngNHpbcY7). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo.
|
||||||
* [X(Twitter)](https://twitter.com/dify_ai). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo.
|
* [X(Twitter)](https://twitter.com/dify_ai). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo.
|
||||||
|
|
||||||
**Contributors**
|
**Contributors**
|
||||||
|
|
||||||
<a href="https://github.com/langgenius/dify/graphs/contributors">
|
<a href="https://github.com/langgenius/dify/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=langgenius/dify" />
|
<img src="https://contrib.rocks/image?repo=langgenius/dify" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## Star history
|
## Star history
|
||||||
|
|
||||||
[](https://star-history.com/#langgenius/dify&Date)
|
[](https://star-history.com/#langgenius/dify&Date)
|
||||||
|
|
||||||
|
|
||||||
## Varnostno razkritje
|
## Varnostno razkritje
|
||||||
|
|
||||||
Zaradi zaščite vaše zasebnosti se izogibajte objavljanju varnostnih vprašanj na GitHub. Namesto tega pošljite vprašanja na security@dify.ai in zagotovili vam bomo podrobnejši odgovor.
|
Zaradi zaščite vaše zasebnosti se izogibajte objavljanju varnostnih vprašanj na GitHub. Namesto tega pošljite vprašanja na security@dify.ai in zagotovili vam bomo podrobnejši odgovor.
|
||||||
|
|
||||||
## Licenca
|
## Licenca
|
||||||
|
|
||||||
To skladišče je na voljo pod [odprtokodno licenco Dify](LICENSE) , ki je v bistvu Apache 2.0 z nekaj dodatnimi omejitvami.
|
To skladišče je na voljo pod [odprtokodno licenco Dify](LICENSE) , ki je v bistvu Apache 2.0 z nekaj dodatnimi omejitvami.
|
||||||
|
@ -16,4 +16,4 @@ logs
|
|||||||
.ruff_cache
|
.ruff_cache
|
||||||
|
|
||||||
# venv
|
# venv
|
||||||
.venv
|
.venv
|
||||||
|
@ -476,6 +476,7 @@ LOGIN_LOCKOUT_DURATION=86400
|
|||||||
ENABLE_OTEL=false
|
ENABLE_OTEL=false
|
||||||
OTLP_BASE_ENDPOINT=http://localhost:4318
|
OTLP_BASE_ENDPOINT=http://localhost:4318
|
||||||
OTLP_API_KEY=
|
OTLP_API_KEY=
|
||||||
|
OTEL_EXPORTER_OTLP_PROTOCOL=
|
||||||
OTEL_EXPORTER_TYPE=otlp
|
OTEL_EXPORTER_TYPE=otlp
|
||||||
OTEL_SAMPLING_RATE=0.1
|
OTEL_SAMPLING_RATE=0.1
|
||||||
OTEL_BATCH_EXPORT_SCHEDULE_DELAY=5000
|
OTEL_BATCH_EXPORT_SCHEDULE_DELAY=5000
|
||||||
|
@ -90,3 +90,4 @@
|
|||||||
```bash
|
```bash
|
||||||
uv run -P api bash dev/pytest/pytest_all_tests.sh
|
uv run -P api bash dev/pytest/pytest_all_tests.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ else:
|
|||||||
# so we need to disable gevent in debug mode.
|
# so we need to disable gevent in debug mode.
|
||||||
# If you are using debugpy and set GEVENT_SUPPORT=True, you can debug with gevent.
|
# If you are using debugpy and set GEVENT_SUPPORT=True, you can debug with gevent.
|
||||||
if (flask_debug := os.environ.get("FLASK_DEBUG", "0")) and flask_debug.lower() in {"false", "0", "no"}:
|
if (flask_debug := os.environ.get("FLASK_DEBUG", "0")) and flask_debug.lower() in {"false", "0", "no"}:
|
||||||
from gevent import monkey # type: ignore
|
from gevent import monkey
|
||||||
|
|
||||||
# gevent
|
# gevent
|
||||||
monkey.patch_all()
|
monkey.patch_all()
|
||||||
|
@ -52,10 +52,8 @@ def initialize_extensions(app: DifyApp):
|
|||||||
ext_mail,
|
ext_mail,
|
||||||
ext_migrate,
|
ext_migrate,
|
||||||
ext_otel,
|
ext_otel,
|
||||||
ext_otel_patch,
|
|
||||||
ext_proxy_fix,
|
ext_proxy_fix,
|
||||||
ext_redis,
|
ext_redis,
|
||||||
ext_repositories,
|
|
||||||
ext_sentry,
|
ext_sentry,
|
||||||
ext_set_secretkey,
|
ext_set_secretkey,
|
||||||
ext_storage,
|
ext_storage,
|
||||||
@ -76,7 +74,6 @@ def initialize_extensions(app: DifyApp):
|
|||||||
ext_migrate,
|
ext_migrate,
|
||||||
ext_redis,
|
ext_redis,
|
||||||
ext_storage,
|
ext_storage,
|
||||||
ext_repositories,
|
|
||||||
ext_celery,
|
ext_celery,
|
||||||
ext_login,
|
ext_login,
|
||||||
ext_mail,
|
ext_mail,
|
||||||
@ -85,7 +82,6 @@ def initialize_extensions(app: DifyApp):
|
|||||||
ext_proxy_fix,
|
ext_proxy_fix,
|
||||||
ext_blueprints,
|
ext_blueprints,
|
||||||
ext_commands,
|
ext_commands,
|
||||||
ext_otel_patch, # Apply patch before initializing OpenTelemetry
|
|
||||||
ext_otel,
|
ext_otel,
|
||||||
]
|
]
|
||||||
for ext in extensions:
|
for ext in extensions:
|
||||||
|
111
api/commands.py
111
api/commands.py
@ -6,6 +6,7 @@ from typing import Optional
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from sqlalchemy import select
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
@ -297,11 +298,11 @@ def migrate_knowledge_vector_database():
|
|||||||
page = 1
|
page = 1
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
datasets = (
|
stmt = (
|
||||||
Dataset.query.filter(Dataset.indexing_technique == "high_quality")
|
select(Dataset).filter(Dataset.indexing_technique == "high_quality").order_by(Dataset.created_at.desc())
|
||||||
.order_by(Dataset.created_at.desc())
|
|
||||||
.paginate(page=page, per_page=50)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
datasets = db.paginate(select=stmt, page=page, per_page=50, max_per_page=50, error_out=False)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -444,13 +445,13 @@ def convert_to_agent_apps():
|
|||||||
WHERE a.mode = 'chat'
|
WHERE a.mode = 'chat'
|
||||||
AND am.agent_mode is not null
|
AND am.agent_mode is not null
|
||||||
AND (
|
AND (
|
||||||
am.agent_mode like '%"strategy": "function_call"%'
|
am.agent_mode like '%"strategy": "function_call"%'
|
||||||
OR am.agent_mode like '%"strategy": "react"%'
|
OR am.agent_mode like '%"strategy": "react"%'
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
am.agent_mode like '{"enabled": true%'
|
am.agent_mode like '{"enabled": true%'
|
||||||
OR am.agent_mode like '{"max_iteration": %'
|
OR am.agent_mode like '{"max_iteration": %'
|
||||||
) ORDER BY a.created_at DESC LIMIT 1000
|
) ORDER BY a.created_at DESC LIMIT 1000
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with db.engine.begin() as conn:
|
with db.engine.begin() as conn:
|
||||||
@ -551,11 +552,12 @@ def old_metadata_migration():
|
|||||||
page = 1
|
page = 1
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
documents = (
|
stmt = (
|
||||||
DatasetDocument.query.filter(DatasetDocument.doc_metadata is not None)
|
select(DatasetDocument)
|
||||||
|
.filter(DatasetDocument.doc_metadata.is_not(None))
|
||||||
.order_by(DatasetDocument.created_at.desc())
|
.order_by(DatasetDocument.created_at.desc())
|
||||||
.paginate(page=page, per_page=50)
|
|
||||||
)
|
)
|
||||||
|
documents = db.paginate(select=stmt, page=page, per_page=50, max_per_page=50, error_out=False)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
break
|
break
|
||||||
if not documents:
|
if not documents:
|
||||||
@ -592,11 +594,15 @@ def old_metadata_migration():
|
|||||||
)
|
)
|
||||||
db.session.add(dataset_metadata_binding)
|
db.session.add(dataset_metadata_binding)
|
||||||
else:
|
else:
|
||||||
dataset_metadata_binding = DatasetMetadataBinding.query.filter(
|
dataset_metadata_binding = (
|
||||||
DatasetMetadataBinding.dataset_id == document.dataset_id,
|
db.session.query(DatasetMetadataBinding) # type: ignore
|
||||||
DatasetMetadataBinding.document_id == document.id,
|
.filter(
|
||||||
DatasetMetadataBinding.metadata_id == dataset_metadata.id,
|
DatasetMetadataBinding.dataset_id == document.dataset_id,
|
||||||
).first()
|
DatasetMetadataBinding.document_id == document.id,
|
||||||
|
DatasetMetadataBinding.metadata_id == dataset_metadata.id,
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not dataset_metadata_binding:
|
if not dataset_metadata_binding:
|
||||||
dataset_metadata_binding = DatasetMetadataBinding(
|
dataset_metadata_binding = DatasetMetadataBinding(
|
||||||
tenant_id=document.tenant_id,
|
tenant_id=document.tenant_id,
|
||||||
@ -668,7 +674,7 @@ def upgrade_db():
|
|||||||
click.echo(click.style("Starting database migration.", fg="green"))
|
click.echo(click.style("Starting database migration.", fg="green"))
|
||||||
|
|
||||||
# run db migration
|
# run db migration
|
||||||
import flask_migrate # type: ignore
|
import flask_migrate
|
||||||
|
|
||||||
flask_migrate.upgrade()
|
flask_migrate.upgrade()
|
||||||
|
|
||||||
@ -818,8 +824,9 @@ def clear_free_plan_tenant_expired_logs(days: int, batch: int, tenant_ids: list[
|
|||||||
click.echo(click.style("Clear free plan tenant expired logs completed.", fg="green"))
|
click.echo(click.style("Clear free plan tenant expired logs completed.", fg="green"))
|
||||||
|
|
||||||
|
|
||||||
|
@click.option("-f", "--force", is_flag=True, help="Skip user confirmation and force the command to execute.")
|
||||||
@click.command("clear-orphaned-file-records", help="Clear orphaned file records.")
|
@click.command("clear-orphaned-file-records", help="Clear orphaned file records.")
|
||||||
def clear_orphaned_file_records():
|
def clear_orphaned_file_records(force: bool):
|
||||||
"""
|
"""
|
||||||
Clear orphaned file records in the database.
|
Clear orphaned file records in the database.
|
||||||
"""
|
"""
|
||||||
@ -845,7 +852,15 @@ def clear_orphaned_file_records():
|
|||||||
|
|
||||||
# notify user and ask for confirmation
|
# notify user and ask for confirmation
|
||||||
click.echo(
|
click.echo(
|
||||||
click.style("This command will find and delete orphaned file records in the following tables:", fg="yellow")
|
click.style(
|
||||||
|
"This command will first find and delete orphaned file records from the message_files table,", fg="yellow"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
"and then it will find and delete orphaned file records in the following tables:",
|
||||||
|
fg="yellow",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
for files_table in files_tables:
|
for files_table in files_tables:
|
||||||
click.echo(click.style(f"- {files_table['table']}", fg="yellow"))
|
click.echo(click.style(f"- {files_table['table']}", fg="yellow"))
|
||||||
@ -878,11 +893,55 @@ def clear_orphaned_file_records():
|
|||||||
fg="yellow",
|
fg="yellow",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
click.confirm("Do you want to proceed?", abort=True)
|
if not force:
|
||||||
|
click.confirm("Do you want to proceed?", abort=True)
|
||||||
|
|
||||||
# start the cleanup process
|
# start the cleanup process
|
||||||
click.echo(click.style("Starting orphaned file records cleanup.", fg="white"))
|
click.echo(click.style("Starting orphaned file records cleanup.", fg="white"))
|
||||||
|
|
||||||
|
# clean up the orphaned records in the message_files table where message_id doesn't exist in messages table
|
||||||
|
try:
|
||||||
|
click.echo(
|
||||||
|
click.style("- Listing message_files records where message_id doesn't exist in messages table", fg="white")
|
||||||
|
)
|
||||||
|
query = (
|
||||||
|
"SELECT mf.id, mf.message_id "
|
||||||
|
"FROM message_files mf LEFT JOIN messages m ON mf.message_id = m.id "
|
||||||
|
"WHERE m.id IS NULL"
|
||||||
|
)
|
||||||
|
orphaned_message_files = []
|
||||||
|
with db.engine.begin() as conn:
|
||||||
|
rs = conn.execute(db.text(query))
|
||||||
|
for i in rs:
|
||||||
|
orphaned_message_files.append({"id": str(i[0]), "message_id": str(i[1])})
|
||||||
|
|
||||||
|
if orphaned_message_files:
|
||||||
|
click.echo(click.style(f"Found {len(orphaned_message_files)} orphaned message_files records:", fg="white"))
|
||||||
|
for record in orphaned_message_files:
|
||||||
|
click.echo(click.style(f" - id: {record['id']}, message_id: {record['message_id']}", fg="black"))
|
||||||
|
|
||||||
|
if not force:
|
||||||
|
click.confirm(
|
||||||
|
(
|
||||||
|
f"Do you want to proceed "
|
||||||
|
f"to delete all {len(orphaned_message_files)} orphaned message_files records?"
|
||||||
|
),
|
||||||
|
abort=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
click.echo(click.style("- Deleting orphaned message_files records", fg="white"))
|
||||||
|
query = "DELETE FROM message_files WHERE id IN :ids"
|
||||||
|
with db.engine.begin() as conn:
|
||||||
|
conn.execute(db.text(query), {"ids": tuple([record["id"] for record in orphaned_message_files])})
|
||||||
|
click.echo(
|
||||||
|
click.style(f"Removed {len(orphaned_message_files)} orphaned message_files records.", fg="green")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
click.echo(click.style("No orphaned message_files records found. There is nothing to delete.", fg="green"))
|
||||||
|
except Exception as e:
|
||||||
|
click.echo(click.style(f"Error deleting orphaned message_files records: {str(e)}", fg="red"))
|
||||||
|
|
||||||
|
# clean up the orphaned records in the rest of the *_files tables
|
||||||
try:
|
try:
|
||||||
# fetch file id and keys from each table
|
# fetch file id and keys from each table
|
||||||
all_files_in_tables = []
|
all_files_in_tables = []
|
||||||
@ -964,7 +1023,8 @@ def clear_orphaned_file_records():
|
|||||||
click.echo(click.style(f"Found {len(orphaned_files)} orphaned file records.", fg="white"))
|
click.echo(click.style(f"Found {len(orphaned_files)} orphaned file records.", fg="white"))
|
||||||
for file in orphaned_files:
|
for file in orphaned_files:
|
||||||
click.echo(click.style(f"- orphaned file id: {file}", fg="black"))
|
click.echo(click.style(f"- orphaned file id: {file}", fg="black"))
|
||||||
click.confirm(f"Do you want to proceed to delete all {len(orphaned_files)} orphaned file records?", abort=True)
|
if not force:
|
||||||
|
click.confirm(f"Do you want to proceed to delete all {len(orphaned_files)} orphaned file records?", abort=True)
|
||||||
|
|
||||||
# delete orphaned records for each file
|
# delete orphaned records for each file
|
||||||
try:
|
try:
|
||||||
@ -979,8 +1039,9 @@ def clear_orphaned_file_records():
|
|||||||
click.echo(click.style(f"Removed {len(orphaned_files)} orphaned file records.", fg="green"))
|
click.echo(click.style(f"Removed {len(orphaned_files)} orphaned file records.", fg="green"))
|
||||||
|
|
||||||
|
|
||||||
|
@click.option("-f", "--force", is_flag=True, help="Skip user confirmation and force the command to execute.")
|
||||||
@click.command("remove-orphaned-files-on-storage", help="Remove orphaned files on the storage.")
|
@click.command("remove-orphaned-files-on-storage", help="Remove orphaned files on the storage.")
|
||||||
def remove_orphaned_files_on_storage():
|
def remove_orphaned_files_on_storage(force: bool):
|
||||||
"""
|
"""
|
||||||
Remove orphaned files on the storage.
|
Remove orphaned files on the storage.
|
||||||
"""
|
"""
|
||||||
@ -1028,7 +1089,8 @@ def remove_orphaned_files_on_storage():
|
|||||||
fg="yellow",
|
fg="yellow",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
click.confirm("Do you want to proceed?", abort=True)
|
if not force:
|
||||||
|
click.confirm("Do you want to proceed?", abort=True)
|
||||||
|
|
||||||
# start the cleanup process
|
# start the cleanup process
|
||||||
click.echo(click.style("Starting orphaned files cleanup.", fg="white"))
|
click.echo(click.style("Starting orphaned files cleanup.", fg="white"))
|
||||||
@ -1069,7 +1131,8 @@ def remove_orphaned_files_on_storage():
|
|||||||
click.echo(click.style(f"Found {len(orphaned_files)} orphaned files.", fg="white"))
|
click.echo(click.style(f"Found {len(orphaned_files)} orphaned files.", fg="white"))
|
||||||
for file in orphaned_files:
|
for file in orphaned_files:
|
||||||
click.echo(click.style(f"- orphaned file: {file}", fg="black"))
|
click.echo(click.style(f"- orphaned file: {file}", fg="black"))
|
||||||
click.confirm(f"Do you want to proceed to remove all {len(orphaned_files)} orphaned files?", abort=True)
|
if not force:
|
||||||
|
click.confirm(f"Do you want to proceed to remove all {len(orphaned_files)} orphaned files?", abort=True)
|
||||||
|
|
||||||
# delete orphaned files
|
# delete orphaned files
|
||||||
removed_files = 0
|
removed_files = 0
|
||||||
|
@ -74,7 +74,7 @@ class CodeExecutionSandboxConfig(BaseSettings):
|
|||||||
|
|
||||||
CODE_EXECUTION_ENDPOINT: HttpUrl = Field(
|
CODE_EXECUTION_ENDPOINT: HttpUrl = Field(
|
||||||
description="URL endpoint for the code execution service",
|
description="URL endpoint for the code execution service",
|
||||||
default="http://sandbox:8194",
|
default=HttpUrl("http://sandbox:8194"),
|
||||||
)
|
)
|
||||||
|
|
||||||
CODE_EXECUTION_API_KEY: str = Field(
|
CODE_EXECUTION_API_KEY: str = Field(
|
||||||
@ -145,7 +145,7 @@ class PluginConfig(BaseSettings):
|
|||||||
|
|
||||||
PLUGIN_DAEMON_URL: HttpUrl = Field(
|
PLUGIN_DAEMON_URL: HttpUrl = Field(
|
||||||
description="Plugin API URL",
|
description="Plugin API URL",
|
||||||
default="http://localhost:5002",
|
default=HttpUrl("http://localhost:5002"),
|
||||||
)
|
)
|
||||||
|
|
||||||
PLUGIN_DAEMON_KEY: str = Field(
|
PLUGIN_DAEMON_KEY: str = Field(
|
||||||
@ -188,7 +188,7 @@ class MarketplaceConfig(BaseSettings):
|
|||||||
|
|
||||||
MARKETPLACE_API_URL: HttpUrl = Field(
|
MARKETPLACE_API_URL: HttpUrl = Field(
|
||||||
description="Marketplace API URL",
|
description="Marketplace API URL",
|
||||||
default="https://marketplace.dify.ai",
|
default=HttpUrl("https://marketplace.dify.ai"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -398,6 +398,11 @@ class InnerAPIConfig(BaseSettings):
|
|||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
INNER_API_KEY: Optional[str] = Field(
|
||||||
|
description="API key for accessing the internal API",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LoggingConfig(BaseSettings):
|
class LoggingConfig(BaseSettings):
|
||||||
"""
|
"""
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from typing import Any, Literal, Optional
|
from typing import Any, Literal, Optional
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import parse_qsl, quote_plus
|
||||||
|
|
||||||
from pydantic import Field, NonNegativeInt, PositiveFloat, PositiveInt, computed_field
|
from pydantic import Field, NonNegativeInt, PositiveFloat, PositiveInt, computed_field
|
||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
@ -173,17 +173,31 @@ class DatabaseConfig(BaseSettings):
|
|||||||
|
|
||||||
RETRIEVAL_SERVICE_EXECUTORS: NonNegativeInt = Field(
|
RETRIEVAL_SERVICE_EXECUTORS: NonNegativeInt = Field(
|
||||||
description="Number of processes for the retrieval service, default to CPU cores.",
|
description="Number of processes for the retrieval service, default to CPU cores.",
|
||||||
default=os.cpu_count(),
|
default=os.cpu_count() or 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@computed_field
|
@computed_field # type: ignore[misc]
|
||||||
|
@property
|
||||||
def SQLALCHEMY_ENGINE_OPTIONS(self) -> dict[str, Any]:
|
def SQLALCHEMY_ENGINE_OPTIONS(self) -> dict[str, Any]:
|
||||||
|
# Parse DB_EXTRAS for 'options'
|
||||||
|
db_extras_dict = dict(parse_qsl(self.DB_EXTRAS))
|
||||||
|
options = db_extras_dict.get("options", "")
|
||||||
|
# Always include timezone
|
||||||
|
timezone_opt = "-c timezone=UTC"
|
||||||
|
if options:
|
||||||
|
# Merge user options and timezone
|
||||||
|
merged_options = f"{options} {timezone_opt}"
|
||||||
|
else:
|
||||||
|
merged_options = timezone_opt
|
||||||
|
|
||||||
|
connect_args = {"options": merged_options}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"pool_size": self.SQLALCHEMY_POOL_SIZE,
|
"pool_size": self.SQLALCHEMY_POOL_SIZE,
|
||||||
"max_overflow": self.SQLALCHEMY_MAX_OVERFLOW,
|
"max_overflow": self.SQLALCHEMY_MAX_OVERFLOW,
|
||||||
"pool_recycle": self.SQLALCHEMY_POOL_RECYCLE,
|
"pool_recycle": self.SQLALCHEMY_POOL_RECYCLE,
|
||||||
"pool_pre_ping": self.SQLALCHEMY_POOL_PRE_PING,
|
"pool_pre_ping": self.SQLALCHEMY_POOL_PRE_PING,
|
||||||
"connect_args": {"options": "-c timezone=UTC"},
|
"connect_args": connect_args,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
10
api/configs/middleware/cache/redis_config.py
vendored
10
api/configs/middleware/cache/redis_config.py
vendored
@ -83,3 +83,13 @@ class RedisConfig(BaseSettings):
|
|||||||
description="Password for Redis Clusters authentication (if required)",
|
description="Password for Redis Clusters authentication (if required)",
|
||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
REDIS_SERIALIZATION_PROTOCOL: int = Field(
|
||||||
|
description="Redis serialization protocol (RESP) version",
|
||||||
|
default=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
REDIS_ENABLE_CLIENT_SIDE_CACHE: bool = Field(
|
||||||
|
description="Enable client side cache in redis",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from typing import Optional
|
import enum
|
||||||
|
from typing import Literal, Optional
|
||||||
|
|
||||||
from pydantic import Field, PositiveInt
|
from pydantic import Field, PositiveInt
|
||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
@ -9,6 +10,14 @@ class OpenSearchConfig(BaseSettings):
|
|||||||
Configuration settings for OpenSearch
|
Configuration settings for OpenSearch
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class AuthMethod(enum.StrEnum):
|
||||||
|
"""
|
||||||
|
Authentication method for OpenSearch
|
||||||
|
"""
|
||||||
|
|
||||||
|
BASIC = "basic"
|
||||||
|
AWS_MANAGED_IAM = "aws_managed_iam"
|
||||||
|
|
||||||
OPENSEARCH_HOST: Optional[str] = Field(
|
OPENSEARCH_HOST: Optional[str] = Field(
|
||||||
description="Hostname or IP address of the OpenSearch server (e.g., 'localhost' or 'opensearch.example.com')",
|
description="Hostname or IP address of the OpenSearch server (e.g., 'localhost' or 'opensearch.example.com')",
|
||||||
default=None,
|
default=None,
|
||||||
@ -19,6 +28,16 @@ class OpenSearchConfig(BaseSettings):
|
|||||||
default=9200,
|
default=9200,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OPENSEARCH_SECURE: bool = Field(
|
||||||
|
description="Whether to use SSL/TLS encrypted connection for OpenSearch (True for HTTPS, False for HTTP)",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
OPENSEARCH_AUTH_METHOD: AuthMethod = Field(
|
||||||
|
description="Authentication method for OpenSearch connection (default is 'basic')",
|
||||||
|
default=AuthMethod.BASIC,
|
||||||
|
)
|
||||||
|
|
||||||
OPENSEARCH_USER: Optional[str] = Field(
|
OPENSEARCH_USER: Optional[str] = Field(
|
||||||
description="Username for authenticating with OpenSearch",
|
description="Username for authenticating with OpenSearch",
|
||||||
default=None,
|
default=None,
|
||||||
@ -29,7 +48,11 @@ class OpenSearchConfig(BaseSettings):
|
|||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
OPENSEARCH_SECURE: bool = Field(
|
OPENSEARCH_AWS_REGION: Optional[str] = Field(
|
||||||
description="Whether to use SSL/TLS encrypted connection for OpenSearch (True for HTTPS, False for HTTP)",
|
description="AWS region for OpenSearch (e.g. 'us-west-2')",
|
||||||
default=False,
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
OPENSEARCH_AWS_SERVICE: Optional[Literal["es", "aoss"]] = Field(
|
||||||
|
description="AWS service for OpenSearch (e.g. 'aoss' for OpenSearch Serverless)", default=None
|
||||||
)
|
)
|
||||||
|
@ -27,6 +27,11 @@ class OTelConfig(BaseSettings):
|
|||||||
default="otlp",
|
default="otlp",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OTEL_EXPORTER_OTLP_PROTOCOL: str = Field(
|
||||||
|
description="OTLP exporter protocol ('grpc' or 'http')",
|
||||||
|
default="http",
|
||||||
|
)
|
||||||
|
|
||||||
OTEL_SAMPLING_RATE: float = Field(default=0.1, description="Sampling rate for traces (0.0 to 1.0)")
|
OTEL_SAMPLING_RATE: float = Field(default=0.1, description="Sampling rate for traces (0.0 to 1.0)")
|
||||||
|
|
||||||
OTEL_BATCH_EXPORT_SCHEDULE_DELAY: int = Field(
|
OTEL_BATCH_EXPORT_SCHEDULE_DELAY: int = Field(
|
||||||
|
@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
|||||||
|
|
||||||
CURRENT_VERSION: str = Field(
|
CURRENT_VERSION: str = Field(
|
||||||
description="Dify version",
|
description="Dify version",
|
||||||
default="1.3.0",
|
default="1.3.1",
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMIT_SHA: str = Field(
|
COMMIT_SHA: str = Field(
|
||||||
|
@ -16,11 +16,25 @@ AUDIO_EXTENSIONS.extend([ext.upper() for ext in AUDIO_EXTENSIONS])
|
|||||||
|
|
||||||
|
|
||||||
if dify_config.ETL_TYPE == "Unstructured":
|
if dify_config.ETL_TYPE == "Unstructured":
|
||||||
DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls"]
|
DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls", "vtt", "properties"]
|
||||||
DOCUMENT_EXTENSIONS.extend(("doc", "docx", "csv", "eml", "msg", "pptx", "xml", "epub"))
|
DOCUMENT_EXTENSIONS.extend(("doc", "docx", "csv", "eml", "msg", "pptx", "xml", "epub"))
|
||||||
if dify_config.UNSTRUCTURED_API_URL:
|
if dify_config.UNSTRUCTURED_API_URL:
|
||||||
DOCUMENT_EXTENSIONS.append("ppt")
|
DOCUMENT_EXTENSIONS.append("ppt")
|
||||||
DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS])
|
DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS])
|
||||||
else:
|
else:
|
||||||
DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls", "docx", "csv"]
|
DOCUMENT_EXTENSIONS = [
|
||||||
|
"txt",
|
||||||
|
"markdown",
|
||||||
|
"md",
|
||||||
|
"mdx",
|
||||||
|
"pdf",
|
||||||
|
"html",
|
||||||
|
"htm",
|
||||||
|
"xlsx",
|
||||||
|
"xls",
|
||||||
|
"docx",
|
||||||
|
"csv",
|
||||||
|
"vtt",
|
||||||
|
"properties",
|
||||||
|
]
|
||||||
DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS])
|
DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS])
|
||||||
|
7
api/constants/mimetypes.py
Normal file
7
api/constants/mimetypes.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# The two constants below should keep in sync.
|
||||||
|
# Default content type for files which have no explicit content type.
|
||||||
|
|
||||||
|
DEFAULT_MIME_TYPE = "application/octet-stream"
|
||||||
|
# Default file extension for files which have no explicit content type, should
|
||||||
|
# correspond to the `DEFAULT_MIME_TYPE` above.
|
||||||
|
DEFAULT_EXTENSION = ".bin"
|
@ -1,4 +1,6 @@
|
|||||||
from flask_restful import fields # type: ignore
|
from flask_restful import fields
|
||||||
|
|
||||||
|
from libs.helper import AppIconUrlField
|
||||||
|
|
||||||
parameters__system_parameters = {
|
parameters__system_parameters = {
|
||||||
"image_file_size_limit": fields.Integer,
|
"image_file_size_limit": fields.Integer,
|
||||||
@ -22,3 +24,20 @@ parameters_fields = {
|
|||||||
"file_upload": fields.Raw,
|
"file_upload": fields.Raw,
|
||||||
"system_parameters": fields.Nested(parameters__system_parameters),
|
"system_parameters": fields.Nested(parameters__system_parameters),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
site_fields = {
|
||||||
|
"title": fields.String,
|
||||||
|
"chat_color_theme": fields.String,
|
||||||
|
"chat_color_theme_inverted": fields.Boolean,
|
||||||
|
"icon_type": fields.String,
|
||||||
|
"icon": fields.String,
|
||||||
|
"icon_background": fields.String,
|
||||||
|
"icon_url": AppIconUrlField,
|
||||||
|
"description": fields.String,
|
||||||
|
"copyright": fields.String,
|
||||||
|
"privacy_policy": fields.String,
|
||||||
|
"custom_disclaimer": fields.String,
|
||||||
|
"default_language": fields.String,
|
||||||
|
"show_workflow_steps": fields.Boolean,
|
||||||
|
"use_icon_as_answer_icon": fields.Boolean,
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import NotFound, Unauthorized
|
from werkzeug.exceptions import NotFound, Unauthorized
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import flask_restful # type: ignore
|
import flask_restful
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, fields, marshal_with
|
from flask_restful import Resource, fields, marshal_with
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.wraps import account_initialization_required, setup_required
|
from controllers.console.wraps import account_initialization_required, setup_required
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal, marshal_with, reqparse
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, inputs, marshal, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, inputs, marshal, marshal_with, reqparse
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import BadRequest, Forbidden, abort
|
from werkzeug.exceptions import BadRequest, Forbidden, abort
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import InternalServerError
|
from werkzeug.exceptions import InternalServerError
|
||||||
|
|
||||||
import services
|
import services
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import flask_login # type: ignore
|
import flask_login
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import InternalServerError, NotFound
|
from werkzeug.exceptions import InternalServerError, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
import pytz # pip install pytz
|
import pytz # pip install pytz
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
from flask_restful.inputs import int_range # type: ignore
|
from flask_restful.inputs import int_range
|
||||||
from sqlalchemy import func, or_
|
from sqlalchemy import func, or_
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, fields, marshal_with, reqparse
|
||||||
from flask_restful.inputs import int_range # type: ignore
|
from flask_restful.inputs import int_range
|
||||||
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -2,8 +2,8 @@ import json
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource # type: ignore
|
from flask_restful import Resource
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
from constants.languages import supported_language
|
from constants.languages import supported_language
|
||||||
|
@ -3,8 +3,8 @@ from decimal import Decimal
|
|||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from flask import jsonify
|
from flask import jsonify
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
|
@ -3,7 +3,7 @@ import logging
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from flask import abort, request
|
from flask import abort, request
|
||||||
from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, inputs, marshal_with, reqparse
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from dateutil.parser import isoparse
|
from dateutil.parser import isoparse
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
from flask_restful.inputs import int_range # type: ignore
|
from flask_restful.inputs import int_range
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
from flask_restful.inputs import int_range # type: ignore
|
from flask_restful.inputs import int_range
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
|
@ -3,8 +3,8 @@ from decimal import Decimal
|
|||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from flask import jsonify
|
from flask import jsonify
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from constants.languages import supported_language
|
from constants.languages import supported_language
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -2,8 +2,8 @@ import logging
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from flask import current_app, redirect, request
|
from flask import current_app, redirect, request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource # type: ignore
|
from flask_restful import Resource
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
@ -2,7 +2,7 @@ import base64
|
|||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
import flask_login # type: ignore
|
import flask_login
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
import services
|
import services
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
@ -4,7 +4,7 @@ from typing import Optional
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from flask import current_app, redirect, request
|
from flask import current_app, redirect, request
|
||||||
from flask_restful import Resource # type: ignore
|
from flask_restful import Resource
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import Unauthorized
|
from werkzeug.exceptions import Unauthorized
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.wraps import account_initialization_required, only_edition_cloud, setup_required
|
from controllers.console.wraps import account_initialization_required, only_edition_cloud, setup_required
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from libs.helper import extract_remote_ip
|
from libs.helper import extract_remote_ip
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
|
@ -2,8 +2,8 @@ import datetime
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import flask_restful # type: ignore
|
import flask_restful
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal, marshal_with, reqparse
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
@ -526,14 +526,20 @@ class DatasetIndexingStatusApi(Resource):
|
|||||||
)
|
)
|
||||||
documents_status = []
|
documents_status = []
|
||||||
for document in documents:
|
for document in documents:
|
||||||
completed_segments = DocumentSegment.query.filter(
|
completed_segments = (
|
||||||
DocumentSegment.completed_at.isnot(None),
|
db.session.query(DocumentSegment)
|
||||||
DocumentSegment.document_id == str(document.id),
|
.filter(
|
||||||
DocumentSegment.status != "re_segment",
|
DocumentSegment.completed_at.isnot(None),
|
||||||
).count()
|
DocumentSegment.document_id == str(document.id),
|
||||||
total_segments = DocumentSegment.query.filter(
|
DocumentSegment.status != "re_segment",
|
||||||
DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment"
|
)
|
||||||
).count()
|
.count()
|
||||||
|
)
|
||||||
|
total_segments = (
|
||||||
|
db.session.query(DocumentSegment)
|
||||||
|
.filter(DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment")
|
||||||
|
.count()
|
||||||
|
)
|
||||||
document.completed_segments = completed_segments
|
document.completed_segments = completed_segments
|
||||||
document.total_segments = total_segments
|
document.total_segments = total_segments
|
||||||
documents_status.append(marshal(document, document_status_fields))
|
documents_status.append(marshal(document, document_status_fields))
|
||||||
|
@ -4,9 +4,9 @@ from datetime import UTC, datetime
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, fields, marshal, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, fields, marshal, marshal_with, reqparse
|
||||||
from sqlalchemy import asc, desc
|
from sqlalchemy import asc, desc, select
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
@ -112,7 +112,7 @@ class GetProcessRuleApi(Resource):
|
|||||||
limits = DocumentService.DEFAULT_RULES["limits"]
|
limits = DocumentService.DEFAULT_RULES["limits"]
|
||||||
if document_id:
|
if document_id:
|
||||||
# get the latest process rule
|
# get the latest process rule
|
||||||
document = Document.query.get_or_404(document_id)
|
document = db.get_or_404(Document, document_id)
|
||||||
|
|
||||||
dataset = DatasetService.get_dataset(document.dataset_id)
|
dataset = DatasetService.get_dataset(document.dataset_id)
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ class DatasetDocumentListApi(Resource):
|
|||||||
except services.errors.account.NoPermissionError as e:
|
except services.errors.account.NoPermissionError as e:
|
||||||
raise Forbidden(str(e))
|
raise Forbidden(str(e))
|
||||||
|
|
||||||
query = Document.query.filter_by(dataset_id=str(dataset_id), tenant_id=current_user.current_tenant_id)
|
query = select(Document).filter_by(dataset_id=str(dataset_id), tenant_id=current_user.current_tenant_id)
|
||||||
|
|
||||||
if search:
|
if search:
|
||||||
search = f"%{search}%"
|
search = f"%{search}%"
|
||||||
@ -209,18 +209,24 @@ class DatasetDocumentListApi(Resource):
|
|||||||
desc(Document.position),
|
desc(Document.position),
|
||||||
)
|
)
|
||||||
|
|
||||||
paginated_documents = query.paginate(page=page, per_page=limit, max_per_page=100, error_out=False)
|
paginated_documents = db.paginate(select=query, page=page, per_page=limit, max_per_page=100, error_out=False)
|
||||||
documents = paginated_documents.items
|
documents = paginated_documents.items
|
||||||
if fetch:
|
if fetch:
|
||||||
for document in documents:
|
for document in documents:
|
||||||
completed_segments = DocumentSegment.query.filter(
|
completed_segments = (
|
||||||
DocumentSegment.completed_at.isnot(None),
|
db.session.query(DocumentSegment)
|
||||||
DocumentSegment.document_id == str(document.id),
|
.filter(
|
||||||
DocumentSegment.status != "re_segment",
|
DocumentSegment.completed_at.isnot(None),
|
||||||
).count()
|
DocumentSegment.document_id == str(document.id),
|
||||||
total_segments = DocumentSegment.query.filter(
|
DocumentSegment.status != "re_segment",
|
||||||
DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment"
|
)
|
||||||
).count()
|
.count()
|
||||||
|
)
|
||||||
|
total_segments = (
|
||||||
|
db.session.query(DocumentSegment)
|
||||||
|
.filter(DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment")
|
||||||
|
.count()
|
||||||
|
)
|
||||||
document.completed_segments = completed_segments
|
document.completed_segments = completed_segments
|
||||||
document.total_segments = total_segments
|
document.total_segments = total_segments
|
||||||
data = marshal(documents, document_with_segments_fields)
|
data = marshal(documents, document_with_segments_fields)
|
||||||
@ -563,14 +569,20 @@ class DocumentBatchIndexingStatusApi(DocumentResource):
|
|||||||
documents = self.get_batch_documents(dataset_id, batch)
|
documents = self.get_batch_documents(dataset_id, batch)
|
||||||
documents_status = []
|
documents_status = []
|
||||||
for document in documents:
|
for document in documents:
|
||||||
completed_segments = DocumentSegment.query.filter(
|
completed_segments = (
|
||||||
DocumentSegment.completed_at.isnot(None),
|
db.session.query(DocumentSegment)
|
||||||
DocumentSegment.document_id == str(document.id),
|
.filter(
|
||||||
DocumentSegment.status != "re_segment",
|
DocumentSegment.completed_at.isnot(None),
|
||||||
).count()
|
DocumentSegment.document_id == str(document.id),
|
||||||
total_segments = DocumentSegment.query.filter(
|
DocumentSegment.status != "re_segment",
|
||||||
DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment"
|
)
|
||||||
).count()
|
.count()
|
||||||
|
)
|
||||||
|
total_segments = (
|
||||||
|
db.session.query(DocumentSegment)
|
||||||
|
.filter(DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment")
|
||||||
|
.count()
|
||||||
|
)
|
||||||
document.completed_segments = completed_segments
|
document.completed_segments = completed_segments
|
||||||
document.total_segments = total_segments
|
document.total_segments = total_segments
|
||||||
if document.is_paused:
|
if document.is_paused:
|
||||||
@ -589,14 +601,20 @@ class DocumentIndexingStatusApi(DocumentResource):
|
|||||||
document_id = str(document_id)
|
document_id = str(document_id)
|
||||||
document = self.get_document(dataset_id, document_id)
|
document = self.get_document(dataset_id, document_id)
|
||||||
|
|
||||||
completed_segments = DocumentSegment.query.filter(
|
completed_segments = (
|
||||||
DocumentSegment.completed_at.isnot(None),
|
db.session.query(DocumentSegment)
|
||||||
DocumentSegment.document_id == str(document_id),
|
.filter(
|
||||||
DocumentSegment.status != "re_segment",
|
DocumentSegment.completed_at.isnot(None),
|
||||||
).count()
|
DocumentSegment.document_id == str(document_id),
|
||||||
total_segments = DocumentSegment.query.filter(
|
DocumentSegment.status != "re_segment",
|
||||||
DocumentSegment.document_id == str(document_id), DocumentSegment.status != "re_segment"
|
)
|
||||||
).count()
|
.count()
|
||||||
|
)
|
||||||
|
total_segments = (
|
||||||
|
db.session.query(DocumentSegment)
|
||||||
|
.filter(DocumentSegment.document_id == str(document_id), DocumentSegment.status != "re_segment")
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
|
||||||
document.completed_segments = completed_segments
|
document.completed_segments = completed_segments
|
||||||
document.total_segments = total_segments
|
document.total_segments = total_segments
|
||||||
|
@ -2,8 +2,9 @@ import uuid
|
|||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal, reqparse # type: ignore
|
from flask_restful import Resource, marshal, reqparse
|
||||||
|
from sqlalchemy import select
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
@ -26,6 +27,7 @@ from controllers.console.wraps import (
|
|||||||
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
|
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
|
||||||
from core.model_manager import ModelManager
|
from core.model_manager import ModelManager
|
||||||
from core.model_runtime.entities.model_entities import ModelType
|
from core.model_runtime.entities.model_entities import ModelType
|
||||||
|
from extensions.ext_database import db
|
||||||
from extensions.ext_redis import redis_client
|
from extensions.ext_redis import redis_client
|
||||||
from fields.segment_fields import child_chunk_fields, segment_fields
|
from fields.segment_fields import child_chunk_fields, segment_fields
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
@ -74,9 +76,14 @@ class DatasetDocumentSegmentListApi(Resource):
|
|||||||
hit_count_gte = args["hit_count_gte"]
|
hit_count_gte = args["hit_count_gte"]
|
||||||
keyword = args["keyword"]
|
keyword = args["keyword"]
|
||||||
|
|
||||||
query = DocumentSegment.query.filter(
|
query = (
|
||||||
DocumentSegment.document_id == str(document_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
select(DocumentSegment)
|
||||||
).order_by(DocumentSegment.position.asc())
|
.filter(
|
||||||
|
DocumentSegment.document_id == str(document_id),
|
||||||
|
DocumentSegment.tenant_id == current_user.current_tenant_id,
|
||||||
|
)
|
||||||
|
.order_by(DocumentSegment.position.asc())
|
||||||
|
)
|
||||||
|
|
||||||
if status_list:
|
if status_list:
|
||||||
query = query.filter(DocumentSegment.status.in_(status_list))
|
query = query.filter(DocumentSegment.status.in_(status_list))
|
||||||
@ -93,7 +100,7 @@ class DatasetDocumentSegmentListApi(Resource):
|
|||||||
elif args["enabled"].lower() == "false":
|
elif args["enabled"].lower() == "false":
|
||||||
query = query.filter(DocumentSegment.enabled == False)
|
query = query.filter(DocumentSegment.enabled == False)
|
||||||
|
|
||||||
segments = query.paginate(page=page, per_page=limit, max_per_page=100, error_out=False)
|
segments = db.paginate(select=query, page=page, per_page=limit, max_per_page=100, error_out=False)
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
"data": marshal(segments.items, segment_fields),
|
"data": marshal(segments.items, segment_fields),
|
||||||
@ -276,9 +283,11 @@ class DatasetDocumentSegmentUpdateApi(Resource):
|
|||||||
raise ProviderNotInitializeError(ex.description)
|
raise ProviderNotInitializeError(ex.description)
|
||||||
# check segment
|
# check segment
|
||||||
segment_id = str(segment_id)
|
segment_id = str(segment_id)
|
||||||
segment = DocumentSegment.query.filter(
|
segment = (
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
db.session.query(DocumentSegment)
|
||||||
).first()
|
.filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not segment:
|
if not segment:
|
||||||
raise NotFound("Segment not found.")
|
raise NotFound("Segment not found.")
|
||||||
# The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
|
# The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
|
||||||
@ -320,9 +329,11 @@ class DatasetDocumentSegmentUpdateApi(Resource):
|
|||||||
raise NotFound("Document not found.")
|
raise NotFound("Document not found.")
|
||||||
# check segment
|
# check segment
|
||||||
segment_id = str(segment_id)
|
segment_id = str(segment_id)
|
||||||
segment = DocumentSegment.query.filter(
|
segment = (
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
db.session.query(DocumentSegment)
|
||||||
).first()
|
.filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not segment:
|
if not segment:
|
||||||
raise NotFound("Segment not found.")
|
raise NotFound("Segment not found.")
|
||||||
# The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
|
# The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
|
||||||
@ -423,9 +434,11 @@ class ChildChunkAddApi(Resource):
|
|||||||
raise NotFound("Document not found.")
|
raise NotFound("Document not found.")
|
||||||
# check segment
|
# check segment
|
||||||
segment_id = str(segment_id)
|
segment_id = str(segment_id)
|
||||||
segment = DocumentSegment.query.filter(
|
segment = (
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
db.session.query(DocumentSegment)
|
||||||
).first()
|
.filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not segment:
|
if not segment:
|
||||||
raise NotFound("Segment not found.")
|
raise NotFound("Segment not found.")
|
||||||
if not current_user.is_dataset_editor:
|
if not current_user.is_dataset_editor:
|
||||||
@ -478,9 +491,11 @@ class ChildChunkAddApi(Resource):
|
|||||||
raise NotFound("Document not found.")
|
raise NotFound("Document not found.")
|
||||||
# check segment
|
# check segment
|
||||||
segment_id = str(segment_id)
|
segment_id = str(segment_id)
|
||||||
segment = DocumentSegment.query.filter(
|
segment = (
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
db.session.query(DocumentSegment)
|
||||||
).first()
|
.filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not segment:
|
if not segment:
|
||||||
raise NotFound("Segment not found.")
|
raise NotFound("Segment not found.")
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
@ -523,9 +538,11 @@ class ChildChunkAddApi(Resource):
|
|||||||
raise NotFound("Document not found.")
|
raise NotFound("Document not found.")
|
||||||
# check segment
|
# check segment
|
||||||
segment_id = str(segment_id)
|
segment_id = str(segment_id)
|
||||||
segment = DocumentSegment.query.filter(
|
segment = (
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
db.session.query(DocumentSegment)
|
||||||
).first()
|
.filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not segment:
|
if not segment:
|
||||||
raise NotFound("Segment not found.")
|
raise NotFound("Segment not found.")
|
||||||
# The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
|
# The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
|
||||||
@ -567,16 +584,20 @@ class ChildChunkUpdateApi(Resource):
|
|||||||
raise NotFound("Document not found.")
|
raise NotFound("Document not found.")
|
||||||
# check segment
|
# check segment
|
||||||
segment_id = str(segment_id)
|
segment_id = str(segment_id)
|
||||||
segment = DocumentSegment.query.filter(
|
segment = (
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
db.session.query(DocumentSegment)
|
||||||
).first()
|
.filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not segment:
|
if not segment:
|
||||||
raise NotFound("Segment not found.")
|
raise NotFound("Segment not found.")
|
||||||
# check child chunk
|
# check child chunk
|
||||||
child_chunk_id = str(child_chunk_id)
|
child_chunk_id = str(child_chunk_id)
|
||||||
child_chunk = ChildChunk.query.filter(
|
child_chunk = (
|
||||||
ChildChunk.id == str(child_chunk_id), ChildChunk.tenant_id == current_user.current_tenant_id
|
db.session.query(ChildChunk)
|
||||||
).first()
|
.filter(ChildChunk.id == str(child_chunk_id), ChildChunk.tenant_id == current_user.current_tenant_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not child_chunk:
|
if not child_chunk:
|
||||||
raise NotFound("Child chunk not found.")
|
raise NotFound("Child chunk not found.")
|
||||||
# The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
|
# The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
|
||||||
@ -612,16 +633,20 @@ class ChildChunkUpdateApi(Resource):
|
|||||||
raise NotFound("Document not found.")
|
raise NotFound("Document not found.")
|
||||||
# check segment
|
# check segment
|
||||||
segment_id = str(segment_id)
|
segment_id = str(segment_id)
|
||||||
segment = DocumentSegment.query.filter(
|
segment = (
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
db.session.query(DocumentSegment)
|
||||||
).first()
|
.filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not segment:
|
if not segment:
|
||||||
raise NotFound("Segment not found.")
|
raise NotFound("Segment not found.")
|
||||||
# check child chunk
|
# check child chunk
|
||||||
child_chunk_id = str(child_chunk_id)
|
child_chunk_id = str(child_chunk_id)
|
||||||
child_chunk = ChildChunk.query.filter(
|
child_chunk = (
|
||||||
ChildChunk.id == str(child_chunk_id), ChildChunk.tenant_id == current_user.current_tenant_id
|
db.session.query(ChildChunk)
|
||||||
).first()
|
.filter(ChildChunk.id == str(child_chunk_id), ChildChunk.tenant_id == current_user.current_tenant_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
if not child_chunk:
|
if not child_chunk:
|
||||||
raise NotFound("Child chunk not found.")
|
raise NotFound("Child chunk not found.")
|
||||||
# The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
|
# The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal, reqparse # type: ignore
|
from flask_restful import Resource, marshal, reqparse
|
||||||
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
@ -209,6 +209,7 @@ class ExternalKnowledgeHitTestingApi(Resource):
|
|||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("query", type=str, location="json")
|
parser.add_argument("query", type=str, location="json")
|
||||||
parser.add_argument("external_retrieval_model", type=dict, required=False, location="json")
|
parser.add_argument("external_retrieval_model", type=dict, required=False, location="json")
|
||||||
|
parser.add_argument("metadata_filtering_conditions", type=dict, required=False, location="json")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
HitTestingService.hit_testing_args_check(args)
|
HitTestingService.hit_testing_args_check(args)
|
||||||
@ -219,6 +220,7 @@ class ExternalKnowledgeHitTestingApi(Resource):
|
|||||||
query=args["query"],
|
query=args["query"],
|
||||||
account=current_user,
|
account=current_user,
|
||||||
external_retrieval_model=args["external_retrieval_model"],
|
external_retrieval_model=args["external_retrieval_model"],
|
||||||
|
metadata_filtering_conditions=args["metadata_filtering_conditions"],
|
||||||
)
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import Resource # type: ignore
|
from flask_restful import Resource
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.datasets.hit_testing_base import DatasetsHitTestingBase
|
from controllers.console.datasets.hit_testing_base import DatasetsHitTestingBase
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import marshal, reqparse # type: ignore
|
from flask_restful import marshal, reqparse
|
||||||
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
||||||
|
|
||||||
import services.dataset_service
|
import services.dataset_service
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.datasets.error import WebsiteCrawlError
|
from controllers.console.datasets.error import WebsiteCrawlError
|
||||||
|
@ -66,7 +66,7 @@ class ChatAudioApi(InstalledAppResource):
|
|||||||
|
|
||||||
class ChatTextApi(InstalledAppResource):
|
class ChatTextApi(InstalledAppResource):
|
||||||
def post(self, installed_app):
|
def post(self, installed_app):
|
||||||
from flask_restful import reqparse # type: ignore
|
from flask_restful import reqparse
|
||||||
|
|
||||||
app_model = installed_app.app
|
app_model = installed_app.app
|
||||||
try:
|
try:
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import reqparse # type: ignore
|
from flask_restful import reqparse
|
||||||
from werkzeug.exceptions import InternalServerError, NotFound
|
from werkzeug.exceptions import InternalServerError, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import marshal_with, reqparse # type: ignore
|
from flask_restful import marshal_with, reqparse
|
||||||
from flask_restful.inputs import int_range # type: ignore
|
from flask_restful.inputs import int_range
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ from datetime import UTC, datetime
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, inputs, marshal_with, reqparse
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
|
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ class InstalledAppsListApi(Resource):
|
|||||||
parser.add_argument("app_id", type=str, required=True, help="Invalid app_id")
|
parser.add_argument("app_id", type=str, required=True, help="Invalid app_id")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
recommended_app = RecommendedApp.query.filter(RecommendedApp.app_id == args["app_id"]).first()
|
recommended_app = db.session.query(RecommendedApp).filter(RecommendedApp.app_id == args["app_id"]).first()
|
||||||
if recommended_app is None:
|
if recommended_app is None:
|
||||||
raise NotFound("App not found")
|
raise NotFound("App not found")
|
||||||
|
|
||||||
@ -79,9 +79,11 @@ class InstalledAppsListApi(Resource):
|
|||||||
if not app.is_public:
|
if not app.is_public:
|
||||||
raise Forbidden("You can't install a non-public app")
|
raise Forbidden("You can't install a non-public app")
|
||||||
|
|
||||||
installed_app = InstalledApp.query.filter(
|
installed_app = (
|
||||||
and_(InstalledApp.app_id == args["app_id"], InstalledApp.tenant_id == current_tenant_id)
|
db.session.query(InstalledApp)
|
||||||
).first()
|
.filter(and_(InstalledApp.app_id == args["app_id"], InstalledApp.tenant_id == current_tenant_id))
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
if installed_app is None:
|
if installed_app is None:
|
||||||
# todo: position
|
# todo: position
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import marshal_with, reqparse # type: ignore
|
from flask_restful import marshal_with, reqparse
|
||||||
from flask_restful.inputs import int_range # type: ignore
|
from flask_restful.inputs import int_range
|
||||||
from werkzeug.exceptions import InternalServerError, NotFound
|
from werkzeug.exceptions import InternalServerError, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import marshal_with # type: ignore
|
from flask_restful import marshal_with
|
||||||
|
|
||||||
from controllers.common import fields
|
from controllers.common import fields
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, fields, marshal_with, reqparse
|
||||||
|
|
||||||
from constants.languages import languages
|
from constants.languages import languages
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import fields, marshal_with, reqparse # type: ignore
|
from flask_restful import fields, marshal_with, reqparse
|
||||||
from flask_restful.inputs import int_range # type: ignore
|
from flask_restful.inputs import int_range
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask_restful import reqparse # type: ignore
|
from flask_restful import reqparse
|
||||||
from werkzeug.exceptions import InternalServerError
|
from werkzeug.exceptions import InternalServerError
|
||||||
|
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource # type: ignore
|
from flask_restful import Resource
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from controllers.console.wraps import account_initialization_required
|
from controllers.console.wraps import account_initialization_required
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
|
|
||||||
from constants import HIDDEN_VALUE
|
from constants import HIDDEN_VALUE
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource # type: ignore
|
from flask_restful import Resource
|
||||||
|
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from services.feature_service import FeatureService
|
from services.feature_service import FeatureService
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with # type: ignore
|
from flask_restful import Resource, marshal_with
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
import services
|
import services
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from flask import session
|
from flask import session
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import Resource # type: ignore
|
from flask_restful import Resource
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ import urllib.parse
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
|
|
||||||
import services
|
import services
|
||||||
from controllers.common import helpers
|
from controllers.common import helpers
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from libs.helper import StrLen, email, extract_remote_ip
|
from libs.helper import StrLen, email, extract_remote_ip
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -2,7 +2,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from packaging import version
|
from packaging import version
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ import datetime
|
|||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, fields, marshal_with, reqparse
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from constants.languages import supported_language
|
from constants.languages import supported_language
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource # type: ignore
|
from flask_restful import Resource
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.wraps import account_initialization_required, setup_required
|
from controllers.console.wraps import account_initialization_required, setup_required
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from urllib import parse
|
from urllib import parse
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, abort, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, abort, marshal_with, reqparse
|
||||||
|
|
||||||
import services
|
import services
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
@ -71,7 +71,6 @@ class MemberInviteEmailApi(Resource):
|
|||||||
invitation_results.append(
|
invitation_results.append(
|
||||||
{"status": "success", "email": invitee_email, "url": f"{console_web_url}/signin"}
|
{"status": "success", "email": invitee_email, "url": f"{console_web_url}/signin"}
|
||||||
)
|
)
|
||||||
break
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
invitation_results.append({"status": "failed", "email": invitee_email, "message": str(e)})
|
invitation_results.append({"status": "failed", "email": invitee_email, "message": str(e)})
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import io
|
import io
|
||||||
|
|
||||||
from flask import send_file
|
from flask import send_file
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import io
|
import io
|
||||||
|
|
||||||
from flask import request, send_file
|
from flask import request, send_file
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import io
|
import io
|
||||||
|
|
||||||
from flask import send_file
|
from flask import send_file
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, fields, inputs, marshal, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, fields, inputs, marshal, marshal_with, reqparse
|
||||||
|
from sqlalchemy import select
|
||||||
from werkzeug.exceptions import Unauthorized
|
from werkzeug.exceptions import Unauthorized
|
||||||
|
|
||||||
import services
|
import services
|
||||||
@ -88,9 +89,8 @@ class WorkspaceListApi(Resource):
|
|||||||
parser.add_argument("limit", type=inputs.int_range(1, 100), required=False, default=20, location="args")
|
parser.add_argument("limit", type=inputs.int_range(1, 100), required=False, default=20, location="args")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
tenants = Tenant.query.order_by(Tenant.created_at.desc()).paginate(
|
stmt = select(Tenant).order_by(Tenant.created_at.desc())
|
||||||
page=args["page"], per_page=args["limit"], error_out=False
|
tenants = db.paginate(select=stmt, page=args["page"], per_page=args["limit"], error_out=False)
|
||||||
)
|
|
||||||
has_more = False
|
has_more = False
|
||||||
|
|
||||||
if tenants.has_next:
|
if tenants.has_next:
|
||||||
@ -162,7 +162,7 @@ class CustomConfigWorkspaceApi(Resource):
|
|||||||
parser.add_argument("replace_webapp_logo", type=str, location="json")
|
parser.add_argument("replace_webapp_logo", type=str, location="json")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
tenant = Tenant.query.filter(Tenant.id == current_user.current_tenant_id).one_or_404()
|
tenant = db.get_or_404(Tenant, current_user.current_tenant_id)
|
||||||
|
|
||||||
custom_config_dict = {
|
custom_config_dict = {
|
||||||
"remove_webapp_brand": args["remove_webapp_brand"],
|
"remove_webapp_brand": args["remove_webapp_brand"],
|
||||||
@ -226,7 +226,7 @@ class WorkspaceInfoApi(Resource):
|
|||||||
parser.add_argument("name", type=str, required=True, location="json")
|
parser.add_argument("name", type=str, required=True, location="json")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
tenant = Tenant.query.filter(Tenant.id == current_user.current_tenant_id).one_or_404()
|
tenant = db.get_or_404(Tenant, current_user.current_tenant_id)
|
||||||
tenant.name = args["name"]
|
tenant.name = args["name"]
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import time
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from flask import abort, request
|
from flask import abort, request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.console.workspace.error import AccountNotInitializedError
|
from controllers.console.workspace.error import AccountNotInitializedError
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
from flask import Response, request
|
from flask import Response, request
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
@ -70,12 +70,26 @@ class FilePreviewApi(Resource):
|
|||||||
direct_passthrough=True,
|
direct_passthrough=True,
|
||||||
headers={},
|
headers={},
|
||||||
)
|
)
|
||||||
|
# add Accept-Ranges header for audio/video files
|
||||||
|
if upload_file.mime_type in [
|
||||||
|
"audio/mpeg",
|
||||||
|
"audio/wav",
|
||||||
|
"audio/mp4",
|
||||||
|
"audio/ogg",
|
||||||
|
"audio/flac",
|
||||||
|
"audio/aac",
|
||||||
|
"video/mp4",
|
||||||
|
"video/webm",
|
||||||
|
"video/quicktime",
|
||||||
|
"audio/x-m4a",
|
||||||
|
]:
|
||||||
|
response.headers["Accept-Ranges"] = "bytes"
|
||||||
if upload_file.size > 0:
|
if upload_file.size > 0:
|
||||||
response.headers["Content-Length"] = str(upload_file.size)
|
response.headers["Content-Length"] = str(upload_file.size)
|
||||||
if args["as_attachment"]:
|
if args["as_attachment"]:
|
||||||
encoded_filename = quote(upload_file.name)
|
encoded_filename = quote(upload_file.name)
|
||||||
response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}"
|
response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||||
response.headers["Content-Type"] = "application/octet-stream"
|
response.headers["Content-Type"] = "application/octet-stream"
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
from flask import Response
|
from flask import Response
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
from controllers.files import api
|
from controllers.files import api
|
||||||
from controllers.files.error import UnsupportedFileTypeError
|
from controllers.files.error import UnsupportedFileTypeError
|
||||||
|
from core.tools.signature import verify_tool_file_signature
|
||||||
from core.tools.tool_file_manager import ToolFileManager
|
from core.tools.tool_file_manager import ToolFileManager
|
||||||
|
from models import db as global_db
|
||||||
|
|
||||||
|
|
||||||
class ToolFilePreviewApi(Resource):
|
class ToolFilePreviewApi(Resource):
|
||||||
@ -19,17 +23,14 @@ class ToolFilePreviewApi(Resource):
|
|||||||
parser.add_argument("as_attachment", type=bool, required=False, default=False, location="args")
|
parser.add_argument("as_attachment", type=bool, required=False, default=False, location="args")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
if not verify_tool_file_signature(
|
||||||
if not ToolFileManager.verify_file(
|
file_id=file_id, timestamp=args["timestamp"], nonce=args["nonce"], sign=args["sign"]
|
||||||
file_id=file_id,
|
|
||||||
timestamp=args["timestamp"],
|
|
||||||
nonce=args["nonce"],
|
|
||||||
sign=args["sign"],
|
|
||||||
):
|
):
|
||||||
raise Forbidden("Invalid request.")
|
raise Forbidden("Invalid request.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stream, tool_file = ToolFileManager.get_file_generator_by_tool_file_id(
|
tool_file_manager = ToolFileManager(engine=global_db.engine)
|
||||||
|
stream, tool_file = tool_file_manager.get_file_generator_by_tool_file_id(
|
||||||
file_id,
|
file_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,7 +48,8 @@ class ToolFilePreviewApi(Resource):
|
|||||||
if tool_file.size > 0:
|
if tool_file.size > 0:
|
||||||
response.headers["Content-Length"] = str(tool_file.size)
|
response.headers["Content-Length"] = str(tool_file.size)
|
||||||
if args["as_attachment"]:
|
if args["as_attachment"]:
|
||||||
response.headers["Content-Disposition"] = f"attachment; filename={tool_file.name}"
|
encoded_filename = quote(tool_file.name)
|
||||||
|
response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user