import type { InspectHeaderProps } from './inspect-layout' import type { DocPathWithoutLang } from '@/types/doc-paths' import type { SandboxFileTreeNode } from '@/types/sandbox-file' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import SearchLinesSparkle from '@/app/components/base/icons/src/vender/knowledge/SearchLinesSparkle' import { FileDownload01 } from '@/app/components/base/icons/src/vender/line/files' import Loading from '@/app/components/base/loading' import ArtifactsTree from '@/app/components/workflow/skill/file-tree/artifacts/artifacts-tree' import ReadOnlyFilePreview from '@/app/components/workflow/skill/viewer/read-only-file-preview' import { useDocLink } from '@/context/i18n' import { useDownloadSandboxFile, useSandboxFileDownloadUrl, useSandboxFilesTree } from '@/service/use-sandbox-file' import { cn } from '@/utils/classnames' import { downloadUrl } from '@/utils/download' import { useStore } from '../store' import { WorkflowRunningStatus } from '../types' import InspectLayout from './inspect-layout' import SplitPanel from './split-panel' const fileSystemArtifactsLocalizedPathMap = { 'zh-Hans': '/use-dify/build/file-system#产物' as DocPathWithoutLang, 'zh_Hans': '/use-dify/build/file-system#产物' as DocPathWithoutLang, 'ja-JP': '/use-dify/build/file-system#アーティファクト' as DocPathWithoutLang, 'ja_JP': '/use-dify/build/file-system#アーティファクト' as DocPathWithoutLang, } const ArtifactsEmpty = ({ description }: { description: string }) => { const { t } = useTranslation('workflow') const docLink = useDocLink() return (
{t('debug.variableInspect.tabArtifacts.emptyTitle')}
{description}
{t('debug.variableInspect.tabArtifacts.emptyLink')}
) } const formatFileSize = (bytes: number | null): string => { if (bytes === null || bytes === 0) return '0 B' const units = ['B', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(1024)) return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}` } const ArtifactsTab = (headerProps: InspectHeaderProps) => { const { t } = useTranslation('workflow') const appId = useStore(s => s.appId) const isWorkflowRunning = useStore( s => s.workflowRunningData?.result?.status === WorkflowRunningStatus.Running, ) const isResponding = useStore(s => s.isResponding) const { data: treeData, flatData, hasFiles, isLoading } = useSandboxFilesTree(appId, { enabled: !!appId, refetchInterval: (isWorkflowRunning || isResponding) ? 5000 : false, }) const { mutateAsync: fetchDownloadUrl, isPending: isDownloading } = useDownloadSandboxFile(appId) const [selectedFile, setSelectedFile] = useState(null) const selectedFilePath = useMemo(() => { if (!selectedFile) return undefined const selectedExists = flatData?.some( node => !node.is_dir && node.path === selectedFile.path, ) ?? false return selectedExists ? selectedFile.path : undefined }, [flatData, selectedFile]) const { data: downloadUrlData, isLoading: isDownloadUrlLoading } = useSandboxFileDownloadUrl( appId, selectedFilePath, { retry: false }, ) const handleFileSelect = useCallback((node: SandboxFileTreeNode) => { if (node.node_type === 'file') setSelectedFile(node) }, []) const handleTreeDownload = useCallback(async (node: SandboxFileTreeNode) => { try { const ticket = await fetchDownloadUrl(node.path) downloadUrl({ url: ticket.download_url, fileName: node.name }) } catch (error) { console.error('Download failed:', error) } }, [fetchDownloadUrl]) const handleSelectedFileDownload = useCallback(() => { if (downloadUrlData?.download_url && selectedFile) downloadUrl({ url: downloadUrlData.download_url, fileName: selectedFile.name }) }, [downloadUrlData, selectedFile]) if (isLoading) { return (
) } if (!hasFiles) { return (
) } const file = selectedFilePath ? selectedFile : null const parts = file?.path.split('/') ?? [] let cumPath = '' const pathSegments = parts.map((part, i) => { cumPath += (cumPath ? '/' : '') + part return { part, key: cumPath, isFirst: i === 0, isLast: i === parts.length - 1 } }) return ( )} > {({ isNarrow, onOpenMenu, onClose: handleClose }) => ( <>
{isNarrow && ( )} {file && ( <>
{pathSegments!.map(seg => ( {!seg.isFirst && /} {seg.part} ))}
{formatFileSize(file.size)}
)}
{file ? (
{isDownloadUrlLoading ?
: downloadUrlData?.download_url ? ( ) : (

{t('debug.variableInspect.tabArtifacts.previewNotAvailable')}

)}
) : (
)}
)}
) } export default ArtifactsTab