|
import re
|
|
from typing import Dict, List, Optional, Tuple
|
|
import base64
|
|
import numpy as np
|
|
from PIL import Image
|
|
import gradio as gr
|
|
|
|
from config import GRADIO_SUPPORTED_LANGUAGES, SEARCH_START, DIVIDER, REPLACE_END
|
|
|
|
History = List[Tuple[str, str]]
|
|
Messages = List[Dict[str, str]]
|
|
|
|
def get_gradio_language(language):
|
|
return language if language in GRADIO_SUPPORTED_LANGUAGES else None
|
|
|
|
def history_to_messages(history: History, system: str) -> Messages:
|
|
messages = [{'role': 'system', 'content': system}]
|
|
for h in history:
|
|
|
|
user_content = h[0]
|
|
if isinstance(user_content, list):
|
|
|
|
text_content = ""
|
|
for item in user_content:
|
|
if isinstance(item, dict) and item.get("type") == "text":
|
|
text_content += item.get("text", "")
|
|
user_content = text_content if text_content else str(user_content)
|
|
|
|
messages.append({'role': 'user', 'content': user_content})
|
|
messages.append({'role': 'assistant', 'content': h[1]})
|
|
return messages
|
|
|
|
def messages_to_history(messages: Messages) -> History:
|
|
assert messages[0]['role'] == 'system'
|
|
history = []
|
|
for q, r in zip(messages[1::2], messages[2::2]):
|
|
|
|
user_content = q['content']
|
|
if isinstance(user_content, list):
|
|
text_content = ""
|
|
for item in user_content:
|
|
if isinstance(item, dict) and item.get("type") == "text":
|
|
text_content += item.get("text", "")
|
|
user_content = text_content if text_content else str(user_content)
|
|
|
|
history.append((user_content, r['content']))
|
|
return history
|
|
|
|
def history_to_chatbot_messages(history: History) -> List[Dict[str, str]]:
|
|
"""Convert history tuples to chatbot message format"""
|
|
messages = []
|
|
for user_msg, assistant_msg in history:
|
|
|
|
if isinstance(user_msg, list):
|
|
text_content = ""
|
|
for item in user_msg:
|
|
if isinstance(item, dict) and item.get("type") == "text":
|
|
text_content += item.get("text", "")
|
|
user_msg = text_content if text_content else str(user_msg)
|
|
|
|
messages.append({"role": "user", "content": user_msg})
|
|
messages.append({"role": "assistant", "content": assistant_msg})
|
|
return messages
|
|
|
|
def remove_code_block(text):
|
|
|
|
patterns = [
|
|
r'```(?:html|HTML)\n([\s\S]+?)\n```',
|
|
r'```\n([\s\S]+?)\n```',
|
|
r'```([\s\S]+?)```'
|
|
]
|
|
for pattern in patterns:
|
|
match = re.search(pattern, text, re.DOTALL)
|
|
if match:
|
|
extracted = match.group(1).strip()
|
|
return extracted
|
|
|
|
if text.strip().startswith('<!DOCTYPE html>') or text.strip().startswith('<html') or text.strip().startswith('<'):
|
|
return text.strip()
|
|
return text.strip()
|
|
|
|
def clear_history():
|
|
return [], [], None, ""
|
|
|
|
def update_image_input_visibility(model):
|
|
"""Update image input visibility based on selected model"""
|
|
is_ernie_vl = model.get("id") == "baidu/ERNIE-4.5-VL-424B-A47B-Base-PT"
|
|
is_glm_vl = model.get("id") == "THUDM/GLM-4.1V-9B-Thinking"
|
|
return gr.update(visible=is_ernie_vl or is_glm_vl)
|
|
|
|
def update_submit_button(query):
|
|
"""Enable submit button if query is not empty"""
|
|
return gr.update(interactive=bool(query))
|
|
|
|
def create_multimodal_message(text, image=None):
|
|
"""Create a multimodal message with text and optional image"""
|
|
if image is None:
|
|
return {"role": "user", "content": text}
|
|
|
|
from file_processing import process_image_for_model
|
|
content = [
|
|
{
|
|
"type": "text",
|
|
"text": text
|
|
},
|
|
{
|
|
"type": "image_url",
|
|
"image_url": {
|
|
"url": process_image_for_model(image)
|
|
}
|
|
}
|
|
]
|
|
|
|
return {"role": "user", "content": content}
|
|
def apply_search_replace_changes(original_html: str, changes_text: str) -> str:
|
|
"""Apply search/replace changes to HTML content"""
|
|
if not changes_text.strip():
|
|
return original_html
|
|
|
|
|
|
blocks = []
|
|
current_block = ""
|
|
lines = changes_text.split('\n')
|
|
|
|
for line in lines:
|
|
if line.strip() == SEARCH_START:
|
|
if current_block.strip():
|
|
blocks.append(current_block.strip())
|
|
current_block = line + '\n'
|
|
elif line.strip() == REPLACE_END:
|
|
current_block += line + '\n'
|
|
blocks.append(current_block.strip())
|
|
current_block = ""
|
|
else:
|
|
current_block += line + '\n'
|
|
|
|
if current_block.strip():
|
|
blocks.append(current_block.strip())
|
|
|
|
modified_html = original_html
|
|
|
|
for block in blocks:
|
|
if not block.strip():
|
|
continue
|
|
|
|
|
|
lines = block.split('\n')
|
|
search_lines = []
|
|
replace_lines = []
|
|
in_search = False
|
|
in_replace = False
|
|
|
|
for line in lines:
|
|
if line.strip() == SEARCH_START:
|
|
in_search = True
|
|
in_replace = False
|
|
elif line.strip() == DIVIDER:
|
|
in_search = False
|
|
in_replace = True
|
|
elif line.strip() == REPLACE_END:
|
|
in_replace = False
|
|
elif in_search:
|
|
search_lines.append(line)
|
|
elif in_replace:
|
|
replace_lines.append(line)
|
|
|
|
|
|
if search_lines:
|
|
search_text = '\n'.join(search_lines).strip()
|
|
replace_text = '\n'.join(replace_lines).strip()
|
|
|
|
if search_text in modified_html:
|
|
modified_html = modified_html.replace(search_text, replace_text)
|
|
else:
|
|
print(f"Warning: Search text not found in HTML: {search_text[:100]}...")
|
|
|
|
return modified_html
|
|
|
|
def send_to_sandbox(code):
|
|
|
|
wrapped_code = f"""
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset=\"UTF-8\">
|
|
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
|
|
<script>
|
|
// Safe localStorage polyfill
|
|
const safeStorage = {{
|
|
_data: {{}},
|
|
getItem: function(key) {{ return this._data[key] || null; }},
|
|
setItem: function(key, value) {{ this._data[key] = value; }},
|
|
removeItem: function(key) {{ delete this._data[key]; }},
|
|
clear: function() {{ this._data = {{}}; }}
|
|
}};
|
|
Object.defineProperty(window, 'localStorage', {{
|
|
value: safeStorage,
|
|
writable: false
|
|
}});
|
|
window.onerror = function(message, source, lineno, colno, error) {{
|
|
console.error('Error:', message);
|
|
}};
|
|
</script>
|
|
</head>
|
|
<body>
|
|
{code}
|
|
</body>
|
|
</html>
|
|
"""
|
|
encoded_html = base64.b64encode(wrapped_code.encode('utf-8')).decode('utf-8')
|
|
data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
|
|
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>'
|
|
return iframe |