import { Button } from '@vertesia/ui/core'; import { useUserSession } from '@vertesia/ui/session'; import { ChevronDownIcon, ChevronRightIcon, FileIcon, FolderIcon, FolderOpenIcon, Loader2Icon, PackageIcon, RefreshCwIcon, } from 'lucide-react'; import React, { useCallback, useState } from 'react'; import { useUITranslation } from '../../../i18n/index.js'; import { useArtifacts, type ArtifactTreeNode } from './hooks/useArtifacts.js'; // --------------------------------------------------------------------------- // Tree node component // --------------------------------------------------------------------------- interface TreeNodeProps { node: ArtifactTreeNode; depth: number; runId: string; onDownload: (relativePath: string) => void; downloadingPath: string | null; } //** Convert a raw directory segment (e.g. "out_files") into a readable label ("Out Files"). */ function formatDirectoryLabel(name: string): string { return name .replace(/[_-]/g, ' ') .replace(/\b\w/g, (c) => c.toUpperCase()); } function TreeNode({ node, depth, runId, onDownload, downloadingPath }: TreeNodeProps) { const [expanded, setExpanded] = useState(true); if (node.isDirectory) { return (
{expanded && node.children.map((child) => ( ))}
); } const isDownloading = downloadingPath === node.path; return ( ); } // --------------------------------------------------------------------------- // Main tab component // --------------------------------------------------------------------------- interface ArtifactsTabProps { runId?: string; refreshKey?: number; } function ArtifactsTabComponent({ runId, refreshKey = 0 }: ArtifactsTabProps) { const { t } = useUITranslation(); const { client } = useUserSession(); const { tree, flatFiles, isLoading, error, refresh } = useArtifacts(client, runId, refreshKey); const [downloadingPath, setDownloadingPath] = useState(null); const handleDownload = useCallback(async (relativePath: string) => { if (!runId) return; setDownloadingPath(relativePath); // Open the tab synchronously (before the await) so the browser treats it as // a direct user action and doesn't block it as a popup. const newTab = window.open('', '_blank'); try { const { url } = await client.files.getArtifactDownloadUrl(runId, relativePath, 'attachment'); if (newTab) { newTab.location.href = url; } } catch (err) { console.error('Failed to get artifact download URL:', err); newTab?.close(); } finally { setDownloadingPath(null); } }, [client, runId]); if (!runId) { return (
No run selected
); } if (isLoading && flatFiles.length === 0) { return (
{t('agent.loadingArtifacts')}
); } if (error) { return (
{error}
); } if (flatFiles.length === 0) { return (
{t('agent.noArtifactsYet')}
); } return (
{/* Top bar */}
{flatFiles.length} file{flatFiles.length !== 1 ? 's' : ''}
{/* Tree */}
{tree.map((node) => ( ))}
); } export const ArtifactsTab = React.memo(ArtifactsTabComponent);