import React, { useState, useRef } from "react"; import { uploadSchemaDB } from "../api"; export default function SchemaUploadModal({ onClose, onUploadSuccess, addToast }) { const [sqlFile, setSqlFile] = useState(null); const [dbFile, setDbFile] = useState(null); const [erdImage, setErdImage] = useState(null); const [erdPreview, setErdPreview] = useState(null); const [isUploading, setIsUploading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); // Parse details const [parsedTablesCount, setParsedTablesCount] = useState(0); const [parsedKeysCount, setParsedKeysCount] = useState(0); const sqlInputRef = useRef(null); const dbInputRef = useRef(null); const erdInputRef = useRef(null); const handleSqlFileChange = (e) => { const file = e.target.files[0]; if (!file) return; if (!file.name.endsWith(".sql")) { addToast("Schema file must be a .sql file", "error"); return; } setSqlFile(file); // Read and parse sql file with client-side regex const reader = new FileReader(); reader.onload = (evt) => { const content = evt.target.result; // Simple table match const tablesMatch = content.match(/CREATE\s+TABLE\s+\w+/gi) || []; // Simple foreign key match const keysMatch = content.match(/FOREIGN\s+KEY/gi) || []; setParsedTablesCount(tablesMatch.length); setParsedKeysCount(keysMatch.length); }; reader.readAsText(file); }; const handleErdFileChange = (e) => { const file = e.target.files[0]; if (!file) return; const ext = file.name.substring(file.name.lastIndexOf(".")).toLowerCase(); if (![".png", ".jpg", ".jpeg", ".pdf"].includes(ext)) { addToast("ERD image must be a .png, .jpg, .jpeg, or .pdf file.", "error"); return; } setErdImage(file); if (ext !== ".pdf") { setErdPreview(URL.createObjectURL(file)); } else { setErdPreview(null); } }; const handleDbFileChange = (e) => { const file = e.target.files[0]; if (!file) return; if (!file.name.endsWith(".db") && !file.name.endsWith(".sqlite")) { addToast("Database file must be a .db or .sqlite file", "error"); return; } setDbFile(file); }; const handleUploadSubmit = async (e) => { e.preventDefault(); if (!dbFile) { setError("Database file (.db) is required."); addToast("Database file (.db) is required.", "error"); return; } if (!sqlFile && !erdImage) { setError("Please provide either a .sql schema file or an ERD diagram image"); addToast("Please provide either a .sql schema file or an ERD diagram image", "error"); return; } setIsUploading(true); setError(null); setSuccess(false); try { console.log("[SchemaUploadModal] Submitting files to server..."); const result = await uploadSchemaDB(sqlFile, erdImage); setSuccess(true); setIsUploading(false); addToast("Relational database uploaded successfully!", "success"); // Auto-dismiss after 2 seconds setTimeout(() => { if (onUploadSuccess) { onUploadSuccess(result); } onClose(); }, 2000); } catch (err) { console.error("[SchemaUploadModal] Upload failed:", err); setError(err.message || "Failed to upload files."); setIsUploading(false); addToast("Upload failed: " + err.message, "error"); } }; return (
{/* Close Button */}

🔗 Upload Relational Database

Provide a database file (.db) along with a schema DDL (.sql) or an ERD diagram image to start querying.

{/* File Selection Zones Stacked Vertically */}
{/* Zone 1 — Schema File (.sql) — OPTIONAL */}
Schema File (.sql) OPTIONAL
sqlInputRef.current && sqlInputRef.current.click()} className="drag-drop-zone" style={{ padding: "16px 12px", borderRadius: "8px", borderColor: sqlFile ? "var(--success-color)" : "var(--border-color)", borderStyle: "dashed", borderWidth: "2px", cursor: "pointer", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", backgroundColor: "rgba(255, 255, 255, 0.02)" }} > 📄 {sqlFile ? sqlFile.name : "Schema (.sql)"} (optional if ERD image provided)
{/* Zone 2 — ERD Diagram Image — OPTIONAL */}
ERD Diagram Image OPTIONAL
erdInputRef.current && erdInputRef.current.click()} className="drag-drop-zone" style={{ padding: "16px 12px", borderRadius: "8px", borderColor: erdImage ? "var(--success-color)" : "var(--border-color)", borderStyle: "dashed", borderWidth: "2px", cursor: "pointer", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", backgroundColor: "rgba(255, 255, 255, 0.02)" }} > 🖼️ {erdImage ? erdImage.name : "ERD Diagram Image"} Upload your ERD diagram — we'll extract relationships, cardinality (1:1, 1:N, N:M), and participation constraints automatically {erdImage && (
{erdPreview && ( ERD Thumbnail )} 🤖 AI will analyze this image
)}
{/* Zone 3 — Database File (.db) — REQUIRED */}
Database File (.db) REQUIRED
dbInputRef.current && dbInputRef.current.click()} className="drag-drop-zone" style={{ padding: "16px 12px", borderRadius: "8px", borderColor: dbFile ? "var(--success-color)" : "var(--border-color)", borderStyle: "dashed", borderWidth: "2px", cursor: "pointer", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", backgroundColor: "rgba(255, 255, 255, 0.02)" }} > 🗃️ {dbFile ? dbFile.name : "Database (.db)"} SQLite database containing tables data
{/* Schema Preview Details Section */} {(sqlFile || erdImage) && (
{sqlFile && !erdImage && ( <>
✓ Detected {parsedTablesCount} tables and {parsedKeysCount} relationships
{parsedKeysCount === 0 && (
⚠️ No FOREIGN KEY constraints detected — schema mode works best with related tables.
)} )} {erdImage && !sqlFile && (
🤖 Schema will be extracted from ERD image using AI vision
)} {sqlFile && erdImage && (
✓ Schema from .sql file will be enriched with ERD relationship details
)}
)} {/* Success Summary Info */} {success && (
✓ Database loaded. Transitioning workspace...
)} {/* Error Details */} {error && (
Error: {error}
)} {/* Submit Actions */}
); }