davanstrien's picture
davanstrien HF Staff
Rewrite as clean HTML/JS app with dynamic label management
161b93c
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSON Schema Generator for Text Classification</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
<style>
.fade-in {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.copy-feedback {
animation: copyPulse 2s ease-out;
}
@keyframes copyPulse {
0% { opacity: 0; transform: translateY(10px); }
20% { opacity: 1; transform: translateY(0); }
80% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-10px); }
}
</style>
</head>
<body class="bg-gray-50">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header -->
<div class="text-center mb-8">
<h1 class="text-3xl font-bold text-gray-800 mb-2">JSON Schema Generator for Text Classification</h1>
<p class="text-gray-600">Generate JSON schemas for structured text classification with LLMs</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Left Column - Configuration -->
<div class="space-y-6">
<!-- Classification Type -->
<div class="bg-white rounded-lg shadow-sm p-6">
<h2 class="text-lg font-semibold mb-4">Classification Type</h2>
<div class="space-y-3">
<label class="flex items-center cursor-pointer">
<input type="radio" name="classification-type" value="single" checked class="mr-3 text-blue-600">
<div>
<span class="font-medium">Single Label</span>
<span class="text-sm text-gray-500 ml-2">One label per text</span>
</div>
</label>
<label class="flex items-center cursor-pointer">
<input type="radio" name="classification-type" value="multi" class="mr-3 text-blue-600">
<div>
<span class="font-medium">Multi Label</span>
<span class="text-sm text-gray-500 ml-2">Multiple labels allowed</span>
</div>
</label>
</div>
</div>
<!-- Labels -->
<div class="bg-white rounded-lg shadow-sm p-6">
<h2 class="text-lg font-semibold mb-4">Labels</h2>
<p class="text-sm text-gray-600 mb-4">Add the categories you want to classify text into:</p>
<div id="labels-container" class="space-y-2">
<!-- Labels will be dynamically added here -->
</div>
</div>
<!-- Advanced Options -->
<details class="bg-white rounded-lg shadow-sm">
<summary class="p-6 cursor-pointer font-semibold">Advanced Options</summary>
<div class="px-6 pb-6 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Field Name</label>
<input type="text" id="field-name" value="classification" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<p class="text-xs text-gray-500 mt-1">JSON key for the classification field</p>
</div>
<div>
<label class="flex items-center cursor-pointer">
<input type="checkbox" id="is-required" checked class="mr-2 text-blue-600">
<span class="text-sm font-medium">Required Field</span>
</label>
</div>
<div id="multi-options" class="space-y-4" style="display: none;">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Min Items</label>
<input type="number" id="min-items" value="0" min="0" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Max Items</label>
<input type="number" id="max-items" value="0" min="0" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<p class="text-xs text-gray-500 mt-1">0 = no limit</p>
</div>
</div>
</div>
</details>
</div>
<!-- Right Column - Output -->
<div class="bg-white rounded-lg shadow-sm p-6">
<!-- Tabs -->
<div class="border-b border-gray-200 mb-4">
<nav class="-mb-px flex space-x-8">
<button class="tab-button py-2 px-1 border-b-2 font-medium text-sm border-blue-500 text-blue-600" data-tab="schema">
Schema
</button>
<button class="tab-button py-2 px-1 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" data-tab="example">
Example
</button>
<button class="tab-button py-2 px-1 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" data-tab="how-to-use">
How to Use
</button>
</nav>
</div>
<!-- Tab Contents -->
<div id="schema" class="tab-content active">
<div class="mb-4">
<pre><code id="schema-output" class="language-json">// Please add at least one label to generate a schema</code></pre>
</div>
<div class="flex gap-2">
<button id="copy-btn" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors">
📋 Copy Schema
</button>
<button id="download-btn" class="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-colors">
💾 Download
</button>
<div id="copy-feedback" class="ml-4 py-2 text-green-600 font-medium" style="display: none;">
✓ Copied to clipboard!
</div>
</div>
</div>
<div id="example" class="tab-content">
<pre><code id="example-output" class="language-json">// Example will appear here</code></pre>
</div>
<div id="how-to-use" class="tab-content prose prose-sm max-w-none">
<h3 class="text-lg font-semibold mb-3">Integration Guide</h3>
<div class="mb-4">
<h4 class="font-medium mb-2">LM Studio:</h4>
<ol class="list-decimal list-inside text-sm text-gray-700 space-y-1">
<li>Copy the generated schema</li>
<li>Paste into the "Structured Output" field</li>
<li>The model will only output valid JSON</li>
</ol>
</div>
<div class="mb-4">
<h4 class="font-medium mb-2">OpenAI API:</h4>
<pre class="bg-gray-100 p-3 rounded text-xs"><code>response_format = {
"type": "json_schema",
"json_schema": {
"name": "classification",
"schema": YOUR_SCHEMA_HERE
}
}</code></pre>
</div>
<div>
<h4 class="font-medium mb-2">Other APIs:</h4>
<ul class="list-disc list-inside text-sm text-gray-700 space-y-1">
<li>Use with any API supporting JSON Schema validation</li>
<li>Check your API documentation for the parameter name</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<script>
// State management
let labels = [];
let labelIdCounter = 0;
// Initialize with 2 labels
function init() {
addLabel('positive');
addLabel('negative');
updateSchema();
setupEventListeners();
}
// Add a new label
function addLabel(value = '') {
const id = labelIdCounter++;
labels.push({ id, value });
const container = document.getElementById('labels-container');
const labelDiv = document.createElement('div');
labelDiv.className = 'flex gap-2 fade-in';
labelDiv.id = `label-${id}`;
labelDiv.innerHTML = `
<input type="text"
value="${value}"
placeholder="Enter label name"
class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
data-label-id="${id}">
<button onclick="addLabelAfter(${id})"
class="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors"
title="Add label after this one">
+
</button>
${labels.length > 2 ? `
<button onclick="removeLabel(${id})"
class="px-3 py-2 bg-red-100 hover:bg-red-200 text-red-700 rounded-md transition-colors"
title="Remove this label">
×
</button>` : ''}
`;
container.appendChild(labelDiv);
// Add event listener to the input
const input = labelDiv.querySelector('input');
input.addEventListener('input', (e) => {
const label = labels.find(l => l.id === id);
if (label) {
label.value = e.target.value;
updateSchema();
// Auto-add new label if typing in the last one
const lastLabel = labels[labels.length - 1];
if (label.id === lastLabel.id && e.target.value.trim() !== '') {
addLabel();
}
}
});
}
// Add label after specific position
function addLabelAfter(afterId) {
const index = labels.findIndex(l => l.id === afterId);
const newId = labelIdCounter++;
labels.splice(index + 1, 0, { id: newId, value: '' });
// Rebuild the container
rebuildLabelsContainer();
updateSchema();
}
// Remove a label
function removeLabel(id) {
if (labels.length <= 2) return;
labels = labels.filter(l => l.id !== id);
document.getElementById(`label-${id}`).remove();
updateSchema();
}
// Rebuild labels container
function rebuildLabelsContainer() {
const container = document.getElementById('labels-container');
container.innerHTML = '';
const currentLabels = [...labels];
labels = [];
currentLabels.forEach(label => {
addLabel(label.value);
});
}
// Generate JSON schema
function generateSchema() {
const classificationType = document.querySelector('input[name="classification-type"]:checked').value;
const fieldName = document.getElementById('field-name').value;
const isRequired = document.getElementById('is-required').checked;
const minItems = parseInt(document.getElementById('min-items').value) || 0;
const maxItems = parseInt(document.getElementById('max-items').value) || 0;
// Get valid labels
const validLabels = labels
.map(l => l.value.trim())
.filter(v => v !== '');
if (validLabels.length === 0) {
return {
schema: '// Please add at least one label to generate a schema',
example: '// Example will appear here'
};
}
// Check for duplicates
if (new Set(validLabels).size !== validLabels.length) {
return {
schema: '// Error: Duplicate labels found. Each label must be unique.',
example: '// Please fix duplicate labels'
};
}
// Build schema
const schema = {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {}
};
if (classificationType === 'single') {
schema.properties[fieldName] = {
"type": "string",
"enum": validLabels,
"description": `Classification into one of ${validLabels.length} categories`
};
} else {
schema.properties[fieldName] = {
"type": "array",
"items": {
"type": "string",
"enum": validLabels
},
"description": `Multiple labels from ${validLabels.length} categories`,
"uniqueItems": true
};
if (minItems > 0) {
schema.properties[fieldName].minItems = minItems;
}
if (maxItems > 0) {
schema.properties[fieldName].maxItems = maxItems;
}
}
if (isRequired) {
schema.required = [fieldName];
}
// Generate example
const example = {};
if (classificationType === 'single') {
example[fieldName] = validLabels[0];
} else {
example[fieldName] = validLabels.slice(0, 2);
}
return {
schema: JSON.stringify(schema, null, 2),
example: JSON.stringify(example, null, 2)
};
}
// Update schema display
function updateSchema() {
const result = generateSchema();
document.getElementById('schema-output').textContent = result.schema;
document.getElementById('example-output').textContent = result.example;
// Re-highlight syntax
Prism.highlightAll();
}
// Setup event listeners
function setupEventListeners() {
// Classification type change
document.querySelectorAll('input[name="classification-type"]').forEach(radio => {
radio.addEventListener('change', (e) => {
document.getElementById('multi-options').style.display =
e.target.value === 'multi' ? 'block' : 'none';
updateSchema();
});
});
// Other inputs
document.getElementById('field-name').addEventListener('input', updateSchema);
document.getElementById('is-required').addEventListener('change', updateSchema);
document.getElementById('min-items').addEventListener('input', updateSchema);
document.getElementById('max-items').addEventListener('input', updateSchema);
// Tab switching
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', (e) => {
// Update active tab
document.querySelectorAll('.tab-button').forEach(b => {
b.classList.remove('border-blue-500', 'text-blue-600');
b.classList.add('border-transparent', 'text-gray-500');
});
e.target.classList.remove('border-transparent', 'text-gray-500');
e.target.classList.add('border-blue-500', 'text-blue-600');
// Show corresponding content
const tabName = e.target.dataset.tab;
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(tabName).classList.add('active');
});
});
// Copy button
document.getElementById('copy-btn').addEventListener('click', () => {
const schema = document.getElementById('schema-output').textContent;
navigator.clipboard.writeText(schema).then(() => {
const feedback = document.getElementById('copy-feedback');
feedback.style.display = 'block';
feedback.classList.add('copy-feedback');
setTimeout(() => {
feedback.style.display = 'none';
feedback.classList.remove('copy-feedback');
}, 2000);
});
});
// Download button
document.getElementById('download-btn').addEventListener('click', () => {
const schema = document.getElementById('schema-output').textContent;
const blob = new Blob([schema], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'schema.json';
a.click();
URL.revokeObjectURL(url);
});
}
// Initialize on load
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>