From 8de6b9780681617ad83333ba01d78b46faf8d22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E6=B5=B7=E8=92=BC=E7=81=86?= Date: Fri, 5 Dec 2025 19:42:35 +0800 Subject: [PATCH] Feature (canvas): Add Api for download "message" component output's file (#11772) ### What problem does this PR solve? -Add Api for download "message" component output's file -Change the attachment output type check from tuple to dictionary,because 'attachement' is not instance of tuple -Update the message type to message_end to avoid the problem that content does not send an error message when the message type is ans ["data"] ["content"] ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) - [x] New Feature (non-breaking change which adds functionality) --- agent/canvas.py | 10 ++++++---- api/apps/sdk/files.py | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/agent/canvas.py b/agent/canvas.py index b693ed434..cc40fd174 100644 --- a/agent/canvas.py +++ b/agent/canvas.py @@ -534,10 +534,12 @@ class Canvas(Graph): yield decorate("message", {"content": cpn_obj.output("content")}) cite = re.search(r"\[ID:[ 0-9]+\]", cpn_obj.output("content")) - if isinstance(cpn_obj.output("attachment"), tuple): - yield decorate("message", {"attachment": cpn_obj.output("attachment")}) - - yield decorate("message_end", {"reference": self.get_reference() if cite else None}) + message_end = {} + if isinstance(cpn_obj.output("attachment"), dict): + message_end["attachment"] = cpn_obj.output("attachment") + if cite: + message_end["reference"] = self.get_reference() + yield decorate("message_end", message_end) while partials: _cpn_obj = self.get_component_obj(partials[0]) diff --git a/api/apps/sdk/files.py b/api/apps/sdk/files.py index 981d3975e..2e9fd6df3 100644 --- a/api/apps/sdk/files.py +++ b/api/apps/sdk/files.py @@ -14,7 +14,7 @@ # limitations under the License. # - +import asyncio import pathlib import re from quart import request, make_response @@ -29,6 +29,7 @@ from api.db import FileType from api.db.services import duplicate_name from api.db.services.file_service import FileService from api.utils.file_utils import filename_type +from api.utils.web_utils import CONTENT_TYPE_MAP from common import settings from common.constants import RetCode @@ -629,6 +630,19 @@ async def get(tenant_id, file_id): except Exception as e: return server_error_response(e) +@manager.route("/file/download/", methods=["GET"]) # noqa: F821 +@token_required +async def download_attachment(tenant_id,attachment_id): + try: + ext = request.args.get("ext", "markdown") + data = await asyncio.to_thread(settings.STORAGE_IMPL.get, tenant_id, attachment_id) + response = await make_response(data) + response.headers.set("Content-Type", CONTENT_TYPE_MAP.get(ext, f"application/{ext}")) + + return response + + except Exception as e: + return server_error_response(e) @manager.route('/file/mv', methods=['POST']) # noqa: F821 @token_required