MINEOGO commited on
Commit
b647320
·
verified ·
1 Parent(s): 8941b06

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +126 -79
app.py CHANGED
@@ -2,6 +2,7 @@ import gradio as gr
2
  from huggingface_hub import InferenceClient
3
  import os
4
  import re
 
5
 
6
  # --- Configuration ---
7
  API_TOKEN = os.getenv("HF_TOKEN", None)
@@ -14,46 +15,54 @@ try:
14
  except Exception as e:
15
  raise gr.Error(f"Failed to initialize model client for {MODEL}. Error: {e}. Check HF_TOKEN and model availability.")
16
 
17
- # --- Helper Function to Parse and Format Code ---
18
- def parse_and_format_code(raw_response: str) -> str:
 
19
  """
20
  Parses raw AI output containing .TAB separators
21
- and formats it for display in a single code block.
 
22
  """
 
23
  # Default filename for the first block if no TAB is present or before the first TAB
24
- default_filename = "index.html"
25
- separator_pattern = r'\.TAB\[NAME=([^\]]+)\]\n?'
26
-
27
- filenames = re.findall(r'\.TAB\[NAME=([^\]]+)\]', raw_response)
28
- code_blocks = re.split(separator_pattern, raw_response)
29
 
30
- formatted_output = []
 
31
 
32
- # Handle the first block (before any potential separator)
33
- first_block = code_blocks[0].strip()
 
 
34
  if first_block:
35
- formatted_output.append(f"--- START FILE: {default_filename} ---\n\n{first_block}\n\n--- END FILE: {default_filename} ---")
 
 
 
 
 
36
 
37
- # Handle subsequent blocks associated with filenames found by the separator
38
- # re.split with capturing groups includes the separator AND the filename capture group
39
- # in the results. We need to iterate carefully.
40
- # Example: ['code1', 'app.py', 'code2', 'style.css', 'code3']
41
- idx = 1
42
- while idx < len(code_blocks) -1 :
43
- filename = code_blocks[idx] # This should be the filename captured by the pattern
44
- code = code_blocks[idx + 1].strip() # This should be the code after the separator
45
- if code : # Only add if there's actual code content
46
- formatted_output.append(f"--- START FILE: {filename} ---\n\n{code}\n\n--- END FILE: {filename} ---")
47
- idx += 2 # Move past the filename and the code block
48
 
49
- # If no separators were found, filenames list will be empty, and only the first block runs.
50
- # If separators were found, the loop processes the rest.
 
 
 
 
 
 
 
 
 
 
51
 
52
- if not formatted_output: # Handle case where input was empty or only whitespace
53
- return raw_response # Return the original if parsing yields nothing
54
 
55
- # Join the formatted blocks with clear separation
56
- return "\n\n\n".join(formatted_output)
57
 
58
 
59
  # --- Core Code Generation Function ---
@@ -63,23 +72,26 @@ def generate_code(
63
  max_tokens: int,
64
  temperature: float,
65
  top_p: float,
 
66
  ):
67
  print(f"Generating code for: {prompt[:100]}... | Backend: {backend_choice}")
 
68
 
 
69
  system_message = (
70
- "You are an AI that generates website code. You MUST ONLY output the raw code, without any conversational text like 'Here is the code' or explanations before or after the code blocks. "
71
- "You MUST NOT wrap the code in markdown fences like ```html, ```python, or ```js. "
72
- "If the user requests 'Static' or the prompt clearly implies only frontend code, generate ONLY the content for the `index.html` file. "
73
- "If the user requests 'Flask' or 'Node.js' and the prompt requires backend logic, you MUST generate both the `index.html` content AND the corresponding main backend file content (e.g., `app.py` for Flask, `server.js` or `app.js` for Node.js). "
74
- "When generating multiple files, you MUST separate them EXACTLY as follows: "
75
- "1. Output the complete code for the first file (e.g., `index.html`). "
76
- "2. On a new line immediately after the first file's code, add the separator '.TAB[NAME=filename.ext]' (e.g., '.TAB[NAME=app.py]' or '.TAB[NAME=server.js]'). "
77
- "3. On the next line, immediately start the code for the second file. "
78
- "Generate only the necessary files (usually index.html and potentially one backend file). "
79
- "The generated website code must be SFW and have minimal errors. "
80
- "Only include comments where user modification is strictly required. Avoid explanatory comments. "
81
- "If the user asks you to create code that is NOT for a website, you MUST respond ONLY with the exact phrase: "
82
- "'hey there! am here to create websites for you unfortunately am programmed to not create codes! otherwise I would go on the naughty list :-('"
83
  )
84
 
85
  user_prompt = f"USER_PROMPT = {prompt}\nUSER_BACKEND = {backend_choice}"
@@ -89,10 +101,12 @@ def generate_code(
89
  {"role": "user", "content": user_prompt}
90
  ]
91
 
92
- response_stream = ""
93
  full_response = ""
 
 
94
 
95
  try:
 
96
  stream = client.chat_completion(
97
  messages=messages,
98
  max_tokens=max_tokens,
@@ -100,27 +114,31 @@ def generate_code(
100
  temperature=temperature,
101
  top_p=top_p,
102
  )
 
 
103
  for message in stream:
104
  token = message.choices[0].delta.content
105
  if isinstance(token, str):
106
- response_stream += token
107
  full_response += token
108
- # Yield intermediate stream for responsiveness during generation
109
- yield response_stream # Show raw output as it comes
 
 
 
110
 
111
- # --- Post-processing (After Stream Ends) ---
112
- cleaned_response = full_response.strip()
113
 
 
 
 
114
  # Fallback fence removal
115
  cleaned_response = re.sub(r"^\s*```[a-z]*\s*\n?", "", cleaned_response)
116
  cleaned_response = re.sub(r"\n?\s*```\s*$", "", cleaned_response)
117
  # Remove potential chat markers
118
  cleaned_response = re.sub(r"<\s*\|?\s*(user|system|assistant)\s*\|?\s*>", "", cleaned_response, flags=re.IGNORECASE).strip()
119
  # Remove common conversational phrases (if they slip through)
120
- common_phrases = [
121
  "Here is the code:", "Okay, here is the code:", "Here's the code:",
122
- "Sure, here is the code you requested:", "Let me know if you need anything else.",
123
- "```html", "```python", "```javascript", "```",
124
  ]
125
  temp_response_lower = cleaned_response.lower()
126
  for phrase in common_phrases:
@@ -131,19 +149,39 @@ def generate_code(
131
  # Check for refusal message
132
  refusal_message = "hey there! am here to create websites for you unfortunately am programmed to not create codes! otherwise I would go on the naughty list :-("
133
  if refusal_message in full_response:
134
- yield refusal_message # Yield the exact refusal message
135
- return # Stop processing
 
136
 
137
- # --- PARSE and FORMAT the final cleaned response ---
138
- formatted_final_code = parse_and_format_code(cleaned_response)
139
 
140
- # Yield the final, formatted version replacing the streamed content
141
- yield formatted_final_code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  except Exception as e:
144
  print(f"ERROR during code generation: {e}") # Log detailed error
145
- # traceback.print_exc() # Uncomment for full traceback in logs
146
- yield f"## Error\n\nFailed to generate or process code.\n**Reason:** {e}"
 
 
 
147
 
148
 
149
  # --- Build Gradio Interface ---
@@ -151,22 +189,14 @@ with gr.Blocks(css=".gradio-container { max-width: 90% !important; }") as demo:
151
  gr.Markdown("# ✨ Website Code Generator ✨")
152
  gr.Markdown(
153
  "Describe the website you want. The AI will generate the necessary code.\n"
154
- "If multiple files are generated (e.g., for Flask/Node.js), they will be shown below separated by `--- START FILE: filename ---` markers.\n" # Updated description
155
- "**Output Format:**\n"
156
- "- No explanations, just code.\n"
157
- "- Multiple files separated by file markers.\n" # Updated description
158
- "- Minimal necessary comments only.\n\n"
159
- "**Rules:**\n"
160
- "- Backend choice guides the AI on whether to include server-side code.\n"
161
- "- Always SFW and aims for minimal errors.\n"
162
- "- Only generates website-related code."
163
  )
164
 
165
  with gr.Row():
166
  with gr.Column(scale=2):
167
  prompt_input = gr.Textbox(
168
  label="Website Description",
169
- placeholder="e.g., A Flask app with a form that stores data in a variable.",
170
  lines=6,
171
  )
172
  backend_radio = gr.Radio(
@@ -178,12 +208,29 @@ with gr.Blocks(css=".gradio-container { max-width: 90% !important; }") as demo:
178
  generate_button = gr.Button("✨ Generate Website Code", variant="primary")
179
 
180
  with gr.Column(scale=3):
181
- code_output = gr.Code(
182
- label="Generated Code (Scroll for multiple files)", # Updated label
183
- language=None, # Keep as None for mixed/plain text display
184
- lines=30,
185
- interactive=False, # Keep non-interactive
186
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
  with gr.Accordion("Advanced Settings", open=False):
189
  max_tokens_slider = gr.Slider(
@@ -196,15 +243,15 @@ with gr.Blocks(css=".gradio-container { max-width: 90% !important; }") as demo:
196
  minimum=0.1, maximum=1.0, value=0.9, step=0.05, label="Top-P"
197
  )
198
 
199
- # The click function now yields the final formatted string AFTER streaming
200
  generate_button.click(
201
  fn=generate_code,
202
  inputs=[prompt_input, backend_radio, max_tokens_slider, temperature_slider, top_p_slider],
203
- outputs=code_output,
 
204
  )
205
 
206
  if __name__ == "__main__":
207
  if not API_TOKEN:
208
  print("Warning: HF_TOKEN environment variable not set. Using anonymous access.")
209
- # Increased max_size slightly for potentially larger combined outputs
210
- demo.queue(max_size=15).launch()
 
2
  from huggingface_hub import InferenceClient
3
  import os
4
  import re
5
+ # import traceback # Optional: for more detailed error logging if needed
6
 
7
  # --- Configuration ---
8
  API_TOKEN = os.getenv("HF_TOKEN", None)
 
15
  except Exception as e:
16
  raise gr.Error(f"Failed to initialize model client for {MODEL}. Error: {e}. Check HF_TOKEN and model availability.")
17
 
18
+
19
+ # --- Helper Function to Parse Code into Files ---
20
+ def parse_code_into_files(raw_response: str) -> dict:
21
  """
22
  Parses raw AI output containing .TAB separators
23
+ into a dictionary where keys are filenames and values are code blocks.
24
+ Returns keys like 'index.html', 'backend_file', 'backend_filename', 'backend_language'.
25
  """
26
+ files = {}
27
  # Default filename for the first block if no TAB is present or before the first TAB
28
+ default_first_filename = "index.html"
29
+ separator_pattern = r'\.TAB\[NAME=([^\]]+)\]\n?' # Capture filename
 
 
 
30
 
31
+ # Find all separators and their positions
32
+ matches = list(re.finditer(separator_pattern, raw_response))
33
 
34
+ start_index = 0
35
+ # Handle the first file (always assume index.html for now)
36
+ first_separator_pos = matches[0].start() if matches else len(raw_response)
37
+ first_block = raw_response[start_index:first_separator_pos].strip()
38
  if first_block:
39
+ files[default_first_filename] = first_block
40
+
41
+ # Handle the second file (if separator exists)
42
+ if matches:
43
+ backend_filename = matches[0].group(1).strip() # Get filename from first match
44
+ start_index = matches[0].end() # Start after the first separator
45
 
46
+ # Find the position of the *next* separator, or end of string
47
+ second_separator_pos = matches[1].start() if len(matches) > 1 else len(raw_response)
48
+ backend_code = raw_response[start_index:second_separator_pos].strip()
 
 
 
 
 
 
 
 
49
 
50
+ if backend_code:
51
+ files['backend_file'] = backend_code
52
+ files['backend_filename'] = backend_filename
53
+ # Determine language from filename extension
54
+ if backend_filename.endswith(".py"):
55
+ files['backend_language'] = 'python'
56
+ elif backend_filename.endswith(".js"):
57
+ files['backend_language'] = 'javascript'
58
+ elif backend_filename.endswith(".css"):
59
+ files['backend_language'] = 'css'
60
+ else:
61
+ files['backend_language'] = None # Default to plain text
62
 
63
+ # If more files were generated (more separators), they are currently ignored by this simple parser.
 
64
 
65
+ return files
 
66
 
67
 
68
  # --- Core Code Generation Function ---
 
72
  max_tokens: int,
73
  temperature: float,
74
  top_p: float,
75
+ progress=gr.Progress(track_ τότε=True) # Add progress tracker
76
  ):
77
  print(f"Generating code for: {prompt[:100]}... | Backend: {backend_choice}")
78
+ progress(0, desc="Initializing Request...")
79
 
80
+ # System message remains the same - instructing the AI on format
81
  system_message = (
82
+ "You are an AI that generates website code. You MUST ONLY output the raw code, without any conversational text like 'Here is the code' or explanations before or after the code blocks. "
83
+ "You MUST NOT wrap the code in markdown fences like ```html, ```python, or ```js. "
84
+ "If the user requests 'Static' or the prompt clearly implies only frontend code, generate ONLY the content for the `index.html` file. "
85
+ "If the user requests 'Flask' or 'Node.js' and the prompt requires backend logic, you MUST generate both the `index.html` content AND the corresponding main backend file content (e.g., `app.py` for Flask, `server.js` or `app.js` for Node.js). "
86
+ "When generating multiple files, you MUST separate them EXACTLY as follows: "
87
+ "1. Output the complete code for the first file (e.g., `index.html`). "
88
+ "2. On a new line immediately after the first file's code, add the separator '.TAB[NAME=filename.ext]' (e.g., '.TAB[NAME=app.py]' or '.TAB[NAME=server.js]'). "
89
+ "3. On the next line, immediately start the code for the second file. "
90
+ "Generate only the necessary files (usually index.html and potentially one backend file). "
91
+ "The generated website code must be SFW and have minimal errors. "
92
+ "Only include comments where user modification is strictly required. Avoid explanatory comments. "
93
+ "If the user asks you to create code that is NOT for a website, you MUST respond ONLY with the exact phrase: "
94
+ "'hey there! am here to create websites for you unfortunately am programmed to not create codes! otherwise I would go on the naughty list :-('"
95
  )
96
 
97
  user_prompt = f"USER_PROMPT = {prompt}\nUSER_BACKEND = {backend_choice}"
 
101
  {"role": "user", "content": user_prompt}
102
  ]
103
 
 
104
  full_response = ""
105
+ token_count = 0
106
+ est_total_tokens = max_tokens # Rough estimate for progress
107
 
108
  try:
109
+ progress(0.1, desc="Sending Request to Model...")
110
  stream = client.chat_completion(
111
  messages=messages,
112
  max_tokens=max_tokens,
 
114
  temperature=temperature,
115
  top_p=top_p,
116
  )
117
+
118
+ progress(0.2, desc="Receiving Stream...")
119
  for message in stream:
120
  token = message.choices[0].delta.content
121
  if isinstance(token, str):
 
122
  full_response += token
123
+ token_count += 1
124
+ # Update progress based on tokens received vs max_tokens
125
+ # Adjust the scaling factor (e.g., 0.7) as needed
126
+ prog = min(0.2 + (token_count / est_total_tokens) * 0.7, 0.9)
127
+ progress(prog, desc="Generating Code...")
128
 
 
 
129
 
130
+ progress(0.9, desc="Processing Response...")
131
+ # --- Post-processing ---
132
+ cleaned_response = full_response.strip()
133
  # Fallback fence removal
134
  cleaned_response = re.sub(r"^\s*```[a-z]*\s*\n?", "", cleaned_response)
135
  cleaned_response = re.sub(r"\n?\s*```\s*$", "", cleaned_response)
136
  # Remove potential chat markers
137
  cleaned_response = re.sub(r"<\s*\|?\s*(user|system|assistant)\s*\|?\s*>", "", cleaned_response, flags=re.IGNORECASE).strip()
138
  # Remove common conversational phrases (if they slip through)
139
+ common_phrases = [ # Simplified list as prompt should handle most
140
  "Here is the code:", "Okay, here is the code:", "Here's the code:",
141
+ "Sure, here is the code you requested:",
 
142
  ]
143
  temp_response_lower = cleaned_response.lower()
144
  for phrase in common_phrases:
 
149
  # Check for refusal message
150
  refusal_message = "hey there! am here to create websites for you unfortunately am programmed to not create codes! otherwise I would go on the naughty list :-("
151
  if refusal_message in full_response:
152
+ # Return updates to clear both code blocks and show refusal in the first
153
+ progress(1, desc="Refusal Message Generated")
154
+ return gr.update(value=refusal_message, language=None, visible=True), gr.update(value="", visible=False)
155
 
156
+ # --- PARSE the final cleaned response into files ---
157
+ parsed_files = parse_code_into_files(cleaned_response)
158
 
159
+ html_code = parsed_files.get("index.html", "")
160
+ backend_code = parsed_files.get("backend_file", "")
161
+ backend_filename = parsed_files.get("backend_filename", "Backend")
162
+ backend_language = parsed_files.get("backend_language", None)
163
+
164
+ # --- Prepare Gradio Updates ---
165
+ # Update for the HTML code block (always visible)
166
+ html_update = gr.update(value=html_code, language='html', visible=True)
167
+
168
+ # Update for the Backend code block (visible only if backend code exists)
169
+ if backend_code:
170
+ backend_update = gr.update(value=backend_code, language=backend_language, label=backend_filename, visible=True)
171
+ else:
172
+ backend_update = gr.update(value="", visible=False) # Hide if no backend code
173
+
174
+ progress(1, desc="Done")
175
+ # Return tuple of updates for the outputs list
176
+ return html_update, backend_update
177
 
178
  except Exception as e:
179
  print(f"ERROR during code generation: {e}") # Log detailed error
180
+ # traceback.print_exc() # Uncomment for full traceback
181
+ progress(1, desc="Error Occurred")
182
+ error_message = f"## Error\n\nFailed to generate or process code.\n**Reason:** {e}"
183
+ # Return updates to show error in the first block and hide the second
184
+ return gr.update(value=error_message, language=None, visible=True), gr.update(value="", visible=False)
185
 
186
 
187
  # --- Build Gradio Interface ---
 
189
  gr.Markdown("# ✨ Website Code Generator ✨")
190
  gr.Markdown(
191
  "Describe the website you want. The AI will generate the necessary code.\n"
192
+ "If multiple files are generated (e.g., for Flask/Node.js), they will appear in separate tabs below." # Updated description
 
 
 
 
 
 
 
 
193
  )
194
 
195
  with gr.Row():
196
  with gr.Column(scale=2):
197
  prompt_input = gr.Textbox(
198
  label="Website Description",
199
+ placeholder="e.g., A Flask app with a simple chat using Socket.IO",
200
  lines=6,
201
  )
202
  backend_radio = gr.Radio(
 
208
  generate_button = gr.Button("✨ Generate Website Code", variant="primary")
209
 
210
  with gr.Column(scale=3):
211
+ # Define Tabs to hold the code outputs
212
+ with gr.Tabs(elem_id="code-tabs") as code_tabs:
213
+ # Tab 1: Always present for HTML
214
+ with gr.Tab("index.html", elem_id="html-tab") as html_tab:
215
+ html_code_output = gr.Code(
216
+ label="index.html", # Label for the code block itself
217
+ language="html",
218
+ lines=25, # Adjusted lines slightly
219
+ interactive=False,
220
+ elem_id="html_code", # Unique ID for targeting
221
+ )
222
+ # Tab 2: For Backend code, initially hidden
223
+ with gr.Tab("Backend", elem_id="backend-tab", visible=False) as backend_tab:
224
+ backend_code_output = gr.Code(
225
+ label="Backend Code", # Label will be updated dynamically
226
+ language=None, # Language updated dynamically
227
+ lines=25,
228
+ interactive=False,
229
+ elem_id="backend_code", # Unique ID for targeting
230
+ visible=False # Component also starts hidden
231
+ )
232
+ # Add more tabs here if needed (e.g., for CSS) following the same pattern
233
+
234
 
235
  with gr.Accordion("Advanced Settings", open=False):
236
  max_tokens_slider = gr.Slider(
 
243
  minimum=0.1, maximum=1.0, value=0.9, step=0.05, label="Top-P"
244
  )
245
 
246
+ # The click function now targets the specific code blocks within the tabs
247
  generate_button.click(
248
  fn=generate_code,
249
  inputs=[prompt_input, backend_radio, max_tokens_slider, temperature_slider, top_p_slider],
250
+ # The outputs list MUST match the order and number of code blocks we want to update
251
+ outputs=[html_code_output, backend_code_output],
252
  )
253
 
254
  if __name__ == "__main__":
255
  if not API_TOKEN:
256
  print("Warning: HF_TOKEN environment variable not set. Using anonymous access.")
257
+ demo.queue(max_size=10).launch() # Allow queueing