diff --git a/web/app/components/base/chat/chat/answer/human-input-content/__tests__/submitted-field-values.spec.tsx b/web/app/components/base/chat/chat/answer/human-input-content/__tests__/submitted-field-values.spec.tsx new file mode 100644 index 0000000000..93eed83406 --- /dev/null +++ b/web/app/components/base/chat/chat/answer/human-input-content/__tests__/submitted-field-values.spec.tsx @@ -0,0 +1,108 @@ +import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types' +import type { FileResponse, HumanInputFormValue } from '@/types/workflow' +import { render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import { InputVarType } from '@/app/components/workflow/types' +import { TransferMethod } from '@/types/app' +import SubmittedFieldValues from '../submitted-field-values' + +const fields: FormInputItem[] = [ + { + type: InputVarType.paragraph, + output_variable_name: 'summary', + default: { type: 'constant', value: '', selector: [] }, + }, + { + type: InputVarType.select, + output_variable_name: 'decision', + option_source: { type: 'constant', value: ['approve', 'reject'], selector: [] }, + }, + { + type: InputVarType.singleFile, + output_variable_name: 'attachment', + allowed_file_extensions: [], + allowed_file_types: [], + allowed_file_upload_methods: [], + }, + { + type: InputVarType.multiFiles, + output_variable_name: 'evidence', + allowed_file_extensions: [], + allowed_file_types: [], + allowed_file_upload_methods: [], + max_upload_count: 5, + }, +] + +const attachmentValue: FileResponse = { + related_id: 'file-1', + upload_file_id: 'upload-1', + filename: 'decision.pdf', + extension: 'pdf', + size: 128, + mime_type: 'application/pdf', + transfer_method: TransferMethod.local_file, + type: 'document', + url: 'https://example.com/decision.pdf', + remote_url: '', +} + +const evidenceValues: FileResponse[] = [ + { + related_id: 'file-2', + upload_file_id: 'upload-2', + filename: 'evidence-1.png', + extension: 'png', + size: 256, + mime_type: 'image/png', + transfer_method: TransferMethod.remote_url, + type: 'image', + url: 'https://example.com/evidence-1.png', + remote_url: 'https://example.com/evidence-1.png', + }, + { + related_id: 'file-3', + upload_file_id: 'upload-3', + filename: 'evidence-2.pdf', + extension: 'pdf', + size: 512, + mime_type: 'application/pdf', + transfer_method: TransferMethod.local_file, + type: 'document', + url: 'https://example.com/evidence-2.pdf', + remote_url: '', + }, +] + +const values: Record = { + summary: 'Need more context', + decision: 'approve', + attachment: attachmentValue, + evidence: evidenceValues, +} + +describe('SubmittedFieldValues', () => { + it('renders text and select values as text', () => { + render() + + expect(screen.getByTestId('submitted-field-summary')).toHaveTextContent('Need more context') + expect(screen.getByTestId('submitted-field-decision')).toHaveTextContent('approve') + }) + + it('renders file and file-list values as file lists', () => { + render() + + expect(screen.getByTestId('submitted-field-attachment')).toHaveTextContent('decision.pdf') + expect(screen.getByRole('img', { name: 'Preview' })).toHaveAttribute('src', 'https://example.com/evidence-1.png') + expect(screen.getByText('evidence-2.pdf')).toBeInTheDocument() + expect(screen.getAllByTestId('file-list')).toHaveLength(2) + }) + + it('skips fields with missing values', () => { + render() + + expect(screen.getByTestId('submitted-field-summary')).toHaveTextContent('Only one field') + expect(screen.queryByTestId('submitted-field-decision')).not.toBeInTheDocument() + expect(screen.queryByTestId('submitted-field-attachment')).not.toBeInTheDocument() + }) +}) diff --git a/web/app/components/base/chat/chat/answer/human-input-content/submitted-field-values.tsx b/web/app/components/base/chat/chat/answer/human-input-content/submitted-field-values.tsx new file mode 100644 index 0000000000..acfa0658ea --- /dev/null +++ b/web/app/components/base/chat/chat/answer/human-input-content/submitted-field-values.tsx @@ -0,0 +1,72 @@ +import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types' +import type { HumanInputFormValue } from '@/types/workflow' +import * as React from 'react' +import { FileList } from '@/app/components/base/file-uploader' +import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' +import { + isFileFormInput, + isFileListFormInput, +} from '@/app/components/workflow/nodes/human-input/types' + +type SubmittedFieldValuesProps = { + fields: FormInputItem[] + values: Record +} + +const SubmittedFieldValues = ({ + fields, + values, +}: SubmittedFieldValuesProps) => { + return ( +
+ {fields.map((field) => { + const value = values[field.output_variable_name] + + if (value == null) + return null + + if (isFileFormInput(field)) { + if (typeof value === 'string' || Array.isArray(value)) + return null + + return ( +
+ +
+ ) + } + + if (isFileListFormInput(field)) { + if (typeof value === 'string' || !Array.isArray(value)) + return null + + return ( +
+ +
+ ) + } + + return ( +
+ {String(value)} +
+ ) + })} +
+ ) +} + +export default React.memo(SubmittedFieldValues)