mgbam commited on
Commit
f22daae
·
verified ·
1 Parent(s): 1edddce

Upload 6 files

Browse files
Files changed (6) hide show
  1. api_clients.py +154 -0
  2. app.py +183 -1245
  3. chat_processing.py +189 -0
  4. config.py +190 -0
  5. file_processing.py +70 -0
  6. web_extraction.py +255 -0
api_clients.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Dict, List, Optional, Tuple
3
+
4
+ import gradio as gr
5
+ from huggingface_hub import InferenceClient
6
+ from tavily import TavilyClient
7
+
8
+ from config import (
9
+ HTML_SYSTEM_PROMPT, GENERIC_SYSTEM_PROMPT, HTML_SYSTEM_PROMPT_WITH_SEARCH,
10
+ GENERIC_SYSTEM_PROMPT_WITH_SEARCH, FollowUpSystemPrompt
11
+ )
12
+ from chat_processing import (
13
+ history_to_messages, messages_to_history, create_multimodal_message,
14
+ remove_code_block, apply_search_replace_changes, send_to_sandbox,
15
+ history_to_chatbot_messages, get_gradio_language
16
+ )
17
+ from file_processing import extract_text_from_file
18
+ from web_extraction import extract_website_content
19
+
20
+ # HF Inference Client
21
+ HF_TOKEN = os.getenv('HF_TOKEN')
22
+
23
+ def get_inference_client(model_id):
24
+ """Return an InferenceClient with provider based on model_id."""
25
+ provider = "groq" if model_id == "moonshotai/Kimi-K2-Instruct" else "auto"
26
+ return InferenceClient(
27
+ provider=provider,
28
+ api_key=HF_TOKEN,
29
+ bill_to="huggingface"
30
+ )
31
+
32
+ # Tavily Search Client
33
+ TAVILY_API_KEY = os.getenv('TAVILY_API_KEY')
34
+ tavily_client = None
35
+ if TAVILY_API_KEY:
36
+ try:
37
+ tavily_client = TavilyClient(api_key=TAVILY_API_KEY)
38
+ except Exception as e:
39
+ print(f"Failed to initialize Tavily client: {e}")
40
+ tavily_client = None
41
+
42
+ def generation_code(query: Optional[str], image: Optional[gr.Image], file: Optional[str], website_url: Optional[str], _setting: Dict[str, str], _history: Optional[List[Tuple[str, str]]], _current_model: Dict, enable_search: bool = False, language: str = "html"):
43
+ if query is None:
44
+ query = ''
45
+ if _history is None:
46
+ _history = []
47
+
48
+ # Check if there's existing HTML content in history to determine if this is a modification request
49
+ has_existing_html = False
50
+ if _history:
51
+ # Check the last assistant message for HTML content
52
+ last_assistant_msg = _history[-1][1] if len(_history) > 0 else ""
53
+ if '<!DOCTYPE html>' in last_assistant_msg or '<html' in last_assistant_msg:
54
+ has_existing_html = True
55
+
56
+ # Choose system prompt based on context
57
+ if has_existing_html:
58
+ # Use follow-up prompt for modifying existing HTML
59
+ system_prompt = FollowUpSystemPrompt
60
+ else:
61
+ # Use language-specific prompt
62
+ if language == "html":
63
+ system_prompt = HTML_SYSTEM_PROMPT_WITH_SEARCH if enable_search else HTML_SYSTEM_PROMPT
64
+ else:
65
+ system_prompt = GENERIC_SYSTEM_PROMPT_WITH_SEARCH.format(language=language) if enable_search else GENERIC_SYSTEM_PROMPT.format(language=language)
66
+
67
+ messages = history_to_messages(_history, system_prompt)
68
+
69
+ # Extract file text and append to query if file is present
70
+ file_text = ""
71
+ if file:
72
+ file_text = extract_text_from_file(file)
73
+ if file_text:
74
+ file_text = file_text[:5000] # Limit to 5000 chars for prompt size
75
+ query = f"{query}\n\n[Reference file content below]\n{file_text}"
76
+
77
+ # Extract website content and append to query if website URL is present
78
+ website_text = ""
79
+ if website_url and website_url.strip():
80
+ website_text = extract_website_content(website_url.strip())
81
+ if website_text and not website_text.startswith("Error"):
82
+ website_text = website_text[:8000] # Limit to 8000 chars for prompt size
83
+ query = f"{query}\n\n[Website content to redesign below]\n{website_text}"
84
+ elif website_text.startswith("Error"):
85
+ # Provide helpful guidance when website extraction fails
86
+ fallback_guidance = """
87
+ Since I couldn't extract the website content, please provide additional details about what you'd like to build:
88
+ 1. What type of website is this? (e.g., e-commerce, blog, portfolio, dashboard)
89
+ 2. What are the main features you want?
90
+ 3. What's the target audience?
91
+ 4. Any specific design preferences? (colors, style, layout)
92
+ This will help me create a better design for you."""
93
+ query = f"{query}\n\n[Error extracting website: {website_text}]{fallback_guidance}"
94
+
95
+ # Enhance query with search if enabled
96
+ enhanced_query = enhance_query_with_search(query, enable_search)
97
+
98
+ # Use dynamic client based on selected model
99
+ client = get_inference_client(_current_model["id"])
100
+
101
+ if image is not None:
102
+ messages.append(create_multimodal_message(enhanced_query, image))
103
+ else:
104
+ messages.append({'role': 'user', 'content': enhanced_query})
105
+ try:
106
+ completion = client.chat.completions.create(
107
+ model=_current_model["id"],
108
+ messages=messages,
109
+ stream=True,
110
+ max_tokens=5000
111
+ )
112
+ content = ""
113
+ for chunk in completion:
114
+ if chunk.choices[0].delta.content:
115
+ content += chunk.choices[0].delta.content
116
+ clean_code = remove_code_block(content)
117
+ if has_existing_html:
118
+ # Fallback: If the model returns a full HTML file, use it directly
119
+ if clean_code.strip().startswith("<!DOCTYPE html>") or clean_code.strip().startswith("<html"):
120
+ yield {
121
+ "code_output": gr.update(value=clean_code, language=get_gradio_language(language)),
122
+ "history_output": history_to_chatbot_messages(_history),
123
+ "sandbox": send_to_sandbox(clean_code) if language == "html" else "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML. Please download your code using the download button above.</div>",
124
+ }
125
+ else:
126
+ last_html = _history[-1][1] if _history else ""
127
+ modified_html = apply_search_replace_changes(last_html, clean_code)
128
+ clean_html = remove_code_block(modified_html)
129
+ yield {
130
+ "code_output": gr.update(value=clean_html, language=get_gradio_language(language)),
131
+ "history_output": history_to_chatbot_messages(_history),
132
+ "sandbox": send_to_sandbox(clean_html) if language == "html" else "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML. Please download your code using the download button above.</div>",
133
+ }
134
+ else:
135
+ yield {
136
+ "code_output": gr.update(value=clean_code, language=get_gradio_language(language)),
137
+ "history_output": history_to_chatbot_messages(_history),
138
+ "sandbox": send_to_sandbox(clean_code) if language == "html" else "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML. Please download your code using the download button above.</div>",
139
+ }
140
+ # Final update
141
+ _history = messages_to_history(messages + [{'role': 'assistant', 'content': content}])
142
+ yield {
143
+ "code_output": remove_code_block(content),
144
+ "history": _history,
145
+ "sandbox": send_to_sandbox(remove_code_block(content)),
146
+ "history_output": history_to_chatbot_messages(_history),
147
+ }
148
+
149
+ except Exception as e:
150
+ error_message = f"Error: {str(e)}"
151
+ yield {
152
+ "code_output": error_message,
153
+ "history_output": history_to_chatbot_messages(_history),
154
+ }
app.py CHANGED
@@ -1,1246 +1,184 @@
1
- import os
2
- import re
3
- from http import HTTPStatus
4
- from typing import Dict, List, Optional, Tuple
5
- import base64
6
- import mimetypes
7
- import PyPDF2
8
- import docx
9
- import cv2
10
- import numpy as np
11
- from PIL import Image
12
- import pytesseract
13
- import requests
14
- from urllib.parse import urlparse, urljoin
15
- from bs4 import BeautifulSoup
16
- import html2text
17
- import json
18
- import time
19
-
20
- import gradio as gr
21
- from huggingface_hub import InferenceClient
22
- from tavily import TavilyClient
23
-
24
- # Gradio supported languages for syntax highlighting
25
- GRADIO_SUPPORTED_LANGUAGES = [
26
- "python", "c", "cpp", "markdown", "latex", "json", "html", "css", "javascript", "jinja2", "typescript", "yaml", "dockerfile", "shell", "r", "sql", "sql-msSQL", "sql-mySQL", "sql-mariaDB", "sql-sqlite", "sql-cassandra", "sql-plSQL", "sql-hive", "sql-pgSQL", "sql-gql", "sql-gpSQL", "sql-sparkSQL", "sql-esper", None
27
- ]
28
-
29
- def get_gradio_language(language):
30
- return language if language in GRADIO_SUPPORTED_LANGUAGES else None
31
-
32
- # Search/Replace Constants
33
- SEARCH_START = "<<<<<<< SEARCH"
34
- DIVIDER = "======="
35
- REPLACE_END = ">>>>>>> REPLACE"
36
-
37
- # Configuration
38
- HTML_SYSTEM_PROMPT = """ONLY USE HTML, CSS AND JAVASCRIPT. If you want to use ICON make sure to import the library first. Try to create the best UI possible by using only HTML, CSS and JAVASCRIPT. MAKE IT RESPONSIVE USING MODERN CSS. Use as much as you can modern CSS for the styling, if you can't do something with modern CSS, then use custom CSS. Also, try to elaborate as much as you can, to create something unique. ALWAYS GIVE THE RESPONSE INTO A SINGLE HTML FILE
39
- For website redesign tasks:
40
- - Use the provided original HTML code as the starting point for redesign
41
- - Preserve all original content, structure, and functionality
42
- - Keep the same semantic HTML structure but enhance the styling
43
- - Reuse all original images and their URLs from the HTML code
44
- - Create a modern, responsive design with improved typography and spacing
45
- - Use modern CSS frameworks and design patterns
46
- - Ensure accessibility and mobile responsiveness
47
- - Maintain the same navigation and user flow
48
- - Enhance the visual design while keeping the original layout structure
49
- If an image is provided, analyze it and use the visual information to better understand the user's requirements.
50
- Always respond with code that can be executed or rendered directly.
51
- Always output only the HTML code inside a ```html ... ``` code block, and do not include any explanations or extra text."""
52
-
53
- GENERIC_SYSTEM_PROMPT = """You are an expert {language} developer. Write clean, idiomatic, and runnable {language} code for the user's request. If possible, include comments and best practices. Output ONLY the code inside a ```{language} ... ``` code block, and do not include any explanations or extra text. If the user provides a file or other context, use it as a reference. If the code is for a script or app, make it as self-contained as possible."""
54
-
55
- # System prompt with search capability
56
- HTML_SYSTEM_PROMPT_WITH_SEARCH = """ONLY USE HTML, CSS AND JAVASCRIPT. If you want to use ICON make sure to import the library first. Try to create the best UI possible by using only HTML, CSS and JAVASCRIPT. MAKE IT RESPONSIVE USING MODERN CSS. Use as much as you can modern CSS for the styling, if you can't do something with modern CSS, then use custom CSS. Also, try to elaborate as much as you can, to create something unique. ALWAYS GIVE THE RESPONSE INTO A SINGLE HTML FILE
57
- You have access to real-time web search. When needed, use web search to find the latest information, best practices, or specific technologies.
58
- For website redesign tasks:
59
- - Use the provided original HTML code as the starting point for redesign
60
- - Preserve all original content, structure, and functionality
61
- - Keep the same semantic HTML structure but enhance the styling
62
- - Reuse all original images and their URLs from the HTML code
63
- - Use web search to find current design trends and best practices for the specific type of website
64
- - Create a modern, responsive design with improved typography and spacing
65
- - Use modern CSS frameworks and design patterns
66
- - Ensure accessibility and mobile responsiveness
67
- - Maintain the same navigation and user flow
68
- - Enhance the visual design while keeping the original layout structure
69
- If an image is provided, analyze it and use the visual information to better understand the user's requirements.
70
- Always respond with code that can be executed or rendered directly.
71
- Always output only the HTML code inside a ```html ... ``` code block, and do not include any explanations or extra text."""
72
-
73
- GENERIC_SYSTEM_PROMPT_WITH_SEARCH = """You are an expert {language} developer. You have access to real-time web search. When needed, use web search to find the latest information, best practices, or specific technologies for {language}.
74
- Write clean, idiomatic, and runnable {language} code for the user's request. If possible, include comments and best practices. Output ONLY the code inside a ```{language} ... ``` code block, and do not include any explanations or extra text. If the user provides a file or other context, use it as a reference. If the code is for a script or app, make it as self-contained as possible."""
75
-
76
- # Follow-up system prompt for modifying existing HTML files
77
- FollowUpSystemPrompt = f"""You are an expert web developer modifying an existing HTML file.
78
- The user wants to apply changes based on their request.
79
- You MUST output ONLY the changes required using the following SEARCH/REPLACE block format. Do NOT output the entire file.
80
- Explain the changes briefly *before* the blocks if necessary, but the code changes THEMSELVES MUST be within the blocks.
81
- Format Rules:
82
- 1. Start with {SEARCH_START}
83
- 2. Provide the exact lines from the current code that need to be replaced.
84
- 3. Use {DIVIDER} to separate the search block from the replacement.
85
- 4. Provide the new lines that should replace the original lines.
86
- 5. End with {REPLACE_END}
87
- 6. You can use multiple SEARCH/REPLACE blocks if changes are needed in different parts of the file.
88
- 7. To insert code, use an empty SEARCH block (only {SEARCH_START} and {DIVIDER} on their lines) if inserting at the very beginning, otherwise provide the line *before* the insertion point in the SEARCH block and include that line plus the new lines in the REPLACE block.
89
- 8. To delete code, provide the lines to delete in the SEARCH block and leave the REPLACE block empty (only {DIVIDER} and {REPLACE_END} on their lines).
90
- 9. IMPORTANT: The SEARCH block must *exactly* match the current code, including indentation and whitespace.
91
- Example Modifying Code:
92
- ```
93
- Some explanation...
94
- {SEARCH_START}
95
- <h1>Old Title</h1>
96
- {DIVIDER}
97
- <h1>New Title</h1>
98
- {REPLACE_END}
99
- {SEARCH_START}
100
- </body>
101
- {DIVIDER}
102
- <script>console.log("Added script");</script>
103
- </body>
104
- {REPLACE_END}
105
- ```
106
- Example Deleting Code:
107
- ```
108
- Removing the paragraph...
109
- {SEARCH_START}
110
- <p>This paragraph will be deleted.</p>
111
- {DIVIDER}
112
- {REPLACE_END}
113
- ```"""
114
-
115
- # Available models
116
- AVAILABLE_MODELS = [
117
- {
118
- "name": "Moonshot Kimi-K2",
119
- "id": "moonshotai/Kimi-K2-Instruct",
120
- "description": "Moonshot AI Kimi-K2-Instruct model for code generation and general tasks"
121
- },
122
- {
123
- "name": "DeepSeek V3",
124
- "id": "deepseek-ai/DeepSeek-V3-0324",
125
- "description": "DeepSeek V3 model for code generation"
126
- },
127
- {
128
- "name": "DeepSeek R1",
129
- "id": "deepseek-ai/DeepSeek-R1-0528",
130
- "description": "DeepSeek R1 model for code generation"
131
- },
132
- {
133
- "name": "ERNIE-4.5-VL",
134
- "id": "baidu/ERNIE-4.5-VL-424B-A47B-Base-PT",
135
- "description": "ERNIE-4.5-VL model for multimodal code generation with image support"
136
- },
137
- {
138
- "name": "MiniMax M1",
139
- "id": "MiniMaxAI/MiniMax-M1-80k",
140
- "description": "MiniMax M1 model for code generation and general tasks"
141
- },
142
- {
143
- "name": "Qwen3-235B-A22B",
144
- "id": "Qwen/Qwen3-235B-A22B",
145
- "description": "Qwen3-235B-A22B model for code generation and general tasks"
146
- },
147
- {
148
- "name": "SmolLM3-3B",
149
- "id": "HuggingFaceTB/SmolLM3-3B",
150
- "description": "SmolLM3-3B model for code generation and general tasks"
151
- },
152
- {
153
- "name": "GLM-4.1V-9B-Thinking",
154
- "id": "THUDM/GLM-4.1V-9B-Thinking",
155
- "description": "GLM-4.1V-9B-Thinking model for multimodal code generation with image support"
156
- }
157
- ]
158
-
159
- DEMO_LIST = [
160
- {
161
- "title": "Todo App",
162
- "description": "Create a simple todo application with add, delete, and mark as complete functionality"
163
- },
164
- {
165
- "title": "Calculator",
166
- "description": "Build a basic calculator with addition, subtraction, multiplication, and division"
167
- },
168
- {
169
- "title": "Weather Dashboard",
170
- "description": "Create a weather dashboard that displays current weather information"
171
- },
172
- {
173
- "title": "Chat Interface",
174
- "description": "Build a chat interface with message history and user input"
175
- },
176
- {
177
- "title": "E-commerce Product Card",
178
- "description": "Create a product card component for an e-commerce website"
179
- },
180
- {
181
- "title": "Login Form",
182
- "description": "Build a responsive login form with validation"
183
- },
184
- {
185
- "title": "Dashboard Layout",
186
- "description": "Create a dashboard layout with sidebar navigation and main content area"
187
- },
188
- {
189
- "title": "Data Table",
190
- "description": "Build a data table with sorting and filtering capabilities"
191
- },
192
- {
193
- "title": "Image Gallery",
194
- "description": "Create an image gallery with lightbox functionality and responsive grid layout"
195
- },
196
- {
197
- "title": "UI from Image",
198
- "description": "Upload an image of a UI design and I'll generate the HTML/CSS code for it"
199
- },
200
- {
201
- "title": "Extract Text from Image",
202
- "description": "Upload an image containing text and I'll extract and process the text content"
203
- },
204
- {
205
- "title": "Website Redesign",
206
- "description": "Enter a website URL to extract its content and redesign it with a modern, responsive layout"
207
- },
208
- {
209
- "title": "Modify HTML",
210
- "description": "After generating HTML, ask me to modify it with specific changes using search/replace format"
211
- },
212
- {
213
- "title": "Search/Replace Example",
214
- "description": "Generate HTML first, then ask: 'Change the title to My New Title' or 'Add a blue background to the body'"
215
- }
216
- ]
217
-
218
- HF_TOKEN = os.getenv('HF_TOKEN')
219
- GROQ_API_KEY = os.getenv('GROQ_API_KEY')
220
- FIREWORKS_API_KEY = os.getenv('FIREWORKS_API_KEY')
221
-
222
- def get_inference_client(model_id):
223
- """Return an InferenceClient configured for Hugging Face, Groq, or Fireworks AI."""
224
- if model_id == "moonshotai/Kimi-K2-Instruct":
225
- return InferenceClient(
226
- base_url="https://api.groq.com/openai/v1",
227
- api_key=GROQ_API_KEY
228
- )
229
- elif model_id.startswith("fireworks/"):
230
- return InferenceClient(
231
- base_url="https://api.fireworks.ai/inference/v1",
232
- api_key=FIREWORKS_API_KEY
233
- )
234
- else:
235
- return InferenceClient(
236
- model=model_id,
237
- api_key=HF_TOKEN
238
- )
239
-
240
- # Type definitions
241
- History = List[Tuple[str, str]]
242
- Messages = List[Dict[str, str]]
243
-
244
- # Tavily Search Client
245
- TAVILY_API_KEY = os.getenv('TAVILY_API_KEY')
246
- tavily_client = None
247
- if TAVILY_API_KEY:
248
- try:
249
- tavily_client = TavilyClient(api_key=TAVILY_API_KEY)
250
- except Exception as e:
251
- print(f"Failed to initialize Tavily client: {e}")
252
- tavily_client = None
253
-
254
- def history_to_messages(history: History, system: str) -> Messages:
255
- messages = [{'role': 'system', 'content': system}]
256
- for h in history:
257
- # Handle multimodal content in history
258
- user_content = h[0]
259
- if isinstance(user_content, list):
260
- # Extract text from multimodal content
261
- text_content = ""
262
- for item in user_content:
263
- if isinstance(item, dict) and item.get("type") == "text":
264
- text_content += item.get("text", "")
265
- user_content = text_content if text_content else str(user_content)
266
-
267
- messages.append({'role': 'user', 'content': user_content})
268
- messages.append({'role': 'assistant', 'content': h[1]})
269
- return messages
270
-
271
- def messages_to_history(messages: Messages) -> Tuple[str, History]:
272
- assert messages[0]['role'] == 'system'
273
- history = []
274
- for q, r in zip(messages[1::2], messages[2::2]):
275
- # Extract text content from multimodal messages for history
276
- user_content = q['content']
277
- if isinstance(user_content, list):
278
- text_content = ""
279
- for item in user_content:
280
- if isinstance(item, dict) and item.get("type") == "text":
281
- text_content += item.get("text", "")
282
- user_content = text_content if text_content else str(user_content)
283
-
284
- history.append([user_content, r['content']])
285
- return history
286
-
287
- def history_to_chatbot_messages(history: History) -> List[Dict[str, str]]:
288
- """Convert history tuples to chatbot message format"""
289
- messages = []
290
- for user_msg, assistant_msg in history:
291
- # Handle multimodal content
292
- if isinstance(user_msg, list):
293
- text_content = ""
294
- for item in user_msg:
295
- if isinstance(item, dict) and item.get("type") == "text":
296
- text_content += item.get("text", "")
297
- user_msg = text_content if text_content else str(user_msg)
298
-
299
- messages.append({"role": "user", "content": user_msg})
300
- messages.append({"role": "assistant", "content": assistant_msg})
301
- return messages
302
-
303
- def remove_code_block(text):
304
- # Try to match code blocks with language markers
305
- patterns = [
306
- r'```(?:html|HTML)\n([\s\S]+?)\n```', # Match ```html or ```HTML
307
- r'```\n([\s\S]+?)\n```', # Match code blocks without language markers
308
- r'```([\s\S]+?)```' # Match code blocks without line breaks
309
- ]
310
- for pattern in patterns:
311
- match = re.search(pattern, text, re.DOTALL)
312
- if match:
313
- extracted = match.group(1).strip()
314
- return extracted
315
- # If no code block is found, check if the entire text is HTML
316
- if text.strip().startswith('<!DOCTYPE html>') or text.strip().startswith('<html') or text.strip().startswith('<'):
317
- return text.strip()
318
- return text.strip()
319
-
320
- def history_render(history: History):
321
- return gr.update(visible=True), history
322
-
323
- def clear_history():
324
- return [], [], None, "" # Empty lists for both tuple format and chatbot messages, None for file, empty string for website URL
325
-
326
- def update_image_input_visibility(model):
327
- """Update image input visibility based on selected model"""
328
- is_ernie_vl = model.get("id") == "baidu/ERNIE-4.5-VL-424B-A47B-Base-PT"
329
- is_glm_vl = model.get("id") == "THUDM/GLM-4.1V-9B-Thinking"
330
- return gr.update(visible=is_ernie_vl or is_glm_vl)
331
-
332
- def process_image_for_model(image):
333
- """Convert image to base64 for model input"""
334
- if image is None:
335
- return None
336
-
337
- # Convert numpy array to PIL Image if needed
338
- import io
339
- import base64
340
- import numpy as np
341
- from PIL import Image
342
-
343
- # Handle numpy array from Gradio
344
- if isinstance(image, np.ndarray):
345
- image = Image.fromarray(image)
346
-
347
- buffer = io.BytesIO()
348
- image.save(buffer, format='PNG')
349
- img_str = base64.b64encode(buffer.getvalue()).decode()
350
- return f"data:image/png;base64,{img_str}"
351
-
352
- def create_multimodal_message(text, image=None):
353
- """Create a multimodal message with text and optional image"""
354
- if image is None:
355
- return {"role": "user", "content": text}
356
-
357
- content = [
358
- {
359
- "type": "text",
360
- "text": text
361
- },
362
- {
363
- "type": "image_url",
364
- "image_url": {
365
- "url": process_image_for_model(image)
366
- }
367
- }
368
- ]
369
-
370
- return {"role": "user", "content": content}
371
-
372
- def apply_search_replace_changes(original_html: str, changes_text: str) -> str:
373
- """Apply search/replace changes to HTML content"""
374
- if not changes_text.strip():
375
- return original_html
376
-
377
- # Split the changes text into individual search/replace blocks
378
- blocks = []
379
- current_block = ""
380
- lines = changes_text.split('\n')
381
-
382
- for line in lines:
383
- if line.strip() == SEARCH_START:
384
- if current_block.strip():
385
- blocks.append(current_block.strip())
386
- current_block = line + '\n'
387
- elif line.strip() == REPLACE_END:
388
- current_block += line + '\n'
389
- blocks.append(current_block.strip())
390
- current_block = ""
391
- else:
392
- current_block += line + '\n'
393
-
394
- if current_block.strip():
395
- blocks.append(current_block.strip())
396
-
397
- modified_html = original_html
398
-
399
- for block in blocks:
400
- if not block.strip():
401
- continue
402
-
403
- # Parse the search/replace block
404
- lines = block.split('\n')
405
- search_lines = []
406
- replace_lines = []
407
- in_search = False
408
- in_replace = False
409
-
410
- for line in lines:
411
- if line.strip() == SEARCH_START:
412
- in_search = True
413
- in_replace = False
414
- elif line.strip() == DIVIDER:
415
- in_search = False
416
- in_replace = True
417
- elif line.strip() == REPLACE_END:
418
- in_replace = False
419
- elif in_search:
420
- search_lines.append(line)
421
- elif in_replace:
422
- replace_lines.append(line)
423
-
424
- # Apply the search/replace
425
- if search_lines:
426
- search_text = '\n'.join(search_lines).strip()
427
- replace_text = '\n'.join(replace_lines).strip()
428
-
429
- if search_text in modified_html:
430
- modified_html = modified_html.replace(search_text, replace_text)
431
- else:
432
- print(f"Warning: Search text not found in HTML: {search_text[:100]}...")
433
-
434
- return modified_html
435
-
436
- # Updated for faster Tavily search and closer prompt usage
437
- # Uses 'advanced' search_depth and auto_parameters=True for speed and relevance
438
-
439
- def perform_web_search(query: str, max_results: int = 5, include_domains=None, exclude_domains=None) -> str:
440
- """Perform web search using Tavily with default parameters"""
441
- if not tavily_client:
442
- return "Web search is not available. Please set the TAVILY_API_KEY environment variable."
443
-
444
- try:
445
- # Use Tavily defaults with advanced search depth for better results
446
- search_params = {
447
- "search_depth": "advanced",
448
- "max_results": min(max(1, max_results), 20)
449
- }
450
- if include_domains is not None:
451
- search_params["include_domains"] = include_domains
452
- if exclude_domains is not None:
453
- search_params["exclude_domains"] = exclude_domains
454
-
455
- response = tavily_client.search(query, **search_params)
456
-
457
- search_results = []
458
- for result in response.get('results', []):
459
- title = result.get('title', 'No title')
460
- url = result.get('url', 'No URL')
461
- content = result.get('content', 'No content')
462
- search_results.append(f"Title: {title}\nURL: {url}\nContent: {content}\n")
463
-
464
- if search_results:
465
- return "Web Search Results:\n\n" + "\n---\n".join(search_results)
466
- else:
467
- return "No search results found."
468
-
469
- except Exception as e:
470
- return f"Search error: {str(e)}"
471
-
472
- def enhance_query_with_search(query: str, enable_search: bool) -> str:
473
- """Enhance the query with web search results if search is enabled"""
474
- if not enable_search or not tavily_client:
475
- return query
476
-
477
- # Perform search to get relevant information
478
- search_results = perform_web_search(query)
479
-
480
- # Combine original query with search results
481
- enhanced_query = f"""Original Query: {query}
482
- {search_results}
483
- Please use the search results above to help create the requested application with the most up-to-date information and best practices."""
484
-
485
- return enhanced_query
486
-
487
- def send_to_sandbox(code):
488
- # Add a wrapper to inject necessary permissions and ensure full HTML
489
- wrapped_code = f"""
490
- <!DOCTYPE html>
491
- <html>
492
- <head>
493
- <meta charset=\"UTF-8\">
494
- <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
495
- <script>
496
- // Safe localStorage polyfill
497
- const safeStorage = {{
498
- _data: {{}},
499
- getItem: function(key) {{ return this._data[key] || null; }},
500
- setItem: function(key, value) {{ this._data[key] = value; }},
501
- removeItem: function(key) {{ delete this._data[key]; }},
502
- clear: function() {{ this._data = {{}}; }}
503
- }};
504
- Object.defineProperty(window, 'localStorage', {{
505
- value: safeStorage,
506
- writable: false
507
- }});
508
- window.onerror = function(message, source, lineno, colno, error) {{
509
- console.error('Error:', message);
510
- }};
511
- </script>
512
- </head>
513
- <body>
514
- {code}
515
- </body>
516
- </html>
517
- """
518
- encoded_html = base64.b64encode(wrapped_code.encode('utf-8')).decode('utf-8')
519
- data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
520
- iframe = f'<iframe src="{data_uri}" width="100%" height="920px" sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-presentation" allow="display-capture"></iframe>'
521
- return iframe
522
-
523
- def demo_card_click(e: gr.EventData):
524
- try:
525
- # Get the index from the event data
526
- if hasattr(e, '_data') and e._data:
527
- # Try different ways to get the index
528
- if 'index' in e._data:
529
- index = e._data['index']
530
- elif 'component' in e._data and 'index' in e._data['component']:
531
- index = e._data['component']['index']
532
- elif 'target' in e._data and 'index' in e._data['target']:
533
- index = e._data['target']['index']
534
- else:
535
- # If we can't get the index, try to extract it from the card data
536
- index = 0
537
- else:
538
- index = 0
539
-
540
- # Ensure index is within bounds
541
- if index >= len(DEMO_LIST):
542
- index = 0
543
-
544
- return DEMO_LIST[index]['description']
545
- except (KeyError, IndexError, AttributeError) as e:
546
- # Return the first demo description as fallback
547
- return DEMO_LIST[0]['description']
548
-
549
- def extract_text_from_image(image_path):
550
- """Extract text from image using OCR"""
551
- try:
552
- # Check if tesseract is available
553
- try:
554
- pytesseract.get_tesseract_version()
555
- except Exception:
556
- return "Error: Tesseract OCR is not installed. Please install Tesseract to extract text from images. See install_tesseract.md for instructions."
557
-
558
- # Read image using OpenCV
559
- image = cv2.imread(image_path)
560
- if image is None:
561
- return "Error: Could not read image file"
562
-
563
- # Convert to RGB (OpenCV uses BGR)
564
- image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
565
-
566
- # Preprocess image for better OCR results
567
- # Convert to grayscale
568
- gray = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2GRAY)
569
-
570
- # Apply thresholding to get binary image
571
- _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
572
-
573
- # Extract text using pytesseract
574
- text = pytesseract.image_to_string(binary, config='--psm 6')
575
-
576
- return text.strip() if text.strip() else "No text found in image"
577
-
578
- except Exception as e:
579
- return f"Error extracting text from image: {e}"
580
-
581
- def extract_text_from_file(file_path):
582
- if not file_path:
583
- return ""
584
- mime, _ = mimetypes.guess_type(file_path)
585
- ext = os.path.splitext(file_path)[1].lower()
586
- try:
587
- if ext == ".pdf":
588
- with open(file_path, "rb") as f:
589
- reader = PyPDF2.PdfReader(f)
590
- return "\n".join(page.extract_text() or "" for page in reader.pages)
591
- elif ext in [".txt", ".md"]:
592
- with open(file_path, "r", encoding="utf-8") as f:
593
- return f.read()
594
- elif ext == ".csv":
595
- with open(file_path, "r", encoding="utf-8") as f:
596
- return f.read()
597
- elif ext == ".docx":
598
- doc = docx.Document(file_path)
599
- return "\n".join([para.text for para in doc.paragraphs])
600
- elif ext.lower() in [".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif", ".gif", ".webp"]:
601
- return extract_text_from_image(file_path)
602
- else:
603
- return ""
604
- except Exception as e:
605
- return f"Error extracting text: {e}"
606
-
607
- def extract_website_content(url: str) -> str:
608
- """Extract HTML code and content from a website URL"""
609
- try:
610
- # Validate URL
611
- parsed_url = urlparse(url)
612
- if not parsed_url.scheme:
613
- url = "https://" + url
614
- parsed_url = urlparse(url)
615
-
616
- if not parsed_url.netloc:
617
- return "Error: Invalid URL provided"
618
-
619
- # Set comprehensive headers to mimic a real browser request
620
- headers = {
621
- 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
622
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
623
- 'Accept-Language': 'en-US,en;q=0.9',
624
- 'Accept-Encoding': 'gzip, deflate, br',
625
- 'DNT': '1',
626
- 'Connection': 'keep-alive',
627
- 'Upgrade-Insecure-Requests': '1',
628
- 'Sec-Fetch-Dest': 'document',
629
- 'Sec-Fetch-Mode': 'navigate',
630
- 'Sec-Fetch-Site': 'none',
631
- 'Sec-Fetch-User': '?1',
632
- 'Cache-Control': 'max-age=0'
633
- }
634
-
635
- # Create a session to maintain cookies and handle redirects
636
- session = requests.Session()
637
- session.headers.update(headers)
638
-
639
- # Make the request with retry logic
640
- max_retries = 3
641
- for attempt in range(max_retries):
642
- try:
643
- response = session.get(url, timeout=15, allow_redirects=True)
644
- response.raise_for_status()
645
- break
646
- except requests.exceptions.HTTPError as e:
647
- if e.response.status_code == 403 and attempt < max_retries - 1:
648
- # Try with different User-Agent on 403
649
- session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
650
- continue
651
- else:
652
- raise
653
-
654
- # Get the raw HTML content with proper encoding
655
- try:
656
- # Try to get the content with automatic encoding detection
657
- response.encoding = response.apparent_encoding
658
- raw_html = response.text
659
- except:
660
- # Fallback to UTF-8 if encoding detection fails
661
- raw_html = response.content.decode('utf-8', errors='ignore')
662
-
663
- # Debug: Check if we got valid HTML
664
- if not raw_html.strip().startswith('<!DOCTYPE') and not raw_html.strip().startswith('<html'):
665
- print(f"Warning: Response doesn't look like HTML. First 200 chars: {raw_html[:200]}")
666
- print(f"Response headers: {dict(response.headers)}")
667
- print(f"Response encoding: {response.encoding}")
668
- print(f"Apparent encoding: {response.apparent_encoding}")
669
-
670
- # Try alternative approaches
671
- try:
672
- raw_html = response.content.decode('latin-1', errors='ignore')
673
- print("Tried latin-1 decoding")
674
- except:
675
- try:
676
- raw_html = response.content.decode('utf-8', errors='ignore')
677
- print("Tried UTF-8 decoding")
678
- except:
679
- raw_html = response.content.decode('cp1252', errors='ignore')
680
- print("Tried cp1252 decoding")
681
-
682
- # Parse HTML content for analysis
683
- soup = BeautifulSoup(raw_html, 'html.parser')
684
-
685
- # Check if this is a JavaScript-heavy site
686
- script_tags = soup.find_all('script')
687
- if len(script_tags) > 10:
688
- print(f"Warning: This site has {len(script_tags)} script tags - it may be a JavaScript-heavy site")
689
- print("The content might be loaded dynamically and not available in the initial HTML")
690
-
691
- # Extract title
692
- title = soup.find('title')
693
- title_text = title.get_text().strip() if title else "No title found"
694
-
695
- # Extract meta description
696
- meta_desc = soup.find('meta', attrs={'name': 'description'})
697
- description = meta_desc.get('content', '') if meta_desc else ""
698
-
699
- # Extract main content areas for analysis
700
- content_sections = []
701
- main_selectors = [
702
- 'main', 'article', '.content', '.main-content', '.post-content',
703
- '#content', '#main', '.entry-content', '.post-body'
704
- ]
705
-
706
- for selector in main_selectors:
707
- elements = soup.select(selector)
708
- for element in elements:
709
- text = element.get_text().strip()
710
- if len(text) > 100: # Only include substantial content
711
- content_sections.append(text)
712
-
713
- # Extract navigation links for analysis
714
- nav_links = []
715
- nav_elements = soup.find_all(['nav', 'header'])
716
- for nav in nav_elements:
717
- links = nav.find_all('a')
718
- for link in links:
719
- link_text = link.get_text().strip()
720
- link_href = link.get('href', '')
721
- if link_text and link_href:
722
- nav_links.append(f"{link_text}: {link_href}")
723
-
724
- # Extract and fix image URLs in the HTML
725
- img_elements = soup.find_all('img')
726
- for img in img_elements:
727
- src = img.get('src', '')
728
- if src:
729
- # Handle different URL formats
730
- if src.startswith('//'):
731
- # Protocol-relative URL
732
- absolute_src = 'https:' + src
733
- img['src'] = absolute_src
734
- elif src.startswith('/'):
735
- # Root-relative URL
736
- absolute_src = urljoin(url, src)
737
- img['src'] = absolute_src
738
- elif not src.startswith(('http://', 'https://')):
739
- # Relative URL
740
- absolute_src = urljoin(url, src)
741
- img['src'] = absolute_src
742
- # If it's already absolute, keep it as is
743
-
744
- # Also check for data-src (lazy loading) and other common attributes
745
- data_src = img.get('data-src', '')
746
- if data_src and not src:
747
- # Use data-src if src is empty
748
- if data_src.startswith('//'):
749
- absolute_data_src = 'https:' + data_src
750
- img['src'] = absolute_data_src
751
- elif data_src.startswith('/'):
752
- absolute_data_src = urljoin(url, data_src)
753
- img['src'] = absolute_data_src
754
- elif not data_src.startswith(('http://', 'https://')):
755
- absolute_data_src = urljoin(url, data_src)
756
- img['src'] = absolute_data_src
757
- else:
758
- img['src'] = data_src
759
-
760
- # Also fix background image URLs in style attributes
761
- elements_with_style = soup.find_all(attrs={'style': True})
762
- for element in elements_with_style:
763
- style_attr = element.get('style', '')
764
- # Find and replace relative URLs in background-image
765
- import re
766
- bg_pattern = r'background-image:\s*url\(["\']?([^"\']+)["\']?\)'
767
- matches = re.findall(bg_pattern, style_attr, re.IGNORECASE)
768
- for match in matches:
769
- if match:
770
- if match.startswith('//'):
771
- absolute_bg = 'https:' + match
772
- style_attr = style_attr.replace(match, absolute_bg)
773
- elif match.startswith('/'):
774
- absolute_bg = urljoin(url, match)
775
- style_attr = style_attr.replace(match, absolute_bg)
776
- elif not match.startswith(('http://', 'https://')):
777
- absolute_bg = urljoin(url, match)
778
- style_attr = style_attr.replace(match, absolute_bg)
779
- element['style'] = style_attr
780
-
781
- # Fix background images in <style> tags
782
- style_elements = soup.find_all('style')
783
- for style in style_elements:
784
- if style.string:
785
- style_content = style.string
786
- # Find and replace relative URLs in background-image
787
- bg_pattern = r'background-image:\s*url\(["\']?([^"\']+)["\']?\)'
788
- matches = re.findall(bg_pattern, style_content, re.IGNORECASE)
789
- for match in matches:
790
- if match:
791
- if match.startswith('//'):
792
- absolute_bg = 'https:' + match
793
- style_content = style_content.replace(match, absolute_bg)
794
- elif match.startswith('/'):
795
- absolute_bg = urljoin(url, match)
796
- style_content = style_content.replace(match, absolute_bg)
797
- elif not match.startswith(('http://', 'https://')):
798
- absolute_bg = urljoin(url, match)
799
- style_content = style_content.replace(match, absolute_bg)
800
- style.string = style_content
801
-
802
- # Extract images for analysis (after fixing URLs)
803
- images = []
804
- img_elements = soup.find_all('img')
805
- for img in img_elements:
806
- src = img.get('src', '')
807
- alt = img.get('alt', '')
808
- if src:
809
- images.append({'src': src, 'alt': alt})
810
-
811
- # Debug: Print some image URLs to see what we're getting
812
- print(f"Found {len(images)} images:")
813
- for i, img in enumerate(images[:5]): # Show first 5 images
814
- print(f" {i+1}. {img['alt'] or 'No alt'} - {img['src']}")
815
-
816
- # Test a few image URLs to see if they're accessible
817
- def test_image_url(img_url):
818
- try:
819
- test_response = requests.head(img_url, timeout=5, allow_redirects=True)
820
- return test_response.status_code == 200
821
- except:
822
- return False
823
-
824
- # Test first few images
825
- working_images = []
826
- for img in images[:10]: # Test first 10 images
827
- if test_image_url(img['src']):
828
- working_images.append(img)
829
- else:
830
- print(f" ❌ Broken image: {img['src']}")
831
-
832
- print(f"Working images: {len(working_images)} out of {len(images)}")
833
-
834
- # Get the modified HTML with absolute URLs
835
- modified_html = str(soup)
836
-
837
- # Clean and format the HTML for better readability
838
- # Remove unnecessary whitespace and comments
839
- import re
840
- cleaned_html = re.sub(r'<!--.*?-->', '', modified_html, flags=re.DOTALL) # Remove HTML comments
841
- cleaned_html = re.sub(r'\s+', ' ', cleaned_html) # Normalize whitespace
842
- cleaned_html = re.sub(r'>\s+<', '><', cleaned_html) # Remove whitespace between tags
843
-
844
- # Limit HTML size to avoid token limits (keep first 15000 chars)
845
- if len(cleaned_html) > 15000:
846
- cleaned_html = cleaned_html[:15000] + "\n<!-- ... HTML truncated for length ... -->"
847
-
848
- # Check if we got any meaningful content
849
- if not title_text or title_text == "No title found":
850
- title_text = url.split('/')[-1] or url.split('/')[-2] or "Website"
851
-
852
- # If we couldn't extract any meaningful content, provide a fallback
853
- if len(cleaned_html.strip()) < 100:
854
- website_content = f"""
855
- WEBSITE REDESIGN - EXTRACTION FAILED
856
- ====================================
857
- URL: {url}
858
- Title: {title_text}
859
- ERROR: Could not extract meaningful HTML content from this website. This could be due to:
860
- 1. The website uses heavy JavaScript to load content dynamically
861
- 2. The website has anti-bot protection
862
- 3. The website requires authentication
863
- 4. The website is using advanced compression or encoding
864
- FALLBACK APPROACH:
865
- Please create a modern, responsive website design for a {title_text.lower()} website. Since I couldn't extract the original content, you can:
866
- 1. Create a typical layout for this type of website
867
- 2. Use placeholder content that would be appropriate
868
- 3. Include modern design elements and responsive features
869
- 4. Use a clean, professional design with good typography
870
- 5. Make it mobile-friendly and accessible
871
- The website appears to be: {title_text}
872
- """
873
- return website_content.strip()
874
-
875
- # Compile the extracted content with the actual HTML code
876
- website_content = f"""
877
- WEBSITE REDESIGN - ORIGINAL HTML CODE
878
- =====================================
879
- URL: {url}
880
- Title: {title_text}
881
- Description: {description}
882
- PAGE ANALYSIS:
883
- - This appears to be a {title_text.lower()} website
884
- - Contains {len(content_sections)} main content sections
885
- - Has {len(nav_links)} navigation links
886
- - Includes {len(images)} images
887
- IMAGES FOUND (use these exact URLs in your redesign):
888
- {chr(10).join([f"• {img['alt'] or 'Image'} - {img['src']}" for img in working_images[:20]]) if working_images else "No working images found"}
889
- ALL IMAGES (including potentially broken ones):
890
- {chr(10).join([f"• {img['alt'] or 'Image'} - {img['src']}" for img in images[:20]]) if images else "No images found"}
891
- ORIGINAL HTML CODE (use this as the base for redesign):
892
- ```html
893
- {cleaned_html}
894
- ```
895
- REDESIGN INSTRUCTIONS:
896
- Please redesign this website with a modern, responsive layout while:
897
- 1. Preserving all the original content and structure
898
- 2. Maintaining the same navigation and functionality
899
- 3. Using the original images and their URLs (listed above)
900
- 4. Creating a modern, clean design with improved typography and spacing
901
- 5. Making it fully responsive for mobile devices
902
- 6. Using modern CSS frameworks and best practices
903
- 7. Keeping the same semantic structure but with enhanced styling
904
- IMPORTANT: All image URLs in the HTML code above have been converted to absolute URLs and are ready to use. Make sure to preserve these exact image URLs in your redesigned version.
905
- The HTML code above contains the complete original website structure with all images properly linked. Use it as your starting point and create a modernized version.
906
- """
907
-
908
- return website_content.strip()
909
-
910
- except requests.exceptions.HTTPError as e:
911
- if e.response.status_code == 403:
912
- return f"Error: Website blocked access (403 Forbidden). This website may have anti-bot protection. Try a different website or provide a description of what you want to build instead."
913
- elif e.response.status_code == 404:
914
- return f"Error: Website not found (404). Please check the URL and try again."
915
- elif e.response.status_code >= 500:
916
- return f"Error: Website server error ({e.response.status_code}). Please try again later."
917
- else:
918
- return f"Error accessing website: HTTP {e.response.status_code} - {str(e)}"
919
- except requests.exceptions.Timeout:
920
- return "Error: Request timed out. The website may be slow or unavailable."
921
- except requests.exceptions.ConnectionError:
922
- return "Error: Could not connect to the website. Please check your internet connection and the URL."
923
- except requests.exceptions.RequestException as e:
924
- return f"Error accessing website: {str(e)}"
925
- except Exception as e:
926
- return f"Error extracting website content: {str(e)}"
927
-
928
- def generation_code(query: Optional[str], image: Optional[gr.Image], file: Optional[str], website_url: Optional[str], _setting: Dict[str, str], _history: Optional[History], _current_model: Dict, enable_search: bool = False, language: str = "html"):
929
- if query is None:
930
- query = ''
931
- if _history is None:
932
- _history = []
933
-
934
- # Check if there's existing HTML content in history to determine if this is a modification request
935
- has_existing_html = False
936
- if _history:
937
- # Check the last assistant message for HTML content
938
- last_assistant_msg = _history[-1][1] if len(_history) > 0 else ""
939
- if '<!DOCTYPE html>' in last_assistant_msg or '<html' in last_assistant_msg:
940
- has_existing_html = True
941
-
942
- # Choose system prompt based on context
943
- if has_existing_html:
944
- # Use follow-up prompt for modifying existing HTML
945
- system_prompt = FollowUpSystemPrompt
946
- else:
947
- # Use language-specific prompt
948
- if language == "html":
949
- system_prompt = HTML_SYSTEM_PROMPT_WITH_SEARCH if enable_search else HTML_SYSTEM_PROMPT
950
- else:
951
- system_prompt = GENERIC_SYSTEM_PROMPT_WITH_SEARCH.format(language=language) if enable_search else GENERIC_SYSTEM_PROMPT.format(language=language)
952
-
953
- messages = history_to_messages(_history, system_prompt)
954
-
955
- # Extract file text and append to query if file is present
956
- file_text = ""
957
- if file:
958
- file_text = extract_text_from_file(file)
959
- if file_text:
960
- file_text = file_text[:5000] # Limit to 5000 chars for prompt size
961
- query = f"{query}\n\n[Reference file content below]\n{file_text}"
962
-
963
- # Extract website content and append to query if website URL is present
964
- website_text = ""
965
- if website_url and website_url.strip():
966
- website_text = extract_website_content(website_url.strip())
967
- if website_text and not website_text.startswith("Error"):
968
- website_text = website_text[:8000] # Limit to 8000 chars for prompt size
969
- query = f"{query}\n\n[Website content to redesign below]\n{website_text}"
970
- elif website_text.startswith("Error"):
971
- # Provide helpful guidance when website extraction fails
972
- fallback_guidance = """
973
- Since I couldn't extract the website content, please provide additional details about what you'd like to build:
974
- 1. What type of website is this? (e.g., e-commerce, blog, portfolio, dashboard)
975
- 2. What are the main features you want?
976
- 3. What's the target audience?
977
- 4. Any specific design preferences? (colors, style, layout)
978
- This will help me create a better design for you."""
979
- query = f"{query}\n\n[Error extracting website: {website_text}]{fallback_guidance}"
980
-
981
- # Enhance query with search if enabled
982
- enhanced_query = enhance_query_with_search(query, enable_search)
983
-
984
- # Use dynamic client based on selected model
985
- client = get_inference_client(_current_model["id"])
986
-
987
- if image is not None:
988
- messages.append(create_multimodal_message(enhanced_query, image))
989
- else:
990
- messages.append({'role': 'user', 'content': enhanced_query})
991
- try:
992
- completion = client.chat.completions.create(
993
- model=_current_model["id"],
994
- messages=messages,
995
- stream=True,
996
- max_tokens=5000
997
- )
998
- content = ""
999
- for chunk in completion:
1000
- if chunk.choices[0].delta.content:
1001
- content += chunk.choices[0].delta.content
1002
- clean_code = remove_code_block(content)
1003
- search_status = " (with web search)" if enable_search and tavily_client else ""
1004
- if has_existing_html:
1005
- # Fallback: If the model returns a full HTML file, use it directly
1006
- if clean_code.strip().startswith("<!DOCTYPE html>") or clean_code.strip().startswith("<html"):
1007
- yield {
1008
- code_output: gr.update(value=clean_code, language=get_gradio_language(language)),
1009
- history_output: history_to_chatbot_messages(_history),
1010
- sandbox: send_to_sandbox(clean_code) if language == "html" else "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML. Please download your code using the download button above.</div>",
1011
- }
1012
- else:
1013
- last_html = _history[-1][1] if _history else ""
1014
- modified_html = apply_search_replace_changes(last_html, clean_code)
1015
- clean_html = remove_code_block(modified_html)
1016
- yield {
1017
- code_output: gr.update(value=clean_html, language=get_gradio_language(language)),
1018
- history_output: history_to_chatbot_messages(_history),
1019
- sandbox: send_to_sandbox(clean_html) if language == "html" else "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML. Please download your code using the download button above.</div>",
1020
- }
1021
- else:
1022
- yield {
1023
- code_output: gr.update(value=clean_code, language=get_gradio_language(language)),
1024
- history_output: history_to_chatbot_messages(_history),
1025
- sandbox: send_to_sandbox(clean_code) if language == "html" else "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML. Please download your code using the download button above.</div>",
1026
- }
1027
- # Handle response based on whether this is a modification or new generation
1028
- if has_existing_html:
1029
- # Fallback: If the model returns a full HTML file, use it directly
1030
- final_code = remove_code_block(content)
1031
- if final_code.strip().startswith("<!DOCTYPE html>") or final_code.strip().startswith("<html"):
1032
- clean_html = final_code
1033
- else:
1034
- last_html = _history[-1][1] if _history else ""
1035
- modified_html = apply_search_replace_changes(last_html, final_code)
1036
- clean_html = remove_code_block(modified_html)
1037
- # Update history with the cleaned HTML
1038
- _history = messages_to_history(messages + [{
1039
- 'role': 'assistant',
1040
- 'content': clean_html
1041
- }])
1042
- yield {
1043
- code_output: clean_html,
1044
- history: _history,
1045
- sandbox: send_to_sandbox(clean_html),
1046
- history_output: history_to_chatbot_messages(_history),
1047
- }
1048
- else:
1049
- # Regular generation - use the content as is
1050
- _history = messages_to_history(messages + [{
1051
- 'role': 'assistant',
1052
- 'content': content
1053
- }])
1054
- yield {
1055
- code_output: remove_code_block(content),
1056
- history: _history,
1057
- sandbox: send_to_sandbox(remove_code_block(content)),
1058
- history_output: history_to_chatbot_messages(_history),
1059
- }
1060
- except Exception as e:
1061
- error_message = f"Error: {str(e)}"
1062
- yield {
1063
- code_output: error_message,
1064
- history_output: history_to_chatbot_messages(_history),
1065
- }
1066
-
1067
- # Main application
1068
- with gr.Blocks(
1069
- theme=gr.themes.Base(
1070
- primary_hue="blue",
1071
- secondary_hue="gray",
1072
- neutral_hue="gray",
1073
- font=gr.themes.GoogleFont("Inter"),
1074
- font_mono=gr.themes.GoogleFont("JetBrains Mono"),
1075
- text_size=gr.themes.sizes.text_md,
1076
- spacing_size=gr.themes.sizes.spacing_md,
1077
- radius_size=gr.themes.sizes.radius_md
1078
- ),
1079
- title="AnyCoder - AI Code Generator"
1080
- ) as demo:
1081
- history = gr.State([])
1082
- setting = gr.State({
1083
- "system": HTML_SYSTEM_PROMPT,
1084
- })
1085
- current_model = gr.State(AVAILABLE_MODELS[0]) # Moonshot Kimi-K2
1086
- open_panel = gr.State(None)
1087
- last_login_state = gr.State(None)
1088
-
1089
- with gr.Sidebar():
1090
- # Remove Hugging Face Login Button and login-required message
1091
- # login_button = gr.LoginButton(
1092
- # value="Sign in with Hugging Face",
1093
- # variant="huggingface",
1094
- # size="lg"
1095
- # )
1096
- # login_required_msg = gr.Markdown("**Please sign in with Hugging Face to use the app.**", visible=True)
1097
- input = gr.Textbox(
1098
- label="What would you like to build?",
1099
- placeholder="Describe your application...",
1100
- lines=3,
1101
- visible=True # Always visible
1102
- )
1103
- # Language dropdown for code generation
1104
- language_choices = [
1105
- "python", "c", "cpp", "markdown", "latex", "json", "html", "css", "javascript", "jinja2", "typescript", "yaml", "dockerfile", "shell", "r", "sql", "sql-msSQL", "sql-mySQL", "sql-mariaDB", "sql-sqlite", "sql-cassandra", "sql-plSQL", "sql-hive", "sql-pgSQL", "sql-gql", "sql-gpSQL", "sql-sparkSQL", "sql-esper"
1106
- ]
1107
- language_dropdown = gr.Dropdown(
1108
- choices=language_choices,
1109
- value="html",
1110
- label="Code Language",
1111
- visible=True # Always visible
1112
- )
1113
- website_url_input = gr.Textbox(
1114
- label="Website URL for redesign",
1115
- placeholder="https://example.com",
1116
- lines=1,
1117
- visible=True # Always visible
1118
- )
1119
- file_input = gr.File(
1120
- label="Reference file",
1121
- file_types=[".pdf", ".txt", ".md", ".csv", ".docx", ".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif", ".gif", ".webp"],
1122
- visible=True # Always visible
1123
- )
1124
- image_input = gr.Image(
1125
- label="UI design image",
1126
- visible=False # Hidden by default; shown only for ERNIE-VL or GLM-VL
1127
- )
1128
- with gr.Row():
1129
- btn = gr.Button("Generate", variant="primary", size="lg", scale=2, visible=True) # Always visible
1130
- clear_btn = gr.Button("Clear", variant="secondary", size="sm", scale=1, visible=True) # Always visible
1131
- search_toggle = gr.Checkbox(
1132
- label="🔍 Web search",
1133
- value=False,
1134
- visible=True # Always visible
1135
- )
1136
- model_dropdown = gr.Dropdown(
1137
- choices=[model['name'] for model in AVAILABLE_MODELS],
1138
- value=AVAILABLE_MODELS[0]['name'], # Moonshot Kimi-K2
1139
- label="Model",
1140
- visible=True # Always visible
1141
- )
1142
- gr.Markdown("**Quick start**", visible=True)
1143
- with gr.Column(visible=True) as quick_examples_col:
1144
- for i, demo_item in enumerate(DEMO_LIST[:3]):
1145
- demo_card = gr.Button(
1146
- value=demo_item['title'],
1147
- variant="secondary",
1148
- size="sm"
1149
- )
1150
- demo_card.click(
1151
- fn=lambda idx=i: gr.update(value=DEMO_LIST[idx]['description']),
1152
- outputs=input
1153
- )
1154
- if not tavily_client:
1155
- gr.Markdown("⚠️ Web search unavailable", visible=True)
1156
- else:
1157
- gr.Markdown("✅ Web search available", visible=True)
1158
- model_display = gr.Markdown(f"**Model:** {AVAILABLE_MODELS[0]['name']}", visible=True) # Moonshot Kimi-K2
1159
- def on_model_change(model_name):
1160
- for m in AVAILABLE_MODELS:
1161
- if m['name'] == model_name:
1162
- return m, f"**Model:** {m['name']}", update_image_input_visibility(m)
1163
- return AVAILABLE_MODELS[0], f"**Model:** {AVAILABLE_MODELS[0]['name']}", update_image_input_visibility(AVAILABLE_MODELS[0]) # Moonshot Kimi-K2 fallback
1164
- def save_prompt(input):
1165
- return {setting: {"system": input}}
1166
- model_dropdown.change(
1167
- on_model_change,
1168
- inputs=model_dropdown,
1169
- outputs=[current_model, model_display, image_input]
1170
- )
1171
- with gr.Accordion("Advanced", open=False, visible=True) as advanced_accordion:
1172
- systemPromptInput = gr.Textbox(
1173
- value=HTML_SYSTEM_PROMPT,
1174
- label="System prompt",
1175
- lines=5
1176
- )
1177
- save_prompt_btn = gr.Button("Save", variant="primary", size="sm")
1178
- save_prompt_btn.click(save_prompt, inputs=systemPromptInput, outputs=setting)
1179
-
1180
- # Remove login state and timer logic
1181
- # login_state = gr.State(False)
1182
- # timer = gr.Timer(1, active=True)
1183
- # def check_login(label, last_state):
1184
- # logged_in = label.startswith("Logout (")
1185
- # # Only update if state changes
1186
- # if last_state == logged_in:
1187
- # return [gr.skip()] * 13 # skip updating all outputs
1188
- # return (
1189
- # logged_in, # login_state
1190
- # gr.update(visible=not logged_in), # login_required_msg
1191
- # gr.update(visible=logged_in), # input
1192
- # gr.update(visible=logged_in), # website_url_input
1193
- # gr.update(visible=logged_in), # file_input
1194
- # gr.update(visible=logged_in), # btn
1195
- # gr.update(visible=logged_in), # clear_btn
1196
- # gr.update(visible=logged_in), # search_toggle
1197
- # gr.update(visible=logged_in), # model_dropdown
1198
- # gr.update(visible=logged_in), # quick_examples_col
1199
- # gr.update(visible=logged_in), # advanced_accordion
1200
- # logged_in, # update last_login_state
1201
- # gr.update(visible=logged_in), # language_dropdown
1202
- # )
1203
- # timer.tick(
1204
- # fn=check_login,
1205
- # inputs=[login_button, last_login_state],
1206
- # outputs=[login_state, login_required_msg, input, website_url_input, file_input, btn, clear_btn, search_toggle, model_dropdown, quick_examples_col, advanced_accordion, last_login_state, language_dropdown]
1207
- # )
1208
-
1209
- with gr.Column():
1210
- with gr.Tabs():
1211
- with gr.Tab("Code"):
1212
- code_output = gr.Code(
1213
- language="html",
1214
- lines=25,
1215
- interactive=False,
1216
- label="Generated code"
1217
- )
1218
- with gr.Tab("Preview"):
1219
- sandbox = gr.HTML(label="Live preview")
1220
- with gr.Tab("History"):
1221
- history_output = gr.Chatbot(show_label=False, height=400, type="messages")
1222
-
1223
- # Event handlers
1224
- def update_code_language(language):
1225
- return gr.update(language=get_gradio_language(language))
1226
-
1227
- language_dropdown.change(update_code_language, inputs=language_dropdown, outputs=code_output)
1228
-
1229
- def preview_logic(code, language):
1230
- if language == "html":
1231
- return send_to_sandbox(code)
1232
- else:
1233
- return "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML. Please download your code using the download button above.</div>"
1234
-
1235
- btn.click(
1236
- generation_code,
1237
- inputs=[input, image_input, file_input, website_url_input, setting, history, current_model, search_toggle, language_dropdown],
1238
- outputs=[code_output, history, sandbox, history_output]
1239
- )
1240
- # Update preview when code or language changes
1241
- code_output.change(preview_logic, inputs=[code_output, language_dropdown], outputs=sandbox)
1242
- language_dropdown.change(preview_logic, inputs=[code_output, language_dropdown], outputs=sandbox)
1243
- clear_btn.click(clear_history, outputs=[history, history_output, file_input, website_url_input])
1244
-
1245
- if __name__ == "__main__":
1246
  demo.queue(api_open=False, default_concurrency_limit=20).launch(ssr_mode=True, mcp_server=False, show_api=False)
 
1
+ import os
2
+ from typing import Dict, List, Optional, Tuple
3
+
4
+ import gradio as gr
5
+
6
+ from config import (
7
+ AVAILABLE_MODELS, DEMO_LIST, HTML_SYSTEM_PROMPT
8
+ )
9
+ from api_clients import generation_code, tavily_client
10
+ from chat_processing import (
11
+ clear_history, history_to_chatbot_messages, update_image_input_visibility,
12
+ get_gradio_language, send_to_sandbox
13
+ )
14
+
15
+
16
+ def demo_card_click(e: gr.EventData):
17
+ try:
18
+ # Get the index from the event data
19
+ if hasattr(e, '_data') and e._data:
20
+ # Try different ways to get the index
21
+ if 'index' in e._data:
22
+ index = e._data['index']
23
+ elif 'component' in e._data and 'index' in e._data['component']:
24
+ index = e._data['component']['index']
25
+ elif 'target' in e._data and 'index' in e._data['target']:
26
+ index = e._data['target']['index']
27
+ else:
28
+ # If we can't get the index, try to extract it from the card data
29
+ index = 0
30
+ else:
31
+ index = 0
32
+
33
+ # Ensure index is within bounds
34
+ if index >= len(DEMO_LIST):
35
+ index = 0
36
+
37
+ return DEMO_LIST[index]['description']
38
+ except (KeyError, IndexError, AttributeError) as e:
39
+ # Return the first demo description as fallback
40
+ return DEMO_LIST[0]['description']
41
+
42
+ # Main application
43
+ with gr.Blocks(
44
+ theme=gr.themes.Base(
45
+ primary_hue="blue",
46
+ secondary_hue="gray",
47
+ neutral_hue="gray",
48
+ font=gr.themes.GoogleFont("Inter"),
49
+ font_mono=gr.themes.GoogleFont("JetBrains Mono"),
50
+ text_size=gr.themes.sizes.text_md,
51
+ spacing_size=gr.themes.sizes.spacing_md,
52
+ radius_size=gr.themes.sizes.radius_md
53
+ ),
54
+ title="AnyCoder - AI Code Generator"
55
+ ) as demo:
56
+ history = gr.State([])
57
+ setting = gr.State({
58
+ "system": HTML_SYSTEM_PROMPT,
59
+ })
60
+ current_model = gr.State(AVAILABLE_MODELS[0]) # Moonshot Kimi-K2
61
+ open_panel = gr.State(None)
62
+ last_login_state = gr.State(None)
63
+ with gr.Sidebar():
64
+ input = gr.Textbox(
65
+ label="What would you like to build?",
66
+ placeholder="Describe your application...",
67
+ lines=3,
68
+ visible=True # Always visible
69
+ )
70
+ # Language dropdown for code generation
71
+ language_choices = [
72
+ "python", "c", "cpp", "markdown", "latex", "json", "html", "css", "javascript", "jinja2", "typescript", "yaml", "dockerfile", "shell", "r", "sql", "sql-msSQL", "sql-mySQL", "sql-mariaDB", "sql-sqlite", "sql-cassandra", "sql-plSQL", "sql-hive", "sql-pgSQL", "sql-gql", "sql-gpSQL", "sql-sparkSQL", "sql-esper"
73
+ ]
74
+ language_dropdown = gr.Dropdown(
75
+ choices=language_choices,
76
+ value="html",
77
+ label="Code Language",
78
+ visible=True # Always visible
79
+ )
80
+ website_url_input = gr.Textbox(
81
+ label="Website URL for redesign",
82
+ placeholder="https://example.com",
83
+ lines=1,
84
+ visible=True # Always visible
85
+ )
86
+ file_input = gr.File(
87
+ label="Reference file",
88
+ file_types=[".pdf", ".txt", ".md", ".csv", ".docx", ".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif", ".gif", ".webp"],
89
+ visible=True # Always visible
90
+ )
91
+ image_input = gr.Image(
92
+ label="UI design image",
93
+ visible=False # Hidden by default; shown only for ERNIE-VL or GLM-VL
94
+ )
95
+ with gr.Row():
96
+ btn = gr.Button("Generate", variant="primary", size="lg", scale=2, visible=True) # Always visible
97
+ clear_btn = gr.Button("Clear", variant="secondary", size="sm", scale=1, visible=True) # Always visible
98
+ search_toggle = gr.Checkbox(
99
+ label="🔍 Web search",
100
+ value=False,
101
+ visible=True # Always visible
102
+ )
103
+ model_dropdown = gr.Dropdown(
104
+ choices=[model['name'] for model in AVAILABLE_MODELS],
105
+ value=AVAILABLE_MODELS[0]['name'], # Moonshot Kimi-K2
106
+ label="Model",
107
+ visible=True # Always visible
108
+ )
109
+ gr.Markdown("**Quick start**", visible=True)
110
+ with gr.Column(visible=True) as quick_examples_col:
111
+ for i, demo_item in enumerate(DEMO_LIST[:3]):
112
+ demo_card = gr.Button(
113
+ value=demo_item['title'],
114
+ variant="secondary",
115
+ size="sm"
116
+ )
117
+ demo_card.click(
118
+ fn=lambda idx=i: gr.update(value=DEMO_LIST[idx]['description']),
119
+ outputs=input
120
+ )
121
+ if not tavily_client:
122
+ gr.Markdown("⚠️ Web search unavailable", visible=True)
123
+ else:
124
+ gr.Markdown(" Web search available", visible=True)
125
+ model_display = gr.Markdown(f"**Model:** {AVAILABLE_MODELS[0]['name']}", visible=True) # Moonshot Kimi-K2
126
+ def on_model_change(model_name):
127
+ for m in AVAILABLE_MODELS:
128
+ if m['name'] == model_name:
129
+ return m, f"**Model:** {m['name']}", update_image_input_visibility(m)
130
+ return AVAILABLE_MODELS[0], f"**Model:** {AVAILABLE_MODELS[0]['name']}", update_image_input_visibility(AVAILABLE_MODELS[0]) # Moonshot Kimi-K2 fallback
131
+ def save_prompt(input):
132
+ return {setting: {"system": input}}
133
+ model_dropdown.change(
134
+ on_model_change,
135
+ inputs=model_dropdown,
136
+ outputs=[current_model, model_display, image_input]
137
+ )
138
+ with gr.Accordion("Advanced", open=False, visible=True) as advanced_accordion:
139
+ systemPromptInput = gr.Textbox(
140
+ value=HTML_SYSTEM_PROMPT,
141
+ label="System prompt",
142
+ lines=5
143
+ )
144
+ save_prompt_btn = gr.Button("Save", variant="primary", size="sm")
145
+ save_prompt_btn.click(save_prompt, inputs=systemPromptInput, outputs=setting)
146
+
147
+ with gr.Column():
148
+ with gr.Tabs():
149
+ with gr.Tab("Code"):
150
+ code_output = gr.Code(
151
+ language="html",
152
+ lines=25,
153
+ interactive=False,
154
+ label="Generated code"
155
+ )
156
+ with gr.Tab("Preview"):
157
+ sandbox = gr.HTML(label="Live preview")
158
+ with gr.Tab("History"):
159
+ history_output = gr.Chatbot(show_label=False, height=400, type="messages")
160
+
161
+ # Event handlers
162
+ def update_code_language(language):
163
+ return gr.update(language=get_gradio_language(language))
164
+
165
+ language_dropdown.change(update_code_language, inputs=language_dropdown, outputs=code_output)
166
+
167
+ def preview_logic(code, language):
168
+ if language == "html":
169
+ return send_to_sandbox(code)
170
+ else:
171
+ return "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML. Please download your code using the download button above.</div>"
172
+
173
+ btn.click(
174
+ generation_code,
175
+ inputs=[input, image_input, file_input, website_url_input, setting, history, current_model, search_toggle, language_dropdown],
176
+ outputs=[code_output, history, sandbox, history_output]
177
+ )
178
+ # Update preview when code or language changes
179
+ code_output.change(preview_logic, inputs=[code_output, language_dropdown], outputs=sandbox)
180
+ language_dropdown.change(preview_logic, inputs=[code_output, language_dropdown], outputs=sandbox)
181
+ clear_btn.click(clear_history, outputs=[history, history_output, file_input, website_url_input])
182
+
183
+ if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  demo.queue(api_open=False, default_concurrency_limit=20).launch(ssr_mode=True, mcp_server=False, show_api=False)
chat_processing.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from typing import Dict, List, Optional, Tuple
3
+ import base64
4
+ import numpy as np
5
+ from PIL import Image
6
+ import gradio as gr
7
+
8
+ from config import GRADIO_SUPPORTED_LANGUAGES, SEARCH_START, DIVIDER, REPLACE_END
9
+
10
+ History = List[Tuple[str, str]]
11
+ Messages = List[Dict[str, str]]
12
+
13
+ def get_gradio_language(language):
14
+ return language if language in GRADIO_SUPPORTED_LANGUAGES else None
15
+
16
+ def history_to_messages(history: History, system: str) -> Messages:
17
+ messages = [{'role': 'system', 'content': system}]
18
+ for h in history:
19
+ # Handle multimodal content in history
20
+ user_content = h[0]
21
+ if isinstance(user_content, list):
22
+ # Extract text from multimodal content
23
+ text_content = ""
24
+ for item in user_content:
25
+ if isinstance(item, dict) and item.get("type") == "text":
26
+ text_content += item.get("text", "")
27
+ user_content = text_content if text_content else str(user_content)
28
+
29
+ messages.append({'role': 'user', 'content': user_content})
30
+ messages.append({'role': 'assistant', 'content': h[1]})
31
+ return messages
32
+
33
+ def messages_to_history(messages: Messages) -> History:
34
+ assert messages[0]['role'] == 'system'
35
+ history = []
36
+ for q, r in zip(messages[1::2], messages[2::2]):
37
+ # Extract text content from multimodal messages for history
38
+ user_content = q['content']
39
+ if isinstance(user_content, list):
40
+ text_content = ""
41
+ for item in user_content:
42
+ if isinstance(item, dict) and item.get("type") == "text":
43
+ text_content += item.get("text", "")
44
+ user_content = text_content if text_content else str(user_content)
45
+
46
+ history.append((user_content, r['content']))
47
+ return history
48
+
49
+ def history_to_chatbot_messages(history: History) -> List[Dict[str, str]]:
50
+ """Convert history tuples to chatbot message format"""
51
+ messages = []
52
+ for user_msg, assistant_msg in history:
53
+ # Handle multimodal content
54
+ if isinstance(user_msg, list):
55
+ text_content = ""
56
+ for item in user_msg:
57
+ if isinstance(item, dict) and item.get("type") == "text":
58
+ text_content += item.get("text", "")
59
+ user_msg = text_content if text_content else str(user_msg)
60
+
61
+ messages.append({"role": "user", "content": user_msg})
62
+ messages.append({"role": "assistant", "content": assistant_msg})
63
+ return messages
64
+
65
+ def remove_code_block(text):
66
+ # Try to match code blocks with language markers
67
+ patterns = [
68
+ r'```(?:html|HTML)\n([\s\S]+?)\n```', # Match ```html or ```HTML
69
+ r'```\n([\s\S]+?)\n```', # Match code blocks without language markers
70
+ r'```([\s\S]+?)```' # Match code blocks without line breaks
71
+ ]
72
+ for pattern in patterns:
73
+ match = re.search(pattern, text, re.DOTALL)
74
+ if match:
75
+ extracted = match.group(1).strip()
76
+ return extracted
77
+ # If no code block is found, check if the entire text is HTML
78
+ if text.strip().startswith('<!DOCTYPE html>') or text.strip().startswith('<html') or text.strip().startswith('<'):
79
+ return text.strip()
80
+ return text.strip()
81
+
82
+ def clear_history():
83
+ return [], [], None, "" # Empty lists for both tuple format and chatbot messages, None for file, empty string for website URL
84
+
85
+ def update_image_input_visibility(model):
86
+ """Update image input visibility based on selected model"""
87
+ is_ernie_vl = model.get("id") == "baidu/ERNIE-4.5-VL-424B-A47B-Base-PT"
88
+ is_glm_vl = model.get("id") == "THUDM/GLM-4.1V-9B-Thinking"
89
+ return gr.update(visible=is_ernie_vl or is_glm_vl)
90
+
91
+ def apply_search_replace_changes(original_html: str, changes_text: str) -> str:
92
+ """Apply search/replace changes to HTML content"""
93
+ if not changes_text.strip():
94
+ return original_html
95
+
96
+ # Split the changes text into individual search/replace blocks
97
+ blocks = []
98
+ current_block = ""
99
+ lines = changes_text.split('\n')
100
+
101
+ for line in lines:
102
+ if line.strip() == SEARCH_START:
103
+ if current_block.strip():
104
+ blocks.append(current_block.strip())
105
+ current_block = line + '\n'
106
+ elif line.strip() == REPLACE_END:
107
+ current_block += line + '\n'
108
+ blocks.append(current_block.strip())
109
+ current_block = ""
110
+ else:
111
+ current_block += line + '\n'
112
+
113
+ if current_block.strip():
114
+ blocks.append(current_block.strip())
115
+
116
+ modified_html = original_html
117
+
118
+ for block in blocks:
119
+ if not block.strip():
120
+ continue
121
+
122
+ # Parse the search/replace block
123
+ lines = block.split('\n')
124
+ search_lines = []
125
+ replace_lines = []
126
+ in_search = False
127
+ in_replace = False
128
+
129
+ for line in lines:
130
+ if line.strip() == SEARCH_START:
131
+ in_search = True
132
+ in_replace = False
133
+ elif line.strip() == DIVIDER:
134
+ in_search = False
135
+ in_replace = True
136
+ elif line.strip() == REPLACE_END:
137
+ in_replace = False
138
+ elif in_search:
139
+ search_lines.append(line)
140
+ elif in_replace:
141
+ replace_lines.append(line)
142
+
143
+ # Apply the search/replace
144
+ if search_lines:
145
+ search_text = '\n'.join(search_lines).strip()
146
+ replace_text = '\n'.join(replace_lines).strip()
147
+
148
+ if search_text in modified_html:
149
+ modified_html = modified_html.replace(search_text, replace_text)
150
+ else:
151
+ print(f"Warning: Search text not found in HTML: {search_text[:100]}...")
152
+
153
+ return modified_html
154
+
155
+ def send_to_sandbox(code):
156
+ # Add a wrapper to inject necessary permissions and ensure full HTML
157
+ wrapped_code = f"""
158
+ <!DOCTYPE html>
159
+ <html>
160
+ <head>
161
+ <meta charset=\"UTF-8\">
162
+ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
163
+ <script>
164
+ // Safe localStorage polyfill
165
+ const safeStorage = {{
166
+ _data: {{}},
167
+ getItem: function(key) {{ return this._data[key] || null; }},
168
+ setItem: function(key, value) {{ this._data[key] = value; }},
169
+ removeItem: function(key) {{ delete this._data[key]; }},
170
+ clear: function() {{ this._data = {{}}; }}
171
+ }};
172
+ Object.defineProperty(window, 'localStorage', {{
173
+ value: safeStorage,
174
+ writable: false
175
+ }});
176
+ window.onerror = function(message, source, lineno, colno, error) {{
177
+ console.error('Error:', message);
178
+ }};
179
+ </script>
180
+ </head>
181
+ <body>
182
+ {code}
183
+ </body>
184
+ </html>
185
+ """
186
+ encoded_html = base64.b64encode(wrapped_code.encode('utf-8')).decode('utf-8')
187
+ data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
188
+ iframe = f'<iframe src="{data_uri}" width="100%" height="920px" sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-presentation" allow="display-capture"></iframe>'
189
+ return iframe
config.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gradio supported languages for syntax highlighting
2
+ GRADIO_SUPPORTED_LANGUAGES = [
3
+ "python", "c", "cpp", "markdown", "latex", "json", "html", "css", "javascript", "jinja2", "typescript", "yaml", "dockerfile", "shell", "r", "sql", "sql-msSQL", "sql-mySQL", "sql-mariaDB", "sql-sqlite", "sql-cassandra", "sql-plSQL", "sql-hive", "sql-pgSQL", "sql-gql", "sql-gpSQL", "sql-sparkSQL", "sql-esper", None
4
+ ]
5
+
6
+ # Search/Replace Constants
7
+ SEARCH_START = "<<<<<<< SEARCH"
8
+ DIVIDER = "======="
9
+ REPLACE_END = ">>>>>>> REPLACE"
10
+
11
+ # Configuration
12
+ HTML_SYSTEM_PROMPT = """ONLY USE HTML, CSS AND JAVASCRIPT. If you want to use ICON make sure to import the library first. Try to create the best UI possible by using only HTML, CSS and JAVASCRIPT. MAKE IT RESPONSIVE USING MODERN CSS. Use as much as you can modern CSS for the styling, if you can't do something with modern CSS, then use custom CSS. Also, try to elaborate as much as you can, to create something unique. ALWAYS GIVE THE RESPONSE INTO A SINGLE HTML FILE
13
+ For website redesign tasks:
14
+ - Use the provided original HTML code as the starting point for redesign
15
+ - Preserve all original content, structure, and functionality
16
+ - Keep the same semantic HTML structure but enhance the styling
17
+ - Reuse all original images and their URLs from the HTML code
18
+ - Create a modern, responsive design with improved typography and spacing
19
+ - Use modern CSS frameworks and design patterns
20
+ - Ensure accessibility and mobile responsiveness
21
+ - Maintain the same navigation and user flow
22
+ - Enhance the visual design while keeping the original layout structure
23
+ If an image is provided, analyze it and use the visual information to better understand the user's requirements.
24
+ Always respond with code that can be executed or rendered directly.
25
+ Always output only the HTML code inside a ```html ... ``` code block, and do not include any explanations or extra text."""
26
+
27
+ GENERIC_SYSTEM_PROMPT = """You are an expert {language} developer. Write clean, idiomatic, and runnable {language} code for the user's request. If possible, include comments and best practices. Output ONLY the code inside a ```{language} ... ``` code block, and do not include any explanations or extra text. If the user provides a file or other context, use it as a reference. If the code is for a script or app, make it as self-contained as possible."""
28
+
29
+ # System prompt with search capability
30
+ HTML_SYSTEM_PROMPT_WITH_SEARCH = """ONLY USE HTML, CSS AND JAVASCRIPT. If you want to use ICON make sure to import the library first. Try to create the best UI possible by using only HTML, CSS and JAVASCRIPT. MAKE IT RESPONSIVE USING MODERN CSS. Use as much as you can modern CSS for the styling, if you can't do something with modern CSS, then use custom CSS. Also, try to elaborate as much as you can, to create something unique. ALWAYS GIVE THE RESPONSE INTO A SINGLE HTML FILE
31
+ You have access to real-time web search. When needed, use web search to find the latest information, best practices, or specific technologies.
32
+ For website redesign tasks:
33
+ - Use the provided original HTML code as the starting point for redesign
34
+ - Preserve all original content, structure, and functionality
35
+ - Keep the same semantic HTML structure but enhance the styling
36
+ - Reuse all original images and their URLs from the HTML code
37
+ - Use web search to find current design trends and best practices for the specific type of website
38
+ - Create a modern, responsive design with improved typography and spacing
39
+ - Use modern CSS frameworks and design patterns
40
+ - Ensure accessibility and mobile responsiveness
41
+ - Maintain the same navigation and user flow
42
+ - Enhance the visual design while keeping the original layout structure
43
+ If an image is provided, analyze it and use the visual information to better understand the user's requirements.
44
+ Always respond with code that can be executed or rendered directly.
45
+ Always output only the HTML code inside a ```html ... ``` code block, and do not include any explanations or extra text."""
46
+
47
+ GENERIC_SYSTEM_PROMPT_WITH_SEARCH = """You are an expert {language} developer. You have access to real-time web search. When needed, use web search to find the latest information, best practices, or specific technologies for {language}.
48
+ Write clean, idiomatic, and runnable {language} code for the user's request. If possible, include comments and best practices. Output ONLY the code inside a ```{language} ... ``` code block, and do not include any explanations or extra text. If the user provides a file or other context, use it as a reference. If the code is for a script or app, make it as self-contained as possible."""
49
+
50
+ # Follow-up system prompt for modifying existing HTML files
51
+ FollowUpSystemPrompt = f"""You are an expert web developer modifying an existing HTML file.
52
+ The user wants to apply changes based on their request.
53
+ You MUST output ONLY the changes required using the following SEARCH/REPLACE block format. Do NOT output the entire file.
54
+ Explain the changes briefly *before* the blocks if necessary, but the code changes THEMSELVES MUST be within the blocks.
55
+ Format Rules:
56
+ 1. Start with {SEARCH_START}
57
+ 2. Provide the exact lines from the current code that need to be replaced.
58
+ 3. Use {DIVIDER} to separate the search block from the replacement.
59
+ 4. Provide the new lines that should replace the original lines.
60
+ 5. End with {REPLACE_END}
61
+ 6. You can use multiple SEARCH/REPLACE blocks if changes are needed in different parts of the file.
62
+ 7. To insert code, use an empty SEARCH block (only {SEARCH_START} and {DIVIDER} on their lines) if inserting at the very beginning, otherwise provide the line *before* the insertion point in the SEARCH block and include that line plus the new lines in the REPLACE block.
63
+ 8. To delete code, provide the lines to delete in the SEARCH block and leave the REPLACE block empty (only {DIVIDER} and {REPLACE_END} on their lines).
64
+ 9. IMPORTANT: The SEARCH block must *exactly* match the current code, including indentation and whitespace.
65
+ Example Modifying Code:
66
+ ```
67
+ Some explanation...
68
+ {SEARCH_START}
69
+ <h1>Old Title</h1>
70
+ {DIVIDER}
71
+ <h1>New Title</h1>
72
+ {REPLACE_END}
73
+ {SEARCH_START}
74
+ </body>
75
+ {DIVIDER}
76
+ <script>console.log("Added script");</script>
77
+ </body>
78
+ {REPLACE_END}
79
+ ```
80
+ Example Deleting Code:
81
+ ```
82
+ Removing the paragraph...
83
+ {SEARCH_START}
84
+ <p>This paragraph will be deleted.</p>
85
+ {DIVIDER}
86
+ {REPLACE_END}
87
+ ```"""
88
+
89
+ # Available models
90
+ AVAILABLE_MODELS = [
91
+ {
92
+ "name": "Moonshot Kimi-K2",
93
+ "id": "moonshotai/Kimi-K2-Instruct",
94
+ "description": "Moonshot AI Kimi-K2-Instruct model for code generation and general tasks"
95
+ },
96
+ {
97
+ "name": "DeepSeek V3",
98
+ "id": "deepseek-ai/DeepSeek-V3-0324",
99
+ "description": "DeepSeek V3 model for code generation"
100
+ },
101
+ {
102
+ "name": "DeepSeek R1",
103
+ "id": "deepseek-ai/DeepSeek-R1-0528",
104
+ "description": "DeepSeek R1 model for code generation"
105
+ },
106
+ {
107
+ "name": "ERNIE-4.5-VL",
108
+ "id": "baidu/ERNIE-4.5-VL-424B-A47B-Base-PT",
109
+ "description": "ERNIE-4.5-VL model for multimodal code generation with image support"
110
+ },
111
+ {
112
+ "name": "MiniMax M1",
113
+ "id": "MiniMaxAI/MiniMax-M1-80k",
114
+ "description": "MiniMax M1 model for code generation and general tasks"
115
+ },
116
+ {
117
+ "name": "Qwen3-235B-A22B",
118
+ "id": "Qwen/Qwen3-235B-A22B",
119
+ "description": "Qwen3-235B-A22B model for code generation and general tasks"
120
+ },
121
+ {
122
+ "name": "SmolLM3-3B",
123
+ "id": "HuggingFaceTB/SmolLM3-3B",
124
+ "description": "SmolLM3-3B model for code generation and general tasks"
125
+ },
126
+ {
127
+ "name": "GLM-4.1V-9B-Thinking",
128
+ "id": "THUDM/GLM-4.1V-9B-Thinking",
129
+ "description": "GLM-4.1V-9B-Thinking model for multimodal code generation with image support"
130
+ }
131
+ ]
132
+
133
+ DEMO_LIST = [
134
+ {
135
+ "title": "Todo App",
136
+ "description": "Create a simple todo application with add, delete, and mark as complete functionality"
137
+ },
138
+ {
139
+ "title": "Calculator",
140
+ "description": "Build a basic calculator with addition, subtraction, multiplication, and division"
141
+ },
142
+ {
143
+ "title": "Weather Dashboard",
144
+ "description": "Create a weather dashboard that displays current weather information"
145
+ },
146
+ {
147
+ "title": "Chat Interface",
148
+ "description": "Build a chat interface with message history and user input"
149
+ },
150
+ {
151
+ "title": "E-commerce Product Card",
152
+ "description": "Create a product card component for an e-commerce website"
153
+ },
154
+ {
155
+ "title": "Login Form",
156
+ "description": "Build a responsive login form with validation"
157
+ },
158
+ {
159
+ "title": "Dashboard Layout",
160
+ "description": "Create a dashboard layout with sidebar navigation and main content area"
161
+ },
162
+ {
163
+ "title": "Data Table",
164
+ "description": "Build a data table with sorting and filtering capabilities"
165
+ },
166
+ {
167
+ "title": "Image Gallery",
168
+ "description": "Create an image gallery with lightbox functionality and responsive grid layout"
169
+ },
170
+ {
171
+ "title": "UI from Image",
172
+ "description": "Upload an image of a UI design and I'll generate the HTML/CSS code for it"
173
+ },
174
+ {
175
+ "title": "Extract Text from Image",
176
+ "description": "Upload an image containing text and I'll extract and process the text content"
177
+ },
178
+ {
179
+ "title": "Website Redesign",
180
+ "description": "Enter a website URL to extract its content and redesign it with a modern, responsive layout"
181
+ },
182
+ {
183
+ "title": "Modify HTML",
184
+ "description": "After generating HTML, ask me to modify it with specific changes using search/replace format"
185
+ },
186
+ {
187
+ "title": "Search/Replace Example",
188
+ "description": "Generate HTML first, then ask: 'Change the title to My New Title' or 'Add a blue background to the body'"
189
+ }
190
+ ]
file_processing.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import mimetypes
3
+ import PyPDF2
4
+ import docx
5
+ import cv2
6
+ import numpy as np
7
+ from PIL import Image
8
+ import pytesseract
9
+
10
+ def process_image_for_model(image):
11
+ """Convert image to base64 for model input"""
12
+ if image is None:
13
+ return None
14
+
15
+ # Convert numpy array to PIL Image if needed
16
+ import io
17
+ import base64
18
+
19
+ # Handle numpy array from Gradio
20
+ if isinstance(image, np.ndarray):
21
+ image = Image.fromarray(image)
22
+
23
+ buffer = io.BytesIO()
24
+ image.save(buffer, format='PNG')
25
+ img_str = base64.b64encode(buffer.getvalue()).decode()
26
+ return f"data:image/png;base64,{img_str}"
27
+
28
+ def extract_text_from_image(image_path):
29
+ """Extract text from image using OCR"""
30
+ try:
31
+ # Check if tesseract is available
32
+ try:
33
+ pytesseract.get_tesseract_version()
34
+ except Exception:
35
+ return "Error: Tesseract OCR is not installed. Please install Tesseract to extract text from images. See install_tesseract.md for instructions."
36
+
37
+ image = cv2.imread(image_path)
38
+ if image is None:
39
+ return "Error: Could not read image file"
40
+
41
+ image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
42
+ gray = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2GRAY)
43
+ _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
44
+ text = pytesseract.image_to_string(binary, config='--psm 6')
45
+ return text.strip() if text.strip() else "No text found in image"
46
+
47
+ except Exception as e:
48
+ return f"Error extracting text from image: {e}"
49
+
50
+ def extract_text_from_file(file_path):
51
+ if not file_path:
52
+ return ""
53
+ ext = os.path.splitext(file_path)[1].lower()
54
+ try:
55
+ if ext == ".pdf":
56
+ with open(file_path, "rb") as f:
57
+ reader = PyPDF2.PdfReader(f)
58
+ return "\n".join(page.extract_text() or "" for page in reader.pages)
59
+ elif ext in [".txt", ".md", ".csv"]:
60
+ with open(file_path, "r", encoding="utf-8") as f:
61
+ return f.read()
62
+ elif ext == ".docx":
63
+ doc = docx.Document(file_path)
64
+ return "\n".join([para.text for para in doc.paragraphs])
65
+ elif ext.lower() in [".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif", ".gif", ".webp"]:
66
+ return extract_text_from_image(file_path)
67
+ else:
68
+ return ""
69
+ except Exception as e:
70
+ return f"Error extracting text: {e}"
web_extraction.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from urllib.parse import urlparse, urljoin
3
+ from bs4 import BeautifulSoup
4
+ import re
5
+ from tavily import TavilyClient
6
+ import os
7
+
8
+ tavily_client = None
9
+ TAVILY_API_KEY = os.getenv('TAVILY_API_KEY')
10
+ if TAVILY_API_KEY:
11
+ try:
12
+ tavily_client = TavilyClient(api_key=TAVILY_API_KEY)
13
+ except Exception as e:
14
+ print(f"Failed to initialize Tavily client: {e}")
15
+
16
+ def perform_web_search(query: str, max_results: int = 5, include_domains=None, exclude_domains=None) -> str:
17
+ """Perform web search using Tavily with default parameters"""
18
+ if not tavily_client:
19
+ return "Web search is not available. Please set the TAVILY_API_KEY environment variable."
20
+
21
+ try:
22
+ # Use Tavily defaults with advanced search depth for better results
23
+ search_params = {
24
+ "search_depth": "advanced",
25
+ "max_results": min(max(1, max_results), 20)
26
+ }
27
+ if include_domains is not None:
28
+ search_params["include_domains"] = include_domains
29
+ if exclude_domains is not None:
30
+ search_params["exclude_domains"] = exclude_domains
31
+
32
+ response = tavily_client.search(query, **search_params)
33
+
34
+ search_results = []
35
+ for result in response.get('results', []):
36
+ title = result.get('title', 'No title')
37
+ url = result.get('url', 'No URL')
38
+ content = result.get('content', 'No content')
39
+ search_results.append(f"Title: {title}\nURL: {url}\nContent: {content}\n")
40
+
41
+ if search_results:
42
+ return "Web Search Results:\n\n" + "\n---\n".join(search_results)
43
+ else:
44
+ return "No search results found."
45
+
46
+ except Exception as e:
47
+ return f"Search error: {str(e)}"
48
+
49
+ def enhance_query_with_search(query: str, enable_search: bool) -> str:
50
+ """Enhance the query with web search results if search is enabled"""
51
+ if not enable_search or not tavily_client:
52
+ return query
53
+
54
+ # Perform search to get relevant information
55
+ search_results = perform_web_search(query)
56
+
57
+ # Combine original query with search results
58
+ enhanced_query = f"""Original Query: {query}
59
+ {search_results}
60
+ Please use the search results above to help create the requested application with the most up-to-date information and best practices."""
61
+
62
+ return enhanced_query
63
+
64
+ def extract_website_content(url: str) -> str:
65
+ """Extract HTML code and content from a website URL"""
66
+ try:
67
+ # Validate URL
68
+ parsed_url = urlparse(url)
69
+ if not parsed_url.scheme:
70
+ url = "https://" + url
71
+ parsed_url = urlparse(url)
72
+
73
+ if not parsed_url.netloc:
74
+ return "Error: Invalid URL provided"
75
+
76
+ # Set comprehensive headers to mimic a real browser request
77
+ headers = {
78
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
79
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
80
+ 'Accept-Language': 'en-US,en;q=0.9',
81
+ 'Accept-Encoding': 'gzip, deflate, br',
82
+ 'DNT': '1',
83
+ 'Connection': 'keep-alive',
84
+ 'Upgrade-Insecure-Requests': '1',
85
+ 'Sec-Fetch-Dest': 'document',
86
+ 'Sec-Fetch-Mode': 'navigate',
87
+ 'Sec-Fetch-Site': 'none',
88
+ 'Sec-Fetch-User': '?1',
89
+ 'Cache-Control': 'max-age=0'
90
+ }
91
+
92
+ # Create a session to maintain cookies and handle redirects
93
+ session = requests.Session()
94
+ session.headers.update(headers)
95
+
96
+ # Make the request with retry logic
97
+ max_retries = 3
98
+ for attempt in range(max_retries):
99
+ try:
100
+ response = session.get(url, timeout=15, allow_redirects=True)
101
+ response.raise_for_status()
102
+ break
103
+ except requests.exceptions.HTTPError as e:
104
+ if e.response.status_code == 403 and attempt < max_retries - 1:
105
+ # Try with different User-Agent on 403
106
+ session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
107
+ continue
108
+ else:
109
+ raise
110
+
111
+ # Get the raw HTML content with proper encoding
112
+ try:
113
+ # Try to get the content with automatic encoding detection
114
+ response.encoding = response.apparent_encoding
115
+ raw_html = response.text
116
+ except:
117
+ # Fallback to UTF-8 if encoding detection fails
118
+ raw_html = response.content.decode('utf-8', errors='ignore')
119
+
120
+ # Debug: Check if we got valid HTML
121
+ if not raw_html.strip().startswith('<!DOCTYPE') and not raw_html.strip().startswith('<html'):
122
+ print(f"Warning: Response doesn't look like HTML. First 200 chars: {raw_html[:200]}")
123
+
124
+ # Try alternative approaches
125
+ try:
126
+ raw_html = response.content.decode('latin-1', errors='ignore')
127
+ except:
128
+ try:
129
+ raw_html = response.content.decode('utf-8', errors='ignore')
130
+ except:
131
+ raw_html = response.content.decode('cp1252', errors='ignore')
132
+
133
+ # Parse HTML content for analysis
134
+ soup = BeautifulSoup(raw_html, 'html.parser')
135
+
136
+ # Check if this is a JavaScript-heavy site
137
+ script_tags = soup.find_all('script')
138
+ if len(script_tags) > 10:
139
+ print(f"Warning: This site has {len(script_tags)} script tags - it may be a JavaScript-heavy site")
140
+ # Attempt to use Playwright to render the page and get full HTML
141
+ try:
142
+ from playwright.sync_api import sync_playwright
143
+ with sync_playwright() as p:
144
+ browser = p.chromium.launch()
145
+ page = browser.new_page()
146
+ page.goto(url, timeout=30000)
147
+ page.wait_for_load_state("networkidle")
148
+ rendered_html = page.content()
149
+ browser.close()
150
+ soup = BeautifulSoup(rendered_html, 'html.parser')
151
+ except Exception as e:
152
+ print(f"Playwright rendering failed: {e}")
153
+
154
+ # Extract title, meta description, etc.
155
+ title = soup.find('title')
156
+ title_text = title.get_text().strip() if title else "No title found"
157
+ meta_desc = soup.find('meta', attrs={'name': 'description'})
158
+ description = meta_desc.get('content', '') if meta_desc else ""
159
+
160
+ # Fix image URLs
161
+ for img in soup.find_all('img'):
162
+ src = img.get('src', '')
163
+ if src:
164
+ img['src'] = urljoin(url, src)
165
+
166
+ # Fix background images in style attributes
167
+ for element in soup.find_all(attrs={'style': True}):
168
+ style_attr = element.get('style', '')
169
+ bg_pattern = r'background-image:\s*url\(["\']?([^"\']+)["\']?\)'
170
+ matches = re.findall(bg_pattern, style_attr, re.IGNORECASE)
171
+ for match in matches:
172
+ if not match.startswith(('http', '//', 'data:')):
173
+ style_attr = style_attr.replace(match, urljoin(url, match))
174
+ element['style'] = style_attr
175
+
176
+ # Fix background images in <style> tags
177
+ for style in soup.find_all('style'):
178
+ if style.string:
179
+ style_content = style.string
180
+ bg_pattern = r'background-image:\s*url\(["\']?([^"\']+)["\']?\)'
181
+ matches = re.findall(bg_pattern, style_content, re.IGNORECASE)
182
+ for match in matches:
183
+ if not match.startswith(('http', '//', 'data:')):
184
+ style_content = style_content.replace(match, urljoin(url, match))
185
+ style.string = style_content
186
+
187
+ # Test a few image URLs to see if they're accessible
188
+ def test_image_url(img_url):
189
+ try:
190
+ test_response = requests.head(img_url, timeout=5, allow_redirects=True)
191
+ return test_response.status_code == 200
192
+ except:
193
+ return False
194
+
195
+ working_images = []
196
+ for img in soup.find_all('img')[:10]:
197
+ if test_image_url(img['src']):
198
+ working_images.append(img)
199
+
200
+ modified_html = str(soup)
201
+ cleaned_html = re.sub(r'<!--.*?-->', '', modified_html, flags=re.DOTALL)
202
+ cleaned_html = re.sub(r'\s+', ' ', cleaned_html)
203
+ cleaned_html = re.sub(r'>\s+<', '><', cleaned_html)
204
+
205
+ if len(cleaned_html) > 15000:
206
+ cleaned_html = cleaned_html[:15000] + "\n<!-- ... HTML truncated for length ... -->"
207
+
208
+ if len(cleaned_html.strip()) < 100:
209
+ website_content = f"""
210
+ WEBSITE REDESIGN - EXTRACTION FAILED
211
+ ====================================
212
+ URL: {url}
213
+ Title: {title_text}
214
+ ERROR: Could not extract meaningful HTML content from this website. This could be due to:
215
+ 1. The website uses heavy JavaScript to load content dynamically
216
+ 2. The website has anti-bot protection
217
+ 3. The website requires authentication
218
+ FALLBACK APPROACH:
219
+ Please create a modern, responsive website design for a {title_text.lower()} website."""
220
+ return website_content.strip()
221
+
222
+ website_content = f"""
223
+ WEBSITE REDESIGN - ORIGINAL HTML CODE
224
+ =====================================
225
+ URL: {url}
226
+ Title: {title_text}
227
+ Description: {description}
228
+ IMAGES FOUND (use these exact URLs in your redesign):
229
+ {chr(10).join([f"• {img.get('alt', 'Image')} - {img.get('src')}" for img in working_images]) if working_images else "No working images found"}
230
+ ORIGINAL HTML CODE (use this as the base for redesign):
231
+ ```html
232
+ {cleaned_html}
233
+ ```
234
+ REDESIGN INSTRUCTIONS:
235
+ Please redesign this website with a modern, responsive layout while preserving all original content and using the original images."""
236
+
237
+ return website_content.strip()
238
+
239
+ except requests.exceptions.HTTPError as e:
240
+ if e.response.status_code == 403:
241
+ return f"Error: Website blocked access (403 Forbidden). This website may have anti-bot protection. Try a different website or provide a description of what you want to build instead."
242
+ elif e.response.status_code == 404:
243
+ return f"Error: Website not found (404). Please check the URL and try again."
244
+ elif e.response.status_code >= 500:
245
+ return f"Error: Website server error ({e.response.status_code}). Please try again later."
246
+ else:
247
+ return f"Error accessing website: HTTP {e.response.status_code} - {str(e)}"
248
+ except requests.exceptions.Timeout:
249
+ return "Error: Request timed out. The website may be slow or unavailable."
250
+ except requests.exceptions.ConnectionError:
251
+ return "Error: Could not connect to the website. Please check your internet connection and the URL."
252
+ except requests.exceptions.RequestException as e:
253
+ return f"Error accessing website: {str(e)}"
254
+ except Exception as e:
255
+ return f"Error extracting website content: {str(e)}"