import gradio as gr
import logging
from config import CHAT_MODEL_SPECS, LING_1T, CODE_FRAMEWORK_SPECS, REACT_TAILWIND
from ui_components.model_selector import create_model_selector
from ui_components.code_framework_selector import create_code_framework_selector
from code_kit.agent_code_generator import code_generation_agent
from code_kit.agent_style_generator import generate_random_style
from code_kit.code_examples import CODE_EXAMPLES
from i18n import get_text
# Configure logging
logger = logging.getLogger(__name__)
def refresh_preview(code_type_display_name, current_code, chatbot_history, lang):
"""
Refresh the preview and add a log entry.
Refactored to rely solely on the HTML content for preview, assuming all frameworks
produce a single-file HTML output that works in an iframe.
"""
logger.info(f"--- [Manual Refresh] Start ---")
logger.info(f"Code Type: {code_type_display_name}")
# Simple validation: Check if code seems to be HTML
if not current_code or not isinstance(current_code, str):
chatbot_history.append({"role": "assistant", "content": get_text('code_log_no_code_warning', lang)})
return gr.update(), chatbot_history
# For all currently supported frameworks (Static, React+Tailwind, R3F, Old School),
# the output is a self-contained HTML string.
# So we can process them uniformly.
escaped_code = current_code.replace("'", "'").replace('"', '"')
final_preview_html = f"""
"""
chatbot_history.append({"role": "assistant", "content": get_text('code_log_refreshed_state', lang)})
logger.info("Refreshed preview.")
return gr.HTML(final_preview_html), chatbot_history
def toggle_fullscreen(is_fullscreen, lang):
logger.info(f"--- [Toggle Fullscreen] Called ---")
logger.info(f"Current state before toggle: {is_fullscreen}")
is_fullscreen = not is_fullscreen
new_button_text = get_text('code_exit_fullscreen_button', lang) if is_fullscreen else get_text('code_fullscreen_button', lang)
logger.info(f"New state: {is_fullscreen}")
return (
is_fullscreen,
gr.update(value=new_button_text)
)
def log_js_error(error_text, chatbot_history, lang):
"""Appends a JavaScript error received from the frontend to the log chatbot."""
if not error_text:
return chatbot_history
formatted_error = get_text('code_log_runtime_error', lang).format(error_text=error_text)
# Check if the last message is the same error to prevent flooding
if chatbot_history and chatbot_history[-1]["content"] == formatted_error:
return chatbot_history
chatbot_history.append({"role": "assistant", "content": formatted_error})
return chatbot_history
def create_code_tab(initial_lang: str, current_lang_state: gr.State):
gr.HTML("""""")
fullscreen_state = gr.State(False)
# JavaScript to dispatch a custom event for fullscreen toggle
# Instead of relying on the potentially stale 'is_fullscreen' state passed from Python,
# we check the actual DOM state of the panel to decide what to do.
dispatch_fullscreen_event_js = """
() => {
const left = document.getElementById('left-panel');
let isCurrentlyVisible = true;
if (left) {
// Check if hidden class is present or display is none
// Note: We verify both class and inline style to be safe
const isHidden = left.classList.contains('hidden-panel') || window.getComputedStyle(left).display === 'none';
isCurrentlyVisible = !isHidden;
}
// Logic:
// If panels are currently VISIBLE -> We want to enter Fullscreen (True)
// If panels are currently HIDDEN -> We want to exit Fullscreen (False)
const targetState = isCurrentlyVisible;
console.log("DOM Check - Panel Visible:", isCurrentlyVisible, "-> Target Fullscreen:", targetState);
window.dispatchEvent(new CustomEvent('tab.code.toggleFullscreen', { detail: { isFullscreen: targetState } }));
return targetState;
}
"""
with gr.Row(elem_id="indicator-code-tab"):
with gr.Column(scale=1, visible=True, elem_id="left-panel") as left_panel:
with gr.Column(scale=1): # Settings Panel
code_framework_dropdown = create_code_framework_selector(
framework_specs=CODE_FRAMEWORK_SPECS,
default_framework_constant=REACT_TAILWIND
)
model_choice_dropdown, model_description_markdown = create_model_selector(
model_specs=CHAT_MODEL_SPECS,
default_model_constant=LING_1T,
lang_state=current_lang_state,
initial_lang=initial_lang
)
prompt_input = gr.Textbox(lines=5, placeholder=get_text('code_prompt_placeholder', initial_lang), label=get_text('code_prompt_label', initial_lang))
overall_style_input = gr.Textbox(label=get_text('code_overall_style_label', initial_lang), placeholder=get_text('code_overall_style_placeholder', initial_lang), lines=2)
decoration_input = gr.Textbox(label=get_text('code_decoration_style_label', initial_lang), placeholder=get_text('code_decoration_style_placeholder', initial_lang), lines=2)
palette_input = gr.Textbox(label="Palette (Raw)", elem_classes="hidden-component")
palette_display = gr.HTML(value=f"{get_text('code_palette_placeholder', initial_lang)}
", container=True, label=get_text('code_palette_label', initial_lang), show_label=True)
generate_style_btn = gr.Button(get_text('code_generate_style_button', initial_lang), size="sm")
with gr.Column():
examples_title = gr.Markdown(get_text('code_examples_title', initial_lang))
examples_dataset = gr.Dataset(
components=[gr.Textbox(visible=False)],
samples=[[item["task"]] for item in CODE_EXAMPLES],
label=get_text('code_examples_dataset_label', initial_lang),
headers=[get_text('code_examples_dataset_header', initial_lang)],
)
generate_button = gr.Button(get_text('code_generate_code_button', initial_lang), variant="primary")
with gr.Column(scale=4):
with gr.Tabs(elem_id="result_tabs") as result_tabs:
with gr.TabItem(get_text('code_preview_tab_title', initial_lang), id=0) as preview_tab:
with gr.Row():
preview_header = gr.Markdown(get_text('code_preview_header', initial_lang))
fullscreen_button = gr.Button(get_text('code_fullscreen_button', initial_lang), scale=0)
preview_output = gr.HTML(value=get_text('code_preview_placeholder', initial_lang))
with gr.TabItem(get_text('code_source_code_tab_title', initial_lang), id=1) as source_code_tab:
source_code_header = gr.Markdown(get_text('code_source_code_header', initial_lang))
code_output = gr.Code(language="html", label=get_text('code_source_code_label', initial_lang), interactive=True)
refresh_button = gr.Button(get_text('code_refresh_preview_button', initial_lang))
with gr.Column(scale=1, elem_id="right-panel") as right_panel:
log_chatbot = gr.Chatbot(label=get_text('code_log_chatbot_label', initial_lang), height=300)
js_error_channel = gr.Textbox(visible=True, elem_classes=["js_error_channel"], label=get_text('code_debug_channel_label', initial_lang), interactive=False)
# Event Handler for Example Selection
def on_select_example(evt: gr.SelectData):
selected_task = evt.value[0]
item = next((i for i in CODE_EXAMPLES if i["task"] == selected_task), None)
if not item:
return gr.update(), gr.update()
return gr.update(value=item["user_prompt"]), gr.update(value=item["model"])
examples_dataset.select(
fn=on_select_example,
inputs=None,
outputs=[prompt_input, model_choice_dropdown],
show_progress="hidden"
)
# Event Handler for Style Generation
generate_style_btn.click(
fn=generate_random_style,
inputs=[model_choice_dropdown],
outputs=[palette_display, palette_input, decoration_input, overall_style_input]
)
refresh_button.click(
fn=refresh_preview,
inputs=[code_framework_dropdown, code_output, log_chatbot, current_lang_state],
outputs=[preview_output, log_chatbot]
)
generate_button.click(
fn=code_generation_agent,
inputs=[
code_framework_dropdown,
model_choice_dropdown,
prompt_input,
palette_input, # Pass the raw palette string
decoration_input,
overall_style_input,
log_chatbot
],
outputs=[code_output, preview_output, log_chatbot, result_tabs]
)
fullscreen_button.click(
fn=toggle_fullscreen,
inputs=[fullscreen_state, current_lang_state],
outputs=[fullscreen_state, fullscreen_button],
js=dispatch_fullscreen_event_js
)
js_error_channel.change(
fn=log_js_error,
inputs=[js_error_channel, log_chatbot, current_lang_state],
outputs=log_chatbot
)
return {
"prompt_input": prompt_input, "overall_style_input": overall_style_input,
"decoration_input": decoration_input, "palette_display": palette_display,
"generate_style_btn": generate_style_btn, "examples_title": examples_title,
"examples_dataset": examples_dataset, "generate_button": generate_button,
"preview_tab": preview_tab, "preview_header": preview_header,
"fullscreen_button": fullscreen_button, "preview_output": preview_output,
"source_code_tab": source_code_tab, "source_code_header": source_code_header,
"code_output": code_output, "refresh_button": refresh_button,
"log_chatbot": log_chatbot, "js_error_channel": js_error_channel,
"model_choice_dropdown": model_choice_dropdown,
"model_description_markdown": model_description_markdown
}
def update_language(lang: str, components: dict):
return {
components["prompt_input"]: gr.update(label=get_text('code_prompt_label', lang), placeholder=get_text('code_prompt_placeholder', lang)),
components["overall_style_input"]: gr.update(label=get_text('code_overall_style_label', lang), placeholder=get_text('code_overall_style_placeholder', lang)),
components["decoration_input"]: gr.update(label=get_text('code_decoration_style_label', lang), placeholder=get_text('code_decoration_style_placeholder', lang)),
components["palette_display"]: gr.update(label=get_text('code_palette_label', lang)),
components["generate_style_btn"]: gr.update(value=get_text('code_generate_style_button', lang)),
components["examples_title"]: gr.update(value=get_text('code_examples_title', lang)),
components["examples_dataset"]: gr.update(label=get_text('code_examples_dataset_label', lang), headers=[get_text('code_examples_dataset_header', lang)]),
components["generate_button"]: gr.update(value=get_text('code_generate_code_button', lang)),
components["preview_tab"]: gr.update(label=get_text('code_preview_tab_title', lang)),
components["preview_header"]: gr.update(value=get_text('code_preview_header', lang)),
# Fullscreen button is updated in its own event handler
components["source_code_tab"]: gr.update(label=get_text('code_source_code_tab_title', lang)),
components["source_code_header"]: gr.update(value=get_text('code_source_code_header', lang)),
components["code_output"]: gr.update(label=get_text('code_source_code_label', lang)),
components["refresh_button"]: gr.update(value=get_text('code_refresh_preview_button', lang)),
components["log_chatbot"]: gr.update(label=get_text('code_log_chatbot_label', lang)),
components["js_error_channel"]: gr.update(label=get_text('code_debug_channel_label', lang)),
}