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 (
)
}
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