| @@ -334,7 +334,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({ | |||
| </SimpleBtn> | |||
| ) | |||
| } | |||
| {(currentTab === 'RESULT' || !isWorkflow) && ( | |||
| {((currentTab === 'RESULT' && workflowProcessData?.resultText) || !isWorkflow) && ( | |||
| <SimpleBtn | |||
| isDisabled={isError || !messageId} | |||
| className={cn(isMobile && '!px-1.5', 'space-x-1')} | |||
| @@ -27,15 +27,15 @@ const ResultTab = ({ | |||
| onCurrentTabChange(tab) | |||
| } | |||
| useEffect(() => { | |||
| if (data?.resultText) | |||
| if (data?.resultText || !!data?.files?.length) | |||
| switchTab('RESULT') | |||
| else | |||
| switchTab('DETAIL') | |||
| }, [data?.resultText]) | |||
| }, [data?.files?.length, data?.resultText]) | |||
| return ( | |||
| <div className='grow relative flex flex-col'> | |||
| {data?.resultText && ( | |||
| {(data?.resultText || !!data?.files?.length) && ( | |||
| <div className='shrink-0 flex items-center mb-2 border-b-[0.5px] border-[rgba(0,0,0,0.05)]'> | |||
| <div | |||
| className={cn( | |||
| @@ -56,14 +56,21 @@ const ResultTab = ({ | |||
| <div className={cn('grow bg-white')}> | |||
| {currentTab === 'RESULT' && ( | |||
| <> | |||
| <Markdown content={data?.resultText || ''} /> | |||
| {data?.resultText && <Markdown content={data?.resultText || ''} />} | |||
| {!!data?.files?.length && ( | |||
| <FileList | |||
| files={data?.files} | |||
| showDeleteAction={false} | |||
| showDownloadAction | |||
| canPreview | |||
| /> | |||
| <div className='flex flex-col gap-2'> | |||
| {data?.files.map((item: any) => ( | |||
| <div key={item.varName} className='flex flex-col gap-1 system-xs-regular'> | |||
| <div className='py-1 text-text-tertiary '>{item.varName}</div> | |||
| <FileList | |||
| files={item.list} | |||
| showDeleteAction={false} | |||
| showDownloadAction | |||
| canPreview | |||
| /> | |||
| </div> | |||
| ))} | |||
| </div> | |||
| )} | |||
| </> | |||
| )} | |||
| @@ -1,4 +1,5 @@ | |||
| import React, { useState } from 'react' | |||
| import React, { useMemo, useState } from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { RiArrowRightSLine } from '@remixicon/react' | |||
| import FileImageRender from './file-image-render' | |||
| import FileTypeIcon from './file-type-icon' | |||
| @@ -12,23 +13,36 @@ import { SupportUploadFileTypes } from '@/app/components/workflow/types' | |||
| import cn from '@/utils/classnames' | |||
| type Props = { | |||
| fileList: FileEntity[] | |||
| fileList: { | |||
| varName: string | |||
| list: FileEntity[] | |||
| }[] | |||
| isExpanded?: boolean | |||
| noBorder?: boolean | |||
| noPadding?: boolean | |||
| } | |||
| const FileListInLog = ({ fileList }: Props) => { | |||
| const [expanded, setExpanded] = useState(false) | |||
| const FileListInLog = ({ fileList, isExpanded = false, noBorder = false, noPadding = false }: Props) => { | |||
| const { t } = useTranslation() | |||
| const [expanded, setExpanded] = useState(isExpanded) | |||
| const fullList = useMemo(() => { | |||
| return fileList.reduce((acc: FileEntity[], { list }) => { | |||
| return [...acc, ...list] | |||
| }, []) | |||
| }, [fileList]) | |||
| if (!fileList.length) | |||
| return null | |||
| return ( | |||
| <div className={cn('border-t border-divider-subtle px-3 py-2', expanded && 'py-3')}> | |||
| <div className={cn('px-3 py-2', expanded && 'py-3', !noBorder && 'border-t border-divider-subtle', noPadding && '!p-0')}> | |||
| <div className='flex justify-between gap-1'> | |||
| {expanded && ( | |||
| <div></div> | |||
| <div className='grow py-1 text-text-secondary system-xs-semibold-uppercase cursor-pointer' onClick={() => setExpanded(!expanded)}>{t('appLog.runDetail.fileListLabel')}</div> | |||
| )} | |||
| {!expanded && ( | |||
| <div className='flex'> | |||
| {fileList.map((file) => { | |||
| <div className='flex gap-1'> | |||
| {fullList.map((file) => { | |||
| const { id, name, type, supportFileType, base64Url, url } = file | |||
| const isImageFile = supportFileType === SupportUploadFileTypes.image | |||
| return ( | |||
| @@ -63,19 +77,25 @@ const FileListInLog = ({ fileList }: Props) => { | |||
| </div> | |||
| )} | |||
| <div className='flex items-center gap-1 cursor-pointer' onClick={() => setExpanded(!expanded)}> | |||
| {!expanded && <div className='text-text-tertiary system-xs-medium-uppercase'>DETAIL</div>} | |||
| {!expanded && <div className='text-text-tertiary system-xs-medium-uppercase'>{t('appLog.runDetail.fileListDetail')}</div>} | |||
| <RiArrowRightSLine className={cn('w-4 h-4 text-text-tertiary', expanded && 'rotate-90')} /> | |||
| </div> | |||
| </div> | |||
| {expanded && ( | |||
| <div className='flex flex-col gap-1'> | |||
| {fileList.map(file => ( | |||
| <FileItem | |||
| key={file.id} | |||
| file={file} | |||
| showDeleteAction={false} | |||
| showDownloadAction | |||
| /> | |||
| <div className='flex flex-col gap-3'> | |||
| {fileList.map(item => ( | |||
| <div key={item.varName} className='flex flex-col gap-1 system-xs-regular'> | |||
| <div className='py-1 text-text-tertiary '>{item.varName}</div> | |||
| {item.list.map(file => ( | |||
| <FileItem | |||
| key={file.id} | |||
| file={file} | |||
| showDeleteAction={false} | |||
| showDownloadAction | |||
| canPreview | |||
| /> | |||
| ))} | |||
| </div> | |||
| ))} | |||
| </div> | |||
| )} | |||
| @@ -1,12 +1,15 @@ | |||
| import { | |||
| memo, | |||
| useState, | |||
| } from 'react' | |||
| import { | |||
| RiDeleteBinLine, | |||
| RiDownloadLine, | |||
| RiEyeLine, | |||
| } from '@remixicon/react' | |||
| import FileTypeIcon from '../file-type-icon' | |||
| import { | |||
| downloadFile, | |||
| fileIsUploaded, | |||
| getFileAppearanceType, | |||
| getFileExtension, | |||
| @@ -19,6 +22,7 @@ import { formatFileSize } from '@/utils/format' | |||
| import cn from '@/utils/classnames' | |||
| import { ReplayLine } from '@/app/components/base/icons/src/vender/other' | |||
| import { SupportUploadFileTypes } from '@/app/components/workflow/types' | |||
| import ImagePreview from '@/app/components/base/image-uploader/image-preview' | |||
| type FileInAttachmentItemProps = { | |||
| file: FileEntity | |||
| @@ -26,6 +30,7 @@ type FileInAttachmentItemProps = { | |||
| showDownloadAction?: boolean | |||
| onRemove?: (fileId: string) => void | |||
| onReUpload?: (fileId: string) => void | |||
| canPreview?: boolean | |||
| } | |||
| const FileInAttachmentItem = ({ | |||
| file, | |||
| @@ -33,96 +38,116 @@ const FileInAttachmentItem = ({ | |||
| showDownloadAction = true, | |||
| onRemove, | |||
| onReUpload, | |||
| canPreview, | |||
| }: FileInAttachmentItemProps) => { | |||
| const { id, name, type, progress, supportFileType, base64Url, url, isRemote } = file | |||
| const ext = getFileExtension(name, type, isRemote) | |||
| const isImageFile = supportFileType === SupportUploadFileTypes.image | |||
| const [imagePreviewUrl, setImagePreviewUrl] = useState('') | |||
| return ( | |||
| <div className={cn( | |||
| 'flex items-center pr-3 h-12 rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', | |||
| progress === -1 && 'bg-state-destructive-hover border-state-destructive-border', | |||
| )}> | |||
| <div className='flex items-center justify-center w-12 h-12'> | |||
| { | |||
| isImageFile && ( | |||
| <FileImageRender | |||
| className='w-8 h-8' | |||
| imageUrl={base64Url || url || ''} | |||
| /> | |||
| ) | |||
| } | |||
| { | |||
| !isImageFile && ( | |||
| <FileTypeIcon | |||
| type={getFileAppearanceType(name, type)} | |||
| size='lg' | |||
| /> | |||
| ) | |||
| } | |||
| </div> | |||
| <div className='grow w-0 mr-1'> | |||
| <div | |||
| className='flex items-center mb-0.5 system-xs-medium text-text-secondary truncate' | |||
| title={file.name} | |||
| > | |||
| <div className='truncate'>{name}</div> | |||
| <> | |||
| <div className={cn( | |||
| 'flex items-center pr-3 h-12 rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', | |||
| progress === -1 && 'bg-state-destructive-hover border-state-destructive-border', | |||
| )}> | |||
| <div className='flex items-center justify-center w-12 h-12'> | |||
| { | |||
| isImageFile && ( | |||
| <FileImageRender | |||
| className='w-8 h-8' | |||
| imageUrl={base64Url || url || ''} | |||
| /> | |||
| ) | |||
| } | |||
| { | |||
| !isImageFile && ( | |||
| <FileTypeIcon | |||
| type={getFileAppearanceType(name, type)} | |||
| size='lg' | |||
| /> | |||
| ) | |||
| } | |||
| </div> | |||
| <div className='flex items-center system-2xs-medium-uppercase text-text-tertiary'> | |||
| <div className='grow w-0 mr-1'> | |||
| <div | |||
| className='flex items-center mb-0.5 system-xs-medium text-text-secondary truncate' | |||
| title={file.name} | |||
| > | |||
| <div className='truncate'>{name}</div> | |||
| </div> | |||
| <div className='flex items-center system-2xs-medium-uppercase text-text-tertiary'> | |||
| { | |||
| ext && ( | |||
| <span>{ext.toLowerCase()}</span> | |||
| ) | |||
| } | |||
| { | |||
| ext && ( | |||
| <span className='mx-1 system-2xs-medium'>•</span> | |||
| ) | |||
| } | |||
| { | |||
| !!file.size && ( | |||
| <span>{formatFileSize(file.size)}</span> | |||
| ) | |||
| } | |||
| </div> | |||
| </div> | |||
| <div className='shrink-0 flex items-center'> | |||
| { | |||
| progress >= 0 && !fileIsUploaded(file) && ( | |||
| <ProgressCircle | |||
| className='mr-2.5' | |||
| percentage={progress} | |||
| /> | |||
| ) | |||
| } | |||
| { | |||
| ext && ( | |||
| <span>{ext.toLowerCase()}</span> | |||
| progress === -1 && ( | |||
| <ActionButton | |||
| className='mr-1' | |||
| onClick={() => onReUpload?.(id)} | |||
| > | |||
| <ReplayLine className='w-4 h-4 text-text-tertiary' /> | |||
| </ActionButton> | |||
| ) | |||
| } | |||
| { | |||
| ext && ( | |||
| <span className='mx-1 system-2xs-medium'>•</span> | |||
| showDeleteAction && ( | |||
| <ActionButton onClick={() => onRemove?.(id)}> | |||
| <RiDeleteBinLine className='w-4 h-4' /> | |||
| </ActionButton> | |||
| ) | |||
| } | |||
| { | |||
| !!file.size && ( | |||
| <span>{formatFileSize(file.size)}</span> | |||
| canPreview && isImageFile && ( | |||
| <ActionButton className='mr-1' onClick={() => setImagePreviewUrl(url || '')}> | |||
| <RiEyeLine className='w-4 h-4' /> | |||
| </ActionButton> | |||
| ) | |||
| } | |||
| { | |||
| showDownloadAction && ( | |||
| <ActionButton onClick={(e) => { | |||
| e.stopPropagation() | |||
| downloadFile(url || base64Url || '', name) | |||
| }}> | |||
| <RiDownloadLine className='w-4 h-4' /> | |||
| </ActionButton> | |||
| ) | |||
| } | |||
| </div> | |||
| </div> | |||
| <div className='shrink-0 flex items-center'> | |||
| { | |||
| progress >= 0 && !fileIsUploaded(file) && ( | |||
| <ProgressCircle | |||
| className='mr-2.5' | |||
| percentage={progress} | |||
| /> | |||
| ) | |||
| } | |||
| { | |||
| progress === -1 && ( | |||
| <ActionButton | |||
| className='mr-1' | |||
| onClick={() => onReUpload?.(id)} | |||
| > | |||
| <ReplayLine className='w-4 h-4 text-text-tertiary' /> | |||
| </ActionButton> | |||
| ) | |||
| } | |||
| { | |||
| showDeleteAction && ( | |||
| <ActionButton onClick={() => onRemove?.(id)}> | |||
| <RiDeleteBinLine className='w-4 h-4' /> | |||
| </ActionButton> | |||
| ) | |||
| } | |||
| { | |||
| showDownloadAction && ( | |||
| <ActionButton | |||
| size='xs' | |||
| > | |||
| <RiDownloadLine className='w-3.5 h-3.5 text-text-tertiary' /> | |||
| </ActionButton> | |||
| ) | |||
| } | |||
| </div> | |||
| </div> | |||
| { | |||
| imagePreviewUrl && canPreview && ( | |||
| <ImagePreview | |||
| title={name} | |||
| url={imagePreviewUrl} | |||
| onCancel={() => setImagePreviewUrl('')} | |||
| /> | |||
| ) | |||
| } | |||
| </> | |||
| ) | |||
| } | |||
| @@ -31,7 +31,7 @@ const FileItem = ({ | |||
| onRemove, | |||
| onReUpload, | |||
| }: FileItemProps) => { | |||
| const { id, name, type, progress, url, isRemote } = file | |||
| const { id, name, type, progress, url, base64Url, isRemote } = file | |||
| const ext = getFileExtension(name, type, isRemote) | |||
| const uploadError = progress === -1 | |||
| @@ -86,7 +86,7 @@ const FileItem = ({ | |||
| className='hidden group-hover/file-item:flex absolute -right-1 -top-1' | |||
| onClick={(e) => { | |||
| e.stopPropagation() | |||
| downloadFile(url || '', name) | |||
| downloadFile(url || base64Url || '', name) | |||
| }} | |||
| > | |||
| <RiDownloadLine className='w-3.5 h-3.5 text-text-tertiary' /> | |||
| @@ -1,5 +1,4 @@ | |||
| import mime from 'mime' | |||
| import { flatten } from 'lodash-es' | |||
| import { FileAppearanceTypeEnum } from './types' | |||
| import type { FileEntity } from './types' | |||
| import { upload } from '@/service/base' | |||
| @@ -158,12 +157,22 @@ export const isAllowedFileExtension = (fileName: string, fileMimetype: string, a | |||
| } | |||
| export const getFilesInLogs = (rawData: any) => { | |||
| const originalFiles = flatten(Object.keys(rawData || {}).map((key) => { | |||
| if (typeof rawData[key] === 'object' || Array.isArray(rawData[key])) | |||
| return rawData[key] | |||
| const result = Object.keys(rawData || {}).map((key) => { | |||
| if (typeof rawData[key] === 'object' && rawData[key].dify_model_identity === '__dify__file__') { | |||
| return { | |||
| varName: key, | |||
| list: getProcessedFilesFromResponse([rawData[key]]), | |||
| } | |||
| } | |||
| if (Array.isArray(rawData[key]) && rawData[key].some(item => item.dify_model_identity === '__dify__file__')) { | |||
| return { | |||
| varName: key, | |||
| list: getProcessedFilesFromResponse(rawData[key]), | |||
| } | |||
| } | |||
| return undefined | |||
| }).filter(Boolean)).filter(item => item?.model_identity === '__dify__file__') | |||
| return getProcessedFilesFromResponse(originalFiles) | |||
| }).filter(Boolean) | |||
| return result | |||
| } | |||
| export const fileIsUploaded = (file: FileEntity) => { | |||
| @@ -21,7 +21,7 @@ import { sleep } from '@/utils' | |||
| import type { SiteInfo } from '@/models/share' | |||
| import { TEXT_GENERATION_TIMEOUT_MS } from '@/config' | |||
| import { | |||
| getProcessedFilesFromResponse, | |||
| getFilesInLogs, | |||
| } from '@/app/components/base/file-uploader/utils' | |||
| export type IResultProps = { | |||
| @@ -288,7 +288,7 @@ const Result: FC<IResultProps> = ({ | |||
| } | |||
| setWorkflowProcessData(produce(getWorkflowProcessData()!, (draft) => { | |||
| draft.status = WorkflowRunningStatus.Succeeded | |||
| draft.files = getProcessedFilesFromResponse(data.files || []) | |||
| draft.files = getFilesInLogs(data.outputs || []) as any[] | |||
| })) | |||
| if (!data.outputs) { | |||
| setCompletionRes('') | |||
| @@ -26,7 +26,7 @@ import { | |||
| import { useFeaturesStore } from '@/app/components/base/features/hooks' | |||
| import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager' | |||
| import { | |||
| getProcessedFilesFromResponse, | |||
| getFilesInLogs, | |||
| } from '@/app/components/base/file-uploader/utils' | |||
| export const useWorkflowRun = () => { | |||
| @@ -213,7 +213,7 @@ export const useWorkflowRun = () => { | |||
| draft.result = { | |||
| ...draft.result, | |||
| ...data, | |||
| files: getProcessedFilesFromResponse(data.files || []), | |||
| files: getFilesInLogs(data.outputs), | |||
| } as any | |||
| if (isStringOutput) { | |||
| draft.resultTabActive = true | |||
| @@ -27,7 +27,10 @@ type Props = { | |||
| isInNode?: boolean | |||
| onGenerated?: (prompt: string) => void | |||
| codeLanguages?: CodeLanguage | |||
| fileList?: FileEntity[] | |||
| fileList?: { | |||
| varName: string | |||
| list: FileEntity[] | |||
| }[] | |||
| showFileList?: boolean | |||
| showCodeGenerator?: boolean | |||
| } | |||
| @@ -208,7 +208,7 @@ const CodeEditor: FC<Props> = ({ | |||
| isInNode={isInNode} | |||
| onGenerated={onGenerated} | |||
| codeLanguages={language} | |||
| fileList={fileList} | |||
| fileList={fileList as any} | |||
| showFileList={showFileList} | |||
| showCodeGenerator={showCodeGenerator} | |||
| > | |||
| @@ -48,7 +48,7 @@ const WorkflowPreview = () => { | |||
| }, [showDebugAndPreviewPanel, showInputsPanel]) | |||
| useEffect(() => { | |||
| if ((workflowRunningData?.result.status === WorkflowRunningStatus.Succeeded || workflowRunningData?.result.status === WorkflowRunningStatus.Failed) && !workflowRunningData.resultText) | |||
| if ((workflowRunningData?.result.status === WorkflowRunningStatus.Succeeded || workflowRunningData?.result.status === WorkflowRunningStatus.Failed) && !workflowRunningData.resultText && !workflowRunningData.result.files?.length) | |||
| switchTab('DETAIL') | |||
| }, [workflowRunningData]) | |||
| @@ -1,10 +1,13 @@ | |||
| 'use client' | |||
| import type { FC } from 'react' | |||
| import { useMemo } from 'react' | |||
| import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' | |||
| import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' | |||
| import { Markdown } from '@/app/components/base/markdown' | |||
| import LoadingAnim from '@/app/components/base/chat/chat/loading-anim' | |||
| import { FileList } from '@/app/components/base/file-uploader' | |||
| import StatusContainer from '@/app/components/workflow/run/status-container' | |||
| import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' | |||
| type OutputPanelProps = { | |||
| isRunning?: boolean | |||
| @@ -19,6 +22,30 @@ const OutputPanel: FC<OutputPanelProps> = ({ | |||
| error, | |||
| height, | |||
| }) => { | |||
| const isTextOutput = useMemo(() => { | |||
| return outputs && Object.keys(outputs).length === 1 && typeof outputs[Object.keys(outputs)[0]] === 'string' | |||
| }, [outputs]) | |||
| const fileList = useMemo(() => { | |||
| const fileList: any[] = [] | |||
| if (!outputs) | |||
| return fileList | |||
| if (Object.keys(outputs).length > 1) | |||
| return fileList | |||
| for (const key in outputs) { | |||
| if (Array.isArray(outputs[key])) { | |||
| outputs[key].map((output: any) => { | |||
| if (output.dify_model_identity === '__dify__file__') | |||
| fileList.push(output) | |||
| return null | |||
| }) | |||
| } | |||
| else if (outputs[key].dify_model_identity === '__dify__file__') { | |||
| fileList.push(outputs[key]) | |||
| } | |||
| } | |||
| return getProcessedFilesFromResponse(fileList) | |||
| }, [outputs]) | |||
| return ( | |||
| <div className='py-2'> | |||
| {isRunning && ( | |||
| @@ -36,20 +63,31 @@ const OutputPanel: FC<OutputPanelProps> = ({ | |||
| <Markdown content='No Output' /> | |||
| </div> | |||
| )} | |||
| {outputs && Object.keys(outputs).length === 1 && ( | |||
| {isTextOutput && ( | |||
| <div className='px-4 py-2'> | |||
| <Markdown content={outputs[Object.keys(outputs)[0]] || ''} /> | |||
| </div> | |||
| )} | |||
| {fileList.length > 0 && ( | |||
| <div className='px-4 py-2'> | |||
| <FileList | |||
| files={fileList} | |||
| showDeleteAction={false} | |||
| showDownloadAction | |||
| canPreview | |||
| /> | |||
| </div> | |||
| )} | |||
| {outputs && Object.keys(outputs).length > 1 && height! > 0 && ( | |||
| <div className='px-4 py-2 flex flex-col gap-2'> | |||
| <div className='flex flex-col gap-2'> | |||
| <CodeEditor | |||
| showFileList | |||
| readOnly | |||
| title={<div></div>} | |||
| language={CodeLanguage.json} | |||
| value={outputs} | |||
| isJSONStringifyBeauty | |||
| height={height} | |||
| height={height ? (height - 16) / 2 : undefined} | |||
| /> | |||
| </div> | |||
| )} | |||
| @@ -6,14 +6,13 @@ import { Markdown } from '@/app/components/base/markdown' | |||
| import LoadingAnim from '@/app/components/base/chat/chat/loading-anim' | |||
| import StatusContainer from '@/app/components/workflow/run/status-container' | |||
| import { FileList } from '@/app/components/base/file-uploader' | |||
| import type { FileEntity } from '@/app/components/base/file-uploader/types' | |||
| type ResultTextProps = { | |||
| isRunning?: boolean | |||
| outputs?: any | |||
| error?: string | |||
| onClick?: () => void | |||
| allFiles?: FileEntity[] | |||
| allFiles?: any[] | |||
| } | |||
| const ResultText: FC<ResultTextProps> = ({ | |||
| @@ -25,20 +24,20 @@ const ResultText: FC<ResultTextProps> = ({ | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| return ( | |||
| <div className='bg-background-section-burn py-2'> | |||
| <div className='bg-background-section-burn'> | |||
| {isRunning && !outputs && ( | |||
| <div className='pt-4 pl-[26px]'> | |||
| <LoadingAnim type='text' /> | |||
| </div> | |||
| )} | |||
| {!isRunning && error && ( | |||
| <div className='px-4'> | |||
| <div className='px-4 py-2'> | |||
| <StatusContainer status='failed'> | |||
| {error} | |||
| </StatusContainer> | |||
| </div> | |||
| )} | |||
| {!isRunning && !outputs && !error && ( | |||
| {!isRunning && !outputs && !error && !allFiles?.length && ( | |||
| <div className='mt-[120px] px-4 py-2 flex flex-col items-center text-[13px] leading-[18px] text-gray-500'> | |||
| <ImageIndentLeft className='w-6 h-6 text-gray-400' /> | |||
| <div className='mr-2'>{t('runLog.resultEmpty.title')}</div> | |||
| @@ -49,18 +48,25 @@ const ResultText: FC<ResultTextProps> = ({ | |||
| </div> | |||
| </div> | |||
| )} | |||
| {outputs && ( | |||
| <div className='px-4 py-2'> | |||
| <Markdown content={outputs} /> | |||
| {!!allFiles?.length && ( | |||
| <FileList | |||
| files={allFiles} | |||
| showDeleteAction={false} | |||
| showDownloadAction | |||
| canPreview | |||
| /> | |||
| {(outputs || !!allFiles?.length) && ( | |||
| <> | |||
| {outputs && ( | |||
| <div className='px-4 py-2'> | |||
| <Markdown content={outputs} /> | |||
| </div> | |||
| )} | |||
| </div> | |||
| {!!allFiles?.length && allFiles.map(item => ( | |||
| <div key={item.varName} className='px-4 py-2 flex flex-col gap-1 system-xs-regular'> | |||
| <div className='py-1 text-text-tertiary '>{item.varName}</div> | |||
| <FileList | |||
| files={item.list} | |||
| showDeleteAction={false} | |||
| showDownloadAction | |||
| canPreview | |||
| /> | |||
| </div> | |||
| ))} | |||
| </> | |||
| )} | |||
| </div> | |||
| ) | |||
| @@ -79,6 +79,8 @@ const translation = { | |||
| runDetail: { | |||
| title: 'Conversation Log', | |||
| workflowTitle: 'Log Detail', | |||
| fileListLabel: 'File Details', | |||
| fileListDetail: 'Detail', | |||
| }, | |||
| promptLog: 'Prompt Log', | |||
| agentLog: 'Agent Log', | |||
| @@ -79,6 +79,8 @@ const translation = { | |||
| runDetail: { | |||
| title: '对话日志', | |||
| workflowTitle: '日志详情', | |||
| fileListLabel: '文件详情', | |||
| fileListDetail: '详情', | |||
| }, | |||
| promptLog: 'Prompt 日志', | |||
| agentLog: 'Agent 日志', | |||