mirror of
https://github.com/langgenius/dify.git
synced 2026-04-19 02:07:29 +08:00
Compare commits
8 Commits
feat/webap
...
feat/node-
| Author | SHA1 | Date | |
|---|---|---|---|
| fde3fe0ab6 | |||
| 07528f82b9 | |||
| 127291a90f | |||
| 9e0c28791d | |||
| b411087bb7 | |||
| 357769c72e | |||
| 853b9af09c | |||
| b99f1a09f4 |
2
.github/actions/setup-poetry/action.yml
vendored
2
.github/actions/setup-poetry/action.yml
vendored
@ -8,7 +8,7 @@ inputs:
|
|||||||
poetry-version:
|
poetry-version:
|
||||||
description: Poetry version to set up
|
description: Poetry version to set up
|
||||||
required: true
|
required: true
|
||||||
default: '2.0.1'
|
default: '1.8.4'
|
||||||
poetry-lockfile:
|
poetry-lockfile:
|
||||||
description: Path to the Poetry lockfile to restore cache from
|
description: Path to the Poetry lockfile to restore cache from
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
16
.github/workflows/api-tests.yml
vendored
16
.github/workflows/api-tests.yml
vendored
@ -42,23 +42,19 @@ jobs:
|
|||||||
run: poetry install -C api --with dev
|
run: poetry install -C api --with dev
|
||||||
|
|
||||||
- name: Check dependencies in pyproject.toml
|
- name: Check dependencies in pyproject.toml
|
||||||
run: poetry run -P api bash dev/pytest/pytest_artifacts.sh
|
run: poetry run -C api bash dev/pytest/pytest_artifacts.sh
|
||||||
|
|
||||||
- name: Run Unit tests
|
- name: Run Unit tests
|
||||||
run: poetry run -P api bash dev/pytest/pytest_unit_tests.sh
|
run: poetry run -C api bash dev/pytest/pytest_unit_tests.sh
|
||||||
|
|
||||||
- name: Run ModelRuntime
|
- name: Run ModelRuntime
|
||||||
run: poetry run -P api bash dev/pytest/pytest_model_runtime.sh
|
run: poetry run -C api bash dev/pytest/pytest_model_runtime.sh
|
||||||
|
|
||||||
- name: Run dify config tests
|
- name: Run dify config tests
|
||||||
run: poetry run -P api python dev/pytest/pytest_config_tests.py
|
run: poetry run -C api python dev/pytest/pytest_config_tests.py
|
||||||
|
|
||||||
- name: Run Tool
|
- name: Run Tool
|
||||||
run: poetry run -P api bash dev/pytest/pytest_tools.sh
|
run: poetry run -C api bash dev/pytest/pytest_tools.sh
|
||||||
|
|
||||||
- name: Run mypy
|
|
||||||
run: |
|
|
||||||
poetry run -C api python -m mypy --install-types --non-interactive .
|
|
||||||
|
|
||||||
- name: Set up dotenvs
|
- name: Set up dotenvs
|
||||||
run: |
|
run: |
|
||||||
@ -78,4 +74,4 @@ jobs:
|
|||||||
ssrf_proxy
|
ssrf_proxy
|
||||||
|
|
||||||
- name: Run Workflow
|
- name: Run Workflow
|
||||||
run: poetry run -P api bash dev/pytest/pytest_workflow.sh
|
run: poetry run -C api bash dev/pytest/pytest_workflow.sh
|
||||||
|
|||||||
2
.github/workflows/build-push.yml
vendored
2
.github/workflows/build-push.yml
vendored
@ -5,8 +5,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "main"
|
- "main"
|
||||||
- "deploy/dev"
|
- "deploy/dev"
|
||||||
- "deploy/enterprise"
|
|
||||||
- "e-0154"
|
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
|
|||||||
29
.github/workflows/deploy-enterprise.yml
vendored
29
.github/workflows/deploy-enterprise.yml
vendored
@ -1,29 +0,0 @@
|
|||||||
name: Deploy Enterprise
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ["Build and Push API & Web"]
|
|
||||||
branches:
|
|
||||||
- "deploy/enterprise"
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: |
|
|
||||||
github.event.workflow_run.conclusion == 'success' &&
|
|
||||||
github.event.workflow_run.head_branch == 'deploy/enterprise'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Deploy to server
|
|
||||||
uses: appleboy/ssh-action@v0.1.8
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.ENTERPRISE_SSH_HOST }}
|
|
||||||
username: ${{ secrets.ENTERPRISE_SSH_USER }}
|
|
||||||
password: ${{ secrets.ENTERPRISE_SSH_PASSWORD }}
|
|
||||||
script: |
|
|
||||||
${{ vars.ENTERPRISE_SSH_SCRIPT || secrets.ENTERPRISE_SSH_SCRIPT }}
|
|
||||||
47
.github/workflows/docker-build.yml
vendored
47
.github/workflows/docker-build.yml
vendored
@ -1,47 +0,0 @@
|
|||||||
name: Build docker image
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- "main"
|
|
||||||
paths:
|
|
||||||
- api/Dockerfile
|
|
||||||
- web/Dockerfile
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: docker-build-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- service_name: "api-amd64"
|
|
||||||
platform: linux/amd64
|
|
||||||
context: "api"
|
|
||||||
- service_name: "api-arm64"
|
|
||||||
platform: linux/arm64
|
|
||||||
context: "api"
|
|
||||||
- service_name: "web-amd64"
|
|
||||||
platform: linux/amd64
|
|
||||||
context: "web"
|
|
||||||
- service_name: "web-arm64"
|
|
||||||
platform: linux/arm64
|
|
||||||
context: "web"
|
|
||||||
steps:
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Build Docker Image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
push: false
|
|
||||||
context: "{{defaultContext}}:${{ matrix.context }}"
|
|
||||||
platforms: ${{ matrix.platform }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
33
.github/workflows/style.yml
vendored
33
.github/workflows/style.yml
vendored
@ -38,12 +38,12 @@ jobs:
|
|||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
poetry run -C api ruff --version
|
poetry run -C api ruff --version
|
||||||
poetry run -C api ruff check ./
|
poetry run -C api ruff check ./api
|
||||||
poetry run -C api ruff format --check ./
|
poetry run -C api ruff format --check ./api
|
||||||
|
|
||||||
- name: Dotenv check
|
- name: Dotenv check
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: poetry run -P api dotenv-linter ./api/.env.example ./web/.env.example
|
run: poetry run -C api dotenv-linter ./api/.env.example ./web/.env.example
|
||||||
|
|
||||||
- name: Lint hints
|
- name: Lint hints
|
||||||
if: failure()
|
if: failure()
|
||||||
@ -82,33 +82,6 @@ jobs:
|
|||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: yarn run lint
|
run: yarn run lint
|
||||||
|
|
||||||
docker-compose-template:
|
|
||||||
name: Docker Compose Template
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Check changed files
|
|
||||||
id: changed-files
|
|
||||||
uses: tj-actions/changed-files@v45
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
docker/generate_docker_compose
|
|
||||||
docker/.env.example
|
|
||||||
docker/docker-compose-template.yaml
|
|
||||||
docker/docker-compose.yaml
|
|
||||||
|
|
||||||
- name: Generate Docker Compose
|
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
|
||||||
run: |
|
|
||||||
cd docker
|
|
||||||
./generate_docker_compose
|
|
||||||
|
|
||||||
- name: Check for changes
|
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
|
||||||
run: git diff --exit-code
|
|
||||||
|
|
||||||
superlinter:
|
superlinter:
|
||||||
name: SuperLinter
|
name: SuperLinter
|
||||||
|
|||||||
2
.github/workflows/vdb-tests.yml
vendored
2
.github/workflows/vdb-tests.yml
vendored
@ -70,4 +70,4 @@ jobs:
|
|||||||
tidb
|
tidb
|
||||||
|
|
||||||
- name: Test Vector Stores
|
- name: Test Vector Stores
|
||||||
run: poetry run -P api bash dev/pytest/pytest_vdb.sh
|
run: poetry run -C api bash dev/pytest/pytest_vdb.sh
|
||||||
|
|||||||
@ -25,9 +25,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="follow on LinkedIn"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -21,9 +21,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="follow on LinkedIn"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -21,9 +21,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="follow on LinkedIn"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -21,9 +21,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="seguir en X(Twitter)"></a>
|
alt="seguir en X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="seguir en LinkedIn"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Descargas de Docker" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Descargas de Docker" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -21,9 +21,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="suivre sur X(Twitter)"></a>
|
alt="suivre sur X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="suivre sur LinkedIn"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Tirages Docker" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Tirages Docker" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -21,9 +21,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="X(Twitter)でフォロー"></a>
|
alt="X(Twitter)でフォロー"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="LinkedInでフォロー"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -21,9 +21,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="follow on LinkedIn"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -21,9 +21,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="follow on LinkedIn"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -25,9 +25,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="follow on LinkedIn"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -22,9 +22,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="follow on LinkedIn"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -21,9 +21,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="X(Twitter)'da takip et"></a>
|
alt="X(Twitter)'da takip et"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="LinkedIn'da takip et"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Çekmeleri" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Çekmeleri" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -65,6 +62,8 @@ Görsel bir arayüz üzerinde güçlü AI iş akışları oluşturun ve test edi
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
Özür dilerim, haklısınız. Daha anlamlı ve akıcı bir çeviri yapmaya çalışayım. İşte güncellenmiş çeviri:
|
||||||
|
|
||||||
**3. Prompt IDE**:
|
**3. Prompt IDE**:
|
||||||
Komut istemlerini oluşturmak, model performansını karşılaştırmak ve sohbet tabanlı uygulamalara metin-konuşma gibi ek özellikler eklemek için kullanıcı dostu bir arayüz.
|
Komut istemlerini oluşturmak, model performansını karşılaştırmak ve sohbet tabanlı uygulamalara metin-konuşma gibi ek özellikler eklemek için kullanıcı dostu bir arayüz.
|
||||||
|
|
||||||
@ -151,6 +150,8 @@ Görsel bir arayüz üzerinde güçlü AI iş akışları oluşturun ve test edi
|
|||||||
## Dify'ı Kullanma
|
## Dify'ı Kullanma
|
||||||
|
|
||||||
- **Cloud </br>**
|
- **Cloud </br>**
|
||||||
|
İşte verdiğiniz metnin Türkçe çevirisi, kod bloğu içinde:
|
||||||
|
-
|
||||||
Herkesin sıfır kurulumla denemesi için bir [Dify Cloud](https://dify.ai) hizmeti sunuyoruz. Bu hizmet, kendi kendine dağıtılan versiyonun tüm yeteneklerini sağlar ve sandbox planında 200 ücretsiz GPT-4 çağrısı içerir.
|
Herkesin sıfır kurulumla denemesi için bir [Dify Cloud](https://dify.ai) hizmeti sunuyoruz. Bu hizmet, kendi kendine dağıtılan versiyonun tüm yeteneklerini sağlar ve sandbox planında 200 ücretsiz GPT-4 çağrısı içerir.
|
||||||
|
|
||||||
- **Dify Topluluk Sürümünü Kendi Sunucunuzda Barındırma</br>**
|
- **Dify Topluluk Sürümünü Kendi Sunucunuzda Barındırma</br>**
|
||||||
@ -176,6 +177,8 @@ GitHub'da Dify'a yıldız verin ve yeni sürümlerden anında haberdar olun.
|
|||||||
>- RAM >= 4GB
|
>- RAM >= 4GB
|
||||||
|
|
||||||
</br>
|
</br>
|
||||||
|
İşte verdiğiniz metnin Türkçe çevirisi, kod bloğu içinde:
|
||||||
|
|
||||||
Dify sunucusunu başlatmanın en kolay yolu, [docker-compose.yml](docker/docker-compose.yaml) dosyamızı çalıştırmaktır. Kurulum komutunu çalıştırmadan önce, makinenizde [Docker](https://docs.docker.com/get-docker/) ve [Docker Compose](https://docs.docker.com/compose/install/)'un kurulu olduğundan emin olun:
|
Dify sunucusunu başlatmanın en kolay yolu, [docker-compose.yml](docker/docker-compose.yaml) dosyamızı çalıştırmaktır. Kurulum komutunu çalıştırmadan önce, makinenizde [Docker](https://docs.docker.com/get-docker/) ve [Docker Compose](https://docs.docker.com/compose/install/)'un kurulu olduğundan emin olun:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@ -21,9 +21,6 @@
|
|||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="theo dõi trên X(Twitter)"></a>
|
alt="theo dõi trên X(Twitter)"></a>
|
||||||
<a href="https://www.linkedin.com/company/langgenius/" target="_blank">
|
|
||||||
<img src="https://custom-icon-badges.demolab.com/badge/LinkedIn-0A66C2?logo=linkedin-white&logoColor=fff"
|
|
||||||
alt="theo dõi trên LinkedIn"></a>
|
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
|
|||||||
@ -23,9 +23,6 @@ FILES_ACCESS_TIMEOUT=300
|
|||||||
# Access token expiration time in minutes
|
# Access token expiration time in minutes
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES=60
|
ACCESS_TOKEN_EXPIRE_MINUTES=60
|
||||||
|
|
||||||
# Refresh token expiration time in days
|
|
||||||
REFRESH_TOKEN_EXPIRE_DAYS=30
|
|
||||||
|
|
||||||
# celery configuration
|
# celery configuration
|
||||||
CELERY_BROKER_URL=redis://:difyai123456@localhost:6379/1
|
CELERY_BROKER_URL=redis://:difyai123456@localhost:6379/1
|
||||||
|
|
||||||
@ -68,7 +65,7 @@ OPENDAL_FS_ROOT=storage
|
|||||||
|
|
||||||
# S3 Storage configuration
|
# S3 Storage configuration
|
||||||
S3_USE_AWS_MANAGED_IAM=false
|
S3_USE_AWS_MANAGED_IAM=false
|
||||||
S3_ENDPOINT=https://your-bucket-name.storage.s3.cloudflare.com
|
S3_ENDPOINT=https://your-bucket-name.storage.s3.clooudflare.com
|
||||||
S3_BUCKET_NAME=your-bucket-name
|
S3_BUCKET_NAME=your-bucket-name
|
||||||
S3_ACCESS_KEY=your-access-key
|
S3_ACCESS_KEY=your-access-key
|
||||||
S3_SECRET_KEY=your-secret-key
|
S3_SECRET_KEY=your-secret-key
|
||||||
@ -77,7 +74,7 @@ S3_REGION=your-region
|
|||||||
# Azure Blob Storage configuration
|
# Azure Blob Storage configuration
|
||||||
AZURE_BLOB_ACCOUNT_NAME=your-account-name
|
AZURE_BLOB_ACCOUNT_NAME=your-account-name
|
||||||
AZURE_BLOB_ACCOUNT_KEY=your-account-key
|
AZURE_BLOB_ACCOUNT_KEY=your-account-key
|
||||||
AZURE_BLOB_CONTAINER_NAME=your-container-name
|
AZURE_BLOB_CONTAINER_NAME=yout-container-name
|
||||||
AZURE_BLOB_ACCOUNT_URL=https://<your_account_name>.blob.core.windows.net
|
AZURE_BLOB_ACCOUNT_URL=https://<your_account_name>.blob.core.windows.net
|
||||||
|
|
||||||
# Aliyun oss Storage configuration
|
# Aliyun oss Storage configuration
|
||||||
@ -91,7 +88,7 @@ ALIYUN_OSS_REGION=your-region
|
|||||||
ALIYUN_OSS_PATH=your-path
|
ALIYUN_OSS_PATH=your-path
|
||||||
|
|
||||||
# Google Storage configuration
|
# Google Storage configuration
|
||||||
GOOGLE_STORAGE_BUCKET_NAME=your-bucket-name
|
GOOGLE_STORAGE_BUCKET_NAME=yout-bucket-name
|
||||||
GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64=your-google-service-account-json-base64-string
|
GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64=your-google-service-account-json-base64-string
|
||||||
|
|
||||||
# Tencent COS Storage configuration
|
# Tencent COS Storage configuration
|
||||||
@ -402,7 +399,6 @@ INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000
|
|||||||
WORKFLOW_MAX_EXECUTION_STEPS=500
|
WORKFLOW_MAX_EXECUTION_STEPS=500
|
||||||
WORKFLOW_MAX_EXECUTION_TIME=1200
|
WORKFLOW_MAX_EXECUTION_TIME=1200
|
||||||
WORKFLOW_CALL_MAX_DEPTH=5
|
WORKFLOW_CALL_MAX_DEPTH=5
|
||||||
WORKFLOW_PARALLEL_DEPTH_LIMIT=3
|
|
||||||
MAX_VARIABLE_SIZE=204800
|
MAX_VARIABLE_SIZE=204800
|
||||||
|
|
||||||
# App configuration
|
# App configuration
|
||||||
|
|||||||
@ -53,12 +53,10 @@ ignore = [
|
|||||||
"FURB152", # math-constant
|
"FURB152", # math-constant
|
||||||
"UP007", # non-pep604-annotation
|
"UP007", # non-pep604-annotation
|
||||||
"UP032", # f-string
|
"UP032", # f-string
|
||||||
"UP045", # non-pep604-annotation-optional
|
|
||||||
"B005", # strip-with-multi-characters
|
"B005", # strip-with-multi-characters
|
||||||
"B006", # mutable-argument-default
|
"B006", # mutable-argument-default
|
||||||
"B007", # unused-loop-control-variable
|
"B007", # unused-loop-control-variable
|
||||||
"B026", # star-arg-unpacking-after-keyword-arg
|
"B026", # star-arg-unpacking-after-keyword-arg
|
||||||
"B903", # class-as-data-structure
|
|
||||||
"B904", # raise-without-from-inside-except
|
"B904", # raise-without-from-inside-except
|
||||||
"B905", # zip-without-explicit-strict
|
"B905", # zip-without-explicit-strict
|
||||||
"N806", # non-lowercase-variable-in-function
|
"N806", # non-lowercase-variable-in-function
|
||||||
@ -69,7 +67,7 @@ ignore = [
|
|||||||
"SIM105", # suppressible-exception
|
"SIM105", # suppressible-exception
|
||||||
"SIM107", # return-in-try-except-finally
|
"SIM107", # return-in-try-except-finally
|
||||||
"SIM108", # if-else-block-instead-of-if-exp
|
"SIM108", # if-else-block-instead-of-if-exp
|
||||||
"SIM113", # enumerate-for-loop
|
"SIM113", # eumerate-for-loop
|
||||||
"SIM117", # multiple-with-statements
|
"SIM117", # multiple-with-statements
|
||||||
"SIM210", # if-expr-with-true-false
|
"SIM210", # if-expr-with-true-false
|
||||||
]
|
]
|
||||||
@ -87,11 +85,11 @@ ignore = [
|
|||||||
]
|
]
|
||||||
"tests/*" = [
|
"tests/*" = [
|
||||||
"F811", # redefined-while-unused
|
"F811", # redefined-while-unused
|
||||||
|
"F401", # unused-import
|
||||||
]
|
]
|
||||||
|
|
||||||
[lint.pyflakes]
|
[lint.pyflakes]
|
||||||
allowed-unused-imports = [
|
extend-generics = [
|
||||||
"_pytest.monkeypatch",
|
"_pytest.monkeypatch",
|
||||||
"tests.integration_tests",
|
"tests.integration_tests",
|
||||||
"tests.unit_tests",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -4,7 +4,7 @@ FROM python:3.12-slim-bookworm AS base
|
|||||||
WORKDIR /app/api
|
WORKDIR /app/api
|
||||||
|
|
||||||
# Install Poetry
|
# Install Poetry
|
||||||
ENV POETRY_VERSION=2.0.1
|
ENV POETRY_VERSION=1.8.4
|
||||||
|
|
||||||
# if you located in China, you can use aliyun mirror to speed up
|
# if you located in China, you can use aliyun mirror to speed up
|
||||||
# RUN pip install --no-cache-dir poetry==${POETRY_VERSION} -i https://mirrors.aliyun.com/pypi/simple/
|
# RUN pip install --no-cache-dir poetry==${POETRY_VERSION} -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
@ -48,18 +48,16 @@ ENV TZ=UTC
|
|||||||
|
|
||||||
WORKDIR /app/api
|
WORKDIR /app/api
|
||||||
|
|
||||||
RUN \
|
RUN apt-get update \
|
||||||
apt-get update \
|
&& apt-get install -y --no-install-recommends curl nodejs libgmp-dev libmpfr-dev libmpc-dev \
|
||||||
# Install dependencies
|
# if you located in China, you can use aliyun mirror to speed up
|
||||||
&& apt-get install -y --no-install-recommends \
|
# && echo "deb http://mirrors.aliyun.com/debian testing main" > /etc/apt/sources.list \
|
||||||
# basic environment
|
&& echo "deb http://deb.debian.org/debian testing main" > /etc/apt/sources.list \
|
||||||
curl nodejs libgmp-dev libmpfr-dev libmpc-dev \
|
&& apt-get update \
|
||||||
# For Security
|
# For Security
|
||||||
expat libldap-2.5-0 perl libsqlite3-0 zlib1g \
|
&& apt-get install -y --no-install-recommends expat=2.6.4-1 libldap-2.5-0=2.5.18+dfsg-3+b1 perl=5.40.0-8 libsqlite3-0=3.46.1-1 zlib1g=1:1.3.dfsg+really1.3.1-1+b1 \
|
||||||
# install a chinese font to support the use of tools like matplotlib
|
# install a chinese font to support the use of tools like matplotlib
|
||||||
fonts-noto-cjk \
|
&& apt-get install -y fonts-noto-cjk \
|
||||||
# install libmagic to support the use of python-magic guess MIMETYPE
|
|
||||||
libmagic1 \
|
|
||||||
&& apt-get autoremove -y \
|
&& apt-get autoremove -y \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
@ -78,6 +76,7 @@ COPY . /app/api/
|
|||||||
COPY docker/entrypoint.sh /entrypoint.sh
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
|
||||||
ARG COMMIT_SHA
|
ARG COMMIT_SHA
|
||||||
ENV COMMIT_SHA=${COMMIT_SHA}
|
ENV COMMIT_SHA=${COMMIT_SHA}
|
||||||
|
|
||||||
|
|||||||
@ -79,5 +79,5 @@
|
|||||||
2. Run the tests locally with mocked system environment variables in `tool.pytest_env` section in `pyproject.toml`
|
2. Run the tests locally with mocked system environment variables in `tool.pytest_env` section in `pyproject.toml`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry run -P api bash dev/pytest/pytest_all_tests.sh
|
poetry run -C api bash dev/pytest/pytest_all_tests.sh
|
||||||
```
|
```
|
||||||
|
|||||||
29
api/app.py
29
api/app.py
@ -1,8 +1,12 @@
|
|||||||
import os
|
from libs import version_utils
|
||||||
import sys
|
|
||||||
|
# preparation before creating app
|
||||||
|
version_utils.check_supported_python_version()
|
||||||
|
|
||||||
|
|
||||||
def is_db_command():
|
def is_db_command():
|
||||||
|
import sys
|
||||||
|
|
||||||
if len(sys.argv) > 1 and sys.argv[0].endswith("flask") and sys.argv[1] == "db":
|
if len(sys.argv) > 1 and sys.argv[0].endswith("flask") and sys.argv[1] == "db":
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@ -14,25 +18,10 @@ if is_db_command():
|
|||||||
|
|
||||||
app = create_migrations_app()
|
app = create_migrations_app()
|
||||||
else:
|
else:
|
||||||
# It seems that JetBrains Python debugger does not work well with gevent,
|
|
||||||
# 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 (flask_debug := os.environ.get("FLASK_DEBUG", "0")) and flask_debug.lower() in {"false", "0", "no"}:
|
|
||||||
from gevent import monkey # type: ignore
|
|
||||||
|
|
||||||
# gevent
|
|
||||||
monkey.patch_all()
|
|
||||||
|
|
||||||
from grpc.experimental import gevent as grpc_gevent # type: ignore
|
|
||||||
|
|
||||||
# grpc gevent
|
|
||||||
grpc_gevent.init_gevent()
|
|
||||||
|
|
||||||
import psycogreen.gevent # type: ignore
|
|
||||||
|
|
||||||
psycogreen.gevent.patch_psycopg()
|
|
||||||
|
|
||||||
from app_factory import create_app
|
from app_factory import create_app
|
||||||
|
from libs import threadings_utils
|
||||||
|
|
||||||
|
threadings_utils.apply_gevent_threading_patch()
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
celery = app.extensions["celery"]
|
celery = app.extensions["celery"]
|
||||||
|
|||||||
@ -159,7 +159,8 @@ def migrate_annotation_vector_database():
|
|||||||
try:
|
try:
|
||||||
# get apps info
|
# get apps info
|
||||||
apps = (
|
apps = (
|
||||||
App.query.filter(App.status == "normal")
|
db.session.query(App)
|
||||||
|
.filter(App.status == "normal")
|
||||||
.order_by(App.created_at.desc())
|
.order_by(App.created_at.desc())
|
||||||
.paginate(page=page, per_page=50)
|
.paginate(page=page, per_page=50)
|
||||||
)
|
)
|
||||||
@ -284,7 +285,8 @@ def migrate_knowledge_vector_database():
|
|||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
datasets = (
|
datasets = (
|
||||||
Dataset.query.filter(Dataset.indexing_technique == "high_quality")
|
db.session.query(Dataset)
|
||||||
|
.filter(Dataset.indexing_technique == "high_quality")
|
||||||
.order_by(Dataset.created_at.desc())
|
.order_by(Dataset.created_at.desc())
|
||||||
.paginate(page=page, per_page=50)
|
.paginate(page=page, per_page=50)
|
||||||
)
|
)
|
||||||
@ -448,8 +450,7 @@ def convert_to_agent_apps():
|
|||||||
if app_id not in proceeded_app_ids:
|
if app_id not in proceeded_app_ids:
|
||||||
proceeded_app_ids.append(app_id)
|
proceeded_app_ids.append(app_id)
|
||||||
app = db.session.query(App).filter(App.id == app_id).first()
|
app = db.session.query(App).filter(App.id == app_id).first()
|
||||||
if app is not None:
|
apps.append(app)
|
||||||
apps.append(app)
|
|
||||||
|
|
||||||
if len(apps) == 0:
|
if len(apps) == 0:
|
||||||
break
|
break
|
||||||
@ -554,20 +555,14 @@ def create_tenant(email: str, language: Optional[str] = None, name: Optional[str
|
|||||||
if language not in languages:
|
if language not in languages:
|
||||||
language = "en-US"
|
language = "en-US"
|
||||||
|
|
||||||
# Validates name encoding for non-Latin characters.
|
name = name.strip()
|
||||||
name = name.strip().encode("utf-8").decode("utf-8") if name else None
|
|
||||||
|
|
||||||
# generate random password
|
# generate random password
|
||||||
new_password = secrets.token_urlsafe(16)
|
new_password = secrets.token_urlsafe(16)
|
||||||
|
|
||||||
# register account
|
# register account
|
||||||
account = RegisterService.register(
|
account = RegisterService.register(email=email, name=account_name, password=new_password, language=language)
|
||||||
email=email,
|
|
||||||
name=account_name,
|
|
||||||
password=new_password,
|
|
||||||
language=language,
|
|
||||||
create_workspace_required=False,
|
|
||||||
)
|
|
||||||
TenantService.create_owner_tenant_if_not_exist(account, name)
|
TenantService.create_owner_tenant_if_not_exist(account, name)
|
||||||
|
|
||||||
click.echo(
|
click.echo(
|
||||||
@ -587,7 +582,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()
|
||||||
|
|
||||||
@ -625,10 +620,6 @@ where sites.id is null limit 1000"""
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
app = db.session.query(App).filter(App.id == app_id).first()
|
app = db.session.query(App).filter(App.id == app_id).first()
|
||||||
if not app:
|
|
||||||
print(f"App {app_id} not found")
|
|
||||||
continue
|
|
||||||
|
|
||||||
tenant = app.tenant
|
tenant = app.tenant
|
||||||
if tenant:
|
if tenant:
|
||||||
accounts = tenant.get_accounts()
|
accounts = tenant.get_accounts()
|
||||||
|
|||||||
@ -146,7 +146,7 @@ class EndpointConfig(BaseSettings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
CONSOLE_WEB_URL: str = Field(
|
CONSOLE_WEB_URL: str = Field(
|
||||||
description="Base URL for the console web interface,used for frontend references and CORS configuration",
|
description="Base URL for the console web interface," "used for frontend references and CORS configuration",
|
||||||
default="",
|
default="",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -239,6 +239,7 @@ class HttpConfig(BaseSettings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
|
@property
|
||||||
def CONSOLE_CORS_ALLOW_ORIGINS(self) -> list[str]:
|
def CONSOLE_CORS_ALLOW_ORIGINS(self) -> list[str]:
|
||||||
return self.inner_CONSOLE_CORS_ALLOW_ORIGINS.split(",")
|
return self.inner_CONSOLE_CORS_ALLOW_ORIGINS.split(",")
|
||||||
|
|
||||||
@ -249,6 +250,7 @@ class HttpConfig(BaseSettings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
|
@property
|
||||||
def WEB_API_CORS_ALLOW_ORIGINS(self) -> list[str]:
|
def WEB_API_CORS_ALLOW_ORIGINS(self) -> list[str]:
|
||||||
return self.inner_WEB_API_CORS_ALLOW_ORIGINS.split(",")
|
return self.inner_WEB_API_CORS_ALLOW_ORIGINS.split(",")
|
||||||
|
|
||||||
@ -431,11 +433,6 @@ class WorkflowConfig(BaseSettings):
|
|||||||
default=5,
|
default=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
WORKFLOW_PARALLEL_DEPTH_LIMIT: PositiveInt = Field(
|
|
||||||
description="Maximum allowed depth for nested parallel executions",
|
|
||||||
default=3,
|
|
||||||
)
|
|
||||||
|
|
||||||
MAX_VARIABLE_SIZE: PositiveInt = Field(
|
MAX_VARIABLE_SIZE: PositiveInt = Field(
|
||||||
description="Maximum size in bytes for a single variable in workflows. Default to 200 KB.",
|
description="Maximum size in bytes for a single variable in workflows. Default to 200 KB.",
|
||||||
default=200 * 1024,
|
default=200 * 1024,
|
||||||
@ -488,11 +485,6 @@ class AuthConfig(BaseSettings):
|
|||||||
default=60,
|
default=60,
|
||||||
)
|
)
|
||||||
|
|
||||||
REFRESH_TOKEN_EXPIRE_DAYS: PositiveFloat = Field(
|
|
||||||
description="Expiration time for refresh tokens in days",
|
|
||||||
default=30,
|
|
||||||
)
|
|
||||||
|
|
||||||
LOGIN_LOCKOUT_DURATION: PositiveInt = Field(
|
LOGIN_LOCKOUT_DURATION: PositiveInt = Field(
|
||||||
description="Time (in seconds) a user must wait before retrying login after exceeding the rate limit.",
|
description="Time (in seconds) a user must wait before retrying login after exceeding the rate limit.",
|
||||||
default=86400,
|
default=86400,
|
||||||
@ -606,7 +598,7 @@ class RagEtlConfig(BaseSettings):
|
|||||||
|
|
||||||
UNSTRUCTURED_API_KEY: Optional[str] = Field(
|
UNSTRUCTURED_API_KEY: Optional[str] = Field(
|
||||||
description="API key for Unstructured.io service",
|
description="API key for Unstructured.io service",
|
||||||
default="",
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
SCARF_NO_ANALYTICS: Optional[str] = Field(
|
SCARF_NO_ANALYTICS: Optional[str] = Field(
|
||||||
@ -672,11 +664,6 @@ class IndexingConfig(BaseSettings):
|
|||||||
default=4000,
|
default=4000,
|
||||||
)
|
)
|
||||||
|
|
||||||
CHILD_CHUNKS_PREVIEW_NUMBER: PositiveInt = Field(
|
|
||||||
description="Maximum number of child chunks to preview",
|
|
||||||
default=50,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MultiModalTransferConfig(BaseSettings):
|
class MultiModalTransferConfig(BaseSettings):
|
||||||
MULTIMODAL_SEND_FORMAT: Literal["base64", "url"] = Field(
|
MULTIMODAL_SEND_FORMAT: Literal["base64", "url"] = Field(
|
||||||
@ -723,27 +710,27 @@ class PositionConfig(BaseSettings):
|
|||||||
default="",
|
default="",
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@computed_field
|
||||||
def POSITION_PROVIDER_PINS_LIST(self) -> list[str]:
|
def POSITION_PROVIDER_PINS_LIST(self) -> list[str]:
|
||||||
return [item.strip() for item in self.POSITION_PROVIDER_PINS.split(",") if item.strip() != ""]
|
return [item.strip() for item in self.POSITION_PROVIDER_PINS.split(",") if item.strip() != ""]
|
||||||
|
|
||||||
@property
|
@computed_field
|
||||||
def POSITION_PROVIDER_INCLUDES_SET(self) -> set[str]:
|
def POSITION_PROVIDER_INCLUDES_SET(self) -> set[str]:
|
||||||
return {item.strip() for item in self.POSITION_PROVIDER_INCLUDES.split(",") if item.strip() != ""}
|
return {item.strip() for item in self.POSITION_PROVIDER_INCLUDES.split(",") if item.strip() != ""}
|
||||||
|
|
||||||
@property
|
@computed_field
|
||||||
def POSITION_PROVIDER_EXCLUDES_SET(self) -> set[str]:
|
def POSITION_PROVIDER_EXCLUDES_SET(self) -> set[str]:
|
||||||
return {item.strip() for item in self.POSITION_PROVIDER_EXCLUDES.split(",") if item.strip() != ""}
|
return {item.strip() for item in self.POSITION_PROVIDER_EXCLUDES.split(",") if item.strip() != ""}
|
||||||
|
|
||||||
@property
|
@computed_field
|
||||||
def POSITION_TOOL_PINS_LIST(self) -> list[str]:
|
def POSITION_TOOL_PINS_LIST(self) -> list[str]:
|
||||||
return [item.strip() for item in self.POSITION_TOOL_PINS.split(",") if item.strip() != ""]
|
return [item.strip() for item in self.POSITION_TOOL_PINS.split(",") if item.strip() != ""]
|
||||||
|
|
||||||
@property
|
@computed_field
|
||||||
def POSITION_TOOL_INCLUDES_SET(self) -> set[str]:
|
def POSITION_TOOL_INCLUDES_SET(self) -> set[str]:
|
||||||
return {item.strip() for item in self.POSITION_TOOL_INCLUDES.split(",") if item.strip() != ""}
|
return {item.strip() for item in self.POSITION_TOOL_INCLUDES.split(",") if item.strip() != ""}
|
||||||
|
|
||||||
@property
|
@computed_field
|
||||||
def POSITION_TOOL_EXCLUDES_SET(self) -> set[str]:
|
def POSITION_TOOL_EXCLUDES_SET(self) -> set[str]:
|
||||||
return {item.strip() for item in self.POSITION_TOOL_EXCLUDES.split(",") if item.strip() != ""}
|
return {item.strip() for item in self.POSITION_TOOL_EXCLUDES.split(",") if item.strip() != ""}
|
||||||
|
|
||||||
@ -775,13 +762,6 @@ class LoginConfig(BaseSettings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AccountConfig(BaseSettings):
|
|
||||||
ACCOUNT_DELETION_TOKEN_EXPIRY_MINUTES: PositiveInt = Field(
|
|
||||||
description="Duration in minutes for which a account deletion token remains valid",
|
|
||||||
default=5,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FeatureConfig(
|
class FeatureConfig(
|
||||||
# place the configs in alphabet order
|
# place the configs in alphabet order
|
||||||
AppExecutionConfig,
|
AppExecutionConfig,
|
||||||
@ -809,7 +789,6 @@ class FeatureConfig(
|
|||||||
WorkflowNodeExecutionConfig,
|
WorkflowNodeExecutionConfig,
|
||||||
WorkspaceConfig,
|
WorkspaceConfig,
|
||||||
LoginConfig,
|
LoginConfig,
|
||||||
AccountConfig,
|
|
||||||
# hosted services config
|
# hosted services config
|
||||||
HostedServiceConfig,
|
HostedServiceConfig,
|
||||||
CeleryBeatConfig,
|
CeleryBeatConfig,
|
||||||
|
|||||||
@ -1,40 +1,9 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import Field, NonNegativeInt, computed_field
|
from pydantic import Field, NonNegativeInt
|
||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
class HostedCreditConfig(BaseSettings):
|
|
||||||
HOSTED_MODEL_CREDIT_CONFIG: str = Field(
|
|
||||||
description="Model credit configuration in format 'model:credits,model:credits', e.g., 'gpt-4:20,gpt-4o:10'",
|
|
||||||
default="",
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_model_credits(self, model_name: str) -> int:
|
|
||||||
"""
|
|
||||||
Get credit value for a specific model name.
|
|
||||||
Returns 1 if model is not found in configuration (default credit).
|
|
||||||
|
|
||||||
:param model_name: The name of the model to search for
|
|
||||||
:return: The credit value for the model
|
|
||||||
"""
|
|
||||||
if not self.HOSTED_MODEL_CREDIT_CONFIG:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
try:
|
|
||||||
credit_map = dict(
|
|
||||||
item.strip().split(":", 1) for item in self.HOSTED_MODEL_CREDIT_CONFIG.split(",") if ":" in item
|
|
||||||
)
|
|
||||||
|
|
||||||
# Search for matching model pattern
|
|
||||||
for pattern, credit in credit_map.items():
|
|
||||||
if pattern.strip() == model_name:
|
|
||||||
return int(credit)
|
|
||||||
return 1 # Default quota if no match found
|
|
||||||
except (ValueError, AttributeError):
|
|
||||||
return 1 # Return default quota if parsing fails
|
|
||||||
|
|
||||||
|
|
||||||
class HostedOpenAiConfig(BaseSettings):
|
class HostedOpenAiConfig(BaseSettings):
|
||||||
"""
|
"""
|
||||||
Configuration for hosted OpenAI service
|
Configuration for hosted OpenAI service
|
||||||
@ -212,7 +181,7 @@ class HostedFetchAppTemplateConfig(BaseSettings):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
HOSTED_FETCH_APP_TEMPLATES_MODE: str = Field(
|
HOSTED_FETCH_APP_TEMPLATES_MODE: str = Field(
|
||||||
description="Mode for fetching app templates: remote, db, or builtin default to remote,",
|
description="Mode for fetching app templates: remote, db, or builtin" " default to remote,",
|
||||||
default="remote",
|
default="remote",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -233,7 +202,5 @@ class HostedServiceConfig(
|
|||||||
HostedZhipuAIConfig,
|
HostedZhipuAIConfig,
|
||||||
# moderation
|
# moderation
|
||||||
HostedModerationConfig,
|
HostedModerationConfig,
|
||||||
# credit config
|
|
||||||
HostedCreditConfig,
|
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -130,6 +130,7 @@ class DatabaseConfig(BaseSettings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
|
@property
|
||||||
def SQLALCHEMY_DATABASE_URI(self) -> str:
|
def SQLALCHEMY_DATABASE_URI(self) -> str:
|
||||||
db_extras = (
|
db_extras = (
|
||||||
f"{self.DB_EXTRAS}&client_encoding={self.DB_CHARSET}" if self.DB_CHARSET else self.DB_EXTRAS
|
f"{self.DB_EXTRAS}&client_encoding={self.DB_CHARSET}" if self.DB_CHARSET else self.DB_EXTRAS
|
||||||
@ -167,6 +168,7 @@ class DatabaseConfig(BaseSettings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
|
@property
|
||||||
def SQLALCHEMY_ENGINE_OPTIONS(self) -> dict[str, Any]:
|
def SQLALCHEMY_ENGINE_OPTIONS(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"pool_size": self.SQLALCHEMY_POOL_SIZE,
|
"pool_size": self.SQLALCHEMY_POOL_SIZE,
|
||||||
@ -204,6 +206,7 @@ class CeleryConfig(DatabaseConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
|
@property
|
||||||
def CELERY_RESULT_BACKEND(self) -> str | None:
|
def CELERY_RESULT_BACKEND(self) -> str | None:
|
||||||
return (
|
return (
|
||||||
"db+{}".format(self.SQLALCHEMY_DATABASE_URI)
|
"db+{}".format(self.SQLALCHEMY_DATABASE_URI)
|
||||||
@ -211,6 +214,7 @@ class CeleryConfig(DatabaseConfig):
|
|||||||
else self.CELERY_BROKER_URL
|
else self.CELERY_BROKER_URL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@computed_field
|
||||||
@property
|
@property
|
||||||
def BROKER_USE_SSL(self) -> bool:
|
def BROKER_USE_SSL(self) -> bool:
|
||||||
return self.CELERY_BROKER_URL.startswith("rediss://") if self.CELERY_BROKER_URL else False
|
return self.CELERY_BROKER_URL.startswith("rediss://") if self.CELERY_BROKER_URL else False
|
||||||
|
|||||||
@ -33,9 +33,3 @@ class MilvusConfig(BaseSettings):
|
|||||||
description="Name of the Milvus database to connect to (default is 'default')",
|
description="Name of the Milvus database to connect to (default is 'default')",
|
||||||
default="default",
|
default="default",
|
||||||
)
|
)
|
||||||
|
|
||||||
MILVUS_ENABLE_HYBRID_SEARCH: bool = Field(
|
|
||||||
description="Enable hybrid search features (requires Milvus >= 2.5.0). Set to false for compatibility with "
|
|
||||||
"older versions",
|
|
||||||
default=True,
|
|
||||||
)
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
|||||||
|
|
||||||
CURRENT_VERSION: str = Field(
|
CURRENT_VERSION: str = Field(
|
||||||
description="Dify version",
|
description="Dify version",
|
||||||
default="0.15.4",
|
default="0.14.1",
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMIT_SHA: str = Field(
|
COMMIT_SHA: str = Field(
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from collections.abc import Mapping
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .python_3x import http_request, makedirs_wrapper
|
from .python_3x import http_request, makedirs_wrapper
|
||||||
@ -256,8 +255,8 @@ class ApolloClient:
|
|||||||
logger.info("stopped, long_poll")
|
logger.info("stopped, long_poll")
|
||||||
|
|
||||||
# add the need for endorsement to the header
|
# add the need for endorsement to the header
|
||||||
def _sign_headers(self, url: str) -> Mapping[str, str]:
|
def _sign_headers(self, url):
|
||||||
headers: dict[str, str] = {}
|
headers = {}
|
||||||
if self.secret == "":
|
if self.secret == "":
|
||||||
return headers
|
return headers
|
||||||
uri = url[len(self.config_url) : len(url)]
|
uri = url[len(self.config_url) : len(url)]
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
from collections.abc import Mapping
|
|
||||||
|
|
||||||
from models.model import AppMode
|
from models.model import AppMode
|
||||||
|
|
||||||
default_app_templates: Mapping[AppMode, Mapping] = {
|
default_app_templates = {
|
||||||
# workflow default mode
|
# workflow default mode
|
||||||
AppMode.WORKFLOW: {
|
AppMode.WORKFLOW: {
|
||||||
"app": {
|
"app": {
|
||||||
|
|||||||
@ -4,8 +4,3 @@ from werkzeug.exceptions import HTTPException
|
|||||||
class FilenameNotExistsError(HTTPException):
|
class FilenameNotExistsError(HTTPException):
|
||||||
code = 400
|
code = 400
|
||||||
description = "The specified filename does not exist."
|
description = "The specified filename does not exist."
|
||||||
|
|
||||||
|
|
||||||
class RemoteFileUploadError(HTTPException):
|
|
||||||
code = 400
|
|
||||||
description = "Error uploading remote file."
|
|
||||||
|
|||||||
@ -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,32 +1,12 @@
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import re
|
import re
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import warnings
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
try:
|
|
||||||
import magic
|
|
||||||
except ImportError:
|
|
||||||
if platform.system() == "Windows":
|
|
||||||
warnings.warn(
|
|
||||||
"To use python-magic guess MIMETYPE, you need to run `pip install python-magic-bin`", stacklevel=2
|
|
||||||
)
|
|
||||||
elif platform.system() == "Darwin":
|
|
||||||
warnings.warn("To use python-magic guess MIMETYPE, you need to run `brew install libmagic`", stacklevel=2)
|
|
||||||
elif platform.system() == "Linux":
|
|
||||||
warnings.warn(
|
|
||||||
"To use python-magic guess MIMETYPE, you need to run `sudo apt-get install libmagic1`", stacklevel=2
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
warnings.warn("To use python-magic guess MIMETYPE, you need to install `libmagic`", stacklevel=2)
|
|
||||||
magic = None # type: ignore
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
@ -67,13 +47,6 @@ def guess_file_info_from_response(response: httpx.Response):
|
|||||||
# If guessing fails, use Content-Type from response headers
|
# If guessing fails, use Content-Type from response headers
|
||||||
mimetype = response.headers.get("Content-Type", "application/octet-stream")
|
mimetype = response.headers.get("Content-Type", "application/octet-stream")
|
||||||
|
|
||||||
# Use python-magic to guess MIME type if still unknown or generic
|
|
||||||
if mimetype == "application/octet-stream" and magic is not None:
|
|
||||||
try:
|
|
||||||
mimetype = magic.from_buffer(response.content[:1024], mime=True)
|
|
||||||
except magic.MagicException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
extension = os.path.splitext(filename)[1]
|
extension = os.path.splitext(filename)[1]
|
||||||
|
|
||||||
# Ensure filename has an extension
|
# Ensure filename has an extension
|
||||||
|
|||||||
@ -3,25 +3,6 @@ from flask import Blueprint
|
|||||||
from libs.external_api import ExternalApi
|
from libs.external_api import ExternalApi
|
||||||
|
|
||||||
from .app.app_import import AppImportApi, AppImportConfirmApi
|
from .app.app_import import AppImportApi, AppImportConfirmApi
|
||||||
from .explore.audio import ChatAudioApi, ChatTextApi
|
|
||||||
from .explore.completion import ChatApi, ChatStopApi, CompletionApi, CompletionStopApi
|
|
||||||
from .explore.conversation import (
|
|
||||||
ConversationApi,
|
|
||||||
ConversationListApi,
|
|
||||||
ConversationPinApi,
|
|
||||||
ConversationRenameApi,
|
|
||||||
ConversationUnPinApi,
|
|
||||||
)
|
|
||||||
from .explore.message import (
|
|
||||||
MessageFeedbackApi,
|
|
||||||
MessageListApi,
|
|
||||||
MessageMoreLikeThisApi,
|
|
||||||
MessageSuggestedQuestionApi,
|
|
||||||
)
|
|
||||||
from .explore.workflow import (
|
|
||||||
InstalledAppWorkflowRunApi,
|
|
||||||
InstalledAppWorkflowTaskStopApi,
|
|
||||||
)
|
|
||||||
from .files import FileApi, FilePreviewApi, FileSupportTypeApi
|
from .files import FileApi, FilePreviewApi, FileSupportTypeApi
|
||||||
from .remote_files import RemoteFileInfoApi, RemoteFileUploadApi
|
from .remote_files import RemoteFileInfoApi, RemoteFileUploadApi
|
||||||
|
|
||||||
@ -85,81 +66,15 @@ from .datasets import (
|
|||||||
|
|
||||||
# Import explore controllers
|
# Import explore controllers
|
||||||
from .explore import (
|
from .explore import (
|
||||||
|
audio,
|
||||||
|
completion,
|
||||||
|
conversation,
|
||||||
installed_app,
|
installed_app,
|
||||||
|
message,
|
||||||
parameter,
|
parameter,
|
||||||
recommended_app,
|
recommended_app,
|
||||||
saved_message,
|
saved_message,
|
||||||
)
|
workflow,
|
||||||
|
|
||||||
# Explore Audio
|
|
||||||
api.add_resource(ChatAudioApi, "/installed-apps/<uuid:installed_app_id>/audio-to-text", endpoint="installed_app_audio")
|
|
||||||
api.add_resource(ChatTextApi, "/installed-apps/<uuid:installed_app_id>/text-to-audio", endpoint="installed_app_text")
|
|
||||||
|
|
||||||
# Explore Completion
|
|
||||||
api.add_resource(
|
|
||||||
CompletionApi, "/installed-apps/<uuid:installed_app_id>/completion-messages", endpoint="installed_app_completion"
|
|
||||||
)
|
|
||||||
api.add_resource(
|
|
||||||
CompletionStopApi,
|
|
||||||
"/installed-apps/<uuid:installed_app_id>/completion-messages/<string:task_id>/stop",
|
|
||||||
endpoint="installed_app_stop_completion",
|
|
||||||
)
|
|
||||||
api.add_resource(
|
|
||||||
ChatApi, "/installed-apps/<uuid:installed_app_id>/chat-messages", endpoint="installed_app_chat_completion"
|
|
||||||
)
|
|
||||||
api.add_resource(
|
|
||||||
ChatStopApi,
|
|
||||||
"/installed-apps/<uuid:installed_app_id>/chat-messages/<string:task_id>/stop",
|
|
||||||
endpoint="installed_app_stop_chat_completion",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Explore Conversation
|
|
||||||
api.add_resource(
|
|
||||||
ConversationRenameApi,
|
|
||||||
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/name",
|
|
||||||
endpoint="installed_app_conversation_rename",
|
|
||||||
)
|
|
||||||
api.add_resource(
|
|
||||||
ConversationListApi, "/installed-apps/<uuid:installed_app_id>/conversations", endpoint="installed_app_conversations"
|
|
||||||
)
|
|
||||||
api.add_resource(
|
|
||||||
ConversationApi,
|
|
||||||
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>",
|
|
||||||
endpoint="installed_app_conversation",
|
|
||||||
)
|
|
||||||
api.add_resource(
|
|
||||||
ConversationPinApi,
|
|
||||||
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/pin",
|
|
||||||
endpoint="installed_app_conversation_pin",
|
|
||||||
)
|
|
||||||
api.add_resource(
|
|
||||||
ConversationUnPinApi,
|
|
||||||
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/unpin",
|
|
||||||
endpoint="installed_app_conversation_unpin",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Explore Message
|
|
||||||
api.add_resource(MessageListApi, "/installed-apps/<uuid:installed_app_id>/messages", endpoint="installed_app_messages")
|
|
||||||
api.add_resource(
|
|
||||||
MessageFeedbackApi,
|
|
||||||
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/feedbacks",
|
|
||||||
endpoint="installed_app_message_feedback",
|
|
||||||
)
|
|
||||||
api.add_resource(
|
|
||||||
MessageMoreLikeThisApi,
|
|
||||||
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/more-like-this",
|
|
||||||
endpoint="installed_app_more_like_this",
|
|
||||||
)
|
|
||||||
api.add_resource(
|
|
||||||
MessageSuggestedQuestionApi,
|
|
||||||
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/suggested-questions",
|
|
||||||
endpoint="installed_app_suggested_question",
|
|
||||||
)
|
|
||||||
# Explore Workflow
|
|
||||||
api.add_resource(InstalledAppWorkflowRunApi, "/installed-apps/<uuid:installed_app_id>/workflows/run")
|
|
||||||
api.add_resource(
|
|
||||||
InstalledAppWorkflowTaskStopApi, "/installed-apps/<uuid:installed_app_id>/workflows/tasks/<string:task_id>/stop"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Import tag controllers
|
# Import tag controllers
|
||||||
|
|||||||
@ -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 werkzeug.exceptions import NotFound, Unauthorized
|
from werkzeug.exceptions import NotFound, Unauthorized
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
@ -56,7 +56,7 @@ class InsertExploreAppListApi(Resource):
|
|||||||
|
|
||||||
app = App.query.filter(App.id == args["app_id"]).first()
|
app = App.query.filter(App.id == args["app_id"]).first()
|
||||||
if not app:
|
if not app:
|
||||||
raise NotFound(f"App '{args['app_id']}' is not found")
|
raise NotFound(f'App \'{args["app_id"]}\' is not found')
|
||||||
|
|
||||||
site = app.site
|
site = app.site
|
||||||
if not site:
|
if not site:
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
from typing import Any
|
import flask_restful
|
||||||
|
from flask_login import current_user
|
||||||
import flask_restful # type: ignore
|
|
||||||
from flask_login import current_user # type: ignore
|
|
||||||
from flask_restful import Resource, fields, marshal_with
|
from flask_restful import Resource, fields, marshal_with
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
@ -37,15 +35,14 @@ def _get_resource(resource_id, tenant_id, resource_model):
|
|||||||
class BaseApiKeyListResource(Resource):
|
class BaseApiKeyListResource(Resource):
|
||||||
method_decorators = [account_initialization_required, login_required, setup_required]
|
method_decorators = [account_initialization_required, login_required, setup_required]
|
||||||
|
|
||||||
resource_type: str | None = None
|
resource_type = None
|
||||||
resource_model: Any = None
|
resource_model = None
|
||||||
resource_id_field: str | None = None
|
resource_id_field = None
|
||||||
token_prefix: str | None = None
|
token_prefix = None
|
||||||
max_keys = 10
|
max_keys = 10
|
||||||
|
|
||||||
@marshal_with(api_key_list)
|
@marshal_with(api_key_list)
|
||||||
def get(self, resource_id):
|
def get(self, resource_id):
|
||||||
assert self.resource_id_field is not None, "resource_id_field must be set"
|
|
||||||
resource_id = str(resource_id)
|
resource_id = str(resource_id)
|
||||||
_get_resource(resource_id, current_user.current_tenant_id, self.resource_model)
|
_get_resource(resource_id, current_user.current_tenant_id, self.resource_model)
|
||||||
keys = (
|
keys = (
|
||||||
@ -57,7 +54,6 @@ class BaseApiKeyListResource(Resource):
|
|||||||
|
|
||||||
@marshal_with(api_key_fields)
|
@marshal_with(api_key_fields)
|
||||||
def post(self, resource_id):
|
def post(self, resource_id):
|
||||||
assert self.resource_id_field is not None, "resource_id_field must be set"
|
|
||||||
resource_id = str(resource_id)
|
resource_id = str(resource_id)
|
||||||
_get_resource(resource_id, current_user.current_tenant_id, self.resource_model)
|
_get_resource(resource_id, current_user.current_tenant_id, self.resource_model)
|
||||||
if not current_user.is_editor:
|
if not current_user.is_editor:
|
||||||
@ -90,12 +86,11 @@ class BaseApiKeyListResource(Resource):
|
|||||||
class BaseApiKeyResource(Resource):
|
class BaseApiKeyResource(Resource):
|
||||||
method_decorators = [account_initialization_required, login_required, setup_required]
|
method_decorators = [account_initialization_required, login_required, setup_required]
|
||||||
|
|
||||||
resource_type: str | None = None
|
resource_type = None
|
||||||
resource_model: Any = None
|
resource_model = None
|
||||||
resource_id_field: str | None = None
|
resource_id_field = None
|
||||||
|
|
||||||
def delete(self, resource_id, api_key_id):
|
def delete(self, resource_id, api_key_id):
|
||||||
assert self.resource_id_field is not None, "resource_id_field must be set"
|
|
||||||
resource_id = str(resource_id)
|
resource_id = str(resource_id)
|
||||||
api_key_id = str(api_key_id)
|
api_key_id = str(api_key_id)
|
||||||
_get_resource(resource_id, current_user.current_tenant_id, self.resource_model)
|
_get_resource(resource_id, current_user.current_tenant_id, self.resource_model)
|
||||||
|
|||||||
@ -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
|
||||||
@ -110,7 +110,7 @@ class AnnotationListApi(Resource):
|
|||||||
|
|
||||||
page = request.args.get("page", default=1, type=int)
|
page = request.args.get("page", default=1, type=int)
|
||||||
limit = request.args.get("limit", default=20, type=int)
|
limit = request.args.get("limit", default=20, type=int)
|
||||||
keyword = request.args.get("keyword", default="", type=str)
|
keyword = request.args.get("keyword", default=None, type=str)
|
||||||
|
|
||||||
app_id = str(app_id)
|
app_id = str(app_id)
|
||||||
annotation_list, total = AppAnnotationService.get_annotation_list_by_app_id(app_id, page, limit, keyword)
|
annotation_list, total = AppAnnotationService.get_annotation_list_by_app_id(app_id, page, limit, keyword)
|
||||||
|
|||||||
@ -1,29 +1,31 @@
|
|||||||
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, # type: ignore
|
from flask_restful import Resource, inputs, marshal, marshal_with, reqparse
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
from controllers.console.wraps import (account_initialization_required,
|
from controllers.console.wraps import (
|
||||||
cloud_edition_billing_resource_check,
|
account_initialization_required,
|
||||||
enterprise_license_required,
|
cloud_edition_billing_resource_check,
|
||||||
setup_required)
|
enterprise_license_required,
|
||||||
|
setup_required,
|
||||||
|
)
|
||||||
from core.ops.ops_trace_manager import OpsTraceManager
|
from core.ops.ops_trace_manager import OpsTraceManager
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from fields.app_fields import (app_detail_fields, app_detail_fields_with_site,
|
from fields.app_fields import (
|
||||||
app_pagination_fields)
|
app_detail_fields,
|
||||||
|
app_detail_fields_with_site,
|
||||||
|
app_pagination_fields,
|
||||||
|
)
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from models import Account, App
|
from models import Account, App
|
||||||
from services.app_dsl_service import AppDslService, ImportMode
|
from services.app_dsl_service import AppDslService, ImportMode
|
||||||
from services.app_service import AppService
|
from services.app_service import AppService
|
||||||
from services.enterprise.enterprise_service import EnterpriseService
|
|
||||||
from services.feature_service import FeatureService
|
|
||||||
|
|
||||||
ALLOW_CREATE_APP_MODES = ["chat", "agent-chat", "advanced-chat", "workflow", "completion"]
|
ALLOW_CREATE_APP_MODES = ["chat", "agent-chat", "advanced-chat", "workflow", "completion"]
|
||||||
|
|
||||||
@ -55,27 +57,16 @@ class AppListApi(Resource):
|
|||||||
)
|
)
|
||||||
parser.add_argument("name", type=str, location="args", required=False)
|
parser.add_argument("name", type=str, location="args", required=False)
|
||||||
parser.add_argument("tag_ids", type=uuid_list, location="args", required=False)
|
parser.add_argument("tag_ids", type=uuid_list, location="args", required=False)
|
||||||
parser.add_argument("is_created_by_me", type=inputs.boolean, location="args", required=False)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# get app list
|
# get app list
|
||||||
app_service = AppService()
|
app_service = AppService()
|
||||||
app_pagination = app_service.get_paginate_apps(current_user.id, current_user.current_tenant_id, args)
|
app_pagination = app_service.get_paginate_apps(current_user.current_tenant_id, args)
|
||||||
if not app_pagination:
|
if not app_pagination:
|
||||||
return {"data": [], "total": 0, "page": 1, "limit": 20, "has_more": False}
|
return {"data": [], "total": 0, "page": 1, "limit": 20, "has_more": False}
|
||||||
|
|
||||||
if FeatureService.get_system_features().webapp_auth.enabled:
|
return marshal(app_pagination, app_pagination_fields)
|
||||||
app_ids = [str(app.id) for app in app_pagination.items]
|
|
||||||
res = EnterpriseService.WebAppAuth.batch_get_app_access_mode_by_id(app_ids=app_ids)
|
|
||||||
if len(res) != len(app_ids):
|
|
||||||
raise BadRequest("Invalid app id in webapp auth")
|
|
||||||
|
|
||||||
for app in app_pagination.items:
|
|
||||||
if str(app.id) in res:
|
|
||||||
app.access_mode = res[str(app.id)].access_mode
|
|
||||||
|
|
||||||
return marshal(app_pagination, app_pagination_fields), 200
|
|
||||||
|
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@ -119,10 +110,6 @@ class AppApi(Resource):
|
|||||||
|
|
||||||
app_model = app_service.get_app(app_model)
|
app_model = app_service.get_app(app_model)
|
||||||
|
|
||||||
if FeatureService.get_system_features().webapp_auth.enabled:
|
|
||||||
app_setting = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id=str(app_model.id))
|
|
||||||
app_model.access_mode = app_setting.access_mode
|
|
||||||
|
|
||||||
return app_model
|
return app_model
|
||||||
|
|
||||||
@setup_required
|
@setup_required
|
||||||
|
|||||||
@ -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
|
||||||
@ -22,7 +22,7 @@ from controllers.console.wraps import account_initialization_required, setup_req
|
|||||||
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
|
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
|
||||||
from core.model_runtime.errors.invoke import InvokeError
|
from core.model_runtime.errors.invoke import InvokeError
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from models import App, AppMode
|
from models.model import AppMode
|
||||||
from services.audio_service import AudioService
|
from services.audio_service import AudioService
|
||||||
from services.errors.audio import (
|
from services.errors.audio import (
|
||||||
AudioTooLargeServiceError,
|
AudioTooLargeServiceError,
|
||||||
@ -79,7 +79,7 @@ class ChatMessageTextApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model
|
@get_app_model
|
||||||
def post(self, app_model: App):
|
def post(self, app_model):
|
||||||
from werkzeug.exceptions import InternalServerError
|
from werkzeug.exceptions import InternalServerError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -98,13 +98,9 @@ class ChatMessageTextApi(Resource):
|
|||||||
and app_model.workflow.features_dict
|
and app_model.workflow.features_dict
|
||||||
):
|
):
|
||||||
text_to_speech = app_model.workflow.features_dict.get("text_to_speech")
|
text_to_speech = app_model.workflow.features_dict.get("text_to_speech")
|
||||||
if text_to_speech is None:
|
|
||||||
raise ValueError("TTS is not enabled")
|
|
||||||
voice = args.get("voice") or text_to_speech.get("voice")
|
voice = args.get("voice") or text_to_speech.get("voice")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if app_model.app_model_config is None:
|
|
||||||
raise ValueError("AppModelConfig not found")
|
|
||||||
voice = args.get("voice") or app_model.app_model_config.text_to_speech_dict.get("voice")
|
voice = args.get("voice") or app_model.app_model_config.text_to_speech_dict.get("voice")
|
||||||
except Exception:
|
except Exception:
|
||||||
voice = None
|
voice = None
|
||||||
|
|||||||
@ -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
|
||||||
@ -20,6 +20,7 @@ from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpErr
|
|||||||
from core.app.apps.base_app_queue_manager import AppQueueManager
|
from core.app.apps.base_app_queue_manager import AppQueueManager
|
||||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||||
from core.errors.error import (
|
from core.errors.error import (
|
||||||
|
AppInvokeQuotaExceededError,
|
||||||
ModelCurrentlyNotSupportError,
|
ModelCurrentlyNotSupportError,
|
||||||
ProviderTokenNotInitError,
|
ProviderTokenNotInitError,
|
||||||
QuotaExceededError,
|
QuotaExceededError,
|
||||||
@ -75,7 +76,7 @@ class CompletionMessageApi(Resource):
|
|||||||
raise ProviderModelCurrentlyNotSupportError()
|
raise ProviderModelCurrentlyNotSupportError()
|
||||||
except InvokeError as e:
|
except InvokeError as e:
|
||||||
raise CompletionRequestError(e.description)
|
raise CompletionRequestError(e.description)
|
||||||
except ValueError as e:
|
except (ValueError, AppInvokeQuotaExceededError) as e:
|
||||||
raise e
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("internal server error.")
|
logging.exception("internal server error.")
|
||||||
@ -140,7 +141,7 @@ class ChatMessageApi(Resource):
|
|||||||
raise InvokeRateLimitHttpError(ex.description)
|
raise InvokeRateLimitHttpError(ex.description)
|
||||||
except InvokeError as e:
|
except InvokeError as e:
|
||||||
raise CompletionRequestError(e.description)
|
raise CompletionRequestError(e.description)
|
||||||
except ValueError as e:
|
except (ValueError, AppInvokeQuotaExceededError) as e:
|
||||||
raise e
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("internal server error.")
|
logging.exception("internal server error.")
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
import pytz # pip install pytz
|
import 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
|
||||||
@ -77,9 +77,8 @@ class CompletionConversationApi(Resource):
|
|||||||
|
|
||||||
query = query.where(Conversation.created_at < end_datetime_utc)
|
query = query.where(Conversation.created_at < end_datetime_utc)
|
||||||
|
|
||||||
# FIXME, the type ignore in this file
|
|
||||||
if args["annotation_status"] == "annotated":
|
if args["annotation_status"] == "annotated":
|
||||||
query = query.options(joinedload(Conversation.message_annotations)).join( # type: ignore
|
query = query.options(joinedload(Conversation.message_annotations)).join(
|
||||||
MessageAnnotation, MessageAnnotation.conversation_id == Conversation.id
|
MessageAnnotation, MessageAnnotation.conversation_id == Conversation.id
|
||||||
)
|
)
|
||||||
elif args["annotation_status"] == "not_annotated":
|
elif args["annotation_status"] == "not_annotated":
|
||||||
@ -223,7 +222,7 @@ class ChatConversationApi(Resource):
|
|||||||
query = query.where(Conversation.created_at <= end_datetime_utc)
|
query = query.where(Conversation.created_at <= end_datetime_utc)
|
||||||
|
|
||||||
if args["annotation_status"] == "annotated":
|
if args["annotation_status"] == "annotated":
|
||||||
query = query.options(joinedload(Conversation.message_annotations)).join( # type: ignore
|
query = query.options(joinedload(Conversation.message_annotations)).join(
|
||||||
MessageAnnotation, MessageAnnotation.conversation_id == Conversation.id
|
MessageAnnotation, MessageAnnotation.conversation_id == Conversation.id
|
||||||
)
|
)
|
||||||
elif args["annotation_status"] == "not_annotated":
|
elif args["annotation_status"] == "not_annotated":
|
||||||
@ -235,7 +234,7 @@ class ChatConversationApi(Resource):
|
|||||||
|
|
||||||
if args["message_count_gte"] and args["message_count_gte"] >= 1:
|
if args["message_count_gte"] and args["message_count_gte"] >= 1:
|
||||||
query = (
|
query = (
|
||||||
query.options(joinedload(Conversation.messages)) # type: ignore
|
query.options(joinedload(Conversation.messages))
|
||||||
.join(Message, Message.conversation_id == Conversation.id)
|
.join(Message, Message.conversation_id == Conversation.id)
|
||||||
.group_by(Conversation.id)
|
.group_by(Conversation.id)
|
||||||
.having(func.count(Message.id) >= args["message_count_gte"])
|
.having(func.count(Message.id) >= args["message_count_gte"])
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
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
|
||||||
@ -27,9 +26,7 @@ class ModelConfigResource(Resource):
|
|||||||
"""Modify app model config"""
|
"""Modify app model config"""
|
||||||
# validate config
|
# validate config
|
||||||
model_configuration = AppModelConfigService.validate_configuration(
|
model_configuration = AppModelConfigService.validate_configuration(
|
||||||
tenant_id=current_user.current_tenant_id,
|
tenant_id=current_user.current_tenant_id, config=request.json, app_mode=AppMode.value_of(app_model.mode)
|
||||||
config=cast(dict, request.json),
|
|
||||||
app_mode=AppMode.value_of(app_model.mode),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
new_app_model_config = AppModelConfig(
|
new_app_model_config = AppModelConfig(
|
||||||
@ -41,11 +38,9 @@ class ModelConfigResource(Resource):
|
|||||||
|
|
||||||
if app_model.mode == AppMode.AGENT_CHAT.value or app_model.is_agent:
|
if app_model.mode == AppMode.AGENT_CHAT.value or app_model.is_agent:
|
||||||
# get original app model config
|
# get original app model config
|
||||||
original_app_model_config = (
|
original_app_model_config: AppModelConfig = (
|
||||||
db.session.query(AppModelConfig).filter(AppModelConfig.id == app_model.app_model_config_id).first()
|
db.session.query(AppModelConfig).filter(AppModelConfig.id == app_model.app_model_config_id).first()
|
||||||
)
|
)
|
||||||
if original_app_model_config is None:
|
|
||||||
raise ValueError("Original app model config not found")
|
|
||||||
agent_mode = original_app_model_config.agent_mode_dict
|
agent_mode = original_app_model_config.agent_mode_dict
|
||||||
# decrypt agent tool parameters if it's secret-input
|
# decrypt agent tool parameters if it's secret-input
|
||||||
parameter_map = {}
|
parameter_map = {}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
from constants.languages import supported_language
|
from constants.languages import supported_language
|
||||||
@ -50,7 +50,7 @@ class AppSite(Resource):
|
|||||||
if not current_user.is_editor:
|
if not current_user.is_editor:
|
||||||
raise Forbidden()
|
raise Forbidden()
|
||||||
|
|
||||||
site = Site.query.filter(Site.app_id == app_model.id).one_or_404()
|
site = db.session.query(Site).filter(Site.app_id == app_model.id).one_or_404()
|
||||||
|
|
||||||
for attr_name in [
|
for attr_name in [
|
||||||
"title",
|
"title",
|
||||||
|
|||||||
@ -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
|
||||||
@ -273,7 +273,8 @@ FROM
|
|||||||
messages m
|
messages m
|
||||||
ON c.id = m.conversation_id
|
ON c.id = m.conversation_id
|
||||||
WHERE
|
WHERE
|
||||||
c.app_id = :app_id"""
|
c.override_model_configs IS NULL
|
||||||
|
AND c.app_id = :app_id"""
|
||||||
arg_dict = {"tz": account.timezone, "app_id": app_model.id}
|
arg_dict = {"tz": account.timezone, "app_id": app_model.id}
|
||||||
|
|
||||||
timezone = pytz.timezone(account.timezone)
|
timezone = pytz.timezone(account.timezone)
|
||||||
|
|||||||
@ -2,11 +2,10 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
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, marshal_with, reqparse
|
||||||
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
from configs import dify_config
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.app.error import ConversationCompletedError, DraftWorkflowNotExist, DraftWorkflowNotSync
|
from controllers.console.app.error import ConversationCompletedError, DraftWorkflowNotExist, DraftWorkflowNotSync
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
@ -14,7 +13,7 @@ from controllers.console.wraps import account_initialization_required, setup_req
|
|||||||
from core.app.apps.base_app_queue_manager import AppQueueManager
|
from core.app.apps.base_app_queue_manager import AppQueueManager
|
||||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||||
from factories import variable_factory
|
from factories import variable_factory
|
||||||
from fields.workflow_fields import workflow_fields, workflow_pagination_fields
|
from fields.workflow_fields import workflow_fields
|
||||||
from fields.workflow_run_fields import workflow_run_node_execution_fields
|
from fields.workflow_run_fields import workflow_run_node_execution_fields
|
||||||
from libs import helper
|
from libs import helper
|
||||||
from libs.helper import TimestampField, uuid_value
|
from libs.helper import TimestampField, uuid_value
|
||||||
@ -427,46 +426,7 @@ class ConvertToWorkflowApi(Resource):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class WorkflowConfigApi(Resource):
|
|
||||||
"""Resource for workflow configuration."""
|
|
||||||
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
|
||||||
def get(self, app_model: App):
|
|
||||||
return {
|
|
||||||
"parallel_depth_limit": dify_config.WORKFLOW_PARALLEL_DEPTH_LIMIT,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PublishedAllWorkflowApi(Resource):
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
|
|
||||||
@marshal_with(workflow_pagination_fields)
|
|
||||||
def get(self, app_model: App):
|
|
||||||
"""
|
|
||||||
Get published workflows
|
|
||||||
"""
|
|
||||||
if not current_user.is_editor:
|
|
||||||
raise Forbidden()
|
|
||||||
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
parser.add_argument("page", type=inputs.int_range(1, 99999), required=False, default=1, location="args")
|
|
||||||
parser.add_argument("limit", type=inputs.int_range(1, 100), required=False, default=20, location="args")
|
|
||||||
args = parser.parse_args()
|
|
||||||
page = args.get("page")
|
|
||||||
limit = args.get("limit")
|
|
||||||
workflow_service = WorkflowService()
|
|
||||||
workflows, has_more = workflow_service.get_all_published_workflow(app_model=app_model, page=page, limit=limit)
|
|
||||||
|
|
||||||
return {"items": workflows, "page": page, "limit": limit, "has_more": has_more}
|
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(DraftWorkflowApi, "/apps/<uuid:app_id>/workflows/draft")
|
api.add_resource(DraftWorkflowApi, "/apps/<uuid:app_id>/workflows/draft")
|
||||||
api.add_resource(WorkflowConfigApi, "/apps/<uuid:app_id>/workflows/draft/config")
|
|
||||||
api.add_resource(AdvancedChatDraftWorkflowRunApi, "/apps/<uuid:app_id>/advanced-chat/workflows/draft/run")
|
api.add_resource(AdvancedChatDraftWorkflowRunApi, "/apps/<uuid:app_id>/advanced-chat/workflows/draft/run")
|
||||||
api.add_resource(DraftWorkflowRunApi, "/apps/<uuid:app_id>/workflows/draft/run")
|
api.add_resource(DraftWorkflowRunApi, "/apps/<uuid:app_id>/workflows/draft/run")
|
||||||
api.add_resource(WorkflowTaskStopApi, "/apps/<uuid:app_id>/workflow-runs/tasks/<string:task_id>/stop")
|
api.add_resource(WorkflowTaskStopApi, "/apps/<uuid:app_id>/workflow-runs/tasks/<string:task_id>/stop")
|
||||||
@ -479,7 +439,6 @@ api.add_resource(
|
|||||||
WorkflowDraftRunIterationNodeApi, "/apps/<uuid:app_id>/workflows/draft/iteration/nodes/<string:node_id>/run"
|
WorkflowDraftRunIterationNodeApi, "/apps/<uuid:app_id>/workflows/draft/iteration/nodes/<string:node_id>/run"
|
||||||
)
|
)
|
||||||
api.add_resource(PublishedWorkflowApi, "/apps/<uuid:app_id>/workflows/publish")
|
api.add_resource(PublishedWorkflowApi, "/apps/<uuid:app_id>/workflows/publish")
|
||||||
api.add_resource(PublishedAllWorkflowApi, "/apps/<uuid:app_id>/workflows")
|
|
||||||
api.add_resource(DefaultBlockConfigsApi, "/apps/<uuid:app_id>/workflows/default-workflow-block-configs")
|
api.add_resource(DefaultBlockConfigsApi, "/apps/<uuid:app_id>/workflows/default-workflow-block-configs")
|
||||||
api.add_resource(
|
api.add_resource(
|
||||||
DefaultBlockConfigApi, "/apps/<uuid:app_id>/workflows/default-workflow-block-configs/<string:block_type>"
|
DefaultBlockConfigApi, "/apps/<uuid:app_id>/workflows/default-workflow-block-configs/<string:block_type>"
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -5,10 +5,11 @@ from typing import Optional, Union
|
|||||||
from controllers.console.app.error import AppNotFoundError
|
from controllers.console.app.error import AppNotFoundError
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.login import current_user
|
from libs.login import current_user
|
||||||
from models import App, AppMode
|
from models import App
|
||||||
|
from models.model import AppMode
|
||||||
|
|
||||||
|
|
||||||
def get_app_model(view: Optional[Callable] = None, *, mode: Union[AppMode, list[AppMode], None] = None):
|
def get_app_model(view: Optional[Callable] = None, *, mode: Union[AppMode, list[AppMode]] = None):
|
||||||
def decorator(view_func):
|
def decorator(view_func):
|
||||||
@wraps(view_func)
|
@wraps(view_func)
|
||||||
def decorated_view(*args, **kwargs):
|
def decorated_view(*args, **kwargs):
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
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
|
||||||
from controllers.console.error import AlreadyActivateError
|
from controllers.console.error import AlreadyActivateError
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.helper import StrLen, email, extract_remote_ip, timezone
|
from libs.helper import StrLen, email, extract_remote_ip, timezone
|
||||||
from models.account import AccountStatus
|
from models.account import AccountStatus, Tenant
|
||||||
from services.account_service import AccountService, RegisterService
|
from services.account_service import AccountService, RegisterService
|
||||||
|
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ class ActivateCheckApi(Resource):
|
|||||||
invitation = RegisterService.get_invitation_if_token_valid(workspaceId, reg_email, token)
|
invitation = RegisterService.get_invitation_if_token_valid(workspaceId, reg_email, token)
|
||||||
if invitation:
|
if invitation:
|
||||||
data = invitation.get("data", {})
|
data = invitation.get("data", {})
|
||||||
tenant = invitation.get("tenant", None)
|
tenant: Tenant = invitation.get("tenant", None)
|
||||||
workspace_name = tenant.name if tenant else None
|
workspace_name = tenant.name if tenant else None
|
||||||
workspace_id = tenant.id if tenant else None
|
workspace_id = tenant.id if tenant else None
|
||||||
invitee_email = data.get("email") if data else None
|
invitee_email = data.get("email") if data else None
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import logging
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from flask import current_app, redirect, request
|
from flask import current_app, redirect, request
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource # type: ignore
|
from flask_restful import Resource
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
@ -17,8 +17,8 @@ from ..wraps import account_initialization_required, setup_required
|
|||||||
def get_oauth_providers():
|
def get_oauth_providers():
|
||||||
with current_app.app_context():
|
with current_app.app_context():
|
||||||
notion_oauth = NotionOAuth(
|
notion_oauth = NotionOAuth(
|
||||||
client_id=dify_config.NOTION_CLIENT_ID or "",
|
client_id=dify_config.NOTION_CLIENT_ID,
|
||||||
client_secret=dify_config.NOTION_CLIENT_SECRET or "",
|
client_secret=dify_config.NOTION_CLIENT_SECRET,
|
||||||
redirect_uri=dify_config.CONSOLE_API_URL + "/console/api/oauth/data-source/callback/notion",
|
redirect_uri=dify_config.CONSOLE_API_URL + "/console/api/oauth/data-source/callback/notion",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -53,9 +53,3 @@ class EmailCodeLoginRateLimitExceededError(BaseHTTPException):
|
|||||||
error_code = "email_code_login_rate_limit_exceeded"
|
error_code = "email_code_login_rate_limit_exceeded"
|
||||||
description = "Too many login emails have been sent. Please try again in 5 minutes."
|
description = "Too many login emails have been sent. Please try again in 5 minutes."
|
||||||
code = 429
|
code = 429
|
||||||
|
|
||||||
|
|
||||||
class EmailCodeAccountDeletionRateLimitExceededError(BaseHTTPException):
|
|
||||||
error_code = "email_code_account_deletion_rate_limit_exceeded"
|
|
||||||
description = "Too many account deletion emails have been sent. Please try again in 5 minutes."
|
|
||||||
code = 429
|
|
||||||
|
|||||||
@ -2,31 +2,30 @@ 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 constants.languages import languages
|
from constants.languages import languages
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.auth.error import (EmailCodeError, InvalidEmailError,
|
from controllers.console.auth.error import (
|
||||||
InvalidTokenError,
|
EmailCodeError,
|
||||||
PasswordMismatchError)
|
InvalidEmailError,
|
||||||
from controllers.console.error import (AccountInFreezeError, AccountNotFound,
|
InvalidTokenError,
|
||||||
EmailSendIpLimitError)
|
PasswordMismatchError,
|
||||||
from controllers.console.wraps import (email_password_login_enabled,
|
)
|
||||||
setup_required)
|
from controllers.console.error import AccountNotFound, EmailSendIpLimitError
|
||||||
|
from controllers.console.wraps import setup_required
|
||||||
from events.tenant_event import tenant_was_created
|
from events.tenant_event import tenant_was_created
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.helper import email, extract_remote_ip
|
from libs.helper import email, extract_remote_ip
|
||||||
from libs.password import hash_password, valid_password
|
from libs.password import hash_password, valid_password
|
||||||
from models.account import Account
|
from models.account import Account
|
||||||
from services.account_service import AccountService, TenantService
|
from services.account_service import AccountService, TenantService
|
||||||
from services.errors.account import AccountRegisterError
|
|
||||||
from services.errors.workspace import WorkSpaceNotAllowedCreateError
|
from services.errors.workspace import WorkSpaceNotAllowedCreateError
|
||||||
from services.feature_service import FeatureService
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
|
|
||||||
class ForgotPasswordSendEmailApi(Resource):
|
class ForgotPasswordSendEmailApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
@email_password_login_enabled
|
|
||||||
def post(self):
|
def post(self):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("email", type=email, required=True, location="json")
|
parser.add_argument("email", type=email, required=True, location="json")
|
||||||
@ -58,7 +57,6 @@ class ForgotPasswordSendEmailApi(Resource):
|
|||||||
|
|
||||||
class ForgotPasswordCheckApi(Resource):
|
class ForgotPasswordCheckApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
@email_password_login_enabled
|
|
||||||
def post(self):
|
def post(self):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("email", type=str, required=True, location="json")
|
parser.add_argument("email", type=str, required=True, location="json")
|
||||||
@ -78,20 +76,11 @@ class ForgotPasswordCheckApi(Resource):
|
|||||||
if args["code"] != token_data.get("code"):
|
if args["code"] != token_data.get("code"):
|
||||||
raise EmailCodeError()
|
raise EmailCodeError()
|
||||||
|
|
||||||
# Verified, revoke the first token
|
return {"is_valid": True, "email": token_data.get("email")}
|
||||||
AccountService.revoke_reset_password_token(args["token"])
|
|
||||||
|
|
||||||
# Refresh token data by generating a new token
|
|
||||||
_, new_token = AccountService.generate_reset_password_token(
|
|
||||||
user_email, code=args["code"], additional_data={"phase": "reset"}
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"is_valid": True, "email": token_data.get("email"), "token": new_token}
|
|
||||||
|
|
||||||
|
|
||||||
class ForgotPasswordResetApi(Resource):
|
class ForgotPasswordResetApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
@email_password_login_enabled
|
|
||||||
def post(self):
|
def post(self):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("token", type=str, required=True, nullable=False, location="json")
|
parser.add_argument("token", type=str, required=True, nullable=False, location="json")
|
||||||
@ -110,9 +99,6 @@ class ForgotPasswordResetApi(Resource):
|
|||||||
|
|
||||||
if reset_data is None:
|
if reset_data is None:
|
||||||
raise InvalidTokenError()
|
raise InvalidTokenError()
|
||||||
# Must use token in reset phase
|
|
||||||
if reset_data.get("phase", "") != "reset":
|
|
||||||
raise InvalidTokenError()
|
|
||||||
|
|
||||||
AccountService.revoke_reset_password_token(token)
|
AccountService.revoke_reset_password_token(token)
|
||||||
|
|
||||||
@ -136,15 +122,13 @@ class ForgotPasswordResetApi(Resource):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
account = AccountService.create_account_and_tenant(
|
account = AccountService.create_account_and_tenant(
|
||||||
email=reset_data.get("email", ""),
|
email=reset_data.get("email"),
|
||||||
name=reset_data.get("email", ""),
|
name=reset_data.get("email"),
|
||||||
password=password_confirm,
|
password=password_confirm,
|
||||||
interface_language=languages[0],
|
interface_language=languages[0],
|
||||||
)
|
)
|
||||||
except WorkSpaceNotAllowedCreateError:
|
except WorkSpaceNotAllowedCreateError:
|
||||||
pass
|
pass
|
||||||
except AccountRegisterError as are:
|
|
||||||
raise AccountInFreezeError()
|
|
||||||
|
|
||||||
return {"result": "success"}
|
return {"result": "success"}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
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 constants.languages import languages
|
from constants.languages import languages
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.auth.error import (
|
from controllers.console.auth.error import (
|
||||||
@ -17,19 +16,16 @@ from controllers.console.auth.error import (
|
|||||||
)
|
)
|
||||||
from controllers.console.error import (
|
from controllers.console.error import (
|
||||||
AccountBannedError,
|
AccountBannedError,
|
||||||
AccountInFreezeError,
|
|
||||||
AccountNotFound,
|
AccountNotFound,
|
||||||
EmailSendIpLimitError,
|
EmailSendIpLimitError,
|
||||||
NotAllowedCreateWorkspace,
|
NotAllowedCreateWorkspace,
|
||||||
)
|
)
|
||||||
from controllers.console.wraps import email_password_login_enabled, setup_required
|
from controllers.console.wraps import setup_required
|
||||||
from events.tenant_event import tenant_was_created
|
from events.tenant_event import tenant_was_created
|
||||||
from libs.helper import email, extract_remote_ip
|
from libs.helper import email, extract_remote_ip
|
||||||
from libs.password import valid_password
|
from libs.password import valid_password
|
||||||
from models.account import Account
|
from models.account import Account
|
||||||
from services.account_service import AccountService, RegisterService, TenantService
|
from services.account_service import AccountService, RegisterService, TenantService
|
||||||
from services.billing_service import BillingService
|
|
||||||
from services.errors.account import AccountRegisterError
|
|
||||||
from services.errors.workspace import WorkSpaceNotAllowedCreateError
|
from services.errors.workspace import WorkSpaceNotAllowedCreateError
|
||||||
from services.feature_service import FeatureService
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
@ -38,7 +34,6 @@ class LoginApi(Resource):
|
|||||||
"""Resource for user login."""
|
"""Resource for user login."""
|
||||||
|
|
||||||
@setup_required
|
@setup_required
|
||||||
@email_password_login_enabled
|
|
||||||
def post(self):
|
def post(self):
|
||||||
"""Authenticate user and login."""
|
"""Authenticate user and login."""
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
@ -49,9 +44,6 @@ class LoginApi(Resource):
|
|||||||
parser.add_argument("language", type=str, required=False, default="en-US", location="json")
|
parser.add_argument("language", type=str, required=False, default="en-US", location="json")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(args["email"]):
|
|
||||||
raise AccountInFreezeError()
|
|
||||||
|
|
||||||
is_login_error_rate_limit = AccountService.is_login_error_rate_limit(args["email"])
|
is_login_error_rate_limit = AccountService.is_login_error_rate_limit(args["email"])
|
||||||
if is_login_error_rate_limit:
|
if is_login_error_rate_limit:
|
||||||
raise EmailPasswordLoginLimitError()
|
raise EmailPasswordLoginLimitError()
|
||||||
@ -111,7 +103,6 @@ class LogoutApi(Resource):
|
|||||||
|
|
||||||
class ResetPasswordSendEmailApi(Resource):
|
class ResetPasswordSendEmailApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
@email_password_login_enabled
|
|
||||||
def post(self):
|
def post(self):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("email", type=email, required=True, location="json")
|
parser.add_argument("email", type=email, required=True, location="json")
|
||||||
@ -122,10 +113,8 @@ class ResetPasswordSendEmailApi(Resource):
|
|||||||
language = "zh-Hans"
|
language = "zh-Hans"
|
||||||
else:
|
else:
|
||||||
language = "en-US"
|
language = "en-US"
|
||||||
try:
|
|
||||||
account = AccountService.get_user_through_email(args["email"])
|
account = AccountService.get_user_through_email(args["email"])
|
||||||
except AccountRegisterError as are:
|
|
||||||
raise AccountInFreezeError()
|
|
||||||
if account is None:
|
if account is None:
|
||||||
if FeatureService.get_system_features().is_allow_register:
|
if FeatureService.get_system_features().is_allow_register:
|
||||||
token = AccountService.send_reset_password_email(email=args["email"], language=language)
|
token = AccountService.send_reset_password_email(email=args["email"], language=language)
|
||||||
@ -153,11 +142,8 @@ class EmailCodeLoginSendEmailApi(Resource):
|
|||||||
language = "zh-Hans"
|
language = "zh-Hans"
|
||||||
else:
|
else:
|
||||||
language = "en-US"
|
language = "en-US"
|
||||||
try:
|
|
||||||
account = AccountService.get_user_through_email(args["email"])
|
|
||||||
except AccountRegisterError as are:
|
|
||||||
raise AccountInFreezeError()
|
|
||||||
|
|
||||||
|
account = AccountService.get_user_through_email(args["email"])
|
||||||
if account is None:
|
if account is None:
|
||||||
if FeatureService.get_system_features().is_allow_register:
|
if FeatureService.get_system_features().is_allow_register:
|
||||||
token = AccountService.send_email_code_login_email(email=args["email"], language=language)
|
token = AccountService.send_email_code_login_email(email=args["email"], language=language)
|
||||||
@ -191,10 +177,7 @@ class EmailCodeLoginApi(Resource):
|
|||||||
raise EmailCodeError()
|
raise EmailCodeError()
|
||||||
|
|
||||||
AccountService.revoke_email_code_login_token(args["token"])
|
AccountService.revoke_email_code_login_token(args["token"])
|
||||||
try:
|
account = AccountService.get_user_through_email(user_email)
|
||||||
account = AccountService.get_user_through_email(user_email)
|
|
||||||
except AccountRegisterError as are:
|
|
||||||
raise AccountInFreezeError()
|
|
||||||
if account:
|
if account:
|
||||||
tenant = TenantService.get_join_tenants(account)
|
tenant = TenantService.get_join_tenants(account)
|
||||||
if not tenant:
|
if not tenant:
|
||||||
@ -213,8 +196,6 @@ class EmailCodeLoginApi(Resource):
|
|||||||
)
|
)
|
||||||
except WorkSpaceNotAllowedCreateError:
|
except WorkSpaceNotAllowedCreateError:
|
||||||
return NotAllowedCreateWorkspace()
|
return NotAllowedCreateWorkspace()
|
||||||
except AccountRegisterError as are:
|
|
||||||
raise AccountInFreezeError()
|
|
||||||
token_pair = AccountService.login(account, ip_address=extract_remote_ip(request))
|
token_pair = AccountService.login(account, ip_address=extract_remote_ip(request))
|
||||||
AccountService.reset_login_error_rate_limit(args["email"])
|
AccountService.reset_login_error_rate_limit(args["email"])
|
||||||
return {"result": "success", "data": token_pair.model_dump()}
|
return {"result": "success", "data": token_pair.model_dump()}
|
||||||
|
|||||||
@ -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 werkzeug.exceptions import Unauthorized
|
from werkzeug.exceptions import Unauthorized
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
@ -16,7 +16,7 @@ from libs.oauth import GitHubOAuth, GoogleOAuth, OAuthUserInfo
|
|||||||
from models import Account
|
from models import Account
|
||||||
from models.account import AccountStatus
|
from models.account import AccountStatus
|
||||||
from services.account_service import AccountService, RegisterService, TenantService
|
from services.account_service import AccountService, RegisterService, TenantService
|
||||||
from services.errors.account import AccountNotFoundError, AccountRegisterError
|
from services.errors.account import AccountNotFoundError
|
||||||
from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkSpaceNotFoundError
|
from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkSpaceNotFoundError
|
||||||
from services.feature_service import FeatureService
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
@ -76,9 +76,8 @@ class OAuthCallback(Resource):
|
|||||||
try:
|
try:
|
||||||
token = oauth_provider.get_access_token(code)
|
token = oauth_provider.get_access_token(code)
|
||||||
user_info = oauth_provider.get_user_info(token)
|
user_info = oauth_provider.get_user_info(token)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
error_text = e.response.text if e.response else str(e)
|
logging.exception(f"An error occurred during the OAuth process with {provider}: {e.response.text}")
|
||||||
logging.exception(f"An error occurred during the OAuth process with {provider}: {error_text}")
|
|
||||||
return {"error": "OAuth process failed"}, 400
|
return {"error": "OAuth process failed"}, 400
|
||||||
|
|
||||||
if invite_token and RegisterService.is_valid_invite_token(invite_token):
|
if invite_token and RegisterService.is_valid_invite_token(invite_token):
|
||||||
@ -99,8 +98,6 @@ class OAuthCallback(Resource):
|
|||||||
f"{dify_config.CONSOLE_WEB_URL}/signin"
|
f"{dify_config.CONSOLE_WEB_URL}/signin"
|
||||||
"?message=Workspace not found, please contact system admin to invite you to join in a workspace."
|
"?message=Workspace not found, please contact system admin to invite you to join in a workspace."
|
||||||
)
|
)
|
||||||
except AccountRegisterError as e:
|
|
||||||
return redirect(f"{dify_config.CONSOLE_WEB_URL}/signin?message={e.description}")
|
|
||||||
|
|
||||||
# Check account status
|
# Check account status
|
||||||
if account.status == AccountStatus.BANNED.value:
|
if account.status == AccountStatus.BANNED.value:
|
||||||
@ -132,7 +129,7 @@ class OAuthCallback(Resource):
|
|||||||
|
|
||||||
|
|
||||||
def _get_account_by_openid_or_email(provider: str, user_info: OAuthUserInfo) -> Optional[Account]:
|
def _get_account_by_openid_or_email(provider: str, user_info: OAuthUserInfo) -> Optional[Account]:
|
||||||
account: Optional[Account] = Account.get_by_openid(provider, user_info.id)
|
account = Account.get_by_openid(provider, user_info.id)
|
||||||
|
|
||||||
if not account:
|
if not account:
|
||||||
account = Account.query.filter_by(email=user_info.email).first()
|
account = Account.query.filter_by(email=user_info.email).first()
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
@ -218,7 +218,7 @@ class DataSourceNotionApi(Resource):
|
|||||||
args["doc_form"],
|
args["doc_form"],
|
||||||
args["doc_language"],
|
args["doc_language"],
|
||||||
)
|
)
|
||||||
return response.model_dump(), 200
|
return response, 200
|
||||||
|
|
||||||
|
|
||||||
class DataSourceNotionDatasetSyncApi(Resource):
|
class DataSourceNotionDatasetSyncApi(Resource):
|
||||||
|
|||||||
@ -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
|
||||||
@ -52,12 +52,12 @@ class DatasetListApi(Resource):
|
|||||||
# provider = request.args.get("provider", default="vendor")
|
# provider = request.args.get("provider", default="vendor")
|
||||||
search = request.args.get("keyword", default=None, type=str)
|
search = request.args.get("keyword", default=None, type=str)
|
||||||
tag_ids = request.args.getlist("tag_ids")
|
tag_ids = request.args.getlist("tag_ids")
|
||||||
include_all = request.args.get("include_all", default="false").lower() == "true"
|
|
||||||
if ids:
|
if ids:
|
||||||
datasets, total = DatasetService.get_datasets_by_ids(ids, current_user.current_tenant_id)
|
datasets, total = DatasetService.get_datasets_by_ids(ids, current_user.current_tenant_id)
|
||||||
else:
|
else:
|
||||||
datasets, total = DatasetService.get_datasets(
|
datasets, total = DatasetService.get_datasets(
|
||||||
page, limit, current_user.current_tenant_id, current_user, search, tag_ids, include_all
|
page, limit, current_user.current_tenant_id, current_user, search, tag_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
# check embedding setting
|
# check embedding setting
|
||||||
@ -457,14 +457,14 @@ class DatasetIndexingEstimateApi(Resource):
|
|||||||
)
|
)
|
||||||
except LLMBadRequestError:
|
except LLMBadRequestError:
|
||||||
raise ProviderNotInitializeError(
|
raise ProviderNotInitializeError(
|
||||||
"No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
|
"No Embedding Model available. Please configure a valid provider " "in the Settings -> Model Provider."
|
||||||
)
|
)
|
||||||
except ProviderTokenNotInitError as ex:
|
except ProviderTokenNotInitError as ex:
|
||||||
raise ProviderNotInitializeError(ex.description)
|
raise ProviderNotInitializeError(ex.description)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise IndexingEstimateError(str(e))
|
raise IndexingEstimateError(str(e))
|
||||||
|
|
||||||
return response.model_dump(), 200
|
return response, 200
|
||||||
|
|
||||||
|
|
||||||
class DatasetRelatedAppListApi(Resource):
|
class DatasetRelatedAppListApi(Resource):
|
||||||
@ -619,7 +619,9 @@ class DatasetRetrievalSettingApi(Resource):
|
|||||||
vector_type = dify_config.VECTOR_STORE
|
vector_type = dify_config.VECTOR_STORE
|
||||||
match vector_type:
|
match vector_type:
|
||||||
case (
|
case (
|
||||||
VectorType.RELYT
|
VectorType.MILVUS
|
||||||
|
| VectorType.RELYT
|
||||||
|
| VectorType.PGVECTOR
|
||||||
| VectorType.TIDB_VECTOR
|
| VectorType.TIDB_VECTOR
|
||||||
| VectorType.CHROMA
|
| VectorType.CHROMA
|
||||||
| VectorType.TENCENT
|
| VectorType.TENCENT
|
||||||
@ -638,12 +640,10 @@ class DatasetRetrievalSettingApi(Resource):
|
|||||||
| VectorType.MYSCALE
|
| VectorType.MYSCALE
|
||||||
| VectorType.ORACLE
|
| VectorType.ORACLE
|
||||||
| VectorType.ELASTICSEARCH
|
| VectorType.ELASTICSEARCH
|
||||||
| VectorType.ELASTICSEARCH_JA
|
|
||||||
| VectorType.PGVECTOR
|
| VectorType.PGVECTOR
|
||||||
| VectorType.TIDB_ON_QDRANT
|
| VectorType.TIDB_ON_QDRANT
|
||||||
| VectorType.LINDORM
|
| VectorType.LINDORM
|
||||||
| VectorType.COUCHBASE
|
| VectorType.COUCHBASE
|
||||||
| VectorType.MILVUS
|
|
||||||
):
|
):
|
||||||
return {
|
return {
|
||||||
"retrieval_method": [
|
"retrieval_method": [
|
||||||
@ -683,7 +683,6 @@ class DatasetRetrievalSettingMockApi(Resource):
|
|||||||
| VectorType.MYSCALE
|
| VectorType.MYSCALE
|
||||||
| VectorType.ORACLE
|
| VectorType.ORACLE
|
||||||
| VectorType.ELASTICSEARCH
|
| VectorType.ELASTICSEARCH
|
||||||
| VectorType.ELASTICSEARCH_JA
|
|
||||||
| VectorType.COUCHBASE
|
| VectorType.COUCHBASE
|
||||||
| VectorType.PGVECTOR
|
| VectorType.PGVECTOR
|
||||||
| VectorType.LINDORM
|
| VectorType.LINDORM
|
||||||
@ -734,18 +733,6 @@ class DatasetPermissionUserListApi(Resource):
|
|||||||
}, 200
|
}, 200
|
||||||
|
|
||||||
|
|
||||||
class DatasetAutoDisableLogApi(Resource):
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
def get(self, dataset_id):
|
|
||||||
dataset_id_str = str(dataset_id)
|
|
||||||
dataset = DatasetService.get_dataset(dataset_id_str)
|
|
||||||
if dataset is None:
|
|
||||||
raise NotFound("Dataset not found.")
|
|
||||||
return DatasetService.get_dataset_auto_disable_logs(dataset_id_str), 200
|
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(DatasetListApi, "/datasets")
|
api.add_resource(DatasetListApi, "/datasets")
|
||||||
api.add_resource(DatasetApi, "/datasets/<uuid:dataset_id>")
|
api.add_resource(DatasetApi, "/datasets/<uuid:dataset_id>")
|
||||||
api.add_resource(DatasetUseCheckApi, "/datasets/<uuid:dataset_id>/use-check")
|
api.add_resource(DatasetUseCheckApi, "/datasets/<uuid:dataset_id>/use-check")
|
||||||
@ -760,4 +747,3 @@ api.add_resource(DatasetApiBaseUrlApi, "/datasets/api-base-info")
|
|||||||
api.add_resource(DatasetRetrievalSettingApi, "/datasets/retrieval-setting")
|
api.add_resource(DatasetRetrievalSettingApi, "/datasets/retrieval-setting")
|
||||||
api.add_resource(DatasetRetrievalSettingMockApi, "/datasets/retrieval-setting/<string:vector_type>")
|
api.add_resource(DatasetRetrievalSettingMockApi, "/datasets/retrieval-setting/<string:vector_type>")
|
||||||
api.add_resource(DatasetPermissionUserListApi, "/datasets/<uuid:dataset_id>/permission-part-users")
|
api.add_resource(DatasetPermissionUserListApi, "/datasets/<uuid:dataset_id>/permission-part-users")
|
||||||
api.add_resource(DatasetAutoDisableLogApi, "/datasets/<uuid:dataset_id>/auto-disable-logs")
|
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
from argparse import ArgumentTypeError
|
from argparse import ArgumentTypeError
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
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 transformers.hf_argparser import string_to_bool # type: ignore
|
from transformers.hf_argparser import string_to_bool
|
||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
import services
|
import services
|
||||||
@ -52,7 +51,6 @@ from fields.document_fields import (
|
|||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from models import Dataset, DatasetProcessRule, Document, DocumentSegment, UploadFile
|
from models import Dataset, DatasetProcessRule, Document, DocumentSegment, UploadFile
|
||||||
from services.dataset_service import DatasetService, DocumentService
|
from services.dataset_service import DatasetService, DocumentService
|
||||||
from services.entities.knowledge_entities.knowledge_entities import KnowledgeConfig
|
|
||||||
from tasks.add_document_to_index_task import add_document_to_index_task
|
from tasks.add_document_to_index_task import add_document_to_index_task
|
||||||
from tasks.remove_document_from_index_task import remove_document_from_index_task
|
from tasks.remove_document_from_index_task import remove_document_from_index_task
|
||||||
|
|
||||||
@ -256,23 +254,20 @@ class DatasetDocumentListApi(Resource):
|
|||||||
parser.add_argument("duplicate", type=bool, default=True, nullable=False, location="json")
|
parser.add_argument("duplicate", type=bool, default=True, nullable=False, location="json")
|
||||||
parser.add_argument("original_document_id", type=str, required=False, location="json")
|
parser.add_argument("original_document_id", type=str, required=False, location="json")
|
||||||
parser.add_argument("doc_form", type=str, default="text_model", required=False, nullable=False, location="json")
|
parser.add_argument("doc_form", type=str, default="text_model", required=False, nullable=False, location="json")
|
||||||
parser.add_argument("retrieval_model", type=dict, required=False, nullable=False, location="json")
|
|
||||||
parser.add_argument("embedding_model", type=str, required=False, nullable=True, location="json")
|
|
||||||
parser.add_argument("embedding_model_provider", type=str, required=False, nullable=True, location="json")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"doc_language", type=str, default="English", required=False, nullable=False, location="json"
|
"doc_language", type=str, default="English", required=False, nullable=False, location="json"
|
||||||
)
|
)
|
||||||
|
parser.add_argument("retrieval_model", type=dict, required=False, nullable=False, location="json")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
knowledge_config = KnowledgeConfig(**args)
|
|
||||||
|
|
||||||
if not dataset.indexing_technique and not knowledge_config.indexing_technique:
|
if not dataset.indexing_technique and not args["indexing_technique"]:
|
||||||
raise ValueError("indexing_technique is required.")
|
raise ValueError("indexing_technique is required.")
|
||||||
|
|
||||||
# validate args
|
# validate args
|
||||||
DocumentService.document_create_args_validate(knowledge_config)
|
DocumentService.document_create_args_validate(args)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
documents, batch = DocumentService.save_document_with_dataset_id(dataset, knowledge_config, current_user)
|
documents, batch = DocumentService.save_document_with_dataset_id(dataset, args, current_user)
|
||||||
except ProviderTokenNotInitError as ex:
|
except ProviderTokenNotInitError as ex:
|
||||||
raise ProviderNotInitializeError(ex.description)
|
raise ProviderNotInitializeError(ex.description)
|
||||||
except QuotaExceededError:
|
except QuotaExceededError:
|
||||||
@ -282,25 +277,6 @@ class DatasetDocumentListApi(Resource):
|
|||||||
|
|
||||||
return {"documents": documents, "batch": batch}
|
return {"documents": documents, "batch": batch}
|
||||||
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
def delete(self, dataset_id):
|
|
||||||
dataset_id = str(dataset_id)
|
|
||||||
dataset = DatasetService.get_dataset(dataset_id)
|
|
||||||
if dataset is None:
|
|
||||||
raise NotFound("Dataset not found.")
|
|
||||||
# check user's model setting
|
|
||||||
DatasetService.check_dataset_model_setting(dataset)
|
|
||||||
|
|
||||||
try:
|
|
||||||
document_ids = request.args.getlist("document_id")
|
|
||||||
DocumentService.delete_documents(dataset, document_ids)
|
|
||||||
except services.errors.document.DocumentIndexingError:
|
|
||||||
raise DocumentIndexingError("Cannot delete document during indexing.")
|
|
||||||
|
|
||||||
return {"result": "success"}, 204
|
|
||||||
|
|
||||||
|
|
||||||
class DatasetInitApi(Resource):
|
class DatasetInitApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
@ -336,9 +312,9 @@ class DatasetInitApi(Resource):
|
|||||||
# The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
|
# The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
|
||||||
if not current_user.is_dataset_editor:
|
if not current_user.is_dataset_editor:
|
||||||
raise Forbidden()
|
raise Forbidden()
|
||||||
knowledge_config = KnowledgeConfig(**args)
|
|
||||||
if knowledge_config.indexing_technique == "high_quality":
|
if args["indexing_technique"] == "high_quality":
|
||||||
if knowledge_config.embedding_model is None or knowledge_config.embedding_model_provider is None:
|
if args["embedding_model"] is None or args["embedding_model_provider"] is None:
|
||||||
raise ValueError("embedding model and embedding model provider are required for high quality indexing.")
|
raise ValueError("embedding model and embedding model provider are required for high quality indexing.")
|
||||||
try:
|
try:
|
||||||
model_manager = ModelManager()
|
model_manager = ModelManager()
|
||||||
@ -350,17 +326,18 @@ class DatasetInitApi(Resource):
|
|||||||
)
|
)
|
||||||
except InvokeAuthorizationError:
|
except InvokeAuthorizationError:
|
||||||
raise ProviderNotInitializeError(
|
raise ProviderNotInitializeError(
|
||||||
"No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
|
"No Embedding Model available. Please configure a valid provider "
|
||||||
|
"in the Settings -> Model Provider."
|
||||||
)
|
)
|
||||||
except ProviderTokenNotInitError as ex:
|
except ProviderTokenNotInitError as ex:
|
||||||
raise ProviderNotInitializeError(ex.description)
|
raise ProviderNotInitializeError(ex.description)
|
||||||
|
|
||||||
# validate args
|
# validate args
|
||||||
DocumentService.document_create_args_validate(knowledge_config)
|
DocumentService.document_create_args_validate(args)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dataset, documents, batch = DocumentService.save_document_without_dataset_id(
|
dataset, documents, batch = DocumentService.save_document_without_dataset_id(
|
||||||
tenant_id=current_user.current_tenant_id, knowledge_config=knowledge_config, account=current_user
|
tenant_id=current_user.current_tenant_id, document_data=args, account=current_user
|
||||||
)
|
)
|
||||||
except ProviderTokenNotInitError as ex:
|
except ProviderTokenNotInitError as ex:
|
||||||
raise ProviderNotInitializeError(ex.description)
|
raise ProviderNotInitializeError(ex.description)
|
||||||
@ -413,7 +390,7 @@ class DocumentIndexingEstimateApi(DocumentResource):
|
|||||||
indexing_runner = IndexingRunner()
|
indexing_runner = IndexingRunner()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
estimate_response = indexing_runner.indexing_estimate(
|
response = indexing_runner.indexing_estimate(
|
||||||
current_user.current_tenant_id,
|
current_user.current_tenant_id,
|
||||||
[extract_setting],
|
[extract_setting],
|
||||||
data_process_rule_dict,
|
data_process_rule_dict,
|
||||||
@ -421,7 +398,6 @@ class DocumentIndexingEstimateApi(DocumentResource):
|
|||||||
"English",
|
"English",
|
||||||
dataset_id,
|
dataset_id,
|
||||||
)
|
)
|
||||||
return estimate_response.model_dump(), 200
|
|
||||||
except LLMBadRequestError:
|
except LLMBadRequestError:
|
||||||
raise ProviderNotInitializeError(
|
raise ProviderNotInitializeError(
|
||||||
"No Embedding Model available. Please configure a valid provider "
|
"No Embedding Model available. Please configure a valid provider "
|
||||||
@ -432,7 +408,7 @@ class DocumentIndexingEstimateApi(DocumentResource):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise IndexingEstimateError(str(e))
|
raise IndexingEstimateError(str(e))
|
||||||
|
|
||||||
return response, 200
|
return response
|
||||||
|
|
||||||
|
|
||||||
class DocumentBatchIndexingEstimateApi(DocumentResource):
|
class DocumentBatchIndexingEstimateApi(DocumentResource):
|
||||||
@ -443,8 +419,9 @@ class DocumentBatchIndexingEstimateApi(DocumentResource):
|
|||||||
dataset_id = str(dataset_id)
|
dataset_id = str(dataset_id)
|
||||||
batch = str(batch)
|
batch = str(batch)
|
||||||
documents = self.get_batch_documents(dataset_id, batch)
|
documents = self.get_batch_documents(dataset_id, batch)
|
||||||
|
response = {"tokens": 0, "total_price": 0, "currency": "USD", "total_segments": 0, "preview": []}
|
||||||
if not documents:
|
if not documents:
|
||||||
return {"tokens": 0, "total_price": 0, "currency": "USD", "total_segments": 0, "preview": []}, 200
|
return response
|
||||||
data_process_rule = documents[0].dataset_process_rule
|
data_process_rule = documents[0].dataset_process_rule
|
||||||
data_process_rule_dict = data_process_rule.to_dict()
|
data_process_rule_dict = data_process_rule.to_dict()
|
||||||
info_list = []
|
info_list = []
|
||||||
@ -522,15 +499,16 @@ class DocumentBatchIndexingEstimateApi(DocumentResource):
|
|||||||
"English",
|
"English",
|
||||||
dataset_id,
|
dataset_id,
|
||||||
)
|
)
|
||||||
return response.model_dump(), 200
|
|
||||||
except LLMBadRequestError:
|
except LLMBadRequestError:
|
||||||
raise ProviderNotInitializeError(
|
raise ProviderNotInitializeError(
|
||||||
"No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
|
"No Embedding Model available. Please configure a valid provider "
|
||||||
|
"in the Settings -> Model Provider."
|
||||||
)
|
)
|
||||||
except ProviderTokenNotInitError as ex:
|
except ProviderTokenNotInitError as ex:
|
||||||
raise ProviderNotInitializeError(ex.description)
|
raise ProviderNotInitializeError(ex.description)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise IndexingEstimateError(str(e))
|
raise IndexingEstimateError(str(e))
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class DocumentBatchIndexingStatusApi(DocumentResource):
|
class DocumentBatchIndexingStatusApi(DocumentResource):
|
||||||
@ -603,8 +581,7 @@ class DocumentDetailApi(DocumentResource):
|
|||||||
if metadata == "only":
|
if metadata == "only":
|
||||||
response = {"id": document.id, "doc_type": document.doc_type, "doc_metadata": document.doc_metadata}
|
response = {"id": document.id, "doc_type": document.doc_type, "doc_metadata": document.doc_metadata}
|
||||||
elif metadata == "without":
|
elif metadata == "without":
|
||||||
dataset_process_rules = DatasetService.get_process_rules(dataset_id)
|
process_rules = DatasetService.get_process_rules(dataset_id)
|
||||||
document_process_rules = document.dataset_process_rule.to_dict()
|
|
||||||
data_source_info = document.data_source_detail_dict
|
data_source_info = document.data_source_detail_dict
|
||||||
response = {
|
response = {
|
||||||
"id": document.id,
|
"id": document.id,
|
||||||
@ -612,8 +589,7 @@ class DocumentDetailApi(DocumentResource):
|
|||||||
"data_source_type": document.data_source_type,
|
"data_source_type": document.data_source_type,
|
||||||
"data_source_info": data_source_info,
|
"data_source_info": data_source_info,
|
||||||
"dataset_process_rule_id": document.dataset_process_rule_id,
|
"dataset_process_rule_id": document.dataset_process_rule_id,
|
||||||
"dataset_process_rule": dataset_process_rules,
|
"dataset_process_rule": process_rules,
|
||||||
"document_process_rule": document_process_rules,
|
|
||||||
"name": document.name,
|
"name": document.name,
|
||||||
"created_from": document.created_from,
|
"created_from": document.created_from,
|
||||||
"created_by": document.created_by,
|
"created_by": document.created_by,
|
||||||
@ -636,8 +612,7 @@ class DocumentDetailApi(DocumentResource):
|
|||||||
"doc_language": document.doc_language,
|
"doc_language": document.doc_language,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
dataset_process_rules = DatasetService.get_process_rules(dataset_id)
|
process_rules = DatasetService.get_process_rules(dataset_id)
|
||||||
document_process_rules = document.dataset_process_rule.to_dict()
|
|
||||||
data_source_info = document.data_source_detail_dict
|
data_source_info = document.data_source_detail_dict
|
||||||
response = {
|
response = {
|
||||||
"id": document.id,
|
"id": document.id,
|
||||||
@ -645,8 +620,7 @@ class DocumentDetailApi(DocumentResource):
|
|||||||
"data_source_type": document.data_source_type,
|
"data_source_type": document.data_source_type,
|
||||||
"data_source_info": data_source_info,
|
"data_source_info": data_source_info,
|
||||||
"dataset_process_rule_id": document.dataset_process_rule_id,
|
"dataset_process_rule_id": document.dataset_process_rule_id,
|
||||||
"dataset_process_rule": dataset_process_rules,
|
"dataset_process_rule": process_rules,
|
||||||
"document_process_rule": document_process_rules,
|
|
||||||
"name": document.name,
|
"name": document.name,
|
||||||
"created_from": document.created_from,
|
"created_from": document.created_from,
|
||||||
"created_by": document.created_by,
|
"created_by": document.created_by,
|
||||||
@ -759,7 +733,8 @@ class DocumentMetadataApi(DocumentResource):
|
|||||||
|
|
||||||
if not isinstance(doc_metadata, dict):
|
if not isinstance(doc_metadata, dict):
|
||||||
raise ValueError("doc_metadata must be a dictionary.")
|
raise ValueError("doc_metadata must be a dictionary.")
|
||||||
metadata_schema: dict = cast(dict, DocumentService.DOCUMENT_METADATA_SCHEMA[doc_type])
|
|
||||||
|
metadata_schema = DocumentService.DOCUMENT_METADATA_SCHEMA[doc_type]
|
||||||
|
|
||||||
document.doc_metadata = {}
|
document.doc_metadata = {}
|
||||||
if doc_type == "others":
|
if doc_type == "others":
|
||||||
@ -782,8 +757,9 @@ class DocumentStatusApi(DocumentResource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@cloud_edition_billing_resource_check("vector_space")
|
@cloud_edition_billing_resource_check("vector_space")
|
||||||
def patch(self, dataset_id, action):
|
def patch(self, dataset_id, document_id, action):
|
||||||
dataset_id = str(dataset_id)
|
dataset_id = str(dataset_id)
|
||||||
|
document_id = str(document_id)
|
||||||
dataset = DatasetService.get_dataset(dataset_id)
|
dataset = DatasetService.get_dataset(dataset_id)
|
||||||
if dataset is None:
|
if dataset is None:
|
||||||
raise NotFound("Dataset not found.")
|
raise NotFound("Dataset not found.")
|
||||||
@ -798,79 +774,84 @@ class DocumentStatusApi(DocumentResource):
|
|||||||
# check user's permission
|
# check user's permission
|
||||||
DatasetService.check_dataset_permission(dataset, current_user)
|
DatasetService.check_dataset_permission(dataset, current_user)
|
||||||
|
|
||||||
document_ids = request.args.getlist("document_id")
|
document = self.get_document(dataset_id, document_id)
|
||||||
for document_id in document_ids:
|
|
||||||
document = self.get_document(dataset_id, document_id)
|
|
||||||
|
|
||||||
indexing_cache_key = "document_{}_indexing".format(document.id)
|
indexing_cache_key = "document_{}_indexing".format(document.id)
|
||||||
cache_result = redis_client.get(indexing_cache_key)
|
cache_result = redis_client.get(indexing_cache_key)
|
||||||
if cache_result is not None:
|
if cache_result is not None:
|
||||||
raise InvalidActionError(f"Document:{document.name} is being indexed, please try again later")
|
raise InvalidActionError("Document is being indexed, please try again later")
|
||||||
|
|
||||||
if action == "enable":
|
if action == "enable":
|
||||||
if document.enabled:
|
if document.enabled:
|
||||||
continue
|
raise InvalidActionError("Document already enabled.")
|
||||||
document.enabled = True
|
|
||||||
document.disabled_at = None
|
|
||||||
document.disabled_by = None
|
|
||||||
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Set cache to prevent indexing the same document multiple times
|
document.enabled = True
|
||||||
redis_client.setex(indexing_cache_key, 600, 1)
|
document.disabled_at = None
|
||||||
|
document.disabled_by = None
|
||||||
|
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
add_document_to_index_task.delay(document_id)
|
# Set cache to prevent indexing the same document multiple times
|
||||||
|
redis_client.setex(indexing_cache_key, 600, 1)
|
||||||
|
|
||||||
elif action == "disable":
|
add_document_to_index_task.delay(document_id)
|
||||||
if not document.completed_at or document.indexing_status != "completed":
|
|
||||||
raise InvalidActionError(f"Document: {document.name} is not completed.")
|
|
||||||
if not document.enabled:
|
|
||||||
continue
|
|
||||||
|
|
||||||
document.enabled = False
|
return {"result": "success"}, 200
|
||||||
document.disabled_at = datetime.now(UTC).replace(tzinfo=None)
|
|
||||||
document.disabled_by = current_user.id
|
|
||||||
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
|
elif action == "disable":
|
||||||
|
if not document.completed_at or document.indexing_status != "completed":
|
||||||
|
raise InvalidActionError("Document is not completed.")
|
||||||
|
if not document.enabled:
|
||||||
|
raise InvalidActionError("Document already disabled.")
|
||||||
|
|
||||||
|
document.enabled = False
|
||||||
|
document.disabled_at = datetime.now(UTC).replace(tzinfo=None)
|
||||||
|
document.disabled_by = current_user.id
|
||||||
|
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Set cache to prevent indexing the same document multiple times
|
||||||
|
redis_client.setex(indexing_cache_key, 600, 1)
|
||||||
|
|
||||||
|
remove_document_from_index_task.delay(document_id)
|
||||||
|
|
||||||
|
return {"result": "success"}, 200
|
||||||
|
|
||||||
|
elif action == "archive":
|
||||||
|
if document.archived:
|
||||||
|
raise InvalidActionError("Document already archived.")
|
||||||
|
|
||||||
|
document.archived = True
|
||||||
|
document.archived_at = datetime.now(UTC).replace(tzinfo=None)
|
||||||
|
document.archived_by = current_user.id
|
||||||
|
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if document.enabled:
|
||||||
# Set cache to prevent indexing the same document multiple times
|
# Set cache to prevent indexing the same document multiple times
|
||||||
redis_client.setex(indexing_cache_key, 600, 1)
|
redis_client.setex(indexing_cache_key, 600, 1)
|
||||||
|
|
||||||
remove_document_from_index_task.delay(document_id)
|
remove_document_from_index_task.delay(document_id)
|
||||||
|
|
||||||
elif action == "archive":
|
return {"result": "success"}, 200
|
||||||
if document.archived:
|
elif action == "un_archive":
|
||||||
continue
|
if not document.archived:
|
||||||
|
raise InvalidActionError("Document is not archived.")
|
||||||
|
|
||||||
document.archived = True
|
document.archived = False
|
||||||
document.archived_at = datetime.now(UTC).replace(tzinfo=None)
|
document.archived_at = None
|
||||||
document.archived_by = current_user.id
|
document.archived_by = None
|
||||||
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
if document.enabled:
|
# Set cache to prevent indexing the same document multiple times
|
||||||
# Set cache to prevent indexing the same document multiple times
|
redis_client.setex(indexing_cache_key, 600, 1)
|
||||||
redis_client.setex(indexing_cache_key, 600, 1)
|
|
||||||
|
|
||||||
remove_document_from_index_task.delay(document_id)
|
add_document_to_index_task.delay(document_id)
|
||||||
|
|
||||||
elif action == "un_archive":
|
return {"result": "success"}, 200
|
||||||
if not document.archived:
|
else:
|
||||||
continue
|
raise InvalidActionError()
|
||||||
document.archived = False
|
|
||||||
document.archived_at = None
|
|
||||||
document.archived_by = None
|
|
||||||
document.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Set cache to prevent indexing the same document multiple times
|
|
||||||
redis_client.setex(indexing_cache_key, 600, 1)
|
|
||||||
|
|
||||||
add_document_to_index_task.delay(document_id)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise InvalidActionError()
|
|
||||||
return {"result": "success"}, 200
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentPauseApi(DocumentResource):
|
class DocumentPauseApi(DocumentResource):
|
||||||
@ -1041,7 +1022,7 @@ api.add_resource(
|
|||||||
)
|
)
|
||||||
api.add_resource(DocumentDeleteApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>")
|
api.add_resource(DocumentDeleteApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>")
|
||||||
api.add_resource(DocumentMetadataApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/metadata")
|
api.add_resource(DocumentMetadataApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/metadata")
|
||||||
api.add_resource(DocumentStatusApi, "/datasets/<uuid:dataset_id>/documents/status/<string:action>/batch")
|
api.add_resource(DocumentStatusApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/status/<string:action>")
|
||||||
api.add_resource(DocumentPauseApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/processing/pause")
|
api.add_resource(DocumentPauseApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/processing/pause")
|
||||||
api.add_resource(DocumentRecoverApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/processing/resume")
|
api.add_resource(DocumentRecoverApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/processing/resume")
|
||||||
api.add_resource(DocumentRetryApi, "/datasets/<uuid:dataset_id>/retry")
|
api.add_resource(DocumentRetryApi, "/datasets/<uuid:dataset_id>/retry")
|
||||||
|
|||||||
@ -1,21 +1,16 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
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
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.app.error import ProviderNotInitializeError
|
from controllers.console.app.error import ProviderNotInitializeError
|
||||||
from controllers.console.datasets.error import (
|
from controllers.console.datasets.error import InvalidActionError, NoFileUploadedError, TooManyFilesError
|
||||||
ChildChunkDeleteIndexError,
|
|
||||||
ChildChunkIndexingError,
|
|
||||||
InvalidActionError,
|
|
||||||
NoFileUploadedError,
|
|
||||||
TooManyFilesError,
|
|
||||||
)
|
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
cloud_edition_billing_knowledge_limit_check,
|
cloud_edition_billing_knowledge_limit_check,
|
||||||
@ -25,15 +20,15 @@ from controllers.console.wraps import (
|
|||||||
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
|
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
|
||||||
from core.model_manager import ModelManager
|
from core.model_manager import ModelManager
|
||||||
from core.model_runtime.entities.model_entities import ModelType
|
from core.model_runtime.entities.model_entities import ModelType
|
||||||
|
from extensions.ext_database import db
|
||||||
from extensions.ext_redis import redis_client
|
from extensions.ext_redis import redis_client
|
||||||
from fields.segment_fields import child_chunk_fields, segment_fields
|
from fields.segment_fields import segment_fields
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from models.dataset import ChildChunk, DocumentSegment
|
from models import DocumentSegment
|
||||||
from services.dataset_service import DatasetService, DocumentService, SegmentService
|
from services.dataset_service import DatasetService, DocumentService, SegmentService
|
||||||
from services.entities.knowledge_entities.knowledge_entities import ChildChunkUpdateArgs, SegmentUpdateArgs
|
|
||||||
from services.errors.chunk import ChildChunkDeleteIndexError as ChildChunkDeleteIndexServiceError
|
|
||||||
from services.errors.chunk import ChildChunkIndexingError as ChildChunkIndexingServiceError
|
|
||||||
from tasks.batch_create_segment_to_index_task import batch_create_segment_to_index_task
|
from tasks.batch_create_segment_to_index_task import batch_create_segment_to_index_task
|
||||||
|
from tasks.disable_segment_from_index_task import disable_segment_from_index_task
|
||||||
|
from tasks.enable_segment_to_index_task import enable_segment_to_index_task
|
||||||
|
|
||||||
|
|
||||||
class DatasetDocumentSegmentListApi(Resource):
|
class DatasetDocumentSegmentListApi(Resource):
|
||||||
@ -58,16 +53,15 @@ class DatasetDocumentSegmentListApi(Resource):
|
|||||||
raise NotFound("Document not found.")
|
raise NotFound("Document not found.")
|
||||||
|
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument("last_id", type=str, default=None, location="args")
|
||||||
parser.add_argument("limit", type=int, default=20, location="args")
|
parser.add_argument("limit", type=int, default=20, location="args")
|
||||||
parser.add_argument("status", type=str, action="append", default=[], location="args")
|
parser.add_argument("status", type=str, action="append", default=[], location="args")
|
||||||
parser.add_argument("hit_count_gte", type=int, default=None, location="args")
|
parser.add_argument("hit_count_gte", type=int, default=None, location="args")
|
||||||
parser.add_argument("enabled", type=str, default="all", location="args")
|
parser.add_argument("enabled", type=str, default="all", location="args")
|
||||||
parser.add_argument("keyword", type=str, default=None, location="args")
|
parser.add_argument("keyword", type=str, default=None, location="args")
|
||||||
parser.add_argument("page", type=int, default=1, location="args")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
page = args["page"]
|
last_id = args["last_id"]
|
||||||
limit = min(args["limit"], 100)
|
limit = min(args["limit"], 100)
|
||||||
status_list = args["status"]
|
status_list = args["status"]
|
||||||
hit_count_gte = args["hit_count_gte"]
|
hit_count_gte = args["hit_count_gte"]
|
||||||
@ -75,7 +69,14 @@ class DatasetDocumentSegmentListApi(Resource):
|
|||||||
|
|
||||||
query = DocumentSegment.query.filter(
|
query = DocumentSegment.query.filter(
|
||||||
DocumentSegment.document_id == str(document_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
DocumentSegment.document_id == str(document_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
||||||
).order_by(DocumentSegment.position.asc())
|
)
|
||||||
|
|
||||||
|
if last_id is not None:
|
||||||
|
last_segment = db.session.get(DocumentSegment, str(last_id))
|
||||||
|
if last_segment:
|
||||||
|
query = query.filter(DocumentSegment.position > last_segment.position)
|
||||||
|
else:
|
||||||
|
return {"data": [], "has_more": False, "limit": limit}, 200
|
||||||
|
|
||||||
if status_list:
|
if status_list:
|
||||||
query = query.filter(DocumentSegment.status.in_(status_list))
|
query = query.filter(DocumentSegment.status.in_(status_list))
|
||||||
@ -92,44 +93,21 @@ class DatasetDocumentSegmentListApi(Resource):
|
|||||||
elif args["enabled"].lower() == "false":
|
elif args["enabled"].lower() == "false":
|
||||||
query = query.filter(DocumentSegment.enabled == False)
|
query = query.filter(DocumentSegment.enabled == False)
|
||||||
|
|
||||||
segments = query.paginate(page=page, per_page=limit, max_per_page=100, error_out=False)
|
total = query.count()
|
||||||
|
segments = query.order_by(DocumentSegment.position).limit(limit + 1).all()
|
||||||
|
|
||||||
response = {
|
has_more = False
|
||||||
"data": marshal(segments.items, segment_fields),
|
if len(segments) > limit:
|
||||||
|
has_more = True
|
||||||
|
segments = segments[:-1]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"data": marshal(segments, segment_fields),
|
||||||
|
"doc_form": document.doc_form,
|
||||||
|
"has_more": has_more,
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
"total": segments.total,
|
"total": total,
|
||||||
"total_pages": segments.pages,
|
}, 200
|
||||||
"page": page,
|
|
||||||
}
|
|
||||||
return response, 200
|
|
||||||
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
def delete(self, dataset_id, document_id):
|
|
||||||
# check dataset
|
|
||||||
dataset_id = str(dataset_id)
|
|
||||||
dataset = DatasetService.get_dataset(dataset_id)
|
|
||||||
if not dataset:
|
|
||||||
raise NotFound("Dataset not found.")
|
|
||||||
# check user's model setting
|
|
||||||
DatasetService.check_dataset_model_setting(dataset)
|
|
||||||
# check document
|
|
||||||
document_id = str(document_id)
|
|
||||||
document = DocumentService.get_document(dataset_id, document_id)
|
|
||||||
if not document:
|
|
||||||
raise NotFound("Document not found.")
|
|
||||||
segment_ids = request.args.getlist("segment_id")
|
|
||||||
|
|
||||||
# The role of the current user in the ta table must be admin or owner
|
|
||||||
if not current_user.is_editor:
|
|
||||||
raise Forbidden()
|
|
||||||
try:
|
|
||||||
DatasetService.check_dataset_permission(dataset, current_user)
|
|
||||||
except services.errors.account.NoPermissionError as e:
|
|
||||||
raise Forbidden(str(e))
|
|
||||||
SegmentService.delete_segments(segment_ids, document, dataset)
|
|
||||||
return {"result": "success"}, 200
|
|
||||||
|
|
||||||
|
|
||||||
class DatasetDocumentSegmentApi(Resource):
|
class DatasetDocumentSegmentApi(Resource):
|
||||||
@ -137,15 +115,11 @@ class DatasetDocumentSegmentApi(Resource):
|
|||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@cloud_edition_billing_resource_check("vector_space")
|
@cloud_edition_billing_resource_check("vector_space")
|
||||||
def patch(self, dataset_id, document_id, action):
|
def patch(self, dataset_id, segment_id, action):
|
||||||
dataset_id = str(dataset_id)
|
dataset_id = str(dataset_id)
|
||||||
dataset = DatasetService.get_dataset(dataset_id)
|
dataset = DatasetService.get_dataset(dataset_id)
|
||||||
if not dataset:
|
if not dataset:
|
||||||
raise NotFound("Dataset not found.")
|
raise NotFound("Dataset not found.")
|
||||||
document_id = str(document_id)
|
|
||||||
document = DocumentService.get_document(dataset_id, document_id)
|
|
||||||
if not document:
|
|
||||||
raise NotFound("Document not found.")
|
|
||||||
# check user's model setting
|
# check user's model setting
|
||||||
DatasetService.check_dataset_model_setting(dataset)
|
DatasetService.check_dataset_model_setting(dataset)
|
||||||
# The role of the current user in the ta table must be admin, owner, or editor
|
# The role of the current user in the ta table must be admin, owner, or editor
|
||||||
@ -168,21 +142,64 @@ class DatasetDocumentSegmentApi(Resource):
|
|||||||
)
|
)
|
||||||
except LLMBadRequestError:
|
except LLMBadRequestError:
|
||||||
raise ProviderNotInitializeError(
|
raise ProviderNotInitializeError(
|
||||||
"No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
|
"No Embedding Model available. Please configure a valid provider "
|
||||||
|
"in the Settings -> Model Provider."
|
||||||
)
|
)
|
||||||
except ProviderTokenNotInitError as ex:
|
except ProviderTokenNotInitError as ex:
|
||||||
raise ProviderNotInitializeError(ex.description)
|
raise ProviderNotInitializeError(ex.description)
|
||||||
segment_ids = request.args.getlist("segment_id")
|
|
||||||
|
|
||||||
document_indexing_cache_key = "document_{}_indexing".format(document.id)
|
segment = DocumentSegment.query.filter(
|
||||||
|
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not segment:
|
||||||
|
raise NotFound("Segment not found.")
|
||||||
|
|
||||||
|
if segment.status != "completed":
|
||||||
|
raise NotFound("Segment is not completed, enable or disable function is not allowed")
|
||||||
|
|
||||||
|
document_indexing_cache_key = "document_{}_indexing".format(segment.document_id)
|
||||||
cache_result = redis_client.get(document_indexing_cache_key)
|
cache_result = redis_client.get(document_indexing_cache_key)
|
||||||
if cache_result is not None:
|
if cache_result is not None:
|
||||||
raise InvalidActionError("Document is being indexed, please try again later")
|
raise InvalidActionError("Document is being indexed, please try again later")
|
||||||
try:
|
|
||||||
SegmentService.update_segments_status(segment_ids, action, dataset, document)
|
indexing_cache_key = "segment_{}_indexing".format(segment.id)
|
||||||
except Exception as e:
|
cache_result = redis_client.get(indexing_cache_key)
|
||||||
raise InvalidActionError(str(e))
|
if cache_result is not None:
|
||||||
return {"result": "success"}, 200
|
raise InvalidActionError("Segment is being indexed, please try again later")
|
||||||
|
|
||||||
|
if action == "enable":
|
||||||
|
if segment.enabled:
|
||||||
|
raise InvalidActionError("Segment is already enabled.")
|
||||||
|
|
||||||
|
segment.enabled = True
|
||||||
|
segment.disabled_at = None
|
||||||
|
segment.disabled_by = None
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Set cache to prevent indexing the same segment multiple times
|
||||||
|
redis_client.setex(indexing_cache_key, 600, 1)
|
||||||
|
|
||||||
|
enable_segment_to_index_task.delay(segment.id)
|
||||||
|
|
||||||
|
return {"result": "success"}, 200
|
||||||
|
elif action == "disable":
|
||||||
|
if not segment.enabled:
|
||||||
|
raise InvalidActionError("Segment is already disabled.")
|
||||||
|
|
||||||
|
segment.enabled = False
|
||||||
|
segment.disabled_at = datetime.now(UTC).replace(tzinfo=None)
|
||||||
|
segment.disabled_by = current_user.id
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Set cache to prevent indexing the same segment multiple times
|
||||||
|
redis_client.setex(indexing_cache_key, 600, 1)
|
||||||
|
|
||||||
|
disable_segment_from_index_task.delay(segment.id)
|
||||||
|
|
||||||
|
return {"result": "success"}, 200
|
||||||
|
else:
|
||||||
|
raise InvalidActionError()
|
||||||
|
|
||||||
|
|
||||||
class DatasetDocumentSegmentAddApi(Resource):
|
class DatasetDocumentSegmentAddApi(Resource):
|
||||||
@ -216,7 +233,8 @@ class DatasetDocumentSegmentAddApi(Resource):
|
|||||||
)
|
)
|
||||||
except LLMBadRequestError:
|
except LLMBadRequestError:
|
||||||
raise ProviderNotInitializeError(
|
raise ProviderNotInitializeError(
|
||||||
"No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
|
"No Embedding Model available. Please configure a valid provider "
|
||||||
|
"in the Settings -> Model Provider."
|
||||||
)
|
)
|
||||||
except ProviderTokenNotInitError as ex:
|
except ProviderTokenNotInitError as ex:
|
||||||
raise ProviderNotInitializeError(ex.description)
|
raise ProviderNotInitializeError(ex.description)
|
||||||
@ -265,7 +283,8 @@ class DatasetDocumentSegmentUpdateApi(Resource):
|
|||||||
)
|
)
|
||||||
except LLMBadRequestError:
|
except LLMBadRequestError:
|
||||||
raise ProviderNotInitializeError(
|
raise ProviderNotInitializeError(
|
||||||
"No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
|
"No Embedding Model available. Please configure a valid provider "
|
||||||
|
"in the Settings -> Model Provider."
|
||||||
)
|
)
|
||||||
except ProviderTokenNotInitError as ex:
|
except ProviderTokenNotInitError as ex:
|
||||||
raise ProviderNotInitializeError(ex.description)
|
raise ProviderNotInitializeError(ex.description)
|
||||||
@ -288,12 +307,9 @@ class DatasetDocumentSegmentUpdateApi(Resource):
|
|||||||
parser.add_argument("content", type=str, required=True, nullable=False, location="json")
|
parser.add_argument("content", type=str, required=True, nullable=False, location="json")
|
||||||
parser.add_argument("answer", type=str, required=False, nullable=True, location="json")
|
parser.add_argument("answer", type=str, required=False, nullable=True, location="json")
|
||||||
parser.add_argument("keywords", type=list, required=False, nullable=True, location="json")
|
parser.add_argument("keywords", type=list, required=False, nullable=True, location="json")
|
||||||
parser.add_argument(
|
|
||||||
"regenerate_child_chunks", type=bool, required=False, nullable=True, default=False, location="json"
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
SegmentService.segment_create_args_validate(args, document)
|
SegmentService.segment_create_args_validate(args, document)
|
||||||
segment = SegmentService.update_segment(SegmentUpdateArgs(**args), segment, document, dataset)
|
segment = SegmentService.update_segment(args, segment, document, dataset)
|
||||||
return {"data": marshal(segment, segment_fields), "doc_form": document.doc_form}, 200
|
return {"data": marshal(segment, segment_fields), "doc_form": document.doc_form}, 200
|
||||||
|
|
||||||
@setup_required
|
@setup_required
|
||||||
@ -365,9 +381,9 @@ class DatasetDocumentSegmentBatchImportApi(Resource):
|
|||||||
result = []
|
result = []
|
||||||
for index, row in df.iterrows():
|
for index, row in df.iterrows():
|
||||||
if document.doc_form == "qa_model":
|
if document.doc_form == "qa_model":
|
||||||
data = {"content": row.iloc[0], "answer": row.iloc[1]}
|
data = {"content": row[0], "answer": row[1]}
|
||||||
else:
|
else:
|
||||||
data = {"content": row.iloc[0]}
|
data = {"content": row[0]}
|
||||||
result.append(data)
|
result.append(data)
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
raise ValueError("The CSV file is empty.")
|
raise ValueError("The CSV file is empty.")
|
||||||
@ -396,247 +412,8 @@ class DatasetDocumentSegmentBatchImportApi(Resource):
|
|||||||
return {"job_id": job_id, "job_status": cache_result.decode()}, 200
|
return {"job_id": job_id, "job_status": cache_result.decode()}, 200
|
||||||
|
|
||||||
|
|
||||||
class ChildChunkAddApi(Resource):
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
@cloud_edition_billing_resource_check("vector_space")
|
|
||||||
@cloud_edition_billing_knowledge_limit_check("add_segment")
|
|
||||||
def post(self, dataset_id, document_id, segment_id):
|
|
||||||
# check dataset
|
|
||||||
dataset_id = str(dataset_id)
|
|
||||||
dataset = DatasetService.get_dataset(dataset_id)
|
|
||||||
if not dataset:
|
|
||||||
raise NotFound("Dataset not found.")
|
|
||||||
# check document
|
|
||||||
document_id = str(document_id)
|
|
||||||
document = DocumentService.get_document(dataset_id, document_id)
|
|
||||||
if not document:
|
|
||||||
raise NotFound("Document not found.")
|
|
||||||
# check segment
|
|
||||||
segment_id = str(segment_id)
|
|
||||||
segment = DocumentSegment.query.filter(
|
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
|
||||||
).first()
|
|
||||||
if not segment:
|
|
||||||
raise NotFound("Segment not found.")
|
|
||||||
if not current_user.is_editor:
|
|
||||||
raise Forbidden()
|
|
||||||
# check embedding model setting
|
|
||||||
if dataset.indexing_technique == "high_quality":
|
|
||||||
try:
|
|
||||||
model_manager = ModelManager()
|
|
||||||
model_manager.get_model_instance(
|
|
||||||
tenant_id=current_user.current_tenant_id,
|
|
||||||
provider=dataset.embedding_model_provider,
|
|
||||||
model_type=ModelType.TEXT_EMBEDDING,
|
|
||||||
model=dataset.embedding_model,
|
|
||||||
)
|
|
||||||
except LLMBadRequestError:
|
|
||||||
raise ProviderNotInitializeError(
|
|
||||||
"No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
|
|
||||||
)
|
|
||||||
except ProviderTokenNotInitError as ex:
|
|
||||||
raise ProviderNotInitializeError(ex.description)
|
|
||||||
try:
|
|
||||||
DatasetService.check_dataset_permission(dataset, current_user)
|
|
||||||
except services.errors.account.NoPermissionError as e:
|
|
||||||
raise Forbidden(str(e))
|
|
||||||
# validate args
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
parser.add_argument("content", type=str, required=True, nullable=False, location="json")
|
|
||||||
args = parser.parse_args()
|
|
||||||
try:
|
|
||||||
child_chunk = SegmentService.create_child_chunk(args.get("content"), segment, document, dataset)
|
|
||||||
except ChildChunkIndexingServiceError as e:
|
|
||||||
raise ChildChunkIndexingError(str(e))
|
|
||||||
return {"data": marshal(child_chunk, child_chunk_fields)}, 200
|
|
||||||
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
def get(self, dataset_id, document_id, segment_id):
|
|
||||||
# check dataset
|
|
||||||
dataset_id = str(dataset_id)
|
|
||||||
dataset = DatasetService.get_dataset(dataset_id)
|
|
||||||
if not dataset:
|
|
||||||
raise NotFound("Dataset not found.")
|
|
||||||
# check user's model setting
|
|
||||||
DatasetService.check_dataset_model_setting(dataset)
|
|
||||||
# check document
|
|
||||||
document_id = str(document_id)
|
|
||||||
document = DocumentService.get_document(dataset_id, document_id)
|
|
||||||
if not document:
|
|
||||||
raise NotFound("Document not found.")
|
|
||||||
# check segment
|
|
||||||
segment_id = str(segment_id)
|
|
||||||
segment = DocumentSegment.query.filter(
|
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
|
||||||
).first()
|
|
||||||
if not segment:
|
|
||||||
raise NotFound("Segment not found.")
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
parser.add_argument("limit", type=int, default=20, location="args")
|
|
||||||
parser.add_argument("keyword", type=str, default=None, location="args")
|
|
||||||
parser.add_argument("page", type=int, default=1, location="args")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
page = args["page"]
|
|
||||||
limit = min(args["limit"], 100)
|
|
||||||
keyword = args["keyword"]
|
|
||||||
|
|
||||||
child_chunks = SegmentService.get_child_chunks(segment_id, document_id, dataset_id, page, limit, keyword)
|
|
||||||
return {
|
|
||||||
"data": marshal(child_chunks.items, child_chunk_fields),
|
|
||||||
"total": child_chunks.total,
|
|
||||||
"total_pages": child_chunks.pages,
|
|
||||||
"page": page,
|
|
||||||
"limit": limit,
|
|
||||||
}, 200
|
|
||||||
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
@cloud_edition_billing_resource_check("vector_space")
|
|
||||||
def patch(self, dataset_id, document_id, segment_id):
|
|
||||||
# check dataset
|
|
||||||
dataset_id = str(dataset_id)
|
|
||||||
dataset = DatasetService.get_dataset(dataset_id)
|
|
||||||
if not dataset:
|
|
||||||
raise NotFound("Dataset not found.")
|
|
||||||
# check user's model setting
|
|
||||||
DatasetService.check_dataset_model_setting(dataset)
|
|
||||||
# check document
|
|
||||||
document_id = str(document_id)
|
|
||||||
document = DocumentService.get_document(dataset_id, document_id)
|
|
||||||
if not document:
|
|
||||||
raise NotFound("Document not found.")
|
|
||||||
# check segment
|
|
||||||
segment_id = str(segment_id)
|
|
||||||
segment = DocumentSegment.query.filter(
|
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
|
||||||
).first()
|
|
||||||
if not segment:
|
|
||||||
raise NotFound("Segment not found.")
|
|
||||||
# The role of the current user in the ta table must be admin, owner, or editor
|
|
||||||
if not current_user.is_editor:
|
|
||||||
raise Forbidden()
|
|
||||||
try:
|
|
||||||
DatasetService.check_dataset_permission(dataset, current_user)
|
|
||||||
except services.errors.account.NoPermissionError as e:
|
|
||||||
raise Forbidden(str(e))
|
|
||||||
# validate args
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
parser.add_argument("chunks", type=list, required=True, nullable=False, location="json")
|
|
||||||
args = parser.parse_args()
|
|
||||||
try:
|
|
||||||
chunks = [ChildChunkUpdateArgs(**chunk) for chunk in args.get("chunks")]
|
|
||||||
child_chunks = SegmentService.update_child_chunks(chunks, segment, document, dataset)
|
|
||||||
except ChildChunkIndexingServiceError as e:
|
|
||||||
raise ChildChunkIndexingError(str(e))
|
|
||||||
return {"data": marshal(child_chunks, child_chunk_fields)}, 200
|
|
||||||
|
|
||||||
|
|
||||||
class ChildChunkUpdateApi(Resource):
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
def delete(self, dataset_id, document_id, segment_id, child_chunk_id):
|
|
||||||
# check dataset
|
|
||||||
dataset_id = str(dataset_id)
|
|
||||||
dataset = DatasetService.get_dataset(dataset_id)
|
|
||||||
if not dataset:
|
|
||||||
raise NotFound("Dataset not found.")
|
|
||||||
# check user's model setting
|
|
||||||
DatasetService.check_dataset_model_setting(dataset)
|
|
||||||
# check document
|
|
||||||
document_id = str(document_id)
|
|
||||||
document = DocumentService.get_document(dataset_id, document_id)
|
|
||||||
if not document:
|
|
||||||
raise NotFound("Document not found.")
|
|
||||||
# check segment
|
|
||||||
segment_id = str(segment_id)
|
|
||||||
segment = DocumentSegment.query.filter(
|
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
|
||||||
).first()
|
|
||||||
if not segment:
|
|
||||||
raise NotFound("Segment not found.")
|
|
||||||
# check child chunk
|
|
||||||
child_chunk_id = str(child_chunk_id)
|
|
||||||
child_chunk = ChildChunk.query.filter(
|
|
||||||
ChildChunk.id == str(child_chunk_id), ChildChunk.tenant_id == current_user.current_tenant_id
|
|
||||||
).first()
|
|
||||||
if not child_chunk:
|
|
||||||
raise NotFound("Child chunk not found.")
|
|
||||||
# The role of the current user in the ta table must be admin or owner
|
|
||||||
if not current_user.is_editor:
|
|
||||||
raise Forbidden()
|
|
||||||
try:
|
|
||||||
DatasetService.check_dataset_permission(dataset, current_user)
|
|
||||||
except services.errors.account.NoPermissionError as e:
|
|
||||||
raise Forbidden(str(e))
|
|
||||||
try:
|
|
||||||
SegmentService.delete_child_chunk(child_chunk, dataset)
|
|
||||||
except ChildChunkDeleteIndexServiceError as e:
|
|
||||||
raise ChildChunkDeleteIndexError(str(e))
|
|
||||||
return {"result": "success"}, 200
|
|
||||||
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
@cloud_edition_billing_resource_check("vector_space")
|
|
||||||
def patch(self, dataset_id, document_id, segment_id, child_chunk_id):
|
|
||||||
# check dataset
|
|
||||||
dataset_id = str(dataset_id)
|
|
||||||
dataset = DatasetService.get_dataset(dataset_id)
|
|
||||||
if not dataset:
|
|
||||||
raise NotFound("Dataset not found.")
|
|
||||||
# check user's model setting
|
|
||||||
DatasetService.check_dataset_model_setting(dataset)
|
|
||||||
# check document
|
|
||||||
document_id = str(document_id)
|
|
||||||
document = DocumentService.get_document(dataset_id, document_id)
|
|
||||||
if not document:
|
|
||||||
raise NotFound("Document not found.")
|
|
||||||
# check segment
|
|
||||||
segment_id = str(segment_id)
|
|
||||||
segment = DocumentSegment.query.filter(
|
|
||||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
|
||||||
).first()
|
|
||||||
if not segment:
|
|
||||||
raise NotFound("Segment not found.")
|
|
||||||
# check child chunk
|
|
||||||
child_chunk_id = str(child_chunk_id)
|
|
||||||
child_chunk = ChildChunk.query.filter(
|
|
||||||
ChildChunk.id == str(child_chunk_id), ChildChunk.tenant_id == current_user.current_tenant_id
|
|
||||||
).first()
|
|
||||||
if not child_chunk:
|
|
||||||
raise NotFound("Child chunk not found.")
|
|
||||||
# The role of the current user in the ta table must be admin or owner
|
|
||||||
if not current_user.is_editor:
|
|
||||||
raise Forbidden()
|
|
||||||
try:
|
|
||||||
DatasetService.check_dataset_permission(dataset, current_user)
|
|
||||||
except services.errors.account.NoPermissionError as e:
|
|
||||||
raise Forbidden(str(e))
|
|
||||||
# validate args
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
parser.add_argument("content", type=str, required=True, nullable=False, location="json")
|
|
||||||
args = parser.parse_args()
|
|
||||||
try:
|
|
||||||
child_chunk = SegmentService.update_child_chunk(
|
|
||||||
args.get("content"), child_chunk, segment, document, dataset
|
|
||||||
)
|
|
||||||
except ChildChunkIndexingServiceError as e:
|
|
||||||
raise ChildChunkIndexingError(str(e))
|
|
||||||
return {"data": marshal(child_chunk, child_chunk_fields)}, 200
|
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(DatasetDocumentSegmentListApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments")
|
api.add_resource(DatasetDocumentSegmentListApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments")
|
||||||
api.add_resource(
|
api.add_resource(DatasetDocumentSegmentApi, "/datasets/<uuid:dataset_id>/segments/<uuid:segment_id>/<string:action>")
|
||||||
DatasetDocumentSegmentApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segment/<string:action>"
|
|
||||||
)
|
|
||||||
api.add_resource(DatasetDocumentSegmentAddApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segment")
|
api.add_resource(DatasetDocumentSegmentAddApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segment")
|
||||||
api.add_resource(
|
api.add_resource(
|
||||||
DatasetDocumentSegmentUpdateApi,
|
DatasetDocumentSegmentUpdateApi,
|
||||||
@ -647,11 +424,3 @@ api.add_resource(
|
|||||||
"/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/batch_import",
|
"/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/batch_import",
|
||||||
"/datasets/batch_import_status/<uuid:job_id>",
|
"/datasets/batch_import_status/<uuid:job_id>",
|
||||||
)
|
)
|
||||||
api.add_resource(
|
|
||||||
ChildChunkAddApi,
|
|
||||||
"/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>/child_chunks",
|
|
||||||
)
|
|
||||||
api.add_resource(
|
|
||||||
ChildChunkUpdateApi,
|
|
||||||
"/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>/child_chunks/<uuid:child_chunk_id>",
|
|
||||||
)
|
|
||||||
|
|||||||
@ -89,15 +89,3 @@ class IndexingEstimateError(BaseHTTPException):
|
|||||||
error_code = "indexing_estimate_error"
|
error_code = "indexing_estimate_error"
|
||||||
description = "Knowledge indexing estimate failed: {message}"
|
description = "Knowledge indexing estimate failed: {message}"
|
||||||
code = 500
|
code = 500
|
||||||
|
|
||||||
|
|
||||||
class ChildChunkIndexingError(BaseHTTPException):
|
|
||||||
error_code = "child_chunk_indexing_error"
|
|
||||||
description = "Create child chunk index failed: {message}"
|
|
||||||
code = 500
|
|
||||||
|
|
||||||
|
|
||||||
class ChildChunkDeleteIndexError(BaseHTTPException):
|
|
||||||
error_code = "child_chunk_delete_index_error"
|
|
||||||
description = "Delete child chunk index failed: {message}"
|
|
||||||
code = 500
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,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
|
||||||
|
|||||||
@ -92,12 +92,3 @@ class UnauthorizedAndForceLogout(BaseHTTPException):
|
|||||||
error_code = "unauthorized_and_force_logout"
|
error_code = "unauthorized_and_force_logout"
|
||||||
description = "Unauthorized and force logout."
|
description = "Unauthorized and force logout."
|
||||||
code = 401
|
code = 401
|
||||||
|
|
||||||
|
|
||||||
class AccountInFreezeError(BaseHTTPException):
|
|
||||||
error_code = "account_in_freeze"
|
|
||||||
code = 400
|
|
||||||
description = (
|
|
||||||
"This email account has been deleted within the past 30 days"
|
|
||||||
"and is temporarily unavailable for new account registration."
|
|
||||||
)
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from flask import request
|
|||||||
from werkzeug.exceptions import InternalServerError
|
from werkzeug.exceptions import InternalServerError
|
||||||
|
|
||||||
import services
|
import services
|
||||||
|
from controllers.console import api
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
AppUnavailableError,
|
AppUnavailableError,
|
||||||
AudioTooLargeError,
|
AudioTooLargeError,
|
||||||
@ -66,7 +67,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:
|
||||||
@ -117,3 +118,9 @@ class ChatTextApi(InstalledAppResource):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("internal server error.")
|
logging.exception("internal server error.")
|
||||||
raise InternalServerError()
|
raise InternalServerError()
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(ChatAudioApi, "/installed-apps/<uuid:installed_app_id>/audio-to-text", endpoint="installed_app_audio")
|
||||||
|
api.add_resource(ChatTextApi, "/installed-apps/<uuid:installed_app_id>/text-to-audio", endpoint="installed_app_text")
|
||||||
|
# api.add_resource(ChatTextApiWithMessageId, '/installed-apps/<uuid:installed_app_id>/text-to-audio/message-id',
|
||||||
|
# endpoint='installed_app_text_with_message_id')
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
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
|
||||||
|
from controllers.console import api
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
AppUnavailableError,
|
AppUnavailableError,
|
||||||
CompletionRequestError,
|
CompletionRequestError,
|
||||||
@ -18,11 +19,7 @@ from controllers.console.explore.error import NotChatAppError, NotCompletionAppE
|
|||||||
from controllers.console.explore.wraps import InstalledAppResource
|
from controllers.console.explore.wraps import InstalledAppResource
|
||||||
from core.app.apps.base_app_queue_manager import AppQueueManager
|
from core.app.apps.base_app_queue_manager import AppQueueManager
|
||||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||||
from core.errors.error import (
|
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
|
||||||
ModelCurrentlyNotSupportError,
|
|
||||||
ProviderTokenNotInitError,
|
|
||||||
QuotaExceededError,
|
|
||||||
)
|
|
||||||
from core.model_runtime.errors.invoke import InvokeError
|
from core.model_runtime.errors.invoke import InvokeError
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs import helper
|
from libs import helper
|
||||||
@ -150,3 +147,21 @@ class ChatStopApi(InstalledAppResource):
|
|||||||
AppQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
|
AppQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
|
||||||
|
|
||||||
return {"result": "success"}, 200
|
return {"result": "success"}, 200
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(
|
||||||
|
CompletionApi, "/installed-apps/<uuid:installed_app_id>/completion-messages", endpoint="installed_app_completion"
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
CompletionStopApi,
|
||||||
|
"/installed-apps/<uuid:installed_app_id>/completion-messages/<string:task_id>/stop",
|
||||||
|
endpoint="installed_app_stop_completion",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
ChatApi, "/installed-apps/<uuid:installed_app_id>/chat-messages", endpoint="installed_app_chat_completion"
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
ChatStopApi,
|
||||||
|
"/installed-apps/<uuid:installed_app_id>/chat-messages/<string:task_id>/stop",
|
||||||
|
endpoint="installed_app_stop_chat_completion",
|
||||||
|
)
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
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 werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
from controllers.console import api
|
||||||
from controllers.console.explore.error import NotChatAppError
|
from controllers.console.explore.error import NotChatAppError
|
||||||
from controllers.console.explore.wraps import InstalledAppResource
|
from controllers.console.explore.wraps import InstalledAppResource
|
||||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||||
from extensions.ext_database import db
|
|
||||||
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
|
from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields
|
||||||
from libs.helper import uuid_value
|
from libs.helper import uuid_value
|
||||||
from models.model import AppMode
|
from models.model import AppMode
|
||||||
@ -32,19 +31,17 @@ class ConversationListApi(InstalledAppResource):
|
|||||||
|
|
||||||
pinned = None
|
pinned = None
|
||||||
if "pinned" in args and args["pinned"] is not None:
|
if "pinned" in args and args["pinned"] is not None:
|
||||||
pinned = args["pinned"] == "true"
|
pinned = True if args["pinned"] == "true" else False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with Session(db.engine) as session:
|
return WebConversationService.pagination_by_last_id(
|
||||||
return WebConversationService.pagination_by_last_id(
|
app_model=app_model,
|
||||||
session=session,
|
user=current_user,
|
||||||
app_model=app_model,
|
last_id=args["last_id"],
|
||||||
user=current_user,
|
limit=args["limit"],
|
||||||
last_id=args["last_id"],
|
invoke_from=InvokeFrom.EXPLORE,
|
||||||
limit=args["limit"],
|
pinned=pinned,
|
||||||
invoke_from=InvokeFrom.EXPLORE,
|
)
|
||||||
pinned=pinned,
|
|
||||||
)
|
|
||||||
except LastConversationNotExistsError:
|
except LastConversationNotExistsError:
|
||||||
raise NotFound("Last Conversation Not Exists.")
|
raise NotFound("Last Conversation Not Exists.")
|
||||||
|
|
||||||
@ -117,3 +114,28 @@ class ConversationUnPinApi(InstalledAppResource):
|
|||||||
WebConversationService.unpin(app_model, conversation_id, current_user)
|
WebConversationService.unpin(app_model, conversation_id, current_user)
|
||||||
|
|
||||||
return {"result": "success"}
|
return {"result": "success"}
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(
|
||||||
|
ConversationRenameApi,
|
||||||
|
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/name",
|
||||||
|
endpoint="installed_app_conversation_rename",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
ConversationListApi, "/installed-apps/<uuid:installed_app_id>/conversations", endpoint="installed_app_conversations"
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
ConversationApi,
|
||||||
|
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>",
|
||||||
|
endpoint="installed_app_conversation",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
ConversationPinApi,
|
||||||
|
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/pin",
|
||||||
|
endpoint="installed_app_conversation_pin",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
ConversationUnPinApi,
|
||||||
|
"/installed-apps/<uuid:installed_app_id>/conversations/<uuid:c_id>/unpin",
|
||||||
|
endpoint="installed_app_conversation_unpin",
|
||||||
|
)
|
||||||
|
|||||||
@ -23,9 +23,3 @@ class AppSuggestedQuestionsAfterAnswerDisabledError(BaseHTTPException):
|
|||||||
error_code = "app_suggested_questions_after_answer_disabled"
|
error_code = "app_suggested_questions_after_answer_disabled"
|
||||||
description = "Function Suggested questions after answer disabled."
|
description = "Function Suggested questions after answer disabled."
|
||||||
code = 403
|
code = 403
|
||||||
|
|
||||||
|
|
||||||
class AppAccessDeniedError(BaseHTTPException):
|
|
||||||
error_code = "access_denied"
|
|
||||||
description = "App access denied."
|
|
||||||
code = 403
|
|
||||||
|
|||||||
@ -1,26 +1,19 @@
|
|||||||
import logging
|
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
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, # type: ignore
|
from flask_restful import Resource, inputs, marshal_with, reqparse
|
||||||
reqparse)
|
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
|
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.explore.wraps import InstalledAppResource
|
from controllers.console.explore.wraps import InstalledAppResource
|
||||||
from controllers.console.wraps import (account_initialization_required,
|
from controllers.console.wraps import account_initialization_required, cloud_edition_billing_resource_check
|
||||||
cloud_edition_billing_resource_check)
|
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from fields.installed_app_fields import installed_app_list_fields
|
from fields.installed_app_fields import installed_app_list_fields
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from models import App, InstalledApp, RecommendedApp
|
from models import App, InstalledApp, RecommendedApp
|
||||||
from services.account_service import TenantService
|
from services.account_service import TenantService
|
||||||
from services.app_service import AppService
|
|
||||||
from services.enterprise.enterprise_service import EnterpriseService
|
|
||||||
from services.feature_service import FeatureService
|
|
||||||
|
|
||||||
|
|
||||||
class InstalledAppsListApi(Resource):
|
class InstalledAppsListApi(Resource):
|
||||||
@ -41,7 +34,7 @@ class InstalledAppsListApi(Resource):
|
|||||||
installed_apps = db.session.query(InstalledApp).filter(InstalledApp.tenant_id == current_tenant_id).all()
|
installed_apps = db.session.query(InstalledApp).filter(InstalledApp.tenant_id == current_tenant_id).all()
|
||||||
|
|
||||||
current_user.role = TenantService.get_user_role(current_user, current_user.current_tenant)
|
current_user.role = TenantService.get_user_role(current_user, current_user.current_tenant)
|
||||||
installed_app_list: list[dict[str, Any]] = [
|
installed_apps = [
|
||||||
{
|
{
|
||||||
"id": installed_app.id,
|
"id": installed_app.id,
|
||||||
"app": installed_app.app,
|
"app": installed_app.app,
|
||||||
@ -54,24 +47,7 @@ class InstalledAppsListApi(Resource):
|
|||||||
for installed_app in installed_apps
|
for installed_app in installed_apps
|
||||||
if installed_app.app is not None
|
if installed_app.app is not None
|
||||||
]
|
]
|
||||||
|
installed_apps.sort(
|
||||||
# filter out apps that user doesn't have access to
|
|
||||||
if FeatureService.get_system_features().webapp_auth.enabled:
|
|
||||||
user_id = current_user.id
|
|
||||||
res = []
|
|
||||||
for installed_app in installed_app_list:
|
|
||||||
app_code = AppService.get_app_code_by_id(str(installed_app["app"].id))
|
|
||||||
if EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(
|
|
||||||
user_id=user_id,
|
|
||||||
app_code=app_code,
|
|
||||||
):
|
|
||||||
res.append(installed_app)
|
|
||||||
installed_app_list = res
|
|
||||||
logging.info(
|
|
||||||
f"installed_app_list: {installed_app_list}, user_id: {user_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
installed_app_list.sort(
|
|
||||||
key=lambda app: (
|
key=lambda app: (
|
||||||
-app["is_pinned"],
|
-app["is_pinned"],
|
||||||
app["last_used_at"] is None,
|
app["last_used_at"] is None,
|
||||||
@ -79,7 +55,7 @@ class InstalledAppsListApi(Resource):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"installed_apps": installed_app_list}
|
return {"installed_apps": installed_apps}
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
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
|
||||||
|
from controllers.console import api
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
AppMoreLikeThisDisabledError,
|
AppMoreLikeThisDisabledError,
|
||||||
CompletionRequestError,
|
CompletionRequestError,
|
||||||
@ -50,7 +51,7 @@ class MessageListApi(InstalledAppResource):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
return MessageService.pagination_by_first_id(
|
return MessageService.pagination_by_first_id(
|
||||||
app_model, current_user, args["conversation_id"], args["first_id"], args["limit"]
|
app_model, current_user, args["conversation_id"], args["first_id"], args["limit"], "desc"
|
||||||
)
|
)
|
||||||
except services.errors.conversation.ConversationNotExistsError:
|
except services.errors.conversation.ConversationNotExistsError:
|
||||||
raise NotFound("Conversation Not Exists.")
|
raise NotFound("Conversation Not Exists.")
|
||||||
@ -66,17 +67,10 @@ class MessageFeedbackApi(InstalledAppResource):
|
|||||||
|
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("rating", type=str, choices=["like", "dislike", None], location="json")
|
parser.add_argument("rating", type=str, choices=["like", "dislike", None], location="json")
|
||||||
parser.add_argument("content", type=str, location="json")
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
MessageService.create_feedback(
|
MessageService.create_feedback(app_model, message_id, current_user, args["rating"])
|
||||||
app_model=app_model,
|
|
||||||
message_id=message_id,
|
|
||||||
user=current_user,
|
|
||||||
rating=args.get("rating"),
|
|
||||||
content=args.get("content"),
|
|
||||||
)
|
|
||||||
except services.errors.message.MessageNotExistsError:
|
except services.errors.message.MessageNotExistsError:
|
||||||
raise NotFound("Message Not Exists.")
|
raise NotFound("Message Not Exists.")
|
||||||
|
|
||||||
@ -159,3 +153,21 @@ class MessageSuggestedQuestionApi(InstalledAppResource):
|
|||||||
raise InternalServerError()
|
raise InternalServerError()
|
||||||
|
|
||||||
return {"data": questions}
|
return {"data": questions}
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(MessageListApi, "/installed-apps/<uuid:installed_app_id>/messages", endpoint="installed_app_messages")
|
||||||
|
api.add_resource(
|
||||||
|
MessageFeedbackApi,
|
||||||
|
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/feedbacks",
|
||||||
|
endpoint="installed_app_message_feedback",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
MessageMoreLikeThisApi,
|
||||||
|
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/more-like-this",
|
||||||
|
endpoint="installed_app_more_like_this",
|
||||||
|
)
|
||||||
|
api.add_resource(
|
||||||
|
MessageSuggestedQuestionApi,
|
||||||
|
"/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/suggested-questions",
|
||||||
|
endpoint="installed_app_suggested_question",
|
||||||
|
)
|
||||||
|
|||||||
@ -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.common import helpers as controller_helpers
|
from controllers.common import helpers as controller_helpers
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, fields, marshal_with, reqparse
|
||||||
|
|
||||||
from constants.languages import languages
|
from constants.languages import languages
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import fields, marshal_with, reqparse # type: ignore
|
from flask_restful import fields, marshal_with, reqparse
|
||||||
from flask_restful.inputs import int_range # type: ignore
|
from flask_restful.inputs import int_range
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
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 import api
|
||||||
from controllers.console.app.error import (
|
from controllers.console.app.error import (
|
||||||
CompletionRequestError,
|
CompletionRequestError,
|
||||||
ProviderModelCurrentlyNotSupportError,
|
ProviderModelCurrentlyNotSupportError,
|
||||||
@ -13,11 +14,7 @@ from controllers.console.explore.error import NotWorkflowAppError
|
|||||||
from controllers.console.explore.wraps import InstalledAppResource
|
from controllers.console.explore.wraps import InstalledAppResource
|
||||||
from core.app.apps.base_app_queue_manager import AppQueueManager
|
from core.app.apps.base_app_queue_manager import AppQueueManager
|
||||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||||
from core.errors.error import (
|
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
|
||||||
ModelCurrentlyNotSupportError,
|
|
||||||
ProviderTokenNotInitError,
|
|
||||||
QuotaExceededError,
|
|
||||||
)
|
|
||||||
from core.model_runtime.errors.invoke import InvokeError
|
from core.model_runtime.errors.invoke import InvokeError
|
||||||
from libs import helper
|
from libs import helper
|
||||||
from libs.login import current_user
|
from libs.login import current_user
|
||||||
@ -76,3 +73,9 @@ class InstalledAppWorkflowTaskStopApi(InstalledAppResource):
|
|||||||
AppQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
|
AppQueueManager.set_stop_flag(task_id, InvokeFrom.EXPLORE, current_user.id)
|
||||||
|
|
||||||
return {"result": "success"}
|
return {"result": "success"}
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(InstalledAppWorkflowRunApi, "/installed-apps/<uuid:installed_app_id>/workflows/run")
|
||||||
|
api.add_resource(
|
||||||
|
InstalledAppWorkflowTaskStopApi, "/installed-apps/<uuid:installed_app_id>/workflows/tasks/<string:task_id>/stop"
|
||||||
|
)
|
||||||
|
|||||||
@ -1,17 +1,13 @@
|
|||||||
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.explore.error import AppAccessDeniedError
|
|
||||||
from controllers.console.wraps import account_initialization_required
|
from controllers.console.wraps import account_initialization_required
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from models import InstalledApp
|
from models import InstalledApp
|
||||||
from services.app_service import AppService
|
|
||||||
from services.enterprise.enterprise_service import EnterpriseService
|
|
||||||
from services.feature_service import FeatureService
|
|
||||||
|
|
||||||
|
|
||||||
def installed_app_required(view=None):
|
def installed_app_required(view=None):
|
||||||
@ -52,30 +48,6 @@ def installed_app_required(view=None):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def user_allowed_to_access_app(view=None):
|
|
||||||
def decorator(view):
|
|
||||||
@wraps(view)
|
|
||||||
def decorated(installed_app: InstalledApp, *args, **kwargs):
|
|
||||||
feature = FeatureService.get_system_features()
|
|
||||||
if feature.webapp_auth.enabled:
|
|
||||||
app_id = installed_app.app_id
|
|
||||||
app_code = AppService.get_app_code_by_id(app_id)
|
|
||||||
res = EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(
|
|
||||||
user_id=str(current_user.id),
|
|
||||||
app_code=app_code,
|
|
||||||
)
|
|
||||||
if not res:
|
|
||||||
raise AppAccessDeniedError()
|
|
||||||
|
|
||||||
return view(installed_app, *args, **kwargs)
|
|
||||||
|
|
||||||
return decorated
|
|
||||||
if view:
|
|
||||||
return decorator(view)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
class InstalledAppResource(Resource):
|
class InstalledAppResource(Resource):
|
||||||
# must be reversed if there are multiple decorators
|
# must be reversed if there are multiple decorators
|
||||||
|
method_decorators = [installed_app_required, account_initialization_required, login_required]
|
||||||
method_decorators = [user_allowed_to_access_app, installed_app_required, account_initialization_required, login_required]
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, marshal_with, reqparse
|
||||||
|
|
||||||
from constants import HIDDEN_VALUE
|
from constants import HIDDEN_VALUE
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource # type: ignore
|
from flask_restful import Resource
|
||||||
|
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from services.feature_service import FeatureService
|
from services.feature_service import FeatureService
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
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
|
||||||
@ -50,8 +48,7 @@ class FileApi(Resource):
|
|||||||
@cloud_edition_billing_resource_check("documents")
|
@cloud_edition_billing_resource_check("documents")
|
||||||
def post(self):
|
def post(self):
|
||||||
file = request.files["file"]
|
file = request.files["file"]
|
||||||
source_str = request.form.get("source")
|
source = request.form.get("source")
|
||||||
source: Literal["datasets"] | None = "datasets" if source_str == "datasets" else None
|
|
||||||
|
|
||||||
if "file" not in request.files:
|
if "file" not in request.files:
|
||||||
raise NoFileUploadedError()
|
raise NoFileUploadedError()
|
||||||
|
|||||||
@ -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 configs import dify_config
|
from configs import dify_config
|
||||||
from libs.helper import StrLen
|
from libs.helper import StrLen
|
||||||
|
|||||||
@ -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,12 +2,11 @@ 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
|
||||||
from controllers.common.errors import RemoteFileUploadError
|
|
||||||
from core.file import helpers as file_helpers
|
from core.file import helpers as file_helpers
|
||||||
from core.helper import ssrf_proxy
|
from core.helper import ssrf_proxy
|
||||||
from fields.file_fields import file_fields_with_signed_url, remote_file_info_fields
|
from fields.file_fields import file_fields_with_signed_url, remote_file_info_fields
|
||||||
@ -44,14 +43,10 @@ class RemoteFileUploadApi(Resource):
|
|||||||
|
|
||||||
url = args["url"]
|
url = args["url"]
|
||||||
|
|
||||||
try:
|
resp = ssrf_proxy.head(url=url)
|
||||||
resp = ssrf_proxy.head(url=url)
|
if resp.status_code != httpx.codes.OK:
|
||||||
if resp.status_code != httpx.codes.OK:
|
resp = ssrf_proxy.get(url=url, timeout=3, follow_redirects=True)
|
||||||
resp = ssrf_proxy.get(url=url, timeout=3, follow_redirects=True)
|
resp.raise_for_status()
|
||||||
if resp.status_code != httpx.codes.OK:
|
|
||||||
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {resp.text}")
|
|
||||||
except httpx.RequestError as e:
|
|
||||||
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {str(e)}")
|
|
||||||
|
|
||||||
file_info = helpers.guess_file_info_from_response(resp)
|
file_info = helpers.guess_file_info_from_response(resp)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
@ -23,7 +23,7 @@ class TagListApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@marshal_with(tag_fields)
|
@marshal_with(tag_fields)
|
||||||
def get(self):
|
def get(self):
|
||||||
tag_type = request.args.get("type", type=str, default="")
|
tag_type = request.args.get("type", type=str)
|
||||||
keyword = request.args.get("keyword", default=None, type=str)
|
keyword = request.args.get("keyword", default=None, type=str)
|
||||||
tags = TagService.get_tags(tag_type, current_user.current_tenant_id, keyword)
|
tags = TagService.get_tags(tag_type, current_user.current_tenant_id, keyword)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
@ -11,7 +11,6 @@ from controllers.console import api
|
|||||||
from controllers.console.workspace.error import (
|
from controllers.console.workspace.error import (
|
||||||
AccountAlreadyInitedError,
|
AccountAlreadyInitedError,
|
||||||
CurrentPasswordIncorrectError,
|
CurrentPasswordIncorrectError,
|
||||||
InvalidAccountDeletionCodeError,
|
|
||||||
InvalidInvitationCodeError,
|
InvalidInvitationCodeError,
|
||||||
RepeatPasswordNotMatchError,
|
RepeatPasswordNotMatchError,
|
||||||
)
|
)
|
||||||
@ -22,7 +21,6 @@ from libs.helper import TimestampField, timezone
|
|||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
from models import AccountIntegrate, InvitationCode
|
from models import AccountIntegrate, InvitationCode
|
||||||
from services.account_service import AccountService
|
from services.account_service import AccountService
|
||||||
from services.billing_service import BillingService
|
|
||||||
from services.errors.account import CurrentPasswordIncorrectError as ServiceCurrentPasswordIncorrectError
|
from services.errors.account import CurrentPasswordIncorrectError as ServiceCurrentPasswordIncorrectError
|
||||||
|
|
||||||
|
|
||||||
@ -244,54 +242,6 @@ class AccountIntegrateApi(Resource):
|
|||||||
return {"data": integrate_data}
|
return {"data": integrate_data}
|
||||||
|
|
||||||
|
|
||||||
class AccountDeleteVerifyApi(Resource):
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
def get(self):
|
|
||||||
account = current_user
|
|
||||||
|
|
||||||
token, code = AccountService.generate_account_deletion_verification_code(account)
|
|
||||||
AccountService.send_account_deletion_verification_email(account, code)
|
|
||||||
|
|
||||||
return {"result": "success", "data": token}
|
|
||||||
|
|
||||||
|
|
||||||
class AccountDeleteApi(Resource):
|
|
||||||
@setup_required
|
|
||||||
@login_required
|
|
||||||
@account_initialization_required
|
|
||||||
def post(self):
|
|
||||||
account = current_user
|
|
||||||
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
parser.add_argument("token", type=str, required=True, location="json")
|
|
||||||
parser.add_argument("code", type=str, required=True, location="json")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if not AccountService.verify_account_deletion_code(args["token"], args["code"]):
|
|
||||||
raise InvalidAccountDeletionCodeError()
|
|
||||||
|
|
||||||
AccountService.delete_account(account)
|
|
||||||
|
|
||||||
return {"result": "success"}
|
|
||||||
|
|
||||||
|
|
||||||
class AccountDeleteUpdateFeedbackApi(Resource):
|
|
||||||
@setup_required
|
|
||||||
def post(self):
|
|
||||||
account = current_user
|
|
||||||
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
parser.add_argument("email", type=str, required=True, location="json")
|
|
||||||
parser.add_argument("feedback", type=str, required=True, location="json")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
BillingService.update_account_deletion_feedback(args["email"], args["feedback"])
|
|
||||||
|
|
||||||
return {"result": "success"}
|
|
||||||
|
|
||||||
|
|
||||||
# Register API resources
|
# Register API resources
|
||||||
api.add_resource(AccountInitApi, "/account/init")
|
api.add_resource(AccountInitApi, "/account/init")
|
||||||
api.add_resource(AccountProfileApi, "/account/profile")
|
api.add_resource(AccountProfileApi, "/account/profile")
|
||||||
@ -302,8 +252,5 @@ api.add_resource(AccountInterfaceThemeApi, "/account/interface-theme")
|
|||||||
api.add_resource(AccountTimezoneApi, "/account/timezone")
|
api.add_resource(AccountTimezoneApi, "/account/timezone")
|
||||||
api.add_resource(AccountPasswordApi, "/account/password")
|
api.add_resource(AccountPasswordApi, "/account/password")
|
||||||
api.add_resource(AccountIntegrateApi, "/account/integrates")
|
api.add_resource(AccountIntegrateApi, "/account/integrates")
|
||||||
api.add_resource(AccountDeleteVerifyApi, "/account/delete/verify")
|
|
||||||
api.add_resource(AccountDeleteApi, "/account/delete")
|
|
||||||
api.add_resource(AccountDeleteUpdateFeedbackApi, "/account/delete/feedback")
|
|
||||||
# api.add_resource(AccountEmailApi, '/account/email')
|
# api.add_resource(AccountEmailApi, '/account/email')
|
||||||
# api.add_resource(AccountEmailVerifyApi, '/account/email-verify')
|
# api.add_resource(AccountEmailVerifyApi, '/account/email-verify')
|
||||||
|
|||||||
@ -35,9 +35,3 @@ class AccountNotInitializedError(BaseHTTPException):
|
|||||||
error_code = "account_not_initialized"
|
error_code = "account_not_initialized"
|
||||||
description = "The account has not been initialized yet. Please proceed with the initialization process first."
|
description = "The account has not been initialized yet. Please proceed with the initialization process first."
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
|
|
||||||
class InvalidAccountDeletionCodeError(BaseHTTPException):
|
|
||||||
error_code = "invalid_account_deletion_code"
|
|
||||||
description = "Invalid account deletion code."
|
|
||||||
code = 400
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from flask_restful import Resource, reqparse # type: ignore
|
from flask_restful import Resource, reqparse
|
||||||
from werkzeug.exceptions import Forbidden
|
from werkzeug.exceptions import Forbidden
|
||||||
|
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
@ -37,7 +37,7 @@ class LoadBalancingCredentialsValidateApi(Resource):
|
|||||||
model_load_balancing_service = ModelLoadBalancingService()
|
model_load_balancing_service = ModelLoadBalancingService()
|
||||||
|
|
||||||
result = True
|
result = True
|
||||||
error = ""
|
error = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
model_load_balancing_service.validate_load_balancing_credentials(
|
model_load_balancing_service.validate_load_balancing_credentials(
|
||||||
@ -86,7 +86,7 @@ class LoadBalancingConfigCredentialsValidateApi(Resource):
|
|||||||
model_load_balancing_service = ModelLoadBalancingService()
|
model_load_balancing_service = ModelLoadBalancingService()
|
||||||
|
|
||||||
result = True
|
result = True
|
||||||
error = ""
|
error = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
model_load_balancing_service.validate_load_balancing_credentials(
|
model_load_balancing_service.validate_load_balancing_credentials(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from urllib import parse
|
from urllib import parse
|
||||||
|
|
||||||
from flask_login import current_user # type: ignore
|
from flask_login import current_user
|
||||||
from flask_restful import Resource, abort, marshal_with, reqparse # type: ignore
|
from flask_restful import Resource, abort, marshal_with, reqparse
|
||||||
|
|
||||||
import services
|
import services
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
@ -89,19 +89,19 @@ class MemberCancelInviteApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
def delete(self, member_id):
|
def delete(self, member_id):
|
||||||
member = db.session.query(Account).filter(Account.id == str(member_id)).first()
|
member = db.session.query(Account).filter(Account.id == str(member_id)).first()
|
||||||
if member is None:
|
if not member:
|
||||||
abort(404)
|
abort(404)
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
TenantService.remove_member_from_tenant(current_user.current_tenant, member, current_user)
|
TenantService.remove_member_from_tenant(current_user.current_tenant, member, current_user)
|
||||||
except services.errors.account.CannotOperateSelfError as e:
|
except services.errors.account.CannotOperateSelfError as e:
|
||||||
return {"code": "cannot-operate-self", "message": str(e)}, 400
|
return {"code": "cannot-operate-self", "message": str(e)}, 400
|
||||||
except services.errors.account.NoPermissionError as e:
|
except services.errors.account.NoPermissionError as e:
|
||||||
return {"code": "forbidden", "message": str(e)}, 403
|
return {"code": "forbidden", "message": str(e)}, 403
|
||||||
except services.errors.account.MemberNotInTenantError as e:
|
except services.errors.account.MemberNotInTenantError as e:
|
||||||
return {"code": "member-not-found", "message": str(e)}, 404
|
return {"code": "member-not-found", "message": str(e)}, 404
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(str(e))
|
raise ValueError(str(e))
|
||||||
|
|
||||||
return {"result": "success"}, 204
|
return {"result": "success"}, 204
|
||||||
|
|
||||||
@ -126,7 +126,6 @@ class MemberUpdateRoleApi(Resource):
|
|||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
assert member is not None, "Member not found"
|
|
||||||
TenantService.update_member_role(current_user.current_tenant, member, new_role, current_user)
|
TenantService.update_member_role(current_user.current_tenant, member, new_role, current_user)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(str(e))
|
raise ValueError(str(e))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user