"use client"; import { useState, useEffect, useCallback } from "react"; import { HugeiconsIcon } from "@hugeicons/react"; import { RefreshIcon, Search01Icon, SourceCodeIcon, CodeIcon, File01Icon } from "@hugeicons/core-free-icons"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import type { FileTreeNode } from "@/types"; import { FileTree as AIFileTree, FileTreeFolder, FileTreeFile, } from "@/components/ai-elements/file-tree"; import { useTranslation } from "@/hooks/useTranslation"; import type { ReactNode } from "react"; interface FileTreeProps { workingDirectory: string; onFileSelect: (path: string) => void; onFileAdd?: (path: string) => void; } function getFileIcon(extension?: string): ReactNode { switch (extension) { case "ts": case "tsx": case "js": case "jsx": case "py": case "rb": case "rs": case "go": case "java": case "c": case "cpp": case "h": case "hpp": case "cs": case "swift": case "kt": case "dart": case "lua": case "php": case "zig": return ; case "json": case "yaml": case "yml": case "toml": return ; case "md": case "mdx": case "txt": case "csv": return ; default: return ; } } function containsMatch(node: FileTreeNode, query: string): boolean { const q = query.toLowerCase(); if (node.name.toLowerCase().includes(q)) return true; if (node.children) { return node.children.some((child) => containsMatch(child, q)); } return false; } function filterTree(nodes: FileTreeNode[], query: string): FileTreeNode[] { if (!query) return nodes; return nodes .filter((node) => containsMatch(node, query)) .map((node) => ({ ...node, children: node.children ? filterTree(node.children, query) : undefined, })); } function RenderTreeNodes({ nodes, searchQuery }: { nodes: FileTreeNode[]; searchQuery: string }) { const filtered = searchQuery ? filterTree(nodes, searchQuery) : nodes; return ( <> {filtered.map((node) => { if (node.type === "directory") { return ( {node.children && ( )} ); } return ( ); })} ); } export function FileTree({ workingDirectory, onFileSelect, onFileAdd }: FileTreeProps) { const [tree, setTree] = useState([]); const [loading, setLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const { t } = useTranslation(); const fetchTree = useCallback(async () => { if (!workingDirectory) { setTree([]); return; } setLoading(true); try { const res = await fetch( `/api/files?dir=${encodeURIComponent(workingDirectory)}&baseDir=${encodeURIComponent(workingDirectory)}&depth=4&_t=${Date.now()}` ); if (res.ok) { const data = await res.json(); setTree(data.tree || []); } else { setTree([]); } } catch { setTree([]); } finally { setLoading(false); } }, [workingDirectory]); useEffect(() => { fetchTree(); }, [fetchTree]); // Auto-refresh when AI finishes streaming useEffect(() => { const handler = () => fetchTree(); window.addEventListener('refresh-file-tree', handler); return () => window.removeEventListener('refresh-file-tree', handler); }, [fetchTree]); // Default to all directories collapsed const defaultExpanded = new Set(); return (
{/* Search + Refresh */}
setSearchQuery(e.target.value)} className="h-7 pl-7 text-xs" />
{/* Tree */}
{loading && tree.length === 0 ? (
) : tree.length === 0 ? (

{workingDirectory ? t('fileTree.noFiles') : t('fileTree.selectFolder')}

) : ( )}
); }