Quellcode durchsuchen

feat(workflow): add configurable workflow file upload limit (#10176)

Co-authored-by: JzoNg <jzongcode@gmail.com>
-LAN- vor 5 Monaten
Ursprung
Commit
6452342222

+ 3 - 0
api/.env.example

@@ -327,6 +327,9 @@ SSRF_DEFAULT_MAX_RETRIES=3
 BATCH_UPLOAD_LIMIT=10
 BATCH_UPLOAD_LIMIT=10
 KEYWORD_DATA_SOURCE_TYPE=database
 KEYWORD_DATA_SOURCE_TYPE=database
 
 
+# Workflow file upload limit
+WORKFLOW_FILE_UPLOAD_LIMIT=10
+
 # CODE EXECUTION CONFIGURATION
 # CODE EXECUTION CONFIGURATION
 CODE_EXECUTION_ENDPOINT=http://127.0.0.1:8194
 CODE_EXECUTION_ENDPOINT=http://127.0.0.1:8194
 CODE_EXECUTION_API_KEY=dify-sandbox
 CODE_EXECUTION_API_KEY=dify-sandbox

+ 5 - 0
api/configs/feature/__init__.py

@@ -216,6 +216,11 @@ class FileUploadConfig(BaseSettings):
         default=20,
         default=20,
     )
     )
 
 
+    WORKFLOW_FILE_UPLOAD_LIMIT: PositiveInt = Field(
+        description="Maximum number of files allowed in a workflow upload operation",
+        default=10,
+    )
+
 
 
 class HttpConfig(BaseSettings):
 class HttpConfig(BaseSettings):
     """
     """

+ 24 - 0
api/controllers/common/fields.py

@@ -0,0 +1,24 @@
+from flask_restful import fields
+
+parameters__system_parameters = {
+    "image_file_size_limit": fields.Integer,
+    "video_file_size_limit": fields.Integer,
+    "audio_file_size_limit": fields.Integer,
+    "file_size_limit": fields.Integer,
+    "workflow_file_upload_limit": fields.Integer,
+}
+
+parameters_fields = {
+    "opening_statement": fields.String,
+    "suggested_questions": fields.Raw,
+    "suggested_questions_after_answer": fields.Raw,
+    "speech_to_text": fields.Raw,
+    "text_to_speech": fields.Raw,
+    "retriever_resource": fields.Raw,
+    "annotation_reply": fields.Raw,
+    "more_like_this": fields.Raw,
+    "user_input_form": fields.Raw,
+    "sensitive_word_avoidance": fields.Raw,
+    "file_upload": fields.Raw,
+    "system_parameters": fields.Nested(parameters__system_parameters),
+}

+ 39 - 0
api/controllers/common/helpers.py

@@ -2,11 +2,15 @@ import mimetypes
 import os
 import os
 import re
 import re
 import urllib.parse
 import urllib.parse
+from collections.abc import Mapping
+from typing import Any
 from uuid import uuid4
 from uuid import uuid4
 
 
 import httpx
 import httpx
 from pydantic import BaseModel
 from pydantic import BaseModel
 
 
+from configs import dify_config
+
 
 
 class FileInfo(BaseModel):
 class FileInfo(BaseModel):
     filename: str
     filename: str
@@ -56,3 +60,38 @@ def guess_file_info_from_response(response: httpx.Response):
         mimetype=mimetype,
         mimetype=mimetype,
         size=int(response.headers.get("Content-Length", -1)),
         size=int(response.headers.get("Content-Length", -1)),
     )
     )
+
+
+def get_parameters_from_feature_dict(*, features_dict: Mapping[str, Any], user_input_form: list[dict[str, Any]]):
+    return {
+        "opening_statement": features_dict.get("opening_statement"),
+        "suggested_questions": features_dict.get("suggested_questions", []),
+        "suggested_questions_after_answer": features_dict.get("suggested_questions_after_answer", {"enabled": False}),
+        "speech_to_text": features_dict.get("speech_to_text", {"enabled": False}),
+        "text_to_speech": features_dict.get("text_to_speech", {"enabled": False}),
+        "retriever_resource": features_dict.get("retriever_resource", {"enabled": False}),
+        "annotation_reply": features_dict.get("annotation_reply", {"enabled": False}),
+        "more_like_this": features_dict.get("more_like_this", {"enabled": False}),
+        "user_input_form": user_input_form,
+        "sensitive_word_avoidance": features_dict.get(
+            "sensitive_word_avoidance", {"enabled": False, "type": "", "configs": []}
+        ),
+        "file_upload": features_dict.get(
+            "file_upload",
+            {
+                "image": {
+                    "enabled": False,
+                    "number_limits": 3,
+                    "detail": "high",
+                    "transfer_methods": ["remote_url", "local_file"],
+                }
+            },
+        ),
+        "system_parameters": {
+            "image_file_size_limit": dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT,
+            "video_file_size_limit": dify_config.UPLOAD_VIDEO_FILE_SIZE_LIMIT,
+            "audio_file_size_limit": dify_config.UPLOAD_AUDIO_FILE_SIZE_LIMIT,
+            "file_size_limit": dify_config.UPLOAD_FILE_SIZE_LIMIT,
+            "workflow_file_upload_limit": dify_config.WORKFLOW_FILE_UPLOAD_LIMIT,
+        },
+    }

+ 13 - 68
api/controllers/console/explore/parameter.py

@@ -1,6 +1,7 @@
-from flask_restful import fields, marshal_with
+from flask_restful import marshal_with
 
 
-from configs import dify_config
+from controllers.common import fields
+from controllers.common import helpers as controller_helpers
 from controllers.console import api
 from controllers.console import api
 from controllers.console.app.error import AppUnavailableError
 from controllers.console.app.error import AppUnavailableError
 from controllers.console.explore.wraps import InstalledAppResource
 from controllers.console.explore.wraps import InstalledAppResource
@@ -11,43 +12,14 @@ from services.app_service import AppService
 class AppParameterApi(InstalledAppResource):
 class AppParameterApi(InstalledAppResource):
     """Resource for app variables."""
     """Resource for app variables."""
 
 
-    variable_fields = {
-        "key": fields.String,
-        "name": fields.String,
-        "description": fields.String,
-        "type": fields.String,
-        "default": fields.String,
-        "max_length": fields.Integer,
-        "options": fields.List(fields.String),
-    }
-
-    system_parameters_fields = {
-        "image_file_size_limit": fields.Integer,
-        "video_file_size_limit": fields.Integer,
-        "audio_file_size_limit": fields.Integer,
-        "file_size_limit": fields.Integer,
-    }
-
-    parameters_fields = {
-        "opening_statement": fields.String,
-        "suggested_questions": fields.Raw,
-        "suggested_questions_after_answer": fields.Raw,
-        "speech_to_text": fields.Raw,
-        "text_to_speech": fields.Raw,
-        "retriever_resource": fields.Raw,
-        "annotation_reply": fields.Raw,
-        "more_like_this": fields.Raw,
-        "user_input_form": fields.Raw,
-        "sensitive_word_avoidance": fields.Raw,
-        "file_upload": fields.Raw,
-        "system_parameters": fields.Nested(system_parameters_fields),
-    }
-
-    @marshal_with(parameters_fields)
+    @marshal_with(fields.parameters_fields)
     def get(self, installed_app: InstalledApp):
     def get(self, installed_app: InstalledApp):
         """Retrieve app parameters."""
         """Retrieve app parameters."""
         app_model = installed_app.app
         app_model = installed_app.app
 
 
+        if app_model is None:
+            raise AppUnavailableError()
+
         if app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
         if app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
             workflow = app_model.workflow
             workflow = app_model.workflow
             if workflow is None:
             if workflow is None:
@@ -57,43 +29,16 @@ class AppParameterApi(InstalledAppResource):
             user_input_form = workflow.user_input_form(to_old_structure=True)
             user_input_form = workflow.user_input_form(to_old_structure=True)
         else:
         else:
             app_model_config = app_model.app_model_config
             app_model_config = app_model.app_model_config
+            if app_model_config is None:
+                raise AppUnavailableError()
+
             features_dict = app_model_config.to_dict()
             features_dict = app_model_config.to_dict()
 
 
             user_input_form = features_dict.get("user_input_form", [])
             user_input_form = features_dict.get("user_input_form", [])
 
 
-        return {
-            "opening_statement": features_dict.get("opening_statement"),
-            "suggested_questions": features_dict.get("suggested_questions", []),
-            "suggested_questions_after_answer": features_dict.get(
-                "suggested_questions_after_answer", {"enabled": False}
-            ),
-            "speech_to_text": features_dict.get("speech_to_text", {"enabled": False}),
-            "text_to_speech": features_dict.get("text_to_speech", {"enabled": False}),
-            "retriever_resource": features_dict.get("retriever_resource", {"enabled": False}),
-            "annotation_reply": features_dict.get("annotation_reply", {"enabled": False}),
-            "more_like_this": features_dict.get("more_like_this", {"enabled": False}),
-            "user_input_form": user_input_form,
-            "sensitive_word_avoidance": features_dict.get(
-                "sensitive_word_avoidance", {"enabled": False, "type": "", "configs": []}
-            ),
-            "file_upload": features_dict.get(
-                "file_upload",
-                {
-                    "image": {
-                        "enabled": False,
-                        "number_limits": 3,
-                        "detail": "high",
-                        "transfer_methods": ["remote_url", "local_file"],
-                    }
-                },
-            ),
-            "system_parameters": {
-                "image_file_size_limit": dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT,
-                "video_file_size_limit": dify_config.UPLOAD_VIDEO_FILE_SIZE_LIMIT,
-                "audio_file_size_limit": dify_config.UPLOAD_AUDIO_FILE_SIZE_LIMIT,
-                "file_size_limit": dify_config.UPLOAD_FILE_SIZE_LIMIT,
-            },
-        }
+        return controller_helpers.get_parameters_from_feature_dict(
+            features_dict=features_dict, user_input_form=user_input_form
+        )
 
 
 
 
 class ExploreAppMetaApi(InstalledAppResource):
 class ExploreAppMetaApi(InstalledAppResource):

+ 1 - 0
api/controllers/console/files/__init__.py

@@ -37,6 +37,7 @@ class FileApi(Resource):
             "image_file_size_limit": dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT,
             "image_file_size_limit": dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT,
             "video_file_size_limit": dify_config.UPLOAD_VIDEO_FILE_SIZE_LIMIT,
             "video_file_size_limit": dify_config.UPLOAD_VIDEO_FILE_SIZE_LIMIT,
             "audio_file_size_limit": dify_config.UPLOAD_AUDIO_FILE_SIZE_LIMIT,
             "audio_file_size_limit": dify_config.UPLOAD_AUDIO_FILE_SIZE_LIMIT,
+            "workflow_file_upload_limit": dify_config.WORKFLOW_FILE_UPLOAD_LIMIT,
         }, 200
         }, 200
 
 
     @setup_required
     @setup_required

+ 10 - 68
api/controllers/service_api/app/app.py

@@ -1,6 +1,7 @@
-from flask_restful import Resource, fields, marshal_with
+from flask_restful import Resource, marshal_with
 
 
-from configs import dify_config
+from controllers.common import fields
+from controllers.common import helpers as controller_helpers
 from controllers.service_api import api
 from controllers.service_api import api
 from controllers.service_api.app.error import AppUnavailableError
 from controllers.service_api.app.error import AppUnavailableError
 from controllers.service_api.wraps import validate_app_token
 from controllers.service_api.wraps import validate_app_token
@@ -11,40 +12,8 @@ from services.app_service import AppService
 class AppParameterApi(Resource):
 class AppParameterApi(Resource):
     """Resource for app variables."""
     """Resource for app variables."""
 
 
-    variable_fields = {
-        "key": fields.String,
-        "name": fields.String,
-        "description": fields.String,
-        "type": fields.String,
-        "default": fields.String,
-        "max_length": fields.Integer,
-        "options": fields.List(fields.String),
-    }
-
-    system_parameters_fields = {
-        "image_file_size_limit": fields.Integer,
-        "video_file_size_limit": fields.Integer,
-        "audio_file_size_limit": fields.Integer,
-        "file_size_limit": fields.Integer,
-    }
-
-    parameters_fields = {
-        "opening_statement": fields.String,
-        "suggested_questions": fields.Raw,
-        "suggested_questions_after_answer": fields.Raw,
-        "speech_to_text": fields.Raw,
-        "text_to_speech": fields.Raw,
-        "retriever_resource": fields.Raw,
-        "annotation_reply": fields.Raw,
-        "more_like_this": fields.Raw,
-        "user_input_form": fields.Raw,
-        "sensitive_word_avoidance": fields.Raw,
-        "file_upload": fields.Raw,
-        "system_parameters": fields.Nested(system_parameters_fields),
-    }
-
     @validate_app_token
     @validate_app_token
-    @marshal_with(parameters_fields)
+    @marshal_with(fields.parameters_fields)
     def get(self, app_model: App):
     def get(self, app_model: App):
         """Retrieve app parameters."""
         """Retrieve app parameters."""
         if app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
         if app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
@@ -56,43 +25,16 @@ class AppParameterApi(Resource):
             user_input_form = workflow.user_input_form(to_old_structure=True)
             user_input_form = workflow.user_input_form(to_old_structure=True)
         else:
         else:
             app_model_config = app_model.app_model_config
             app_model_config = app_model.app_model_config
+            if app_model_config is None:
+                raise AppUnavailableError()
+
             features_dict = app_model_config.to_dict()
             features_dict = app_model_config.to_dict()
 
 
             user_input_form = features_dict.get("user_input_form", [])
             user_input_form = features_dict.get("user_input_form", [])
 
 
-        return {
-            "opening_statement": features_dict.get("opening_statement"),
-            "suggested_questions": features_dict.get("suggested_questions", []),
-            "suggested_questions_after_answer": features_dict.get(
-                "suggested_questions_after_answer", {"enabled": False}
-            ),
-            "speech_to_text": features_dict.get("speech_to_text", {"enabled": False}),
-            "text_to_speech": features_dict.get("text_to_speech", {"enabled": False}),
-            "retriever_resource": features_dict.get("retriever_resource", {"enabled": False}),
-            "annotation_reply": features_dict.get("annotation_reply", {"enabled": False}),
-            "more_like_this": features_dict.get("more_like_this", {"enabled": False}),
-            "user_input_form": user_input_form,
-            "sensitive_word_avoidance": features_dict.get(
-                "sensitive_word_avoidance", {"enabled": False, "type": "", "configs": []}
-            ),
-            "file_upload": features_dict.get(
-                "file_upload",
-                {
-                    "image": {
-                        "enabled": False,
-                        "number_limits": 3,
-                        "detail": "high",
-                        "transfer_methods": ["remote_url", "local_file"],
-                    }
-                },
-            ),
-            "system_parameters": {
-                "image_file_size_limit": dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT,
-                "video_file_size_limit": dify_config.UPLOAD_VIDEO_FILE_SIZE_LIMIT,
-                "audio_file_size_limit": dify_config.UPLOAD_AUDIO_FILE_SIZE_LIMIT,
-                "file_size_limit": dify_config.UPLOAD_FILE_SIZE_LIMIT,
-            },
-        }
+        return controller_helpers.get_parameters_from_feature_dict(
+            features_dict=features_dict, user_input_form=user_input_form
+        )
 
 
 
 
 class AppMetaApi(Resource):
 class AppMetaApi(Resource):

+ 10 - 68
api/controllers/web/app.py

@@ -1,6 +1,7 @@
-from flask_restful import fields, marshal_with
+from flask_restful import marshal_with
 
 
-from configs import dify_config
+from controllers.common import fields
+from controllers.common import helpers as controller_helpers
 from controllers.web import api
 from controllers.web import api
 from controllers.web.error import AppUnavailableError
 from controllers.web.error import AppUnavailableError
 from controllers.web.wraps import WebApiResource
 from controllers.web.wraps import WebApiResource
@@ -11,39 +12,7 @@ from services.app_service import AppService
 class AppParameterApi(WebApiResource):
 class AppParameterApi(WebApiResource):
     """Resource for app variables."""
     """Resource for app variables."""
 
 
-    variable_fields = {
-        "key": fields.String,
-        "name": fields.String,
-        "description": fields.String,
-        "type": fields.String,
-        "default": fields.String,
-        "max_length": fields.Integer,
-        "options": fields.List(fields.String),
-    }
-
-    system_parameters_fields = {
-        "image_file_size_limit": fields.Integer,
-        "video_file_size_limit": fields.Integer,
-        "audio_file_size_limit": fields.Integer,
-        "file_size_limit": fields.Integer,
-    }
-
-    parameters_fields = {
-        "opening_statement": fields.String,
-        "suggested_questions": fields.Raw,
-        "suggested_questions_after_answer": fields.Raw,
-        "speech_to_text": fields.Raw,
-        "text_to_speech": fields.Raw,
-        "retriever_resource": fields.Raw,
-        "annotation_reply": fields.Raw,
-        "more_like_this": fields.Raw,
-        "user_input_form": fields.Raw,
-        "sensitive_word_avoidance": fields.Raw,
-        "file_upload": fields.Raw,
-        "system_parameters": fields.Nested(system_parameters_fields),
-    }
-
-    @marshal_with(parameters_fields)
+    @marshal_with(fields.parameters_fields)
     def get(self, app_model: App, end_user):
     def get(self, app_model: App, end_user):
         """Retrieve app parameters."""
         """Retrieve app parameters."""
         if app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
         if app_model.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
@@ -55,43 +24,16 @@ class AppParameterApi(WebApiResource):
             user_input_form = workflow.user_input_form(to_old_structure=True)
             user_input_form = workflow.user_input_form(to_old_structure=True)
         else:
         else:
             app_model_config = app_model.app_model_config
             app_model_config = app_model.app_model_config
+            if app_model_config is None:
+                raise AppUnavailableError()
+
             features_dict = app_model_config.to_dict()
             features_dict = app_model_config.to_dict()
 
 
             user_input_form = features_dict.get("user_input_form", [])
             user_input_form = features_dict.get("user_input_form", [])
 
 
-        return {
-            "opening_statement": features_dict.get("opening_statement"),
-            "suggested_questions": features_dict.get("suggested_questions", []),
-            "suggested_questions_after_answer": features_dict.get(
-                "suggested_questions_after_answer", {"enabled": False}
-            ),
-            "speech_to_text": features_dict.get("speech_to_text", {"enabled": False}),
-            "text_to_speech": features_dict.get("text_to_speech", {"enabled": False}),
-            "retriever_resource": features_dict.get("retriever_resource", {"enabled": False}),
-            "annotation_reply": features_dict.get("annotation_reply", {"enabled": False}),
-            "more_like_this": features_dict.get("more_like_this", {"enabled": False}),
-            "user_input_form": user_input_form,
-            "sensitive_word_avoidance": features_dict.get(
-                "sensitive_word_avoidance", {"enabled": False, "type": "", "configs": []}
-            ),
-            "file_upload": features_dict.get(
-                "file_upload",
-                {
-                    "image": {
-                        "enabled": False,
-                        "number_limits": 3,
-                        "detail": "high",
-                        "transfer_methods": ["remote_url", "local_file"],
-                    }
-                },
-            ),
-            "system_parameters": {
-                "image_file_size_limit": dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT,
-                "video_file_size_limit": dify_config.UPLOAD_VIDEO_FILE_SIZE_LIMIT,
-                "audio_file_size_limit": dify_config.UPLOAD_AUDIO_FILE_SIZE_LIMIT,
-                "file_size_limit": dify_config.UPLOAD_FILE_SIZE_LIMIT,
-            },
-        }
+        return controller_helpers.get_parameters_from_feature_dict(
+            features_dict=features_dict, user_input_form=user_input_form
+        )
 
 
 
 
 class AppMeta(WebApiResource):
 class AppMeta(WebApiResource):

+ 2 - 3
api/core/app/app_config/features/file_upload/manager.py

@@ -1,8 +1,7 @@
 from collections.abc import Mapping
 from collections.abc import Mapping
 from typing import Any
 from typing import Any
 
 
-from core.file.models import FileExtraConfig
-from models import FileUploadConfig
+from core.file import FileExtraConfig
 
 
 
 
 class FileUploadConfigManager:
 class FileUploadConfigManager:
@@ -43,6 +42,6 @@ class FileUploadConfigManager:
         if not config.get("file_upload"):
         if not config.get("file_upload"):
             config["file_upload"] = {}
             config["file_upload"] = {}
         else:
         else:
-            FileUploadConfig.model_validate(config["file_upload"])
+            FileExtraConfig.model_validate(config["file_upload"])
 
 
         return config, ["file_upload"]
         return config, ["file_upload"]

+ 1 - 0
api/fields/file_fields.py

@@ -8,6 +8,7 @@ upload_config_fields = {
     "image_file_size_limit": fields.Integer,
     "image_file_size_limit": fields.Integer,
     "video_file_size_limit": fields.Integer,
     "video_file_size_limit": fields.Integer,
     "audio_file_size_limit": fields.Integer,
     "audio_file_size_limit": fields.Integer,
+    "workflow_file_upload_limit": fields.Integer,
 }
 }
 
 
 file_fields = {
 file_fields = {

+ 0 - 2
api/models/__init__.py

@@ -6,7 +6,6 @@ from .model import (
     AppMode,
     AppMode,
     Conversation,
     Conversation,
     EndUser,
     EndUser,
-    FileUploadConfig,
     InstalledApp,
     InstalledApp,
     Message,
     Message,
     MessageAnnotation,
     MessageAnnotation,
@@ -50,6 +49,5 @@ __all__ = [
     "Tenant",
     "Tenant",
     "Conversation",
     "Conversation",
     "MessageAnnotation",
     "MessageAnnotation",
-    "FileUploadConfig",
     "ToolFile",
     "ToolFile",
 ]
 ]

+ 2 - 11
api/models/model.py

@@ -1,7 +1,7 @@
 import json
 import json
 import re
 import re
 import uuid
 import uuid
-from collections.abc import Mapping, Sequence
+from collections.abc import Mapping
 from datetime import datetime
 from datetime import datetime
 from enum import Enum
 from enum import Enum
 from typing import Any, Literal, Optional
 from typing import Any, Literal, Optional
@@ -9,7 +9,6 @@ from typing import Any, Literal, Optional
 import sqlalchemy as sa
 import sqlalchemy as sa
 from flask import request
 from flask import request
 from flask_login import UserMixin
 from flask_login import UserMixin
-from pydantic import BaseModel, Field
 from sqlalchemy import Float, func, text
 from sqlalchemy import Float, func, text
 from sqlalchemy.orm import Mapped, mapped_column
 from sqlalchemy.orm import Mapped, mapped_column
 
 
@@ -25,14 +24,6 @@ from .account import Account, Tenant
 from .types import StringUUID
 from .types import StringUUID
 
 
 
 
-class FileUploadConfig(BaseModel):
-    enabled: bool = Field(default=False)
-    allowed_file_types: Sequence[FileType] = Field(default_factory=list)
-    allowed_extensions: Sequence[str] = Field(default_factory=list)
-    allowed_upload_methods: Sequence[FileTransferMethod] = Field(default_factory=list)
-    number_limits: int = Field(default=0, gt=0, le=10)
-
-
 class DifySetup(db.Model):
 class DifySetup(db.Model):
     __tablename__ = "dify_setups"
     __tablename__ = "dify_setups"
     __table_args__ = (db.PrimaryKeyConstraint("version", name="dify_setup_pkey"),)
     __table_args__ = (db.PrimaryKeyConstraint("version", name="dify_setup_pkey"),)
@@ -115,7 +106,7 @@ class App(db.Model):
         return site
         return site
 
 
     @property
     @property
-    def app_model_config(self) -> Optional["AppModelConfig"]:
+    def app_model_config(self):
         if self.app_model_config_id:
         if self.app_model_config_id:
             return db.session.query(AppModelConfig).filter(AppModelConfig.id == self.app_model_config_id).first()
             return db.session.query(AppModelConfig).filter(AppModelConfig.id == self.app_model_config_id).first()
 
 

+ 1 - 0
docker/.env.example

@@ -690,6 +690,7 @@ 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
 MAX_VARIABLE_SIZE=204800
 MAX_VARIABLE_SIZE=204800
+WORKFLOW_FILE_UPLOAD_LIMIT=10
 
 
 # HTTP request node in workflow configuration
 # HTTP request node in workflow configuration
 HTTP_REQUEST_NODE_MAX_BINARY_SIZE=10485760
 HTTP_REQUEST_NODE_MAX_BINARY_SIZE=10485760

+ 1 - 0
docker/docker-compose.yaml

@@ -1,4 +1,5 @@
 x-shared-env: &shared-api-worker-env
 x-shared-env: &shared-api-worker-env
+  WORKFLOW_FILE_UPLOAD_LIMIT: ${WORKFLOW_FILE_UPLOAD_LIMIT:-10}
   LOG_LEVEL: ${LOG_LEVEL:-INFO}
   LOG_LEVEL: ${LOG_LEVEL:-INFO}
   LOG_FILE: ${LOG_FILE:-}
   LOG_FILE: ${LOG_FILE:-}
   LOG_FILE_MAX_SIZE: ${LOG_FILE_MAX_SIZE:-20}
   LOG_FILE_MAX_SIZE: ${LOG_FILE_MAX_SIZE:-20}

+ 1 - 0
web/app/components/base/file-uploader/constants.ts

@@ -3,5 +3,6 @@ export const IMG_SIZE_LIMIT = 10 * 1024 * 1024
 export const FILE_SIZE_LIMIT = 15 * 1024 * 1024
 export const FILE_SIZE_LIMIT = 15 * 1024 * 1024
 export const AUDIO_SIZE_LIMIT = 50 * 1024 * 1024
 export const AUDIO_SIZE_LIMIT = 50 * 1024 * 1024
 export const VIDEO_SIZE_LIMIT = 100 * 1024 * 1024
 export const VIDEO_SIZE_LIMIT = 100 * 1024 * 1024
+export const MAX_FILE_UPLOAD_LIMIT = 10
 
 
 export const FILE_URL_REGEX = /^(https?|ftp):\/\//
 export const FILE_URL_REGEX = /^(https?|ftp):\/\//

+ 3 - 0
web/app/components/base/file-uploader/hooks.ts

@@ -18,6 +18,7 @@ import {
   AUDIO_SIZE_LIMIT,
   AUDIO_SIZE_LIMIT,
   FILE_SIZE_LIMIT,
   FILE_SIZE_LIMIT,
   IMG_SIZE_LIMIT,
   IMG_SIZE_LIMIT,
+  MAX_FILE_UPLOAD_LIMIT,
   VIDEO_SIZE_LIMIT,
   VIDEO_SIZE_LIMIT,
 } from '@/app/components/base/file-uploader/constants'
 } from '@/app/components/base/file-uploader/constants'
 import { useToastContext } from '@/app/components/base/toast'
 import { useToastContext } from '@/app/components/base/toast'
@@ -33,12 +34,14 @@ export const useFileSizeLimit = (fileUploadConfig?: FileUploadConfigResponse) =>
   const docSizeLimit = Number(fileUploadConfig?.file_size_limit) * 1024 * 1024 || FILE_SIZE_LIMIT
   const docSizeLimit = Number(fileUploadConfig?.file_size_limit) * 1024 * 1024 || FILE_SIZE_LIMIT
   const audioSizeLimit = Number(fileUploadConfig?.audio_file_size_limit) * 1024 * 1024 || AUDIO_SIZE_LIMIT
   const audioSizeLimit = Number(fileUploadConfig?.audio_file_size_limit) * 1024 * 1024 || AUDIO_SIZE_LIMIT
   const videoSizeLimit = Number(fileUploadConfig?.video_file_size_limit) * 1024 * 1024 || VIDEO_SIZE_LIMIT
   const videoSizeLimit = Number(fileUploadConfig?.video_file_size_limit) * 1024 * 1024 || VIDEO_SIZE_LIMIT
+  const maxFileUploadLimit = Number(fileUploadConfig?.workflow_file_upload_limit) || MAX_FILE_UPLOAD_LIMIT
 
 
   return {
   return {
     imgSizeLimit,
     imgSizeLimit,
     docSizeLimit,
     docSizeLimit,
     audioSizeLimit,
     audioSizeLimit,
     videoSizeLimit,
     videoSizeLimit,
+    maxFileUploadLimit,
   }
   }
 }
 }
 
 

+ 8 - 2
web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx

@@ -39,7 +39,13 @@ const FileUploadSetting: FC<Props> = ({
     allowed_file_extensions,
     allowed_file_extensions,
   } = payload
   } = payload
   const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
   const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
-  const { imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit } = useFileSizeLimit(fileUploadConfigResponse)
+  const {
+    imgSizeLimit,
+    docSizeLimit,
+    audioSizeLimit,
+    videoSizeLimit,
+    maxFileUploadLimit,
+  } = useFileSizeLimit(fileUploadConfigResponse)
 
 
   const handleSupportFileTypeChange = useCallback((type: SupportUploadFileTypes) => {
   const handleSupportFileTypeChange = useCallback((type: SupportUploadFileTypes) => {
     const newPayload = produce(payload, (draft) => {
     const newPayload = produce(payload, (draft) => {
@@ -156,7 +162,7 @@ const FileUploadSetting: FC<Props> = ({
             <InputNumberWithSlider
             <InputNumberWithSlider
               value={max_length}
               value={max_length}
               min={1}
               min={1}
-              max={10}
+              max={maxFileUploadLimit}
               onChange={handleMaxUploadNumLimitChange}
               onChange={handleMaxUploadNumLimitChange}
             />
             />
           </div>
           </div>

+ 1 - 1
web/models/common.ts

@@ -216,7 +216,7 @@ export type FileUploadConfigResponse = {
   file_size_limit: number // default is 15MB
   file_size_limit: number // default is 15MB
   audio_file_size_limit?: number // default is 50MB
   audio_file_size_limit?: number // default is 50MB
   video_file_size_limit?: number // default is 100MB
   video_file_size_limit?: number // default is 100MB
-
+  workflow_file_upload_limit?: number // default is 10
 }
 }
 
 
 export type InvitationResult = {
 export type InvitationResult = {