import React, { useState, useEffect, useRef } from "react"; import axios from "axios"; import { Box, Button, CircularProgress, InputLabel, MenuItem, Select, Typography, FormControl, Grid, Paper, Snackbar, Alert, Dialog, DialogTitle, DialogContent, DialogActions, useMediaQuery, Menu, MenuItem as ContextMenuItem, } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import CloudUploadIcon from "@mui/icons-material/CloudUpload"; import DownloadIcon from "@mui/icons-material/Download"; import FolderIcon from "@mui/icons-material/Folder"; import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile"; import { SimpleTreeView, TreeItem } from "@mui/x-tree-view"; import Prism from './prism-setup'; import { detectLanguage } from './utils/languageDetect'; const FileUploadForm = () => { const [file, setFile] = useState(null); const [frontend, setFrontend] = useState("React"); const [backend, setBackend] = useState("Flask"); const [database, setDatabase] = useState("PostgreSQL"); const [loading, setLoading] = useState(false); const [showSnackbar, setShowSnackbar] = useState(false); const [showModal, setShowModal] = useState(false); const [zipBlob, setZipBlob] = useState(null); const [fileListInfo, setFileListInfo] = useState({}); const [selectedFile, setSelectedFile] = useState(null); const [expandedIds, setExpandedIds] = useState([]); // ✅ array, not Set const [contextAnchor, setContextAnchor] = useState(null); const [contextFile, setContextFile] = useState(null); const codeRef = useRef(null); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const buildTree = (files) => { const root = {}; for (const path in files) { const parts = path.split("/"); let current = root; for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (!current[part]) { current[part] = i === parts.length - 1 ? files[path] : {}; } current = current[part]; } } return root; }; const renderTree = (node, path = "") => { if (!node || typeof node !== "object") return null; return Object.entries(node).map(([key, value]) => { const fullPath = path ? `${path}/${key}` : key; const itemId = fullPath.replaceAll("/", "-"); const isFile = value && typeof value.preview !== "undefined"; return ( handleContextMenu(e, fullPath, value)} > {isFile ? : } {key} {isFile ? ` (${(value.size / 1024).toFixed(1)} KB)` : ""} } onClick={() => { if (isFile) { setSelectedFile({ name: fullPath, content: value.preview }); Prism.highlightAll(); } else { setExpandedIds((prev) => prev.includes(itemId) ? prev.filter((id) => id !== itemId) // collapse : [...prev, itemId] // expand ); } }} > {!isFile && renderTree(value, fullPath)} ); }); }; const handleDownload = async (e) => { e.preventDefault(); if (!file) return alert("Please select a requirement document!"); const formData = new FormData(); formData.append("file", file); formData.append("frontend", frontend); formData.append("backend", backend); formData.append("database", database); try { setLoading(true); const response = await axios.post( "https://fredericksundeep-aichatmatedev.hf.space/chat-stream-doc", formData, { responseType: "blob" } ); const blob = new Blob([response.data], { type: "application/zip" }); setZipBlob(blob); const JSZip = (await import("jszip")).default; const zip = await JSZip.loadAsync(blob); const fileInfo = {}; await Promise.all( Object.entries(zip.files).map(async ([name, file]) => { if (!file.dir) { const content = await file.async("string"); fileInfo[name] = { size: file._data.uncompressedSize, preview: content.slice(0, 1000), }; } }) ); setFileListInfo(fileInfo); setExpandedIds([]); // reset tree state setShowModal(true); } catch (error) { console.error("Error:", error); alert("Failed to generate project."); } finally { setLoading(false); } }; const handleZipDownload = () => { const url = window.URL.createObjectURL(zipBlob); const a = document.createElement("a"); a.href = url; a.download = "generated_project.zip"; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); setShowSnackbar(true); setShowModal(false); }; const handleContextMenu = (event, filename, value) => { event.preventDefault(); if (typeof value.preview === "undefined") return; // not a file setContextFile({ name: filename, content: value.preview }); setContextAnchor(event.currentTarget); }; const handleDownloadFile = () => { if (!contextFile) return; const blob = new Blob([contextFile.content], { type: "text/plain" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = contextFile.name.split("/").pop(); a.click(); setContextAnchor(null); }; // ✅ Expand/collapse logic const expandAll = () => { const allPaths = Object.keys(fileListInfo); // files only const allIds = new Set(); allPaths.forEach((filePath) => { const parts = filePath.split("/"); for (let i = 1; i <= parts.length; i++) { const partialPath = parts.slice(0, i).join("/"); allIds.add(partialPath.replaceAll("/", "-")); // folder or file } }); setExpandedIds(Array.from(allIds)); }; const collapseAll = () => { setExpandedIds([]); // ✅ empty array }; const { lang, code } = React.useMemo(() => { if (selectedFile && selectedFile.content) { const lines = selectedFile.content.split('\n'); const firstLine = lines[0].trim(); const hasLang = /^[a-zA-Z]+$/.test(firstLine); const lang = hasLang ? firstLine.toLowerCase() : detectLanguage(selectedFile.content) || 'markdown'; const code = hasLang ? lines.slice(1).join('\n') : selectedFile.content; return { lang, code }; } return { lang: 'markdown', code: '' }; }, [selectedFile]); useEffect(() => { if (codeRef.current) { Prism.highlightElement(codeRef.current); } }, [code, lang]); return ( 🧠 AI - Full-Stack Project Generator
{["Frontend", "Backend", "Database"].map((label, idx) => ( {label} ))}
{/* 📦 Preview Dialog */} setShowModal(false)} maxWidth="md" fullWidth> 📦 Generated Files { if (Array.isArray(newItems)) { setExpandedIds(newItems); // ✅ directly set array } else { console.warn("Unexpected expandedItems:", newItems); setExpandedIds([]); } }} onItemToggle={(itemId, isExpanded) => { setExpandedIds((prev) => isExpanded ? [...prev, itemId] : prev.filter((id) => id !== itemId) ); }} > {renderTree(buildTree(fileListInfo))} {selectedFile ? ( <> {selectedFile.name}
  
    {code}
  
) : ( Select a file to preview its content )}
{/* 🎯 Context Menu for Download */} setContextAnchor(null)} > 📥 Download File {/* ✅ Success Snackbar */} setShowSnackbar(false)} anchorOrigin={{ vertical: "bottom", horizontal: "center" }} > setShowSnackbar(false)}> ✅ ZIP downloaded successfully!
); }; export default FileUploadForm;