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
{/* 📦 Preview Dialog */}
{/* 🎯 Context Menu for Download */}
{/* ✅ Success Snackbar */}
setShowSnackbar(false)}
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
>
setShowSnackbar(false)}>
✅ ZIP downloaded successfully!
);
};
export default FileUploadForm;