Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -6,20 +6,24 @@ import os
|
|
6 |
# Use environment variable for token, fallback to default if not set
|
7 |
# For Spaces, set the HF_TOKEN secret
|
8 |
API_TOKEN = os.getenv("HF_TOKEN", None)
|
9 |
-
MODEL = "HuggingFaceH4/zephyr-7b-beta"
|
10 |
|
11 |
# --- Initialize Inference Client ---
|
12 |
try:
|
|
|
13 |
if API_TOKEN:
|
14 |
-
print("Using HF Token.")
|
15 |
client = InferenceClient(model=MODEL, token=API_TOKEN)
|
16 |
else:
|
17 |
print("HF Token not found. Running without token (may lead to rate limits).")
|
18 |
client = InferenceClient(model=MODEL)
|
|
|
|
|
|
|
19 |
except Exception as e:
|
20 |
print(f"Error initializing Inference Client: {e}")
|
21 |
-
#
|
22 |
-
raise gr.Error(f"Failed to initialize the AI model client. Please check model name and token. Error: {e}")
|
23 |
|
24 |
# --- Core Code Generation Function ---
|
25 |
def generate_code(
|
@@ -34,112 +38,143 @@ def generate_code(
|
|
34 |
Generates website code based on user prompt and choices.
|
35 |
Yields the code token by token for live updates.
|
36 |
"""
|
37 |
-
print(f"
|
38 |
-
print(f"
|
39 |
-
print(f"
|
40 |
-
print(f"
|
|
|
41 |
|
42 |
# --- System Message (Internal) ---
|
43 |
-
#
|
|
|
44 |
system_message = (
|
45 |
-
"You are an expert frontend web developer AI. Your task is to generate HTML, CSS, and JavaScript code "
|
46 |
-
"for a website based on the user's description.
|
47 |
-
"**
|
48 |
-
"
|
49 |
-
"
|
50 |
-
"-
|
51 |
-
"-
|
52 |
-
"
|
53 |
-
"
|
54 |
-
"
|
55 |
-
"
|
56 |
-
"
|
57 |
-
"-
|
58 |
-
"
|
59 |
-
|
60 |
-
|
|
|
|
|
|
|
61 |
)
|
62 |
|
|
|
63 |
# --- Construct the messages for the API ---
|
64 |
messages = [
|
65 |
{"role": "system", "content": system_message},
|
66 |
-
{"role": "user", "content": prompt}
|
67 |
]
|
68 |
|
69 |
# --- Stream the response from the API ---
|
70 |
response_stream = ""
|
71 |
try:
|
72 |
-
print("Sending request to API...")
|
|
|
73 |
for message in client.chat_completion(
|
74 |
messages=messages,
|
75 |
max_tokens=max_tokens,
|
76 |
stream=True,
|
77 |
temperature=temperature,
|
78 |
top_p=top_p,
|
79 |
-
stop_sequences=["<|endoftext|>", "<|im_end|>", "</s>"] # Add common stop tokens
|
80 |
):
|
81 |
token = message.choices[0].delta.content
|
82 |
# Basic check to ensure token is a string (it should be)
|
83 |
if isinstance(token, str):
|
84 |
response_stream += token
|
|
|
|
|
|
|
85 |
yield response_stream # Yield the cumulative response for live update
|
86 |
-
# Add a small safety break if generation seems stuck or too long (optional)
|
87 |
-
# if len(response_stream) > max_tokens * 5: # Heuristic limit
|
88 |
-
# print("Warning: Exceeded heuristic length limit, stopping generation.")
|
89 |
-
# break
|
90 |
|
91 |
-
print("API stream finished.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
except Exception as e:
|
94 |
-
|
95 |
-
|
|
|
|
|
96 |
|
97 |
# --- Build Gradio Interface using Blocks ---
|
98 |
-
with gr.Blocks() as demo:
|
99 |
gr.Markdown("# Website Code Generator 🚀")
|
100 |
gr.Markdown(
|
101 |
"Describe the website you want, choose your options, and the AI will generate the frontend code (HTML, CSS, JS). "
|
102 |
-
"The code will appear live in the text editor below."
|
103 |
)
|
104 |
|
105 |
with gr.Row():
|
106 |
with gr.Column(scale=2):
|
107 |
prompt_input = gr.Textbox(
|
108 |
label="Website Description",
|
109 |
-
placeholder="e.g., A simple
|
110 |
-
lines=
|
111 |
)
|
112 |
backend_radio = gr.Radio(
|
113 |
["Static", "Flask", "Node.js"],
|
114 |
-
label="Backend Context",
|
115 |
value="Static",
|
116 |
-
info="
|
117 |
)
|
118 |
file_structure_radio = gr.Radio(
|
119 |
-
["
|
120 |
label="Output File Structure",
|
121 |
value="Multiple Files",
|
122 |
-
info="Generate
|
123 |
)
|
124 |
generate_button = gr.Button("Generate Website Code", variant="primary")
|
125 |
|
126 |
with gr.Column(scale=3):
|
|
|
127 |
code_output = gr.Code(
|
128 |
label="Generated Code",
|
129 |
-
language="html", #
|
130 |
-
lines=
|
131 |
interactive=False, # Read-only display
|
132 |
)
|
133 |
|
134 |
with gr.Accordion("Advanced Generation Settings", open=False):
|
135 |
max_tokens_slider = gr.Slider(
|
136 |
-
minimum=
|
|
|
|
|
|
|
|
|
|
|
137 |
)
|
138 |
temperature_slider = gr.Slider(
|
139 |
-
minimum=0.1,
|
|
|
|
|
|
|
|
|
|
|
140 |
)
|
141 |
top_p_slider = gr.Slider(
|
142 |
-
minimum=0.1,
|
|
|
|
|
|
|
|
|
|
|
143 |
)
|
144 |
|
145 |
# --- Connect Inputs/Outputs to the Function ---
|
@@ -154,10 +189,29 @@ with gr.Blocks() as demo:
|
|
154 |
top_p_slider,
|
155 |
],
|
156 |
outputs=code_output,
|
157 |
-
api_name="generate_website_code" # Optional: for API usage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
)
|
159 |
|
|
|
160 |
# --- Launch the App ---
|
161 |
if __name__ == "__main__":
|
162 |
-
|
163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
# Use environment variable for token, fallback to default if not set
|
7 |
# For Spaces, set the HF_TOKEN secret
|
8 |
API_TOKEN = os.getenv("HF_TOKEN", None)
|
9 |
+
MODEL = "HuggingFaceH4/zephyr-7b-beta" # Or choose another suitable model
|
10 |
|
11 |
# --- Initialize Inference Client ---
|
12 |
try:
|
13 |
+
print(f"Attempting to initialize Inference Client for model: {MODEL}")
|
14 |
if API_TOKEN:
|
15 |
+
print("Using HF Token found in environment.")
|
16 |
client = InferenceClient(model=MODEL, token=API_TOKEN)
|
17 |
else:
|
18 |
print("HF Token not found. Running without token (may lead to rate limits).")
|
19 |
client = InferenceClient(model=MODEL)
|
20 |
+
# Optional: Add a quick health check if needed, though client init usually suffices
|
21 |
+
# client.get_model_status(MODEL) # Example check, might raise if model invalid
|
22 |
+
print("Inference Client initialized successfully.")
|
23 |
except Exception as e:
|
24 |
print(f"Error initializing Inference Client: {e}")
|
25 |
+
# Provide a more informative error in the Gradio interface
|
26 |
+
raise gr.Error(f"Failed to initialize the AI model client for '{MODEL}'. Please check the model name and ensure network connectivity. If running locally without a token, you might hit rate limits. If using HF Spaces, ensure the HF_TOKEN secret is set correctly. Original Error: {e}")
|
27 |
|
28 |
# --- Core Code Generation Function ---
|
29 |
def generate_code(
|
|
|
38 |
Generates website code based on user prompt and choices.
|
39 |
Yields the code token by token for live updates.
|
40 |
"""
|
41 |
+
print(f"--- Generating Code ---")
|
42 |
+
print(f"Prompt: {prompt[:100]}...") # Log truncated prompt
|
43 |
+
print(f"Backend Context: {backend_choice}")
|
44 |
+
print(f"File Structure: {file_structure}")
|
45 |
+
print(f"Settings: Max Tokens={max_tokens}, Temp={temperature}, Top-P={top_p}")
|
46 |
|
47 |
# --- System Message (Internal) ---
|
48 |
+
# Guides the AI's behavior. Not user-editable in the UI.
|
49 |
+
# Refined system prompt for clarity and stricter output formatting
|
50 |
system_message = (
|
51 |
+
"You are an expert frontend web developer AI. Your task is to generate HTML, CSS, and potentially JavaScript code "
|
52 |
+
"for a website based ONLY on the user's description. Adhere strictly to the following constraints:\n"
|
53 |
+
"1. **Output ONLY Code:** Generate only the raw code for the requested files (`index.html`, `style.css`, `script.js`). Do NOT include any introductory text, explanations, apologies, markdown formatting (like ```html), or closing remarks. Your response must start *immediately* with the code (e.g., `<!DOCTYPE html>` or `<!-- index.html -->`).\n"
|
54 |
+
"2. **index.html is Mandatory:** ALWAYS generate a complete `index.html` file.\n"
|
55 |
+
"3. **File Structure:**\n"
|
56 |
+
f" - If '{file_structure}' is 'Multiple Files':\n"
|
57 |
+
" - Use clear markers EXACTLY as follows:\n"
|
58 |
+
" `<!-- index.html -->`\n"
|
59 |
+
" `/* style.css */`\n"
|
60 |
+
" `// script.js` (only include if JavaScript is necessary for the described functionality)\n"
|
61 |
+
" - Place the corresponding code directly after each marker.\n"
|
62 |
+
" - Link the CSS (`<link rel='stylesheet' href='style.css'>`) in the `<head>` of `index.html`.\n"
|
63 |
+
" - Include the JS (`<script src='script.js'></script>`) just before the closing `</body>` tag in `index.html` if `script.js` is generated.\n"
|
64 |
+
f" - If '{file_structure}' is 'Single File':\n"
|
65 |
+
" - Embed ALL CSS within `<style>` tags inside the `<head>` of the `index.html` file.\n"
|
66 |
+
" - Embed ALL necessary JavaScript within `<script>` tags just before the closing `</body>` tag of the `index.html` file.\n"
|
67 |
+
"4. **Backend Context ({backend_choice}):** This choice provides context. For 'Flask' or 'Node.js', you might include standard template placeholders (like `{{ variable }}` for Flask/Jinja2 or similar patterns for Node templating engines if appropriate for the frontend structure), but primarily focus on generating the static frontend assets (HTML structure, CSS styling, client-side JS interactions). For 'Static', generate standard HTML/CSS/JS without backend-specific placeholders.\n"
|
68 |
+
"5. **Focus on Frontend:** Generate the client-side code. Do not generate server-side Flask or Node.js code.\n"
|
69 |
+
"6. **Completeness:** Generate functional code based on the prompt. If the prompt is vague, create a reasonable default structure."
|
70 |
)
|
71 |
|
72 |
+
|
73 |
# --- Construct the messages for the API ---
|
74 |
messages = [
|
75 |
{"role": "system", "content": system_message},
|
76 |
+
{"role": "user", "content": f"Create a website based on this description: {prompt}"} # Make user role explicit
|
77 |
]
|
78 |
|
79 |
# --- Stream the response from the API ---
|
80 |
response_stream = ""
|
81 |
try:
|
82 |
+
print("Sending request to Hugging Face Inference API...")
|
83 |
+
# CORRECTED: Removed stop_sequences
|
84 |
for message in client.chat_completion(
|
85 |
messages=messages,
|
86 |
max_tokens=max_tokens,
|
87 |
stream=True,
|
88 |
temperature=temperature,
|
89 |
top_p=top_p,
|
|
|
90 |
):
|
91 |
token = message.choices[0].delta.content
|
92 |
# Basic check to ensure token is a string (it should be)
|
93 |
if isinstance(token, str):
|
94 |
response_stream += token
|
95 |
+
# Clean potential unwanted prefixes sometimes added by models
|
96 |
+
# if response_stream.strip().startswith(("```html", "```")):
|
97 |
+
# response_stream = response_stream.split("\n", 1)[-1]
|
98 |
yield response_stream # Yield the cumulative response for live update
|
|
|
|
|
|
|
|
|
99 |
|
100 |
+
print(f"API stream finished. Total length: {len(response_stream)}")
|
101 |
+
# Optional: Post-process to remove leading/trailing whitespace or markdown
|
102 |
+
final_response = response_stream.strip()
|
103 |
+
# More aggressive cleaning if needed:
|
104 |
+
# if final_response.startswith("```html"):
|
105 |
+
# final_response = final_response[7:]
|
106 |
+
# if final_response.endswith("```"):
|
107 |
+
# final_response = final_response[:-3]
|
108 |
+
# yield final_response.strip() # Yield final cleaned response
|
109 |
|
110 |
except Exception as e:
|
111 |
+
error_message = f"An error occurred during the API call: {e}"
|
112 |
+
print(error_message)
|
113 |
+
# Display the error clearly in the output box
|
114 |
+
yield f"## Error\n\nFailed to generate code.\n**Reason:** {e}\n\nPlease check the model status, your connection, and API token (if applicable)."
|
115 |
|
116 |
# --- Build Gradio Interface using Blocks ---
|
117 |
+
with gr.Blocks(css=".gradio-container { max-width: 90% !important; }") as demo: # Add CSS for wider layout
|
118 |
gr.Markdown("# Website Code Generator 🚀")
|
119 |
gr.Markdown(
|
120 |
"Describe the website you want, choose your options, and the AI will generate the frontend code (HTML, CSS, JS). "
|
121 |
+
"The code will appear live in the text editor below. **Note:** The AI generates only frontend code based on your description."
|
122 |
)
|
123 |
|
124 |
with gr.Row():
|
125 |
with gr.Column(scale=2):
|
126 |
prompt_input = gr.Textbox(
|
127 |
label="Website Description",
|
128 |
+
placeholder="e.g., A simple landing page with a navigation bar (Home, About, Contact), a hero section with a title and button, and a simple footer.",
|
129 |
+
lines=5, # Increased lines for better prompt visibility
|
130 |
)
|
131 |
backend_radio = gr.Radio(
|
132 |
["Static", "Flask", "Node.js"],
|
133 |
+
label="Backend Context Hint",
|
134 |
value="Static",
|
135 |
+
info="Hint for AI: influences potential template placeholders (e.g., {{var}}) but AI generates ONLY frontend code.",
|
136 |
)
|
137 |
file_structure_radio = gr.Radio(
|
138 |
+
["Multiple Files", "Single File"], # Default to multiple for clarity
|
139 |
label="Output File Structure",
|
140 |
value="Multiple Files",
|
141 |
+
info="Generate separate files (index.html, style.css, script.js) or embed all in index.html?",
|
142 |
)
|
143 |
generate_button = gr.Button("Generate Website Code", variant="primary")
|
144 |
|
145 |
with gr.Column(scale=3):
|
146 |
+
# Use Code component which is better suited for displaying code
|
147 |
code_output = gr.Code(
|
148 |
label="Generated Code",
|
149 |
+
language="html", # Base language, will contain CSS/JS markers if multiple files
|
150 |
+
lines=28, # Increased lines
|
151 |
interactive=False, # Read-only display
|
152 |
)
|
153 |
|
154 |
with gr.Accordion("Advanced Generation Settings", open=False):
|
155 |
max_tokens_slider = gr.Slider(
|
156 |
+
minimum=512, # Increased minimum for potentially complex sites
|
157 |
+
maximum=4096, # Match common context lengths
|
158 |
+
value=2048, # Increased default
|
159 |
+
step=128,
|
160 |
+
label="Max New Tokens",
|
161 |
+
info="Maximum number of tokens (approx. words/code elements) the AI can generate."
|
162 |
)
|
163 |
temperature_slider = gr.Slider(
|
164 |
+
minimum=0.1,
|
165 |
+
maximum=1.2, # Allow slightly higher for more creativity if needed
|
166 |
+
value=0.6, # Slightly lower default for more predictable code
|
167 |
+
step=0.1,
|
168 |
+
label="Temperature",
|
169 |
+
info="Controls randomness. Lower values (e.g., 0.2) make output more focused, higher values (e.g., 0.9) make it more creative/random."
|
170 |
)
|
171 |
top_p_slider = gr.Slider(
|
172 |
+
minimum=0.1,
|
173 |
+
maximum=1.0,
|
174 |
+
value=0.9, # Slightly lower default top-p
|
175 |
+
step=0.05,
|
176 |
+
label="Top-P (Nucleus Sampling)",
|
177 |
+
info="Alternative to temperature for controlling randomness. Considers only the most probable tokens with cumulative probability p."
|
178 |
)
|
179 |
|
180 |
# --- Connect Inputs/Outputs to the Function ---
|
|
|
189 |
top_p_slider,
|
190 |
],
|
191 |
outputs=code_output,
|
192 |
+
#api_name="generate_website_code" # Optional: for API usage
|
193 |
+
)
|
194 |
+
|
195 |
+
# Add examples for guidance
|
196 |
+
gr.Examples(
|
197 |
+
examples=[
|
198 |
+
["A simple counter page with a number display, an increment button, and a decrement button. Use Javascript for the logic.", "Static", "Single File"],
|
199 |
+
["A login form with fields for username and password, and a submit button. Basic styling.", "Static", "Multiple Files"],
|
200 |
+
["Product cards display grid. Each card should show an image, product name, price, and an 'Add to Cart' button. Make it responsive.", "Static", "Multiple Files"],
|
201 |
+
["A personal blog homepage with a header, a list of recent posts (just placeholders), and a sidebar with categories.", "Flask", "Multiple Files"],
|
202 |
+
],
|
203 |
+
inputs=[prompt_input, backend_radio, file_structure_radio],
|
204 |
+
label="Example Prompts" # Optional label for the examples section
|
205 |
)
|
206 |
|
207 |
+
|
208 |
# --- Launch the App ---
|
209 |
if __name__ == "__main__":
|
210 |
+
print("Starting Gradio app...")
|
211 |
+
# Enable queuing for handling multiple users, essential for Spaces
|
212 |
+
# Increase concurrency count if needed and if your hardware/Space plan supports it
|
213 |
+
demo.queue(max_size=10).launch(
|
214 |
+
# debug=True, # Set debug=False for production/Spaces deployment
|
215 |
+
# share=False # Set share=True to create a temporary public link (useful for local testing)
|
216 |
+
)
|
217 |
+
print("Gradio app launched.")
|