Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,85 +1,212 @@
|
|
1 |
import gradio as gr
|
2 |
from huggingface_hub import InferenceClient
|
3 |
import re
|
4 |
-
|
5 |
-
|
6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
def parse_files(raw_response):
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
def generate_code(prompt, backend, system_message, max_tokens, temperature, top_p):
|
|
|
|
|
|
|
|
|
|
|
13 |
full_sys_msg = f"""
|
14 |
-
You are a code
|
15 |
Always include an index.html file.
|
|
|
|
|
16 |
|
17 |
-
Respond ONLY with filenames and raw code. NO commentary, NO backticks, NO markdown.
|
18 |
-
|
19 |
-
Example:
|
20 |
index.html
|
21 |
-
|
|
|
|
|
|
|
|
|
22 |
|
23 |
style.css
|
24 |
-
body {{
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
-
|
|
|
27 |
""".strip()
|
28 |
|
29 |
messages = [
|
30 |
-
{"role": "system", "content": full_sys_msg + "\n" + system_message},
|
31 |
{"role": "user", "content": prompt}
|
32 |
]
|
33 |
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
|
41 |
-
|
42 |
-
|
|
|
|
|
43 |
|
44 |
-
|
|
|
|
|
|
|
45 |
|
46 |
-
def on_generate(prompt, backend, system_message, max_tokens, temperature, top_p):
|
47 |
-
files = generate_code(prompt, backend, system_message, max_tokens, temperature, top_p)
|
48 |
tabs = []
|
49 |
for name, content in files:
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
with gr.Blocks() as demo:
|
60 |
gr.Markdown("### Website Generator (Static / Flask / Node.js)")
|
|
|
61 |
|
62 |
with gr.Row():
|
63 |
-
prompt = gr.Textbox(label="Describe your website", placeholder="E.g
|
64 |
-
backend = gr.Dropdown(["Static", "Flask", "Node.js"], value="Static", label="Backend")
|
65 |
|
66 |
with gr.Accordion("Advanced Options", open=False):
|
67 |
-
system_message = gr.Textbox(label="Extra instructions for the AI", value="")
|
68 |
-
max_tokens = gr.Slider(256,
|
69 |
-
temperature = gr.Slider(0.1,
|
70 |
-
top_p = gr.Slider(0.1, 1.0, value=0.95, label="Top-p")
|
71 |
|
72 |
-
|
73 |
|
74 |
-
|
|
|
|
|
75 |
|
76 |
# Button click action
|
77 |
generate_button.click(
|
78 |
on_generate,
|
79 |
inputs=[prompt, backend, system_message, max_tokens, temperature, top_p],
|
80 |
-
outputs=out_tabs,
|
81 |
-
show_progress=
|
82 |
)
|
83 |
|
84 |
if __name__ == "__main__":
|
85 |
-
demo.launch()
|
|
|
1 |
import gradio as gr
|
2 |
from huggingface_hub import InferenceClient
|
3 |
import re
|
4 |
+
import os # Good practice to import os if needed, though not strictly used here yet
|
5 |
+
|
6 |
+
# --- Hugging Face Token (Optional but Recommended) ---
|
7 |
+
# It's better to use a token, especially for private models or higher rate limits
|
8 |
+
# from huggingface_hub import login
|
9 |
+
# login("YOUR_HUGGINGFACE_TOKEN") # Replace with your actual token or set HF_TOKEN env var
|
10 |
+
|
11 |
+
# --- Inference Client ---
|
12 |
+
# Consider adding error handling for client initialization if needed
|
13 |
+
try:
|
14 |
+
client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
|
15 |
+
except Exception as e:
|
16 |
+
print(f"Error initializing InferenceClient: {e}")
|
17 |
+
# Optionally, raise the exception or handle it gracefully in the UI
|
18 |
+
# For now, we'll let it proceed and potentially fail later if client is None
|
19 |
+
client = None
|
20 |
+
|
21 |
+
# --- Parsing Function ---
|
22 |
def parse_files(raw_response):
|
23 |
+
"""
|
24 |
+
Parses filenames and code blocks from the raw AI output.
|
25 |
+
Assumes format:
|
26 |
+
filename1.ext
|
27 |
+
```lang # Optional code block marker
|
28 |
+
code for file1
|
29 |
+
``` # Optional code block marker
|
30 |
+
filename2.ext
|
31 |
+
code for file2
|
32 |
+
...
|
33 |
+
"""
|
34 |
+
if not raw_response:
|
35 |
+
return []
|
36 |
+
|
37 |
+
# Improved pattern to handle optional code blocks and leading/trailing whitespace
|
38 |
+
# It looks for a filename line followed by content until the next filename line or end of string.
|
39 |
+
pattern = re.compile(
|
40 |
+
r"^\s*([\w\-.\/\\]+\.\w+)\s*\n" # Filename line (must have an extension)
|
41 |
+
r"(.*?)" # Capture content (non-greedy)
|
42 |
+
r"(?=\n\s*[\w\-.\/\\]+\.\w+\s*\n|\Z)", # Lookahead for next filename or end of string
|
43 |
+
re.DOTALL | re.MULTILINE
|
44 |
+
)
|
45 |
|
46 |
+
files = pattern.findall(raw_response)
|
47 |
+
|
48 |
+
# Clean up content: remove potential code block markers and extra whitespace
|
49 |
+
cleaned_files = []
|
50 |
+
for name, content in files:
|
51 |
+
# Remove common code block markers (``` optionally followed by lang)
|
52 |
+
content_cleaned = re.sub(r"^\s*```[a-zA-Z]*\n?", "", content, flags=re.MULTILINE)
|
53 |
+
content_cleaned = re.sub(r"\n?```\s*$", "", content_cleaned, flags=re.MULTILINE)
|
54 |
+
cleaned_files.append((name.strip(), content_cleaned.strip()))
|
55 |
+
|
56 |
+
# Handle case where the AI might just output code without filenames
|
57 |
+
if not cleaned_files and raw_response.strip():
|
58 |
+
# Basic check if it looks like code (e.g., contains common HTML/CSS/JS chars)
|
59 |
+
if any(c in raw_response for c in ['<','>','{','}',';','(',')']):
|
60 |
+
# Default to index.html if no files parsed but content exists
|
61 |
+
print("Warning: No filenames found, defaulting to index.html")
|
62 |
+
lang = "html" # Guess language, default to html
|
63 |
+
if "{" in raw_response and "}" in raw_response and ":" in raw_response:
|
64 |
+
lang = "css"
|
65 |
+
elif "function" in raw_response or "const" in raw_response or "let" in raw_response:
|
66 |
+
lang = "javascript"
|
67 |
+
# Determine a default filename based on guessed language
|
68 |
+
default_filename = "index.html"
|
69 |
+
if lang == "css": default_filename = "style.css"
|
70 |
+
elif lang == "javascript": default_filename = "script.js"
|
71 |
+
|
72 |
+
cleaned_files.append((default_filename, raw_response.strip()))
|
73 |
+
|
74 |
+
|
75 |
+
return cleaned_files
|
76 |
+
|
77 |
+
# --- Code Generation Function ---
|
78 |
def generate_code(prompt, backend, system_message, max_tokens, temperature, top_p):
|
79 |
+
"""Generates code using the InferenceClient."""
|
80 |
+
if not client:
|
81 |
+
# Return an error structure if client failed to initialize
|
82 |
+
return "Error: Inference Client not available.", []
|
83 |
+
|
84 |
full_sys_msg = f"""
|
85 |
+
You are a code generation AI. Given a prompt, generate the necessary files for a website using the {backend} backend.
|
86 |
Always include an index.html file.
|
87 |
+
Respond ONLY with filenames and the raw code for each file.
|
88 |
+
Each file must start with its filename on a new line. Example:
|
89 |
|
|
|
|
|
|
|
90 |
index.html
|
91 |
+
<!DOCTYPE html>
|
92 |
+
<html>
|
93 |
+
<head><title>My Site</title></head>
|
94 |
+
<body><h1>Hello</h1></body>
|
95 |
+
</html>
|
96 |
|
97 |
style.css
|
98 |
+
body {{
|
99 |
+
font-family: sans-serif;
|
100 |
+
}}
|
101 |
+
|
102 |
+
script.js
|
103 |
+
console.log('Hello World!');
|
104 |
|
105 |
+
Ensure the code is complete and functional for each file. NO commentary, NO explanations, NO markdown formatting like backticks (```).
|
106 |
+
Start generating the files now.
|
107 |
""".strip()
|
108 |
|
109 |
messages = [
|
110 |
+
{"role": "system", "content": full_sys_msg + ("\n" + system_message if system_message else "")},
|
111 |
{"role": "user", "content": prompt}
|
112 |
]
|
113 |
|
114 |
+
try:
|
115 |
+
response = client.chat_completion(
|
116 |
+
messages=messages,
|
117 |
+
max_tokens=int(max_tokens), # Ensure max_tokens is int
|
118 |
+
temperature=temperature,
|
119 |
+
top_p=top_p,
|
120 |
+
stream=False # Ensure streaming is off for this logic
|
121 |
+
)
|
122 |
+
raw = response.choices[0].message.content
|
123 |
+
print("\n--- Raw AI Response ---")
|
124 |
+
print(raw)
|
125 |
+
print("----------------------\n")
|
126 |
+
files = parse_files(raw)
|
127 |
+
return None, files # Return None for error, and the list of files
|
128 |
+
|
129 |
+
except Exception as e:
|
130 |
+
print(f"Error during AI generation: {e}")
|
131 |
+
return f"Error during AI generation: {e}", [] # Return error message
|
132 |
+
|
133 |
+
# --- Gradio Event Handler ---
|
134 |
+
def on_generate(prompt, backend, system_message, max_tokens, temperature, top_p):
|
135 |
+
"""Callback function for the generate button."""
|
136 |
+
error_msg, files = generate_code(prompt, backend, system_message, max_tokens, temperature, top_p)
|
137 |
|
138 |
+
if error_msg:
|
139 |
+
# Display error in a single tab if generation failed
|
140 |
+
error_tab = gr.TabItem(label="Error", children=[gr.Textbox(value=error_msg, label="Generation Error")])
|
141 |
+
return gr.Tabs(tabs=[error_tab]) # Return a Tabs component with the error tab
|
142 |
|
143 |
+
if not files:
|
144 |
+
# Display message if no files were parsed
|
145 |
+
no_files_tab = gr.TabItem(label="Output", children=[gr.Textbox(value="AI did not return recognizable file content. Check raw output in console.", label="Result")])
|
146 |
+
return gr.Tabs(tabs=[no_files_tab]) # Return a Tabs component with this message
|
147 |
|
|
|
|
|
148 |
tabs = []
|
149 |
for name, content in files:
|
150 |
+
name = name.strip()
|
151 |
+
content = content.strip()
|
152 |
+
if not name or not content: # Skip empty names or content
|
153 |
+
print(f"Skipping file with empty name or content: Name='{name}'")
|
154 |
+
continue
|
155 |
+
|
156 |
+
# Determine language for syntax highlighting
|
157 |
+
lang = "text" # Default
|
158 |
+
if name.endswith(".html") or name.endswith(".htm"):
|
159 |
+
lang = "html"
|
160 |
+
elif name.endswith(".css"):
|
161 |
+
lang = "css"
|
162 |
+
elif name.endswith(".js"):
|
163 |
+
lang = "javascript"
|
164 |
+
elif name.endswith(".py"):
|
165 |
+
lang = "python"
|
166 |
+
elif name.endswith(".json"):
|
167 |
+
lang = "json"
|
168 |
+
elif name.endswith(".md"):
|
169 |
+
lang = "markdown"
|
170 |
+
elif name.endswith(".sh") or name.endswith(".bash"):
|
171 |
+
lang = "bash"
|
172 |
+
|
173 |
+
tab_item = gr.TabItem(label=name, elem_id=f"tab_{name.replace('.', '_')}", children=[ # Ensure unique elem_id
|
174 |
+
gr.Code(value=content, language=lang, label=name) # Add label to Code block
|
175 |
+
])
|
176 |
+
tabs.append(tab_item)
|
177 |
+
|
178 |
+
# *** The Key Fix ***
|
179 |
+
# Return a new gr.Tabs component instance containing the generated TabItems
|
180 |
+
return gr.Tabs(tabs=tabs)
|
181 |
+
|
182 |
+
# --- Gradio UI Definition ---
|
183 |
with gr.Blocks() as demo:
|
184 |
gr.Markdown("### Website Generator (Static / Flask / Node.js)")
|
185 |
+
gr.Markdown("Describe the website you want to create. The AI will generate the necessary files.")
|
186 |
|
187 |
with gr.Row():
|
188 |
+
prompt = gr.Textbox(label="Describe your website", placeholder="E.g., a simple portfolio site with a contact form", scale=3)
|
189 |
+
backend = gr.Dropdown(["Static", "Flask", "Node.js"], value="Static", label="Backend Technology", scale=1)
|
190 |
|
191 |
with gr.Accordion("Advanced Options", open=False):
|
192 |
+
system_message = gr.Textbox(label="Extra instructions for the AI (System Message)", placeholder="Optional: e.g., 'Use Bootstrap 5', 'Prefer functional components in React'", value="")
|
193 |
+
max_tokens = gr.Slider(minimum=256, maximum=4096, value=1536, step=64, label="Max Tokens (Length)") # Increased max
|
194 |
+
temperature = gr.Slider(minimum=0.1, maximum=1.5, value=0.7, step=0.1, label="Temperature (Creativity)")
|
195 |
+
top_p = gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top-p (Sampling)")
|
196 |
|
197 |
+
generate_button = gr.Button("✨ Generate Code ✨", variant="primary")
|
198 |
|
199 |
+
gr.Markdown("#### Generated Files")
|
200 |
+
# Define the Tabs component placeholder. It will be replaced by the output of on_generate.
|
201 |
+
out_tabs = gr.Tabs(elem_id="output_tabs")
|
202 |
|
203 |
# Button click action
|
204 |
generate_button.click(
|
205 |
on_generate,
|
206 |
inputs=[prompt, backend, system_message, max_tokens, temperature, top_p],
|
207 |
+
outputs=[out_tabs], # Output the new Tabs component to replace the placeholder
|
208 |
+
show_progress="full" # Show progress during generation
|
209 |
)
|
210 |
|
211 |
if __name__ == "__main__":
|
212 |
+
demo.launch(debug=True) # Use debug=True for more detailed error messages in console
|