Khushal-kreeda
fix: ui enhancements
826a975
import React, { useState } from "react";
import { ApiService } from "../utils/apiService";
import { debugLog } from "../utils/config";
import DataViewer from "./DataViewer";
const Step4 = ({
apiKey,
s3Link,
config: generationConfig,
fileMetadata,
generatedDataLink,
setGeneratedDataLink,
stepNumber,
stepTitle,
stepIcon,
enabled = true,
}) => {
const [isGenerating, setIsGenerating] = useState(false);
const [generationStatus, setGenerationStatus] = useState("");
const [generationProgress, setGenerationProgress] = useState(0);
const [hasError, setHasError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const generateSyntheticData = async () => {
// Prevent action if step is disabled
if (!enabled) {
debugLog("Generation attempted but step is disabled");
return;
}
setIsGenerating(true);
setGenerationStatus("Initializing generation...");
setGenerationProgress(0);
setHasError(false);
setErrorMessage("");
try {
debugLog("Starting synthetic data generation", {
s3Link,
config: generationConfig,
fileMetadata,
apiKeyPrefix: apiKey.substring(0, 8) + "...",
});
// Use the ApiService with retry logic
const result = await ApiService.retryRequest(async () => {
// Update progress during API call
setGenerationStatus("Sending request to AI model...");
setGenerationProgress(10);
return await ApiService.generateSyntheticData(apiKey, s3Link, {
...generationConfig,
fileSizeBytes: fileMetadata?.fileSizeBytes || 0,
sourceFileRows: fileMetadata?.sourceFileRows || 0,
});
});
// If the API returns progress updates, handle them
if (result.jobId) {
// For now, simulate progress since polling is not implemented
await simulateProgress();
// In a real implementation, you would poll for job status here
setGeneratedDataLink(
result.data?.fileUrl ||
result.fileUrl ||
result.download_link ||
result.link ||
result.s3_url
);
} else {
// Simulate progress for immediate results
await simulateProgress();
// Handle the specific response format: { "status": "success", "data": { "fileUrl": "..." } }
const dataLink =
result.data?.fileUrl ||
result.fileUrl ||
result.s3_url ||
result.download_link ||
result.link;
if (!dataLink) {
throw new Error(
"No download link received from the API. The generation may have failed on the server side."
);
}
setGeneratedDataLink(dataLink);
}
setGenerationStatus("Generation completed successfully!");
setGenerationProgress(100);
debugLog("Synthetic data generation completed", {
downloadLink:
result.data?.fileUrl ||
result.fileUrl ||
result.s3_url ||
result.download_link ||
result.link,
status: result.status,
message: result.message,
fullResult: result,
});
} catch (error) {
debugLog("Synthetic data generation failed", error);
setHasError(true);
setGenerationProgress(0);
// Create a user-friendly error message
let friendlyErrorMessage = "An error occurred during generation.";
if (error.message.includes("403") || error.message.includes("401")) {
friendlyErrorMessage =
"Authentication failed. Please check your API key and try again.";
} else if (error.message.includes("404")) {
friendlyErrorMessage =
"Generation service not found. Please try again later.";
} else if (error.message.includes("500")) {
friendlyErrorMessage =
"Server error occurred. The service may be temporarily unavailable.";
} else if (
error.message.includes("timeout") ||
error.message.includes("TimeoutError")
) {
friendlyErrorMessage =
"Request timed out. The generation process may take longer than expected. Please try again.";
} else if (
error.message.includes("Network") ||
error.message.includes("fetch")
) {
friendlyErrorMessage =
"Network error. Please check your internet connection and try again.";
} else if (error.message.includes("No download link")) {
friendlyErrorMessage =
"Generation completed but no download link was provided. Please try generating again.";
} else if (error.message) {
friendlyErrorMessage = error.message;
}
setErrorMessage(friendlyErrorMessage);
setGenerationStatus(`❌ Generation failed: ${friendlyErrorMessage}`);
} finally {
setIsGenerating(false);
}
};
const simulateProgress = async () => {
const steps = [
{ progress: 25, message: "Analyzing data structure..." },
{ progress: 50, message: "Training AI model..." },
{ progress: 75, message: "Generating synthetic data..." },
{ progress: 90, message: "Finalizing output..." },
];
for (const step of steps) {
setGenerationProgress(step.progress);
setGenerationStatus(step.message);
await new Promise((resolve) => setTimeout(resolve, 1500));
}
};
const handleDownload = () => {
if (generatedDataLink) {
debugLog("Downloading generated data", { link: generatedDataLink });
// Open in new tab so user can see if download works
const newWindow = window.open(generatedDataLink, "_blank");
// If popup blocked, provide fallback
if (!newWindow) {
// Fallback: create a temporary download link
const link = document.createElement("a");
link.href = generatedDataLink;
link.download = `synthetic_data_${Date.now()}.csv`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
const handleCopyLink = async () => {
if (generatedDataLink) {
try {
await navigator.clipboard.writeText(generatedDataLink);
// You could add a toast notification here
debugLog("Link copied to clipboard");
} catch (error) {
debugLog("Failed to copy link:", error);
// Fallback for older browsers
const textArea = document.createElement("textarea");
textArea.value = generatedDataLink;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
}
}
};
const isReadyForGeneration =
apiKey &&
s3Link &&
generationConfig.targetColumn &&
generationConfig.numRows;
return (
<div className="step-container fade-in">
<div className="step-header">
<h2>
<span className="step-number">{stepNumber}</span>
{stepIcon} {stepTitle}
</h2>
<p>Generate high-quality synthetic data based on your configuration</p>
</div>
<div className="step-body">
{!generatedDataLink && !isGenerating && !hasError && (
<div className="generate-section">
<div
className={`status-message ${
isReadyForGeneration ? "info" : "warning"
}`}
>
<div className="status-message-icon">
{isReadyForGeneration ? "βœ…" : "⚠️"}
</div>
<div className="status-message-content">
<h4>
{isReadyForGeneration
? "Ready for Generation"
: "Configuration Required"}
</h4>
{!isReadyForGeneration && (
<p
style={{
marginTop: "0.5rem",
fontSize: "0.875rem",
opacity: 0.9,
}}
>
Please complete all previous steps before generating data.
</p>
)}
<div className="status-summary">
<div className="status-row">
<span className="status-label">πŸ”‘ API Key:</span>
<span
className={`status-badge ${
apiKey ? "success" : "warning"
}`}
>
{apiKey ? "βœ“ Valid" : "⚠ Required"}
</span>
</div>
<div className="status-row">
<span className="status-label">πŸ“Š Data:</span>
<span
className={`status-badge ${
s3Link ? "success" : "warning"
}`}
>
{s3Link ? "βœ“ Uploaded" : "⚠ Required"}
</span>
</div>
<div className="status-row">
<span className="status-label">🎯 Target:</span>
<span
className={`status-badge ${
generationConfig.targetColumn ? "success" : "warning"
}`}
>
{generationConfig.targetColumn
? `βœ“ ${generationConfig.targetColumn}`
: "⚠ Not set"}
</span>
</div>
<div className="status-row">
<span className="status-label">πŸ“₯ Source:</span>
<span className="status-badge info">
{fileMetadata?.sourceFileRows || 0} rows
</span>
</div>
<div className="status-row">
<span className="status-label">πŸ“€ Generate:</span>
<span className="status-badge info">
{generationConfig.numRows} rows
</span>
</div>
</div>
</div>
</div>
<button
className="btn btn-primary generate-btn"
onClick={generateSyntheticData}
disabled={!isReadyForGeneration || !enabled}
style={{ marginTop: "1.5rem" }}
>
🎯 Generate Synthetic Data
</button>
</div>
)}
{isGenerating && (
<div className="generation-progress">
<div className="spinner"></div>
<div className="file-upload-text" style={{ marginTop: "1rem" }}>
{generationStatus}
</div>
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${generationProgress}%` }}
></div>
</div>
<div
className="file-upload-subtext"
style={{ marginTop: "0.75rem" }}
>
{generationProgress}% Complete β€’ This may take a few minutes
</div>
</div>
)}
{hasError && !isGenerating && (
<div className="error-section">
<div className="status-message error">
<div className="status-message-icon">❌</div>
<div className="status-message-content">
<h4>Generation Failed</h4>
<p>There was an error generating your synthetic data.</p>
<div className="error-details">
<strong>Error:</strong> {errorMessage}
</div>
<div
className="error-help"
style={{ marginTop: "0.75rem", fontSize: "0.875rem" }}
>
<strong>What you can try:</strong>
<ul
style={{
marginTop: "0.5rem",
paddingLeft: "1.5rem",
textAlign: "left",
}}
>
<li>Check your internet connection and try again</li>
<li>
Verify your API key is valid and has sufficient credits
</li>
<li>Try reducing the number of rows to generate</li>
<li>Contact support if the problem persists</li>
</ul>
</div>
</div>
</div>
<div style={{ marginTop: "1.5rem", textAlign: "center" }}>
<button
className="btn btn-primary"
onClick={() => {
setHasError(false);
setErrorMessage("");
setGenerationStatus("");
setGenerationProgress(0);
}}
style={{ marginRight: "1rem" }}
>
πŸ”„ Try Again
</button>
<button
className="btn btn-secondary"
onClick={() => {
// Reset to initial state
setHasError(false);
setErrorMessage("");
setGenerationStatus("");
setGenerationProgress(0);
setGeneratedDataLink("");
}}
>
πŸ”™ Start Over
</button>
</div>
</div>
)}
{generatedDataLink && !isGenerating && (
<div className="results-section">
<div
className="status-message success"
style={{ marginBottom: "1.5rem" }}
>
<div className="status-message-icon">πŸŽ‰</div>
<div className="status-message-content">
<h4>Generation Complete!</h4>
<p style={{ margin: "0.5rem 0" }}>
Successfully generated {generationConfig.numRows} rows of
synthetic data targeting the{" "}
<strong>{generationConfig.targetColumn}</strong> column.
</p>
</div>
</div>
<div
className="data-preview-section"
style={{ marginTop: "1.5rem" }}
>
<div
className="preview-header"
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "1rem",
flexWrap: "wrap",
gap: "1rem",
}}
>
<h4 style={{ margin: 0, color: "var(--text-primary)" }}>
πŸ“Š Data Preview
</h4>
<div
className="action-buttons"
style={{
display: "flex",
gap: "0.75rem",
flexWrap: "wrap",
}}
>
<button
className="btn btn-success"
onClick={handleDownload}
title="Download the generated synthetic data file"
>
πŸ“₯ Download
</button>
<button
className="btn btn-outline"
onClick={handleCopyLink}
title="Copy download link to clipboard"
>
πŸ“‹ Copy Link
</button>
<button
className="btn btn-secondary"
onClick={() => {
setGeneratedDataLink("");
setGenerationProgress(0);
setGenerationStatus("");
setHasError(false);
setErrorMessage("");
}}
title="Generate new synthetic data with different parameters"
>
πŸ”„ New Generation
</button>
</div>
</div>
<DataViewer s3Url={generatedDataLink} showPreviewOnly={true} />
<div
className="preview-info"
style={{
marginTop: "1rem",
padding: "0.75rem",
background: "var(--bg-tertiary)",
borderRadius: "8px",
fontSize: "0.875rem",
color: "var(--text-secondary)",
textAlign: "center",
}}
>
πŸ’‘ Showing preview of first few rows. Download the complete file
({generationConfig.numRows} rows) using the button above.
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default Step4;