Merge branch 'main' into feat-add-remote-file-upload

pull/18666/head
GuanMu 1 year ago committed by GitHub
commit 46c2a920a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -5,18 +5,35 @@ root = true
# Unix-style newlines with a newline ending every file # Unix-style newlines with a newline ending every file
[*] [*]
charset = utf-8
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true
[*.py]
indent_size = 4
indent_style = space
[*.{yml,yaml}]
indent_style = space
indent_size = 2
[*.toml]
indent_size = 4
indent_style = space
# Markdown and MDX are whitespace sensitive languages.
# Do not remove trailing spaces.
[*.{md,mdx}]
trim_trailing_whitespace = false
# Matches multiple files with brace expansion notation # Matches multiple files with brace expansion notation
# Set default charset # Set default charset
[*.{js,tsx}] [*.{js,tsx}]
charset = utf-8
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
# Matches the exact files package.json
# Matches the exact files either package.json or .travis.yml [package.json]
[{package.json,.travis.yml}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2

@ -0,0 +1,22 @@
{
"Verbose": false,
"Debug": false,
"IgnoreDefaults": false,
"SpacesAfterTabs": false,
"NoColor": false,
"Exclude": [
"^web/public/vs/",
"^web/public/pdf.worker.min.mjs$",
"web/app/components/base/icons/src/vender/"
],
"AllowedContentTypes": [],
"PassedFiles": [],
"Disable": {
"EndOfLine": false,
"Indentation": false,
"IndentSize": true,
"InsertFinalNewline": false,
"TrimTrailingWhitespace": false,
"MaxLineLength": false
}
}

@ -88,3 +88,6 @@ jobs:
- name: Run Workflow - name: Run Workflow
run: uv run --project api bash dev/pytest/pytest_workflow.sh run: uv run --project api bash dev/pytest/pytest_workflow.sh
- name: Run Tool
run: uv run --project api bash dev/pytest/pytest_tools.sh

@ -9,6 +9,12 @@ concurrency:
group: style-${{ github.head_ref || github.run_id }} group: style-${{ github.head_ref || github.run_id }}
cancel-in-progress: true cancel-in-progress: true
permissions:
checks: write
statuses: write
contents: read
jobs: jobs:
python-style: python-style:
name: Python Style name: Python Style
@ -43,8 +49,8 @@ jobs:
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
run: | run: |
uv run --directory api ruff --version uv run --directory api ruff --version
uv run --directory api ruff check ./ uv run --directory api ruff check --diff ./
uv run --directory api ruff format --check ./ uv run --directory api ruff format --check --diff ./
- name: Dotenv check - name: Dotenv check
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
@ -163,3 +169,14 @@ jobs:
VALIDATE_DOCKERFILE_HADOLINT: true VALIDATE_DOCKERFILE_HADOLINT: true
VALIDATE_XML: true VALIDATE_XML: true
VALIDATE_YAML: true VALIDATE_YAML: true
- name: EditorConfig checks
uses: super-linter/super-linter/slim@v7
env:
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IGNORE_GENERATED_FILES: true
IGNORE_GITIGNORED_FILES: true
# EditorConfig validation
VALIDATE_EDITORCONFIG: true
EDITORCONFIG_FILE_NAME: editorconfig-checker.json

@ -90,3 +90,4 @@
```bash ```bash
uv run -P api bash dev/pytest/pytest_all_tests.sh uv run -P api bash dev/pytest/pytest_all_tests.sh
``` ```

@ -18,7 +18,7 @@ else:
# so we need to disable gevent in debug mode. # so we need to disable gevent in debug mode.
# If you are using debugpy and set GEVENT_SUPPORT=True, you can debug with gevent. # If you are using debugpy and set GEVENT_SUPPORT=True, you can debug with gevent.
if (flask_debug := os.environ.get("FLASK_DEBUG", "0")) and flask_debug.lower() in {"false", "0", "no"}: if (flask_debug := os.environ.get("FLASK_DEBUG", "0")) and flask_debug.lower() in {"false", "0", "no"}:
from gevent import monkey # type: ignore from gevent import monkey
# gevent # gevent
monkey.patch_all() monkey.patch_all()

@ -52,10 +52,8 @@ def initialize_extensions(app: DifyApp):
ext_mail, ext_mail,
ext_migrate, ext_migrate,
ext_otel, ext_otel,
ext_otel_patch,
ext_proxy_fix, ext_proxy_fix,
ext_redis, ext_redis,
ext_repositories,
ext_sentry, ext_sentry,
ext_set_secretkey, ext_set_secretkey,
ext_storage, ext_storage,
@ -76,7 +74,6 @@ def initialize_extensions(app: DifyApp):
ext_migrate, ext_migrate,
ext_redis, ext_redis,
ext_storage, ext_storage,
ext_repositories,
ext_celery, ext_celery,
ext_login, ext_login,
ext_mail, ext_mail,
@ -85,7 +82,6 @@ def initialize_extensions(app: DifyApp):
ext_proxy_fix, ext_proxy_fix,
ext_blueprints, ext_blueprints,
ext_commands, ext_commands,
ext_otel_patch, # Apply patch before initializing OpenTelemetry
ext_otel, ext_otel,
] ]
for ext in extensions: for ext in extensions:

@ -17,6 +17,7 @@ from core.rag.models.document import Document
from events.app_event import app_was_created from events.app_event import app_was_created
from extensions.ext_database import db from extensions.ext_database import db
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from extensions.ext_storage import storage
from libs.helper import email as email_validate from libs.helper import email as email_validate
from libs.password import hash_password, password_pattern, valid_password from libs.password import hash_password, password_pattern, valid_password
from libs.rsa import generate_key_pair from libs.rsa import generate_key_pair
@ -667,7 +668,7 @@ def upgrade_db():
click.echo(click.style("Starting database migration.", fg="green")) click.echo(click.style("Starting database migration.", fg="green"))
# run db migration # run db migration
import flask_migrate # type: ignore import flask_migrate
flask_migrate.upgrade() flask_migrate.upgrade()
@ -815,3 +816,331 @@ def clear_free_plan_tenant_expired_logs(days: int, batch: int, tenant_ids: list[
ClearFreePlanTenantExpiredLogs.process(days, batch, tenant_ids) ClearFreePlanTenantExpiredLogs.process(days, batch, tenant_ids)
click.echo(click.style("Clear free plan tenant expired logs completed.", fg="green")) click.echo(click.style("Clear free plan tenant expired logs completed.", fg="green"))
@click.option("-f", "--force", is_flag=True, help="Skip user confirmation and force the command to execute.")
@click.command("clear-orphaned-file-records", help="Clear orphaned file records.")
def clear_orphaned_file_records(force: bool):
"""
Clear orphaned file records in the database.
"""
# define tables and columns to process
files_tables = [
{"table": "upload_files", "id_column": "id", "key_column": "key"},
{"table": "tool_files", "id_column": "id", "key_column": "file_key"},
]
ids_tables = [
{"type": "uuid", "table": "message_files", "column": "upload_file_id"},
{"type": "text", "table": "documents", "column": "data_source_info"},
{"type": "text", "table": "document_segments", "column": "content"},
{"type": "text", "table": "messages", "column": "answer"},
{"type": "text", "table": "workflow_node_executions", "column": "inputs"},
{"type": "text", "table": "workflow_node_executions", "column": "process_data"},
{"type": "text", "table": "workflow_node_executions", "column": "outputs"},
{"type": "text", "table": "conversations", "column": "introduction"},
{"type": "text", "table": "conversations", "column": "system_instruction"},
{"type": "json", "table": "messages", "column": "inputs"},
{"type": "json", "table": "messages", "column": "message"},
]
# notify user and ask for confirmation
click.echo(
click.style(
"This command will first find and delete orphaned file records from the message_files table,", fg="yellow"
)
)
click.echo(
click.style(
"and then it will find and delete orphaned file records in the following tables:",
fg="yellow",
)
)
for files_table in files_tables:
click.echo(click.style(f"- {files_table['table']}", fg="yellow"))
click.echo(
click.style("The following tables and columns will be scanned to find orphaned file records:", fg="yellow")
)
for ids_table in ids_tables:
click.echo(click.style(f"- {ids_table['table']} ({ids_table['column']})", fg="yellow"))
click.echo("")
click.echo(click.style("!!! USE WITH CAUTION !!!", fg="red"))
click.echo(
click.style(
(
"Since not all patterns have been fully tested, "
"please note that this command may delete unintended file records."
),
fg="yellow",
)
)
click.echo(
click.style("This cannot be undone. Please make sure to back up your database before proceeding.", fg="yellow")
)
click.echo(
click.style(
(
"It is also recommended to run this during the maintenance window, "
"as this may cause high load on your instance."
),
fg="yellow",
)
)
if not force:
click.confirm("Do you want to proceed?", abort=True)
# start the cleanup process
click.echo(click.style("Starting orphaned file records cleanup.", fg="white"))
# clean up the orphaned records in the message_files table where message_id doesn't exist in messages table
try:
click.echo(
click.style("- Listing message_files records where message_id doesn't exist in messages table", fg="white")
)
query = (
"SELECT mf.id, mf.message_id "
"FROM message_files mf LEFT JOIN messages m ON mf.message_id = m.id "
"WHERE m.id IS NULL"
)
orphaned_message_files = []
with db.engine.begin() as conn:
rs = conn.execute(db.text(query))
for i in rs:
orphaned_message_files.append({"id": str(i[0]), "message_id": str(i[1])})
if orphaned_message_files:
click.echo(click.style(f"Found {len(orphaned_message_files)} orphaned message_files records:", fg="white"))
for record in orphaned_message_files:
click.echo(click.style(f" - id: {record['id']}, message_id: {record['message_id']}", fg="black"))
if not force:
click.confirm(
(
f"Do you want to proceed "
f"to delete all {len(orphaned_message_files)} orphaned message_files records?"
),
abort=True,
)
click.echo(click.style("- Deleting orphaned message_files records", fg="white"))
query = "DELETE FROM message_files WHERE id IN :ids"
with db.engine.begin() as conn:
conn.execute(db.text(query), {"ids": tuple([record["id"] for record in orphaned_message_files])})
click.echo(
click.style(f"Removed {len(orphaned_message_files)} orphaned message_files records.", fg="green")
)
else:
click.echo(click.style("No orphaned message_files records found. There is nothing to delete.", fg="green"))
except Exception as e:
click.echo(click.style(f"Error deleting orphaned message_files records: {str(e)}", fg="red"))
# clean up the orphaned records in the rest of the *_files tables
try:
# fetch file id and keys from each table
all_files_in_tables = []
for files_table in files_tables:
click.echo(click.style(f"- Listing file records in table {files_table['table']}", fg="white"))
query = f"SELECT {files_table['id_column']}, {files_table['key_column']} FROM {files_table['table']}"
with db.engine.begin() as conn:
rs = conn.execute(db.text(query))
for i in rs:
all_files_in_tables.append({"table": files_table["table"], "id": str(i[0]), "key": i[1]})
click.echo(click.style(f"Found {len(all_files_in_tables)} files in tables.", fg="white"))
# fetch referred table and columns
guid_regexp = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
all_ids_in_tables = []
for ids_table in ids_tables:
query = ""
if ids_table["type"] == "uuid":
click.echo(
click.style(
f"- Listing file ids in column {ids_table['column']} in table {ids_table['table']}", fg="white"
)
)
query = (
f"SELECT {ids_table['column']} FROM {ids_table['table']} WHERE {ids_table['column']} IS NOT NULL"
)
with db.engine.begin() as conn:
rs = conn.execute(db.text(query))
for i in rs:
all_ids_in_tables.append({"table": ids_table["table"], "id": str(i[0])})
elif ids_table["type"] == "text":
click.echo(
click.style(
f"- Listing file-id-like strings in column {ids_table['column']} in table {ids_table['table']}",
fg="white",
)
)
query = (
f"SELECT regexp_matches({ids_table['column']}, '{guid_regexp}', 'g') AS extracted_id "
f"FROM {ids_table['table']}"
)
with db.engine.begin() as conn:
rs = conn.execute(db.text(query))
for i in rs:
for j in i[0]:
all_ids_in_tables.append({"table": ids_table["table"], "id": j})
elif ids_table["type"] == "json":
click.echo(
click.style(
(
f"- Listing file-id-like JSON string in column {ids_table['column']} "
f"in table {ids_table['table']}"
),
fg="white",
)
)
query = (
f"SELECT regexp_matches({ids_table['column']}::text, '{guid_regexp}', 'g') AS extracted_id "
f"FROM {ids_table['table']}"
)
with db.engine.begin() as conn:
rs = conn.execute(db.text(query))
for i in rs:
for j in i[0]:
all_ids_in_tables.append({"table": ids_table["table"], "id": j})
click.echo(click.style(f"Found {len(all_ids_in_tables)} file ids in tables.", fg="white"))
except Exception as e:
click.echo(click.style(f"Error fetching keys: {str(e)}", fg="red"))
return
# find orphaned files
all_files = [file["id"] for file in all_files_in_tables]
all_ids = [file["id"] for file in all_ids_in_tables]
orphaned_files = list(set(all_files) - set(all_ids))
if not orphaned_files:
click.echo(click.style("No orphaned file records found. There is nothing to delete.", fg="green"))
return
click.echo(click.style(f"Found {len(orphaned_files)} orphaned file records.", fg="white"))
for file in orphaned_files:
click.echo(click.style(f"- orphaned file id: {file}", fg="black"))
if not force:
click.confirm(f"Do you want to proceed to delete all {len(orphaned_files)} orphaned file records?", abort=True)
# delete orphaned records for each file
try:
for files_table in files_tables:
click.echo(click.style(f"- Deleting orphaned file records in table {files_table['table']}", fg="white"))
query = f"DELETE FROM {files_table['table']} WHERE {files_table['id_column']} IN :ids"
with db.engine.begin() as conn:
conn.execute(db.text(query), {"ids": tuple(orphaned_files)})
except Exception as e:
click.echo(click.style(f"Error deleting orphaned file records: {str(e)}", fg="red"))
return
click.echo(click.style(f"Removed {len(orphaned_files)} orphaned file records.", fg="green"))
@click.option("-f", "--force", is_flag=True, help="Skip user confirmation and force the command to execute.")
@click.command("remove-orphaned-files-on-storage", help="Remove orphaned files on the storage.")
def remove_orphaned_files_on_storage(force: bool):
"""
Remove orphaned files on the storage.
"""
# define tables and columns to process
files_tables = [
{"table": "upload_files", "key_column": "key"},
{"table": "tool_files", "key_column": "file_key"},
]
storage_paths = ["image_files", "tools", "upload_files"]
# notify user and ask for confirmation
click.echo(click.style("This command will find and remove orphaned files on the storage,", fg="yellow"))
click.echo(
click.style("by comparing the files on the storage with the records in the following tables:", fg="yellow")
)
for files_table in files_tables:
click.echo(click.style(f"- {files_table['table']}", fg="yellow"))
click.echo(click.style("The following paths on the storage will be scanned to find orphaned files:", fg="yellow"))
for storage_path in storage_paths:
click.echo(click.style(f"- {storage_path}", fg="yellow"))
click.echo("")
click.echo(click.style("!!! USE WITH CAUTION !!!", fg="red"))
click.echo(
click.style(
"Currently, this command will work only for opendal based storage (STORAGE_TYPE=opendal).", fg="yellow"
)
)
click.echo(
click.style(
"Since not all patterns have been fully tested, please note that this command may delete unintended files.",
fg="yellow",
)
)
click.echo(
click.style("This cannot be undone. Please make sure to back up your storage before proceeding.", fg="yellow")
)
click.echo(
click.style(
(
"It is also recommended to run this during the maintenance window, "
"as this may cause high load on your instance."
),
fg="yellow",
)
)
if not force:
click.confirm("Do you want to proceed?", abort=True)
# start the cleanup process
click.echo(click.style("Starting orphaned files cleanup.", fg="white"))
# fetch file id and keys from each table
all_files_in_tables = []
try:
for files_table in files_tables:
click.echo(click.style(f"- Listing files from table {files_table['table']}", fg="white"))
query = f"SELECT {files_table['key_column']} FROM {files_table['table']}"
with db.engine.begin() as conn:
rs = conn.execute(db.text(query))
for i in rs:
all_files_in_tables.append(str(i[0]))
click.echo(click.style(f"Found {len(all_files_in_tables)} files in tables.", fg="white"))
except Exception as e:
click.echo(click.style(f"Error fetching keys: {str(e)}", fg="red"))
all_files_on_storage = []
for storage_path in storage_paths:
try:
click.echo(click.style(f"- Scanning files on storage path {storage_path}", fg="white"))
files = storage.scan(path=storage_path, files=True, directories=False)
all_files_on_storage.extend(files)
except FileNotFoundError as e:
click.echo(click.style(f" -> Skipping path {storage_path} as it does not exist.", fg="yellow"))
continue
except Exception as e:
click.echo(click.style(f" -> Error scanning files on storage path {storage_path}: {str(e)}", fg="red"))
continue
click.echo(click.style(f"Found {len(all_files_on_storage)} files on storage.", fg="white"))
# find orphaned files
orphaned_files = list(set(all_files_on_storage) - set(all_files_in_tables))
if not orphaned_files:
click.echo(click.style("No orphaned files found. There is nothing to remove.", fg="green"))
return
click.echo(click.style(f"Found {len(orphaned_files)} orphaned files.", fg="white"))
for file in orphaned_files:
click.echo(click.style(f"- orphaned file: {file}", fg="black"))
if not force:
click.confirm(f"Do you want to proceed to remove all {len(orphaned_files)} orphaned files?", abort=True)
# delete orphaned files
removed_files = 0
error_files = 0
for file in orphaned_files:
try:
storage.delete(file)
removed_files += 1
click.echo(click.style(f"- Removing orphaned file: {file}", fg="white"))
except Exception as e:
error_files += 1
click.echo(click.style(f"- Error deleting orphaned file {file}: {str(e)}", fg="red"))
continue
if error_files == 0:
click.echo(click.style(f"Removed {removed_files} orphaned files without errors.", fg="green"))
else:
click.echo(click.style(f"Removed {removed_files} orphaned files, with {error_files} errors.", fg="yellow"))

@ -398,6 +398,11 @@ class InnerAPIConfig(BaseSettings):
default=False, default=False,
) )
INNER_API_KEY: Optional[str] = Field(
description="API key for accessing the internal API",
default=None,
)
class LoggingConfig(BaseSettings): class LoggingConfig(BaseSettings):
""" """

@ -1,4 +1,5 @@
from typing import Optional import enum
from typing import Literal, Optional
from pydantic import Field, PositiveInt from pydantic import Field, PositiveInt
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
@ -9,6 +10,14 @@ class OpenSearchConfig(BaseSettings):
Configuration settings for OpenSearch Configuration settings for OpenSearch
""" """
class AuthMethod(enum.StrEnum):
"""
Authentication method for OpenSearch
"""
BASIC = "basic"
AWS_MANAGED_IAM = "aws_managed_iam"
OPENSEARCH_HOST: Optional[str] = Field( OPENSEARCH_HOST: Optional[str] = Field(
description="Hostname or IP address of the OpenSearch server (e.g., 'localhost' or 'opensearch.example.com')", description="Hostname or IP address of the OpenSearch server (e.g., 'localhost' or 'opensearch.example.com')",
default=None, default=None,
@ -19,6 +28,16 @@ class OpenSearchConfig(BaseSettings):
default=9200, default=9200,
) )
OPENSEARCH_SECURE: bool = Field(
description="Whether to use SSL/TLS encrypted connection for OpenSearch (True for HTTPS, False for HTTP)",
default=False,
)
OPENSEARCH_AUTH_METHOD: AuthMethod = Field(
description="Authentication method for OpenSearch connection (default is 'basic')",
default=AuthMethod.BASIC,
)
OPENSEARCH_USER: Optional[str] = Field( OPENSEARCH_USER: Optional[str] = Field(
description="Username for authenticating with OpenSearch", description="Username for authenticating with OpenSearch",
default=None, default=None,
@ -29,7 +48,11 @@ class OpenSearchConfig(BaseSettings):
default=None, default=None,
) )
OPENSEARCH_SECURE: bool = Field( OPENSEARCH_AWS_REGION: Optional[str] = Field(
description="Whether to use SSL/TLS encrypted connection for OpenSearch (True for HTTPS, False for HTTP)", description="AWS region for OpenSearch (e.g. 'us-west-2')",
default=False, default=None,
)
OPENSEARCH_AWS_SERVICE: Optional[Literal["es", "aoss"]] = Field(
description="AWS service for OpenSearch (e.g. 'aoss' for OpenSearch Serverless)", default=None
) )

@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
CURRENT_VERSION: str = Field( CURRENT_VERSION: str = Field(
description="Dify version", description="Dify version",
default="1.3.0", default="1.3.1",
) )
COMMIT_SHA: str = Field( COMMIT_SHA: str = Field(

@ -16,11 +16,25 @@ AUDIO_EXTENSIONS.extend([ext.upper() for ext in AUDIO_EXTENSIONS])
if dify_config.ETL_TYPE == "Unstructured": if dify_config.ETL_TYPE == "Unstructured":
DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls"] DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls", "vtt", "properties"]
DOCUMENT_EXTENSIONS.extend(("doc", "docx", "csv", "eml", "msg", "pptx", "xml", "epub")) DOCUMENT_EXTENSIONS.extend(("doc", "docx", "csv", "eml", "msg", "pptx", "xml", "epub"))
if dify_config.UNSTRUCTURED_API_URL: if dify_config.UNSTRUCTURED_API_URL:
DOCUMENT_EXTENSIONS.append("ppt") DOCUMENT_EXTENSIONS.append("ppt")
DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS]) DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS])
else: else:
DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls", "docx", "csv"] DOCUMENT_EXTENSIONS = [
"txt",
"markdown",
"md",
"mdx",
"pdf",
"html",
"htm",
"xlsx",
"xls",
"docx",
"csv",
"vtt",
"properties",
]
DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS]) DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS])

@ -0,0 +1,7 @@
# The two constants below should keep in sync.
# Default content type for files which have no explicit content type.
DEFAULT_MIME_TYPE = "application/octet-stream"
# Default file extension for files which have no explicit content type, should
# correspond to the `DEFAULT_MIME_TYPE` above.
DEFAULT_EXTENSION = ".bin"

@ -1,4 +1,4 @@
from flask_restful import fields # type: ignore from flask_restful import fields
parameters__system_parameters = { parameters__system_parameters = {
"image_file_size_limit": fields.Integer, "image_file_size_limit": fields.Integer,

@ -1,7 +1,7 @@
from functools import wraps from functools import wraps
from flask import request from flask import request
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound, Unauthorized from werkzeug.exceptions import NotFound, Unauthorized

@ -1,7 +1,7 @@
from typing import Any from typing import Any
import flask_restful # type: ignore import flask_restful
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, fields, marshal_with from flask_restful import Resource, fields, marshal_with
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session

@ -1,4 +1,4 @@
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from controllers.console import api from controllers.console import api
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required

@ -1,4 +1,4 @@
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from controllers.console import api from controllers.console import api
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model

@ -1,6 +1,6 @@
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal, marshal_with, reqparse
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from controllers.console import api from controllers.console import api
@ -186,7 +186,7 @@ class AnnotationUpdateDeleteApi(Resource):
app_id = str(app_id) app_id = str(app_id)
annotation_id = str(annotation_id) annotation_id = str(annotation_id)
AppAnnotationService.delete_app_annotation(app_id, annotation_id) AppAnnotationService.delete_app_annotation(app_id, annotation_id)
return {"result": "success"}, 200 return {"result": "success"}, 204
class AnnotationBatchImportApi(Resource): class AnnotationBatchImportApi(Resource):

@ -1,8 +1,8 @@
import uuid import uuid
from typing import cast from typing import cast
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, inputs, marshal, marshal_with, reqparse # type: ignore from flask_restful import Resource, inputs, marshal, marshal_with, reqparse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import BadRequest, Forbidden, abort from werkzeug.exceptions import BadRequest, Forbidden, abort

@ -1,7 +1,7 @@
from typing import cast from typing import cast
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden

@ -1,7 +1,7 @@
import logging import logging
from flask import request from flask import request
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import InternalServerError from werkzeug.exceptions import InternalServerError
import services import services

@ -1,7 +1,7 @@
import logging import logging
import flask_login # type: ignore import flask_login
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import InternalServerError, NotFound from werkzeug.exceptions import InternalServerError, NotFound
import services import services

@ -1,9 +1,9 @@
from datetime import UTC, datetime from datetime import UTC, datetime
import pytz # pip install pytz import pytz # pip install pytz
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
from flask_restful.inputs import int_range # type: ignore from flask_restful.inputs import int_range
from sqlalchemy import func, or_ from sqlalchemy import func, or_
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from werkzeug.exceptions import Forbidden, NotFound from werkzeug.exceptions import Forbidden, NotFound

@ -1,4 +1,4 @@
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session

@ -1,7 +1,7 @@
import os import os
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from controllers.console import api from controllers.console import api
from controllers.console.app.error import ( from controllers.console.app.error import (

@ -1,8 +1,8 @@
import logging import logging
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore from flask_restful import Resource, fields, marshal_with, reqparse
from flask_restful.inputs import int_range # type: ignore from flask_restful.inputs import int_range
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
from controllers.console import api from controllers.console import api

@ -2,8 +2,8 @@ import json
from typing import cast from typing import cast
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource # type: ignore from flask_restful import Resource
from controllers.console import api from controllers.console import api
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model

@ -1,4 +1,4 @@
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from controllers.console import api from controllers.console import api
@ -84,7 +84,7 @@ class TraceAppConfigApi(Resource):
result = OpsService.delete_tracing_app_config(app_id=app_id, tracing_provider=args["tracing_provider"]) result = OpsService.delete_tracing_app_config(app_id=app_id, tracing_provider=args["tracing_provider"])
if not result: if not result:
raise TracingConfigNotExist() raise TracingConfigNotExist()
return {"result": "success"} return {"result": "success"}, 204
except Exception as e: except Exception as e:
raise BadRequest(str(e)) raise BadRequest(str(e))

@ -1,7 +1,7 @@
from datetime import UTC, datetime from datetime import UTC, datetime
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
from werkzeug.exceptions import Forbidden, NotFound from werkzeug.exceptions import Forbidden, NotFound
from constants.languages import supported_language from constants.languages import supported_language

@ -3,8 +3,8 @@ from decimal import Decimal
import pytz import pytz
from flask import jsonify from flask import jsonify
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from controllers.console import api from controllers.console import api
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model

@ -3,7 +3,7 @@ import logging
from typing import cast from typing import cast
from flask import abort, request from flask import abort, request
from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore from flask_restful import Resource, inputs, marshal_with, reqparse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound from werkzeug.exceptions import Forbidden, InternalServerError, NotFound

@ -1,6 +1,6 @@
from dateutil.parser import isoparse from dateutil.parser import isoparse
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
from flask_restful.inputs import int_range # type: ignore from flask_restful.inputs import int_range
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from controllers.console import api from controllers.console import api

@ -1,5 +1,5 @@
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
from flask_restful.inputs import int_range # type: ignore from flask_restful.inputs import int_range
from controllers.console import api from controllers.console import api
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model

@ -3,8 +3,8 @@ from decimal import Decimal
import pytz import pytz
from flask import jsonify from flask import jsonify
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from controllers.console import api from controllers.console import api
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model

@ -1,7 +1,7 @@
import datetime import datetime
from flask import request from flask import request
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from constants.languages import supported_language from constants.languages import supported_language
from controllers.console import api from controllers.console import api

@ -1,5 +1,5 @@
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from controllers.console import api from controllers.console import api
@ -65,7 +65,7 @@ class ApiKeyAuthDataSourceBindingDelete(Resource):
ApiKeyAuthService.delete_provider_auth(current_user.current_tenant_id, binding_id) ApiKeyAuthService.delete_provider_auth(current_user.current_tenant_id, binding_id)
return {"result": "success"}, 200 return {"result": "success"}, 204
api.add_resource(ApiKeyAuthDataSource, "/api-key-auth/data-source") api.add_resource(ApiKeyAuthDataSource, "/api-key-auth/data-source")

@ -2,8 +2,8 @@ import logging
import requests import requests
from flask import current_app, redirect, request from flask import current_app, redirect, request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource # type: ignore from flask_restful import Resource
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from configs import dify_config from configs import dify_config

@ -2,7 +2,7 @@ import base64
import secrets import secrets
from flask import request from flask import request
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session

@ -1,8 +1,8 @@
from typing import cast from typing import cast
import flask_login # type: ignore import flask_login
from flask import request from flask import request
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
import services import services
from configs import dify_config from configs import dify_config

@ -4,7 +4,7 @@ from typing import Optional
import requests import requests
from flask import current_app, redirect, request from flask import current_app, redirect, request
from flask_restful import Resource # type: ignore from flask_restful import Resource
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import Unauthorized from werkzeug.exceptions import Unauthorized

@ -1,5 +1,5 @@
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from controllers.console import api from controllers.console import api
from controllers.console.wraps import account_initialization_required, only_edition_cloud, setup_required from controllers.console.wraps import account_initialization_required, only_edition_cloud, setup_required

@ -1,6 +1,6 @@
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from libs.helper import extract_remote_ip from libs.helper import extract_remote_ip
from libs.login import login_required from libs.login import login_required

@ -2,8 +2,8 @@ import datetime
import json import json
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound

@ -1,7 +1,7 @@
import flask_restful # type: ignore import flask_restful
from flask import request from flask import request
from flask_login import current_user # type: ignore # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal, marshal_with, reqparse
from werkzeug.exceptions import Forbidden, NotFound from werkzeug.exceptions import Forbidden, NotFound
import services import services

@ -4,8 +4,8 @@ from datetime import UTC, datetime
from typing import cast from typing import cast
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, fields, marshal, marshal_with, reqparse # type: ignore from flask_restful import Resource, fields, marshal, marshal_with, reqparse
from sqlalchemy import asc, desc from sqlalchemy import asc, desc
from werkzeug.exceptions import Forbidden, NotFound from werkzeug.exceptions import Forbidden, NotFound
@ -40,7 +40,7 @@ from core.indexing_runner import IndexingRunner
from core.model_manager import ModelManager from core.model_manager import ModelManager
from core.model_runtime.entities.model_entities import ModelType from core.model_runtime.entities.model_entities import ModelType
from core.model_runtime.errors.invoke import InvokeAuthorizationError from core.model_runtime.errors.invoke import InvokeAuthorizationError
from core.plugin.manager.exc import PluginDaemonClientSideError from core.plugin.impl.exc import PluginDaemonClientSideError
from core.rag.extractor.entity.extract_setting import ExtractSetting from core.rag.extractor.entity.extract_setting import ExtractSetting
from extensions.ext_database import db from extensions.ext_database import db
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client

@ -2,8 +2,8 @@ import uuid
import pandas as pd import pandas as pd
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal, reqparse # type: ignore from flask_restful import Resource, marshal, reqparse
from werkzeug.exceptions import Forbidden, NotFound from werkzeug.exceptions import Forbidden, NotFound
import services import services
@ -131,7 +131,7 @@ class DatasetDocumentSegmentListApi(Resource):
except services.errors.account.NoPermissionError as e: except services.errors.account.NoPermissionError as e:
raise Forbidden(str(e)) raise Forbidden(str(e))
SegmentService.delete_segments(segment_ids, document, dataset) SegmentService.delete_segments(segment_ids, document, dataset)
return {"result": "success"}, 200 return {"result": "success"}, 204
class DatasetDocumentSegmentApi(Resource): class DatasetDocumentSegmentApi(Resource):
@ -333,7 +333,7 @@ class DatasetDocumentSegmentUpdateApi(Resource):
except services.errors.account.NoPermissionError as e: except services.errors.account.NoPermissionError as e:
raise Forbidden(str(e)) raise Forbidden(str(e))
SegmentService.delete_segment(segment, document, dataset) SegmentService.delete_segment(segment, document, dataset)
return {"result": "success"}, 200 return {"result": "success"}, 204
class DatasetDocumentSegmentBatchImportApi(Resource): class DatasetDocumentSegmentBatchImportApi(Resource):
@ -590,7 +590,7 @@ class ChildChunkUpdateApi(Resource):
SegmentService.delete_child_chunk(child_chunk, dataset) SegmentService.delete_child_chunk(child_chunk, dataset)
except ChildChunkDeleteIndexServiceError as e: except ChildChunkDeleteIndexServiceError as e:
raise ChildChunkDeleteIndexError(str(e)) raise ChildChunkDeleteIndexError(str(e))
return {"result": "success"}, 200 return {"result": "success"}, 204
@setup_required @setup_required
@login_required @login_required

@ -1,6 +1,6 @@
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal, reqparse # type: ignore from flask_restful import Resource, marshal, reqparse
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
import services import services
@ -135,7 +135,7 @@ class ExternalApiTemplateApi(Resource):
raise Forbidden() raise Forbidden()
ExternalDatasetService.delete_external_knowledge_api(current_user.current_tenant_id, external_knowledge_api_id) ExternalDatasetService.delete_external_knowledge_api(current_user.current_tenant_id, external_knowledge_api_id)
return {"result": "success"}, 200 return {"result": "success"}, 204
class ExternalApiUseCheckApi(Resource): class ExternalApiUseCheckApi(Resource):

@ -1,4 +1,4 @@
from flask_restful import Resource # type: ignore from flask_restful import Resource
from controllers.console import api from controllers.console import api
from controllers.console.datasets.hit_testing_base import DatasetsHitTestingBase from controllers.console.datasets.hit_testing_base import DatasetsHitTestingBase

@ -1,7 +1,7 @@
import logging import logging
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import marshal, reqparse # type: ignore from flask_restful import marshal, reqparse
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
import services.dataset_service import services.dataset_service

@ -1,5 +1,5 @@
from flask_login import current_user # type: ignore # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from controllers.console import api from controllers.console import api
@ -82,7 +82,7 @@ class DatasetMetadataApi(Resource):
DatasetService.check_dataset_permission(dataset, current_user) DatasetService.check_dataset_permission(dataset, current_user)
MetadataService.delete_metadata(dataset_id_str, metadata_id_str) MetadataService.delete_metadata(dataset_id_str, metadata_id_str)
return 200 return {"result": "success"}, 204
class DatasetMetadataBuiltInFieldApi(Resource): class DatasetMetadataBuiltInFieldApi(Resource):

@ -1,4 +1,4 @@
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from controllers.console import api from controllers.console import api
from controllers.console.datasets.error import WebsiteCrawlError from controllers.console.datasets.error import WebsiteCrawlError

@ -66,7 +66,7 @@ class ChatAudioApi(InstalledAppResource):
class ChatTextApi(InstalledAppResource): class ChatTextApi(InstalledAppResource):
def post(self, installed_app): def post(self, installed_app):
from flask_restful import reqparse # type: ignore from flask_restful import reqparse
app_model = installed_app.app app_model = installed_app.app
try: try:

@ -1,8 +1,8 @@
import logging import logging
from datetime import UTC, datetime from datetime import UTC, datetime
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import reqparse # type: ignore from flask_restful import reqparse
from werkzeug.exceptions import InternalServerError, NotFound from werkzeug.exceptions import InternalServerError, NotFound
import services import services

@ -1,6 +1,6 @@
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import marshal_with, reqparse # type: ignore from flask_restful import marshal_with, reqparse
from flask_restful.inputs import int_range # type: ignore from flask_restful.inputs import int_range
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound

@ -2,8 +2,8 @@ from datetime import UTC, datetime
from typing import Any from typing import Any
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore from flask_restful import Resource, inputs, marshal_with, reqparse
from sqlalchemy import and_ from sqlalchemy import and_
from werkzeug.exceptions import BadRequest, Forbidden, NotFound from werkzeug.exceptions import BadRequest, Forbidden, NotFound
@ -113,7 +113,7 @@ class InstalledAppApi(InstalledAppResource):
db.session.delete(installed_app) db.session.delete(installed_app)
db.session.commit() db.session.commit()
return {"result": "success", "message": "App uninstalled successfully"} return {"result": "success", "message": "App uninstalled successfully"}, 204
def patch(self, installed_app): def patch(self, installed_app):
parser = reqparse.RequestParser() parser = reqparse.RequestParser()

@ -1,8 +1,8 @@
import logging import logging
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import marshal_with, reqparse # type: ignore from flask_restful import marshal_with, reqparse
from flask_restful.inputs import int_range # type: ignore from flask_restful.inputs import int_range
from werkzeug.exceptions import InternalServerError, NotFound from werkzeug.exceptions import InternalServerError, NotFound
import services import services

@ -1,4 +1,4 @@
from flask_restful import marshal_with # type: ignore from flask_restful import marshal_with
from controllers.common import fields from controllers.common import fields
from controllers.console import api from controllers.console import api

@ -1,5 +1,5 @@
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore from flask_restful import Resource, fields, marshal_with, reqparse
from constants.languages import languages from constants.languages import languages
from controllers.console import api from controllers.console import api

@ -1,6 +1,6 @@
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import fields, marshal_with, reqparse # type: ignore from flask_restful import fields, marshal_with, reqparse
from flask_restful.inputs import int_range # type: ignore from flask_restful.inputs import int_range
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from controllers.console import api from controllers.console import api
@ -72,7 +72,7 @@ class SavedMessageApi(InstalledAppResource):
SavedMessageService.delete(app_model, current_user, message_id) SavedMessageService.delete(app_model, current_user, message_id)
return {"result": "success"} return {"result": "success"}, 204
api.add_resource( api.add_resource(

@ -1,6 +1,6 @@
import logging import logging
from flask_restful import reqparse # type: ignore from flask_restful import reqparse
from werkzeug.exceptions import InternalServerError from werkzeug.exceptions import InternalServerError
from controllers.console.app.error import ( from controllers.console.app.error import (

@ -1,7 +1,7 @@
from functools import wraps from functools import wraps
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource # type: ignore from flask_restful import Resource
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from controllers.console.wraps import account_initialization_required from controllers.console.wraps import account_initialization_required

@ -1,5 +1,5 @@
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
from constants import HIDDEN_VALUE from constants import HIDDEN_VALUE
from controllers.console import api from controllers.console import api
@ -99,7 +99,7 @@ class APIBasedExtensionDetailAPI(Resource):
APIBasedExtensionService.delete(extension_data_from_db) APIBasedExtensionService.delete(extension_data_from_db)
return {"result": "success"} return {"result": "success"}, 204
api.add_resource(CodeBasedExtensionAPI, "/code-based-extension") api.add_resource(CodeBasedExtensionAPI, "/code-based-extension")

@ -1,5 +1,5 @@
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource # type: ignore from flask_restful import Resource
from libs.login import login_required from libs.login import login_required
from services.feature_service import FeatureService from services.feature_service import FeatureService

@ -1,8 +1,8 @@
from typing import Literal from typing import Literal
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal_with # type: ignore from flask_restful import Resource, marshal_with
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
import services import services

@ -1,7 +1,7 @@
import os import os
from flask import session from flask import session
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session

@ -1,4 +1,4 @@
from flask_restful import Resource # type: ignore from flask_restful import Resource
from controllers.console import api from controllers.console import api

@ -2,8 +2,8 @@ import urllib.parse
from typing import cast from typing import cast
import httpx import httpx
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
import services import services
from controllers.common import helpers from controllers.common import helpers

@ -1,5 +1,5 @@
from flask import request from flask import request
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from configs import dify_config from configs import dify_config
from libs.helper import StrLen, email, extract_remote_ip from libs.helper import StrLen, email, extract_remote_ip

@ -1,6 +1,6 @@
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from controllers.console import api from controllers.console import api
@ -86,7 +86,7 @@ class TagUpdateDeleteApi(Resource):
TagService.delete_tag(tag_id) TagService.delete_tag(tag_id)
return 200 return 204
class TagBindingCreateApi(Resource): class TagBindingCreateApi(Resource):

@ -2,7 +2,7 @@ import json
import logging import logging
import requests import requests
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from packaging import version from packaging import version
from configs import dify_config from configs import dify_config

@ -1,6 +1,6 @@
from functools import wraps from functools import wraps
from flask_login import current_user # type: ignore from flask_login import current_user
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden

@ -2,8 +2,8 @@ import datetime
import pytz import pytz
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore from flask_restful import Resource, fields, marshal_with, reqparse
from configs import dify_config from configs import dify_config
from constants.languages import supported_language from constants.languages import supported_language

@ -1,5 +1,5 @@
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource # type: ignore from flask_restful import Resource
from controllers.console import api from controllers.console import api
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required

@ -1,11 +1,11 @@
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
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from core.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.manager.exc import PluginPermissionDeniedError from core.plugin.impl.exc import PluginPermissionDeniedError
from libs.login import login_required from libs.login import login_required
from services.plugin.endpoint_service import EndpointService from services.plugin.endpoint_service import EndpointService

@ -1,4 +1,4 @@
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from controllers.console import api from controllers.console import api

@ -1,7 +1,7 @@
from urllib import parse from urllib import parse
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, abort, marshal_with, reqparse # type: ignore from flask_restful import Resource, abort, marshal_with, reqparse
import services import services
from configs import dify_config from configs import dify_config

@ -1,8 +1,8 @@
import io import io
from flask import send_file from flask import send_file
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from controllers.console import api from controllers.console import api

@ -1,7 +1,7 @@
import logging import logging
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from controllers.console import api from controllers.console import api

@ -1,8 +1,8 @@
import io import io
from flask import request, send_file from flask import request, send_file
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from configs import dify_config from configs import dify_config
@ -10,7 +10,7 @@ from controllers.console import api
from controllers.console.workspace import plugin_permission_required from controllers.console.workspace import plugin_permission_required
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from core.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from core.plugin.manager.exc import PluginDaemonClientSideError from core.plugin.impl.exc import PluginDaemonClientSideError
from libs.login import login_required from libs.login import login_required
from models.account import TenantPluginPermission from models.account import TenantPluginPermission
from services.plugin.plugin_permission_service import PluginPermissionService from services.plugin.plugin_permission_service import PluginPermissionService

@ -1,8 +1,8 @@
import io import io
from flask import send_file from flask import send_file
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden

@ -1,8 +1,8 @@
import logging import logging
from flask import request from flask import request
from flask_login import current_user # type: ignore from flask_login import current_user
from flask_restful import Resource, fields, inputs, marshal, marshal_with, reqparse # type: ignore from flask_restful import Resource, fields, inputs, marshal, marshal_with, reqparse
from werkzeug.exceptions import Unauthorized from werkzeug.exceptions import Unauthorized
import services import services

@ -4,7 +4,7 @@ import time
from functools import wraps from functools import wraps
from flask import abort, request from flask import abort, request
from flask_login import current_user # type: ignore from flask_login import current_user
from configs import dify_config from configs import dify_config
from controllers.console.workspace.error import AccountNotInitializedError from controllers.console.workspace.error import AccountNotInitializedError

@ -1,7 +1,7 @@
from urllib.parse import quote from urllib.parse import quote
from flask import Response, request from flask import Response, request
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
import services import services
@ -70,6 +70,20 @@ class FilePreviewApi(Resource):
direct_passthrough=True, direct_passthrough=True,
headers={}, headers={},
) )
# add Accept-Ranges header for audio/video files
if upload_file.mime_type in [
"audio/mpeg",
"audio/wav",
"audio/mp4",
"audio/ogg",
"audio/flac",
"audio/aac",
"video/mp4",
"video/webm",
"video/quicktime",
"audio/x-m4a",
]:
response.headers["Accept-Ranges"] = "bytes"
if upload_file.size > 0: if upload_file.size > 0:
response.headers["Content-Length"] = str(upload_file.size) response.headers["Content-Length"] = str(upload_file.size)
if args["as_attachment"]: if args["as_attachment"]:

@ -1,10 +1,14 @@
from urllib.parse import quote
from flask import Response from flask import Response
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import Forbidden, NotFound from werkzeug.exceptions import Forbidden, NotFound
from controllers.files import api from controllers.files import api
from controllers.files.error import UnsupportedFileTypeError from controllers.files.error import UnsupportedFileTypeError
from core.tools.signature import verify_tool_file_signature
from core.tools.tool_file_manager import ToolFileManager from core.tools.tool_file_manager import ToolFileManager
from models import db as global_db
class ToolFilePreviewApi(Resource): class ToolFilePreviewApi(Resource):
@ -19,17 +23,14 @@ class ToolFilePreviewApi(Resource):
parser.add_argument("as_attachment", type=bool, required=False, default=False, location="args") parser.add_argument("as_attachment", type=bool, required=False, default=False, location="args")
args = parser.parse_args() args = parser.parse_args()
if not verify_tool_file_signature(
if not ToolFileManager.verify_file( file_id=file_id, timestamp=args["timestamp"], nonce=args["nonce"], sign=args["sign"]
file_id=file_id,
timestamp=args["timestamp"],
nonce=args["nonce"],
sign=args["sign"],
): ):
raise Forbidden("Invalid request.") raise Forbidden("Invalid request.")
try: try:
stream, tool_file = ToolFileManager.get_file_generator_by_tool_file_id( tool_file_manager = ToolFileManager(engine=global_db.engine)
stream, tool_file = tool_file_manager.get_file_generator_by_tool_file_id(
file_id, file_id,
) )
@ -47,7 +48,8 @@ class ToolFilePreviewApi(Resource):
if tool_file.size > 0: if tool_file.size > 0:
response.headers["Content-Length"] = str(tool_file.size) response.headers["Content-Length"] = str(tool_file.size)
if args["as_attachment"]: if args["as_attachment"]:
response.headers["Content-Disposition"] = f"attachment; filename={tool_file.name}" encoded_filename = quote(tool_file.name)
response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}"
return response return response

@ -1,7 +1,7 @@
from mimetypes import guess_extension from mimetypes import guess_extension
from flask import request from flask import request
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
@ -53,7 +53,7 @@ class PluginUploadFileApi(Resource):
raise Forbidden("Invalid request.") raise Forbidden("Invalid request.")
try: try:
tool_file = ToolFileManager.create_file_by_raw( tool_file = ToolFileManager().create_file_by_raw(
user_id=user.id, user_id=user.id,
tenant_id=tenant_id, tenant_id=tenant_id,
file_binary=file.read(), file_binary=file.read(),

@ -1,4 +1,4 @@
from flask_restful import Resource # type: ignore from flask_restful import Resource
from controllers.console.wraps import setup_required from controllers.console.wraps import setup_required
from controllers.inner_api import api from controllers.inner_api import api

@ -3,7 +3,7 @@ from functools import wraps
from typing import Optional from typing import Optional
from flask import request from flask import request
from flask_restful import reqparse # type: ignore from flask_restful import reqparse
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy.orm import Session from sqlalchemy.orm import Session

@ -1,6 +1,6 @@
import json import json
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from controllers.console.wraps import setup_required from controllers.console.wraps import setup_required
from controllers.inner_api import api from controllers.inner_api import api

@ -18,7 +18,7 @@ def enterprise_inner_api_only(view):
# get header 'X-Inner-Api-Key' # get header 'X-Inner-Api-Key'
inner_api_key = request.headers.get("X-Inner-Api-Key") inner_api_key = request.headers.get("X-Inner-Api-Key")
if not inner_api_key or inner_api_key != dify_config.INNER_API_KEY_FOR_PLUGIN: if not inner_api_key or inner_api_key != dify_config.INNER_API_KEY:
abort(401) abort(401)
return view(*args, **kwargs) return view(*args, **kwargs)

@ -1,5 +1,5 @@
from flask import request from flask import request
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.service_api import api from controllers.service_api import api
@ -79,7 +79,7 @@ class AnnotationListApi(Resource):
class AnnotationUpdateDeleteApi(Resource): class AnnotationUpdateDeleteApi(Resource):
@validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON))
@marshal_with(annotation_fields) @marshal_with(annotation_fields)
def post(self, app_model: App, end_user: EndUser, annotation_id): def put(self, app_model: App, end_user: EndUser, annotation_id):
if not current_user.is_editor: if not current_user.is_editor:
raise Forbidden() raise Forbidden()
@ -98,7 +98,7 @@ class AnnotationUpdateDeleteApi(Resource):
annotation_id = str(annotation_id) annotation_id = str(annotation_id)
AppAnnotationService.delete_app_annotation(app_model.id, annotation_id) AppAnnotationService.delete_app_annotation(app_model.id, annotation_id)
return {"result": "success"}, 200 return {"result": "success"}, 204
api.add_resource(AnnotationReplyActionApi, "/apps/annotation-reply/<string:action>") api.add_resource(AnnotationReplyActionApi, "/apps/annotation-reply/<string:action>")

@ -1,4 +1,4 @@
from flask_restful import Resource, marshal_with # type: ignore from flask_restful import Resource, marshal_with
from controllers.common import fields from controllers.common import fields
from controllers.service_api import api from controllers.service_api import api
@ -47,7 +47,7 @@ class AppInfoApi(Resource):
def get(self, app_model: App): def get(self, app_model: App):
"""Get app information""" """Get app information"""
tags = [tag.name for tag in app_model.tags] tags = [tag.name for tag in app_model.tags]
return {"name": app_model.name, "description": app_model.description, "tags": tags} return {"name": app_model.name, "description": app_model.description, "tags": tags, "mode": app_model.mode}
api.add_resource(AppParameterApi, "/parameters") api.add_resource(AppParameterApi, "/parameters")

@ -1,7 +1,7 @@
import logging import logging
from flask import request from flask import request
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import InternalServerError from werkzeug.exceptions import InternalServerError
import services import services

@ -1,6 +1,6 @@
import logging import logging
from flask_restful import Resource, reqparse # type: ignore from flask_restful import Resource, reqparse
from werkzeug.exceptions import InternalServerError, NotFound from werkzeug.exceptions import InternalServerError, NotFound
import services import services

@ -1,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 sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
@ -72,7 +72,7 @@ class ConversationDetailApi(Resource):
ConversationService.delete(app_model, conversation_id, end_user) ConversationService.delete(app_model, conversation_id, end_user)
except services.errors.conversation.ConversationNotExistsError: except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.") raise NotFound("Conversation Not Exists.")
return {"result": "success"}, 200 return {"result": "success"}, 204
class ConversationRenameApi(Resource): class ConversationRenameApi(Resource):

@ -1,6 +1,6 @@
import httpx import httpx
from flask import request from flask import request
from flask_restful import Resource, marshal_with, reqparse # type: ignore from flask_restful import Resource, marshal_with, reqparse
import services import services
from controllers.common import helpers from controllers.common import helpers

@ -1,8 +1,8 @@
import json import json
import logging import logging
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 BadRequest, InternalServerError, NotFound from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
import services import services

@ -1,8 +1,8 @@
import logging import logging
from dateutil.parser import isoparse from dateutil.parser import isoparse
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 sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import InternalServerError from werkzeug.exceptions import InternalServerError

@ -1,5 +1,5 @@
from flask import request from flask import request
from flask_restful import marshal, reqparse # type: ignore from flask_restful import marshal, reqparse
from werkzeug.exceptions import Forbidden, NotFound from werkzeug.exceptions import Forbidden, NotFound
import services.dataset_service import services.dataset_service

@ -1,7 +1,7 @@
import json import json
from flask import request from flask import request
from flask_restful import marshal, reqparse # type: ignore from flask_restful import marshal, reqparse
from sqlalchemy import desc from sqlalchemy import desc
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
@ -323,7 +323,7 @@ class DocumentDeleteApi(DatasetApiResource):
except services.errors.document.DocumentIndexingError: except services.errors.document.DocumentIndexingError:
raise DocumentIndexingError("Cannot delete document during indexing.") raise DocumentIndexingError("Cannot delete document during indexing.")
return {"result": "success"}, 200 return {"result": "success"}, 204
class DocumentListApi(DatasetApiResource): class DocumentListApi(DatasetApiResource):

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

Loading…
Cancel
Save