mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-18 19:55:56 +08:00
merge main
This commit is contained in:
commit
6d31e268e9
@ -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
|
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
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
本指南和 Dify 一样在不断完善中。如果有任何滞后于项目实际情况的地方,恳请谅解,我们也欢迎任何改进建议。
|
本指南和 Dify 一样在不断完善中。如果有任何滞后于项目实际情况的地方,恳请谅解,我们也欢迎任何改进建议。
|
||||||
|
|
||||||
关于许可证,请花一分钟阅读我们简短的[许可和贡献者协议](./LICENSE)。社区同时也遵循[行为准则](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
|
关于许可证,请花一分钟阅读我们简短的[许可和贡献者协议](./LICENSE)。同时也请遵循社区[行为准则](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)。
|
||||||
|
|
||||||
## 开始之前
|
## 开始之前
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<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">Self-hosting</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Self-hosting</a> ·
|
||||||
<a href="https://docs.dify.ai">Documentation</a> ·
|
<a href="https://docs.dify.ai">Documentation</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">Enterprise inquiry</a>
|
<a href="https://dify.ai/pricing">Dify edition overview</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@ -54,7 +54,7 @@
|
|||||||
<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 is an open-source LLM app development platform. Its intuitive interface combines agentic AI workflow, RAG pipeline, agent capabilities, model management, observability features and more, letting you quickly go from prototype to production.
|
Dify is an open-source LLM app development platform. Its intuitive interface combines agentic AI workflow, RAG pipeline, agent capabilities, model management, observability features, and more, allowing you to quickly move from prototype to production.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ All of Dify's offerings come with corresponding APIs, so you could effortlessly
|
|||||||
|
|
||||||
- **Dify for enterprise / organizations</br>**
|
- **Dify for enterprise / organizations</br>**
|
||||||
We provide additional enterprise-centric features. [Log your questions for us through this chatbot](https://udify.app/chat/22L1zSxg6yW1cWQg) or [send us an email](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry) to discuss enterprise needs. </br>
|
We provide additional enterprise-centric features. [Log your questions for us through this chatbot](https://udify.app/chat/22L1zSxg6yW1cWQg) or [send us an email](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry) to discuss enterprise needs. </br>
|
||||||
> For startups and small businesses using AWS, check out [Dify Premium on AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6) and deploy it to your own AWS VPC with one-click. It's an affordable AMI offering with the option to create apps with custom logo and branding.
|
> For startups and small businesses using AWS, check out [Dify Premium on AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6) and deploy it to your own AWS VPC with one click. It's an affordable AMI offering with the option to create apps with custom logo and branding.
|
||||||
|
|
||||||
## Staying ahead
|
## Staying ahead
|
||||||
|
|
||||||
@ -233,7 +233,7 @@ Deploy Dify to AWS with [CDK](https://aws.amazon.com/cdk/)
|
|||||||
For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
At the same time, please consider supporting Dify by sharing it on social media and at events and conferences.
|
At the same time, please consider supporting Dify by sharing it on social media and at events and conferences.
|
||||||
|
|
||||||
> We are looking for contributors to help with translating Dify to languages other than Mandarin or English. If you are interested in helping, please see the [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) for more information, and leave us a comment in the `global-users` channel of our [Discord Community Server](https://discord.gg/8Tpq4AcN9c).
|
> We are looking for contributors to help translate Dify into languages other than Mandarin or English. If you are interested in helping, please see the [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) for more information, and leave us a comment in the `global-users` channel of our [Discord Community Server](https://discord.gg/8Tpq4AcN9c).
|
||||||
|
|
||||||
## Community & contact
|
## Community & contact
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<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">الاستضافة الذاتية</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">الاستضافة الذاتية</a> ·
|
||||||
<a href="https://docs.dify.ai">التوثيق</a> ·
|
<a href="https://docs.dify.ai">التوثيق</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">استفسار الشركات (للإنجليزية فقط)</a>
|
<a href="https://dify.ai/pricing">نظرة عامة على منتجات Dify</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<a href="https://cloud.dify.ai">ডিফাই ক্লাউড</a> ·
|
<a href="https://cloud.dify.ai">ডিফাই ক্লাউড</a> ·
|
||||||
<a href="https://docs.dify.ai/getting-started/install-self-hosted">সেল্ফ-হোস্টিং</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">সেল্ফ-হোস্টিং</a> ·
|
||||||
<a href="https://docs.dify.ai">ডকুমেন্টেশন</a> ·
|
<a href="https://docs.dify.ai">ডকুমেন্টেশন</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">ব্যাবসায়িক অনুসন্ধান</a>
|
<a href="https://dify.ai/pricing">Dify পণ্যের রূপভেদ</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<a href="https://cloud.dify.ai">Dify 云服务</a> ·
|
<a href="https://cloud.dify.ai">Dify 云服务</a> ·
|
||||||
<a href="https://docs.dify.ai/getting-started/install-self-hosted">自托管</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">自托管</a> ·
|
||||||
<a href="https://docs.dify.ai">文档</a> ·
|
<a href="https://docs.dify.ai">文档</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">(需用英文)常见问题解答 / 联系团队</a>
|
<a href="https://dify.ai/pricing">Dify 产品形态总览</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<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">Selbstgehostetes</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Selbstgehostetes</a> ·
|
||||||
<a href="https://docs.dify.ai">Dokumentation</a> ·
|
<a href="https://docs.dify.ai">Dokumentation</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">Anfrage an Unternehmen</a>
|
<a href="https://dify.ai/pricing">Überblick über die Dify-Produkte</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<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">Auto-alojamiento</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Auto-alojamiento</a> ·
|
||||||
<a href="https://docs.dify.ai">Documentación</a> ·
|
<a href="https://docs.dify.ai">Documentación</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">Consultas empresariales (en inglés)</a>
|
<a href="https://dify.ai/pricing">Resumen de las ediciones de Dify</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<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">Auto-hébergement</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Auto-hébergement</a> ·
|
||||||
<a href="https://docs.dify.ai">Documentation</a> ·
|
<a href="https://docs.dify.ai">Documentation</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">Demande d’entreprise (en anglais seulement)</a>
|
<a href="https://dify.ai/pricing">Présentation des différentes offres Dify</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<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">セルフホスティング</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">セルフホスティング</a> ·
|
||||||
<a href="https://docs.dify.ai">ドキュメント</a> ·
|
<a href="https://docs.dify.ai">ドキュメント</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">企業のお問い合わせ(英語のみ)</a>
|
<a href="https://dify.ai/pricing">Difyの各種エディションについて</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<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">Self-hosting</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Self-hosting</a> ·
|
||||||
<a href="https://docs.dify.ai">Documentation</a> ·
|
<a href="https://docs.dify.ai">Documentation</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">Commercial enquiries</a>
|
<a href="https://dify.ai/pricing">Dify product editions</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<a href="https://cloud.dify.ai">Dify 클라우드</a> ·
|
<a href="https://cloud.dify.ai">Dify 클라우드</a> ·
|
||||||
<a href="https://docs.dify.ai/getting-started/install-self-hosted">셀프-호스팅</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">셀프-호스팅</a> ·
|
||||||
<a href="https://docs.dify.ai">문서</a> ·
|
<a href="https://docs.dify.ai">문서</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">기업 문의 (영어만 가능)</a>
|
<a href="https://dify.ai/pricing">Dify 제품 에디션 안내</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<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">Auto-hospedagem</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Auto-hospedagem</a> ·
|
||||||
<a href="https://docs.dify.ai">Documentação</a> ·
|
<a href="https://docs.dify.ai">Documentação</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">Consultas empresariais</a>
|
<a href="https://dify.ai/pricing">Visão geral das edições do Dify</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<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://udify.app/chat/22L1zSxg6yW1cWQg">Povpraševanje za podjetja</a>
|
<a href="https://dify.ai/pricing">Pregled ponudb izdelkov Dify</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<a href="https://cloud.dify.ai">Dify Bulut</a> ·
|
<a href="https://cloud.dify.ai">Dify Bulut</a> ·
|
||||||
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Kendi Sunucunuzda Barındırma</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Kendi Sunucunuzda Barındırma</a> ·
|
||||||
<a href="https://docs.dify.ai">Dokümantasyon</a> ·
|
<a href="https://docs.dify.ai">Dokümantasyon</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">Yalnızca İngilizce: Kurumsal Sorgulama</a>
|
<a href="https://dify.ai/pricing">Dify ürün seçeneklerine genel bakış</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<a href="https://cloud.dify.ai">Dify 雲端服務</a> ·
|
<a href="https://cloud.dify.ai">Dify 雲端服務</a> ·
|
||||||
<a href="https://docs.dify.ai/getting-started/install-self-hosted">自行託管</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">自行託管</a> ·
|
||||||
<a href="https://docs.dify.ai">說明文件</a> ·
|
<a href="https://docs.dify.ai">說明文件</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">企業諮詢</a>
|
<a href="https://dify.ai/pricing">產品方案概覽</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<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">Tự triển khai</a> ·
|
<a href="https://docs.dify.ai/getting-started/install-self-hosted">Tự triển khai</a> ·
|
||||||
<a href="https://docs.dify.ai">Tài liệu</a> ·
|
<a href="https://docs.dify.ai">Tài liệu</a> ·
|
||||||
<a href="https://udify.app/chat/22L1zSxg6yW1cWQg">Yêu cầu doanh nghiệp</a>
|
<a href="https://dify.ai/pricing">Tổng quan các lựa chọn sản phẩm Dify</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -297,6 +297,7 @@ LINDORM_URL=http://ld-*******************-proxy-search-pub.lindorm.aliyuncs.com:
|
|||||||
LINDORM_USERNAME=admin
|
LINDORM_USERNAME=admin
|
||||||
LINDORM_PASSWORD=admin
|
LINDORM_PASSWORD=admin
|
||||||
USING_UGC_INDEX=False
|
USING_UGC_INDEX=False
|
||||||
|
LINDORM_QUERY_TIMEOUT=1
|
||||||
|
|
||||||
# OceanBase Vector configuration
|
# OceanBase Vector configuration
|
||||||
OCEANBASE_VECTOR_HOST=127.0.0.1
|
OCEANBASE_VECTOR_HOST=127.0.0.1
|
||||||
@ -483,3 +484,6 @@ OTEL_MAX_EXPORT_BATCH_SIZE=512
|
|||||||
OTEL_METRIC_EXPORT_INTERVAL=60000
|
OTEL_METRIC_EXPORT_INTERVAL=60000
|
||||||
OTEL_BATCH_EXPORT_TIMEOUT=10000
|
OTEL_BATCH_EXPORT_TIMEOUT=10000
|
||||||
OTEL_METRIC_EXPORT_TIMEOUT=30000
|
OTEL_METRIC_EXPORT_TIMEOUT=30000
|
||||||
|
|
||||||
|
# Prevent Clickjacking
|
||||||
|
ALLOW_EMBED=false
|
||||||
|
@ -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()
|
||||||
|
@ -54,7 +54,6 @@ def initialize_extensions(app: DifyApp):
|
|||||||
ext_otel,
|
ext_otel,
|
||||||
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,
|
||||||
@ -75,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,
|
||||||
|
332
api/commands.py
332
api/commands.py
@ -17,6 +17,7 @@ from core.rag.models.document import Document
|
|||||||
from events.app_event import app_was_created
|
from events.app_event import app_was_created
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from extensions.ext_redis import redis_client
|
from extensions.ext_redis import redis_client
|
||||||
|
from extensions.ext_storage import storage
|
||||||
from libs.helper import email as email_validate
|
from libs.helper import email as email_validate
|
||||||
from libs.password import hash_password, password_pattern, valid_password
|
from libs.password import hash_password, password_pattern, valid_password
|
||||||
from libs.rsa import generate_key_pair
|
from libs.rsa import generate_key_pair
|
||||||
@ -271,6 +272,7 @@ def migrate_knowledge_vector_database():
|
|||||||
upper_collection_vector_types = {
|
upper_collection_vector_types = {
|
||||||
VectorType.MILVUS,
|
VectorType.MILVUS,
|
||||||
VectorType.PGVECTOR,
|
VectorType.PGVECTOR,
|
||||||
|
VectorType.VASTBASE,
|
||||||
VectorType.RELYT,
|
VectorType.RELYT,
|
||||||
VectorType.WEAVIATE,
|
VectorType.WEAVIATE,
|
||||||
VectorType.ORACLE,
|
VectorType.ORACLE,
|
||||||
@ -666,7 +668,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()
|
||||||
|
|
||||||
@ -814,3 +816,331 @@ def clear_free_plan_tenant_expired_logs(days: int, batch: int, tenant_ids: list[
|
|||||||
ClearFreePlanTenantExpiredLogs.process(days, batch, tenant_ids)
|
ClearFreePlanTenantExpiredLogs.process(days, batch, tenant_ids)
|
||||||
|
|
||||||
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.")
|
||||||
|
def clear_orphaned_file_records(force: bool):
|
||||||
|
"""
|
||||||
|
Clear orphaned file records in the database.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# define tables and columns to process
|
||||||
|
files_tables = [
|
||||||
|
{"table": "upload_files", "id_column": "id", "key_column": "key"},
|
||||||
|
{"table": "tool_files", "id_column": "id", "key_column": "file_key"},
|
||||||
|
]
|
||||||
|
ids_tables = [
|
||||||
|
{"type": "uuid", "table": "message_files", "column": "upload_file_id"},
|
||||||
|
{"type": "text", "table": "documents", "column": "data_source_info"},
|
||||||
|
{"type": "text", "table": "document_segments", "column": "content"},
|
||||||
|
{"type": "text", "table": "messages", "column": "answer"},
|
||||||
|
{"type": "text", "table": "workflow_node_executions", "column": "inputs"},
|
||||||
|
{"type": "text", "table": "workflow_node_executions", "column": "process_data"},
|
||||||
|
{"type": "text", "table": "workflow_node_executions", "column": "outputs"},
|
||||||
|
{"type": "text", "table": "conversations", "column": "introduction"},
|
||||||
|
{"type": "text", "table": "conversations", "column": "system_instruction"},
|
||||||
|
{"type": "json", "table": "messages", "column": "inputs"},
|
||||||
|
{"type": "json", "table": "messages", "column": "message"},
|
||||||
|
]
|
||||||
|
|
||||||
|
# notify user and ask for confirmation
|
||||||
|
click.echo(
|
||||||
|
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:
|
||||||
|
click.echo(click.style(f"- {files_table['table']}", fg="yellow"))
|
||||||
|
click.echo(
|
||||||
|
click.style("The following tables and columns will be scanned to find orphaned file records:", fg="yellow")
|
||||||
|
)
|
||||||
|
for ids_table in ids_tables:
|
||||||
|
click.echo(click.style(f"- {ids_table['table']} ({ids_table['column']})", fg="yellow"))
|
||||||
|
click.echo("")
|
||||||
|
|
||||||
|
click.echo(click.style("!!! USE WITH CAUTION !!!", fg="red"))
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
(
|
||||||
|
"Since not all patterns have been fully tested, "
|
||||||
|
"please note that this command may delete unintended file records."
|
||||||
|
),
|
||||||
|
fg="yellow",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
click.echo(
|
||||||
|
click.style("This cannot be undone. Please make sure to back up your database before proceeding.", fg="yellow")
|
||||||
|
)
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
(
|
||||||
|
"It is also recommended to run this during the maintenance window, "
|
||||||
|
"as this may cause high load on your instance."
|
||||||
|
),
|
||||||
|
fg="yellow",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not force:
|
||||||
|
click.confirm("Do you want to proceed?", abort=True)
|
||||||
|
|
||||||
|
# start the cleanup process
|
||||||
|
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:
|
||||||
|
# fetch file id and keys from each table
|
||||||
|
all_files_in_tables = []
|
||||||
|
for files_table in files_tables:
|
||||||
|
click.echo(click.style(f"- Listing file records in table {files_table['table']}", fg="white"))
|
||||||
|
query = f"SELECT {files_table['id_column']}, {files_table['key_column']} FROM {files_table['table']}"
|
||||||
|
with db.engine.begin() as conn:
|
||||||
|
rs = conn.execute(db.text(query))
|
||||||
|
for i in rs:
|
||||||
|
all_files_in_tables.append({"table": files_table["table"], "id": str(i[0]), "key": i[1]})
|
||||||
|
click.echo(click.style(f"Found {len(all_files_in_tables)} files in tables.", fg="white"))
|
||||||
|
|
||||||
|
# fetch referred table and columns
|
||||||
|
guid_regexp = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
|
||||||
|
all_ids_in_tables = []
|
||||||
|
for ids_table in ids_tables:
|
||||||
|
query = ""
|
||||||
|
if ids_table["type"] == "uuid":
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
f"- Listing file ids in column {ids_table['column']} in table {ids_table['table']}", fg="white"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
query = (
|
||||||
|
f"SELECT {ids_table['column']} FROM {ids_table['table']} WHERE {ids_table['column']} IS NOT NULL"
|
||||||
|
)
|
||||||
|
with db.engine.begin() as conn:
|
||||||
|
rs = conn.execute(db.text(query))
|
||||||
|
for i in rs:
|
||||||
|
all_ids_in_tables.append({"table": ids_table["table"], "id": str(i[0])})
|
||||||
|
elif ids_table["type"] == "text":
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
f"- Listing file-id-like strings in column {ids_table['column']} in table {ids_table['table']}",
|
||||||
|
fg="white",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
query = (
|
||||||
|
f"SELECT regexp_matches({ids_table['column']}, '{guid_regexp}', 'g') AS extracted_id "
|
||||||
|
f"FROM {ids_table['table']}"
|
||||||
|
)
|
||||||
|
with db.engine.begin() as conn:
|
||||||
|
rs = conn.execute(db.text(query))
|
||||||
|
for i in rs:
|
||||||
|
for j in i[0]:
|
||||||
|
all_ids_in_tables.append({"table": ids_table["table"], "id": j})
|
||||||
|
elif ids_table["type"] == "json":
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
(
|
||||||
|
f"- Listing file-id-like JSON string in column {ids_table['column']} "
|
||||||
|
f"in table {ids_table['table']}"
|
||||||
|
),
|
||||||
|
fg="white",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
query = (
|
||||||
|
f"SELECT regexp_matches({ids_table['column']}::text, '{guid_regexp}', 'g') AS extracted_id "
|
||||||
|
f"FROM {ids_table['table']}"
|
||||||
|
)
|
||||||
|
with db.engine.begin() as conn:
|
||||||
|
rs = conn.execute(db.text(query))
|
||||||
|
for i in rs:
|
||||||
|
for j in i[0]:
|
||||||
|
all_ids_in_tables.append({"table": ids_table["table"], "id": j})
|
||||||
|
click.echo(click.style(f"Found {len(all_ids_in_tables)} file ids in tables.", fg="white"))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
click.echo(click.style(f"Error fetching keys: {str(e)}", fg="red"))
|
||||||
|
return
|
||||||
|
|
||||||
|
# find orphaned files
|
||||||
|
all_files = [file["id"] for file in all_files_in_tables]
|
||||||
|
all_ids = [file["id"] for file in all_ids_in_tables]
|
||||||
|
orphaned_files = list(set(all_files) - set(all_ids))
|
||||||
|
if not orphaned_files:
|
||||||
|
click.echo(click.style("No orphaned file records found. There is nothing to delete.", fg="green"))
|
||||||
|
return
|
||||||
|
click.echo(click.style(f"Found {len(orphaned_files)} orphaned file records.", fg="white"))
|
||||||
|
for file in orphaned_files:
|
||||||
|
click.echo(click.style(f"- orphaned file id: {file}", fg="black"))
|
||||||
|
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
|
||||||
|
try:
|
||||||
|
for files_table in files_tables:
|
||||||
|
click.echo(click.style(f"- Deleting orphaned file records in table {files_table['table']}", fg="white"))
|
||||||
|
query = f"DELETE FROM {files_table['table']} WHERE {files_table['id_column']} IN :ids"
|
||||||
|
with db.engine.begin() as conn:
|
||||||
|
conn.execute(db.text(query), {"ids": tuple(orphaned_files)})
|
||||||
|
except Exception as e:
|
||||||
|
click.echo(click.style(f"Error deleting orphaned file records: {str(e)}", fg="red"))
|
||||||
|
return
|
||||||
|
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.")
|
||||||
|
def remove_orphaned_files_on_storage(force: bool):
|
||||||
|
"""
|
||||||
|
Remove orphaned files on the storage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# define tables and columns to process
|
||||||
|
files_tables = [
|
||||||
|
{"table": "upload_files", "key_column": "key"},
|
||||||
|
{"table": "tool_files", "key_column": "file_key"},
|
||||||
|
]
|
||||||
|
storage_paths = ["image_files", "tools", "upload_files"]
|
||||||
|
|
||||||
|
# notify user and ask for confirmation
|
||||||
|
click.echo(click.style("This command will find and remove orphaned files on the storage,", fg="yellow"))
|
||||||
|
click.echo(
|
||||||
|
click.style("by comparing the files on the storage with the records in the following tables:", fg="yellow")
|
||||||
|
)
|
||||||
|
for files_table in files_tables:
|
||||||
|
click.echo(click.style(f"- {files_table['table']}", fg="yellow"))
|
||||||
|
click.echo(click.style("The following paths on the storage will be scanned to find orphaned files:", fg="yellow"))
|
||||||
|
for storage_path in storage_paths:
|
||||||
|
click.echo(click.style(f"- {storage_path}", fg="yellow"))
|
||||||
|
click.echo("")
|
||||||
|
|
||||||
|
click.echo(click.style("!!! USE WITH CAUTION !!!", fg="red"))
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
"Currently, this command will work only for opendal based storage (STORAGE_TYPE=opendal).", fg="yellow"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
"Since not all patterns have been fully tested, please note that this command may delete unintended files.",
|
||||||
|
fg="yellow",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
click.echo(
|
||||||
|
click.style("This cannot be undone. Please make sure to back up your storage before proceeding.", fg="yellow")
|
||||||
|
)
|
||||||
|
click.echo(
|
||||||
|
click.style(
|
||||||
|
(
|
||||||
|
"It is also recommended to run this during the maintenance window, "
|
||||||
|
"as this may cause high load on your instance."
|
||||||
|
),
|
||||||
|
fg="yellow",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not force:
|
||||||
|
click.confirm("Do you want to proceed?", abort=True)
|
||||||
|
|
||||||
|
# start the cleanup process
|
||||||
|
click.echo(click.style("Starting orphaned files cleanup.", fg="white"))
|
||||||
|
|
||||||
|
# fetch file id and keys from each table
|
||||||
|
all_files_in_tables = []
|
||||||
|
try:
|
||||||
|
for files_table in files_tables:
|
||||||
|
click.echo(click.style(f"- Listing files from table {files_table['table']}", fg="white"))
|
||||||
|
query = f"SELECT {files_table['key_column']} FROM {files_table['table']}"
|
||||||
|
with db.engine.begin() as conn:
|
||||||
|
rs = conn.execute(db.text(query))
|
||||||
|
for i in rs:
|
||||||
|
all_files_in_tables.append(str(i[0]))
|
||||||
|
click.echo(click.style(f"Found {len(all_files_in_tables)} files in tables.", fg="white"))
|
||||||
|
except Exception as e:
|
||||||
|
click.echo(click.style(f"Error fetching keys: {str(e)}", fg="red"))
|
||||||
|
|
||||||
|
all_files_on_storage = []
|
||||||
|
for storage_path in storage_paths:
|
||||||
|
try:
|
||||||
|
click.echo(click.style(f"- Scanning files on storage path {storage_path}", fg="white"))
|
||||||
|
files = storage.scan(path=storage_path, files=True, directories=False)
|
||||||
|
all_files_on_storage.extend(files)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
click.echo(click.style(f" -> Skipping path {storage_path} as it does not exist.", fg="yellow"))
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
click.echo(click.style(f" -> Error scanning files on storage path {storage_path}: {str(e)}", fg="red"))
|
||||||
|
continue
|
||||||
|
click.echo(click.style(f"Found {len(all_files_on_storage)} files on storage.", fg="white"))
|
||||||
|
|
||||||
|
# find orphaned files
|
||||||
|
orphaned_files = list(set(all_files_on_storage) - set(all_files_in_tables))
|
||||||
|
if not orphaned_files:
|
||||||
|
click.echo(click.style("No orphaned files found. There is nothing to remove.", fg="green"))
|
||||||
|
return
|
||||||
|
click.echo(click.style(f"Found {len(orphaned_files)} orphaned files.", fg="white"))
|
||||||
|
for file in orphaned_files:
|
||||||
|
click.echo(click.style(f"- orphaned file: {file}", fg="black"))
|
||||||
|
if not force:
|
||||||
|
click.confirm(f"Do you want to proceed to remove all {len(orphaned_files)} orphaned files?", abort=True)
|
||||||
|
|
||||||
|
# delete orphaned files
|
||||||
|
removed_files = 0
|
||||||
|
error_files = 0
|
||||||
|
for file in orphaned_files:
|
||||||
|
try:
|
||||||
|
storage.delete(file)
|
||||||
|
removed_files += 1
|
||||||
|
click.echo(click.style(f"- Removing orphaned file: {file}", fg="white"))
|
||||||
|
except Exception as e:
|
||||||
|
error_files += 1
|
||||||
|
click.echo(click.style(f"- Error deleting orphaned file {file}: {str(e)}", fg="red"))
|
||||||
|
continue
|
||||||
|
if error_files == 0:
|
||||||
|
click.echo(click.style(f"Removed {removed_files} orphaned files without errors.", fg="green"))
|
||||||
|
else:
|
||||||
|
click.echo(click.style(f"Removed {removed_files} orphaned files, with {error_files} errors.", fg="yellow"))
|
||||||
|
@ -13,6 +13,7 @@ from .observability import ObservabilityConfig
|
|||||||
from .packaging import PackagingInfo
|
from .packaging import PackagingInfo
|
||||||
from .remote_settings_sources import RemoteSettingsSource, RemoteSettingsSourceConfig, RemoteSettingsSourceName
|
from .remote_settings_sources import RemoteSettingsSource, RemoteSettingsSourceConfig, RemoteSettingsSourceName
|
||||||
from .remote_settings_sources.apollo import ApolloSettingsSource
|
from .remote_settings_sources.apollo import ApolloSettingsSource
|
||||||
|
from .remote_settings_sources.nacos import NacosSettingsSource
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -34,6 +35,8 @@ class RemoteSettingsSourceFactory(PydanticBaseSettingsSource):
|
|||||||
match remote_source_name:
|
match remote_source_name:
|
||||||
case RemoteSettingsSourceName.APOLLO:
|
case RemoteSettingsSourceName.APOLLO:
|
||||||
remote_source = ApolloSettingsSource(current_state)
|
remote_source = ApolloSettingsSource(current_state)
|
||||||
|
case RemoteSettingsSourceName.NACOS:
|
||||||
|
remote_source = NacosSettingsSource(current_state)
|
||||||
case _:
|
case _:
|
||||||
logger.warning(f"Unsupported remote source: {remote_source_name}")
|
logger.warning(f"Unsupported remote source: {remote_source_name}")
|
||||||
return {}
|
return {}
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -22,6 +22,7 @@ from .vdb.baidu_vector_config import BaiduVectorDBConfig
|
|||||||
from .vdb.chroma_config import ChromaConfig
|
from .vdb.chroma_config import ChromaConfig
|
||||||
from .vdb.couchbase_config import CouchbaseConfig
|
from .vdb.couchbase_config import CouchbaseConfig
|
||||||
from .vdb.elasticsearch_config import ElasticsearchConfig
|
from .vdb.elasticsearch_config import ElasticsearchConfig
|
||||||
|
from .vdb.huawei_cloud_config import HuaweiCloudConfig
|
||||||
from .vdb.lindorm_config import LindormConfig
|
from .vdb.lindorm_config import LindormConfig
|
||||||
from .vdb.milvus_config import MilvusConfig
|
from .vdb.milvus_config import MilvusConfig
|
||||||
from .vdb.myscale_config import MyScaleConfig
|
from .vdb.myscale_config import MyScaleConfig
|
||||||
@ -38,6 +39,7 @@ from .vdb.tencent_vector_config import TencentVectorDBConfig
|
|||||||
from .vdb.tidb_on_qdrant_config import TidbOnQdrantConfig
|
from .vdb.tidb_on_qdrant_config import TidbOnQdrantConfig
|
||||||
from .vdb.tidb_vector_config import TiDBVectorConfig
|
from .vdb.tidb_vector_config import TiDBVectorConfig
|
||||||
from .vdb.upstash_config import UpstashConfig
|
from .vdb.upstash_config import UpstashConfig
|
||||||
|
from .vdb.vastbase_vector_config import VastbaseVectorConfig
|
||||||
from .vdb.vikingdb_config import VikingDBConfig
|
from .vdb.vikingdb_config import VikingDBConfig
|
||||||
from .vdb.weaviate_config import WeaviateConfig
|
from .vdb.weaviate_config import WeaviateConfig
|
||||||
|
|
||||||
@ -263,11 +265,13 @@ class MiddlewareConfig(
|
|||||||
VectorStoreConfig,
|
VectorStoreConfig,
|
||||||
AnalyticdbConfig,
|
AnalyticdbConfig,
|
||||||
ChromaConfig,
|
ChromaConfig,
|
||||||
|
HuaweiCloudConfig,
|
||||||
MilvusConfig,
|
MilvusConfig,
|
||||||
MyScaleConfig,
|
MyScaleConfig,
|
||||||
OpenSearchConfig,
|
OpenSearchConfig,
|
||||||
OracleConfig,
|
OracleConfig,
|
||||||
PGVectorConfig,
|
PGVectorConfig,
|
||||||
|
VastbaseVectorConfig,
|
||||||
PGVectoRSConfig,
|
PGVectoRSConfig,
|
||||||
QdrantConfig,
|
QdrantConfig,
|
||||||
RelytConfig,
|
RelytConfig,
|
||||||
|
25
api/configs/middleware/vdb/huawei_cloud_config.py
Normal file
25
api/configs/middleware/vdb/huawei_cloud_config.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class HuaweiCloudConfig(BaseSettings):
|
||||||
|
"""
|
||||||
|
Configuration settings for Huawei cloud search service
|
||||||
|
"""
|
||||||
|
|
||||||
|
HUAWEI_CLOUD_HOSTS: Optional[str] = Field(
|
||||||
|
description="Hostname or IP address of the Huawei cloud search service instance",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
HUAWEI_CLOUD_USER: Optional[str] = Field(
|
||||||
|
description="Username for authenticating with Huawei cloud search service",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
HUAWEI_CLOUD_PASSWORD: Optional[str] = Field(
|
||||||
|
description="Password for authenticating with Huawei cloud search service",
|
||||||
|
default=None,
|
||||||
|
)
|
@ -32,3 +32,4 @@ class LindormConfig(BaseSettings):
|
|||||||
description="Using UGC index will store the same type of Index in a single index but can retrieve separately.",
|
description="Using UGC index will store the same type of Index in a single index but can retrieve separately.",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
LINDORM_QUERY_TIMEOUT: Optional[float] = Field(description="The lindorm search request timeout (s)", default=2.0)
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
45
api/configs/middleware/vdb/vastbase_vector_config.py
Normal file
45
api/configs/middleware/vdb/vastbase_vector_config.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import Field, PositiveInt
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class VastbaseVectorConfig(BaseSettings):
|
||||||
|
"""
|
||||||
|
Configuration settings for Vector (Vastbase with vector extension)
|
||||||
|
"""
|
||||||
|
|
||||||
|
VASTBASE_HOST: Optional[str] = Field(
|
||||||
|
description="Hostname or IP address of the Vastbase server with Vector extension (e.g., 'localhost')",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
VASTBASE_PORT: PositiveInt = Field(
|
||||||
|
description="Port number on which the Vastbase server is listening (default is 5432)",
|
||||||
|
default=5432,
|
||||||
|
)
|
||||||
|
|
||||||
|
VASTBASE_USER: Optional[str] = Field(
|
||||||
|
description="Username for authenticating with the Vastbase database",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
VASTBASE_PASSWORD: Optional[str] = Field(
|
||||||
|
description="Password for authenticating with the Vastbase database",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
VASTBASE_DATABASE: Optional[str] = Field(
|
||||||
|
description="Name of the Vastbase database to connect to",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
VASTBASE_MIN_CONNECTION: PositiveInt = Field(
|
||||||
|
description="Min connection of the Vastbase database",
|
||||||
|
default=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
VASTBASE_MAX_CONNECTION: PositiveInt = Field(
|
||||||
|
description="Max connection of the Vastbase database",
|
||||||
|
default=5,
|
||||||
|
)
|
@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
|||||||
|
|
||||||
CURRENT_VERSION: str = Field(
|
CURRENT_VERSION: str = Field(
|
||||||
description="Dify version",
|
description="Dify version",
|
||||||
default="1.2.0",
|
default="1.3.1",
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMIT_SHA: str = Field(
|
COMMIT_SHA: str = Field(
|
||||||
|
@ -3,3 +3,4 @@ from enum import StrEnum
|
|||||||
|
|
||||||
class RemoteSettingsSourceName(StrEnum):
|
class RemoteSettingsSourceName(StrEnum):
|
||||||
APOLLO = "apollo"
|
APOLLO = "apollo"
|
||||||
|
NACOS = "nacos"
|
||||||
|
52
api/configs/remote_settings_sources/nacos/__init__.py
Normal file
52
api/configs/remote_settings_sources/nacos/__init__.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic.fields import FieldInfo
|
||||||
|
|
||||||
|
from .http_request import NacosHttpClient
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from configs.remote_settings_sources.base import RemoteSettingsSource
|
||||||
|
|
||||||
|
from .utils import _parse_config
|
||||||
|
|
||||||
|
|
||||||
|
class NacosSettingsSource(RemoteSettingsSource):
|
||||||
|
def __init__(self, configs: Mapping[str, Any]):
|
||||||
|
self.configs = configs
|
||||||
|
self.remote_configs: dict[str, Any] = {}
|
||||||
|
self.async_init()
|
||||||
|
|
||||||
|
def async_init(self):
|
||||||
|
data_id = os.getenv("DIFY_ENV_NACOS_DATA_ID", "dify-api-env.properties")
|
||||||
|
group = os.getenv("DIFY_ENV_NACOS_GROUP", "nacos-dify")
|
||||||
|
tenant = os.getenv("DIFY_ENV_NACOS_NAMESPACE", "")
|
||||||
|
|
||||||
|
params = {"dataId": data_id, "group": group, "tenant": tenant}
|
||||||
|
try:
|
||||||
|
content = NacosHttpClient().http_request("/nacos/v1/cs/configs", method="GET", headers={}, params=params)
|
||||||
|
self.remote_configs = self._parse_config(content)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("[get-access-token] exception occurred")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _parse_config(self, content: str) -> dict:
|
||||||
|
if not content:
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
return _parse_config(self, content)
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Failed to parse config: {e}")
|
||||||
|
|
||||||
|
def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
|
||||||
|
if not isinstance(self.remote_configs, dict):
|
||||||
|
raise ValueError(f"remote configs is not dict, but {type(self.remote_configs)}")
|
||||||
|
|
||||||
|
field_value = self.remote_configs.get(field_name)
|
||||||
|
if field_value is None:
|
||||||
|
return None, field_name, False
|
||||||
|
|
||||||
|
return field_value, field_name, False
|
83
api/configs/remote_settings_sources/nacos/http_request.py
Normal file
83
api/configs/remote_settings_sources/nacos/http_request.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NacosHttpClient:
|
||||||
|
def __init__(self):
|
||||||
|
self.username = os.getenv("DIFY_ENV_NACOS_USERNAME")
|
||||||
|
self.password = os.getenv("DIFY_ENV_NACOS_PASSWORD")
|
||||||
|
self.ak = os.getenv("DIFY_ENV_NACOS_ACCESS_KEY")
|
||||||
|
self.sk = os.getenv("DIFY_ENV_NACOS_SECRET_KEY")
|
||||||
|
self.server = os.getenv("DIFY_ENV_NACOS_SERVER_ADDR", "localhost:8848")
|
||||||
|
self.token = None
|
||||||
|
self.token_ttl = 18000
|
||||||
|
self.token_expire_time: float = 0
|
||||||
|
|
||||||
|
def http_request(self, url, method="GET", headers=None, params=None):
|
||||||
|
try:
|
||||||
|
self._inject_auth_info(headers, params)
|
||||||
|
response = requests.request(method, url="http://" + self.server + url, headers=headers, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.text
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return f"Request to Nacos failed: {e}"
|
||||||
|
|
||||||
|
def _inject_auth_info(self, headers, params, module="config"):
|
||||||
|
headers.update({"User-Agent": "Nacos-Http-Client-In-Dify:v0.0.1"})
|
||||||
|
|
||||||
|
if module == "login":
|
||||||
|
return
|
||||||
|
|
||||||
|
ts = str(int(time.time() * 1000))
|
||||||
|
|
||||||
|
if self.ak and self.sk:
|
||||||
|
sign_str = self.get_sign_str(params["group"], params["tenant"], ts)
|
||||||
|
headers["Spas-AccessKey"] = self.ak
|
||||||
|
headers["Spas-Signature"] = self.__do_sign(sign_str, self.sk)
|
||||||
|
headers["timeStamp"] = ts
|
||||||
|
if self.username and self.password:
|
||||||
|
self.get_access_token(force_refresh=False)
|
||||||
|
params["accessToken"] = self.token
|
||||||
|
|
||||||
|
def __do_sign(self, sign_str, sk):
|
||||||
|
return (
|
||||||
|
base64.encodebytes(hmac.new(sk.encode(), sign_str.encode(), digestmod=hashlib.sha1).digest())
|
||||||
|
.decode()
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_sign_str(self, group, tenant, ts):
|
||||||
|
sign_str = ""
|
||||||
|
if tenant:
|
||||||
|
sign_str = tenant + "+"
|
||||||
|
if group:
|
||||||
|
sign_str = sign_str + group + "+"
|
||||||
|
if sign_str:
|
||||||
|
sign_str += ts
|
||||||
|
return sign_str
|
||||||
|
|
||||||
|
def get_access_token(self, force_refresh=False):
|
||||||
|
current_time = time.time()
|
||||||
|
if self.token and not force_refresh and self.token_expire_time > current_time:
|
||||||
|
return self.token
|
||||||
|
|
||||||
|
params = {"username": self.username, "password": self.password}
|
||||||
|
url = "http://" + self.server + "/nacos/v1/auth/login"
|
||||||
|
try:
|
||||||
|
resp = requests.request("POST", url, headers=None, params=params)
|
||||||
|
resp.raise_for_status()
|
||||||
|
response_data = resp.json()
|
||||||
|
self.token = response_data.get("accessToken")
|
||||||
|
self.token_ttl = response_data.get("tokenTtl", 18000)
|
||||||
|
self.token_expire_time = current_time + self.token_ttl - 10
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("[get-access-token] exception occur")
|
||||||
|
raise
|
31
api/configs/remote_settings_sources/nacos/utils.py
Normal file
31
api/configs/remote_settings_sources/nacos/utils.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
def _parse_config(self, content: str) -> dict[str, str]:
|
||||||
|
config: dict[str, str] = {}
|
||||||
|
if not content:
|
||||||
|
return config
|
||||||
|
|
||||||
|
for line in content.splitlines():
|
||||||
|
cleaned_line = line.strip()
|
||||||
|
if not cleaned_line or cleaned_line.startswith(("#", "!")):
|
||||||
|
continue
|
||||||
|
|
||||||
|
separator_index = -1
|
||||||
|
for i, c in enumerate(cleaned_line):
|
||||||
|
if c in ("=", ":") and (i == 0 or cleaned_line[i - 1] != "\\"):
|
||||||
|
separator_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if separator_index == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = cleaned_line[:separator_index].strip()
|
||||||
|
raw_value = cleaned_line[separator_index + 1 :].strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
decoded_value = bytes(raw_value, "utf-8").decode("unicode_escape")
|
||||||
|
decoded_value = decoded_value.replace(r"\=", "=").replace(r"\:", ":")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
decoded_value = raw_value
|
||||||
|
|
||||||
|
config[key] = decoded_value
|
||||||
|
|
||||||
|
return config
|
@ -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,4 @@
|
|||||||
from flask_restful import fields # type: ignore
|
from flask_restful import fields
|
||||||
|
|
||||||
parameters__system_parameters = {
|
parameters__system_parameters = {
|
||||||
"image_file_size_limit": fields.Integer,
|
"image_file_size_limit": fields.Integer,
|
||||||
|
@ -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
|
||||||
@ -186,7 +186,7 @@ class AnnotationUpdateDeleteApi(Resource):
|
|||||||
app_id = str(app_id)
|
app_id = str(app_id)
|
||||||
annotation_id = str(annotation_id)
|
annotation_id = str(annotation_id)
|
||||||
AppAnnotationService.delete_app_annotation(app_id, annotation_id)
|
AppAnnotationService.delete_app_annotation(app_id, annotation_id)
|
||||||
return {"result": "success"}, 200
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
|
|
||||||
class AnnotationBatchImportApi(Resource):
|
class AnnotationBatchImportApi(Resource):
|
||||||
|
@ -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
|
||||||
@ -80,8 +80,6 @@ class ChatMessageTextApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model
|
@get_app_model
|
||||||
def post(self, app_model: App):
|
def post(self, app_model: App):
|
||||||
from werkzeug.exceptions import InternalServerError
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("message_id", type=str, location="json")
|
parser.add_argument("message_id", type=str, location="json")
|
||||||
|
@ -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
|
||||||
@ -84,7 +84,7 @@ class TraceAppConfigApi(Resource):
|
|||||||
result = OpsService.delete_tracing_app_config(app_id=app_id, tracing_provider=args["tracing_provider"])
|
result = OpsService.delete_tracing_app_config(app_id=app_id, tracing_provider=args["tracing_provider"])
|
||||||
if not result:
|
if not result:
|
||||||
raise TracingConfigNotExist()
|
raise TracingConfigNotExist()
|
||||||
return {"result": "success"}
|
return {"result": "success"}, 204
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise BadRequest(str(e))
|
raise BadRequest(str(e))
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -65,7 +65,7 @@ class ApiKeyAuthDataSourceBindingDelete(Resource):
|
|||||||
|
|
||||||
ApiKeyAuthService.delete_provider_auth(current_user.current_tenant_id, binding_id)
|
ApiKeyAuthService.delete_provider_auth(current_user.current_tenant_id, binding_id)
|
||||||
|
|
||||||
return {"result": "success"}, 200
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(ApiKeyAuthDataSource, "/api-key-auth/data-source")
|
api.add_resource(ApiKeyAuthDataSource, "/api-key-auth/data-source")
|
||||||
|
@ -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
|
||||||
@ -657,6 +657,7 @@ class DatasetRetrievalSettingApi(Resource):
|
|||||||
| VectorType.ELASTICSEARCH
|
| VectorType.ELASTICSEARCH
|
||||||
| VectorType.ELASTICSEARCH_JA
|
| VectorType.ELASTICSEARCH_JA
|
||||||
| VectorType.PGVECTOR
|
| VectorType.PGVECTOR
|
||||||
|
| VectorType.VASTBASE
|
||||||
| VectorType.TIDB_ON_QDRANT
|
| VectorType.TIDB_ON_QDRANT
|
||||||
| VectorType.LINDORM
|
| VectorType.LINDORM
|
||||||
| VectorType.COUCHBASE
|
| VectorType.COUCHBASE
|
||||||
@ -664,6 +665,7 @@ class DatasetRetrievalSettingApi(Resource):
|
|||||||
| VectorType.OPENGAUSS
|
| VectorType.OPENGAUSS
|
||||||
| VectorType.OCEANBASE
|
| VectorType.OCEANBASE
|
||||||
| VectorType.TABLESTORE
|
| VectorType.TABLESTORE
|
||||||
|
| VectorType.HUAWEI_CLOUD
|
||||||
| VectorType.TENCENT
|
| VectorType.TENCENT
|
||||||
):
|
):
|
||||||
return {
|
return {
|
||||||
@ -705,11 +707,13 @@ class DatasetRetrievalSettingMockApi(Resource):
|
|||||||
| VectorType.ELASTICSEARCH_JA
|
| VectorType.ELASTICSEARCH_JA
|
||||||
| VectorType.COUCHBASE
|
| VectorType.COUCHBASE
|
||||||
| VectorType.PGVECTOR
|
| VectorType.PGVECTOR
|
||||||
|
| VectorType.VASTBASE
|
||||||
| VectorType.LINDORM
|
| VectorType.LINDORM
|
||||||
| VectorType.OPENGAUSS
|
| VectorType.OPENGAUSS
|
||||||
| VectorType.OCEANBASE
|
| VectorType.OCEANBASE
|
||||||
| VectorType.TABLESTORE
|
| VectorType.TABLESTORE
|
||||||
| VectorType.TENCENT
|
| VectorType.TENCENT
|
||||||
|
| VectorType.HUAWEI_CLOUD
|
||||||
):
|
):
|
||||||
return {
|
return {
|
||||||
"retrieval_method": [
|
"retrieval_method": [
|
||||||
|
@ -4,8 +4,8 @@ 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
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ from core.indexing_runner import IndexingRunner
|
|||||||
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 core.model_runtime.errors.invoke import InvokeAuthorizationError
|
from core.model_runtime.errors.invoke import InvokeAuthorizationError
|
||||||
from core.plugin.manager.exc import PluginDaemonClientSideError
|
from core.plugin.impl.exc import PluginDaemonClientSideError
|
||||||
from core.rag.extractor.entity.extract_setting import ExtractSetting
|
from core.rag.extractor.entity.extract_setting import ExtractSetting
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from extensions.ext_redis import redis_client
|
from extensions.ext_redis import redis_client
|
||||||
|
@ -2,8 +2,8 @@ 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 werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
@ -131,7 +131,7 @@ class DatasetDocumentSegmentListApi(Resource):
|
|||||||
except services.errors.account.NoPermissionError as e:
|
except services.errors.account.NoPermissionError as e:
|
||||||
raise Forbidden(str(e))
|
raise Forbidden(str(e))
|
||||||
SegmentService.delete_segments(segment_ids, document, dataset)
|
SegmentService.delete_segments(segment_ids, document, dataset)
|
||||||
return {"result": "success"}, 200
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
|
|
||||||
class DatasetDocumentSegmentApi(Resource):
|
class DatasetDocumentSegmentApi(Resource):
|
||||||
@ -333,7 +333,7 @@ class DatasetDocumentSegmentUpdateApi(Resource):
|
|||||||
except services.errors.account.NoPermissionError as e:
|
except services.errors.account.NoPermissionError as e:
|
||||||
raise Forbidden(str(e))
|
raise Forbidden(str(e))
|
||||||
SegmentService.delete_segment(segment, document, dataset)
|
SegmentService.delete_segment(segment, document, dataset)
|
||||||
return {"result": "success"}, 200
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
|
|
||||||
class DatasetDocumentSegmentBatchImportApi(Resource):
|
class DatasetDocumentSegmentBatchImportApi(Resource):
|
||||||
@ -590,7 +590,7 @@ class ChildChunkUpdateApi(Resource):
|
|||||||
SegmentService.delete_child_chunk(child_chunk, dataset)
|
SegmentService.delete_child_chunk(child_chunk, dataset)
|
||||||
except ChildChunkDeleteIndexServiceError as e:
|
except ChildChunkDeleteIndexServiceError as e:
|
||||||
raise ChildChunkDeleteIndexError(str(e))
|
raise ChildChunkDeleteIndexError(str(e))
|
||||||
return {"result": "success"}, 200
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_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, 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
|
||||||
@ -135,7 +135,7 @@ class ExternalApiTemplateApi(Resource):
|
|||||||
raise Forbidden()
|
raise Forbidden()
|
||||||
|
|
||||||
ExternalDatasetService.delete_external_knowledge_api(current_user.current_tenant_id, external_knowledge_api_id)
|
ExternalDatasetService.delete_external_knowledge_api(current_user.current_tenant_id, external_knowledge_api_id)
|
||||||
return {"result": "success"}, 200
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
|
|
||||||
class ExternalApiUseCheckApi(Resource):
|
class ExternalApiUseCheckApi(Resource):
|
||||||
@ -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
|
||||||
@ -82,7 +82,7 @@ class DatasetMetadataApi(Resource):
|
|||||||
DatasetService.check_dataset_permission(dataset, current_user)
|
DatasetService.check_dataset_permission(dataset, current_user)
|
||||||
|
|
||||||
MetadataService.delete_metadata(dataset_id_str, metadata_id_str)
|
MetadataService.delete_metadata(dataset_id_str, metadata_id_str)
|
||||||
return 200
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
|
|
||||||
class DatasetMetadataBuiltInFieldApi(Resource):
|
class DatasetMetadataBuiltInFieldApi(Resource):
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ class InstalledAppApi(InstalledAppResource):
|
|||||||
db.session.delete(installed_app)
|
db.session.delete(installed_app)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"result": "success", "message": "App uninstalled successfully"}
|
return {"result": "success", "message": "App uninstalled successfully"}, 204
|
||||||
|
|
||||||
def patch(self, installed_app):
|
def patch(self, installed_app):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
|
@ -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
|
||||||
@ -72,7 +72,7 @@ class SavedMessageApi(InstalledAppResource):
|
|||||||
|
|
||||||
SavedMessageService.delete(app_model, current_user, message_id)
|
SavedMessageService.delete(app_model, current_user, message_id)
|
||||||
|
|
||||||
return {"result": "success"}
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(
|
api.add_resource(
|
||||||
|
@ -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
|
||||||
@ -99,7 +99,7 @@ class APIBasedExtensionDetailAPI(Resource):
|
|||||||
|
|
||||||
APIBasedExtensionService.delete(extension_data_from_db)
|
APIBasedExtensionService.delete(extension_data_from_db)
|
||||||
|
|
||||||
return {"result": "success"}
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(CodeBasedExtensionAPI, "/code-based-extension")
|
api.add_resource(CodeBasedExtensionAPI, "/code-based-extension")
|
||||||
|
@ -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
|
||||||
@ -86,7 +86,7 @@ class TagUpdateDeleteApi(Resource):
|
|||||||
|
|
||||||
TagService.delete_tag(tag_id)
|
TagService.delete_tag(tag_id)
|
||||||
|
|
||||||
return 200
|
return 204
|
||||||
|
|
||||||
|
|
||||||
class TagBindingCreateApi(Resource):
|
class TagBindingCreateApi(Resource):
|
||||||
|
@ -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
|
||||||
|
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