'use client'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Trash2, Download, Loader2, FolderOpen, Folder, FileText, Upload, FolderPlus, ChevronRight, Home, RefreshCw, FileImage, FileCode, FileArchive, FileSpreadsheet, } from 'lucide-react'; import { browseWorkspace, getWorkspaceDownloadUrl, uploadToWorkspace, deleteWorkspacePath, createWorkspaceDir, getAccessToken, } from '@/lib/api'; import type { WorkspaceItem } from '@/lib/api'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; import { pickAppText } from '@/lib/i18n/core'; import { useAppI18n } from '@/lib/i18n/provider'; export default function FilesPage() { const { locale } = useAppI18n(); const [items, setItems] = useState([]); const [currentPath, setCurrentPath] = useState(''); const [loading, setLoading] = useState(true); const [uploading, setUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [showMkdir, setShowMkdir] = useState(false); const [newDirName, setNewDirName] = useState(''); const fileInputRef = useRef(null); const mkdirInputRef = useRef(null); const load = useCallback(async (path: string = currentPath) => { try { setLoading(true); const data = await browseWorkspace(path); setItems(data.items); setCurrentPath(data.path); } catch { // ignore } finally { setLoading(false); } }, [currentPath]); useEffect(() => { load(''); }, []); // eslint-disable-line react-hooks/exhaustive-deps const navigateTo = (path: string) => { load(path); }; const handleDelete = async (item: WorkspaceItem) => { const label = item.type === 'directory' ? pickAppText(locale, '文件夹', 'folder') : pickAppText(locale, '文件', 'file'); if (!confirm(pickAppText( locale, `确定删除${label} "${item.name}"?${item.type === 'directory' ? '(包含所有子文件)' : ''}`, `Delete ${label} "${item.name}"?${item.type === 'directory' ? ' (including all nested files)' : ''}` ))) { return; } try { await deleteWorkspacePath(item.path); setItems((prev) => prev.filter((i) => i.path !== item.path)); } catch { // ignore } }; const handleDownload = async (item: WorkspaceItem) => { const url = getWorkspaceDownloadUrl(item.path); const token = getAccessToken(); const headers: Record = {}; if (token) headers['Authorization'] = `Bearer ${token}`; try { const res = await fetch(url, { headers }); const blob = await res.blob(); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = item.name; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(a.href); } catch { // ignore } }; const handleUpload = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0) return; setUploading(true); setUploadProgress(0); try { for (let i = 0; i < files.length; i++) { await uploadToWorkspace(files[i], currentPath, (pct) => { setUploadProgress(Math.round((i / files.length) * 100 + pct / files.length)); }); } await load(); } catch { // ignore } finally { setUploading(false); setUploadProgress(0); if (fileInputRef.current) fileInputRef.current.value = ''; } }; const handleCreateDir = async () => { const name = newDirName.trim(); if (!name) return; try { const dirPath = currentPath ? `${currentPath}/${name}` : name; await createWorkspaceDir(dirPath); setShowMkdir(false); setNewDirName(''); await load(); } catch { // ignore } }; // Build breadcrumbs const breadcrumbs = currentPath ? currentPath.split('/') : []; const formatSize = (bytes: number | null) => { if (bytes === null || bytes === undefined) return ''; if (bytes > 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`; if (bytes > 1024) return `${(bytes / 1024).toFixed(0)} KB`; return `${bytes} B`; }; const formatDate = (iso: string) => { try { return new Date(iso).toLocaleString(locale, { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); } catch { return ''; } }; return (
{/* Header */}

{pickAppText(locale, '文件管理', 'Files')}

{/* Breadcrumbs */}
{breadcrumbs.map((segment, idx) => { const path = breadcrumbs.slice(0, idx + 1).join('/'); const isLast = idx === breadcrumbs.length - 1; return ( ); })}
{/* New directory input */} {showMkdir && (
setNewDirName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleCreateDir(); if (e.key === 'Escape') { setShowMkdir(false); setNewDirName(''); } }} placeholder={pickAppText(locale, '文件夹名称', 'Folder name')} className="flex-1 px-3 py-1.5 text-sm border border-border rounded-md bg-background focus:outline-none focus:ring-1 focus:ring-ring" autoFocus />
)} {/* File list */} {loading && items.length === 0 ? (
) : items.length === 0 ? (

{pickAppText(locale, '空文件夹', 'Empty folder')}

{pickAppText(locale, '点击上方"上传"或"新建文件夹"按钮开始使用', 'Use "Upload" or "New folder" above to get started')}

) : (
{items.map((item) => (
{/* Icon */}
{item.type === 'directory' ? ( ) : ( )}
{/* Name - clickable for directories */}
{item.type === 'directory' ? ( ) : (

{item.name}

)}

{item.type === 'file' && formatSize(item.size)} {item.modified && ( <> {item.type === 'file' && ' · '} {formatDate(item.modified)} )}

{/* Actions */}
{item.type === 'file' && ( )}
))}
)}
); } function FileIcon({ name, contentType }: { name: string; contentType?: string }) { const ext = name.split('.').pop()?.toLowerCase() || ''; const ct = contentType || ''; if (ct.startsWith('image/') || ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'].includes(ext)) { return ; } if ( ['js', 'ts', 'tsx', 'jsx', 'py', 'rb', 'go', 'rs', 'java', 'c', 'cpp', 'h', 'css', 'html', 'json', 'yaml', 'yml', 'toml', 'md', 'sh'].includes(ext) ) { return ; } if (['zip', 'tar', 'gz', 'bz2', 'rar', '7z', 'xz'].includes(ext)) { return ; } if (['csv', 'xls', 'xlsx', 'tsv'].includes(ext)) { return ; } return ; }