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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +80 -50
app.py CHANGED
@@ -5,17 +5,57 @@ import re
5
 
6
  # --- Configuration ---
7
  API_TOKEN = os.getenv("HF_TOKEN", None)
8
- # Using a model known for better instruction following might be beneficial
9
- MODEL = "Qwen/Qwen2.5-Coder-32B-Instruct" # Kept your original choice, but consider testing others if needed
10
 
11
  # --- Initialize Inference Client ---
12
  try:
13
  print(f"Initializing Inference Client for model: {MODEL}")
14
  client = InferenceClient(model=MODEL, token=API_TOKEN) if API_TOKEN else InferenceClient(model=MODEL)
15
  except Exception as e:
16
- # Provide a more specific error message if possible
17
  raise gr.Error(f"Failed to initialize model client for {MODEL}. Error: {e}. Check HF_TOKEN and model availability.")
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  # --- Core Code Generation Function ---
20
  def generate_code(
21
  prompt: str,
@@ -26,26 +66,22 @@ def generate_code(
26
  ):
27
  print(f"Generating code for: {prompt[:100]}... | Backend: {backend_choice}")
28
 
29
- # --- Dynamically Build System Message ---
30
- # Modified to include the specific formatting rules
31
  system_message = (
32
  "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. "
33
- "You MUST NOT wrap the code in markdown fences like ```html, ```python, or ```js. " # Explicit instruction to omit fences
34
- "The user can select a backend hint (Static, Flask, Node.js). "
35
  "If the user requests 'Static' or the prompt clearly implies only frontend code, generate ONLY the content for the `index.html` file. "
36
- "If the user requests 'Flask' or 'Node.js' and the prompt requires backend logic (like handling forms, APIs, databases), 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). "
37
  "When generating multiple files, you MUST separate them EXACTLY as follows: "
38
  "1. Output the complete code for the first file (e.g., `index.html`). "
39
- "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]'). " # Specific separator format
40
  "3. On the next line, immediately start the code for the second file. "
41
  "Generate only the necessary files (usually index.html and potentially one backend file). "
42
- "The generated website code must be SFW (safe for work) and have minimal errors. "
43
- "Only include comments where user modification is strictly required (e.g., API keys, database paths). Avoid explanatory comments. "
44
- "If the user asks you to create code that is NOT for a website, you MUST respond ONLY with the exact phrase: " # Specific refusal phrase
45
  "'hey there! am here to create websites for you unfortunately am programmed to not create codes! otherwise I would go on the naughty list :-('"
46
  )
47
 
48
- # User prompt remains the same, passing the raw request and backend choice
49
  user_prompt = f"USER_PROMPT = {prompt}\nUSER_BACKEND = {backend_choice}"
50
 
51
  messages = [
@@ -69,49 +105,45 @@ def generate_code(
69
  if isinstance(token, str):
70
  response_stream += token
71
  full_response += token
72
- # Yield intermediate stream for responsiveness
73
- yield response_stream
74
 
75
- # --- Post-processing (Refined) ---
76
- # Primarily focus on stripping whitespace and potential leftover model markers.
77
- # The fence removal is kept as a fallback in case the model doesn't fully comply.
78
  cleaned_response = full_response.strip()
79
 
80
- # Fallback fence removal (hopefully not needed often with the new prompt)
81
  cleaned_response = re.sub(r"^\s*```[a-z]*\s*\n?", "", cleaned_response)
82
  cleaned_response = re.sub(r"\n?\s*```\s*$", "", cleaned_response)
83
-
84
- # Remove potential chat markers (like <|user|>, <|assistant|>)
85
  cleaned_response = re.sub(r"<\s*\|?\s*(user|system|assistant)\s*\|?\s*>", "", cleaned_response, flags=re.IGNORECASE).strip()
86
-
87
- # Remove common conversational phrases if they somehow slip through despite the prompt
88
  common_phrases = [
89
  "Here is the code:", "Okay, here is the code:", "Here's the code:",
90
  "Sure, here is the code you requested:", "Let me know if you need anything else.",
91
- "```html", "```python", "```javascript", "```", # Adding fences here just in case they appear standalone
92
  ]
93
- # Use lower() for case-insensitive matching of leading phrases
94
  temp_response_lower = cleaned_response.lower()
95
  for phrase in common_phrases:
96
  if temp_response_lower.startswith(phrase.lower()):
97
- # Use original case length for slicing
98
  cleaned_response = cleaned_response[len(phrase):].lstrip()
99
- temp_response_lower = cleaned_response.lower() # Update lower version after stripping
100
 
101
- # Ensure the specific refusal message isn't accidentally cleaned
102
  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 :-("
103
- if refusal_message in full_response: # Check if the refusal message was generated
104
  yield refusal_message # Yield the exact refusal message
105
- else:
106
- yield cleaned_response # Yield the cleaned code
 
 
 
 
 
107
 
108
  except Exception as e:
109
- # Log the full error for debugging on the server side
110
- print(f"ERROR during code generation: {e}")
111
- # Provide a user-friendly error message
112
- yield f"## Error\n\nFailed to generate code.\n**Reason:** An unexpected error occurred. Please check the console logs or try again later."
113
- # Consider raising a gr.Error for critical failures if preferred
114
- # raise gr.Error(f"Code generation failed: {e}")
115
 
116
 
117
  # --- Build Gradio Interface ---
@@ -119,15 +151,15 @@ with gr.Blocks(css=".gradio-container { max-width: 90% !important; }") as demo:
119
  gr.Markdown("# ✨ Website Code Generator ✨")
120
  gr.Markdown(
121
  "Describe the website you want. The AI will generate the necessary code.\n"
122
- "It will aim for `index.html` for 'Static', and potentially `index.html` + a backend file (like `app.py` or `server.js`) for 'Flask'/'Node.js'.\n"
123
  "**Output Format:**\n"
124
  "- No explanations, just code.\n"
125
- "- Multiple files separated by `.TAB[NAME=filename.ext]` on its own line.\n"
126
  "- Minimal necessary comments only.\n\n"
127
  "**Rules:**\n"
128
  "- Backend choice guides the AI on whether to include server-side code.\n"
129
  "- Always SFW and aims for minimal errors.\n"
130
- "- Only generates website-related code. No other types of code."
131
  )
132
 
133
  with gr.Row():
@@ -141,25 +173,21 @@ with gr.Blocks(css=".gradio-container { max-width: 90% !important; }") as demo:
141
  ["Static", "Flask", "Node.js"],
142
  label="Backend Context",
143
  value="Static",
144
- info="Guides AI if backend code (like Python/JS) is needed alongside HTML." # Updated info text
145
  )
146
  generate_button = gr.Button("✨ Generate Website Code", variant="primary")
147
 
148
  with gr.Column(scale=3):
149
  code_output = gr.Code(
150
- label="Generated Code", # Changed label slightly
151
- language=None, # Set language to None for plain text display, better for mixed content
152
  lines=30,
153
- interactive=False,
154
  )
155
 
156
  with gr.Accordion("Advanced Settings", open=False):
157
  max_tokens_slider = gr.Slider(
158
- minimum=512,
159
- maximum=4096, # Adjust max based on model limits if necessary
160
- value=3072,
161
- step=256,
162
- label="Max New Tokens"
163
  )
164
  temperature_slider = gr.Slider(
165
  minimum=0.1, maximum=1.2, value=0.7, step=0.1, label="Temperature"
@@ -168,6 +196,7 @@ with gr.Blocks(css=".gradio-container { max-width: 90% !important; }") as demo:
168
  minimum=0.1, maximum=1.0, value=0.9, step=0.05, label="Top-P"
169
  )
170
 
 
171
  generate_button.click(
172
  fn=generate_code,
173
  inputs=[prompt_input, backend_radio, max_tokens_slider, temperature_slider, top_p_slider],
@@ -177,4 +206,5 @@ with gr.Blocks(css=".gradio-container { max-width: 90% !important; }") as demo:
177
  if __name__ == "__main__":
178
  if not API_TOKEN:
179
  print("Warning: HF_TOKEN environment variable not set. Using anonymous access.")
180
- demo.queue(max_size=10).launch()
 
 
5
 
6
  # --- Configuration ---
7
  API_TOKEN = os.getenv("HF_TOKEN", None)
8
+ MODEL = "Qwen/Qwen2.5-Coder-32B-Instruct"
 
9
 
10
  # --- Initialize Inference Client ---
11
  try:
12
  print(f"Initializing Inference Client for model: {MODEL}")
13
  client = InferenceClient(model=MODEL, token=API_TOKEN) if API_TOKEN else InferenceClient(model=MODEL)
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 ---
60
  def generate_code(
61
  prompt: str,
 
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}"
86
 
87
  messages = [
 
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:
127
  if temp_response_lower.startswith(phrase.lower()):
 
128
  cleaned_response = cleaned_response[len(phrase):].lstrip()
129
+ temp_response_lower = cleaned_response.lower()
130
 
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
  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():
 
173
  ["Static", "Flask", "Node.js"],
174
  label="Backend Context",
175
  value="Static",
176
+ info="Guides AI if backend code (like Python/JS) is needed alongside HTML."
177
  )
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(
190
+ minimum=512, maximum=4096, value=3072, step=256, label="Max New Tokens"
 
 
 
 
191
  )
192
  temperature_slider = gr.Slider(
193
  minimum=0.1, maximum=1.2, value=0.7, step=0.1, label="Temperature"
 
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],
 
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()