Reformat-Datasets / index.html
CultriX's picture
Add 3 files
8f18fd1 verified
raw
history blame
69.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dataset Converter | Finetuning Assistant</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.dropzone {
border: 2px dashed #cbd5e0;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #4f46e5;
background-color: #f0f4ff;
}
.dropzone.error {
border-color: #ef4444;
background-color: #fef2f2;
}
.dropzone.success {
border-color: #10b981;
background-color: #ecfdf5;
}
.preset-card {
transition: all 0.2s ease;
}
.preset-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.preset-card.selected {
border-color: #4f46e5;
background-color: #f0f4ff;
}
.header-input:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.5);
}
.smooth-transition {
transition: all 0.3s ease;
}
.preview-container {
max-height: 300px;
overflow-y: auto;
}
.preview-container::-webkit-scrollbar {
width: 6px;
}
.preview-container::-webkit-scrollbar-track {
background: #f1f1f1;
}
.preview-container::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
.preview-container::-webkit-scrollbar-thumb:hover {
background: #555;
}
.progress-bar {
height: 4px;
background-color: #e5e7eb;
border-radius: 2px;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background-color: #4f46e5;
transition: width 0.3s ease;
}
.file-info {
display: none;
}
.spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.transform-section {
display: none;
}
.transform-section.active {
display: block;
}
.tab-button {
transition: all 0.2s ease;
}
.tab-button.active {
border-bottom: 2px solid #4f46e5;
color: #4f46e5;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header -->
<header class="mb-10 text-center">
<h1 class="text-4xl font-bold text-indigo-700 mb-2">Dataset Converter</h1>
<p class="text-gray-600 text-lg">Quickly transform datasets for AI fine-tuning with one-click presets</p>
</header>
<!-- Main Content -->
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
<!-- Step Navigation -->
<div class="flex border-b border-gray-200">
<div class="w-1/3 py-4 px-6 text-center border-b-2 border-indigo-500 font-medium text-indigo-600">
<i class="fas fa-upload mr-2"></i> Upload Data
</div>
<div class="w-1/3 py-4 px-6 text-center border-b-2 border-gray-200 font-medium text-gray-500">
<i class="fas fa-magic mr-2"></i> Transform
</div>
<div class="w-1/3 py-4 px-6 text-center border-b-2 border-gray-200 font-medium text-gray-500">
<i class="fas fa-download mr-2"></i> Download
</div>
</div>
<!-- Step 1: Upload Data -->
<div id="step1" class="p-8">
<div class="mb-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-3">Upload Your Dataset</h2>
<p class="text-gray-600">Supported formats: CSV, JSON, Excel, TSV (Max 10MB)</p>
</div>
<div id="dropzone" class="dropzone rounded-lg p-12 text-center cursor-pointer mb-4">
<div id="upload-icon" class="mx-auto w-16 h-16 bg-indigo-100 rounded-full flex items-center justify-center mb-4">
<i class="fas fa-cloud-upload-alt text-indigo-500 text-2xl"></i>
</div>
<h3 id="dropzone-title" class="text-lg font-medium text-gray-700 mb-1">Drag & drop your file here</h3>
<p id="dropzone-subtitle" class="text-gray-500 mb-4">or</p>
<label for="file-upload" class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer">
<i class="fas fa-folder-open mr-2"></i> Browse Files
<input id="file-upload" type="file" class="hidden" accept=".csv,.json,.xlsx,.xls,.tsv">
</label>
<div id="progress-container" class="mt-4 hidden">
<div class="progress-bar">
<div id="progress-bar-fill" class="progress-bar-fill" style="width: 0%"></div>
</div>
<p id="progress-text" class="text-sm text-gray-500 mt-1">Uploading...</p>
</div>
<div id="file-info" class="file-info mt-4 p-3 bg-gray-50 rounded-lg">
<div class="flex items-center justify-between">
<div class="flex items-center truncate">
<i id="file-icon" class="fas fa-file-alt text-indigo-500 mr-2"></i>
<span id="file-name" class="text-sm font-medium text-gray-700 truncate"></span>
</div>
<button id="remove-file" class="text-red-500 hover:text-red-700">
<i class="fas fa-times"></i>
</button>
</div>
<div id="file-meta" class="text-xs text-gray-500 mt-1"></div>
</div>
<div id="error-message" class="hidden mt-4 p-3 bg-red-50 text-red-600 rounded-lg text-sm"></div>
</div>
<div class="flex justify-between items-center">
<div class="text-sm text-gray-500">
<i class="fas fa-info-circle mr-1"></i> Your data remains private and is processed locally.
</div>
<button id="next-step-1" class="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
Next <i class="fas fa-arrow-right ml-2"></i>
</button>
</div>
</div>
<!-- Step 2: Transform Data -->
<div id="step2" class="p-8 hidden">
<div class="mb-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-3">Transform Your Dataset</h2>
<p class="text-gray-600">Choose a preset or customize the transformation</p>
</div>
<!-- Transformation Tabs -->
<div class="flex border-b border-gray-200 mb-6">
<button class="tab-button active px-4 py-2 font-medium text-sm focus:outline-none" data-tab="presets">
<i class="fas fa-bolt mr-2"></i> Quick Presets
</button>
<button class="tab-button px-4 py-2 font-medium text-sm text-gray-500 focus:outline-none" data-tab="custom">
<i class="fas fa-sliders-h mr-2"></i> Custom Settings
</button>
</div>
<!-- Presets Section -->
<div id="presets-section" class="transform-section active">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
<!-- Preset Cards -->
<div class="preset-card p-4 border rounded-lg cursor-pointer bg-white" data-preset="chat">
<div class="flex items-center mb-2">
<div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-3">
<i class="fas fa-comments text-indigo-500"></i>
</div>
<h3 class="font-medium">Chat Format</h3>
</div>
<p class="text-sm text-gray-600">Converts to {"messages": [{"role": "user", "content": "..."}]}</p>
<div class="mt-3 text-xs text-indigo-600">
<i class="fas fa-check-circle mr-1"></i> Best for conversational AI
</div>
</div>
<div class="preset-card p-4 border rounded-lg cursor-pointer bg-white" data-preset="instruction">
<div class="flex items-center mb-2">
<div class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center mr-3">
<i class="fas fa-graduation-cap text-green-500"></i>
</div>
<h3 class="font-medium">Instruction Format</h3>
</div>
<p class="text-sm text-gray-600">Converts to {"instruction": "...", "response": "..."}</p>
<div class="mt-3 text-xs text-green-600">
<i class="fas fa-check-circle mr-1"></i> Best for instruction-following models
</div>
</div>
<div class="preset-card p-4 border rounded-lg cursor-pointer bg-white" data-preset="qa">
<div class="flex items-center mb-2">
<div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center mr-3">
<i class="fas fa-question-circle text-blue-500"></i>
</div>
<h3 class="font-medium">Q&A Format</h3>
</div>
<p class="text-sm text-gray-600">Converts to {"question": "...", "answer": "..."}</p>
<div class="mt-3 text-xs text-blue-600">
<i class="fas fa-check-circle mr-1"></i> Best for question answering
</div>
</div>
<div class="preset-card p-4 border rounded-lg cursor-pointer bg-white" data-preset="completion">
<div class="flex items-center mb-2">
<div class="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center mr-3">
<i class="fas fa-align-left text-purple-500"></i>
</div>
<h3 class="font-medium">Completion Format</h3>
</div>
<p class="text-sm text-gray-600">Converts to {"prompt": "...", "completion": "..."}</p>
<div class="mt-3 text-xs text-purple-600">
<i class="fas fa-check-circle mr-1"></i> Best for text completion
</div>
</div>
<div class="preset-card p-4 border rounded-lg cursor-pointer bg-white" data-preset="rename">
<div class="flex items-center mb-2">
<div class="w-10 h-10 rounded-full bg-yellow-100 flex items-center justify-center mr-3">
<i class="fas fa-tags text-yellow-500"></i>
</div>
<h3 class="font-medium">Rename Columns</h3>
</div>
<p class="text-sm text-gray-600">Lets you rename columns without changing structure</p>
<div class="mt-3 text-xs text-yellow-600">
<i class="fas fa-check-circle mr-1"></i> Basic transformation
</div>
</div>
<div class="preset-card p-4 border rounded-lg cursor-pointer bg-white" data-preset="custom">
<div class="flex items-center mb-2">
<div class="w-10 h-10 rounded-full bg-pink-100 flex items-center justify-center mr-3">
<i class="fas fa-code text-pink-500"></i>
</div>
<h3 class="font-medium">Custom Template</h3>
</div>
<p class="text-sm text-gray-600">Define your own structure with placeholders</p>
<div class="mt-3 text-xs text-pink-600">
<i class="fas fa-check-circle mr-1"></i> Advanced users
</div>
</div>
</div>
<!-- Preset Configuration -->
<div id="preset-config" class="hidden mb-6">
<h3 class="text-lg font-medium text-gray-800 mb-3" id="preset-title">Configure Preset</h3>
<div id="chat-config" class="preset-config-section hidden">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">User Message Column</label>
<select id="chat-user-col" class="w-full px-3 py-2 border rounded-md focus:ring-indigo-500 focus:border-indigo-500">
<!-- Dynamically populated -->
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Assistant Message Column</label>
<select id="chat-assistant-col" class="w-full px-3 py-2 border rounded-md focus:ring-indigo-500 focus:border-indigo-500">
<!-- Dynamically populated -->
</select>
</div>
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">System Message (optional)</label>
<input type="text" id="chat-system-msg" class="w-full px-3 py-2 border rounded-md focus:ring-indigo-500 focus:border-indigo-500" placeholder="You are a helpful assistant...">
</div>
</div>
<div id="instruction-config" class="preset-config-section hidden">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Instruction Column</label>
<select id="instruction-col" class="w-full px-3 py-2 border rounded-md focus:ring-indigo-500 focus:border-indigo-500">
<!-- Dynamically populated -->
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Response Column</label>
<select id="response-col" class="w-full px-3 py-2 border rounded-md focus:ring-indigo-500 focus:border-indigo-500">
<!-- Dynamically populated -->
</select>
</div>
</div>
</div>
<div id="qa-config" class="preset-config-section hidden">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Question Column</label>
<select id="question-col" class="w-full px-3 py-2 border rounded-md focus:ring-indigo-500 focus:border-indigo-500">
<!-- Dynamically populated -->
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Answer Column</label>
<select id="answer-col" class="w-full px-3 py-2 border rounded-md focus:ring-indigo-500 focus:border-indigo-500">
<!-- Dynamically populated -->
</select>
</div>
</div>
</div>
<div id="completion-config" class="preset-config-section hidden">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Prompt Column</label>
<select id="prompt-col" class="w-full px-3 py-2 border rounded-md focus:ring-indigo-500 focus:border-indigo-500">
<!-- Dynamically populated -->
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Completion Column</label>
<select id="completion-col" class="w-full px-3 py-2 border rounded-md focus:ring-indigo-500 focus:border-indigo-500">
<!-- Dynamically populated -->
</select>
</div>
</div>
</div>
<div id="rename-config" class="preset-config-section hidden">
<label class="block text-sm font-medium text-gray-700 mb-2">Column Renaming</label>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="grid grid-cols-2 gap-4 mb-4">
<div class="text-sm font-medium text-gray-500">Original Name</div>
<div class="text-sm font-medium text-gray-500">New Name</div>
</div>
<div id="rename-mapping-container">
<!-- Dynamic rename fields will be inserted here -->
</div>
</div>
</div>
<div id="custom-config" class="preset-config-section hidden">
<label class="block text-sm font-medium text-gray-700 mb-2">Custom Template</label>
<textarea id="custom-template" class="w-full h-32 px-3 py-2 text-gray-700 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Example: { 'prompt': '{{input}}', 'completion': '{{output}}' }"></textarea>
<p class="text-xs text-gray-500 mt-1">Use {{column_name}} to reference columns from your data</p>
</div>
</div>
</div>
<!-- Custom Settings Section -->
<div id="custom-section" class="transform-section">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<!-- Format Selection -->
<div class="col-span-1">
<label class="block text-sm font-medium text-gray-700 mb-2">Output Format</label>
<div class="space-y-2">
<div class="p-4 border rounded-lg bg-white">
<div class="flex items-center">
<input type="radio" name="format" id="format-json" value="json" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500" checked>
<label for="format-json" class="ml-3 block text-sm font-medium text-gray-700">JSON</label>
</div>
<p class="mt-1 ml-7 text-xs text-gray-500">Structured data with key-value pairs</p>
</div>
<div class="p-4 border rounded-lg bg-white">
<div class="flex items-center">
<input type="radio" name="format" id="format-csv" value="csv" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500">
<label for="format-csv" class="ml-3 block text-sm font-medium text-gray-700">CSV</label>
</div>
<p class="mt-1 ml-7 text-xs text-gray-500">Comma-separated values</p>
</div>
</div>
</div>
<!-- Header Mapping -->
<div class="col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-2">Header Mapping</label>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="grid grid-cols-2 gap-4 mb-4">
<div class="text-sm font-medium text-gray-500">Original Header</div>
<div class="text-sm font-medium text-gray-500">New Header Name</div>
</div>
<div id="header-mapping-container">
<!-- Dynamic header mapping fields will be inserted here -->
<div class="text-center py-8 text-gray-400">
<i class="fas fa-table fa-2x mb-2"></i>
<p>Select a preset or upload a file to see available headers</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Preview Section -->
<div class="mb-6">
<div class="flex justify-between items-center mb-2">
<label class="block text-sm font-medium text-gray-700">Preview</label>
<button id="refresh-preview" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
<i class="fas fa-sync-alt mr-1"></i> Refresh
</button>
</div>
<div id="preview-content" class="preview-container bg-gray-50 p-4 rounded-lg border border-gray-200 text-sm font-mono">
<div class="text-center py-8 text-gray-400">
<i class="fas fa-eye fa-2x mb-2"></i>
<p>Select a preset or configure settings to see a preview</p>
</div>
</div>
</div>
<div class="flex justify-between items-center">
<button id="prev-step-2" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<i class="fas fa-arrow-left mr-2"></i> Back
</button>
<button id="next-step-2" class="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed">
Next <i class="fas fa-arrow-right ml-2"></i>
</button>
</div>
</div>
<!-- Step 3: Download Result -->
<div id="step3" class="p-8 hidden">
<div class="mb-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-3">Download Converted Data</h2>
<p class="text-gray-600">Your dataset has been successfully transformed</p>
</div>
<div class="bg-indigo-50 border border-indigo-100 rounded-lg p-6 text-center mb-8">
<div class="mx-auto w-16 h-16 bg-indigo-100 rounded-full flex items-center justify-center mb-4">
<i class="fas fa-check-circle text-indigo-500 text-2xl"></i>
</div>
<h3 class="text-lg font-medium text-gray-700 mb-2">Conversion Complete!</h3>
<p class="text-gray-600 mb-4">Your data is ready for fine-tuning</p>
<div class="flex justify-center space-x-4">
<button id="download-btn" class="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<i class="fas fa-download mr-2"></i> Download
</button>
<button id="copy-clipboard" class="px-6 py-2 bg-white text-indigo-600 border border-indigo-300 rounded-md hover:bg-indigo-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<i class="fas fa-copy mr-2"></i> Copy to Clipboard
</button>
</div>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Converted Data Preview</label>
<div id="final-preview" class="preview-container bg-gray-50 p-4 rounded-lg border border-gray-200 text-sm font-mono">
<!-- Final preview will be shown here -->
</div>
</div>
<div class="flex justify-between items-center">
<button id="prev-step-3" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<i class="fas fa-arrow-left mr-2"></i> Back
</button>
<button id="new-conversion" class="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<i class="fas fa-redo mr-2"></i> Start New Conversion
</button>
</div>
</div>
</div>
<!-- Footer -->
<footer class="mt-12 text-center text-gray-500 text-sm">
<p>Dataset Converter Tool | Built for AI Fine-tuning | <a href="#" class="text-indigo-600 hover:underline">Privacy Policy</a></p>
</footer>
</div>
<script>
// Global variables
let uploadedFile = null;
let parsedData = null;
let headers = [];
let convertedData = null;
let selectedPreset = null;
// DOM elements
const dropzone = document.getElementById('dropzone');
const fileUpload = document.getElementById('file-upload');
const nextStep1Btn = document.getElementById('next-step-1');
const step1 = document.getElementById('step1');
const step2 = document.getElementById('step2');
const step3 = document.getElementById('step3');
const headerMappingContainer = document.getElementById('header-mapping-container');
const previewContent = document.getElementById('preview-content');
const finalPreview = document.getElementById('final-preview');
const downloadBtn = document.getElementById('download-btn');
const copyClipboardBtn = document.getElementById('copy-clipboard');
const uploadIcon = document.getElementById('upload-icon');
const dropzoneTitle = document.getElementById('dropzone-title');
const dropzoneSubtitle = document.getElementById('dropzone-subtitle');
const progressContainer = document.getElementById('progress-container');
const progressBarFill = document.getElementById('progress-bar-fill');
const progressText = document.getElementById('progress-text');
const fileInfo = document.getElementById('file-info');
const fileName = document.getElementById('file-name');
const fileMeta = document.getElementById('file-meta');
const fileIcon = document.getElementById('file-icon');
const removeFile = document.getElementById('remove-file');
const errorMessage = document.getElementById('error-message');
const presetCards = document.querySelectorAll('.preset-card');
const presetConfig = document.getElementById('preset-config');
const presetTitle = document.getElementById('preset-title');
const tabButtons = document.querySelectorAll('.tab-button');
const presetsSection = document.getElementById('presets-section');
const customSection = document.getElementById('custom-section');
// Event listeners for navigation
document.getElementById('next-step-1').addEventListener('click', () => {
step1.classList.add('hidden');
step2.classList.remove('hidden');
updateStepNavigation(2);
});
document.getElementById('prev-step-2').addEventListener('click', () => {
step2.classList.add('hidden');
step1.classList.remove('hidden');
updateStepNavigation(1);
});
document.getElementById('next-step-2').addEventListener('click', () => {
convertData();
step2.classList.add('hidden');
step3.classList.remove('hidden');
updateStepNavigation(3);
displayFinalPreview();
});
document.getElementById('prev-step-3').addEventListener('click', () => {
step3.classList.add('hidden');
step2.classList.remove('hidden');
updateStepNavigation(2);
});
document.getElementById('new-conversion').addEventListener('click', () => {
resetConverter();
step3.classList.add('hidden');
step1.classList.remove('hidden');
updateStepNavigation(1);
});
// Tab switching
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const tab = button.dataset.tab;
// Update tab buttons
tabButtons.forEach(btn => {
btn.classList.remove('active', 'text-indigo-600');
btn.classList.add('text-gray-500');
});
button.classList.add('active', 'text-indigo-600');
button.classList.remove('text-gray-500');
// Show corresponding section
if (tab === 'presets') {
presetsSection.classList.add('active');
customSection.classList.remove('active');
} else {
presetsSection.classList.remove('active');
customSection.classList.add('active');
}
});
});
// Preset selection
presetCards.forEach(card => {
card.addEventListener('click', () => {
// Remove selection from all cards
presetCards.forEach(c => {
c.classList.remove('selected');
c.classList.remove('border-indigo-500');
c.classList.add('border-gray-200');
});
// Select clicked card
card.classList.add('selected', 'border-indigo-500');
card.classList.remove('border-gray-200');
// Show configuration for selected preset
selectedPreset = card.dataset.preset;
presetConfig.classList.remove('hidden');
presetTitle.textContent = `Configure ${card.querySelector('h3').textContent}`;
// Hide all config sections
document.querySelectorAll('.preset-config-section').forEach(section => {
section.classList.add('hidden');
});
// Show selected config section
document.getElementById(`${selectedPreset}-config`).classList.remove('hidden');
// Populate dropdowns if needed
if (uploadedFile) {
populatePresetDropdowns();
}
// Update preview
updatePreview();
});
});
// Refresh preview button
document.getElementById('refresh-preview').addEventListener('click', updatePreview);
// Download button
downloadBtn.addEventListener('click', downloadConvertedData);
// Copy to clipboard
copyClipboardBtn.addEventListener('click', copyToClipboard);
// Remove file button
removeFile.addEventListener('click', resetFileUpload);
// Dropzone events
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropzone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropzone.classList.add('active');
}
function unhighlight() {
dropzone.classList.remove('active');
}
dropzone.addEventListener('drop', handleDrop, false);
fileUpload.addEventListener('change', handleFileSelect, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length > 1) {
showError('Please upload only one file at a time');
return;
}
handleFiles(files);
}
function handleFileSelect(e) {
if (e.target.files.length > 1) {
showError('Please upload only one file at a time');
return;
}
handleFiles(e.target.files);
}
function handleFiles(files) {
if (files.length > 0) {
const file = files[0];
// Check file size (max 10MB)
if (file.size > 10 * 1024 * 1024) {
showError('File size exceeds 10MB limit');
return;
}
// Check file type
const fileType = file.name.split('.').pop().toLowerCase();
if (!['csv', 'json', 'xlsx', 'xls', 'tsv'].includes(fileType)) {
showError('Unsupported file format. Please upload CSV, JSON, Excel, or TSV files.');
return;
}
// Show loading state
showUploadingState();
// Simulate upload progress (in a real app, this would be actual upload progress)
let progress = 0;
const progressInterval = setInterval(() => {
progress += 5;
if (progress > 95) {
clearInterval(progressInterval);
parseFile(file);
} else {
updateProgress(progress);
}
}, 50);
}
}
function showUploadingState() {
// Hide error if any
hideError();
// Show progress bar
progressContainer.classList.remove('hidden');
progressBarFill.style.width = '0%';
progressText.textContent = 'Uploading...';
// Change icon to spinner
uploadIcon.innerHTML = '<i class="fas fa-spinner spinner text-indigo-500 text-2xl"></i>';
dropzoneTitle.textContent = 'Uploading your file...';
dropzoneSubtitle.classList.add('hidden');
}
function updateProgress(percent) {
progressBarFill.style.width = `${percent}%`;
progressText.textContent = `Uploading... ${percent}%`;
}
function showFileInfo(file) {
const fileType = file.name.split('.').pop().toLowerCase();
const fileSize = formatFileSize(file.size);
// Set file icon based on type
let iconClass = 'fa-file-alt';
if (fileType === 'csv' || fileType === 'tsv') {
iconClass = 'fa-file-csv';
} else if (fileType === 'json') {
iconClass = 'fa-file-code';
} else if (fileType === 'xlsx' || fileType === 'xls') {
iconClass = 'fa-file-excel';
}
fileIcon.className = `fas ${iconClass} text-indigo-500 mr-2`;
// Set file name and metadata
fileName.textContent = file.name;
fileMeta.textContent = `${fileSize}${fileType.toUpperCase()} file`;
// Show file info and hide progress
fileInfo.style.display = 'block';
progressContainer.classList.add('hidden');
// Update dropzone appearance
dropzone.classList.add('success');
dropzone.classList.remove('active', 'error');
// Update upload icon and text
uploadIcon.innerHTML = '<i class="fas fa-check-circle text-green-500 text-2xl"></i>';
dropzoneTitle.textContent = 'File uploaded successfully';
dropzoneSubtitle.classList.add('hidden');
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' bytes';
else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
else return (bytes / 1048576).toFixed(1) + ' MB';
}
function showError(message) {
errorMessage.textContent = message;
errorMessage.classList.remove('hidden');
// Update dropzone appearance
dropzone.classList.add('error');
dropzone.classList.remove('active', 'success');
// Reset upload icon and text
uploadIcon.innerHTML = '<i class="fas fa-cloud-upload-alt text-indigo-500 text-2xl"></i>';
dropzoneTitle.textContent = 'Drag & drop your file here';
dropzoneSubtitle.classList.remove('hidden');
// Hide progress and file info
progressContainer.classList.add('hidden');
fileInfo.style.display = 'none';
}
function hideError() {
errorMessage.classList.add('hidden');
dropzone.classList.remove('error');
}
function resetFileUpload() {
fileUpload.value = '';
uploadedFile = null;
parsedData = null;
headers = [];
nextStep1Btn.disabled = true;
// Reset dropzone appearance
dropzone.classList.remove('active', 'error', 'success');
// Reset UI elements
uploadIcon.innerHTML = '<i class="fas fa-cloud-upload-alt text-indigo-500 text-2xl"></i>';
dropzoneTitle.textContent = 'Drag & drop your file here';
dropzoneSubtitle.classList.remove('hidden');
progressContainer.classList.add('hidden');
fileInfo.style.display = 'none';
hideError();
// Reset header mapping
headerMappingContainer.innerHTML = `
<div class="text-center py-8 text-gray-400">
<i class="fas fa-table fa-2x mb-2"></i>
<p>Upload a file to see available headers</p>
</div>
`;
// Reset preview
previewContent.innerHTML = `
<div class="text-center py-8 text-gray-400">
<i class="fas fa-eye fa-2x mb-2"></i>
<p>Configure settings to see a preview</p>
</div>
`;
// Reset preset selection
presetCards.forEach(card => {
card.classList.remove('selected', 'border-indigo-500');
card.classList.add('border-gray-200');
});
presetConfig.classList.add('hidden');
selectedPreset = null;
}
function parseFile(file) {
const reader = new FileReader();
const fileType = file.name.split('.').pop().toLowerCase();
reader.onloadstart = function() {
progressText.textContent = 'Processing file...';
};
reader.onload = function(e) {
try {
if (fileType === 'csv' || fileType === 'tsv') {
parseCSV(e.target.result, fileType === 'tsv' ? '\t' : ',');
} else if (fileType === 'json') {
parsedData = JSON.parse(e.target.result);
extractHeadersFromJSON();
} else if (fileType === 'xlsx' || fileType === 'xls') {
// In a real implementation, you would use SheetJS or similar library
// For this demo, we'll just show an error
throw new Error('Excel parsing would require a library like SheetJS. In this demo, please use CSV or JSON.');
} else {
throw new Error('Unsupported file format');
}
// Update UI
uploadedFile = file;
showFileInfo(file);
nextStep1Btn.disabled = false;
updateHeaderMapping();
// If a preset is already selected, populate its dropdowns
if (selectedPreset) {
populatePresetDropdowns();
}
updatePreview();
// Complete progress
progressBarFill.style.width = '100%';
progressText.textContent = 'Processing complete!';
setTimeout(() => progressContainer.classList.add('hidden'), 1000);
} catch (error) {
console.error('Error parsing file:', error);
showError(`Error processing file: ${error.message}`);
progressContainer.classList.add('hidden');
}
};
reader.onerror = function() {
showError('Error reading file. Please try again.');
progressContainer.classList.add('hidden');
};
if (fileType === 'csv' || fileType === 'tsv' || fileType === 'json') {
reader.readAsText(file);
} else {
reader.readAsBinaryString(file);
}
}
function parseCSV(data, delimiter) {
const lines = data.split('\n');
if (lines.length === 0) {
throw new Error('File is empty');
}
headers = lines[0].split(delimiter).map(h => h.trim());
if (headers.length === 0) {
throw new Error('No headers found in the file');
}
parsedData = [];
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === '') continue;
const values = lines[i].split(delimiter);
const row = {};
for (let j = 0; j < headers.length; j++) {
if (j < values.length) {
row[headers[j]] = values[j].trim();
} else {
row[headers[j]] = '';
}
}
parsedData.push(row);
}
if (parsedData.length === 0) {
throw new Error('No data rows found in the file');
}
}
function extractHeadersFromJSON() {
if (Array.isArray(parsedData) && parsedData.length > 0) {
headers = Object.keys(parsedData[0]);
} else if (typeof parsedData === 'object') {
headers = Object.keys(parsedData);
// Convert to array format for consistency
parsedData = [parsedData];
} else {
throw new Error('Invalid JSON structure. Expected an array of objects or a single object.');
}
if (headers.length === 0) {
throw new Error('No headers found in the JSON data');
}
}
function updateHeaderMapping() {
headerMappingContainer.innerHTML = '';
if (headers.length === 0) {
headerMappingContainer.innerHTML = `
<div class="text-center py-8 text-gray-400">
<i class="fas fa-exclamation-circle fa-2x mb-2"></i>
<p>No headers found in the uploaded file</p>
</div>
`;
return;
}
headers.forEach(header => {
const div = document.createElement('div');
div.className = 'grid grid-cols-2 gap-4 mb-3 items-center';
div.innerHTML = `
<div class="text-sm text-gray-700 truncate" title="${header}">${header}</div>
<input type="text" value="${header}" class="header-input w-full px-3 py-2 text-gray-700 border rounded-md focus:border-indigo-500 smooth-transition" data-original="${header}">
`;
headerMappingContainer.appendChild(div);
});
}
function populatePresetDropdowns() {
if (!uploadedFile || !selectedPreset) return;
// Get all select elements in the current preset config
const selects = document.querySelectorAll(`#${selectedPreset}-config select`);
selects.forEach(select => {
// Clear existing options
select.innerHTML = '';
// Add default option
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = 'Select a column...';
select.appendChild(defaultOption);
// Add options for each header
headers.forEach(header => {
const option = document.createElement('option');
option.value = header;
option.textContent = header;
select.appendChild(option);
});
});
// For rename preset, populate the rename fields
if (selectedPreset === 'rename') {
const container = document.getElementById('rename-mapping-container');
container.innerHTML = '';
headers.forEach(header => {
const div = document.createElement('div');
div.className = 'grid grid-cols-2 gap-4 mb-3 items-center';
div.innerHTML = `
<div class="text-sm text-gray-700 truncate" title="${header}">${header}</div>
<input type="text" value="${header}" class="rename-input w-full px-3 py-2 text-gray-700 border rounded-md focus:border-indigo-500 smooth-transition" data-original="${header}">
`;
container.appendChild(div);
});
}
}
function updatePreview() {
if (!uploadedFile) return;
// If using presets tab
if (presetsSection.classList.contains('active') && selectedPreset) {
let previewText = '';
if (selectedPreset === 'chat') {
const userCol = document.getElementById('chat-user-col').value;
const assistantCol = document.getElementById('chat-assistant-col').value;
const systemMsg = document.getElementById('chat-system-msg').value;
if (userCol && assistantCol) {
const sample = getSampleData(1)[0];
const messages = [
systemMsg ? {role: "system", content: systemMsg} : null,
{role: "user", content: sample[userCol]},
{role: "assistant", content: sample[assistantCol]}
].filter(Boolean);
previewText = JSON.stringify({messages}, null, 2);
} else {
previewText = 'Select user and assistant message columns to see preview';
}
}
else if (selectedPreset === 'instruction') {
const instructionCol = document.getElementById('instruction-col').value;
const responseCol = document.getElementById('response-col').value;
if (instructionCol && responseCol) {
const sample = getSampleData(1)[0];
previewText = JSON.stringify({
instruction: sample[instructionCol],
response: sample[responseCol]
}, null, 2);
} else {
previewText = 'Select instruction and response columns to see preview';
}
}
else if (selectedPreset === 'qa') {
const questionCol = document.getElementById('question-col').value;
const answerCol = document.getElementById('answer-col').value;
if (questionCol && answerCol) {
const sample = getSampleData(1)[0];
previewText = JSON.stringify({
question: sample[questionCol],
answer: sample[answerCol]
}, null, 2);
} else {
previewText = 'Select question and answer columns to see preview';
}
}
else if (selectedPreset === 'completion') {
const promptCol = document.getElementById('prompt-col').value;
const completionCol = document.getElementById('completion-col').value;
if (promptCol && completionCol) {
const sample = getSampleData(1)[0];
previewText = JSON.stringify({
prompt: sample[promptCol],
completion: sample[completionCol]
}, null, 2);
} else {
previewText = 'Select prompt and completion columns to see preview';
}
}
else if (selectedPreset === 'rename') {
const inputs = document.querySelectorAll('.rename-input');
const mapping = {};
inputs.forEach(input => {
mapping[input.dataset.original] = input.value || input.dataset.original;
});
const sample = getSampleData(1)[0];
const renamedSample = {};
for (const oldHeader in mapping) {
renamedSample[mapping[oldHeader]] = sample[oldHeader];
}
previewText = JSON.stringify(renamedSample, null, 2);
}
else if (selectedPreset === 'custom') {
const template = document.getElementById('custom-template').value.trim();
if (template) {
try {
const sample = getSampleData(1)[0];
let result = template;
// Replace placeholders with actual values
for (const key in sample) {
const placeholder = `{{${key}}}`;
if (template.includes(placeholder)) {
result = result.replace(new RegExp(placeholder, 'g'), sample[key]);
}
}
previewText = result;
} catch (e) {
previewText = 'Error applying template. Check your syntax.';
}
} else {
previewText = 'Enter a custom template to see a preview';
}
}
previewContent.innerHTML = `<pre class="text-gray-800">${escapeHtml(previewText)}</pre>`;
}
// If using custom settings tab
else {
const selectedFormat = document.querySelector('input[name="format"]:checked').value;
let previewText = '';
if (selectedFormat === 'json') {
const sample = getSampleData(3);
previewText = JSON.stringify(sample, null, 2);
} else if (selectedFormat === 'csv') {
const newHeaders = getNewHeaders();
previewText = newHeaders.join(',') + '\n';
const sample = getSampleData(3);
sample.forEach(row => {
const values = newHeaders.map(h => row[h] || '');
previewText += values.join(',') + '\n';
});
}
previewContent.innerHTML = `<pre class="text-gray-800">${escapeHtml(previewText)}</pre>`;
}
}
function getSampleData(count) {
return parsedData.slice(0, Math.min(count, parsedData.length));
}
function getNewHeaders() {
const inputs = document.querySelectorAll('.header-input');
const newHeaders = [];
inputs.forEach(input => {
newHeaders.push(input.value || input.dataset.original);
});
return newHeaders;
}
function getHeaderMapping() {
const inputs = document.querySelectorAll('.header-input');
const mapping = {};
inputs.forEach(input => {
mapping[input.dataset.original] = input.value || input.dataset.original;
});
return mapping;
}
function convertData() {
// If using presets tab
if (presetsSection.classList.contains('active') && selectedPreset) {
if (selectedPreset === 'chat') {
const userCol = document.getElementById('chat-user-col').value;
const assistantCol = document.getElementById('chat-assistant-col').value;
const systemMsg = document.getElementById('chat-system-msg').value;
if (userCol && assistantCol) {
convertedData = parsedData.map(row => {
const messages = [
systemMsg ? {role: "system", content: systemMsg} : null,
{role: "user", content: row[userCol]},
{role: "assistant", content: row[assistantCol]}
].filter(Boolean);
return {messages};
});
}
}
else if (selectedPreset === 'instruction') {
const instructionCol = document.getElementById('instruction-col').value;
const responseCol = document.getElementById('response-col').value;
if (instructionCol && responseCol) {
convertedData = parsedData.map(row => ({
instruction: row[instructionCol],
response: row[responseCol]
}));
}
}
else if (selectedPreset === 'qa') {
const questionCol = document.getElementById('question-col').value;
const answerCol = document.getElementById('answer-col').value;
if (questionCol && answerCol) {
convertedData = parsedData.map(row => ({
question: row[questionCol],
answer: row[answerCol]
}));
}
}
else if (selectedPreset === 'completion') {
const promptCol = document.getElementById('prompt-col').value;
const completionCol = document.getElementById('completion-col').value;
if (promptCol && completionCol) {
convertedData = parsedData.map(row => ({
prompt: row[promptCol],
completion: row[completionCol]
}));
}
}
else if (selectedPreset === 'rename') {
const inputs = document.querySelectorAll('.rename-input');
const mapping = {};
inputs.forEach(input => {
mapping[input.dataset.original] = input.value || input.dataset.original;
});
convertedData = parsedData.map(row => {
const newRow = {};
for (const oldHeader in mapping) {
newRow[mapping[oldHeader]] = row[oldHeader];
}
return newRow;
});
}
else if (selectedPreset === 'custom') {
const template = document.getElementById('custom-template').value.trim();
if (template) {
try {
convertedData = parsedData.map(row => {
let result = template;
for (const key in row) {
const placeholder = `{{${key}}}`;
if (template.includes(placeholder)) {
result = result.replace(new RegExp(placeholder, 'g'), row[key]);
}
}
return result;
}).join('\n');
} catch (e) {
convertedData = 'Error in custom template: ' + e.message;
}
} else {
convertedData = 'No custom template provided';
}
}
}
// If using custom settings tab
else {
const selectedFormat = document.querySelector('input[name="format"]:checked').value;
const headerMapping = getHeaderMapping();
if (selectedFormat === 'json') {
// Simple renaming of headers
convertedData = parsedData.map(row => {
const newRow = {};
for (const oldHeader in headerMapping) {
newRow[headerMapping[oldHeader]] = row[oldHeader];
}
return newRow;
});
} else if (selectedFormat === 'csv') {
const newHeaders = Object.values(headerMapping);
let csvContent = newHeaders.join(',') + '\n';
parsedData.forEach(row => {
const values = newHeaders.map(h => {
// Find the original header that maps to this new header
const originalHeader = Object.keys(headerMapping).find(key => headerMapping[key] === h);
return row[originalHeader] || '';
});
csvContent += values.join(',') + '\n';
});
convertedData = csvContent;
}
}
}
function displayFinalPreview() {
if (!convertedData) return;
if (typeof convertedData === 'string') {
finalPreview.innerHTML = `<pre class="text-gray-800">${escapeHtml(convertedData)}</pre>`;
} else {
finalPreview.innerHTML = `<pre class="text-gray-800">${escapeHtml(JSON.stringify(convertedData, null, 2))}</pre>`;
}
}
function downloadConvertedData() {
if (!convertedData) return;
let blob, filename;
if (typeof convertedData === 'string') {
blob = new Blob([convertedData], { type: 'text/csv' });
filename = 'converted_data.csv';
} else {
const dataStr = JSON.stringify(convertedData, null, 2);
blob = new Blob([dataStr], { type: 'application/json' });
filename = 'converted_data.json';
}
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function copyToClipboard() {
if (!convertedData) return;
let textToCopy;
if (typeof convertedData === 'string') {
textToCopy = convertedData;
} else {
textToCopy = JSON.stringify(convertedData, null, 2);
}
navigator.clipboard.writeText(textToCopy).then(() => {
const originalText = copyClipboardBtn.innerHTML;
copyClipboardBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Copied!';
setTimeout(() => {
copyClipboardBtn.innerHTML = originalText;
}, 2000);
}).catch(err => {
console.error('Failed to copy: ', err);
alert('Failed to copy to clipboard');
});
}
function resetConverter() {
uploadedFile = null;
parsedData = null;
headers = [];
convertedData = null;
selectedPreset = null;
fileUpload.value = '';
nextStep1Btn.disabled = true;
headerMappingContainer.innerHTML = `
<div class="text-center py-8 text-gray-400">
<i class="fas fa-table fa-2x mb-2"></i>
<p>Upload a file to see available headers</p>
</div>
`;
previewContent.innerHTML = `
<div class="text-center py-8 text-gray-400">
<i class="fas fa-eye fa-2x mb-2"></i>
<p>Configure settings to see a preview</p>
</div>
`;
finalPreview.innerHTML = '';
// Reset dropzone appearance
dropzone.classList.remove('active', 'error', 'success');
uploadIcon.innerHTML = '<i class="fas fa-cloud-upload-alt text-indigo-500 text-2xl"></i>';
dropzoneTitle.textContent = 'Drag & drop your file here';
dropzoneSubtitle.classList.remove('hidden');
progressContainer.classList.add('hidden');
fileInfo.style.display = 'none';
hideError();
// Reset preset selection
presetCards.forEach(card => {
card.classList.remove('selected', 'border-indigo-500');
card.classList.add('border-gray-200');
});
presetConfig.classList.add('hidden');
// Reset tabs to presets
tabButtons.forEach((btn, index) => {
if (index === 0) {
btn.classList.add('active', 'text-indigo-600');
btn.classList.remove('text-gray-500');
} else {
btn.classList.remove('active', 'text-indigo-600');
btn.classList.add('text-gray-500');
}
});
presetsSection.classList.add('active');
customSection.classList.remove('active');
}
function updateStepNavigation(step) {
const steps = document.querySelectorAll('.flex.border-b.border-gray-200 > div');
steps.forEach((stepEl, index) => {
if (index + 1 === step) {
stepEl.classList.remove('border-gray-200', 'text-gray-500');
stepEl.classList.add('border-indigo-500', 'text-indigo-600');
} else {
stepEl.classList.remove('border-indigo-500', 'text-indigo-600');
stepEl.classList.add('border-gray-200', 'text-gray-500');
}
});
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=CultriX/deepsite" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>