Spaces:
Running
Running
File size: 10,965 Bytes
56bf851 826a975 56bf851 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 |
import React, { useState, useRef } from "react";
import Papa from "papaparse";
import { ApiService } from "../utils/apiService";
import { config, debugLog } from "../utils/config";
const Step2 = ({
uploadedFile,
setUploadedFile,
s3Link,
setS3Link,
fileMetadata,
setFileMetadata,
stepNumber,
stepTitle,
stepIcon,
enabled = true,
// Add API key props for validation
apiKey,
isApiKeyValid,
}) => {
const [outputFormat, setOutputFormat] = useState("tabular");
const [isUploading, setIsUploading] = useState(false);
const [csvData, setCsvData] = useState(null);
const [dragOver, setDragOver] = useState(false);
const fileInputRef = useRef(null);
const handleFileUpload = async (file) => {
// Prevent action if step is disabled
if (!enabled) {
debugLog("File upload attempted but step is disabled");
return;
}
if (!file) {
alert("No file selected");
return;
}
if (!file.name.endsWith(".csv")) {
alert("Please upload a CSV file. Only CSV files are supported.");
return;
}
// Check file size against configured limit
const maxSizeBytes = config.maxFileSizeMB * 1024 * 1024;
if (file.size > maxSizeBytes) {
alert(
`File size (${(file.size / 1024 / 1024).toFixed(
2
)}MB) exceeds maximum allowed size of ${config.maxFileSizeMB}MB`
);
return;
}
// Check if file is empty
if (file.size === 0) {
alert(
"The selected file is empty. Please choose a valid CSV file with data."
);
return;
}
setUploadedFile(file);
debugLog("File selected for upload", {
name: file.name,
size: file.size,
type: file.type,
});
// Initialize file metadata with file size
setFileMetadata({
fileSizeBytes: file.size,
sourceFileRows: 0, // Will be updated after CSV parsing
});
// Parse CSV for preview
Papa.parse(file, {
complete: (results) => {
if (results.errors && results.errors.length > 0) {
debugLog("CSV parsing warnings", results.errors);
}
if (!results.data || results.data.length === 0) {
alert(
"The CSV file appears to be empty or invalid. Please check your file and try again."
);
setUploadedFile(null);
setFileMetadata({ fileSizeBytes: 0, sourceFileRows: 0 });
return;
}
setCsvData(results.data);
// Update file metadata with row count
setFileMetadata({
fileSizeBytes: file.size,
sourceFileRows: results.data.length,
});
debugLog("CSV parsed for preview", {
rows: results.data.length,
columns: results.data[0] ? Object.keys(results.data[0]).length : 0,
});
},
header: true,
skipEmptyLines: true,
error: (error) => {
debugLog("CSV parsing error", error);
alert("Error parsing CSV file. Please ensure it's a valid CSV format.");
setUploadedFile(null);
},
});
// Upload to S3 using ApiService
setIsUploading(true);
try {
debugLog("Starting file upload to S3");
// Use the ApiService with retry logic
const result = await ApiService.retryRequest(async () => {
return await ApiService.uploadFileToS3(file);
});
// Handle different response structures
const s3Url =
result.s3_link || result.link || result.publicUrl || result.url;
setS3Link(s3Url);
debugLog("File uploaded successfully", {
s3Link: s3Url,
result: result,
});
} catch (error) {
debugLog("File upload failed", error);
// More specific error messages
let errorMessage = "Failed to upload file. Please try again.";
if (error.message.includes("credentials")) {
errorMessage =
"AWS credentials are not configured properly. Please check your .env.local file and restart the application.";
} else if (error.message.includes("bucket")) {
errorMessage =
"Storage bucket configuration issue. Please check your S3 bucket name in .env.local file.";
} else if (error.message.includes("size")) {
errorMessage = `File size exceeds the maximum limit of ${config.maxFileSizeMB}MB.`;
} else if (
error.message.includes("network") ||
error.message.includes("fetch") ||
error.message.includes("CORS")
) {
errorMessage =
"Network/CORS error. This might be due to S3 bucket CORS configuration or network connectivity issues.";
} else if (error.message.includes("AccessDenied")) {
errorMessage =
"Access denied to S3 bucket. Please check your AWS permissions.";
}
alert(errorMessage);
setUploadedFile(null);
setCsvData(null);
} finally {
setIsUploading(false);
}
};
const handleDrop = (e) => {
// Prevent action if step is disabled
if (!enabled) return;
e.preventDefault();
setDragOver(false);
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFileUpload(files[0]);
}
};
const handleDragOver = (e) => {
e.preventDefault();
setDragOver(true);
};
const handleDragLeave = (e) => {
e.preventDefault();
setDragOver(false);
};
const renderDataPreview = () => {
if (!csvData || csvData.length === 0) return null;
// Show more rows for better preview (up to 20 rows)
const previewData = csvData.slice(0, 20);
const columns = Object.keys(previewData[0]);
return (
<div className="data-preview">
<h4>
π Data Preview ({csvData.length} rows total, showing first{" "}
{previewData.length})
</h4>
<div className="step2-data-table-container">
<table className="data-table-scrollable">
<thead>
<tr>
{columns.map((col, index) => (
<th key={index}>{col}</th>
))}
</tr>
</thead>
<tbody>
{previewData.map((row, index) => (
<tr key={index}>
{columns.map((col, colIndex) => (
<td key={colIndex} title={row[col]}>
{row[col]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
{csvData.length > 20 && (
<p className="preview-note">
Showing first 20 rows of {csvData.length} total rows
</p>
)}
</div>
);
};
return (
<div className="step-container fade-in">
<div className="step-header">
<h2>
<span className="step-number">{stepNumber}</span>
{stepIcon} {stepTitle}
</h2>
<p>
Choose your output format and upload a CSV file for analysis and
synthetic data generation.
</p>
</div>
<div className="step-body">
{!enabled && (
<div
className="status-message warning"
style={{ marginBottom: "1.5rem" }}
>
<div className="status-message-icon">β οΈ</div>
<div className="status-message-content">
<h4>API Key Required</h4>
<p>
Please complete Step 1 (API Key Validation) before uploading
files.
</p>
</div>
</div>
)}
<div className="form-group output-format-section">
<label>Output Format</label>
<div className="output-options">
<div
className={`output-option ${
outputFormat === "tabular" ? "selected" : ""
}`}
onClick={() => setOutputFormat("tabular")}
>
<h4>π Tabular</h4>
<p>CSV format with structured rows and columns</p>
</div>
<div className="output-option disabled" title="Coming Soon">
<h4>π JSONL (Coming Soon)</h4>
<p>JSON Lines format for advanced use cases</p>
</div>
</div>
</div>
<div className="form-group file-upload-section">
<label>π€ Upload Source File</label>
{uploadedFile && s3Link ? (
<div className="file-uploaded">
<div className="file-info">
<div className="file-icon">π</div>
<div className="file-details">
<h4>{uploadedFile.name}</h4>
<p>Successfully uploaded and ready for processing</p>
<p
style={{
fontSize: "0.75rem",
opacity: 0.8,
marginTop: "0.25rem",
}}
>
Size: {(uploadedFile.size / 1024 / 1024).toFixed(2)} MB
</p>
</div>
<button
className="btn btn-secondary"
onClick={() => {
setUploadedFile(null);
setS3Link("");
setCsvData(null);
setFileMetadata({ fileSizeBytes: 0, sourceFileRows: 0 });
}}
style={{ marginLeft: "auto" }}
>
π Change File
</button>
</div>
</div>
) : (
<div
className={`file-upload-area ${dragOver ? "drag-over" : ""}`}
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onClick={() => fileInputRef.current?.click()}
>
<input
ref={fileInputRef}
type="file"
accept=".csv"
onChange={(e) => handleFileUpload(e.target.files[0])}
/>
{isUploading ? (
<div>
<div className="spinner"></div>
<div className="file-upload-text">Uploading your file...</div>
<div className="file-upload-subtext">
Please wait while we process your data
</div>
</div>
) : (
<div>
<div className="file-upload-icon">π</div>
<div className="file-upload-text">
Drop your CSV file here
</div>
<div className="file-upload-subtext">
or click to browse β’ Max {config.maxFileSizeMB}MB β’ CSV
files only
</div>
</div>
)}
</div>
)}
</div>
{renderDataPreview()}
</div>
</div>
);
};
export default Step2;
|