Spaces:
Paused
Paused
| import re | |
| import gradio as gr | |
| from openai import OpenAI | |
| from config import API_KEY, MODEL, SYSTEM_PROMPT, ENDPOINT, EXAMPLES, DEFAULT_LOCALE, DEFAULT_THEME | |
| client = OpenAI(api_key=API_KEY, base_url=ENDPOINT) | |
| react_imports = { | |
| "lucide-react": "https://esm.sh/lucide-react@0.525.0", | |
| "recharts": "https://esm.sh/recharts@3.1.0", | |
| "framer-motion": "https://esm.sh/framer-motion@12.23.6", | |
| "matter-js": "https://esm.sh/matter-js@0.20.0", | |
| "p5": "https://esm.sh/p5@2.0.3", | |
| "konva": "https://esm.sh/konva@9.3.22", | |
| "react-konva": "https://esm.sh/react-konva@19.0.7", | |
| "three": "https://esm.sh/three@0.178.0", | |
| "@react-three/fiber": "https://esm.sh/@react-three/fiber@9.2.0", | |
| "@react-three/drei": "https://esm.sh/@react-three/drei@10.5.2", | |
| "@tailwindcss/browser": "https://esm.sh/@tailwindcss/browser@4.1.11", | |
| "react": "https://esm.sh/react@^19.0.0", | |
| "react/": "https://esm.sh/react@^19.0.0/", | |
| "react-dom": "https://esm.sh/react-dom@^19.0.0", | |
| "react-dom/": "https://esm.sh/react-dom@^19.0.0/", | |
| "react-dom/client": "https://esm.sh/react-dom@^19.0.0/client" | |
| } | |
| class GradioEvents: | |
| def generate_code(input_value, system_prompt_input_value, state_value): | |
| def get_generated_files(text): | |
| patterns = { | |
| 'html': r'```html\n(.+?)\n```', | |
| 'jsx': r'```jsx\n(.+?)\n```', | |
| 'tsx': r'```tsx\n(.+?)\n```', | |
| } | |
| result = {} | |
| for ext, pattern in patterns.items(): | |
| matches = re.findall(pattern, text, re.DOTALL) | |
| if matches: | |
| content = '\n'.join(matches).strip() | |
| result[f'index.{ext}'] = content | |
| if len(result) == 0: | |
| result["index.html"] = text.strip() | |
| return result | |
| def process_react_imports(code): | |
| """Process React code to work with ES modules and import map""" | |
| # Remove export default and replace with const App | |
| processed_code = code.replace("export default", "const App =") | |
| # Process imports to use the import map | |
| import_lines = [] | |
| code_lines = [] | |
| for line in processed_code.split('\n'): | |
| line = line.strip() | |
| if line.startswith('import '): | |
| # Extract import statement | |
| import_lines.append(line) | |
| else: | |
| code_lines.append(line) | |
| # Combine processed imports and code | |
| if import_lines: | |
| processed_code = '\n'.join(import_lines) + '\n\n' + '\n'.join(code_lines) | |
| else: | |
| processed_code = '\n'.join(code_lines) | |
| return processed_code | |
| yield { | |
| empty_output: gr.update(visible=False), | |
| loading_output: gr.update(visible=True), | |
| sandbox: gr.update(visible=False), | |
| output: gr.update(value=None) | |
| } | |
| if input_value is None: | |
| input_value = '' | |
| messages = [{ | |
| 'role': "system", | |
| "content": SYSTEM_PROMPT | |
| # 'content': system_prompt_input_value | |
| }] + state_value["history"] | |
| messages.append({'role': "user", 'content': input_value}) | |
| generator = client.chat.completions.create(model=MODEL, | |
| messages=messages, | |
| stream=True) | |
| response = "" | |
| for chunk in generator: | |
| content = chunk.choices[0].delta.content | |
| if content is not None: | |
| response += content | |
| if chunk.choices[0].finish_reason == 'stop': | |
| state_value["history"] = messages + [{ | |
| 'role': "assistant", | |
| 'content': response | |
| }] | |
| generated_files = get_generated_files(response) | |
| react_code = generated_files.get( | |
| "index.tsx") or generated_files.get("index.jsx") | |
| html_code = generated_files.get("index.html") | |
| # Completed | |
| # Create HTML content for sandbox | |
| if react_code: | |
| # For React code, create a complete HTML page that renders React with ES modules | |
| processed_react_code = process_react_imports(react_code) | |
| escaped_react_code = processed_react_code.replace("`", "`").replace("'", "'") | |
| # Create import map from react_imports | |
| import_map = '{\n "imports": {\n' | |
| for package, url in react_imports.items(): | |
| import_map += f' "{package}": "{url}",\n' | |
| import_map = import_map.rstrip(',\n') + '\n }\n }' | |
| sandbox_content = f""" | |
| <iframe style="width: 100%; height: 600px; border: 1px solid #ddd; border-radius: 8px;" | |
| srcdoc='<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <script type="importmap"> | |
| {import_map} | |
| </script> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| </head> | |
| <body> | |
| <div id="root"></div> | |
| <script type="module"> | |
| import React from "react"; | |
| import { createRoot } from "react-dom/client"; | |
| {escaped_react_code} | |
| const root = createRoot(document.getElementById("root")); | |
| root.render(React.createElement(App)); | |
| </script> | |
| </body> | |
| </html>'> | |
| </iframe> | |
| """ | |
| else: | |
| # For HTML code, just display it in iframe | |
| escaped_html_code = html_code.replace("'", "'") | |
| sandbox_content = f""" | |
| <iframe style="width: 100%; height: 600px; border: 1px solid #ddd; border-radius: 8px;" | |
| srcdoc='{escaped_html_code}'> | |
| </iframe> | |
| """ | |
| yield { | |
| output: gr.update(value=response), | |
| download_content: gr.update(value=react_code or html_code), | |
| empty_output: gr.update(visible=False), | |
| loading_output: gr.update(visible=False), | |
| sandbox: gr.update(value=sandbox_content, visible=True), | |
| download_btn: gr.update(interactive=True), | |
| state: gr.update(value=state_value) | |
| } | |
| else: | |
| # Generating | |
| yield { | |
| output: gr.update(value=response), | |
| } | |
| def select_example(example: dict): | |
| return lambda: gr.update(value=example["description"]) | |
| def toggle_visibility(visible_state): | |
| return gr.update(visible=not visible_state) | |
| def disable_btns(btns: list): | |
| return lambda: [gr.update(disabled=True) for _ in btns] | |
| def enable_btns(btns: list): | |
| return lambda: [gr.update(disabled=False) for _ in btns] | |
| def update_system_prompt(system_prompt_input_value, state_value): | |
| state_value["system_prompt"] = system_prompt_input_value | |
| return gr.update(value=state_value) | |
| def reset_system_prompt(state_value): | |
| return gr.update(value=state_value["system_prompt"]) | |
| def render_history(statue_value): | |
| return gr.update(value=statue_value["history"]) | |
| def clear_history(state_value): | |
| gr.Success("History Cleared.") | |
| state_value["history"] = [] | |
| return gr.update(value=state_value) | |
| css = """ | |
| .output-empty, .output-loading { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| width: 100%; | |
| min-height: 680px; | |
| } | |
| .output-html { | |
| display: flex; | |
| flex-direction: column; | |
| width: 100%; | |
| min-height: 680px; | |
| max-height: 1200px; | |
| } | |
| .output-html > iframe { | |
| flex: 1; | |
| } | |
| .output-code { | |
| flex: 1; | |
| min-height: 100%; | |
| } | |
| .gradio-container { | |
| max-width: 1200px !important; | |
| } | |
| """ | |
| with gr.Blocks(css=css, title="GPT5 Vibe Tester") as demo: | |
| # Global State | |
| state = gr.State({"system_prompt": "", "history": []}) | |
| # Header | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.HTML(""" | |
| <div style="text-align: center; margin: 20px 0;"> | |
| <div style="font-size: 64px; margin-bottom: 10px;">π―</div> | |
| <h1 style="color: #6A57FF; margin: 10px 0;">GPT5 Vibe Tester</h1> | |
| <p style="color: #666; margin: 5px 0;">Test your creative vibes with GPT5</p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| # Left Column - Input and Controls | |
| with gr.Column(scale=1): | |
| # Input | |
| input = gr.Textbox( | |
| lines=4, | |
| placeholder="Describe the idea you want to test", | |
| label="Your Idea", | |
| elem_id="input-container" | |
| ) | |
| # Note | |
| gr.HTML(""" | |
| <p style="color: #ff7f00; font-weight: bold; margin: 10px 0;"> | |
| <strong>Note:</strong> Test your vibes through interactive prototypes. The app supports multi-round dialogue to refine your ideas. | |
| </p> | |
| """) | |
| # Submit Button | |
| submit_btn = gr.Button( | |
| "Submit", | |
| variant="primary", | |
| size="lg", | |
| elem_id="submit-btn" | |
| ) | |
| # Settings | |
| gr.HTML("<hr style='margin: 20px 0;'><h3>Settings</h3>") | |
| with gr.Row(): | |
| history_btn = gr.Button("π View History", elem_id="history-btn") | |
| clear_history_btn = gr.Button("π§Ή Clear History", variant="stop") | |
| # Examples | |
| gr.HTML("<hr style='margin: 20px 0;'><h3>Examples</h3>") | |
| # Create example buttons | |
| example_buttons = [] | |
| for i, example in enumerate(EXAMPLES): | |
| btn = gr.Button( | |
| f"π― {example['title']}", | |
| variant="secondary", | |
| size="sm" | |
| ) | |
| example_buttons.append(btn) | |
| # Set up click event for each example | |
| btn.click( | |
| fn=lambda desc=example['description']: desc, | |
| outputs=[input] | |
| ) | |
| # Right Column - Output | |
| with gr.Column(scale=2): | |
| with gr.Group(): | |
| gr.HTML("<h3>Output</h3>") | |
| with gr.Row(): | |
| download_btn = gr.Button("π₯ Download Code", variant="secondary", interactive=False) | |
| view_code_btn = gr.Button("π§βπ» View Code", variant="primary") | |
| # Output tabs using visibility | |
| output_state = gr.State("empty") # "empty", "loading", "render" | |
| # Empty state | |
| empty_output = gr.HTML( | |
| """ | |
| <div class="output-empty"> | |
| <div style="text-align: center; color: #666;"> | |
| <h3>π Ready to Generate</h3> | |
| <p>Enter your request to generate code</p> | |
| </div> | |
| </div> | |
| """, | |
| visible=True | |
| ) | |
| # Loading state | |
| loading_output = gr.HTML( | |
| """ | |
| <div class="output-loading"> | |
| <div style="text-align: center; color: #666;"> | |
| <h3>β‘ Generating code...</h3> | |
| <p>Please wait while I create your application</p> | |
| </div> | |
| </div> | |
| """, | |
| visible=False | |
| ) | |
| # Render state | |
| sandbox = gr.HTML( | |
| "", | |
| elem_classes="output-html", | |
| visible=False | |
| ) | |
| # Hidden components for functionality | |
| download_content = gr.Text(visible=False) | |
| output_loading = gr.State(False) | |
| state_tab = gr.State("empty") | |
| # Modals using Gradio components | |
| with gr.Row(visible=False) as system_prompt_modal: | |
| with gr.Column(): | |
| gr.HTML("<h3>System Prompt</h3>") | |
| system_prompt_input = gr.Textbox( | |
| lines=8, | |
| placeholder="Enter your system prompt here", | |
| label="System Prompt" | |
| ) | |
| # Code viewer (will be shown/hidden) | |
| with gr.Row(visible=False) as output_code_drawer: | |
| with gr.Column(): | |
| gr.HTML("<h3>Generated Code</h3>") | |
| output = gr.Markdown(label="Code Output") | |
| # History viewer | |
| with gr.Row(visible=False) as history_drawer: | |
| with gr.Column(): | |
| gr.HTML("<h3>Chat History</h3>") | |
| history_output = gr.Chatbot( | |
| show_label=False, | |
| type="messages", | |
| height=400 | |
| ) | |
| # Event Handlers | |
| # Clear history | |
| clear_history_btn.click( | |
| fn=GradioEvents.clear_history, | |
| inputs=[state], | |
| outputs=[state] | |
| ) | |
| # Show history | |
| history_btn.click( | |
| fn=lambda: gr.update(visible=True), | |
| outputs=[history_drawer] | |
| ).then( | |
| fn=GradioEvents.render_history, | |
| inputs=[state], | |
| outputs=[history_output] | |
| ) | |
| # Download code | |
| download_btn.click( | |
| fn=None, | |
| inputs=[download_content], | |
| js="""(content) => { | |
| const blob = new Blob([content], { type: 'text/plain' }) | |
| const url = URL.createObjectURL(blob) | |
| const a = document.createElement('a') | |
| a.href = url | |
| a.download = 'generated_code.txt' | |
| a.click() | |
| }""" | |
| ) | |
| # View code | |
| view_code_btn.click( | |
| fn=lambda: gr.update(visible=True), | |
| outputs=[output_code_drawer] | |
| ) | |
| # Submit button | |
| submit_btn.click( | |
| fn=lambda: [gr.update(interactive=False), gr.update(interactive=False)], | |
| outputs=[submit_btn, download_btn] | |
| ).then( | |
| fn=GradioEvents.generate_code, | |
| inputs=[input, system_prompt_input, state], | |
| outputs=[ | |
| output, empty_output, loading_output, sandbox, | |
| download_content, download_btn, state | |
| ] | |
| ).then( | |
| fn=lambda: [gr.update(interactive=True), gr.update(interactive=True)], | |
| outputs=[submit_btn, download_btn] | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue(default_concurrency_limit=100, | |
| max_size=100).launch(ssr_mode=False, max_threads=100) | |