| @@ -4,13 +4,13 @@ from collections.abc import Sequence | |||
| from typing import cast | |||
| from flask import abort, request | |||
| from flask_restx import Resource, inputs, marshal_with, reqparse | |||
| from flask_restx import Resource, fields, inputs, marshal_with, reqparse | |||
| from sqlalchemy.orm import Session | |||
| from werkzeug.exceptions import Forbidden, InternalServerError, NotFound | |||
| import services | |||
| from configs import dify_config | |||
| from controllers.console import api | |||
| from controllers.console import api, console_ns | |||
| from controllers.console.app.error import ConversationCompletedError, DraftWorkflowNotExist, DraftWorkflowNotSync | |||
| from controllers.console.app.wraps import get_app_model | |||
| from controllers.console.wraps import account_initialization_required, setup_required | |||
| @@ -57,7 +57,13 @@ def _parse_file(workflow: Workflow, files: list[dict] | None = None) -> Sequence | |||
| return file_objs | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft") | |||
| class DraftWorkflowApi(Resource): | |||
| @api.doc("get_draft_workflow") | |||
| @api.doc(description="Get draft workflow for an application") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.response(200, "Draft workflow retrieved successfully", workflow_fields) | |||
| @api.response(404, "Draft workflow not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -86,6 +92,23 @@ class DraftWorkflowApi(Resource): | |||
| @login_required | |||
| @account_initialization_required | |||
| @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]) | |||
| @api.doc("sync_draft_workflow") | |||
| @api.doc(description="Sync draft workflow configuration") | |||
| @api.expect( | |||
| api.model( | |||
| "SyncDraftWorkflowRequest", | |||
| { | |||
| "graph": fields.Raw(required=True, description="Workflow graph configuration"), | |||
| "features": fields.Raw(required=True, description="Workflow features configuration"), | |||
| "hash": fields.String(description="Workflow hash for validation"), | |||
| "environment_variables": fields.List(fields.Raw, required=True, description="Environment variables"), | |||
| "conversation_variables": fields.List(fields.Raw, description="Conversation variables"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Draft workflow synced successfully", workflow_fields) | |||
| @api.response(400, "Invalid workflow configuration") | |||
| @api.response(403, "Permission denied") | |||
| def post(self, app_model: App): | |||
| """ | |||
| Sync draft workflow | |||
| @@ -159,7 +182,25 @@ class DraftWorkflowApi(Resource): | |||
| } | |||
| @console_ns.route("/apps/<uuid:app_id>/advanced-chat/workflows/draft/run") | |||
| class AdvancedChatDraftWorkflowRunApi(Resource): | |||
| @api.doc("run_advanced_chat_draft_workflow") | |||
| @api.doc(description="Run draft workflow for advanced chat application") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "AdvancedChatWorkflowRunRequest", | |||
| { | |||
| "query": fields.String(required=True, description="User query"), | |||
| "inputs": fields.Raw(description="Input variables"), | |||
| "files": fields.List(fields.Raw, description="File uploads"), | |||
| "conversation_id": fields.String(description="Conversation ID"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Workflow run started successfully") | |||
| @api.response(400, "Invalid request parameters") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -208,7 +249,23 @@ class AdvancedChatDraftWorkflowRunApi(Resource): | |||
| raise InternalServerError() | |||
| @console_ns.route("/apps/<uuid:app_id>/advanced-chat/workflows/draft/iteration/nodes/<string:node_id>/run") | |||
| class AdvancedChatDraftRunIterationNodeApi(Resource): | |||
| @api.doc("run_advanced_chat_draft_iteration_node") | |||
| @api.doc(description="Run draft workflow iteration node for advanced chat") | |||
| @api.doc(params={"app_id": "Application ID", "node_id": "Node ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "IterationNodeRunRequest", | |||
| { | |||
| "task_id": fields.String(required=True, description="Task ID"), | |||
| "inputs": fields.Raw(description="Input variables"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Iteration node run started successfully") | |||
| @api.response(403, "Permission denied") | |||
| @api.response(404, "Node not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -244,7 +301,23 @@ class AdvancedChatDraftRunIterationNodeApi(Resource): | |||
| raise InternalServerError() | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/iteration/nodes/<string:node_id>/run") | |||
| class WorkflowDraftRunIterationNodeApi(Resource): | |||
| @api.doc("run_workflow_draft_iteration_node") | |||
| @api.doc(description="Run draft workflow iteration node") | |||
| @api.doc(params={"app_id": "Application ID", "node_id": "Node ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "WorkflowIterationNodeRunRequest", | |||
| { | |||
| "task_id": fields.String(required=True, description="Task ID"), | |||
| "inputs": fields.Raw(description="Input variables"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Workflow iteration node run started successfully") | |||
| @api.response(403, "Permission denied") | |||
| @api.response(404, "Node not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -280,7 +353,23 @@ class WorkflowDraftRunIterationNodeApi(Resource): | |||
| raise InternalServerError() | |||
| @console_ns.route("/apps/<uuid:app_id>/advanced-chat/workflows/draft/loop/nodes/<string:node_id>/run") | |||
| class AdvancedChatDraftRunLoopNodeApi(Resource): | |||
| @api.doc("run_advanced_chat_draft_loop_node") | |||
| @api.doc(description="Run draft workflow loop node for advanced chat") | |||
| @api.doc(params={"app_id": "Application ID", "node_id": "Node ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "LoopNodeRunRequest", | |||
| { | |||
| "task_id": fields.String(required=True, description="Task ID"), | |||
| "inputs": fields.Raw(description="Input variables"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Loop node run started successfully") | |||
| @api.response(403, "Permission denied") | |||
| @api.response(404, "Node not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -317,7 +406,23 @@ class AdvancedChatDraftRunLoopNodeApi(Resource): | |||
| raise InternalServerError() | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/loop/nodes/<string:node_id>/run") | |||
| class WorkflowDraftRunLoopNodeApi(Resource): | |||
| @api.doc("run_workflow_draft_loop_node") | |||
| @api.doc(description="Run draft workflow loop node") | |||
| @api.doc(params={"app_id": "Application ID", "node_id": "Node ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "WorkflowLoopNodeRunRequest", | |||
| { | |||
| "task_id": fields.String(required=True, description="Task ID"), | |||
| "inputs": fields.Raw(description="Input variables"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Workflow loop node run started successfully") | |||
| @api.response(403, "Permission denied") | |||
| @api.response(404, "Node not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -354,7 +459,22 @@ class WorkflowDraftRunLoopNodeApi(Resource): | |||
| raise InternalServerError() | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/run") | |||
| class DraftWorkflowRunApi(Resource): | |||
| @api.doc("run_draft_workflow") | |||
| @api.doc(description="Run draft workflow") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "DraftWorkflowRunRequest", | |||
| { | |||
| "inputs": fields.Raw(required=True, description="Input variables"), | |||
| "files": fields.List(fields.Raw, description="File uploads"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Draft workflow run started successfully") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -393,7 +513,14 @@ class DraftWorkflowRunApi(Resource): | |||
| raise InvokeRateLimitHttpError(ex.description) | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/tasks/<string:task_id>/stop") | |||
| class WorkflowTaskStopApi(Resource): | |||
| @api.doc("stop_workflow_task") | |||
| @api.doc(description="Stop running workflow task") | |||
| @api.doc(params={"app_id": "Application ID", "task_id": "Task ID"}) | |||
| @api.response(200, "Task stopped successfully") | |||
| @api.response(404, "Task not found") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -414,7 +541,22 @@ class WorkflowTaskStopApi(Resource): | |||
| return {"result": "success"} | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/run") | |||
| class DraftWorkflowNodeRunApi(Resource): | |||
| @api.doc("run_draft_workflow_node") | |||
| @api.doc(description="Run draft workflow node") | |||
| @api.doc(params={"app_id": "Application ID", "node_id": "Node ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "DraftWorkflowNodeRunRequest", | |||
| { | |||
| "inputs": fields.Raw(description="Input variables"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Node run started successfully", workflow_run_node_execution_fields) | |||
| @api.response(403, "Permission denied") | |||
| @api.response(404, "Node not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -462,7 +604,13 @@ class DraftWorkflowNodeRunApi(Resource): | |||
| return workflow_node_execution | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/publish") | |||
| class PublishedWorkflowApi(Resource): | |||
| @api.doc("get_published_workflow") | |||
| @api.doc(description="Get published workflow for an application") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.response(200, "Published workflow retrieved successfully", workflow_fields) | |||
| @api.response(404, "Published workflow not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -534,7 +682,12 @@ class PublishedWorkflowApi(Resource): | |||
| } | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/default-block-configs") | |||
| class DefaultBlockConfigsApi(Resource): | |||
| @api.doc("get_default_block_configs") | |||
| @api.doc(description="Get default block configurations for workflow") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.response(200, "Default block configurations retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -555,7 +708,13 @@ class DefaultBlockConfigsApi(Resource): | |||
| return workflow_service.get_default_block_configs() | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/default-block-configs/<string:block_type>") | |||
| class DefaultBlockConfigApi(Resource): | |||
| @api.doc("get_default_block_config") | |||
| @api.doc(description="Get default block configuration by type") | |||
| @api.doc(params={"app_id": "Application ID", "block_type": "Block type"}) | |||
| @api.response(200, "Default block configuration retrieved successfully") | |||
| @api.response(404, "Block type not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -588,7 +747,14 @@ class DefaultBlockConfigApi(Resource): | |||
| return workflow_service.get_default_block_config(node_type=block_type, filters=filters) | |||
| @console_ns.route("/apps/<uuid:app_id>/convert-to-workflow") | |||
| class ConvertToWorkflowApi(Resource): | |||
| @api.doc("convert_to_workflow") | |||
| @api.doc(description="Convert application to workflow mode") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.response(200, "Application converted to workflow successfully") | |||
| @api.response(400, "Application cannot be converted") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -625,9 +791,14 @@ class ConvertToWorkflowApi(Resource): | |||
| } | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/config") | |||
| class WorkflowConfigApi(Resource): | |||
| """Resource for workflow configuration.""" | |||
| @api.doc("get_workflow_config") | |||
| @api.doc(description="Get workflow configuration") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.response(200, "Workflow configuration retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -638,7 +809,12 @@ class WorkflowConfigApi(Resource): | |||
| } | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/published") | |||
| class PublishedAllWorkflowApi(Resource): | |||
| @api.doc("get_all_published_workflows") | |||
| @api.doc(description="Get all published workflows for an application") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.response(200, "Published workflows retrieved successfully", workflow_pagination_fields) | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -689,7 +865,23 @@ class PublishedAllWorkflowApi(Resource): | |||
| } | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/<uuid:workflow_id>") | |||
| class WorkflowByIdApi(Resource): | |||
| @api.doc("update_workflow_by_id") | |||
| @api.doc(description="Update workflow by ID") | |||
| @api.doc(params={"app_id": "Application ID", "workflow_id": "Workflow ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "UpdateWorkflowRequest", | |||
| { | |||
| "environment_variables": fields.List(fields.Raw, description="Environment variables"), | |||
| "conversation_variables": fields.List(fields.Raw, description="Conversation variables"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Workflow updated successfully", workflow_fields) | |||
| @api.response(404, "Workflow not found") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -780,7 +972,14 @@ class WorkflowByIdApi(Resource): | |||
| return None, 204 | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/last-run") | |||
| class DraftWorkflowNodeLastRunApi(Resource): | |||
| @api.doc("get_draft_workflow_node_last_run") | |||
| @api.doc(description="Get last run result for draft workflow node") | |||
| @api.doc(params={"app_id": "Application ID", "node_id": "Node ID"}) | |||
| @api.response(200, "Node last run retrieved successfully", workflow_run_node_execution_fields) | |||
| @api.response(404, "Node last run not found") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -799,73 +998,3 @@ class DraftWorkflowNodeLastRunApi(Resource): | |||
| if node_exec is None: | |||
| raise NotFound("last run not found") | |||
| return node_exec | |||
| api.add_resource( | |||
| DraftWorkflowApi, | |||
| "/apps/<uuid:app_id>/workflows/draft", | |||
| ) | |||
| api.add_resource( | |||
| WorkflowConfigApi, | |||
| "/apps/<uuid:app_id>/workflows/draft/config", | |||
| ) | |||
| api.add_resource( | |||
| AdvancedChatDraftWorkflowRunApi, | |||
| "/apps/<uuid:app_id>/advanced-chat/workflows/draft/run", | |||
| ) | |||
| api.add_resource( | |||
| DraftWorkflowRunApi, | |||
| "/apps/<uuid:app_id>/workflows/draft/run", | |||
| ) | |||
| api.add_resource( | |||
| WorkflowTaskStopApi, | |||
| "/apps/<uuid:app_id>/workflow-runs/tasks/<string:task_id>/stop", | |||
| ) | |||
| api.add_resource( | |||
| DraftWorkflowNodeRunApi, | |||
| "/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/run", | |||
| ) | |||
| api.add_resource( | |||
| AdvancedChatDraftRunIterationNodeApi, | |||
| "/apps/<uuid:app_id>/advanced-chat/workflows/draft/iteration/nodes/<string:node_id>/run", | |||
| ) | |||
| api.add_resource( | |||
| WorkflowDraftRunIterationNodeApi, | |||
| "/apps/<uuid:app_id>/workflows/draft/iteration/nodes/<string:node_id>/run", | |||
| ) | |||
| api.add_resource( | |||
| AdvancedChatDraftRunLoopNodeApi, | |||
| "/apps/<uuid:app_id>/advanced-chat/workflows/draft/loop/nodes/<string:node_id>/run", | |||
| ) | |||
| api.add_resource( | |||
| WorkflowDraftRunLoopNodeApi, | |||
| "/apps/<uuid:app_id>/workflows/draft/loop/nodes/<string:node_id>/run", | |||
| ) | |||
| api.add_resource( | |||
| PublishedWorkflowApi, | |||
| "/apps/<uuid:app_id>/workflows/publish", | |||
| ) | |||
| api.add_resource( | |||
| PublishedAllWorkflowApi, | |||
| "/apps/<uuid:app_id>/workflows", | |||
| ) | |||
| api.add_resource( | |||
| DefaultBlockConfigsApi, | |||
| "/apps/<uuid:app_id>/workflows/default-workflow-block-configs", | |||
| ) | |||
| api.add_resource( | |||
| DefaultBlockConfigApi, | |||
| "/apps/<uuid:app_id>/workflows/default-workflow-block-configs/<string:block_type>", | |||
| ) | |||
| api.add_resource( | |||
| ConvertToWorkflowApi, | |||
| "/apps/<uuid:app_id>/convert-to-workflow", | |||
| ) | |||
| api.add_resource( | |||
| WorkflowByIdApi, | |||
| "/apps/<uuid:app_id>/workflows/<string:workflow_id>", | |||
| ) | |||
| api.add_resource( | |||
| DraftWorkflowNodeLastRunApi, | |||
| "/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/last-run", | |||
| ) | |||
| @@ -3,7 +3,7 @@ from flask_restx import Resource, marshal_with, reqparse | |||
| from flask_restx.inputs import int_range | |||
| from sqlalchemy.orm import Session | |||
| from controllers.console import api | |||
| from controllers.console import api, console_ns | |||
| from controllers.console.app.wraps import get_app_model | |||
| from controllers.console.wraps import account_initialization_required, setup_required | |||
| from core.workflow.entities.workflow_execution import WorkflowExecutionStatus | |||
| @@ -15,7 +15,24 @@ from models.model import AppMode | |||
| from services.workflow_app_service import WorkflowAppService | |||
| @console_ns.route("/apps/<uuid:app_id>/workflow-app-logs") | |||
| class WorkflowAppLogApi(Resource): | |||
| @api.doc("get_workflow_app_logs") | |||
| @api.doc(description="Get workflow application execution logs") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.doc( | |||
| params={ | |||
| "keyword": "Search keyword for filtering logs", | |||
| "status": "Filter by execution status (succeeded, failed, stopped, partial-succeeded)", | |||
| "created_at__before": "Filter logs created before this timestamp", | |||
| "created_at__after": "Filter logs created after this timestamp", | |||
| "created_by_end_user_session_id": "Filter by end user session ID", | |||
| "created_by_account": "Filter by account", | |||
| "page": "Page number (1-99999)", | |||
| "limit": "Number of items per page (1-100)", | |||
| } | |||
| ) | |||
| @api.response(200, "Workflow app logs retrieved successfully", workflow_app_log_pagination_fields) | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -78,6 +95,3 @@ class WorkflowAppLogApi(Resource): | |||
| ) | |||
| return workflow_app_log_pagination | |||
| api.add_resource(WorkflowAppLogApi, "/apps/<uuid:app_id>/workflow-app-logs") | |||
| @@ -6,7 +6,7 @@ from flask_restx import Resource, fields, inputs, marshal, marshal_with, reqpars | |||
| from sqlalchemy.orm import Session | |||
| from werkzeug.exceptions import Forbidden | |||
| from controllers.console import api | |||
| from controllers.console import api, console_ns | |||
| from controllers.console.app.error import ( | |||
| DraftWorkflowNotExist, | |||
| ) | |||
| @@ -144,7 +144,13 @@ def _api_prerequisite(f): | |||
| return wrapper | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/variables") | |||
| class WorkflowVariableCollectionApi(Resource): | |||
| @api.doc("get_workflow_variables") | |||
| @api.doc(description="Get draft workflow variables") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.doc(params={"page": "Page number (1-100000)", "limit": "Number of items per page (1-100)"}) | |||
| @api.response(200, "Workflow variables retrieved successfully", _WORKFLOW_DRAFT_VARIABLE_LIST_WITHOUT_VALUE_FIELDS) | |||
| @_api_prerequisite | |||
| @marshal_with(_WORKFLOW_DRAFT_VARIABLE_LIST_WITHOUT_VALUE_FIELDS) | |||
| def get(self, app_model: App): | |||
| @@ -173,6 +179,9 @@ class WorkflowVariableCollectionApi(Resource): | |||
| return workflow_vars | |||
| @api.doc("delete_workflow_variables") | |||
| @api.doc(description="Delete all draft workflow variables") | |||
| @api.response(204, "Workflow variables deleted successfully") | |||
| @_api_prerequisite | |||
| def delete(self, app_model: App): | |||
| draft_var_srv = WorkflowDraftVariableService( | |||
| @@ -201,7 +210,12 @@ def validate_node_id(node_id: str) -> NoReturn | None: | |||
| return None | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/variables") | |||
| class NodeVariableCollectionApi(Resource): | |||
| @api.doc("get_node_variables") | |||
| @api.doc(description="Get variables for a specific node") | |||
| @api.doc(params={"app_id": "Application ID", "node_id": "Node ID"}) | |||
| @api.response(200, "Node variables retrieved successfully", _WORKFLOW_DRAFT_VARIABLE_LIST_FIELDS) | |||
| @_api_prerequisite | |||
| @marshal_with(_WORKFLOW_DRAFT_VARIABLE_LIST_FIELDS) | |||
| def get(self, app_model: App, node_id: str): | |||
| @@ -214,6 +228,9 @@ class NodeVariableCollectionApi(Resource): | |||
| return node_vars | |||
| @api.doc("delete_node_variables") | |||
| @api.doc(description="Delete all variables for a specific node") | |||
| @api.response(204, "Node variables deleted successfully") | |||
| @_api_prerequisite | |||
| def delete(self, app_model: App, node_id: str): | |||
| validate_node_id(node_id) | |||
| @@ -223,10 +240,16 @@ class NodeVariableCollectionApi(Resource): | |||
| return Response("", 204) | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/variables/<uuid:variable_id>") | |||
| class VariableApi(Resource): | |||
| _PATCH_NAME_FIELD = "name" | |||
| _PATCH_VALUE_FIELD = "value" | |||
| @api.doc("get_variable") | |||
| @api.doc(description="Get a specific workflow variable") | |||
| @api.doc(params={"app_id": "Application ID", "variable_id": "Variable ID"}) | |||
| @api.response(200, "Variable retrieved successfully", _WORKFLOW_DRAFT_VARIABLE_FIELDS) | |||
| @api.response(404, "Variable not found") | |||
| @_api_prerequisite | |||
| @marshal_with(_WORKFLOW_DRAFT_VARIABLE_FIELDS) | |||
| def get(self, app_model: App, variable_id: str): | |||
| @@ -240,6 +263,19 @@ class VariableApi(Resource): | |||
| raise NotFoundError(description=f"variable not found, id={variable_id}") | |||
| return variable | |||
| @api.doc("update_variable") | |||
| @api.doc(description="Update a workflow variable") | |||
| @api.expect( | |||
| api.model( | |||
| "UpdateVariableRequest", | |||
| { | |||
| "name": fields.String(description="Variable name"), | |||
| "value": fields.Raw(description="Variable value"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Variable updated successfully", _WORKFLOW_DRAFT_VARIABLE_FIELDS) | |||
| @api.response(404, "Variable not found") | |||
| @_api_prerequisite | |||
| @marshal_with(_WORKFLOW_DRAFT_VARIABLE_FIELDS) | |||
| def patch(self, app_model: App, variable_id: str): | |||
| @@ -302,6 +338,10 @@ class VariableApi(Resource): | |||
| db.session.commit() | |||
| return variable | |||
| @api.doc("delete_variable") | |||
| @api.doc(description="Delete a workflow variable") | |||
| @api.response(204, "Variable deleted successfully") | |||
| @api.response(404, "Variable not found") | |||
| @_api_prerequisite | |||
| def delete(self, app_model: App, variable_id: str): | |||
| draft_var_srv = WorkflowDraftVariableService( | |||
| @@ -317,7 +357,14 @@ class VariableApi(Resource): | |||
| return Response("", 204) | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/variables/<uuid:variable_id>/reset") | |||
| class VariableResetApi(Resource): | |||
| @api.doc("reset_variable") | |||
| @api.doc(description="Reset a workflow variable to its default value") | |||
| @api.doc(params={"app_id": "Application ID", "variable_id": "Variable ID"}) | |||
| @api.response(200, "Variable reset successfully", _WORKFLOW_DRAFT_VARIABLE_FIELDS) | |||
| @api.response(204, "Variable reset (no content)") | |||
| @api.response(404, "Variable not found") | |||
| @_api_prerequisite | |||
| def put(self, app_model: App, variable_id: str): | |||
| draft_var_srv = WorkflowDraftVariableService( | |||
| @@ -358,7 +405,13 @@ def _get_variable_list(app_model: App, node_id) -> WorkflowDraftVariableList: | |||
| return draft_vars | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/conversation-variables") | |||
| class ConversationVariableCollectionApi(Resource): | |||
| @api.doc("get_conversation_variables") | |||
| @api.doc(description="Get conversation variables for workflow") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.response(200, "Conversation variables retrieved successfully", _WORKFLOW_DRAFT_VARIABLE_LIST_FIELDS) | |||
| @api.response(404, "Draft workflow not found") | |||
| @_api_prerequisite | |||
| @marshal_with(_WORKFLOW_DRAFT_VARIABLE_LIST_FIELDS) | |||
| def get(self, app_model: App): | |||
| @@ -374,14 +427,25 @@ class ConversationVariableCollectionApi(Resource): | |||
| return _get_variable_list(app_model, CONVERSATION_VARIABLE_NODE_ID) | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/system-variables") | |||
| class SystemVariableCollectionApi(Resource): | |||
| @api.doc("get_system_variables") | |||
| @api.doc(description="Get system variables for workflow") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.response(200, "System variables retrieved successfully", _WORKFLOW_DRAFT_VARIABLE_LIST_FIELDS) | |||
| @_api_prerequisite | |||
| @marshal_with(_WORKFLOW_DRAFT_VARIABLE_LIST_FIELDS) | |||
| def get(self, app_model: App): | |||
| return _get_variable_list(app_model, SYSTEM_VARIABLE_NODE_ID) | |||
| @console_ns.route("/apps/<uuid:app_id>/workflows/draft/environment-variables") | |||
| class EnvironmentVariableCollectionApi(Resource): | |||
| @api.doc("get_environment_variables") | |||
| @api.doc(description="Get environment variables for workflow") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.response(200, "Environment variables retrieved successfully") | |||
| @api.response(404, "Draft workflow not found") | |||
| @_api_prerequisite | |||
| def get(self, app_model: App): | |||
| """ | |||
| @@ -413,16 +477,3 @@ class EnvironmentVariableCollectionApi(Resource): | |||
| ) | |||
| return {"items": env_vars_list} | |||
| api.add_resource( | |||
| WorkflowVariableCollectionApi, | |||
| "/apps/<uuid:app_id>/workflows/draft/variables", | |||
| ) | |||
| api.add_resource(NodeVariableCollectionApi, "/apps/<uuid:app_id>/workflows/draft/nodes/<string:node_id>/variables") | |||
| api.add_resource(VariableApi, "/apps/<uuid:app_id>/workflows/draft/variables/<uuid:variable_id>") | |||
| api.add_resource(VariableResetApi, "/apps/<uuid:app_id>/workflows/draft/variables/<uuid:variable_id>/reset") | |||
| api.add_resource(ConversationVariableCollectionApi, "/apps/<uuid:app_id>/workflows/draft/conversation-variables") | |||
| api.add_resource(SystemVariableCollectionApi, "/apps/<uuid:app_id>/workflows/draft/system-variables") | |||
| api.add_resource(EnvironmentVariableCollectionApi, "/apps/<uuid:app_id>/workflows/draft/environment-variables") | |||
| @@ -4,7 +4,7 @@ from flask_login import current_user | |||
| from flask_restx import Resource, marshal_with, reqparse | |||
| from flask_restx.inputs import int_range | |||
| from controllers.console import api | |||
| from controllers.console import api, console_ns | |||
| from controllers.console.app.wraps import get_app_model | |||
| from controllers.console.wraps import account_initialization_required, setup_required | |||
| from fields.workflow_run_fields import ( | |||
| @@ -19,7 +19,13 @@ from models import Account, App, AppMode, EndUser | |||
| from services.workflow_run_service import WorkflowRunService | |||
| @console_ns.route("/apps/<uuid:app_id>/advanced-chat/workflow-runs") | |||
| class AdvancedChatAppWorkflowRunListApi(Resource): | |||
| @api.doc("get_advanced_chat_workflow_runs") | |||
| @api.doc(description="Get advanced chat workflow run list") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.doc(params={"last_id": "Last run ID for pagination", "limit": "Number of items per page (1-100)"}) | |||
| @api.response(200, "Workflow runs retrieved successfully", advanced_chat_workflow_run_pagination_fields) | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -40,7 +46,13 @@ class AdvancedChatAppWorkflowRunListApi(Resource): | |||
| return result | |||
| @console_ns.route("/apps/<uuid:app_id>/workflow-runs") | |||
| class WorkflowRunListApi(Resource): | |||
| @api.doc("get_workflow_runs") | |||
| @api.doc(description="Get workflow run list") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.doc(params={"last_id": "Last run ID for pagination", "limit": "Number of items per page (1-100)"}) | |||
| @api.response(200, "Workflow runs retrieved successfully", workflow_run_pagination_fields) | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -61,7 +73,13 @@ class WorkflowRunListApi(Resource): | |||
| return result | |||
| @console_ns.route("/apps/<uuid:app_id>/workflow-runs/<uuid:run_id>") | |||
| class WorkflowRunDetailApi(Resource): | |||
| @api.doc("get_workflow_run_detail") | |||
| @api.doc(description="Get workflow run detail") | |||
| @api.doc(params={"app_id": "Application ID", "run_id": "Workflow run ID"}) | |||
| @api.response(200, "Workflow run detail retrieved successfully", workflow_run_detail_fields) | |||
| @api.response(404, "Workflow run not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -79,7 +97,13 @@ class WorkflowRunDetailApi(Resource): | |||
| return workflow_run | |||
| @console_ns.route("/apps/<uuid:app_id>/workflow-runs/<uuid:run_id>/node-executions") | |||
| class WorkflowRunNodeExecutionListApi(Resource): | |||
| @api.doc("get_workflow_run_node_executions") | |||
| @api.doc(description="Get workflow run node execution list") | |||
| @api.doc(params={"app_id": "Application ID", "run_id": "Workflow run ID"}) | |||
| @api.response(200, "Node executions retrieved successfully", workflow_run_node_execution_list_fields) | |||
| @api.response(404, "Workflow run not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -100,9 +124,3 @@ class WorkflowRunNodeExecutionListApi(Resource): | |||
| ) | |||
| return {"data": node_executions} | |||
| api.add_resource(AdvancedChatAppWorkflowRunListApi, "/apps/<uuid:app_id>/advanced-chat/workflow-runs") | |||
| api.add_resource(WorkflowRunListApi, "/apps/<uuid:app_id>/workflow-runs") | |||
| api.add_resource(WorkflowRunDetailApi, "/apps/<uuid:app_id>/workflow-runs/<uuid:run_id>") | |||
| api.add_resource(WorkflowRunNodeExecutionListApi, "/apps/<uuid:app_id>/workflow-runs/<uuid:run_id>/node-executions") | |||
| @@ -7,7 +7,7 @@ from flask import jsonify | |||
| from flask_login import current_user | |||
| from flask_restx import Resource, reqparse | |||
| from controllers.console import api | |||
| from controllers.console import api, console_ns | |||
| from controllers.console.app.wraps import get_app_model | |||
| from controllers.console.wraps import account_initialization_required, setup_required | |||
| from extensions.ext_database import db | |||
| @@ -17,7 +17,13 @@ from models.enums import WorkflowRunTriggeredFrom | |||
| from models.model import AppMode | |||
| @console_ns.route("/apps/<uuid:app_id>/workflow/statistics/daily-conversations") | |||
| class WorkflowDailyRunsStatistic(Resource): | |||
| @api.doc("get_workflow_daily_runs_statistic") | |||
| @api.doc(description="Get workflow daily runs statistics") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.doc(params={"start": "Start date and time (YYYY-MM-DD HH:MM)", "end": "End date and time (YYYY-MM-DD HH:MM)"}) | |||
| @api.response(200, "Daily runs statistics retrieved successfully") | |||
| @get_app_model | |||
| @setup_required | |||
| @login_required | |||
| @@ -79,7 +85,13 @@ WHERE | |||
| return jsonify({"data": response_data}) | |||
| @console_ns.route("/apps/<uuid:app_id>/workflow/statistics/daily-terminals") | |||
| class WorkflowDailyTerminalsStatistic(Resource): | |||
| @api.doc("get_workflow_daily_terminals_statistic") | |||
| @api.doc(description="Get workflow daily terminals statistics") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.doc(params={"start": "Start date and time (YYYY-MM-DD HH:MM)", "end": "End date and time (YYYY-MM-DD HH:MM)"}) | |||
| @api.response(200, "Daily terminals statistics retrieved successfully") | |||
| @get_app_model | |||
| @setup_required | |||
| @login_required | |||
| @@ -141,7 +153,13 @@ WHERE | |||
| return jsonify({"data": response_data}) | |||
| @console_ns.route("/apps/<uuid:app_id>/workflow/statistics/token-costs") | |||
| class WorkflowDailyTokenCostStatistic(Resource): | |||
| @api.doc("get_workflow_daily_token_cost_statistic") | |||
| @api.doc(description="Get workflow daily token cost statistics") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.doc(params={"start": "Start date and time (YYYY-MM-DD HH:MM)", "end": "End date and time (YYYY-MM-DD HH:MM)"}) | |||
| @api.response(200, "Daily token cost statistics retrieved successfully") | |||
| @get_app_model | |||
| @setup_required | |||
| @login_required | |||
| @@ -208,7 +226,13 @@ WHERE | |||
| return jsonify({"data": response_data}) | |||
| @console_ns.route("/apps/<uuid:app_id>/workflow/statistics/average-app-interactions") | |||
| class WorkflowAverageAppInteractionStatistic(Resource): | |||
| @api.doc("get_workflow_average_app_interaction_statistic") | |||
| @api.doc(description="Get workflow average app interaction statistics") | |||
| @api.doc(params={"app_id": "Application ID"}) | |||
| @api.doc(params={"start": "Start date and time (YYYY-MM-DD HH:MM)", "end": "End date and time (YYYY-MM-DD HH:MM)"}) | |||
| @api.response(200, "Average app interaction statistics retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -285,11 +309,3 @@ GROUP BY | |||
| ) | |||
| return jsonify({"data": response_data}) | |||
| api.add_resource(WorkflowDailyRunsStatistic, "/apps/<uuid:app_id>/workflow/statistics/daily-conversations") | |||
| api.add_resource(WorkflowDailyTerminalsStatistic, "/apps/<uuid:app_id>/workflow/statistics/daily-terminals") | |||
| api.add_resource(WorkflowDailyTokenCostStatistic, "/apps/<uuid:app_id>/workflow/statistics/token-costs") | |||
| api.add_resource( | |||
| WorkflowAverageAppInteractionStatistic, "/apps/<uuid:app_id>/workflow/statistics/average-app-interactions" | |||
| ) | |||
| @@ -1,13 +1,13 @@ | |||
| import flask_restx | |||
| from flask import request | |||
| from flask_login import current_user | |||
| from flask_restx import Resource, marshal, marshal_with, reqparse | |||
| from flask_restx import Resource, fields, marshal, marshal_with, reqparse | |||
| from sqlalchemy import select | |||
| from werkzeug.exceptions import Forbidden, NotFound | |||
| import services | |||
| from configs import dify_config | |||
| from controllers.console import api | |||
| from controllers.console import api, console_ns | |||
| from controllers.console.apikey import api_key_fields, api_key_list | |||
| from controllers.console.app.error import ProviderNotInitializeError | |||
| from controllers.console.datasets.error import DatasetInUseError, DatasetNameDuplicateError, IndexingEstimateError | |||
| @@ -48,7 +48,21 @@ def _validate_description_length(description): | |||
| return description | |||
| @console_ns.route("/datasets") | |||
| class DatasetListApi(Resource): | |||
| @api.doc("get_datasets") | |||
| @api.doc(description="Get list of datasets") | |||
| @api.doc( | |||
| params={ | |||
| "page": "Page number (default: 1)", | |||
| "limit": "Number of items per page (default: 20)", | |||
| "ids": "Filter by dataset IDs (list)", | |||
| "keyword": "Search keyword", | |||
| "tag_ids": "Filter by tag IDs (list)", | |||
| "include_all": "Include all datasets (default: false)", | |||
| } | |||
| ) | |||
| @api.response(200, "Datasets retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -100,6 +114,24 @@ class DatasetListApi(Resource): | |||
| response = {"data": data, "has_more": len(datasets) == limit, "limit": limit, "total": total, "page": page} | |||
| return response, 200 | |||
| @api.doc("create_dataset") | |||
| @api.doc(description="Create a new dataset") | |||
| @api.expect( | |||
| api.model( | |||
| "CreateDatasetRequest", | |||
| { | |||
| "name": fields.String(required=True, description="Dataset name (1-40 characters)"), | |||
| "description": fields.String(description="Dataset description (max 400 characters)"), | |||
| "indexing_technique": fields.String(description="Indexing technique"), | |||
| "permission": fields.String(description="Dataset permission"), | |||
| "provider": fields.String(description="Provider"), | |||
| "external_knowledge_api_id": fields.String(description="External knowledge API ID"), | |||
| "external_knowledge_id": fields.String(description="External knowledge ID"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(201, "Dataset created successfully") | |||
| @api.response(400, "Invalid request parameters") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -172,7 +204,14 @@ class DatasetListApi(Resource): | |||
| return marshal(dataset, dataset_detail_fields), 201 | |||
| @console_ns.route("/datasets/<uuid:dataset_id>") | |||
| class DatasetApi(Resource): | |||
| @api.doc("get_dataset") | |||
| @api.doc(description="Get dataset details") | |||
| @api.doc(params={"dataset_id": "Dataset ID"}) | |||
| @api.response(200, "Dataset retrieved successfully", dataset_detail_fields) | |||
| @api.response(404, "Dataset not found") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -215,6 +254,23 @@ class DatasetApi(Resource): | |||
| return data, 200 | |||
| @api.doc("update_dataset") | |||
| @api.doc(description="Update dataset details") | |||
| @api.expect( | |||
| api.model( | |||
| "UpdateDatasetRequest", | |||
| { | |||
| "name": fields.String(description="Dataset name"), | |||
| "description": fields.String(description="Dataset description"), | |||
| "permission": fields.String(description="Dataset permission"), | |||
| "indexing_technique": fields.String(description="Indexing technique"), | |||
| "external_retrieval_model": fields.Raw(description="External retrieval model settings"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Dataset updated successfully", dataset_detail_fields) | |||
| @api.response(404, "Dataset not found") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -344,7 +400,12 @@ class DatasetApi(Resource): | |||
| raise DatasetInUseError() | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/use-check") | |||
| class DatasetUseCheckApi(Resource): | |||
| @api.doc("check_dataset_use") | |||
| @api.doc(description="Check if dataset is in use") | |||
| @api.doc(params={"dataset_id": "Dataset ID"}) | |||
| @api.response(200, "Dataset use status retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -355,7 +416,12 @@ class DatasetUseCheckApi(Resource): | |||
| return {"is_using": dataset_is_using}, 200 | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/queries") | |||
| class DatasetQueryApi(Resource): | |||
| @api.doc("get_dataset_queries") | |||
| @api.doc(description="Get dataset query history") | |||
| @api.doc(params={"dataset_id": "Dataset ID"}) | |||
| @api.response(200, "Query history retrieved successfully", dataset_query_detail_fields) | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -385,7 +451,11 @@ class DatasetQueryApi(Resource): | |||
| return response, 200 | |||
| @console_ns.route("/datasets/indexing-estimate") | |||
| class DatasetIndexingEstimateApi(Resource): | |||
| @api.doc("estimate_dataset_indexing") | |||
| @api.doc(description="Estimate dataset indexing cost") | |||
| @api.response(200, "Indexing estimate calculated successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -486,7 +556,12 @@ class DatasetIndexingEstimateApi(Resource): | |||
| return response.model_dump(), 200 | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/related-apps") | |||
| class DatasetRelatedAppListApi(Resource): | |||
| @api.doc("get_dataset_related_apps") | |||
| @api.doc(description="Get applications related to dataset") | |||
| @api.doc(params={"dataset_id": "Dataset ID"}) | |||
| @api.response(200, "Related apps retrieved successfully", related_app_list) | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -513,7 +588,12 @@ class DatasetRelatedAppListApi(Resource): | |||
| return {"data": related_apps, "total": len(related_apps)}, 200 | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/indexing-status") | |||
| class DatasetIndexingStatusApi(Resource): | |||
| @api.doc("get_dataset_indexing_status") | |||
| @api.doc(description="Get dataset indexing status") | |||
| @api.doc(params={"dataset_id": "Dataset ID"}) | |||
| @api.response(200, "Indexing status retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -560,11 +640,15 @@ class DatasetIndexingStatusApi(Resource): | |||
| return data, 200 | |||
| @console_ns.route("/datasets/api-keys") | |||
| class DatasetApiKeyApi(Resource): | |||
| max_keys = 10 | |||
| token_prefix = "dataset-" | |||
| resource_type = "dataset" | |||
| @api.doc("get_dataset_api_keys") | |||
| @api.doc(description="Get dataset API keys") | |||
| @api.response(200, "API keys retrieved successfully", api_key_list) | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -609,9 +693,14 @@ class DatasetApiKeyApi(Resource): | |||
| return api_token, 200 | |||
| @console_ns.route("/datasets/api-keys/<uuid:api_key_id>") | |||
| class DatasetApiDeleteApi(Resource): | |||
| resource_type = "dataset" | |||
| @api.doc("delete_dataset_api_key") | |||
| @api.doc(description="Delete dataset API key") | |||
| @api.doc(params={"api_key_id": "API key ID"}) | |||
| @api.response(204, "API key deleted successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -641,7 +730,11 @@ class DatasetApiDeleteApi(Resource): | |||
| return {"result": "success"}, 204 | |||
| @console_ns.route("/datasets/api-base-info") | |||
| class DatasetApiBaseUrlApi(Resource): | |||
| @api.doc("get_dataset_api_base_info") | |||
| @api.doc(description="Get dataset API base information") | |||
| @api.response(200, "API base info retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -649,7 +742,11 @@ class DatasetApiBaseUrlApi(Resource): | |||
| return {"api_base_url": (dify_config.SERVICE_API_URL or request.host_url.rstrip("/")) + "/v1"} | |||
| @console_ns.route("/datasets/retrieval-setting") | |||
| class DatasetRetrievalSettingApi(Resource): | |||
| @api.doc("get_dataset_retrieval_setting") | |||
| @api.doc(description="Get dataset retrieval settings") | |||
| @api.response(200, "Retrieval settings retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -700,7 +797,12 @@ class DatasetRetrievalSettingApi(Resource): | |||
| raise ValueError(f"Unsupported vector db type {vector_type}.") | |||
| @console_ns.route("/datasets/retrieval-setting/<string:vector_type>") | |||
| class DatasetRetrievalSettingMockApi(Resource): | |||
| @api.doc("get_dataset_retrieval_setting_mock") | |||
| @api.doc(description="Get mock dataset retrieval settings by vector type") | |||
| @api.doc(params={"vector_type": "Vector store type"}) | |||
| @api.response(200, "Mock retrieval settings retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -749,7 +851,13 @@ class DatasetRetrievalSettingMockApi(Resource): | |||
| raise ValueError(f"Unsupported vector db type {vector_type}.") | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/error-docs") | |||
| class DatasetErrorDocs(Resource): | |||
| @api.doc("get_dataset_error_docs") | |||
| @api.doc(description="Get dataset error documents") | |||
| @api.doc(params={"dataset_id": "Dataset ID"}) | |||
| @api.response(200, "Error documents retrieved successfully") | |||
| @api.response(404, "Dataset not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -763,7 +871,14 @@ class DatasetErrorDocs(Resource): | |||
| return {"data": [marshal(item, document_status_fields) for item in results], "total": len(results)}, 200 | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/permission-part-users") | |||
| class DatasetPermissionUserListApi(Resource): | |||
| @api.doc("get_dataset_permission_users") | |||
| @api.doc(description="Get dataset permission user list") | |||
| @api.doc(params={"dataset_id": "Dataset ID"}) | |||
| @api.response(200, "Permission users retrieved successfully") | |||
| @api.response(404, "Dataset not found") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -784,7 +899,13 @@ class DatasetPermissionUserListApi(Resource): | |||
| }, 200 | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/auto-disable-logs") | |||
| class DatasetAutoDisableLogApi(Resource): | |||
| @api.doc("get_dataset_auto_disable_logs") | |||
| @api.doc(description="Get dataset auto disable logs") | |||
| @api.doc(params={"dataset_id": "Dataset ID"}) | |||
| @api.response(200, "Auto disable logs retrieved successfully") | |||
| @api.response(404, "Dataset not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -794,20 +915,3 @@ class DatasetAutoDisableLogApi(Resource): | |||
| if dataset is None: | |||
| raise NotFound("Dataset not found.") | |||
| return DatasetService.get_dataset_auto_disable_logs(dataset_id_str), 200 | |||
| api.add_resource(DatasetListApi, "/datasets") | |||
| api.add_resource(DatasetApi, "/datasets/<uuid:dataset_id>") | |||
| api.add_resource(DatasetUseCheckApi, "/datasets/<uuid:dataset_id>/use-check") | |||
| api.add_resource(DatasetQueryApi, "/datasets/<uuid:dataset_id>/queries") | |||
| api.add_resource(DatasetErrorDocs, "/datasets/<uuid:dataset_id>/error-docs") | |||
| api.add_resource(DatasetIndexingEstimateApi, "/datasets/indexing-estimate") | |||
| api.add_resource(DatasetRelatedAppListApi, "/datasets/<uuid:dataset_id>/related-apps") | |||
| api.add_resource(DatasetIndexingStatusApi, "/datasets/<uuid:dataset_id>/indexing-status") | |||
| api.add_resource(DatasetApiKeyApi, "/datasets/api-keys") | |||
| api.add_resource(DatasetApiDeleteApi, "/datasets/api-keys/<uuid:api_key_id>") | |||
| api.add_resource(DatasetApiBaseUrlApi, "/datasets/api-base-info") | |||
| api.add_resource(DatasetRetrievalSettingApi, "/datasets/retrieval-setting") | |||
| api.add_resource(DatasetRetrievalSettingMockApi, "/datasets/retrieval-setting/<string:vector_type>") | |||
| api.add_resource(DatasetPermissionUserListApi, "/datasets/<uuid:dataset_id>/permission-part-users") | |||
| api.add_resource(DatasetAutoDisableLogApi, "/datasets/<uuid:dataset_id>/auto-disable-logs") | |||
| @@ -5,12 +5,12 @@ from typing import Literal, cast | |||
| from flask import request | |||
| from flask_login import current_user | |||
| from flask_restx import Resource, marshal, marshal_with, reqparse | |||
| from flask_restx import Resource, fields, marshal, marshal_with, reqparse | |||
| from sqlalchemy import asc, desc, select | |||
| from werkzeug.exceptions import Forbidden, NotFound | |||
| import services | |||
| from controllers.console import api | |||
| from controllers.console import api, console_ns | |||
| from controllers.console.app.error import ( | |||
| ProviderModelCurrentlyNotSupportError, | |||
| ProviderNotInitializeError, | |||
| @@ -98,7 +98,12 @@ class DocumentResource(Resource): | |||
| return documents | |||
| @console_ns.route("/datasets/process-rule") | |||
| class GetProcessRuleApi(Resource): | |||
| @api.doc("get_process_rule") | |||
| @api.doc(description="Get dataset document processing rules") | |||
| @api.doc(params={"document_id": "Document ID (optional)"}) | |||
| @api.response(200, "Process rules retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -140,7 +145,21 @@ class GetProcessRuleApi(Resource): | |||
| return {"mode": mode, "rules": rules, "limits": limits} | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/documents") | |||
| class DatasetDocumentListApi(Resource): | |||
| @api.doc("get_dataset_documents") | |||
| @api.doc(description="Get documents in a dataset") | |||
| @api.doc( | |||
| params={ | |||
| "dataset_id": "Dataset ID", | |||
| "page": "Page number (default: 1)", | |||
| "limit": "Number of items per page (default: 20)", | |||
| "keyword": "Search keyword", | |||
| "sort": "Sort order (default: -created_at)", | |||
| "fetch": "Fetch full details (default: false)", | |||
| } | |||
| ) | |||
| @api.response(200, "Documents retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -324,7 +343,23 @@ class DatasetDocumentListApi(Resource): | |||
| return {"result": "success"}, 204 | |||
| @console_ns.route("/datasets/init") | |||
| class DatasetInitApi(Resource): | |||
| @api.doc("init_dataset") | |||
| @api.doc(description="Initialize dataset with documents") | |||
| @api.expect( | |||
| api.model( | |||
| "DatasetInitRequest", | |||
| { | |||
| "upload_file_id": fields.String(required=True, description="Upload file ID"), | |||
| "indexing_technique": fields.String(description="Indexing technique"), | |||
| "process_rule": fields.Raw(description="Processing rules"), | |||
| "data_source": fields.Raw(description="Data source configuration"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(201, "Dataset initialized successfully", dataset_and_document_fields) | |||
| @api.response(400, "Invalid request parameters") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -394,7 +429,14 @@ class DatasetInitApi(Resource): | |||
| return response | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/indexing-estimate") | |||
| class DocumentIndexingEstimateApi(DocumentResource): | |||
| @api.doc("estimate_document_indexing") | |||
| @api.doc(description="Estimate document indexing cost") | |||
| @api.doc(params={"dataset_id": "Dataset ID", "document_id": "Document ID"}) | |||
| @api.response(200, "Indexing estimate calculated successfully") | |||
| @api.response(404, "Document not found") | |||
| @api.response(400, "Document already finished") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -593,7 +635,13 @@ class DocumentBatchIndexingStatusApi(DocumentResource): | |||
| return data | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/indexing-status") | |||
| class DocumentIndexingStatusApi(DocumentResource): | |||
| @api.doc("get_document_indexing_status") | |||
| @api.doc(description="Get document indexing status") | |||
| @api.doc(params={"dataset_id": "Dataset ID", "document_id": "Document ID"}) | |||
| @api.response(200, "Indexing status retrieved successfully") | |||
| @api.response(404, "Document not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -635,9 +683,21 @@ class DocumentIndexingStatusApi(DocumentResource): | |||
| return marshal(document_dict, document_status_fields) | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/documents/<uuid:document_id>") | |||
| class DocumentApi(DocumentResource): | |||
| METADATA_CHOICES = {"all", "only", "without"} | |||
| @api.doc("get_document") | |||
| @api.doc(description="Get document details") | |||
| @api.doc( | |||
| params={ | |||
| "dataset_id": "Dataset ID", | |||
| "document_id": "Document ID", | |||
| "metadata": "Metadata inclusion (all/only/without)", | |||
| } | |||
| ) | |||
| @api.response(200, "Document retrieved successfully") | |||
| @api.response(404, "Document not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -746,7 +806,16 @@ class DocumentApi(DocumentResource): | |||
| return {"result": "success"}, 204 | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/processing/<string:action>") | |||
| class DocumentProcessingApi(DocumentResource): | |||
| @api.doc("update_document_processing") | |||
| @api.doc(description="Update document processing status (pause/resume)") | |||
| @api.doc( | |||
| params={"dataset_id": "Dataset ID", "document_id": "Document ID", "action": "Action to perform (pause/resume)"} | |||
| ) | |||
| @api.response(200, "Processing status updated successfully") | |||
| @api.response(404, "Document not found") | |||
| @api.response(400, "Invalid action") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -781,7 +850,23 @@ class DocumentProcessingApi(DocumentResource): | |||
| return {"result": "success"}, 200 | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/metadata") | |||
| class DocumentMetadataApi(DocumentResource): | |||
| @api.doc("update_document_metadata") | |||
| @api.doc(description="Update document metadata") | |||
| @api.doc(params={"dataset_id": "Dataset ID", "document_id": "Document ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "UpdateDocumentMetadataRequest", | |||
| { | |||
| "doc_type": fields.String(description="Document type"), | |||
| "doc_metadata": fields.Raw(description="Document metadata"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Document metadata updated successfully") | |||
| @api.response(404, "Document not found") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -1015,26 +1100,3 @@ class WebsiteDocumentSyncApi(DocumentResource): | |||
| DocumentService.sync_website_document(dataset_id, document) | |||
| return {"result": "success"}, 200 | |||
| api.add_resource(GetProcessRuleApi, "/datasets/process-rule") | |||
| api.add_resource(DatasetDocumentListApi, "/datasets/<uuid:dataset_id>/documents") | |||
| api.add_resource(DatasetInitApi, "/datasets/init") | |||
| api.add_resource( | |||
| DocumentIndexingEstimateApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/indexing-estimate" | |||
| ) | |||
| api.add_resource(DocumentBatchIndexingEstimateApi, "/datasets/<uuid:dataset_id>/batch/<string:batch>/indexing-estimate") | |||
| api.add_resource(DocumentBatchIndexingStatusApi, "/datasets/<uuid:dataset_id>/batch/<string:batch>/indexing-status") | |||
| api.add_resource(DocumentIndexingStatusApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/indexing-status") | |||
| api.add_resource(DocumentApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>") | |||
| api.add_resource( | |||
| DocumentProcessingApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/processing/<string:action>" | |||
| ) | |||
| api.add_resource(DocumentMetadataApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/metadata") | |||
| api.add_resource(DocumentStatusApi, "/datasets/<uuid:dataset_id>/documents/status/<string:action>/batch") | |||
| api.add_resource(DocumentPauseApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/processing/pause") | |||
| api.add_resource(DocumentRecoverApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/processing/resume") | |||
| api.add_resource(DocumentRetryApi, "/datasets/<uuid:dataset_id>/retry") | |||
| api.add_resource(DocumentRenameApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/rename") | |||
| api.add_resource(WebsiteDocumentSyncApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/website-sync") | |||
| @@ -1,10 +1,10 @@ | |||
| from flask import request | |||
| from flask_login import current_user | |||
| from flask_restx import Resource, marshal, reqparse | |||
| from flask_restx import Resource, fields, marshal, reqparse | |||
| from werkzeug.exceptions import Forbidden, InternalServerError, NotFound | |||
| import services | |||
| from controllers.console import api | |||
| from controllers.console import api, console_ns | |||
| from controllers.console.datasets.error import DatasetNameDuplicateError | |||
| from controllers.console.wraps import account_initialization_required, setup_required | |||
| from fields.dataset_fields import dataset_detail_fields | |||
| @@ -21,7 +21,18 @@ def _validate_name(name): | |||
| return name | |||
| @console_ns.route("/datasets/external-knowledge-api") | |||
| class ExternalApiTemplateListApi(Resource): | |||
| @api.doc("get_external_api_templates") | |||
| @api.doc(description="Get external knowledge API templates") | |||
| @api.doc( | |||
| params={ | |||
| "page": "Page number (default: 1)", | |||
| "limit": "Number of items per page (default: 20)", | |||
| "keyword": "Search keyword", | |||
| } | |||
| ) | |||
| @api.response(200, "External API templates retrieved successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -79,7 +90,13 @@ class ExternalApiTemplateListApi(Resource): | |||
| return external_knowledge_api.to_dict(), 201 | |||
| @console_ns.route("/datasets/external-knowledge-api/<uuid:external_knowledge_api_id>") | |||
| class ExternalApiTemplateApi(Resource): | |||
| @api.doc("get_external_api_template") | |||
| @api.doc(description="Get external knowledge API template details") | |||
| @api.doc(params={"external_knowledge_api_id": "External knowledge API ID"}) | |||
| @api.response(200, "External API template retrieved successfully") | |||
| @api.response(404, "Template not found") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -138,7 +155,12 @@ class ExternalApiTemplateApi(Resource): | |||
| return {"result": "success"}, 204 | |||
| @console_ns.route("/datasets/external-knowledge-api/<uuid:external_knowledge_api_id>/use-check") | |||
| class ExternalApiUseCheckApi(Resource): | |||
| @api.doc("check_external_api_usage") | |||
| @api.doc(description="Check if external knowledge API is being used") | |||
| @api.doc(params={"external_knowledge_api_id": "External knowledge API ID"}) | |||
| @api.response(200, "Usage check completed successfully") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -151,7 +173,24 @@ class ExternalApiUseCheckApi(Resource): | |||
| return {"is_using": external_knowledge_api_is_using, "count": count}, 200 | |||
| @console_ns.route("/datasets/external") | |||
| class ExternalDatasetCreateApi(Resource): | |||
| @api.doc("create_external_dataset") | |||
| @api.doc(description="Create external knowledge dataset") | |||
| @api.expect( | |||
| api.model( | |||
| "CreateExternalDatasetRequest", | |||
| { | |||
| "external_knowledge_api_id": fields.String(required=True, description="External knowledge API ID"), | |||
| "external_knowledge_id": fields.String(required=True, description="External knowledge ID"), | |||
| "name": fields.String(required=True, description="Dataset name"), | |||
| "description": fields.String(description="Dataset description"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(201, "External dataset created successfully", dataset_detail_fields) | |||
| @api.response(400, "Invalid parameters") | |||
| @api.response(403, "Permission denied") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -191,7 +230,24 @@ class ExternalDatasetCreateApi(Resource): | |||
| return marshal(dataset, dataset_detail_fields), 201 | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/external-hit-testing") | |||
| class ExternalKnowledgeHitTestingApi(Resource): | |||
| @api.doc("test_external_knowledge_retrieval") | |||
| @api.doc(description="Test external knowledge retrieval for dataset") | |||
| @api.doc(params={"dataset_id": "Dataset ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "ExternalHitTestingRequest", | |||
| { | |||
| "query": fields.String(required=True, description="Query text for testing"), | |||
| "retrieval_model": fields.Raw(description="Retrieval model configuration"), | |||
| "external_retrieval_model": fields.Raw(description="External retrieval model configuration"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "External hit testing completed successfully") | |||
| @api.response(404, "Dataset not found") | |||
| @api.response(400, "Invalid parameters") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -228,8 +284,22 @@ class ExternalKnowledgeHitTestingApi(Resource): | |||
| raise InternalServerError(str(e)) | |||
| @console_ns.route("/test/retrieval") | |||
| class BedrockRetrievalApi(Resource): | |||
| # this api is only for internal testing | |||
| @api.doc("bedrock_retrieval_test") | |||
| @api.doc(description="Bedrock retrieval test (internal use only)") | |||
| @api.expect( | |||
| api.model( | |||
| "BedrockRetrievalTestRequest", | |||
| { | |||
| "retrieval_setting": fields.Raw(required=True, description="Retrieval settings"), | |||
| "query": fields.String(required=True, description="Query text"), | |||
| "knowledge_id": fields.String(required=True, description="Knowledge ID"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Bedrock retrieval test completed") | |||
| def post(self): | |||
| parser = reqparse.RequestParser() | |||
| parser.add_argument("retrieval_setting", nullable=False, required=True, type=dict, location="json") | |||
| @@ -247,12 +317,3 @@ class BedrockRetrievalApi(Resource): | |||
| args["retrieval_setting"], args["query"], args["knowledge_id"] | |||
| ) | |||
| return result, 200 | |||
| api.add_resource(ExternalKnowledgeHitTestingApi, "/datasets/<uuid:dataset_id>/external-hit-testing") | |||
| api.add_resource(ExternalDatasetCreateApi, "/datasets/external") | |||
| api.add_resource(ExternalApiTemplateListApi, "/datasets/external-knowledge-api") | |||
| api.add_resource(ExternalApiTemplateApi, "/datasets/external-knowledge-api/<uuid:external_knowledge_api_id>") | |||
| api.add_resource(ExternalApiUseCheckApi, "/datasets/external-knowledge-api/<uuid:external_knowledge_api_id>/use-check") | |||
| # this api is only for internal test | |||
| api.add_resource(BedrockRetrievalApi, "/test/retrieval") | |||
| @@ -1,6 +1,6 @@ | |||
| from flask_restx import Resource | |||
| from flask_restx import Resource, fields | |||
| from controllers.console import api | |||
| from controllers.console import api, console_ns | |||
| from controllers.console.datasets.hit_testing_base import DatasetsHitTestingBase | |||
| from controllers.console.wraps import ( | |||
| account_initialization_required, | |||
| @@ -10,7 +10,25 @@ from controllers.console.wraps import ( | |||
| from libs.login import login_required | |||
| @console_ns.route("/datasets/<uuid:dataset_id>/hit-testing") | |||
| class HitTestingApi(Resource, DatasetsHitTestingBase): | |||
| @api.doc("test_dataset_retrieval") | |||
| @api.doc(description="Test dataset knowledge retrieval") | |||
| @api.doc(params={"dataset_id": "Dataset ID"}) | |||
| @api.expect( | |||
| api.model( | |||
| "HitTestingRequest", | |||
| { | |||
| "query": fields.String(required=True, description="Query text for testing"), | |||
| "retrieval_model": fields.Raw(description="Retrieval model configuration"), | |||
| "top_k": fields.Integer(description="Number of top results to return"), | |||
| "score_threshold": fields.Float(description="Score threshold for filtering results"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Hit testing completed successfully") | |||
| @api.response(404, "Dataset not found") | |||
| @api.response(400, "Invalid parameters") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -23,6 +41,3 @@ class HitTestingApi(Resource, DatasetsHitTestingBase): | |||
| self.hit_testing_args_check(args) | |||
| return self.perform_hit_testing(dataset, args) | |||
| api.add_resource(HitTestingApi, "/datasets/<uuid:dataset_id>/hit-testing") | |||
| @@ -1,13 +1,32 @@ | |||
| from flask_restx import Resource, reqparse | |||
| from flask_restx import Resource, fields, reqparse | |||
| from controllers.console import api | |||
| from controllers.console import api, console_ns | |||
| from controllers.console.datasets.error import WebsiteCrawlError | |||
| from controllers.console.wraps import account_initialization_required, setup_required | |||
| from libs.login import login_required | |||
| from services.website_service import WebsiteCrawlApiRequest, WebsiteCrawlStatusApiRequest, WebsiteService | |||
| @console_ns.route("/website/crawl") | |||
| class WebsiteCrawlApi(Resource): | |||
| @api.doc("crawl_website") | |||
| @api.doc(description="Crawl website content") | |||
| @api.expect( | |||
| api.model( | |||
| "WebsiteCrawlRequest", | |||
| { | |||
| "provider": fields.String( | |||
| required=True, | |||
| description="Crawl provider (firecrawl/watercrawl/jinareader)", | |||
| enum=["firecrawl", "watercrawl", "jinareader"], | |||
| ), | |||
| "url": fields.String(required=True, description="URL to crawl"), | |||
| "options": fields.Raw(required=True, description="Crawl options"), | |||
| }, | |||
| ) | |||
| ) | |||
| @api.response(200, "Website crawl initiated successfully") | |||
| @api.response(400, "Invalid crawl parameters") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -39,7 +58,14 @@ class WebsiteCrawlApi(Resource): | |||
| return result, 200 | |||
| @console_ns.route("/website/crawl/status/<string:job_id>") | |||
| class WebsiteCrawlStatusApi(Resource): | |||
| @api.doc("get_crawl_status") | |||
| @api.doc(description="Get website crawl status") | |||
| @api.doc(params={"job_id": "Crawl job ID", "provider": "Crawl provider (firecrawl/watercrawl/jinareader)"}) | |||
| @api.response(200, "Crawl status retrieved successfully") | |||
| @api.response(404, "Crawl job not found") | |||
| @api.response(400, "Invalid provider") | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| @@ -62,7 +88,3 @@ class WebsiteCrawlStatusApi(Resource): | |||
| except Exception as e: | |||
| raise WebsiteCrawlError(str(e)) | |||
| return result, 200 | |||
| api.add_resource(WebsiteCrawlApi, "/website/crawl") | |||
| api.add_resource(WebsiteCrawlStatusApi, "/website/crawl/status/<string:job_id>") | |||