File size: 7,909 Bytes
e0d601d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import os
import re
import time
import json
import subprocess
import gradio as gr
from openai import OpenAI
from duckduckgo_search import DDGS

# --- OpenRouter Setup ---
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "your-openrouter-key")
client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=OPENROUTER_API_KEY,
)

# --- Model Definitions ---
MODEL_RESEARCHER = "z-ai/glm-4.5-air"
MODEL_PLANNER = "arcee-ai/trinity-large-preview"
MODEL_FRONTEND = "qwen/qwen3-coder"
MODEL_BACKEND = "minimax/minimax-m2.5"

WORKSPACE_DIR = "/app/workspace"
preview_process = None

# --- Helper Functions ---
def extract_code(text, lang="javascript"):
    """Extracts code blocks from markdown responses."""
    pattern = rf"```{lang}\n(.*?)\n```"
    matches = re.findall(pattern, text, re.DOTALL)
    if matches:
        return matches[0]
    
    # Fallback to any code block
    pattern_any = r"```.*?\n(.*?)\n```"
    matches_any = re.findall(pattern_any, text, re.DOTALL)
    return matches_any[0] if matches_any else text

def web_search(query):
    """Real-time web search for the Researcher AI"""
    try:
        results = DDGS().text(query, max_results=3)
        return "\n".join([f"- {r['title']}: {r['body']}" for r in results])
    except Exception as e:
        return f"Web search failed: {e}"

def run_openrouter(model, system_prompt, user_prompt):
    """Wrapper for OpenRouter API calls"""
    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ]
    )
    return response.choices[0].message.content

# --- Multi-Agent Pipeline ---
def generate_website(prompt):
    global preview_process
    logs = []
    
    yield "Step 1: 🌐 Researcher (GLM-4.5-Air) gathering real-time data...", "", ""
    
    # 1. RESEARCHER
    search_context = web_search(prompt)
    research_prompt = f"User Request: {prompt}\nReal-time Web Context: {search_context}\nSummarize the requirements, target audience, and modern tech trends for this app."
    research_data = run_openrouter(
        MODEL_RESEARCHER, 
        "You are an expert tech researcher. Analyze the user request and web data.", 
        research_prompt
    )
    logs.append("βœ… Research completed.")
    yield "\n".join(logs), "Step 2: πŸ—οΈ Planner (Trinity-Large) designing architecture...", ""
    
    # 2. PLANNER
    plan_prompt = f"Based on this research: {research_data}\nCreate a full stack architecture plan. Define the React frontend components and Express backend endpoints."
    architecture_plan = run_openrouter(
        MODEL_PLANNER,
        "You are a Lead Solutions Architect. Output a clear, structured technical design.",
        plan_prompt
    )
    logs.append("βœ… Architecture planned.")
    yield "\n".join(logs), "Step 3: πŸ’» Frontend Coder (Qwen3-Coder) building UI...", ""
    
    # 3. FRONTEND CODER
    fe_prompt = f"Architecture: {architecture_plan}\nWrite a complete React App using standard React and Tailwind. Output two things: 1. A package.json file wrapped in ```json ... ``` containing required dependencies. 2. A complete App.jsx wrapped in ```javascript ... ```."
    frontend_code_raw = run_openrouter(
        MODEL_FRONTEND,
        "You are an Expert React Developer. Generate functional, modern React code.",
        fe_prompt
    )
    app_jsx = extract_code(frontend_code_raw, "javascript")
    package_json = extract_code(frontend_code_raw, "json")
    
    # Save Frontend files
    os.makedirs(os.path.join(WORKSPACE_DIR, "frontend", "src"), exist_ok=True)
    with open(os.path.join(WORKSPACE_DIR, "frontend", "package.json"), "w") as f:
        f.write(package_json)
    with open(os.path.join(WORKSPACE_DIR, "frontend", "src", "App.jsx"), "w") as f:
        f.write(app_jsx)
        
    logs.append("βœ… Frontend generated and saved.")
    yield "\n".join(logs), "Step 4: πŸ” Backend Coder (MiniMax-M2.5) building secure API...", ""
    
    # 4. BACKEND CODER & INSTRUCTION GUIDE
    be_prompt = f"Architecture: {architecture_plan}\nWrite a secure Express.js backend. Provide the server code wrapped in ```javascript ... ```. Then, provide a detailed Markdown guide wrapped in ```markdown ... ``` explaining exactly where the user needs to add their Postgres/MongoDB strings, Stripe Secret Keys, and Clerk/Auth0 tokens."
    backend_code_raw = run_openrouter(
        MODEL_BACKEND,
        "You are a Senior Security & Backend Engineer.",
        be_prompt
    )
    server_js = extract_code(backend_code_raw, "javascript")
    setup_guide = extract_code(backend_code_raw, "markdown")
    
    # Save Backend files
    os.makedirs(os.path.join(WORKSPACE_DIR, "backend"), exist_ok=True)
    with open(os.path.join(WORKSPACE_DIR, "backend", "server.js"), "w") as f:
        f.write(server_js)
    with open(os.path.join(WORKSPACE_DIR, "SETUP_GUIDE.md"), "w") as f:
        f.write(setup_guide)
        
    logs.append("βœ… Backend and Setup Guide generated.")
    yield "\n".join(logs), "Step 5: βš™οΈ Installing Dependencies & Starting Live Preview...", setup_guide
    
    # 5. EXECUTION & DEPENDENCY DOWNLOAD
    try:
        # Install dependencies
        subprocess.run(["npm", "install"], cwd=os.path.join(WORKSPACE_DIR, "frontend"), check=True)
        logs.append("βœ… AI successfully downloaded dependencies.")
        yield "\n".join(logs), "Starting Server...", setup_guide
        
        # Kill previous process if it exists
        if preview_process:
            preview_process.kill()
            
        # Normally you would use a bundler like Vite. To keep it robust for this example, 
        # we start a simple HTTP server or a lightweight bundler command if defined in package.json.
        # Assuming AI creates standard start scripts:
        preview_process = subprocess.Popen(
            ["npm", "start"], 
            cwd=os.path.join(WORKSPACE_DIR, "frontend"),
            env=dict(os.environ, PORT="3000")
        )
        
        # Give server time to boot
        time.sleep(5)
        
        logs.append("πŸš€ Live preview is running!")
        iframe_html = f'<iframe src="https://{os.getenv("SPACE_HOST", "localhost")}:3000" width="100%" height="600px" style="border:1px solid #ccc; border-radius: 8px;"></iframe>'
        
        yield "\n".join(logs), iframe_html, setup_guide

    except Exception as e:
        logs.append(f"❌ Execution Error: {str(e)}")
        yield "\n".join(logs), "Failed to start live preview.", setup_guide


# --- UI Setup (Gradio) ---
with gr.Blocks(theme=gr.themes.Soft()) as demo:
    gr.Markdown("# πŸš€ Multi-Agent AI Full-Stack Builder")
    gr.Markdown("Powered by `z-ai/glm-4.5-air` (Researcher), `arcee-ai/trinity-large` (Architect), `qwen/qwen3-coder` (Frontend), and `minimax-m2.5` (Backend).")
    
    with gr.Row():
        with gr.Column(scale=1):
            prompt_input = gr.Textbox(lines=4, placeholder="E.g., Build a SaaS dashboard for an AI writing tool with Stripe subscriptions...", label="App Idea")
            build_btn = gr.Button("Build Application", variant="primary")
            status_box = gr.Textbox(label="Agent Status Logs", lines=10, interactive=False)
            
        with gr.Column(scale=2):
            gr.Markdown("### Live Website Preview")
            preview_box = gr.HTML("<div style='text-align:center; padding: 50px; border:1px dashed gray;'>Preview will appear here once built.</div>")
            
    with gr.Row():
        gr.Markdown("### Setup Guide (DB, Auth & Payments)")
        guide_box = gr.Markdown("Setup instructions will appear here.")
        
    build_btn.click(
        fn=generate_website,
        inputs=[prompt_input],
        outputs=[status_box, preview_box, guide_box]
    )

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)