fix(web): convert agent log thought files only once

The log list converted message files to FileEntity before attaching them to
agent thoughts, and AgentContent converted them again for FileList. The second
conversion dropped supportFileType, so tool image outputs rendered as blank file
cards without a thumbnail or preview. Attach the raw message files, as the chat
history hooks do, so the renderer converts them once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Stephen Zhou
2026-10-16 06:43:33 +08:00
co-authored by Claude Opus 5.5
parent 7553fb3daf
commit 6a889f484e
2 changed files with 65 additions and 2 deletions
@@ -1,5 +1,6 @@
import type { MessageDetailResponse } from '@dify/contracts/api/console/apps/types.gen'
import type { IChatItem } from '@/app/components/base/chat/chat/type'
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
import {
applyAnnotationAdded,
applyAnnotationEdited,
@@ -291,6 +292,55 @@ describe('log list utils', () => {
).toEqual(['https://example.com/file-1'])
})
it('keeps agent thought files raw so the agent renderer converts them once', () => {
const [, answer] = getFormattedChatList(
[
createMessage({
id: 'message-1',
agent_thoughts: [
{
id: 'thought-1',
message_id: 'message-1',
position: 1,
files: ['file-1'],
tool: 'image_generator',
tool_labels: {},
},
],
message_files: [
{
belongs_to: 'assistant',
id: 'file-1',
filename: 'image.png',
transfer_method: 'tool_file',
type: 'image',
url: 'https://example.com/image.png',
},
],
}),
],
'conversation-1',
'UTC',
'YYYY-MM-DD',
)
const thoughtFiles = answer!.agent_thoughts![0]!.message_files!
expect(thoughtFiles).toEqual([
expect.objectContaining({
id: 'file-1',
type: 'image',
url: 'https://example.com/image.png',
}),
])
// Mirrors AgentContent, which converts thought files for FileList.
const [rendered] = getProcessedFilesFromResponse(
thoughtFiles.map((file) => ({ ...file, related_id: file.id }) as never),
)
expect(rendered).toEqual(
expect.objectContaining({ supportFileType: 'image', url: 'https://example.com/image.png' }),
)
})
it('should handle default inputs', () => {
expect(
getConversationRowValues({
+15 -2
View File
@@ -7,6 +7,7 @@ import type {
ThoughtItem,
} from '@/app/components/base/chat/chat/type'
import type { Annotation } from '@/models/log'
import type { VisionFile } from '@/types/app'
import type { FileResponse } from '@/types/workflow'
import dayjs from 'dayjs'
import timezone from 'dayjs/plugin/timezone'
@@ -53,6 +54,19 @@ type LogMessage = MessageDetailResponse
const isTransferMethod = (value: string): value is TransferMethod =>
(Object.values(TransferMethod) as string[]).includes(value)
// Agent thoughts keep raw message files; the agent content renderer converts them once.
const toThoughtFiles = (item: LogMessage): VisionFile[] =>
item.message_files.map((file) => ({
id: file.id,
type: file.type,
transfer_method: isTransferMethod(file.transfer_method)
? file.transfer_method
: TransferMethod.local_file,
url: file.url ?? '',
upload_file_id: file.upload_file_id ?? '',
belongs_to: file.belongs_to ?? undefined,
}))
const toFileResponse = (file: LogMessage['message_files'][number]): FileResponse => ({
related_id: file.id ?? file.upload_file_id,
extension: '',
@@ -146,7 +160,6 @@ export const getFormattedChatList = (
const newChatList: IChatItem[] = []
messages.forEach((item) => {
const files = getProcessedFilesFromResponse(item.message_files.map(toFileResponse))
const questionFiles = getProcessedFilesFromResponse(
item.message_files.filter((file) => file.belongs_to === 'user').map(toFileResponse),
)
@@ -165,7 +178,7 @@ export const getFormattedChatList = (
newChatList.push({
id: item.id,
content: item.answer,
agent_thoughts: addFileInfos(sortAgentSorts(toThoughtItems(item)), files),
agent_thoughts: addFileInfos(sortAgentSorts(toThoughtItems(item)), toThoughtFiles(item)),
feedback: toFeedback(item.feedbacks.find((feedback) => feedback.from_source === 'user')),
adminFeedback: toFeedback(
item.feedbacks.find((feedback) => feedback.from_source === 'admin'),