File size: 14,856 Bytes
a9fb7e9
cb6eafc
a9fb7e9
 
cb6eafc
a9fb7e9
cb6eafc
 
 
a9fb7e9
cb6eafc
a9fb7e9
cb6eafc
 
a9fb7e9
cb6eafc
 
a9fb7e9
 
cb6eafc
a9fb7e9
 
 
cb6eafc
 
 
a9fb7e9
 
cb6eafc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9fb7e9
cb6eafc
a9fb7e9
 
 
 
 
 
 
cb6eafc
a9fb7e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cb6eafc
a9fb7e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cb6eafc
 
 
a9fb7e9
 
cb6eafc
 
bf97259
cb6eafc
bf97259
cb6eafc
a9fb7e9
 
cb6eafc
a9fb7e9
 
cb6eafc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9fb7e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cb6eafc
 
 
 
 
 
 
 
 
 
 
 
 
a9fb7e9
 
 
 
 
 
 
 
 
 
cb6eafc
 
a9fb7e9
 
cb6eafc
a9fb7e9
cb6eafc
a9fb7e9
cb6eafc
a9fb7e9
 
 
cb6eafc
 
 
 
 
 
 
 
 
 
 
a9fb7e9
cb6eafc
a9fb7e9
 
cb6eafc
3461aec
cb6eafc
 
 
 
3461aec
cb6eafc
 
 
 
3461aec
cb6eafc
 
 
 
 
 
 
 
 
 
 
 
 
a9fb7e9
 
 
cb6eafc
 
a9fb7e9
cb6eafc
a9fb7e9
cb6eafc
3461aec
a9fb7e9
 
 
cb6eafc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9fb7e9
 
cb6eafc
 
a9fb7e9
cb6eafc
 
a9fb7e9
cb6eafc
 
a9fb7e9
 
cb6eafc
 
a9fb7e9
 
cb6eafc
 
a9fb7e9
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
import gradio as gr
import re
import os
import sys
import tempfile
import time
import uuid
import logging
import subprocess
from models import generate_code_for_tab
from utils import find_free_port

# Global variable to track the Gradio subprocess
gradio_process = None

# Configure logging
logger = logging.getLogger(__name__)

def get_gradio_sys_prompt():
    """Get the System Prompt for generating Gradio applications"""
    return """
You are an expert Gradio developer. Create a complete, runnable, single-file Gradio application based on the user's request.
The code must be self-contained in a single Python script.
IMPORTANT: The main Gradio app instance MUST be assigned to a variable named `demo`.
The script must end with the app launch command: `demo.launch()`.
Do not include any explanations, just the raw Python code inside a ```python block.
"""

def run_gradio_app(code, port):
    """
    Write the Gradio code to a temporary file and run it in a subprocess on the specified port.
    """
    temp_dir = tempfile.mkdtemp()
    file_path = os.path.join(temp_dir, "app_to_run.py")
    with open(file_path, "w") as f:
        f.write(code)

    env = os.environ.copy()
    env["GRADIO_SERVER_PORT"] = str(port)
    
    # Use subprocess.Popen to run the script in a completely isolated process
    process = subprocess.Popen([sys.executable, file_path], env=env)
    
    # Return the process object for management
    return process, temp_dir, file_path


def get_html_sys_prompt():
    """Get the System Prompt for generating static pages"""
    return """
You are an expert front-end developer. Create a complete, modern, and responsive single HTML file based on the user's request.
The file must be self-contained, including all necessary HTML, CSS, and JavaScript.
Do not include any explanations, just the raw HTML code.
"""

def run_gradio_in_thread(code, url_queue, session_id):
    """Run the Gradio application in a separate thread to avoid blocking the main application"""
    temp_dir = tempfile.mkdtemp()
    file_path = os.path.join(temp_dir, "app.py")
    with open(file_path, "w") as f:
        f.write(code)

    python_executable = sys.executable
    process = subprocess.Popen(
        [python_executable, file_path],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        bufsize=1,
        universal_newlines=True
    )
    running_processes[session_id] = process

    for line in process.stdout:
        print(f"Gradio App stdout: {line.strip()}")
        if "Running on local URL:" in line:
            url = line.split("Running on local URL:")[1].strip()
            url_queue.put(url)
            break
    
    process.wait()
    try:
        os.remove(file_path)
        os.rmdir(temp_dir)
    except OSError as e:
        print(f"Error cleaning up temp files: {e}")

def get_spinner_html():
    """Return HTML with a CSS spinner animation"""
    return """
    <div style="width: 100%; height: 600px; display: flex; justify-content: center; align-items: center; border: 1px solid #ddd; background-color: #f9f9f9;">
        <div class="spinner"></div>
    </div>
    <style>
        .spinner {
            border: 4px solid rgba(0, 0, 0, 0.1);
            width: 36px;
            height: 36px;
            border-radius: 50%;
            border-left-color: #09f;
            animation: spin 1s ease infinite;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
    """

def generate_code(code_type, model_choice, user_prompt):
    """Generate code and provide a preview based on the user's selected type (static page or Gradio application)."""
    global gradio_process
    logger.info(f"--- [Code Generation] Start ---")
    logger.info(f"Code Type: {code_type}, Model: {model_choice}, Prompt: '{user_prompt}'")

    # If a Gradio subprocess is running, terminate it first
    if gradio_process and gradio_process.poll() is None:
        gradio_process.terminate()
        gradio_process.wait(timeout=5)
        logger.info("Terminated existing Gradio process.")

    if not user_prompt:
        yield "Please enter a prompt.", gr.HTML("Preview will appear here."), "Status: Error", gr.update()
        return

    yield "", gr.HTML(get_spinner_html()), "Status: Initializing...", gr.update()

    start_time = time.time()

    if code_type == "Gradio App":
        port = find_free_port()
        system_prompt = get_gradio_sys_prompt()
        
        full_code = ""
        for chunk in generate_code_for_tab(system_prompt, user_prompt, code_type, model_choice):
            full_code += chunk
            elapsed_time = time.time() - start_time
            generated_length = len(full_code)
            speed = generated_length / elapsed_time if elapsed_time > 0 else 0
            status = f"""
            **Status:** Generating...
            - **Generation Time:** {elapsed_time:.2f}s
            - **Generated Length:** {generated_length} chars
            - **Average Speed:** {speed:.2f} char/s
            """
            # Display the generated raw code in real-time
            yield full_code, gr.update(), status, gr.update()

        # Extract the Python code block from the model output
        python_code_match = re.search(r"```python\n(.*?)```", full_code, re.DOTALL)
        if python_code_match:
            python_code = python_code_match.group(1).strip()
        else:
            # If no code block is found, assume the entire output is code
            logger.warning("Could not find Python code block, assuming the entire output is code.")
            python_code = full_code.strip()

        try:
            # Verify that the code can at least be compiled
            compile(python_code, '<string>', 'exec')
            
            # Use subprocess to launch the Gradio application
            process, temp_dir, temp_file_path = run_gradio_app(python_code, port)
            gradio_process = process
            
            # Give the application some time to start
            time.sleep(10) 
            
            preview_html = f'<iframe src="http://127.0.0.1:{port}" width="100%" height="600px" frameborder="0"></iframe>'
            status = "**Status:** Generation Complete"
            yield python_code, gr.HTML(preview_html), status, gr.Tabs(selected=0)
            logger.info(f"Gradio app started on port {port}.")
        except SyntaxError as e:
            error_message = f"Generated code is not valid Python: {e}"
            logger.error(f"Raw output from the model:\n{full_code}")
            status = f"**Status:** Error - {error_message}"
            yield full_code, gr.HTML(f"<p style='color: red;'>{error_message}</p>"), status, gr.update()
            logger.error(error_message)

    elif code_type == "Static Page":
        system_prompt = get_html_sys_prompt()
        full_code_with_think = ""
        full_code_for_preview = ""
        buffer = ""
        is_thinking = False
        
        # The model's raw output is streamed here
        for code_chunk in generate_code_for_tab(system_prompt, user_prompt, code_type, model_choice):
            full_code_with_think += code_chunk
            buffer += code_chunk
            
            # Process the buffer to filter out think tags for the preview
            while True:
                if is_thinking:
                    end_index = buffer.find("</think>")
                    if end_index != -1:
                        is_thinking = False
                        buffer = buffer[end_index + len("</think>"):]
                    else:
                        break 
                else:
                    start_index = buffer.find("<think>")
                    if start_index != -1:
                        part_to_add = buffer[:start_index]
                        full_code_for_preview += part_to_add
                        is_thinking = True
                        buffer = buffer[start_index:]
                    else:
                        full_code_for_preview += buffer
                        buffer = ""
                        break
            
            elapsed_time = time.time() - start_time
            generated_length = len(full_code_with_think)
            speed = generated_length / elapsed_time if elapsed_time > 0 else 0
            status = f"""
            **Status:** Generating...
            - **Generation Time:** {elapsed_time:.2f}s
            - **Generated Length:** {generated_length} chars
            - **Average Speed:** {speed:.2f} char/s
            """

            # In the generation process, we only update the code and status, not the preview.
            # The preview will be updated once at the very end.
            yield full_code_with_think, gr.update(), status, gr.update()
        
        # Final update for the preview without the spinner
        escaped_code = full_code_for_preview.replace("'", "&apos;").replace('"', '&quot;')
        final_preview_html = f"""
        <div style="width: 100%; height: 600px; border: 1px solid #ddd; overflow: hidden; position: relative; background-color: #f9f9f9;">
            <iframe srcdoc='{escaped_code}'
                    style="position: absolute; top: 0; left: 0; width: 200%; height: 200%; transform: scale(0.5); transform-origin: 0 0; border: none;">
            </iframe>
        </div>
        """
        status = "**Status:** Generation Complete"
        yield full_code_with_think, gr.HTML(final_preview_html), status, gr.Tabs(selected=0)
        logger.info("Static page streaming finished.")


def toggle_fullscreen(is_fullscreen):
    """Toggle fullscreen mode visibility"""
    is_fullscreen = not is_fullscreen
    new_button_text = "Exit Fullscreen" if is_fullscreen else "Fullscreen Preview"
    panel_visibility = not is_fullscreen
    return is_fullscreen, gr.update(value=new_button_text), gr.update(visible=panel_visibility)

def cleanup_process():
    """Terminate the subprocess when the application exits"""
    global gradio_process
    if gradio_process and gradio_process.poll() is None:
        gradio_process.terminate()
        gradio_process.wait()
        logger.info("Cleaned up Gradio process on exit.")

import atexit
atexit.register(cleanup_process)

def create_code_tab():
    """Create the UI Tab for the code generation feature"""
    fullscreen_state = gr.State(False)

    html_examples = [
        "Write a hello world alert",
        "Create a Canvas animation of continuous colorful fireworks blooming on a black background.",
        "Generate a Canvas special effect with iridescent light streams.",
        "Design a particle system Canvas animation that interacts with the mouse.",
        "Implement a classic snake game using HTML Canvas.",
        "Create a Canvas animation simulating the 'Elephant\\\'s Toothpaste' chemical experiment: in a container, colored foam continuously and rapidly gushes out, expands, and overflows, filling the entire screen. Use a suitable drawing js library, such as three.js.",
        "Create a dreamy low-poly floating island scene with dynamic lighting and soft animations in a single HTML file. Use a suitable drawing js library, such as three.js.",
    ]

    gradio_examples = [
        "Write a hello world alert",
        "Create a simple calculator that supports addition, subtraction, multiplication, and division",
        "Create a simple Gradio application that displays 'Hello, Gradio!'",
        "Create an image classification application that can classify uploaded images",
        "Create a text summarization application that can summarize long input texts",
        "Create a simple chatbot",
    ]

    def update_examples(code_type):
        if code_type == "Static Page":
            return gr.update(visible=True), gr.update(visible=False)
        else:
            return gr.update(visible=False), gr.update(visible=True)

    with gr.Blocks() as demo:
        with gr.Row():
            with gr.Column(scale=1) as left_panel:
                gr.Markdown("### 1. Select Code Type")
                code_type_radio = gr.Radio(["Static Page", "Gradio App"], value="Static Page", label="Code Type")
                
                gr.Markdown("### 2. Select Model")
                model_choice_radio = gr.Radio(
                    ["Better (using Ling-1T)", "Faster (using Ring-flash-2.0)"],
                    value="Faster (using Ring-flash-2.0)",
                    label="Model Selection"
                )

                gr.Markdown("### 3. Enter Your Requirements")
                prompt_input = gr.Textbox(lines=5, placeholder="e.g., Create a simple page with a title and a button", label="Prompt")
                with gr.Column() as html_examples_column:
                    html_examples_component = gr.Examples(
                        examples=html_examples,
                        inputs=prompt_input,
                        label="✨ Why not try these cool examples",
                        examples_per_page=12,
                    )
                with gr.Column(visible=False) as gradio_examples_column:
                    gradio_examples_component = gr.Examples(
                        examples=gradio_examples,
                        inputs=prompt_input,
                        label="✨ Why not try these cool examples",
                        examples_per_page=12,
                    )
                generate_button = gr.Button("Generate Code", variant="primary")
                status_output = gr.Markdown(value="Status: Waiting", visible=True)
            
            with gr.Column(scale=2):
                with gr.Tabs(elem_id="result_tabs") as result_tabs:
                    with gr.TabItem("Live Preview", id=0):
                        with gr.Row():
                            gr.Markdown("### 3. Live Preview")
                            fullscreen_button = gr.Button("Fullscreen Preview", scale=0)
                        preview_output = gr.HTML(value="<p>Preview will appear here.</p>")
                    with gr.TabItem("Generated Source Code", id=1):
                        gr.Markdown("### 4. Generated Source Code")
                        code_output = gr.Code(language="html", label="Generated Code")

        code_type_radio.change(fn=update_examples, inputs=code_type_radio, outputs=[html_examples_column, gradio_examples_column])

        generate_button.click(
            fn=generate_code,
            inputs=[code_type_radio, model_choice_radio, prompt_input],
            outputs=[code_output, preview_output, status_output, result_tabs]
        )
        
        fullscreen_button.click(
            fn=toggle_fullscreen,
            inputs=[fullscreen_state],
            outputs=[fullscreen_state, fullscreen_button, left_panel]
        )

    return demo