Egrigor's picture
Update index.html
b79d53e verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hugging Face Dataset Creator</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>
.gradient-bg {
background: linear-gradient(135deg, #6b73ff 0%, #000dff 100%);
}
.dataset-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.slide-in {
animation: slideIn 0.3s ease-out forwards;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.field-pill {
transition: all 0.2s ease;
}
.field-pill:hover {
background-color: #e0e7ff;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="gradient-bg text-white py-6 shadow-lg">
<div class="container mx-auto px-4">
<div class="flex justify-between items-center">
<div class="flex items-center space-x-2">
<i class="fas fa-robot text-3xl"></i>
<h1 class="text-2xl font-bold">Hugging Face Dataset Creator</h1>
</div>
<div class="flex items-center space-x-4">
<button id="authBtn" class="bg-white text-blue-600 px-4 py-2 rounded-lg font-medium hover:bg-gray-100 transition">
<i class="fas fa-key mr-2"></i>Authenticate
</button>
</div>
</div>
</div>
</div>
<!-- Auth Modal -->
<div id="authModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-lg p-6 w-full max-w-md slide-in">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-800">Hugging Face Authentication</h2>
<button id="closeAuthModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mb-4">
<label class="block text-gray-700 text-sm font-medium mb-2" for="hfToken">
Hugging Face Token
</label>
<input type="password" id="hfToken" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="hf_xxxxxxxxxxxxxxxx">
<p class="text-xs text-gray-500 mt-1">Get your token from <a href="https://huggingface.co/settings/tokens" target="_blank" class="text-blue-500 hover:underline">Hugging Face settings</a></p>
</div>
<div class="flex justify-end space-x-3">
<button id="cancelAuth" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">
Cancel
</button>
<button id="saveToken" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
Save Token
</button>
</div>
</div>
</div>
<div class="container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Left sidebar - Dataset Configuration -->
<div class="lg:col-span-1 bg-white rounded-lg shadow-md p-6 h-fit">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center">
<i class="fas fa-cog mr-2 text-blue-600"></i> Dataset Configuration
</h2>
<div class="mb-6">
<label class="block text-gray-700 text-sm font-medium mb-2" for="datasetName">
Dataset Name
</label>
<input type="text" id="datasetName" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="my-custom-dataset">
</div>
<div class="mb-6">
<label class="block text-gray-700 text-sm font-medium mb-2" for="datasetDesc">
Description
</label>
<textarea id="datasetDesc" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Describe your dataset..."></textarea>
</div>
<div class="mb-6">
<div class="flex justify-between items-center mb-2">
<label class="block text-gray-700 text-sm font-medium">Fields</label>
<button id="addFieldBtn" class="text-blue-600 text-sm font-medium hover:text-blue-800 flex items-center">
<i class="fas fa-plus mr-1"></i> Add Field
</button>
</div>
<div id="fieldsContainer" class="space-y-2">
<!-- Fields will be added here -->
</div>
</div>
<div class="flex space-x-3">
<button id="createDatasetBtn" class="flex-1 bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 flex items-center justify-center">
<i class="fas fa-database mr-2"></i> Create Dataset
</button>
</div>
</div>
<!-- Main content - Data Entry -->
<div class="lg:col-span-2">
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-bold text-gray-800 flex items-center">
<i class="fas fa-edit mr-2 text-blue-600"></i> Data Entry
</h2>
<div class="flex space-x-2">
<button id="addRowBtn" class="bg-green-600 text-white px-3 py-1 rounded-md text-sm hover:bg-green-700 flex items-center">
<i class="fas fa-plus mr-1"></i> Add Row
</button>
<button id="clearDataBtn" class="bg-gray-200 text-gray-700 px-3 py-1 rounded-md text-sm hover:bg-gray-300 flex items-center">
<i class="fas fa-trash-alt mr-1"></i> Clear
</button>
</div>
</div>
<div id="dataTableContainer" class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr id="tableHeaders">
<!-- Headers will be added here -->
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody id="tableBody" class="bg-white divide-y divide-gray-200">
<!-- Data rows will be added here -->
<tr id="noDataRow">
<td colspan="100" class="px-6 py-4 text-center text-gray-500">
No data yet. Add fields and start entering data!
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Save/Export Section -->
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center">
<i class="fas fa-save mr-2 text-blue-600"></i> Save & Export
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div class="bg-gray-50 p-4 rounded-lg border border-gray-200">
<h3 class="font-medium text-gray-800 mb-2 flex items-center">
<i class="fas fa-download mr-2 text-blue-600"></i> Save Locally
</h3>
<p class="text-sm text-gray-600 mb-3">Download your dataset as a JSON file to your computer.</p>
<button id="saveLocalBtn" class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 flex items-center justify-center">
<i class="fas fa-file-export mr-2"></i> Export to JSON
</button>
</div>
<div class="bg-gray-50 p-4 rounded-lg border border-gray-200">
<h3 class="font-medium text-gray-800 mb-2 flex items-center">
<i class="fas fa-cloud-upload-alt mr-2 text-blue-600"></i> Upload to Hugging Face
</h3>
<p class="text-sm text-gray-600 mb-3">Push your dataset directly to your Hugging Face repository.</p>
<button id="uploadHuggingFaceBtn" class="w-full bg-purple-600 text-white py-2 px-4 rounded-md hover:bg-purple-700 flex items-center justify-center">
<i class="fas fa-robot mr-2"></i> Upload to HF Hub
</button>
</div>
</div>
<div id="uploadStatus" class="hidden">
<!-- Upload status will be shown here -->
</div>
</div>
</div>
</div>
</div>
<script>
// State management
let state = {
hfToken: localStorage.getItem('hfToken') || null,
fields: [],
data: [],
datasetName: '',
datasetDesc: ''
};
// DOM elements
const authBtn = document.getElementById('authBtn');
const authModal = document.getElementById('authModal');
const closeAuthModal = document.getElementById('closeAuthModal');
const cancelAuth = document.getElementById('cancelAuth');
const saveToken = document.getElementById('saveToken');
const hfTokenInput = document.getElementById('hfToken');
const datasetNameInput = document.getElementById('datasetName');
const datasetDescInput = document.getElementById('datasetDesc');
const fieldsContainer = document.getElementById('fieldsContainer');
const addFieldBtn = document.getElementById('addFieldBtn');
const createDatasetBtn = document.getElementById('createDatasetBtn');
const addRowBtn = document.getElementById('addRowBtn');
const clearDataBtn = document.getElementById('clearDataBtn');
const tableHeaders = document.getElementById('tableHeaders');
const tableBody = document.getElementById('tableBody');
const noDataRow = document.getElementById('noDataRow');
const saveLocalBtn = document.getElementById('saveLocalBtn');
const uploadHuggingFaceBtn = document.getElementById('uploadHuggingFaceBtn');
const uploadStatus = document.getElementById('uploadStatus');
// Initialize
document.addEventListener('DOMContentLoaded', () => {
updateAuthUI();
loadFromLocalStorage();
renderFields();
renderTable();
// Set initial dataset name if not set
if (!state.datasetName) {
state.datasetName = `my-dataset-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}`;
datasetNameInput.value = state.datasetName;
}
});
// Event listeners
authBtn.addEventListener('click', () => {
if (state.hfToken) {
// Logout
state.hfToken = null;
localStorage.removeItem('hfToken');
updateAuthUI();
} else {
// Show auth modal
authModal.classList.remove('hidden');
hfTokenInput.value = state.hfToken || '';
}
});
closeAuthModal.addEventListener('click', () => authModal.classList.add('hidden'));
cancelAuth.addEventListener('click', () => authModal.classList.add('hidden'));
saveToken.addEventListener('click', () => {
const token = hfTokenInput.value.trim();
if (token) {
state.hfToken = token;
localStorage.setItem('hfToken', token);
authModal.classList.add('hidden');
updateAuthUI();
showToast('Token saved successfully!', 'success');
} else {
showToast('Please enter a valid token', 'error');
}
});
addFieldBtn.addEventListener('click', () => {
const fieldName = prompt('Enter field name:');
if (fieldName && fieldName.trim()) {
const fieldType = prompt('Enter field type (text, number, boolean):', 'text');
const validTypes = ['text', 'number', 'boolean'];
if (validTypes.includes(fieldType.toLowerCase())) {
state.fields.push({
name: fieldName.trim(),
type: fieldType.toLowerCase()
});
saveToLocalStorage();
renderFields();
renderTable();
} else {
showToast('Invalid field type. Must be text, number, or boolean.', 'error');
}
}
});
createDatasetBtn.addEventListener('click', () => {
state.datasetName = datasetNameInput.value.trim();
state.datasetDesc = datasetDescInput.value.trim();
if (!state.datasetName) {
showToast('Please enter a dataset name', 'error');
return;
}
if (state.fields.length === 0) {
showToast('Please add at least one field', 'error');
return;
}
saveToLocalStorage();
showToast('Dataset configuration saved!', 'success');
});
addRowBtn.addEventListener('click', () => {
if (state.fields.length === 0) {
showToast('Please add fields first', 'error');
return;
}
state.data.push(createEmptyRow());
saveToLocalStorage();
renderTable();
});
clearDataBtn.addEventListener('click', () => {
if (confirm('Are you sure you want to clear all data?')) {
state.data = [];
saveToLocalStorage();
renderTable();
showToast('Data cleared', 'info');
}
});
saveLocalBtn.addEventListener('click', () => {
if (state.data.length === 0) {
showToast('No data to export', 'error');
return;
}
const dataset = {
name: state.datasetName,
description: state.datasetDesc,
fields: state.fields,
data: state.data
};
const blob = new Blob([JSON.stringify(dataset, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${state.datasetName}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('Dataset exported successfully!', 'success');
});
uploadHuggingFaceBtn.addEventListener('click', async () => {
if (!state.hfToken) {
showToast('Please authenticate with Hugging Face first', 'error');
authModal.classList.remove('hidden');
return;
}
if (state.data.length === 0) {
showToast('No data to upload', 'error');
return;
}
try {
uploadStatus.innerHTML = `
<div class="bg-blue-50 border-l-4 border-blue-500 p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-circle-notch fa-spin text-blue-500"></i>
</div>
<div class="ml-3">
<p class="text-sm text-blue-700">
Uploading dataset to Hugging Face...
</p>
</div>
</div>
</div>
`;
uploadStatus.classList.remove('hidden');
// In a real app, you would call the Hugging Face API here
// This is a simulation for the UI
await new Promise(resolve => setTimeout(resolve, 2000));
uploadStatus.innerHTML = `
<div class="bg-green-50 border-l-4 border-green-500 p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-check-circle text-green-500"></i>
</div>
<div class="ml-3">
<p class="text-sm text-green-700">
Dataset uploaded successfully to <span class="font-medium">${state.datasetName}</span>!
</p>
</div>
</div>
</div>
`;
showToast('Dataset uploaded successfully!', 'success');
} catch (error) {
uploadStatus.innerHTML = `
<div class="bg-red-50 border-l-4 border-red-500 p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-exclamation-circle text-red-500"></i>
</div>
<div class="ml-3">
<p class="text-sm text-red-700">
Error uploading dataset: ${error.message}
</p>
</div>
</div>
</div>
`;
showToast('Error uploading dataset', 'error');
}
});
// Helper functions
function updateAuthUI() {
if (state.hfToken) {
authBtn.innerHTML = '<i class="fas fa-check-circle mr-2"></i> Authenticated';
authBtn.classList.remove('bg-white', 'text-blue-600');
authBtn.classList.add('bg-green-100', 'text-green-800');
} else {
authBtn.innerHTML = '<i class="fas fa-key mr-2"></i> Authenticate';
authBtn.classList.remove('bg-green-100', 'text-green-800');
authBtn.classList.add('bg-white', 'text-blue-600');
}
}
function createEmptyRow() {
const row = {};
state.fields.forEach(field => {
row[field.name] = field.type === 'number' ? 0 : field.type === 'boolean' ? false : '';
});
return row;
}
function renderFields() {
fieldsContainer.innerHTML = '';
if (state.fields.length === 0) {
fieldsContainer.innerHTML = `
<div class="text-gray-500 text-sm italic">
No fields added yet. Click "Add Field" to get started.
</div>
`;
return;
}
state.fields.forEach((field, index) => {
const fieldElement = document.createElement('div');
fieldElement.className = 'flex items-center justify-between p-2 border border-gray-200 rounded-md field-pill';
fieldElement.innerHTML = `
<div>
<span class="font-medium text-gray-700">${field.name}</span>
<span class="text-xs text-gray-500 ml-2">(${field.type})</span>
</div>
<button class="text-red-500 hover:text-red-700 delete-field" data-index="${index}">
<i class="fas fa-trash-alt"></i>
</button>
`;
fieldsContainer.appendChild(fieldElement);
});
// Add event listeners to delete buttons
document.querySelectorAll('.delete-field').forEach(btn => {
btn.addEventListenerik('click', (e) => {
const index = parseInt(e.target.closest('.delete-field').getAttribute('data-index'));
state.fields.splice(index, 1);
saveToLocalStorage();
renderFields();
renderTable();
});
});
}
function renderTable() {
// Clear existing headers and rows
tableHeaders.innerHTML = '';
tableBody.innerHTML = '';
if (state.fields.length === 0) {
tableBody.appendChild(noDataRow);
return;
}
// Add headers
state.fields.forEach(field => {
const th = document.createElement('th');
th.className = 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
th.textContent = field.name;
tableHeaders.appendChild(th);
});
// Add actions header
const actionsTh = document.createElement('th');
actionsTh.className = 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
actionsTh.textContent = 'Actions';
tableHeaders.appendChild(actionsTh);
// Add data rows
if (state.data.length === 0) {
tableBody.appendChild(noDataRow);
} else {
state.data.forEach((row, rowIndex) => {
const tr = document.createElement('tr');
tr.className = 'hover:bg-gray-50';
state.fields.forEach(field => {
const td = document.createElement('td');
td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500';
if (field.type === 'boolean') {
td.innerHTML = `
<div class="flex items-center">
<input type="checkbox" ${row[field.name] ? 'checked' : ''}
class="row-checkbox h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
data-row="${rowIndex}" data-field="${field.name}">
</div>
`;
} else {
const input = document.createElement('input');
input.type = field.type === 'number' ? 'number' : 'text';
input.value = row[field.name];
input.className = 'w-full px-2 py-1 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500';
input.setAttribute('data-row', rowIndex);
input.setAttribute('data-field', field.name);
input.addEventListener('change', (e) => {
const value = field.type === 'number' ? parseFloat(e.target.value) : e.target.value;
state.data[e.target.getAttribute('data-row')][e.target.getAttribute('data-field')] = value;
saveToLocalStorage();
});
td.appendChild(input);
}
tr.appendChild(td);
});
// Add actions cell
const actionsTd = document.createElement('td');
actionsTd.className = 'px-6 py-4 whitespace-nowrap text-sm font-medium';
actionsTd.innerHTML = `
<button class="text-red-600 hover:text-red-900 delete-row" data-row="${rowIndex}">
<i class="fas fa-trash-alt"></i>
</button>
`;
tr.appendChild(actionsTd);
tableBody.appendChild(tr);
});
// Add event listeners to delete row buttons
document.querySelectorAll('.delete-row').forEach(btn => {
btn.addEventListener('click', (e) => {
const rowIndex = parseInt(e.target.closest('.delete-row').getAttribute('data-row'));
state.data.splice(rowIndex, 1);
saveToLocalStorage();
renderTable();
});
});
// Add event listeners to checkbox inputs
document.querySelectorAll('.row-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', (e) => {
const rowIndex = parseInt(e.target.getAttribute('data-row'));
const fieldName = e.target.getAttribute('data-field');
state.data[rowIndex][fieldName] = e.target.checked;
saveToLocalStorage();
});
});
}
}
function saveToLocalStorage() {
localStorage.setItem('datasetCreatorState', JSON.stringify(state));
}
function loadFromLocalStorage() {
const savedState = localStorage.getItem('datasetCreatorState');
if (savedState) {
const parsedState = JSON.parse(savedState);
state.fields = parsedState.fields || [];
state.data = parsedState.data || [];
state.datasetName = parsedState.datasetName || '';
state.datasetDesc = parsedState.datasetDesc || '';
datasetNameInput.value = state.datasetName;
datasetDescInput.value = state.datasetDesc;
}
}
function showToast(message, type = 'info') {
const toast = document.createElement('div');
const colors = {
success: 'bg-green-100 border-green-500 text-green-700',
error: 'bg-red-100 border-red-500 text-red-700',
info: 'bg-blue-100 border-blue-500 text-blue-700'
};
toast.className = `fixed bottom-4 right-4 border-l-4 p-4 max-w-xs ${colors[type]} shadow-lg rounded`;
toast.innerHTML = `
<div class="flex items-center">
<div class="flex-shrink-0">
${type === 'success' ? '<i class="fas fa-check-circle"></i>' : ''}
${type === 'error' ? '<i class="fas fa-exclamation-circle"></i>' : ''}
${type === 'info' ? '<i class="fas fa-info-circle"></i>' : ''}
</div>
<div class="ml-3">
<p class="text-sm">${message}</p>
</div>
</div>
`;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.add('opacity-0', 'transition-opacity', 'duration-300');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
</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=Egrigor/huggingface-dataset-creator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>