Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Add HTML+TailwindCSS redesigner with comprehensive system prompt
Browse files- Built Gradio interface for redesigning existing HTML components
- Advanced HTML extraction logic with proper element parsing
- System prompt optimized for spacing, responsive design, and framework preservation
- Supports React/Vue/Svelte/Angular syntax preservation
- Dark mode detection and preservation when present
- Mobile-first responsive design implementation
- Color scheme and typography preservation from input
- Temperature set to 0.8 for creative design variations
app.py
ADDED
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import requests
|
3 |
+
import json
|
4 |
+
import os
|
5 |
+
import re
|
6 |
+
|
7 |
+
SYSTEM_PROMPT = """You are an expert UI/UX designer specializing in redesigning HTML components with TailwindCSS. Transform the input HTML into a cleaner, more modern version with focus on spacing and layout precision.
|
8 |
+
|
9 |
+
Core Design Philosophy:
|
10 |
+
- SPACING FIRST: Prioritize proper spacing over complex styling
|
11 |
+
- PRESERVE EXISTING: Maintain the original color scheme and fonts when they exist
|
12 |
+
- SIMPLE & CLEAN: Focus on clean, minimal designs rather than overly decorative ones
|
13 |
+
- FLEXIBLE LAYOUTS: Use flexbox with proper flex properties for better control
|
14 |
+
|
15 |
+
Spacing & Layout Rules:
|
16 |
+
- Use consistent spacing scales: p-4, p-6, p-8 for padding; m-4, m-6, m-8 for margins
|
17 |
+
- Apply gap-4, gap-6, or gap-8 for flex/grid container spacing
|
18 |
+
- Use space-y-4, space-y-6 for vertical stacking
|
19 |
+
- Add flex-none to elements that should NOT stretch (buttons, labels, icons, fixed-width items)
|
20 |
+
- Use flex-1 or flex-auto only for elements that should grow/stretch
|
21 |
+
- Prefer specific widths (w-32, w-48, w-64) over arbitrary values
|
22 |
+
|
23 |
+
Color & Typography Preservation:
|
24 |
+
- ANALYZE the input HTML for existing color schemes (bg-*, text-*, border-*)
|
25 |
+
- REUSE those exact color classes in the redesigned version
|
26 |
+
- If input uses blue-500, continue using blue-* variants throughout
|
27 |
+
- If input has specific font classes (font-medium, text-lg), maintain that typography scale
|
28 |
+
- Only introduce new colors if the original has none or uses basic colors
|
29 |
+
|
30 |
+
Dark Mode Support:
|
31 |
+
- DETECT dark mode classes in input (dark:*, bg-gray-900, text-white, etc.)
|
32 |
+
- If input HAS dark mode classes, PRESERVE and EXTEND them in the redesign
|
33 |
+
- If input has dark:bg-gray-800, continue using dark:* variants throughout
|
34 |
+
- If input has NO dark mode classes, DO NOT add any dark mode styling
|
35 |
+
- Maintain the same dark mode approach as the original (toggle-based or system-based)
|
36 |
+
|
37 |
+
Design Enhancement Guidelines:
|
38 |
+
- Improve visual hierarchy with better spacing, not more colors
|
39 |
+
- Add subtle shadows (shadow-sm, shadow-md) for depth
|
40 |
+
- Use rounded corners appropriately (rounded-lg, rounded-xl)
|
41 |
+
- Add hover states with opacity or subtle color shifts
|
42 |
+
- Implement focus states with ring utilities
|
43 |
+
- Use border utilities for clean separation
|
44 |
+
- Apply proper contrast ratios for accessibility
|
45 |
+
|
46 |
+
Layout Techniques:
|
47 |
+
- Use flex with gap instead of space-x/space-y when possible
|
48 |
+
- Apply justify-between, justify-center, items-center for alignment
|
49 |
+
- Use flex-none for buttons, icons, and fixed elements
|
50 |
+
- Apply max-w-* for content width constraints
|
51 |
+
- Use grid for complex layouts, flex for simple ones
|
52 |
+
|
53 |
+
Responsive Design Rules:
|
54 |
+
- ALWAYS ensure mobile-first responsive design
|
55 |
+
- Use responsive breakpoints: sm:, md:, lg:, xl: appropriately
|
56 |
+
- Stack elements vertically on mobile, arrange horizontally on larger screens
|
57 |
+
- Apply responsive padding: p-4 sm:p-6 lg:p-8
|
58 |
+
- Use responsive text sizes: text-sm md:text-base lg:text-lg
|
59 |
+
- Implement responsive flexbox: flex-col md:flex-row
|
60 |
+
- Add responsive gaps: gap-4 md:gap-6 lg:gap-8
|
61 |
+
- Ensure buttons and inputs are touch-friendly on mobile (min-h-10, p-3)
|
62 |
+
- Use responsive widths: w-full md:w-auto or w-full md:w-1/2
|
63 |
+
- Apply responsive margins: mt-4 md:mt-0 for layout adjustments
|
64 |
+
|
65 |
+
Technical Requirements:
|
66 |
+
- Return ONLY the redesigned HTML code
|
67 |
+
- PRESERVE ALL FUNCTIONALITY: Keep every link (href), image (src), form action, onclick, etc.
|
68 |
+
- MAINTAIN ALL ATTRIBUTES: Preserve all IDs, classes, data-*, aria-*, name, value, placeholder, etc.
|
69 |
+
- KEEP ALL CONTENT: Preserve all text content, alt texts, button labels exactly as provided
|
70 |
+
- DO NOT REMOVE: Never remove links, images, inputs, buttons, or any functional elements
|
71 |
+
- DO NOT CHANGE: Keep all URLs, image sources, form targets, and JavaScript references unchanged
|
72 |
+
- ENHANCE ONLY STYLING: Only modify TailwindCSS classes for visual improvements
|
73 |
+
- PRESERVE STRUCTURE: Maintain the same HTML element hierarchy and nesting
|
74 |
+
|
75 |
+
Critical Preservation Rules:
|
76 |
+
- If input has <a href="...">Link</a>, output must have identical href and link text
|
77 |
+
- If input has <img src="..." alt="...">, output must preserve exact src and alt
|
78 |
+
- If input has form inputs with name/value/placeholder, keep them exactly the same
|
79 |
+
- If input has buttons with onclick or type attributes, preserve them completely
|
80 |
+
- If input has any data attributes (data-*), maintain them unchanged
|
81 |
+
- All original text content must remain identical
|
82 |
+
|
83 |
+
Framework-Specific Preservation (React/Vue/Svelte/Angular):
|
84 |
+
- REACT: Preserve all JSX syntax: {variable}, {function()}, className, onClick, onChange, etc.
|
85 |
+
- VUE: Preserve all Vue directives: v-if, v-for, v-model, @click, :class, {{interpolation}}, etc.
|
86 |
+
- SVELTE: Preserve all Svelte syntax: {#if}, {#each}, on:click, bind:value, {variable}, etc.
|
87 |
+
- ANGULAR: Preserve all Angular syntax: *ngIf, *ngFor, (click), [class], {{interpolation}}, etc.
|
88 |
+
- ALPINE.JS: Preserve all Alpine directives: x-data, x-show, x-if, @click, :class, etc.
|
89 |
+
- HTMX: Preserve all HTMX attributes: hx-get, hx-post, hx-target, hx-swap, etc.
|
90 |
+
- Keep all curly braces, template literals, and dynamic expressions unchanged
|
91 |
+
- Maintain all event handlers and binding syntax exactly as provided
|
92 |
+
|
93 |
+
Focus on creating clean, spacious, and well-proportioned designs that feel premium through proper spacing rather than complex styling, while ensuring zero functional changes."""
|
94 |
+
|
95 |
+
def extract_html_from_response(response_text):
|
96 |
+
"""Extract HTML code from model response"""
|
97 |
+
|
98 |
+
# First, try to find HTML within code blocks
|
99 |
+
html_pattern = r'```(?:html)?\s*(.*?)```'
|
100 |
+
matches = re.findall(html_pattern, response_text, re.DOTALL | re.IGNORECASE)
|
101 |
+
|
102 |
+
if matches:
|
103 |
+
return matches[0].strip()
|
104 |
+
|
105 |
+
# If no code blocks, look for HTML tags in the response
|
106 |
+
if re.search(r'<[^>]+>', response_text):
|
107 |
+
# Find the first HTML tag
|
108 |
+
html_start = re.search(r'<', response_text)
|
109 |
+
if not html_start:
|
110 |
+
raise ValueError("No HTML content found in the response")
|
111 |
+
|
112 |
+
# Start from the first HTML tag
|
113 |
+
content_from_first_tag = response_text[html_start.start():]
|
114 |
+
|
115 |
+
# Split into lines and process
|
116 |
+
lines = content_from_first_tag.split('\n')
|
117 |
+
html_lines = []
|
118 |
+
|
119 |
+
for line in lines:
|
120 |
+
line_stripped = line.strip()
|
121 |
+
|
122 |
+
# Skip empty lines at the beginning
|
123 |
+
if not line_stripped and not html_lines:
|
124 |
+
continue
|
125 |
+
|
126 |
+
# Stop if we hit obvious explanation text after we have HTML
|
127 |
+
if html_lines and line_stripped:
|
128 |
+
# Check if this line looks like explanation rather than HTML
|
129 |
+
if not re.search(r'<[^>]*>', line_stripped):
|
130 |
+
# If it's a long explanatory sentence, stop here
|
131 |
+
if len(line_stripped) > 80 or any(phrase in line_stripped.lower() for phrase in [
|
132 |
+
'this creates', 'this provides', 'the form', 'this design', 'here\'s', 'this code',
|
133 |
+
'explanation:', 'note:', 'features:', 'improvements:'
|
134 |
+
]):
|
135 |
+
break
|
136 |
+
|
137 |
+
html_lines.append(line.rstrip())
|
138 |
+
|
139 |
+
# If we have HTML and hit a line that ends with a period and looks like explanation, stop
|
140 |
+
if html_lines and line_stripped.endswith('.') and len(line_stripped) > 40:
|
141 |
+
if not re.search(r'<[^>]*>', line_stripped):
|
142 |
+
html_lines.pop() # Remove the explanatory line
|
143 |
+
break
|
144 |
+
|
145 |
+
# Join and clean up
|
146 |
+
html_content = '\n'.join(html_lines).strip()
|
147 |
+
|
148 |
+
# Remove any trailing text after the last complete HTML tag
|
149 |
+
# Find the last complete tag (either closing tag or self-closing)
|
150 |
+
last_complete_tag = None
|
151 |
+
for match in re.finditer(r'<[^>]+>', html_content):
|
152 |
+
tag = match.group()
|
153 |
+
if tag.startswith('</') or tag.endswith('/>') or any(void in tag.lower() for void in ['<input', '<img', '<br', '<hr', '<meta', '<link']):
|
154 |
+
last_complete_tag = match
|
155 |
+
|
156 |
+
if last_complete_tag:
|
157 |
+
# Keep everything up to and including the last complete tag
|
158 |
+
html_content = html_content[:last_complete_tag.end()]
|
159 |
+
|
160 |
+
return html_content
|
161 |
+
|
162 |
+
# If no HTML found, return error
|
163 |
+
raise ValueError("No HTML content found in the response")
|
164 |
+
|
165 |
+
def chat_with_deepseek(message, history):
|
166 |
+
"""Send message to DeepSeek-V3 model via HuggingFace router"""
|
167 |
+
|
168 |
+
# Check if HF_TOKEN is set
|
169 |
+
hf_token = os.getenv("HF_TOKEN")
|
170 |
+
if not hf_token:
|
171 |
+
return "Error: HF_TOKEN environment variable not set. Please set your HuggingFace token."
|
172 |
+
|
173 |
+
# Prepare the API request
|
174 |
+
url = "https://router.huggingface.co/sambanova/v1/chat/completions"
|
175 |
+
headers = {
|
176 |
+
"Authorization": f"Bearer {hf_token}",
|
177 |
+
"Content-Type": "application/json",
|
178 |
+
}
|
179 |
+
|
180 |
+
# Build conversation history for the API with system prompt
|
181 |
+
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
|
182 |
+
|
183 |
+
for human, assistant in history:
|
184 |
+
messages.append({"role": "user", "content": human})
|
185 |
+
messages.append({"role": "assistant", "content": assistant})
|
186 |
+
messages.append({"role": "user", "content": message})
|
187 |
+
|
188 |
+
data = {"messages": messages, "model": "DeepSeek-V3-0324", "stream": False, "temperature": 0.8}
|
189 |
+
|
190 |
+
try:
|
191 |
+
response = requests.post(url, headers=headers, json=data, timeout=30)
|
192 |
+
response.raise_for_status()
|
193 |
+
|
194 |
+
result = response.json()
|
195 |
+
response_content = result["choices"][0]["message"]["content"]
|
196 |
+
|
197 |
+
# Extract HTML from the response
|
198 |
+
try:
|
199 |
+
html_content = extract_html_from_response(response_content)
|
200 |
+
# Wrap in code blocks to prevent HTML rendering
|
201 |
+
return f"```html\n{html_content}\n```"
|
202 |
+
except ValueError as e:
|
203 |
+
return f"Error: {str(e)}. Response was: {response_content[:200]}..."
|
204 |
+
|
205 |
+
except requests.exceptions.RequestException as e:
|
206 |
+
return f"Error: Failed to connect to API - {str(e)}"
|
207 |
+
except KeyError as e:
|
208 |
+
return f"Error: Unexpected API response format - {str(e)}"
|
209 |
+
except Exception as e:
|
210 |
+
return f"Error: {str(e)}"
|
211 |
+
|
212 |
+
|
213 |
+
# Custom CSS for better HTML code display
|
214 |
+
custom_css = """
|
215 |
+
<style>
|
216 |
+
/* Better formatting for HTML code display */
|
217 |
+
.message-wrap .prose {
|
218 |
+
max-width: none !important;
|
219 |
+
}
|
220 |
+
.message-wrap pre {
|
221 |
+
white-space: pre-wrap !important;
|
222 |
+
word-break: break-all !important;
|
223 |
+
background: #1e1e1e !important;
|
224 |
+
color: #d4d4d4 !important;
|
225 |
+
padding: 16px !important;
|
226 |
+
border-radius: 8px !important;
|
227 |
+
border: 1px solid #333 !important;
|
228 |
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace !important;
|
229 |
+
font-size: 14px !important;
|
230 |
+
line-height: 1.5 !important;
|
231 |
+
}
|
232 |
+
</style>
|
233 |
+
"""
|
234 |
+
|
235 |
+
# Create Gradio interface
|
236 |
+
demo = gr.ChatInterface(
|
237 |
+
fn=chat_with_deepseek,
|
238 |
+
title="HTML+TailwindCSS Redesigner",
|
239 |
+
description="Paste your existing HTML+TailwindCSS code and get a beautifully redesigned version with modern aesthetics and improved UX. The output will be pure HTML code with enhanced TailwindCSS styling.",
|
240 |
+
examples=[
|
241 |
+
'<div class="p-4 border"><h1 class="text-xl">Welcome</h1><p>This is a basic card</p><button class="bg-blue-500 text-white px-4 py-2">Click me</button></div>',
|
242 |
+
'<form class="space-y-4"><input type="email" placeholder="Email" class="border p-2 w-full"><input type="password" placeholder="Password" class="border p-2 w-full"><button type="submit" class="bg-green-500 text-white p-2">Login</button></form>',
|
243 |
+
'<nav class="flex justify-between p-4 bg-gray-100"><div class="font-bold">Logo</div><ul class="flex space-x-4"><li><a href="#" class="text-blue-500">Home</a></li><li><a href="#" class="text-blue-500">About</a></li></ul></nav>',
|
244 |
+
'<div class="text-center p-8"><h1 class="text-3xl mb-4">Hero Title</h1><p class="mb-6">Some description text here</p><button class="bg-red-500 text-white px-6 py-3">Get Started</button></div>',
|
245 |
+
'<div class="border p-6 max-w-sm"><h3 class="text-lg font-semibold mb-2">Basic Plan</h3><p class="text-2xl font-bold mb-4">$9/month</p><ul class="space-y-2 mb-6"><li>Feature 1</li><li>Feature 2</li></ul><button class="w-full bg-blue-500 text-white py-2">Choose Plan</button></div>'
|
246 |
+
],
|
247 |
+
css=custom_css
|
248 |
+
)
|
249 |
+
|
250 |
+
if __name__ == "__main__":
|
251 |
+
demo.launch()
|