Download index.html from davanstrien/json-schema-text-classifier: direct link, hf CLI and curl.
- Browser
- Download file 19.7 kB
-
https://huggingface.co/spaces/davanstrien/json-schema-text-classifier/resolve/main/index.html
- Command line
-
hf download hf://spaces/davanstrien/json-schema-text-classifier/index.html
-
curl -L -o index.html https://huggingface.co/spaces/davanstrien/json-schema-text-classifier/resolve/main/index.html
19.7 kB
| <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> |