akhaliq HF Staff commited on
Commit
7771b05
·
1 Parent(s): 74052d0
backend_deploy.py CHANGED
@@ -38,6 +38,246 @@ def parse_html_code(code: str) -> str:
38
  return code
39
 
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  def parse_transformers_js_output(code: str) -> Dict[str, str]:
42
  """Parse transformers.js output into separate files (index.html, index.js, style.css)
43
 
@@ -676,8 +916,9 @@ def deploy_to_huggingface_space(
676
  (temp_path / "index.html").write_text(html_code, encoding='utf-8')
677
 
678
  elif language == "comfyui":
679
- # ComfyUI is JSON, wrap in HTML viewer
680
- (temp_path / "index.html").write_text(code, encoding='utf-8')
 
681
 
682
  elif language in ["gradio", "streamlit"]:
683
  files = parse_multi_file_python_output(code)
 
38
  return code
39
 
40
 
41
+ def prettify_comfyui_json_for_html(json_content: str) -> str:
42
+ """Convert ComfyUI JSON to stylized HTML display with download button"""
43
+ try:
44
+ # Parse and prettify the JSON
45
+ parsed_json = json.loads(json_content)
46
+ prettified_json = json.dumps(parsed_json, indent=2, ensure_ascii=False)
47
+
48
+ # Create Apple-style HTML wrapper
49
+ html_content = f"""<!DOCTYPE html>
50
+ <html lang="en">
51
+ <head>
52
+ <meta charset="UTF-8">
53
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
54
+ <title>ComfyUI Workflow</title>
55
+ <style>
56
+ * {{
57
+ margin: 0;
58
+ padding: 0;
59
+ box-sizing: border-box;
60
+ }}
61
+ body {{
62
+ font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
63
+ background-color: #000000;
64
+ color: #f5f5f7;
65
+ line-height: 1.6;
66
+ padding: 20px;
67
+ min-height: 100vh;
68
+ }}
69
+ .container {{
70
+ max-width: 1200px;
71
+ margin: 0 auto;
72
+ }}
73
+ .header {{
74
+ text-align: center;
75
+ margin-bottom: 40px;
76
+ padding: 40px 20px;
77
+ }}
78
+ .header h1 {{
79
+ font-size: 48px;
80
+ font-weight: 600;
81
+ color: #ffffff;
82
+ margin-bottom: 12px;
83
+ letter-spacing: -0.02em;
84
+ }}
85
+ .header p {{
86
+ font-size: 18px;
87
+ color: #86868b;
88
+ font-weight: 400;
89
+ }}
90
+ .controls {{
91
+ display: flex;
92
+ gap: 12px;
93
+ margin-bottom: 24px;
94
+ justify-content: center;
95
+ }}
96
+ .btn {{
97
+ padding: 12px 24px;
98
+ border: none;
99
+ border-radius: 24px;
100
+ font-size: 14px;
101
+ font-weight: 500;
102
+ cursor: pointer;
103
+ transition: all 0.2s;
104
+ font-family: inherit;
105
+ }}
106
+ .btn-primary {{
107
+ background: #ffffff;
108
+ color: #000000;
109
+ }}
110
+ .btn-primary:hover {{
111
+ background: #f5f5f7;
112
+ transform: scale(0.98);
113
+ }}
114
+ .btn-secondary {{
115
+ background: #1d1d1f;
116
+ color: #f5f5f7;
117
+ border: 1px solid #424245;
118
+ }}
119
+ .btn-secondary:hover {{
120
+ background: #2d2d2f;
121
+ transform: scale(0.98);
122
+ }}
123
+ .json-container {{
124
+ background-color: #1d1d1f;
125
+ border-radius: 16px;
126
+ padding: 32px;
127
+ overflow-x: auto;
128
+ border: 1px solid #424245;
129
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
130
+ }}
131
+ pre {{
132
+ margin: 0;
133
+ font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
134
+ font-size: 13px;
135
+ line-height: 1.6;
136
+ white-space: pre-wrap;
137
+ word-wrap: break-word;
138
+ }}
139
+ .json-key {{
140
+ color: #9cdcfe;
141
+ }}
142
+ .json-string {{
143
+ color: #ce9178;
144
+ }}
145
+ .json-number {{
146
+ color: #b5cea8;
147
+ }}
148
+ .json-boolean {{
149
+ color: #569cd6;
150
+ }}
151
+ .json-null {{
152
+ color: #569cd6;
153
+ }}
154
+ .success {{
155
+ color: #30d158;
156
+ }}
157
+ @media (max-width: 768px) {{
158
+ .header h1 {{
159
+ font-size: 32px;
160
+ }}
161
+ .controls {{
162
+ flex-direction: column;
163
+ }}
164
+ .json-container {{
165
+ padding: 20px;
166
+ }}
167
+ }}
168
+ </style>
169
+ </head>
170
+ <body>
171
+ <div class="container">
172
+ <div class="header">
173
+ <h1>ComfyUI Workflow</h1>
174
+ <p>View and download your workflow JSON</p>
175
+ </div>
176
+
177
+ <div class="controls">
178
+ <button class="btn btn-primary" onclick="downloadJSON()">Download JSON</button>
179
+ <button class="btn btn-secondary" onclick="copyToClipboard()">Copy to Clipboard</button>
180
+ </div>
181
+
182
+ <div class="json-container">
183
+ <pre id="json-content">{prettified_json}</pre>
184
+ </div>
185
+ </div>
186
+
187
+ <script>
188
+ function copyToClipboard() {{
189
+ const jsonContent = document.getElementById('json-content').textContent;
190
+ navigator.clipboard.writeText(jsonContent).then(() => {{
191
+ const btn = event.target;
192
+ const originalText = btn.textContent;
193
+ btn.textContent = 'Copied!';
194
+ btn.classList.add('success');
195
+ setTimeout(() => {{
196
+ btn.textContent = originalText;
197
+ btn.classList.remove('success');
198
+ }}, 2000);
199
+ }}).catch(err => {{
200
+ alert('Failed to copy to clipboard');
201
+ }});
202
+ }}
203
+
204
+ function downloadJSON() {{
205
+ const jsonContent = document.getElementById('json-content').textContent;
206
+ const blob = new Blob([jsonContent], {{ type: 'application/json' }});
207
+ const url = URL.createObjectURL(blob);
208
+ const a = document.createElement('a');
209
+ a.href = url;
210
+ a.download = 'comfyui_workflow.json';
211
+ document.body.appendChild(a);
212
+ a.click();
213
+ document.body.removeChild(a);
214
+ URL.revokeObjectURL(url);
215
+
216
+ const btn = event.target;
217
+ const originalText = btn.textContent;
218
+ btn.textContent = 'Downloaded!';
219
+ btn.classList.add('success');
220
+ setTimeout(() => {{
221
+ btn.textContent = originalText;
222
+ btn.classList.remove('success');
223
+ }}, 2000);
224
+ }}
225
+
226
+ // Add syntax highlighting
227
+ function highlightJSON() {{
228
+ const content = document.getElementById('json-content');
229
+ let html = content.innerHTML;
230
+
231
+ // Highlight different JSON elements
232
+ html = html.replace(/"([^"]+)":/g, '<span class="json-key">"$1":</span>');
233
+ html = html.replace(/: "([^"]*)"/g, ': <span class="json-string">"$1"</span>');
234
+ html = html.replace(/: (-?\\d+\\.?\\d*)/g, ': <span class="json-number">$1</span>');
235
+ html = html.replace(/: (true|false)/g, ': <span class="json-boolean">$1</span>');
236
+ html = html.replace(/: null/g, ': <span class="json-null">null</span>');
237
+
238
+ content.innerHTML = html;
239
+ }}
240
+
241
+ // Apply syntax highlighting after page load
242
+ window.addEventListener('load', highlightJSON);
243
+ </script>
244
+ </body>
245
+ </html>"""
246
+ return html_content
247
+ except json.JSONDecodeError:
248
+ # If it's not valid JSON, return as-is wrapped in basic HTML
249
+ return f"""<!DOCTYPE html>
250
+ <html lang="en">
251
+ <head>
252
+ <meta charset="UTF-8">
253
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
254
+ <title>ComfyUI Workflow</title>
255
+ <style>
256
+ body {{
257
+ font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif;
258
+ background-color: #000000;
259
+ color: #f5f5f7;
260
+ padding: 40px;
261
+ }}
262
+ pre {{
263
+ background: #1d1d1f;
264
+ padding: 24px;
265
+ border-radius: 12px;
266
+ overflow-x: auto;
267
+ }}
268
+ </style>
269
+ </head>
270
+ <body>
271
+ <h1>ComfyUI Workflow</h1>
272
+ <p>Error: Invalid JSON format</p>
273
+ <pre>{json_content}</pre>
274
+ </body>
275
+ </html>"""
276
+ except Exception as e:
277
+ print(f"Error prettifying ComfyUI JSON: {e}")
278
+ return json_content
279
+
280
+
281
  def parse_transformers_js_output(code: str) -> Dict[str, str]:
282
  """Parse transformers.js output into separate files (index.html, index.js, style.css)
283
 
 
916
  (temp_path / "index.html").write_text(html_code, encoding='utf-8')
917
 
918
  elif language == "comfyui":
919
+ # ComfyUI is JSON, wrap in stylized HTML viewer with download button
920
+ html_code = prettify_comfyui_json_for_html(code)
921
+ (temp_path / "index.html").write_text(html_code, encoding='utf-8')
922
 
923
  elif language in ["gradio", "streamlit"]:
924
  files = parse_multi_file_python_output(code)
frontend/src/components/ControlPanel.tsx CHANGED
@@ -127,6 +127,7 @@ export default function ControlPanel({
127
  const formatLanguageName = (lang: Language) => {
128
  if (lang === 'html') return 'HTML';
129
  if (lang === 'transformers.js') return 'Transformers.js';
 
130
  return lang.charAt(0).toUpperCase() + lang.slice(1);
131
  };
132
 
@@ -201,13 +202,13 @@ export default function ControlPanel({
201
  setShowModelDropdown(!showModelDropdown);
202
  setShowLanguageDropdown(false);
203
  }}
204
- disabled={isGenerating || isLoading}
205
  className="w-full px-3 py-2 bg-[#1d1d1f] text-[#f5f5f7] text-sm border border-[#424245]/50 rounded-lg focus:outline-none focus:border-[#424245] disabled:opacity-40 flex items-center justify-between hover:bg-[#2d2d2f] transition-colors"
206
  >
207
  <span className="truncate">
208
  {isLoading
209
  ? 'Loading...'
210
- : models.find(m => m.id === selectedModel)?.name || 'Select model'
211
  }
212
  </span>
213
  <svg
@@ -222,7 +223,7 @@ export default function ControlPanel({
222
  </button>
223
 
224
  {/* Model Dropdown Tray */}
225
- {showModelDropdown && !isLoading && models.length > 0 && (
226
  <div className="absolute z-50 w-full mt-1 bg-[#1d1d1f] border border-[#424245] rounded-lg shadow-xl overflow-hidden">
227
  <div className="max-h-96 overflow-y-auto py-1">
228
  {models.map((model) => (
 
127
  const formatLanguageName = (lang: Language) => {
128
  if (lang === 'html') return 'HTML';
129
  if (lang === 'transformers.js') return 'Transformers.js';
130
+ if (lang === 'comfyui') return 'ComfyUI';
131
  return lang.charAt(0).toUpperCase() + lang.slice(1);
132
  };
133
 
 
202
  setShowModelDropdown(!showModelDropdown);
203
  setShowLanguageDropdown(false);
204
  }}
205
+ disabled={isGenerating}
206
  className="w-full px-3 py-2 bg-[#1d1d1f] text-[#f5f5f7] text-sm border border-[#424245]/50 rounded-lg focus:outline-none focus:border-[#424245] disabled:opacity-40 flex items-center justify-between hover:bg-[#2d2d2f] transition-colors"
207
  >
208
  <span className="truncate">
209
  {isLoading
210
  ? 'Loading...'
211
+ : models.find(m => m.id === selectedModel)?.name || selectedModel || 'Select model'
212
  }
213
  </span>
214
  <svg
 
223
  </button>
224
 
225
  {/* Model Dropdown Tray */}
226
+ {showModelDropdown && models.length > 0 && (
227
  <div className="absolute z-50 w-full mt-1 bg-[#1d1d1f] border border-[#424245] rounded-lg shadow-xl overflow-hidden">
228
  <div className="max-h-96 overflow-y-auto py-1">
229
  {models.map((model) => (
frontend/src/components/LandingPage.tsx CHANGED
@@ -173,6 +173,7 @@ export default function LandingPage({
173
  const formatLanguageName = (lang: Language) => {
174
  if (lang === 'html') return 'HTML';
175
  if (lang === 'transformers.js') return 'Transformers.js';
 
176
  return lang.charAt(0).toUpperCase() + lang.slice(1);
177
  };
178
 
@@ -354,13 +355,12 @@ export default function LandingPage({
354
  setShowModelDropdown(!showModelDropdown);
355
  setShowLanguageDropdown(false);
356
  }}
357
- disabled={isLoading}
358
- className="px-3 py-1.5 bg-[#1d1d1f] text-[#f5f5f7] text-xs border border-[#424245] rounded-full hover:bg-[#2d2d2f] transition-all disabled:opacity-50 flex items-center gap-1.5 max-w-[200px] font-medium"
359
  >
360
  <span className="truncate">
361
  {isLoading
362
  ? '...'
363
- : models.find(m => m.id === selectedModel)?.name || 'Model'
364
  }
365
  </span>
366
  <svg
@@ -375,7 +375,7 @@ export default function LandingPage({
375
  </button>
376
 
377
  {/* Model Dropdown Menu */}
378
- {showModelDropdown && !isLoading && models.length > 0 && (
379
  <div className="absolute bottom-full left-0 mb-2 w-80 bg-[#1d1d1f] border border-[#424245] rounded-xl shadow-2xl overflow-hidden backdrop-blur-xl">
380
  <div className="max-h-96 overflow-y-auto py-1">
381
  {models.map((model) => (
 
173
  const formatLanguageName = (lang: Language) => {
174
  if (lang === 'html') return 'HTML';
175
  if (lang === 'transformers.js') return 'Transformers.js';
176
+ if (lang === 'comfyui') return 'ComfyUI';
177
  return lang.charAt(0).toUpperCase() + lang.slice(1);
178
  };
179
 
 
355
  setShowModelDropdown(!showModelDropdown);
356
  setShowLanguageDropdown(false);
357
  }}
358
+ className="px-3 py-1.5 bg-[#1d1d1f] text-[#f5f5f7] text-xs border border-[#424245] rounded-full hover:bg-[#2d2d2f] transition-all flex items-center gap-1.5 max-w-[200px] font-medium"
 
359
  >
360
  <span className="truncate">
361
  {isLoading
362
  ? '...'
363
+ : models.find(m => m.id === selectedModel)?.name || selectedModel || 'Model'
364
  }
365
  </span>
366
  <svg
 
375
  </button>
376
 
377
  {/* Model Dropdown Menu */}
378
+ {showModelDropdown && models.length > 0 && (
379
  <div className="absolute bottom-full left-0 mb-2 w-80 bg-[#1d1d1f] border border-[#424245] rounded-xl shadow-2xl overflow-hidden backdrop-blur-xl">
380
  <div className="max-h-96 overflow-y-auto py-1">
381
  {models.map((model) => (