davanstrien HF Staff Claude commited on
Commit
161b93c
·
1 Parent(s): 169dab9

Rewrite as clean HTML/JS app with dynamic label management

Browse files

- Replaced buggy Gradio implementation with pure HTML/JS
- Dynamic label addition/removal with inline controls
- Clean, modern UI using Tailwind CSS
- Real-time JSON schema generation
- Syntax highlighting with Prism.js
- Tabbed interface for better organization
- Copy to clipboard and download functionality
- Starts with 2 labels, auto-expands as needed

🤖 Generated with Claude Code

Co-Authored-By: Claude <[email protected]>

README.md CHANGED
@@ -1,12 +1,131 @@
1
  ---
2
- title: Json Schema Text Classifier
3
- emoji: 🏆
4
- colorFrom: yellow
5
  colorTo: purple
6
- sdk: gradio
7
- sdk_version: 5.39.0
8
- app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: JSON Schema Generator for Text Classification
3
+ emoji: 🏷️
4
+ colorFrom: blue
5
  colorTo: purple
6
+ sdk: static
7
+ app_file: index.html
 
8
  pinned: false
9
+ license: mit
10
  ---
11
 
12
+ # JSON Schema Generator for Text Classification
13
+
14
+ A simple, user-friendly tool to generate JSON schemas for text classification tasks. Perfect for structured generation with Large Language Models (LLMs).
15
+
16
+ ## 🎯 Purpose
17
+
18
+ This tool helps you create properly formatted JSON schemas that can be used with:
19
+ - **LM Studio** - Structured Output field
20
+ - **OpenAI API** - `response_format` parameter
21
+ - **llama.cpp** - Grammar constraints
22
+ - **Any LLM API** supporting JSON schema validation
23
+
24
+ ## 🚀 Features
25
+
26
+ - **Single-label classification** - Choose one category from a list
27
+ - **Multi-label classification** - Select multiple applicable categories
28
+ - **Live preview** - See your schema and example outputs in real-time
29
+ - **Copy & Download** - Easy export options for your schemas
30
+ - **Beginner-friendly** - No JSON knowledge required
31
+
32
+ ## 📖 How to Use
33
+
34
+ 1. **Select Classification Type**
35
+ - **Single**: For mutually exclusive categories (e.g., sentiment: positive OR negative)
36
+ - **Multi**: For multiple applicable labels (e.g., topics: can be both "sports" AND "politics")
37
+
38
+ 2. **Add Your Labels**
39
+ - Enter the categories you want to classify text into
40
+ - Add optional descriptions for clarity
41
+ - Use the ➕ button to add more labels
42
+
43
+ 3. **Configure Options** (Optional)
44
+ - Change the field name (default: "classification")
45
+ - Set whether the field is required
46
+ - For multi-label: set min/max number of selections
47
+
48
+ 4. **Get Your Schema**
49
+ - Copy the generated JSON schema
50
+ - Download as a .json file
51
+ - See example outputs
52
+
53
+ ## 🔧 Example Use Cases
54
+
55
+ ### Sentiment Analysis
56
+ ```json
57
+ {
58
+ "type": "object",
59
+ "properties": {
60
+ "sentiment": {
61
+ "type": "string",
62
+ "enum": ["positive", "negative", "neutral"]
63
+ }
64
+ },
65
+ "required": ["sentiment"]
66
+ }
67
+ ```
68
+
69
+ ### Topic Classification (Multi-label)
70
+ ```json
71
+ {
72
+ "type": "object",
73
+ "properties": {
74
+ "topics": {
75
+ "type": "array",
76
+ "items": {
77
+ "type": "string",
78
+ "enum": ["technology", "health", "finance", "education", "entertainment"]
79
+ },
80
+ "uniqueItems": true
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ## 🤝 Integration Examples
87
+
88
+ ### LM Studio
89
+ 1. Generate your schema using this tool
90
+ 2. Copy the schema
91
+ 3. In LM Studio, paste into the "Structured Output" field
92
+ 4. The model will only generate JSON matching your schema
93
+
94
+ ### OpenAI API (Python)
95
+ ```python
96
+ import openai
97
+
98
+ # Your generated schema
99
+ response_format = {
100
+ "type": "json_schema",
101
+ "json_schema": {
102
+ "name": "classification",
103
+ "schema": { ... } # Paste your schema here
104
+ }
105
+ }
106
+
107
+ response = openai.chat.completions.create(
108
+ model="gpt-4",
109
+ messages=[...],
110
+ response_format=response_format
111
+ )
112
+ ```
113
+
114
+ ## 📚 Resources
115
+
116
+ - [JSON Schema Documentation](https://json-schema.org/)
117
+ - [OpenAI Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs)
118
+ - [LM Studio Documentation](https://lmstudio.ai/docs)
119
+
120
+ ## 🛠️ Technical Details
121
+
122
+ This tool generates JSON Schema Draft 7 compatible schemas that enforce:
123
+ - Valid JSON structure
124
+ - Type constraints (string, array)
125
+ - Enum restrictions for valid label values
126
+ - Array uniqueness for multi-label classification
127
+ - Optional min/max constraints for array lengths
128
+
129
+ ## 📝 License
130
+
131
+ MIT License - Feel free to use and modify!
examples/multi_label_tags.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "tags": {
6
+ "type": "array",
7
+ "items": {
8
+ "type": "string",
9
+ "enum": [
10
+ "urgent",
11
+ "important",
12
+ "bug",
13
+ "feature",
14
+ "documentation",
15
+ "question",
16
+ "help-wanted",
17
+ "good-first-issue",
18
+ "enhancement",
19
+ "wontfix"
20
+ ]
21
+ },
22
+ "description": "Multiple labels from 10 categories",
23
+ "uniqueItems": true,
24
+ "minItems": 1,
25
+ "maxItems": 5
26
+ }
27
+ },
28
+ "required": ["tags"]
29
+ }
examples/sentiment_analysis.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "sentiment": {
6
+ "type": "string",
7
+ "enum": ["positive", "negative", "neutral"],
8
+ "description": "Classification into one of 3 categories"
9
+ }
10
+ },
11
+ "required": ["sentiment"]
12
+ }
examples/topic_classification.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "topic": {
6
+ "type": "string",
7
+ "enum": [
8
+ "technology",
9
+ "health",
10
+ "finance",
11
+ "education",
12
+ "entertainment",
13
+ "sports",
14
+ "politics",
15
+ "science",
16
+ "business",
17
+ "lifestyle"
18
+ ],
19
+ "description": "Classification into one of 10 categories"
20
+ }
21
+ },
22
+ "required": ["topic"]
23
+ }
index.html ADDED
@@ -0,0 +1,432 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>JSON Schema Generator for Text Classification</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css">
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
11
+ <style>
12
+ .fade-in {
13
+ animation: fadeIn 0.3s ease-in;
14
+ }
15
+ @keyframes fadeIn {
16
+ from { opacity: 0; transform: translateY(-10px); }
17
+ to { opacity: 1; transform: translateY(0); }
18
+ }
19
+ .tab-content {
20
+ display: none;
21
+ }
22
+ .tab-content.active {
23
+ display: block;
24
+ }
25
+ .copy-feedback {
26
+ animation: copyPulse 2s ease-out;
27
+ }
28
+ @keyframes copyPulse {
29
+ 0% { opacity: 0; transform: translateY(10px); }
30
+ 20% { opacity: 1; transform: translateY(0); }
31
+ 80% { opacity: 1; transform: translateY(0); }
32
+ 100% { opacity: 0; transform: translateY(-10px); }
33
+ }
34
+ </style>
35
+ </head>
36
+ <body class="bg-gray-50">
37
+ <div class="container mx-auto px-4 py-8 max-w-6xl">
38
+ <!-- Header -->
39
+ <div class="text-center mb-8">
40
+ <h1 class="text-3xl font-bold text-gray-800 mb-2">JSON Schema Generator for Text Classification</h1>
41
+ <p class="text-gray-600">Generate JSON schemas for structured text classification with LLMs</p>
42
+ </div>
43
+
44
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
45
+ <!-- Left Column - Configuration -->
46
+ <div class="space-y-6">
47
+ <!-- Classification Type -->
48
+ <div class="bg-white rounded-lg shadow-sm p-6">
49
+ <h2 class="text-lg font-semibold mb-4">Classification Type</h2>
50
+ <div class="space-y-3">
51
+ <label class="flex items-center cursor-pointer">
52
+ <input type="radio" name="classification-type" value="single" checked class="mr-3 text-blue-600">
53
+ <div>
54
+ <span class="font-medium">Single Label</span>
55
+ <span class="text-sm text-gray-500 ml-2">One label per text</span>
56
+ </div>
57
+ </label>
58
+ <label class="flex items-center cursor-pointer">
59
+ <input type="radio" name="classification-type" value="multi" class="mr-3 text-blue-600">
60
+ <div>
61
+ <span class="font-medium">Multi Label</span>
62
+ <span class="text-sm text-gray-500 ml-2">Multiple labels allowed</span>
63
+ </div>
64
+ </label>
65
+ </div>
66
+ </div>
67
+
68
+ <!-- Labels -->
69
+ <div class="bg-white rounded-lg shadow-sm p-6">
70
+ <h2 class="text-lg font-semibold mb-4">Labels</h2>
71
+ <p class="text-sm text-gray-600 mb-4">Add the categories you want to classify text into:</p>
72
+
73
+ <div id="labels-container" class="space-y-2">
74
+ <!-- Labels will be dynamically added here -->
75
+ </div>
76
+ </div>
77
+
78
+ <!-- Advanced Options -->
79
+ <details class="bg-white rounded-lg shadow-sm">
80
+ <summary class="p-6 cursor-pointer font-semibold">Advanced Options</summary>
81
+ <div class="px-6 pb-6 space-y-4">
82
+ <div>
83
+ <label class="block text-sm font-medium text-gray-700 mb-1">Field Name</label>
84
+ <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">
85
+ <p class="text-xs text-gray-500 mt-1">JSON key for the classification field</p>
86
+ </div>
87
+
88
+ <div>
89
+ <label class="flex items-center cursor-pointer">
90
+ <input type="checkbox" id="is-required" checked class="mr-2 text-blue-600">
91
+ <span class="text-sm font-medium">Required Field</span>
92
+ </label>
93
+ </div>
94
+
95
+ <div id="multi-options" class="space-y-4" style="display: none;">
96
+ <div>
97
+ <label class="block text-sm font-medium text-gray-700 mb-1">Min Items</label>
98
+ <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">
99
+ </div>
100
+ <div>
101
+ <label class="block text-sm font-medium text-gray-700 mb-1">Max Items</label>
102
+ <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">
103
+ <p class="text-xs text-gray-500 mt-1">0 = no limit</p>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </details>
108
+ </div>
109
+
110
+ <!-- Right Column - Output -->
111
+ <div class="bg-white rounded-lg shadow-sm p-6">
112
+ <!-- Tabs -->
113
+ <div class="border-b border-gray-200 mb-4">
114
+ <nav class="-mb-px flex space-x-8">
115
+ <button class="tab-button py-2 px-1 border-b-2 font-medium text-sm border-blue-500 text-blue-600" data-tab="schema">
116
+ Schema
117
+ </button>
118
+ <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">
119
+ Example
120
+ </button>
121
+ <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">
122
+ How to Use
123
+ </button>
124
+ </nav>
125
+ </div>
126
+
127
+ <!-- Tab Contents -->
128
+ <div id="schema" class="tab-content active">
129
+ <div class="mb-4">
130
+ <pre><code id="schema-output" class="language-json">// Please add at least one label to generate a schema</code></pre>
131
+ </div>
132
+ <div class="flex gap-2">
133
+ <button id="copy-btn" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors">
134
+ 📋 Copy Schema
135
+ </button>
136
+ <button id="download-btn" class="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-colors">
137
+ 💾 Download
138
+ </button>
139
+ <div id="copy-feedback" class="ml-4 py-2 text-green-600 font-medium" style="display: none;">
140
+ ✓ Copied to clipboard!
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <div id="example" class="tab-content">
146
+ <pre><code id="example-output" class="language-json">// Example will appear here</code></pre>
147
+ </div>
148
+
149
+ <div id="how-to-use" class="tab-content prose prose-sm max-w-none">
150
+ <h3 class="text-lg font-semibold mb-3">Integration Guide</h3>
151
+
152
+ <div class="mb-4">
153
+ <h4 class="font-medium mb-2">LM Studio:</h4>
154
+ <ol class="list-decimal list-inside text-sm text-gray-700 space-y-1">
155
+ <li>Copy the generated schema</li>
156
+ <li>Paste into the "Structured Output" field</li>
157
+ <li>The model will only output valid JSON</li>
158
+ </ol>
159
+ </div>
160
+
161
+ <div class="mb-4">
162
+ <h4 class="font-medium mb-2">OpenAI API:</h4>
163
+ <pre class="bg-gray-100 p-3 rounded text-xs"><code>response_format = {
164
+ "type": "json_schema",
165
+ "json_schema": {
166
+ "name": "classification",
167
+ "schema": YOUR_SCHEMA_HERE
168
+ }
169
+ }</code></pre>
170
+ </div>
171
+
172
+ <div>
173
+ <h4 class="font-medium mb-2">Other APIs:</h4>
174
+ <ul class="list-disc list-inside text-sm text-gray-700 space-y-1">
175
+ <li>Use with any API supporting JSON Schema validation</li>
176
+ <li>Check your API documentation for the parameter name</li>
177
+ </ul>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <script>
185
+ // State management
186
+ let labels = [];
187
+ let labelIdCounter = 0;
188
+
189
+ // Initialize with 2 labels
190
+ function init() {
191
+ addLabel('positive');
192
+ addLabel('negative');
193
+ updateSchema();
194
+ setupEventListeners();
195
+ }
196
+
197
+ // Add a new label
198
+ function addLabel(value = '') {
199
+ const id = labelIdCounter++;
200
+ labels.push({ id, value });
201
+
202
+ const container = document.getElementById('labels-container');
203
+ const labelDiv = document.createElement('div');
204
+ labelDiv.className = 'flex gap-2 fade-in';
205
+ labelDiv.id = `label-${id}`;
206
+
207
+ labelDiv.innerHTML = `
208
+ <input type="text"
209
+ value="${value}"
210
+ placeholder="Enter label name"
211
+ class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
212
+ data-label-id="${id}">
213
+ <button onclick="addLabelAfter(${id})"
214
+ class="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors"
215
+ title="Add label after this one">
216
+ +
217
+ </button>
218
+ ${labels.length > 2 ? `
219
+ <button onclick="removeLabel(${id})"
220
+ class="px-3 py-2 bg-red-100 hover:bg-red-200 text-red-700 rounded-md transition-colors"
221
+ title="Remove this label">
222
+ ×
223
+ </button>` : ''}
224
+ `;
225
+
226
+ container.appendChild(labelDiv);
227
+
228
+ // Add event listener to the input
229
+ const input = labelDiv.querySelector('input');
230
+ input.addEventListener('input', (e) => {
231
+ const label = labels.find(l => l.id === id);
232
+ if (label) {
233
+ label.value = e.target.value;
234
+ updateSchema();
235
+
236
+ // Auto-add new label if typing in the last one
237
+ const lastLabel = labels[labels.length - 1];
238
+ if (label.id === lastLabel.id && e.target.value.trim() !== '') {
239
+ addLabel();
240
+ }
241
+ }
242
+ });
243
+ }
244
+
245
+ // Add label after specific position
246
+ function addLabelAfter(afterId) {
247
+ const index = labels.findIndex(l => l.id === afterId);
248
+ const newId = labelIdCounter++;
249
+ labels.splice(index + 1, 0, { id: newId, value: '' });
250
+
251
+ // Rebuild the container
252
+ rebuildLabelsContainer();
253
+ updateSchema();
254
+ }
255
+
256
+ // Remove a label
257
+ function removeLabel(id) {
258
+ if (labels.length <= 2) return;
259
+
260
+ labels = labels.filter(l => l.id !== id);
261
+ document.getElementById(`label-${id}`).remove();
262
+ updateSchema();
263
+ }
264
+
265
+ // Rebuild labels container
266
+ function rebuildLabelsContainer() {
267
+ const container = document.getElementById('labels-container');
268
+ container.innerHTML = '';
269
+ const currentLabels = [...labels];
270
+ labels = [];
271
+ currentLabels.forEach(label => {
272
+ addLabel(label.value);
273
+ });
274
+ }
275
+
276
+ // Generate JSON schema
277
+ function generateSchema() {
278
+ const classificationType = document.querySelector('input[name="classification-type"]:checked').value;
279
+ const fieldName = document.getElementById('field-name').value;
280
+ const isRequired = document.getElementById('is-required').checked;
281
+ const minItems = parseInt(document.getElementById('min-items').value) || 0;
282
+ const maxItems = parseInt(document.getElementById('max-items').value) || 0;
283
+
284
+ // Get valid labels
285
+ const validLabels = labels
286
+ .map(l => l.value.trim())
287
+ .filter(v => v !== '');
288
+
289
+ if (validLabels.length === 0) {
290
+ return {
291
+ schema: '// Please add at least one label to generate a schema',
292
+ example: '// Example will appear here'
293
+ };
294
+ }
295
+
296
+ // Check for duplicates
297
+ if (new Set(validLabels).size !== validLabels.length) {
298
+ return {
299
+ schema: '// Error: Duplicate labels found. Each label must be unique.',
300
+ example: '// Please fix duplicate labels'
301
+ };
302
+ }
303
+
304
+ // Build schema
305
+ const schema = {
306
+ "$schema": "http://json-schema.org/draft-07/schema#",
307
+ "type": "object",
308
+ "properties": {}
309
+ };
310
+
311
+ if (classificationType === 'single') {
312
+ schema.properties[fieldName] = {
313
+ "type": "string",
314
+ "enum": validLabels,
315
+ "description": `Classification into one of ${validLabels.length} categories`
316
+ };
317
+ } else {
318
+ schema.properties[fieldName] = {
319
+ "type": "array",
320
+ "items": {
321
+ "type": "string",
322
+ "enum": validLabels
323
+ },
324
+ "description": `Multiple labels from ${validLabels.length} categories`,
325
+ "uniqueItems": true
326
+ };
327
+
328
+ if (minItems > 0) {
329
+ schema.properties[fieldName].minItems = minItems;
330
+ }
331
+ if (maxItems > 0) {
332
+ schema.properties[fieldName].maxItems = maxItems;
333
+ }
334
+ }
335
+
336
+ if (isRequired) {
337
+ schema.required = [fieldName];
338
+ }
339
+
340
+ // Generate example
341
+ const example = {};
342
+ if (classificationType === 'single') {
343
+ example[fieldName] = validLabels[0];
344
+ } else {
345
+ example[fieldName] = validLabels.slice(0, 2);
346
+ }
347
+
348
+ return {
349
+ schema: JSON.stringify(schema, null, 2),
350
+ example: JSON.stringify(example, null, 2)
351
+ };
352
+ }
353
+
354
+ // Update schema display
355
+ function updateSchema() {
356
+ const result = generateSchema();
357
+ document.getElementById('schema-output').textContent = result.schema;
358
+ document.getElementById('example-output').textContent = result.example;
359
+
360
+ // Re-highlight syntax
361
+ Prism.highlightAll();
362
+ }
363
+
364
+ // Setup event listeners
365
+ function setupEventListeners() {
366
+ // Classification type change
367
+ document.querySelectorAll('input[name="classification-type"]').forEach(radio => {
368
+ radio.addEventListener('change', (e) => {
369
+ document.getElementById('multi-options').style.display =
370
+ e.target.value === 'multi' ? 'block' : 'none';
371
+ updateSchema();
372
+ });
373
+ });
374
+
375
+ // Other inputs
376
+ document.getElementById('field-name').addEventListener('input', updateSchema);
377
+ document.getElementById('is-required').addEventListener('change', updateSchema);
378
+ document.getElementById('min-items').addEventListener('input', updateSchema);
379
+ document.getElementById('max-items').addEventListener('input', updateSchema);
380
+
381
+ // Tab switching
382
+ document.querySelectorAll('.tab-button').forEach(button => {
383
+ button.addEventListener('click', (e) => {
384
+ // Update active tab
385
+ document.querySelectorAll('.tab-button').forEach(b => {
386
+ b.classList.remove('border-blue-500', 'text-blue-600');
387
+ b.classList.add('border-transparent', 'text-gray-500');
388
+ });
389
+ e.target.classList.remove('border-transparent', 'text-gray-500');
390
+ e.target.classList.add('border-blue-500', 'text-blue-600');
391
+
392
+ // Show corresponding content
393
+ const tabName = e.target.dataset.tab;
394
+ document.querySelectorAll('.tab-content').forEach(content => {
395
+ content.classList.remove('active');
396
+ });
397
+ document.getElementById(tabName).classList.add('active');
398
+ });
399
+ });
400
+
401
+ // Copy button
402
+ document.getElementById('copy-btn').addEventListener('click', () => {
403
+ const schema = document.getElementById('schema-output').textContent;
404
+ navigator.clipboard.writeText(schema).then(() => {
405
+ const feedback = document.getElementById('copy-feedback');
406
+ feedback.style.display = 'block';
407
+ feedback.classList.add('copy-feedback');
408
+ setTimeout(() => {
409
+ feedback.style.display = 'none';
410
+ feedback.classList.remove('copy-feedback');
411
+ }, 2000);
412
+ });
413
+ });
414
+
415
+ // Download button
416
+ document.getElementById('download-btn').addEventListener('click', () => {
417
+ const schema = document.getElementById('schema-output').textContent;
418
+ const blob = new Blob([schema], { type: 'application/json' });
419
+ const url = URL.createObjectURL(blob);
420
+ const a = document.createElement('a');
421
+ a.href = url;
422
+ a.download = 'schema.json';
423
+ a.click();
424
+ URL.revokeObjectURL(url);
425
+ });
426
+ }
427
+
428
+ // Initialize on load
429
+ document.addEventListener('DOMContentLoaded', init);
430
+ </script>
431
+ </body>
432
+ </html>
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ # No requirements needed for static HTML app