'use client' import { useState, useEffect } from 'react' import { Folder, File, FolderOpen, RefreshCw, Code2, FileText, Image, Package } from 'lucide-react' interface FileItem { path: string size: number } interface WorkspaceData { workspace: string files: FileItem[] total: number } const getFileIcon = (filename: string) => { const ext = filename.split('.').pop()?.toLowerCase() if (['ts', 'tsx', 'js', 'jsx', 'py', 'go', 'rs'].includes(ext || '')) return Code2 if (['json', 'yaml', 'yml', 'toml'].includes(ext || '')) return Package if (['md', 'txt', 'rst'].includes(ext || '')) return FileText if (['png', 'jpg', 'svg', 'webp'].includes(ext || '')) return Image return File } const getFileColor = (filename: string) => { const ext = filename.split('.').pop()?.toLowerCase() if (['ts', 'tsx'].includes(ext || '')) return '#3b82f6' if (['py'].includes(ext || '')) return '#f59e0b' if (['js', 'jsx'].includes(ext || '')) return '#eab308' if (['go'].includes(ext || '')) return '#06b6d4' if (['rs'].includes(ext || '')) return '#f97316' if (['json', 'yaml', 'yml'].includes(ext || '')) return '#a78bfa' if (['md'].includes(ext || '')) return '#6b7280' return '#9ca3af' } export default function FileExplorer() { const [workspace, setWorkspace] = useState(null) const [loading, setLoading] = useState(false) const [selectedFile, setSelectedFile] = useState(null) const [expandedDirs, setExpandedDirs] = useState>(new Set()) const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000' const fetchWorkspace = async () => { setLoading(true) try { const resp = await fetch(`${apiUrl}/api/v1/files/workspace`) if (resp.ok) { const data = await resp.json() setWorkspace(data) } } catch (e) { console.error('Failed to fetch workspace', e) } finally { setLoading(false) } } useEffect(() => { fetchWorkspace() }, []) // Build tree structure from flat file list const buildTree = (files: FileItem[]) => { const tree: Record = {} files.forEach(({ path, size }) => { const parts = path.split('/') let current = tree parts.forEach((part, i) => { if (i === parts.length - 1) { current[part] = { _file: true, path, size } } else { if (!current[part]) current[part] = {} current = current[part] } }) }) return tree } const toggleDir = (path: string) => { setExpandedDirs(prev => { const next = new Set(prev) if (next.has(path)) next.delete(path) else next.add(path) return next }) } const renderTree = (node: Record, prefix: string = '', depth: number = 0) => { return Object.entries(node).map(([name, value]) => { const fullPath = prefix ? `${prefix}/${name}` : name const isFile = value?._file === true const Icon = isFile ? getFileIcon(name) : (expandedDirs.has(fullPath) ? FolderOpen : Folder) const color = isFile ? getFileColor(name) : '#f59e0b' return (
{ if (isFile) setSelectedFile(fullPath) else toggleDir(fullPath) }} onMouseEnter={e => { if (selectedFile !== fullPath) (e.currentTarget as HTMLElement).style.background = 'rgba(255,255,255,0.04)' }} onMouseLeave={e => { if (selectedFile !== fullPath) (e.currentTarget as HTMLElement).style.background = 'transparent' }} > {name} {isFile && value.size && ( {value.size > 1024 ? `${(value.size / 1024).toFixed(1)}k` : `${value.size}b`} )}
{!isFile && expandedDirs.has(fullPath) && renderTree(value, fullPath, depth + 1)}
) }) } const tree = workspace ? buildTree(workspace.files) : {} return (
{/* Header */}
File Explorer {workspace && ( {workspace.total} files )}
{/* File Tree */}
{loading ? (
) : workspace && workspace.files.length > 0 ? (
{renderTree(tree)}
) : (

Workspace empty

Ask God Agent to create a project

)}
{/* Selected file info */} {selectedFile && (

📄 {selectedFile}

)}
) }