fix(chat): actually promote workflow-chat attachments

Two reasons a workflow-chat image resolved to nothing, both silent:

The promotion ran under `if not chat_response.is_bot`, but that field defaults
to True, so the user's own question — the only message this was meant for —
was the one case it skipped. Keyed on the category instead.

And it looked for the upload link under `filepath`/`file_path` only, while
workflow chat carries it as `file_url`, so even when it did run there was
nothing to copy. All three spellings are accepted now.

Refs: features/v2.6.0/043-chat-file-permanent-storage
This commit is contained in:
dolphin
2026-07-29 12:54:22 +08:00
parent ffc79496f5
commit 688ea3fc8a
3 changed files with 17 additions and 2 deletions
@@ -81,7 +81,10 @@ def _plan_promotions(files: list[dict], user_id: int | str, tmp_bucket: str) ->
if not isinstance(file, dict) or file.get("object_name"):
# Already permanent — task-mode uploads land in the main bucket.
continue
source_object = temp_object_name_from_url(file.get("filepath") or file.get("file_path") or "", tmp_bucket)
# The upload endpoints disagree on the field name for the link they
# issued -- daily/task say filepath, workflow chat says file_url.
source_url = file.get("filepath") or file.get("file_path") or file.get("file_url") or ""
source_object = temp_object_name_from_url(source_url, tmp_bucket)
if not source_object:
continue
dest_object = build_chat_object_name(user_id, file.get("filename") or file.get("file_name") or "")
@@ -536,7 +536,9 @@ class RedisCallback(BaseCallback):
# Attachments the user sent become permanent here, not at upload time —
# uploads sit in the temp bucket, which clears itself every 3 days.
# Only the user's own files: what the workflow produced is out of scope.
if not chat_response.is_bot:
# Keyed on category, not is_bot: that field defaults to True, so a
# question would have been skipped.
if chat_response.category == "question":
promote_chat_attachments_sync(chat_response.files, self.user_id)
message = ChatMessageDao.insert_one(
@@ -67,6 +67,16 @@ class TestPromoteChatAttachments:
assert "object_name" not in promoted[0] # stays unresolvable, flagged to the user later
assert promoted[1]["object_name"].startswith(f"{CHAT_OBJECT_PREFIX}7/")
async def test_workflow_chat_names_the_link_file_url(self, storage):
# Each upload path spells the link field differently; workflow chat says
# file_url, and reading only `filepath` silently promoted nothing.
files = [{"file_id": "f1", "file_name": "a.png", "file_url": "/bisheng-tmp/abc.png"}]
promoted = await promote_chat_attachments(files, user_id=7)
storage.copy_object.assert_awaited_once()
assert promoted[0]["object_name"].startswith(f"{CHAT_OBJECT_PREFIX}7/")
async def test_extension_is_carried_over_from_the_display_name(self, storage):
files = [{"file_id": "f1", "filename": "报告.PDF", "filepath": "/bisheng-tmp/xyz.pdf"}]