"""
AI Coding Agent Crew - UPGRADE Edition (12 Agents, 22 Tools)
=============================================================
The Most Advanced Free AI Coding Agent on Hugging Face
Features:
- 20-Agent Crew: Planner, Architect, Coder, Designer, Reviewer, Tester, Optimizer, Security, Deployer, Debugger, Documenter, Manager, DevOps, Analyst, QA, Integrator, Refactorer, Monitor, Scraper, API Builder
- 30 Tools for full HF Space lifecycle management
- 18 Pre-built Full Project Templates (100% working, deploy instantly)
- Custom ReAct Agent with OpenAI function calling format
- Smart Error Pattern Matcher (auto-fixes common Gradio errors)
- Session Memory (remembers what you built)
- Space Manager (list, monitor, delete your spaces)
- Auto README Generator
- Deploy Verification (actual HTTP check)
- Gemini-style Dark UI
- 100% Free - Uses Pollinations AI
"""
import os
import json
import re
import logging
import tempfile
import time
import requests as req_lib
import gradio as gr
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ============================================================
# CONSTANTS
# ============================================================
POLLINATIONS_URL = "https://text.pollinations.ai/openai/chat/completions"
MAX_STEPS = 35
MAX_FIX_RETRIES = 3
HF_TOKEN = os.environ.get("HF_TOKEN", "")
# ============================================================
# SESSION MEMORY
# ============================================================
SESSION_MEMORY = {
"spaces_created": [],
"spaces_fixed": [],
"total_steps": 0,
"start_time": time.time(),
}
# ============================================================
# 20-AGENT CREW
# ============================================================
CREW = {
"planner": {"name": "Planner", "emoji": "๐ง ", "desc": "แ
แฎแ
แแบแแฐ", "color": "#7c8cf8"},
"architect": {"name": "Architect", "emoji": "๐๏ธ", "desc": "แแญแแฏแแฌ", "color": "#ff9f43"},
"coder": {"name": "Coder", "emoji": "๐ป", "desc": "แแฏแแบแแฑแธแแฐ", "color": "#4ecdc4"},
"designer": {"name": "Designer", "emoji": "๐จ", "desc": "แแฎแแญแฏแแบแธแแฐ", "color": "#f368e0"},
"reviewer": {"name": "Reviewer", "emoji": "๐", "desc": "แ
แ
แบแแฑแธแแฐ", "color": "#ffe66d"},
"tester": {"name": "Tester", "emoji": "๐งช", "desc": "แ
แแบแธแแแบแแฐ", "color": "#54a0ff"},
"optimizer": {"name": "Optimizer", "emoji": "โก", "desc": "แกแแผแแบแแผแพแแทแบแแฐ", "color": "#feca57"},
"security": {"name": "Security", "emoji": "๐", "desc": "แแฏแถแแผแฏแถแแฑแธ", "color": "#ff6348"},
"deployer": {"name": "Deployer", "emoji": "๐", "desc": "แแแบแแฝแแบแธแแฐ", "color": "#ff6b6b"},
"debugger": {"name": "Debugger", "emoji": "๐ง", "desc": "แแผแแบแแแบแแฐ", "color": "#a8e6cf"},
"documenter": {"name": "Documenter", "emoji": "๐", "desc": "แแพแแบแแแบแธแแฐ", "color": "#c8d6e5"},
"manager": {"name": "Manager", "emoji": "๐", "desc": "แแแบแแฑแแปแฌ", "color": "#dda0dd"},
"devops": {"name": "DevOps", "emoji": "โ๏ธ", "desc": "แแแบแกแฑแฌแทแแบ", "color": "#00b894"},
"analyst": {"name": "Analyst", "emoji": "๐", "desc": "แแฝแฒแแผแแบแธแแฐ", "color": "#6c5ce7"},
"qa": {"name": "QA", "emoji": "โ
", "desc": "แกแแแบแกแแฝแฑแธ", "color": "#00cec9"},
"integrator": {"name": "Integrator", "emoji": "๐", "desc": "แแปแญแแบแแแบแแฐ", "color": "#e17055"},
"refactorer": {"name": "Refactorer", "emoji": "โป๏ธ", "desc": "แแผแแบแแแบแแฑแธ", "color": "#81ecec"},
"monitor": {"name": "Monitor", "emoji": "๐ก", "desc": "แ
แฑแฌแแทแบแแผแแทแบแแฐ", "color": "#fab1a0"},
"scraper": {"name": "Scraper", "emoji": "๐", "desc": "แแฑแแฌแแฐแแฐ", "color": "#74b9ff"},
"api_builder":{"name": "API Builder","emoji": "๐ ๏ธ", "desc": "API แแฑแฌแแบแแฐ", "color": "#a29bfe"},
}
# ============================================================
# GET USERNAME
# ============================================================
def get_hf_username():
try:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
info = api.whoami()
return info.get("name", "")
except Exception as e:
logger.error(f"Cannot get HF username: {e}")
return ""
HF_USERNAME = get_hf_username()
logger.info(f"HF Username: {HF_USERNAME}")
# ============================================================
# PRE-BUILT FULL PROJECT TEMPLATES (100% WORKING CODE)
# ============================================================
PROJECT_TEMPLATES = {
"video_downloader": {
"name": "Video Downloader",
"keywords": ["video downloader", "video download", "youtube download", "แแฎแแญแฏแแบแแฏแแบ", "แแฎแแฎแแญแฏ"],
"repo_suffix": "video-downloader",
"requirements": "gradio>=5.0.0\nyt-dlp>=2024.1.0",
"code": '''import gradio as gr
import yt_dlp
import os
import tempfile
def download_video(url, format_choice):
if not url.strip():
return "Please enter a URL", None
try:
ydl_opts = {
"outtmpl": os.path.join(tempfile.gettempdir(), "%(title)s.%(ext)s"),
"quiet": True,
"no_warnings": True,
}
if format_choice == "Audio Only (MP3)":
ydl_opts.update({
"format": "bestaudio/best",
"postprocessors": [{
"key": "FFmpegExtractAudio",
"preferredcodec": "mp3",
"preferredquality": "192",
}],
})
elif format_choice == "Best Quality (720p+)":
ydl_opts["format"] = "best[height<=1080]"
else:
ydl_opts["format"] = "worst"
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=True)
title = info.get("title", "Unknown")
filepath = ydl.prepare_filename(info)
if format_choice == "Audio Only (MP3)":
filepath = filepath.rsplit(".", 1)[0] + ".mp3"
if os.path.exists(filepath):
return f"Downloaded: {title}", filepath
return f"Downloaded: {title} (file saved)", None
except Exception as e:
return f"Error: {str(e)}", None
with gr.Blocks(title="Video Downloader", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Video Downloader")
gr.Markdown("Enter a video URL to download (YouTube, Twitter, etc.)")
with gr.Row():
url_input = gr.Textbox(label="Video URL", placeholder="https://www.youtube.com/watch?v=...", scale=4)
format_choice = gr.Dropdown(
choices=["Best Quality (720p+)", "Low Quality", "Audio Only (MP3)"],
value="Best Quality (720p+)", label="Format", scale=1
)
download_btn = gr.Button("Download", variant="primary")
status_output = gr.Textbox(label="Status")
file_output = gr.File(label="Downloaded File")
download_btn.click(fn=download_video, inputs=[url_input, format_choice], outputs=[status_output, file_output])
demo.launch()
''',
},
"image_generator": {
"name": "AI Image Generator",
"keywords": ["image generator", "image gen", "ai image", "img gen", "แแฏแแบแแผ", "แแฏแถ"],
"repo_suffix": "ai-image-gen",
"requirements": "gradio>=5.0.0\nrequests>=2.31.0",
"code": '''import gradio as gr
import requests
import json
import tempfile
import os
import base64
def generate_image(prompt, size):
if not prompt.strip():
return None, "Please enter a prompt"
try:
size_map = {
"1024x1024": "1024x1024",
"768x1344": "768x1344",
"1344x768": "1344x768",
"512x512": "512x512",
}
selected_size = size_map.get(size, "1024x1024")
url = f"https://image.pollinations.ai/prompt/{requests.utils.quote(prompt)}?width={selected_size.split('x')[0]}&height={selected_size.split('x')[1]}&nologo=true"
resp = requests.get(url, timeout=60)
if resp.status_code == 200:
tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
tmp.write(resp.content)
tmp.close()
return tmp.name, f"Generated: {prompt}"
return None, f"Error: Status {resp.status_code}"
except Exception as e:
return None, f"Error: {str(e)}"
with gr.Blocks(title="AI Image Generator", theme=gr.themes.Soft()) as demo:
gr.Markdown("# AI Image Generator")
gr.Markdown("Generate images from text using Pollinations AI (Free!)")
prompt_input = gr.Textbox(label="Prompt", placeholder="A cute cat in a garden...", lines=2)
size_choice = gr.Dropdown(choices=["1024x1024", "768x1344", "1344x768", "512x512"], value="1024x1024", label="Size")
generate_btn = gr.Button("Generate Image", variant="primary")
image_output = gr.Image(label="Generated Image")
status_output = gr.Textbox(label="Status")
generate_btn.click(fn=generate_image, inputs=[prompt_input, size_choice], outputs=[image_output, status_output])
gr.Examples(
examples=["A cute cat playing in a garden", "A futuristic city at sunset", "A dragon in a magical forest", "An astronaut riding a horse on Mars"],
inputs=prompt_input
)
demo.launch()
''',
},
"chatbot": {
"name": "AI Chatbot",
"keywords": ["chatbot", "chat bot", "ai chat", "แ
แแฌแธแแผแฑแฌ", "แกแฑแฌแแบ"],
"repo_suffix": "ai-chatbot",
"requirements": "gradio>=5.0.0\nrequests>=2.31.0",
"code": '''import gradio as gr
import requests
import json
POLLINATIONS_URL = "https://text.pollinations.ai/openai/chat/completions"
def chat(message, history):
if not message.strip():
return ""
try:
messages = [{"role": "system", "content": "You are a helpful AI assistant. Reply in the same language as the user."}]
for msg in history:
if isinstance(msg, dict):
role = msg.get("role", "")
content = msg.get("content", "")
if role in ("user", "assistant") and content:
messages.append({"role": role, "content": str(content)})
messages.append({"role": "user", "content": message})
payload = {
"model": "openai",
"messages": messages,
"max_tokens": 2048,
}
resp = requests.post(POLLINATIONS_URL, json=payload, timeout=60)
if resp.status_code == 200:
data = resp.json()
return data.get("choices", [{}])[0].get("message", {}).get("content", "No response")
return f"Error: {resp.status_code}"
except Exception as e:
return f"Error: {str(e)}"
demo = gr.ChatInterface(fn=chat, title="AI Chatbot", description="Free AI Chatbot powered by Pollinations AI")
demo.launch()
''',
},
"translator": {
"name": "AI Translator",
"keywords": ["translator", "translate", "แแฌแแฌแแผแแบ", "แแฌแแฌ"],
"repo_suffix": "ai-translator",
"requirements": "gradio>=5.0.0\nrequests>=2.31.0",
"code": '''import gradio as gr
import requests
import json
POLLINATIONS_URL = "https://text.pollinations.ai/openai/chat/completions"
def translate(text, source_lang, target_lang):
if not text.strip():
return "Please enter text to translate"
try:
messages = [
{"role": "system", "content": f"You are a professional translator. Translate the following text from {source_lang} to {target_lang}. Only output the translation, nothing else."},
{"role": "user", "content": text}
]
payload = {"model": "openai", "messages": messages, "max_tokens": 2048}
resp = requests.post(POLLINATIONS_URL, json=payload, timeout=60)
if resp.status_code == 200:
data = resp.json()
return data.get("choices", [{}])[0].get("message", {}).get("content", "Translation failed")
return f"Error: {resp.status_code}"
except Exception as e:
return f"Error: {str(e)}"
languages = ["English", "Myanmar (Burmese)", "Chinese", "Japanese", "Korean", "Thai", "Hindi", "French", "German", "Spanish", "Arabic", "Russian"]
with gr.Blocks(title="AI Translator", theme=gr.themes.Soft()) as demo:
gr.Markdown("# AI Translator")
gr.Markdown("Translate text between any languages using AI")
with gr.Row():
source_lang = gr.Dropdown(choices=languages, value="English", label="From")
target_lang = gr.Dropdown(choices=languages, value="Myanmar (Burmese)", label="To")
text_input = gr.Textbox(label="Enter Text", placeholder="Type text to translate...", lines=4)
translate_btn = gr.Button("Translate", variant="primary")
output_text = gr.Textbox(label="Translation", lines=4)
translate_btn.click(fn=translate, inputs=[text_input, source_lang, target_lang], outputs=output_text)
gr.Examples(
examples=["Hello, how are you?", "This is a beautiful day", "Artificial intelligence is changing the world", "I love learning new things"],
inputs=text_input
)
demo.launch()
''',
},
"qr_code": {
"name": "QR Code Generator",
"keywords": ["qr code", "qrcode", "qr", "แแปแฐแกแฌแแฏแแบ"],
"repo_suffix": "qr-generator",
"requirements": "gradio>=5.0.0\nqrcode>=7.4\nPillow>=10.0.0",
"code": '''import gradio as gr
import qrcode
from PIL import Image
import tempfile
import os
def generate_qr(text, fill_color, back_color, size):
if not text.strip():
return None, "Please enter text or URL"
try:
qr = qrcode.QRCode(version=1, box_size=size, border=4)
qr.add_data(text)
qr.make(fit=True)
img = qr.make_image(fill_color=fill_color, back_color=back_color)
tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
img.save(tmp.name)
return tmp.name, f"QR Code generated for: {text[:50]}"
except Exception as e:
return None, f"Error: {str(e)}"
with gr.Blocks(title="QR Code Generator", theme=gr.themes.Soft()) as demo:
gr.Markdown("# QR Code Generator")
text_input = gr.Textbox(label="Text or URL", placeholder="https://example.com", lines=2)
with gr.Row():
fill_color = gr.ColorPicker(value="#000000", label="QR Color")
back_color = gr.ColorPicker(value="#FFFFFF", label="Background Color")
size = gr.Slider(minimum=5, maximum=20, value=10, step=1, label="Size")
generate_btn = gr.Button("Generate QR Code", variant="primary")
image_output = gr.Image(label="QR Code")
status_output = gr.Textbox(label="Status")
generate_btn.click(fn=generate_qr, inputs=[text_input, fill_color, back_color, size], outputs=[image_output, status_output])
demo.launch()
''',
},
"password_gen": {
"name": "Password Generator",
"keywords": ["password generator", "password", "แ
แแฌแธแแพแแบ", "แแซแ
แแซ"],
"repo_suffix": "password-generator",
"requirements": "gradio>=5.0.0",
"code": '''import gradio as gr
import random
import string
def generate_password(length, uppercase, lowercase, numbers, symbols):
chars = ""
if uppercase: chars += string.ascii_uppercase
if lowercase: chars += string.ascii_lowercase
if numbers: chars += string.digits
if symbols: chars += "!@#$%^&*()_+-=[]{}|;:,.<>?"
if not chars:
return "Error: Select at least one character type", "", 0
password = "".join(random.choice(chars) for _ in range(length))
score = 0
if length >= 12: score += 1
if length >= 16: score += 1
if uppercase and lowercase: score += 1
if numbers: score += 1
if symbols: score += 1
strength = ["Very Weak", "Weak", "Fair", "Strong", "Very Strong"][min(score, 4)]
entropy = len(chars) ** length
return password, strength, entropy
with gr.Blocks(title="Password Generator", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Password Generator")
length = gr.Slider(minimum=8, maximum=64, value=16, step=1, label="Password Length")
with gr.Row():
uppercase = gr.Checkbox(value=True, label="Uppercase (A-Z)")
lowercase = gr.Checkbox(value=True, label="Lowercase (a-z)")
numbers = gr.Checkbox(value=True, label="Numbers (0-9)")
symbols = gr.Checkbox(value=True, label="Symbols (!@#$)")
generate_btn = gr.Button("Generate Password", variant="primary")
password_output = gr.Textbox(label="Password", interactive=True)
with gr.Row():
strength_output = gr.Textbox(label="Strength")
entropy_output = gr.Number(label="Possible Combinations")
generate_btn.click(fn=generate_password, inputs=[length, uppercase, lowercase, numbers, symbols], outputs=[password_output, strength_output, entropy_output])
demo.launch()
''',
},
"text_to_speech": {
"name": "Text to Speech",
"keywords": ["text to speech", "tts", "voice", "แกแแถ", "แ
แแฌแธแแผแฑแฌ"],
"repo_suffix": "text-to-speech",
"requirements": "gradio>=5.0.0\nedge-tts>=6.1.0",
"code": '''import gradio as gr
import asyncio
import edge_tts
import tempfile
import os
async def generate_speech(text, voice, rate):
if not text.strip():
return None, "Please enter text"
try:
communicate = edge_tts.Communicate(text, voice, rate=rate)
tmp = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
await communicate.save(tmp.name)
return tmp.name, f"Generated speech with {voice}"
except Exception as e:
return None, f"Error: {str(e)}"
def speak(text, voice, rate):
rate_str = f"+{rate}%" if rate > 0 else f"{rate}%" if rate < 0 else "+0%"
return asyncio.run(generate_speech(text, voice, rate_str))
voices = ["en-US-AriaNeural", "en-US-GuyNeural", "en-GB-SoniaNeural", "my-MM-NilarNeural", "zh-CN-XiaoxiaoNeural", "ja-JP-NanamiNeural", "ko-KR-SunHiNeural", "th-TH-PremwadeeNeural"]
with gr.Blocks(title="Text to Speech", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Text to Speech")
text_input = gr.Textbox(label="Text", placeholder="Enter text to convert to speech...", lines=4)
voice = gr.Dropdown(choices=voices, value="en-US-AriaNeural", label="Voice")
rate = gr.Slider(minimum=-50, maximum=50, value=0, step=10, label="Speed (%)")
speak_btn = gr.Button("Generate Speech", variant="primary")
audio_output = gr.Audio(label="Generated Speech")
status_output = gr.Textbox(label="Status")
speak_btn.click(fn=speak, inputs=[text_input, voice, rate], outputs=[audio_output, status_output])
demo.launch()
''',
},
"calculator": {
"name": "Smart Calculator",
"keywords": ["calculator", "calc", "แแฝแแบแแปแแบ", "แแแแบแธ"],
"repo_suffix": "smart-calculator",
"requirements": "gradio>=5.0.0",
"code": '''import gradio as gr
def calculate(expression):
if not expression.strip():
return "Enter an expression"
try:
allowed = set("0123456789+-*/.()% ")
if not all(c in allowed for c in expression):
return "Error: Only numbers and basic operators (+-*/.()%) allowed"
result = eval(expression, {"__builtins__": {}}, {})
return f"{expression} = {result}"
except ZeroDivisionError:
return "Error: Division by zero"
except Exception as e:
return f"Error: {str(e)}"
with gr.Blocks(title="Smart Calculator", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Smart Calculator")
gr.Markdown("Enter a mathematical expression to calculate")
expr_input = gr.Textbox(label="Expression", placeholder="2 + 2 * 3", lines=1)
calc_btn = gr.Button("Calculate", variant="primary")
result_output = gr.Textbox(label="Result", lines=1)
calc_btn.click(fn=calculate, inputs=expr_input, outputs=result_output)
gr.Examples(
examples=["2 + 2", "100 * 50 / 25", "(10 + 5) * 3", "2 ** 10", "100 % 7"],
inputs=expr_input
)
demo.launch()
''',
},
"todo_app": {
"name": "Todo List App",
"keywords": ["todo", "task", "task manager", "todo list", "แกแแฏแแบ"],
"repo_suffix": "todo-app",
"requirements": "gradio>=5.0.0",
"code": '''import gradio as gr
import json
import os
TODO_FILE = "todos.json"
def load_todos():
try:
if os.path.exists(TODO_FILE):
with open(TODO_FILE, "r") as f:
return json.load(f)
except:
pass
return []
def save_todos(todos):
with open(TODO_FILE, "w") as f:
json.dump(todos, f)
def add_todo(task, todos_json):
if not task.strip():
return todos_json, "Please enter a task"
todos = json.loads(todos_json) if todos_json else []
todos.append({"task": task, "done": False})
save_todos(todos)
return json.dumps(todos), f"Added: {task}"
def toggle_todo(index, todos_json):
todos = json.loads(todos_json) if todos_json else []
if 0 <= index < len(todos):
todos[index]["done"] = not todos[index]["done"]
save_todos(todos)
status = "done" if todos[index]["done"] else "undone"
return json.dumps(todos), f"Marked as {status}"
return todos_json, "Invalid index"
def delete_todo(index, todos_json):
todos = json.loads(todos_json) if todos_json else []
if 0 <= index < len(todos):
removed = todos.pop(index)
save_todos(todos)
return json.dumps(todos), f"Deleted: {removed['task']}"
return todos_json, "Invalid index"
def format_display(todos_json):
todos = json.loads(todos_json) if todos_json else []
if not todos:
return "No tasks yet. Add one above!"
lines = []
for i, t in enumerate(todos):
icon = "DONE" if t["done"] else "TODO"
lines.append(f"[{icon}] {i}: {t['task']}")
return "\\n".join(lines)
with gr.Blocks(title="Todo App", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Todo List")
todos_state = gr.State(json.dumps(load_todos()))
task_input = gr.Textbox(label="New Task", placeholder="Enter a task...")
with gr.Row():
add_btn = gr.Button("Add Task", variant="primary")
idx_input = gr.Number(label="Task Index", precision=0)
with gr.Row():
toggle_btn = gr.Button("Toggle Done")
delete_btn = gr.Button("Delete Task")
display = gr.Textbox(label="Tasks", lines=10, interactive=False)
status = gr.Textbox(label="Status")
add_btn.click(fn=add_todo, inputs=[task_input, todos_state], outputs=[todos_state, status]).then(fn=format_display, inputs=todos_state, outputs=display)
toggle_btn.click(fn=toggle_todo, inputs=[idx_input, todos_state], outputs=[todos_state, status]).then(fn=format_display, inputs=todos_state, outputs=display)
delete_btn.click(fn=delete_todo, inputs=[idx_input, todos_state], outputs=[todos_state, status]).then(fn=format_display, inputs=todos_state, outputs=display)
demo.load(fn=format_display, inputs=todos_state, outputs=display)
demo.launch()
''',
},
"url_shortener": {
"name": "URL Shortener",
"keywords": ["url shortener", "shorten url", "link shortener", "link"],
"repo_suffix": "url-shortener",
"requirements": "gradio>=5.0.0\nrequests>=2.31.0",
"code": '''import gradio as gr
import requests
import json
def shorten_url(url):
if not url.strip():
return "Please enter a URL", ""
try:
resp = requests.get(f"https://tinyurl.com/api-create.php?url={url}", timeout=15)
if resp.status_code == 200 and resp.text.startswith("http"):
return resp.text, f"Shortened: {url[:50]}"
return f"Error: Could not shorten URL", ""
except Exception as e:
return f"Error: {str(e)}", ""
with gr.Blocks(title="URL Shortener", theme=gr.themes.Soft()) as demo:
gr.Markdown("# URL Shortener")
url_input = gr.Textbox(label="Long URL", placeholder="https://example.com/very/long/url...", lines=2)
shorten_btn = gr.Button("Shorten URL", variant="primary")
short_url = gr.Textbox(label="Short URL", interactive=True)
status = gr.Textbox(label="Status")
shorten_btn.click(fn=shorten_url, inputs=url_input, outputs=[short_url, status])
demo.launch()
''',
},
"pdf_summarizer": {
"name": "PDF Summarizer",
"keywords": ["pdf", "summarizer", "document", "แแฎแแฎแกแแบแแบ", "แ
แฌแแฝแฑ"],
"repo_suffix": "pdf-summarizer",
"requirements": "gradio>=5.0.0\nrequests>=2.31.0\nPyPDF2>=3.0.0",
"code": '''import gradio as gr
import requests
import json
import PyPDF2
import tempfile
POLLINATIONS_URL = "https://text.pollinations.ai/openai/chat/completions"
def summarize_pdf(pdf_file, question):
if pdf_file is None:
return "Please upload a PDF file"
try:
reader = PyPDF2.PdfReader(pdf_file)
text = ""
for page in reader.pages[:10]:
page_text = page.extract_text()
if page_text:
text += page_text + "\\n"
if not text.strip():
return "Could not extract text from this PDF"
if len(text) > 8000:
text = text[:8000] + "...(truncated)"
prompt = f"Summarize this document concisely:\\n\\n{text}"
if question.strip():
prompt = f"Based on this document, answer the question: {question}\\n\\nDocument:\\n{text}"
messages = [
{"role": "system", "content": "You are a helpful document assistant. Reply concisely."},
{"role": "user", "content": prompt}
]
payload = {"model": "openai", "messages": messages, "max_tokens": 2048}
resp = requests.post(POLLINATIONS_URL, json=payload, timeout=60)
if resp.status_code == 200:
data = resp.json()
return data.get("choices", [{}])[0].get("message", {}).get("content", "Failed to generate summary")
return f"AI Error: {resp.status_code}"
except Exception as e:
return f"Error: {str(e)}"
with gr.Blocks(title="PDF Summarizer", theme=gr.themes.Soft()) as demo:
gr.Markdown("# PDF Summarizer")
pdf_input = gr.File(label="Upload PDF", file_types=[".pdf"])
question_input = gr.Textbox(label="Question (optional)", placeholder="Ask a question about the document...")
summarize_btn = gr.Button("Summarize", variant="primary")
output = gr.Textbox(label="Summary", lines=10)
summarize_btn.click(fn=summarize_pdf, inputs=[pdf_input, question_input], outputs=output)
demo.launch()
''',
},
"code_formatter": {
"name": "Code Formatter",
"keywords": ["code formatter", "code beautifier", "code", "format", "แแฏแแบ"],
"repo_suffix": "code-formatter",
"requirements": "gradio>=5.0.0\nrequests>=2.31.0",
"code": '''import gradio as gr
import requests
import json
POLLINATIONS_URL = "https://text.pollinations.ai/openai/chat/completions"
def format_code(code, language):
if not code.strip():
return "Please enter code"
try:
messages = [
{"role": "system", "content": f"You are a code formatter. Format and clean up the following {language} code. Only output the formatted code, nothing else. Add proper indentation and spacing."},
{"role": "user", "content": code}
]
payload = {"model": "openai", "messages": messages, "max_tokens": 4096}
resp = requests.post(POLLINATIONS_URL, json=payload, timeout=60)
if resp.status_code == 200:
data = resp.json()
return data.get("choices", [{}])[0].get("message", {}).get("content", "Format failed")
return f"Error: {resp.status_code}"
except Exception as e:
return f"Error: {str(e)}"
languages = ["Python", "JavaScript", "HTML", "CSS", "Java", "C++", "SQL", "JSON"]
with gr.Blocks(title="Code Formatter", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Code Formatter")
code_input = gr.Textbox(label="Input Code", placeholder="Paste your code here...", lines=10)
language = gr.Dropdown(choices=languages, value="Python", label="Language")
format_btn = gr.Button("Format Code", variant="primary")
output = gr.Textbox(label="Formatted Code", lines=10)
format_btn.click(fn=format_code, inputs=[code_input, language], outputs=output)
demo.launch()
''',
},
"weather_app": {
"name": "Weather App",
"keywords": ["weather", "forecast", "แแฌแแฎแฅแแฏ"],
"repo_suffix": "weather-app",
"requirements": "gradio>=5.0.0\nrequests>=2.31.0",
"code": """import gradio as gr
import requests as req_lib
def get_weather(location):
if not location.strip(): return "Please enter a location"
try:
resp = req_lib.get(f"https://wttr.in/{location}?format=j1", timeout=15)
if resp.status_code == 200:
d = resp.json(); c = d.get("current_condition",[{}])[0]; a = d.get("nearest_area",[{}])[0]
city = a.get("areaName",[{}])[0].get("value",location)
return f"Location: {city}\nTemp: {c.get('temp_C','?')}C (Feels {c.get('FeelsLikeC','?')}C)\nCondition: {c.get('weatherDesc',[{}])[0].get('value','?')}\nHumidity: {c.get('humidity','?')}%\nWind: {c.get('windspeedKmph','?')}km/h"
return "Error: Could not fetch weather"
except Exception as e: return f"Error: {e}"
with gr.Blocks(title="Weather App", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Weather App")
inp = gr.Textbox(label="Location", placeholder="Enter city...")
btn = gr.Button("Get Weather", variant="primary")
out = gr.Textbox(label="Weather", lines=5)
btn.click(fn=get_weather, inputs=inp, outputs=out)
gr.Examples(examples=["Yangon","Tokyo","London","Bangkok"], inputs=inp)
demo.launch()
""",
},
"markdown_editor": {
"name": "Markdown Editor",
"keywords": ["markdown", "editor", "md"],
"repo_suffix": "markdown-editor",
"requirements": "gradio>=5.0.0",
"code": """import gradio as gr
with gr.Blocks(title="Markdown Editor", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Markdown Editor")
with gr.Row():
md_in = gr.Textbox(label="Write Markdown", placeholder="# Hello\n**bold** *italic*", lines=15, scale=1)
md_out = gr.Markdown(label="Preview", scale=1)
md_in.change(fn=lambda x: x, inputs=md_in, outputs=md_out)
demo.launch()
""",
},
"color_palette": {
"name": "Color Palette",
"keywords": ["color", "palette", "แกแแฑแฌแแบ"],
"repo_suffix": "color-palette",
"requirements": "gradio>=5.0.0",
"code": """import gradio as gr
import random, colorsys
def gen_palette(n, hue):
colors = []
for i in range(int(n)):
h = (hue + i * 360 // int(n)) % 360
r,g,b = [int(c*255) for c in colorsys.hls_to_rgb(h/360, 0.5, 0.7)]
colors.append(f"#{r:02x}{g:02x}{b:02x}")
html = "
"
for c in colors:
html += f"
{c}
"
html += "
"
return html, "\n".join(colors)
with gr.Blocks(title="Color Palette", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Color Palette Generator")
n = gr.Slider(3,10,5,step=1,label="Colors"); hue = gr.Slider(0,360,200,step=1,label="Base Hue")
btn = gr.Button("Generate", variant="primary")
pal = gr.HTML(); hex = gr.Textbox(label="Hex", lines=5, interactive=True)
btn.click(fn=gen_palette, inputs=[n,hue], outputs=[pal,hex])
demo.launch()
""",
},
"json_tool": {
"name": "JSON Tool",
"keywords": ["json", "formatter", "validator"],
"repo_suffix": "json-tool",
"requirements": "gradio>=5.0.0",
"code": """import gradio as gr
import json
def proc(text, action):
if not text.strip(): return "Enter JSON"
try:
d = json.loads(text)
if action == "Pretty": return json.dumps(d, indent=2, ensure_ascii=False)
if action == "Minify": return json.dumps(d, separators=(",",":"), ensure_ascii=False)
if action == "Validate": return f"Valid! Type: {type(d).__name__}"
if action == "Sort": return json.dumps(d, indent=2, sort_keys=True, ensure_ascii=False)
except json.JSONDecodeError as e: return f"Invalid: {e}"
with gr.Blocks(title="JSON Tool", theme=gr.themes.Soft()) as demo:
gr.Markdown("# JSON Tool")
inp = gr.Textbox(label="JSON", placeholder='{"key":"value"}', lines=10)
act = gr.Dropdown(choices=["Pretty","Minify","Validate","Sort"], value="Pretty")
btn = gr.Button("Process", variant="primary")
out = gr.Textbox(label="Output", lines=10)
btn.click(fn=proc, inputs=[inp,act], outputs=out)
demo.launch()
""",
},
"sentiment_analyzer": {
"name": "Sentiment Analyzer",
"keywords": ["sentiment", "emotion", "แแถแ
แฌแธแแปแแบ"],
"repo_suffix": "sentiment-analyzer",
"requirements": "gradio>=5.0.0\nrequests>=2.31.0",
"code": """import gradio as gr
import requests as req_lib
PURL = "https://text.pollinations.ai/openai/chat/completions"
def analyze(text):
if not text.strip(): return "Enter text"
try:
msgs = [{"role":"system","content":"Analyze sentiment. Return: sentiment, confidence, emotions, summary. Reply in user language."},{"role":"user","content":text}]
r = req_lib.post(PURL, json={"model":"openai","messages":msgs,"max_tokens":500}, timeout=60)
if r.status_code == 200: return r.json().get("choices",[{}])[0].get("message",{}).get("content","Failed")
return f"Error: {r.status_code}"
except Exception as e: return f"Error: {e}"
with gr.Blocks(title="Sentiment Analyzer", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Sentiment Analyzer")
inp = gr.Textbox(label="Text", placeholder="Enter text...", lines=4)
btn = gr.Button("Analyze", variant="primary")
out = gr.Textbox(label="Result", lines=5)
btn.click(fn=analyze, inputs=inp, outputs=out)
gr.Examples(examples=["I love this!","This is terrible.","It is okay."], inputs=inp)
demo.launch()
""",
},
"speed_test": {
"name": "Speed Test",
"keywords": ["speed", "internet", "network", "แกแแผแแบแแพแฏแแบแธ"],
"repo_suffix": "speed-test",
"requirements": "gradio>=5.0.0\nrequests>=2.31.0",
"code": """import gradio as gr
import requests as req_lib, time
def test_speed(url):
if not url.strip(): return "Enter URL"
try:
t0 = time.time()
r = req_lib.get(url, timeout=30, stream=True)
total = sum(len(c) for c in r.iter_content(8192))
dt = time.time() - t0
spd = (total*8)/(dt*1e6) if dt>0 else 0
return f"Status: {r.status_code}\nDownloaded: {total/1024:.1f}KB\nTime: {dt:.2f}s\nSpeed: {spd:.2f}Mbps"
except Exception as e: return f"Error: {e}"
with gr.Blocks(title="Speed Test", theme=gr.themes.Soft()) as demo:
gr.Markdown("# Speed Test")
url = gr.Textbox(label="URL", value="https://speed.cloudflare.com/__down?bytes=1000000")
btn = gr.Button("Test", variant="primary")
out = gr.Textbox(label="Result", lines=5)
btn.click(fn=test_speed, inputs=url, outputs=out)
demo.launch()
""",
},
}
# ============================================================
# SMART ERROR PATTERN MATCHER
# ============================================================
ERROR_PATTERNS = {
"type_messages": {
"patterns": [r"type.*messages.*not.*accepted", r"unexpected.*keyword.*type"],
"fix": 'In Gradio 6.x, Chatbot uses messages format by default. Ensure all messages are dicts with role/content keys. Do NOT pass type="messages" parameter.',
"auto_replace": [],
},
"css_blocks": {
"patterns": [r"unexpected.*keyword.*css.*Blocks", r"css.*not.*valid.*Blocks", r"parameters have been moved.*Blocks constructor to the launch"],
"fix": "Move css parameter from gr.Blocks() to demo.launch() - Gradio 6.x changed this",
"auto_replace": [("gr.Blocks(css=", "gr.Blocks("), ("gr.Blocks(title=", "gr.Blocks(title=")],
},
"missing_launch": {
"patterns": [r"no.*attribute.*launch", r"module.*has.*no.*launch"],
"fix": "Add demo.launch() at the end of the script",
},
"import_error": {
"patterns": [r"ModuleNotFoundError.*No module named.*'(\w+)'", r"ImportError.*cannot import name"],
"fix": "Add the missing module to requirements.txt",
},
"syntax_error": {
"patterns": [r"SyntaxError.*invalid syntax", r"IndentationError"],
"fix": "Fix the syntax error in the code",
},
"gradio_version": {
"patterns": [r"AttributeError.*module 'gradio' has no attribute", r"TypeError.*__init__.*got an unexpected keyword"],
"fix": "This may be a Gradio version incompatibility. Try using gradio>=5.0.0 or check the API docs.",
},
"port_in_use": {
"patterns": [r"OSError.*Address already in use", r"port.*already.*in.*use"],
"fix": "The port is already in use. Try adding server_port=7861 to demo.launch()",
},
"requests_conflict": {
"patterns": [r"requests.*has.*no.*attribute", r"module 'requests'"],
"fix": "Rename 'import requests' to 'import requests as req_lib' to avoid conflict with Gradio",
"auto_replace": [("import requests\n", "import requests as req_lib\n"), ("requests.get", "req_lib.get"), ("requests.post", "req_lib.post"), ("requests.put", "req_lib.put"), ("requests.delete", "req_lib.delete")],
},
}
def match_error_pattern(error_msg):
"""Match error message against known patterns"""
for key, pattern_info in ERROR_PATTERNS.items():
for pat in pattern_info["patterns"]:
if re.search(pat, error_msg, re.IGNORECASE):
return key, pattern_info
return None, None
def auto_fix_code(code, error_key, pattern_info):
"""Attempt automatic code fix based on error pattern"""
if "auto_replace" not in pattern_info:
return code, False
fixed_code = code
replaced = False
for old, new in pattern_info["auto_replace"]:
if old in fixed_code:
fixed_code = fixed_code.replace(old, new)
replaced = True
return fixed_code, replaced
# ============================================================
# TOOLS (30 TOOLS - 22 existing + 8 new MEGA)
# ============================================================
def tool_create_space(repo_id: str, sdk: str = "gradio") -> str:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
try:
api.create_repo(repo_id=repo_id, repo_type="space", space_sdk=sdk, exist_ok=True)
return f"OK: Space '{repo_id}' created with {sdk} SDK"
except Exception as e:
return f"ERROR: {e}"
def tool_upload_file(repo_id: str, filename: str, content: str) -> str:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
try:
with tempfile.NamedTemporaryFile(mode="w", suffix="_" + filename, delete=False) as f:
f.write(content)
tmp = f.name
api.upload_file(
path_or_fileobj=tmp, path_in_repo=filename,
repo_id=repo_id, repo_type="space",
commit_message=f"Update {filename}"
)
os.unlink(tmp)
return f"OK: Uploaded {filename} to {repo_id}"
except Exception as e:
return f"ERROR: {e}"
def tool_set_secret(repo_id: str, key: str, value: str) -> str:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
try:
api.add_space_secret(repo_id=repo_id, key=key, value=value)
return f"OK: Secret '{key}' set in {repo_id}"
except Exception as e:
return f"ERROR: {e}"
def tool_list_files(repo_id: str) -> str:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
try:
files = api.list_repo_files(repo_id=repo_id, repo_type="space")
return "OK: Files: " + ", ".join(files)
except Exception as e:
return f"ERROR: {e}"
def tool_read_file(repo_id: str, filename: str) -> str:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
try:
tmp = tempfile.mktemp(suffix="_" + filename)
api.hf_hub_download(repo_id=repo_id, filename=filename, repo_type="space", local_path=tmp)
with open(tmp, "r") as f:
content = f.read()
os.unlink(tmp)
return f"OK: Content of {filename}:\n{content}"
except Exception as e:
return f"ERROR: {e}"
def tool_delete_file(repo_id: str, filename: str) -> str:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
try:
api.delete_file(path_in_repo=filename, repo_id=repo_id, repo_type="space", commit_message=f"Delete {filename}")
return f"OK: Deleted {filename} from {repo_id}"
except Exception as e:
return f"ERROR: {e}"
def tool_restart_space(repo_id: str) -> str:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
try:
api.restart_space(repo_id=repo_id)
return f"OK: Space '{repo_id}' restarted successfully"
except Exception as e:
return f"ERROR: {e}"
def tool_check_status(repo_id: str) -> str:
try:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
info = api.space_info(repo_id=repo_id)
stage = info.runtime.stage if info.runtime else "UNKNOWN"
hardware = info.runtime.hardware if info.runtime else "unknown"
space_url = f"https://{repo_id.replace('/', '-')}.hf.space"
try:
resp = req_lib.get(space_url, timeout=15)
http_status = resp.status_code
except:
http_status = "unreachable"
return f"OK: Stage={stage}, Hardware={hardware}, HTTP={http_status}, URL={space_url}"
except Exception as e:
return f"ERROR: {e}"
def tool_get_space_logs(repo_id: str) -> str:
try:
headers = {"Authorization": f"Bearer {HF_TOKEN}"}
resp = req_lib.get(
f"https://huggingface.co/api/spaces/{repo_id}",
headers=headers, timeout=30
)
if resp.status_code == 200:
data = resp.json()
runtime = data.get("runtime", {})
error_msg = runtime.get("errorMessage", None)
stage = runtime.get("stage", "unknown")
if error_msg:
return f"OK: Stage={stage}, Error={error_msg[:3000]}"
else:
return f"OK: Stage={stage}, No errors found. Space seems healthy."
else:
return f"ERROR: Could not fetch logs (status {resp.status_code})"
except Exception as e:
return f"ERROR: {e}"
def tool_web_search(query: str) -> str:
try:
resp = req_lib.get(
f"https://text.pollinations.ai/",
params={"prompt": f"Search results for: {query}. Provide relevant URLs, code examples, and documentation links.", "model": "openai"},
timeout=30
)
if resp.status_code == 200:
text = resp.text[:3000]
return f"OK: Search results for '{query}':\n{text}"
else:
return f"ERROR: Search failed (status {resp.status_code})"
except Exception as e:
return f"ERROR: {e}"
def tool_validate_code(code: str, filename: str = "app.py") -> str:
if not code or not code.strip():
return "ERROR: Empty code provided"
try:
compile(code, filename, 'exec')
issues = []
if "gradio" in code and "import gradio" not in code and "from gradio" not in code:
issues.append("Missing 'import gradio as gr'")
if "demo.launch()" not in code and "demo().launch()" not in code:
if "gr.Interface" in code or "gr.Blocks" in code or "gr.ChatInterface" in code:
issues.append("Missing demo.launch() at the end")
# type="messages" is now correct in Gradio 6.x, no need to flag it
if "import requests\n" in code and "as " not in code.split("import requests")[0][-20:]:
issues.append("Use 'import requests as req_lib' to avoid Gradio conflict")
if issues:
return f"WARNING: Code compiles but has issues: {'; '.join(issues)}"
return f"OK: Code validates successfully ({len(code)} chars, {code.count(chr(10))+1} lines)"
except SyntaxError as e:
return f"ERROR: Syntax error at line {e.lineno}: {e.msg}"
except Exception as e:
return f"WARNING: Could not fully validate: {e}"
def tool_wait_for_space(repo_id: str, timeout: int = 120) -> str:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
start = time.time()
while time.time() - start < timeout:
try:
info = api.space_info(repo_id=repo_id)
stage = info.runtime.stage if info.runtime else "UNKNOWN"
if stage == "RUNNING":
space_url = f"https://{repo_id.replace('/', '-')}.hf.space"
return f"OK: Space is RUNNING! URL: {space_url}"
elif stage == "RUNTIME_ERROR":
return f"ERROR: Space has RUNTIME_ERROR. Use get_space_logs to diagnose."
elif stage in ("STOPPED", "PAUSED"):
try:
api.restart_space(repo_id=repo_id)
except:
pass
time.sleep(8)
except:
time.sleep(5)
return f"WARNING: Timeout after {timeout}s. Space may still be starting."
def tool_list_my_spaces() -> str:
"""List all your Hugging Face Spaces"""
try:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
spaces = api.list_spaces(author=HF_USERNAME)
if not spaces:
return "OK: No spaces found."
result_lines = []
for space in spaces:
space_id = space.id if hasattr(space, 'id') else str(space)
stage = "unknown"
try:
info = api.space_info(repo_id=space_id)
stage = info.runtime.stage if info.runtime else "unknown"
except:
pass
stage_icon = "RUNNING" if stage == "RUNNING" else "ERROR" if stage == "RUNTIME_ERROR" else stage
result_lines.append(f" {stage_icon}: {space_id}")
return f"OK: Your Spaces ({len(result_lines)}):\n" + "\n".join(result_lines)
except Exception as e:
return f"ERROR: {e}"
def tool_delete_space(repo_id: str) -> str:
"""Delete a Hugging Face Space permanently"""
try:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
api.delete_repo(repo_id=repo_id, repo_type="space")
return f"OK: Space '{repo_id}' deleted permanently"
except Exception as e:
return f"ERROR: {e}"
def tool_deploy_template(template_name: str, repo_id: str) -> str:
"""Deploy a pre-built template directly - instant deployment with working code!"""
tmpl = PROJECT_TEMPLATES.get(template_name)
if not tmpl:
available = ", ".join(PROJECT_TEMPLATES.keys())
return f"ERROR: Template '{template_name}' not found. Available: {available}"
try:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
# Create space
api.create_repo(repo_id=repo_id, repo_type="space", space_sdk="gradio", exist_ok=True)
# Upload app.py
with tempfile.NamedTemporaryFile(mode="w", suffix="_app.py", delete=False) as f:
f.write(tmpl["code"])
app_tmp = f.name
api.upload_file(path_or_fileobj=app_tmp, path_in_repo="app.py", repo_id=repo_id, repo_type="space", commit_message="Deploy from template")
os.unlink(app_tmp)
# Upload requirements.txt
with tempfile.NamedTemporaryFile(mode="w", suffix="_req.txt", delete=False) as f:
f.write(tmpl["requirements"])
req_tmp = f.name
api.upload_file(path_or_fileobj=req_tmp, path_in_repo="requirements.txt", repo_id=repo_id, repo_type="space", commit_message="Add requirements")
os.unlink(req_tmp)
# Upload README.md
readme = f"# {tmpl['name']}\n\n{tmpl['name']} built with AI Coding Agent Crew.\n\n## Usage\n\nJust enter your input and click the button!\n"
with tempfile.NamedTemporaryFile(mode="w", suffix="_readme.md", delete=False) as f:
f.write(readme)
read_tmp = f.name
api.upload_file(path_or_fileobj=read_tmp, path_in_repo="README.md", repo_id=repo_id, repo_type="space", commit_message="Add README")
os.unlink(read_tmp)
# Restart
try:
api.restart_space(repo_id=repo_id)
except:
pass
space_url = f"https://huggingface.co/spaces/{repo_id}"
return f"OK: Template '{tmpl['name']}' deployed to {repo_id}! URL: {space_url}"
except Exception as e:
return f"ERROR: {e}"
def tool_verify_deployment(repo_id: str) -> str:
"""Verify a Space is actually working by making an HTTP request"""
try:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
info = api.space_info(repo_id=repo_id)
stage = info.runtime.stage if info.runtime else "UNKNOWN"
if stage != "RUNNING":
return f"ERROR: Space is {stage}, not RUNNING"
space_url = f"https://{repo_id.replace('/', '-')}.hf.space"
try:
resp = req_lib.get(space_url, timeout=20)
if resp.status_code == 200:
return f"OK: Space is LIVE and responding! URL: {space_url} (HTTP {resp.status_code}, {len(resp.content)} bytes)"
else:
return f"WARNING: Space is RUNNING but returned HTTP {resp.status_code}. URL: {space_url}"
except req_lib.Timeout:
return f"WARNING: Space is RUNNING but not responding (timeout). URL: {space_url}"
except Exception as e:
return f"WARNING: Space is RUNNING but connection failed: {e}. URL: {space_url}"
except Exception as e:
return f"ERROR: {e}"
def tool_auto_fix_space(repo_id: str) -> str:
"""Auto-fix a Space with RUNTIME_ERROR using smart error pattern matching"""
try:
# Get logs
headers = {"Authorization": f"Bearer {HF_TOKEN}"}
resp = req_lib.get(f"https://huggingface.co/api/spaces/{repo_id}", headers=headers, timeout=30)
if resp.status_code != 200:
return f"ERROR: Cannot fetch Space info"
data = resp.json()
runtime = data.get("runtime", {})
error_msg = runtime.get("errorMessage", "")
stage = runtime.get("stage", "unknown")
if stage != "RUNTIME_ERROR" and not error_msg:
return f"OK: Space is {stage}, no errors to fix"
# Try to match error patterns
error_key, pattern_info = match_error_pattern(error_msg)
if pattern_info:
fix_hint = pattern_info["fix"]
# Try auto-replace
if "auto_replace" in pattern_info:
# Read current code
read_result = tool_read_file(repo_id, "app.py")
if read_result.startswith("OK"):
current_code = read_result.split("\n", 1)[1] if "\n" in read_result else ""
fixed_code, replaced = auto_fix_code(current_code, error_key, pattern_info)
if replaced:
# Validate and upload fixed code
val_result = tool_validate_code(fixed_code)
if not val_result.startswith("ERROR"):
upload_result = tool_upload_file(repo_id, "app.py", fixed_code)
if upload_result.startswith("OK"):
# Restart
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
try:
api.restart_space(repo_id=repo_id)
except:
pass
return f"OK: Auto-fixed! Applied: {fix_hint}. Space restarted."
return f"ERROR: Fix found but upload failed: {upload_result}"
return f"ERROR: Fix found but validation failed: {val_result}"
return f"OK: Found issue: {fix_hint}. Could not auto-fix - manual fix needed."
return f"OK: Unknown error pattern. Error: {error_msg[:500]}. Needs manual investigation."
except Exception as e:
return f"ERROR: {e}"
def tool_detect_project(user_message: str) -> str:
"""Detect which pre-built template matches the user's request"""
lower = user_message.lower()
matches = []
for key, tmpl in PROJECT_TEMPLATES.items():
for kw in tmpl["keywords"]:
if kw in lower:
matches.append(f"{key}: {tmpl['name']} (repo: {HF_USERNAME}/{tmpl['repo_suffix']})")
break
if matches:
return f"OK: Found matching templates:\n" + "\n".join(f" - {m}" for m in matches)
return f"OK: No template match found. Will generate custom code. Available templates: {', '.join(PROJECT_TEMPLATES.keys())}"
# ---- 4 NEW TOOLS ----
def tool_duplicate_space(repo_id: str, new_repo_id: str) -> str:
"""Duplicate a Hugging Face Space to a new repo"""
try:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
api.duplicate_space(from_id=repo_id, to_id=new_repo_id, exist_ok=True)
space_url = f"https://huggingface.co/spaces/{new_repo_id}"
return f"OK: Space duplicated from {repo_id} to {new_repo_id}! URL: {space_url}"
except Exception as e:
return f"ERROR: {e}"
def tool_generate_readme(repo_id: str, description: str) -> str:
"""Generate and upload a README.md to a Space"""
try:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
readme_content = f"""---
title: {repo_id.split('/')[-1].replace('-', ' ').title()}
emoji: ๐
colorFrom: blue
colorTo: purple
sdk: gradio
sdk_version: 5.0.0
app_file: app.py
pinned: false
---
# {repo_id.split('/')[-1].replace('-', ' ').title()}
{description}
Built with [AI Coding Agent Crew](https://huggingface.co/spaces/dubberkuro/ai-coding-crew) ๐ง
## Features
- Easy to use interface
- Powered by AI
- 100% Free
## Usage
Just enter your input and click the button!
"""
with tempfile.NamedTemporaryFile(mode="w", suffix="_readme.md", delete=False) as f:
f.write(readme_content)
tmp = f.name
api.upload_file(
path_or_fileobj=tmp, path_in_repo="README.md",
repo_id=repo_id, repo_type="space",
commit_message="Auto-generate README"
)
os.unlink(tmp)
return f"OK: README generated and uploaded to {repo_id}"
except Exception as e:
return f"ERROR: {e}"
def tool_test_space_url(url: str) -> str:
"""Test if a URL is reachable and return status code"""
try:
resp = req_lib.get(url, timeout=20, allow_redirects=True)
return f"OK: URL reachable! Status={resp.status_code}, Size={len(resp.content)} bytes, URL={url}"
except req_lib.Timeout:
return f"ERROR: URL timeout after 20s. URL={url}"
except req_lib.ConnectionError:
return f"ERROR: Connection refused. URL={url}"
except Exception as e:
return f"ERROR: {e}"
def tool_get_space_info(repo_id: str) -> str:
"""Get detailed space info (likes, downloads, etc.)"""
try:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
info = api.space_info(repo_id=repo_id)
stage = info.runtime.stage if info.runtime else "UNKNOWN"
hardware = info.runtime.hardware if info.runtime else "unknown"
# Try to get additional info from API
headers = {"Authorization": f"Bearer {HF_TOKEN}"}
resp = req_lib.get(f"https://huggingface.co/api/spaces/{repo_id}", headers=headers, timeout=15)
extra = ""
if resp.status_code == 200:
data = resp.json()
likes = data.get("likes", 0)
downloads = data.get("downloads", 0)
tags = data.get("tags", [])
sdk = data.get("sdk", "unknown")
extra = f", Likes={likes}, Downloads={downloads}, SDK={sdk}, Tags={tags[:5]}"
space_url = f"https://huggingface.co/spaces/{repo_id}"
return f"OK: Repo={repo_id}, Stage={stage}, Hardware={hardware}{extra}, URL={space_url}"
except Exception as e:
return f"ERROR: {e}"
# ---- 8 NEW TOOLS (MEGA EDITION) ----
def tool_update_space_settings(repo_id: str, hardware: str = "cpu-basic", private: bool = False) -> str:
"""Update space hardware and visibility settings"""
try:
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
kwargs = {"repo_id": repo_id}
if hardware: kwargs["hardware"] = hardware
if private: kwargs["private"] = True
api.update_repo_settings(**kwargs, repo_type="space")
return f"OK: Space '{repo_id}' settings updated (hardware={hardware}, private={private})"
except Exception as e:
return f"ERROR: {e}"
def tool_scrape_webpage(url: str) -> str:
"""Scrape text content from a webpage URL"""
try:
headers = {"User-Agent": "Mozilla/5.0"}
resp = req_lib.get(url, headers=headers, timeout=20)
if resp.status_code == 200:
text = re.sub(r'', '', resp.text, flags=re.DOTALL)
text = re.sub(r'', '', text, flags=re.DOTALL)
text = re.sub(r'<[^>]+>', ' ', text)
text = re.sub(r'\s+', ' ', text).strip()
return f"OK: Scraped {len(text)} chars from {url}\n{text[:5000]}"
return f"ERROR: HTTP {resp.status_code}"
except Exception as e:
return f"ERROR: {e}"
def tool_analyze_code(code: str) -> str:
"""Analyze code for issues, complexity, and patterns"""
if not code or not code.strip():
return "ERROR: Empty code"
lines = code.count('\n') + 1
chars = len(code)
issues = []
if lines > 500: issues.append("Large file (500+ lines)")
if "import *" in code: issues.append("Wildcard import detected")
if "eval(" in code: issues.append("eval() usage - security risk")
if "exec(" in code: issues.append("exec() usage - security risk")
if code.count("def ") > 20: issues.append("Many functions (20+)")
try:
compile(code, 'analyze', 'exec')
syntax = "OK"
except SyntaxError as e:
syntax = f"ERROR at line {e.lineno}: {e.msg}"
issues.append(f"Syntax error: {e.msg}")
result = f"OK: Code Analysis\n Lines: {lines}, Chars: {chars}\n Syntax: {syntax}"
if issues:
result += f"\n Issues ({len(issues)}): " + "; ".join(issues)
else:
result += "\n No issues found"
return result
def tool_generate_api_code(name: str, endpoints: str) -> str:
"""Generate FastAPI code template for REST API"""
eps = [e.strip() for e in endpoints.split(",") if e.strip()]
code = f"""import gradio as gr
import json
# API: {name}
# Endpoints: {', '.join(eps)}
DATA = {{}}
"""
for ep in eps:
code += f"""def api_{ep.replace('/','_').replace('-','_').strip('_')}():
return json.dumps({{"endpoint": "{ep}", "data": DATA.get("{ep}", [])}})
"""
code += """with gr.Blocks(title="API: """ + name + """", theme=gr.themes.Soft()) as demo:
gr.Markdown(f"# API: """ + name + """")
gr.Markdown("Endpoints: """ + ', '.join(eps) + """")
gr.Textbox(label="API Status", value='{"status": "running"}', interactive=False)
demo.launch()
"""
return f"OK: Generated API code for '{name}' with {len(eps)} endpoints ({len(code)} chars)\n{code}"
def tool_batch_deploy(template_names: str, username: str) -> str:
"""Deploy multiple templates at once. template_names: comma-separated"""
names = [n.strip() for n in template_names.split(",") if n.strip()]
results = []
for name in names:
tmpl = PROJECT_TEMPLATES.get(name)
if not tmpl:
results.append(f"SKIP: '{name}' not found")
continue
repo_id = f"{username}/{tmpl['repo_suffix']}"
r = tool_deploy_template(name, repo_id)
results.append(f"{'OK' if r.startswith('OK') else 'FAIL'}: {name} -> {repo_id}")
return f"OK: Batch deploy ({len(names)} templates)\n" + "\n".join(results)
def tool_health_check(repo_id: str) -> str:
"""Comprehensive health check: status + HTTP + logs"""
results = []
# Status
s = tool_check_status(repo_id)
results.append(f"Status: {s}")
# HTTP
url = f"https://{repo_id.replace('/', '-')}.hf.space"
try:
r = req_lib.get(url, timeout=15)
results.append(f"HTTP: {r.status_code} ({len(r.content)} bytes)")
except:
results.append("HTTP: unreachable")
# Logs
l = tool_get_space_logs(repo_id)
results.append(f"Logs: {l[:200]}")
return f"OK: Health Check for {repo_id}\n" + "\n".join(results)
def tool_refactor_code(repo_id: str) -> str:
"""Read code from space, suggest refactoring improvements"""
read_result = tool_read_file(repo_id, "app.py")
if not read_result.startswith("OK"):
return f"ERROR: Cannot read app.py from {repo_id}"
code = read_result.split("\n", 1)[1] if "\n" in read_result else ""
analysis = tool_analyze_code(code)
return f"OK: Refactor Analysis for {repo_id}\n{analysis}\n\nCode length: {len(code)} chars, {code.count(chr(10))+1} lines"
def tool_create_env_file(repo_id: str, env_vars: str) -> str:
"""Create a .env file with environment variables. Format: KEY=VALUE,KEY2=VALUE2"""
lines = []
for pair in env_vars.split(","):
pair = pair.strip()
if "=" in pair:
lines.append(pair)
if not lines:
return "ERROR: No valid KEY=VALUE pairs found"
content_str = "\n".join(lines)
return tool_upload_file(repo_id, ".env", content_str)
# ============================================================
# TOOL REGISTRY
# ============================================================
TOOLS = {
"create_space": tool_create_space,
"upload_file": tool_upload_file,
"set_secret": tool_set_secret,
"list_files": tool_list_files,
"read_file": tool_read_file,
"delete_file": tool_delete_file,
"restart_space": tool_restart_space,
"check_status": tool_check_status,
"get_space_logs": tool_get_space_logs,
"web_search": tool_web_search,
"validate_code": tool_validate_code,
"wait_for_space": tool_wait_for_space,
"list_my_spaces": tool_list_my_spaces,
"delete_space": tool_delete_space,
"deploy_template": tool_deploy_template,
"verify_deployment": tool_verify_deployment,
"auto_fix_space": tool_auto_fix_space,
"detect_project": tool_detect_project,
# 4 new tools
"duplicate_space": tool_duplicate_space,
"generate_readme": tool_generate_readme,
"test_space_url": tool_test_space_url,
"get_space_info": tool_get_space_info,
# 8 new tools (MEGA)
"update_space_settings": tool_update_space_settings,
"scrape_webpage": tool_scrape_webpage,
"analyze_code": tool_analyze_code,
"generate_api_code": tool_generate_api_code,
"batch_deploy": tool_batch_deploy,
"health_check": tool_health_check,
"refactor_code": tool_refactor_code,
"create_env_file": tool_create_env_file,
}
# ============================================================
# OpenAI FUNCTION CALLING DEFINITIONS (30 TOOLS)
# ============================================================
TOOL_DEFS = [
{"type": "function", "function": {"name": "create_space", "description": "Create a new Hugging Face Space", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string", "description": "Space ID like 'username/space-name'"}, "sdk": {"type": "string", "enum": ["gradio", "streamlit", "docker", "static"], "description": "SDK type, default gradio"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "upload_file", "description": "Upload a file to a Space. Content must be COMPLETE file content.", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string", "description": "Space ID"}, "filename": {"type": "string", "description": "File name"}, "content": {"type": "string", "description": "Complete file content"}}, "required": ["repo_id", "filename", "content"]}}},
{"type": "function", "function": {"name": "set_secret", "description": "Set a secret/env var in a Space", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}, "key": {"type": "string"}, "value": {"type": "string"}}, "required": ["repo_id", "key", "value"]}}},
{"type": "function", "function": {"name": "list_files", "description": "List all files in a Space", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "read_file", "description": "Read a file from a Space", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}, "filename": {"type": "string"}}, "required": ["repo_id", "filename"]}}},
{"type": "function", "function": {"name": "delete_file", "description": "Delete a file from a Space", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}, "filename": {"type": "string"}}, "required": ["repo_id", "filename"]}}},
{"type": "function", "function": {"name": "restart_space", "description": "Restart a Space after deploying", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "check_status", "description": "Check Space status and HTTP", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "get_space_logs", "description": "Get runtime error logs from a Space", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "web_search", "description": "Search the web for docs/examples", "parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}},
{"type": "function", "function": {"name": "validate_code", "description": "Validate Python code BEFORE uploading. Always use before upload_file with .py files.", "parameters": {"type": "object", "properties": {"code": {"type": "string", "description": "Python code to validate"}, "filename": {"type": "string", "description": "Filename", "default": "app.py"}}, "required": ["code"]}}},
{"type": "function", "function": {"name": "wait_for_space", "description": "Wait for a Space to start running. Use after restart_space.", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}, "timeout": {"type": "integer", "default": 120}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "list_my_spaces", "description": "List all your Hugging Face Spaces with their status", "parameters": {"type": "object", "properties": {}}}},
{"type": "function", "function": {"name": "delete_space", "description": "Delete a Space permanently", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "deploy_template", "description": "Deploy a pre-built template instantly! Use detect_project first to find the right template. This deploys 100% working code - MUCH faster and more reliable than writing from scratch.", "parameters": {"type": "object", "properties": {"template_name": {"type": "string", "description": "Template name: video_downloader, image_generator, chatbot, translator, qr_code, password_gen, text_to_speech, calculator, todo_app, url_shortener, pdf_summarizer, code_formatter, weather_app, markdown_editor, color_palette, json_tool, sentiment_analyzer, speed_test"}, "repo_id": {"type": "string", "description": "Target Space ID like 'username/space-name'"}}, "required": ["template_name", "repo_id"]}}},
{"type": "function", "function": {"name": "verify_deployment", "description": "Verify a Space is actually working by making an HTTP request to it", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "auto_fix_space", "description": "Auto-fix a Space with RUNTIME_ERROR using smart error pattern matching. Tries to automatically fix common Gradio errors.", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "detect_project", "description": "Detect which pre-built template matches the user's request. ALWAYS call this FIRST when user asks to build something.", "parameters": {"type": "object", "properties": {"user_message": {"type": "string", "description": "User's original message"}}, "required": ["user_message"]}}},
# 4 new tool defs
{"type": "function", "function": {"name": "duplicate_space", "description": "Duplicate an existing Hugging Face Space to a new repo ID. Useful for forking or cloning spaces.", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string", "description": "Source Space ID to duplicate from"}, "new_repo_id": {"type": "string", "description": "New Space ID for the duplicate"}}, "required": ["repo_id", "new_repo_id"]}}},
{"type": "function", "function": {"name": "generate_readme", "description": "Generate and upload a professional README.md to a Space. Use after successful deployment.", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string", "description": "Space ID"}, "description": {"type": "string", "description": "Description of the Space/app"}}, "required": ["repo_id", "description"]}}},
{"type": "function", "function": {"name": "test_space_url", "description": "Test if a URL is reachable and return status code. Useful for checking if a deployed Space is accessible.", "parameters": {"type": "object", "properties": {"url": {"type": "string", "description": "Full URL to test"}}, "required": ["url"]}}},
{"type": "function", "function": {"name": "get_space_info", "description": "Get detailed space info including likes, downloads, SDK version, tags, hardware, and runtime status.", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string", "description": "Space ID"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "update_space_settings", "description": "Update space hardware or visibility settings. Use for upgrading hardware or making spaces private.", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string"}, "hardware": {"type": "string", "enum": ["cpu-basic", "cpu-upgrade", "t4-small", "t4-medium", "a10g-small", "a10g-large", "a100-large"], "description": "Hardware tier"}, "private": {"type": "boolean", "description": "Make space private"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "scrape_webpage", "description": "Scrape and extract text content from any webpage URL. Returns cleaned text.", "parameters": {"type": "object", "properties": {"url": {"type": "string", "description": "Webpage URL to scrape"}}, "required": ["url"]}}},
{"type": "function", "function": {"name": "analyze_code", "description": "Analyze code for issues, complexity, security risks, and patterns. Returns detailed analysis.", "parameters": {"type": "object", "properties": {"code": {"type": "string", "description": "Python code to analyze"}}, "required": ["code"]}}},
{"type": "function", "function": {"name": "generate_api_code", "description": "Generate FastAPI/Gradio REST API code template for given endpoints.", "parameters": {"type": "object", "properties": {"name": {"type": "string", "description": "API name"}, "endpoints": {"type": "string", "description": "Comma-separated endpoint names"}}, "required": ["name", "endpoints"]}}},
{"type": "function", "function": {"name": "batch_deploy", "description": "Deploy multiple templates at once. Fast way to create multiple spaces.", "parameters": {"type": "object", "properties": {"template_names": {"type": "string", "description": "Comma-separated template names"}, "username": {"type": "string", "description": "HF username"}}, "required": ["template_names", "username"]}}},
{"type": "function", "function": {"name": "health_check", "description": "Comprehensive space health check: status + HTTP response + error logs. Full diagnostic.", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string", "description": "Space ID"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "refactor_code", "description": "Analyze code in a Space and suggest refactoring improvements.", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string", "description": "Space ID"}}, "required": ["repo_id"]}}},
{"type": "function", "function": {"name": "create_env_file", "description": "Create .env file with environment variables for a Space.", "parameters": {"type": "object", "properties": {"repo_id": {"type": "string", "description": "Space ID"}, "env_vars": {"type": "string", "description": "KEY=VALUE pairs separated by commas"}}, "required": ["repo_id", "env_vars"]}}},
]
# ============================================================
# SYSTEM PROMPT
# ============================================================
SYSTEM_PROMPT = f"""You are the AI Coding Agent Crew MEGA with 20 specialized agents.
Username: {HF_USERNAME} | Repo: {HF_USERNAME}/space-name
AGENTS: Planner,Architect,Coder,Designer,Reviewer,Tester,Optimizer,Security,Deployer,Debugger,Documenter,Manager,DevOps,Analyst,QA,Integrator,Refactorer,Monitor,Scraper,APIBuilder
WORKFLOW:
1. detect_project - ALWAYS call this FIRST to find matching template
2. deploy_template - Deploy instantly if template found (MUCH faster than writing code)
3. wait_for_space - Wait for space to start running
4. verify_deployment - Check space is actually live via HTTP
5. health_check - Comprehensive health check
6. If RUNTIME_ERROR: auto_fix_space -> get_space_logs -> read_file -> fix code -> upload_file -> restart_space -> wait_for_space -> verify_deployment
7. generate_readme - Add professional README after successful deploy
TEMPLATES: video_downloader,image_generator,chatbot,translator,qr_code,password_gen,text_to_speech,calculator,todo_app,url_shortener,pdf_summarizer,code_formatter,weather_app,markdown_editor,color_palette,json_tool,sentiment_analyzer,speed_test
RULES:
- NEVER ask the user questions - JUST DO IT autonomously
- ALWAYS detect_project first, then deploy_template if match found
- ALWAYS validate_code before upload_file with .py files
- ALWAYS wait_for_space after restart_space, then verify_deployment
- ALWAYS auto_fix_space on RUNTIME_ERROR, then get logs and fix manually if needed
- ALWAYS health_check after deployment
- ALWAYS generate_readme after successful deploy
- Use batch_deploy for multiple deployments
- Use scrape_webpage for research, analyze_code for code review
- Write COMPLETE code, NO placeholders, NO incomplete functions
- ALWAYS reply in the user's language (Myanmar/English/etc)
- If user says "fix it" or "update", refer to the last deployed space
- Gradio 5.x: messages format is default (dict with role/content), css in gr.Blocks(), import requests as req_lib
- Space URL: https://huggingface.co/spaces/{{repo_id}}
- Direct URL: https://{{repo_id.replace('/', '-')}}.hf.space"""
# ============================================================
# AI CALLING WITH RETRY
# ============================================================
def call_ai(messages, retries=4, model="openai"):
"""Call Pollinations AI with robust retry and fallback - supports multiple free models"""
# Trim messages to prevent oversized payloads
trimmed = _trim_messages(messages)
for attempt in range(retries + 1):
try:
# Exponential backoff for retries
if attempt > 0:
wait_time = min(3 * (2 ** (attempt - 1)), 30) # 3, 6, 12, 24, 30s
logger.info(f"Retry {attempt}/{retries}, waiting {wait_time}s...")
time.sleep(wait_time)
payload = {
"model": model,
"messages": trimmed,
"max_tokens": 8192,
"tools": TOOL_DEFS,
"tool_choice": "auto",
}
resp = req_lib.post(POLLINATIONS_URL, json=payload, timeout=180)
if resp.status_code == 200:
data = resp.json()
msg = data.get("choices", [{}])[0].get("message", {})
if msg.get("content") or msg.get("tool_calls") or msg.get("reasoning"):
return msg
# Empty response, retry
logger.warning(f"Empty AI response (attempt {attempt+1})")
continue
elif resp.status_code in (502, 503, 504, 429):
# Server errors - always retry with backoff
logger.warning(f"AI {resp.status_code} (attempt {attempt+1}/{retries+1})")
continue
elif resp.status_code == 400:
# Bad request - try without tools as fallback
logger.warning(f"AI 400 - trying without tools")
try:
payload_no_tools = {
"model": model,
"messages": trimmed,
"max_tokens": 4096,
}
resp2 = req_lib.post(POLLINATIONS_URL, json=payload_no_tools, timeout=120)
if resp2.status_code == 200:
data2 = resp2.json()
return data2.get("choices", [{}])[0].get("message", {})
except:
pass
return {"content": f"AI request error. Please try again."}
else:
logger.warning(f"AI status {resp.status_code} (attempt {attempt+1})")
if attempt < retries:
continue
return {"content": f"AI API error ({resp.status_code}). Please try again."}
except req_lib.Timeout:
logger.warning(f"AI timeout (attempt {attempt+1})")
if attempt < retries:
continue
# Fallback: try with smaller payload
try:
payload_small = {
"model": model,
"messages": trimmed[-4:], # Last 4 messages only
"max_tokens": 2048,
}
resp3 = req_lib.post(POLLINATIONS_URL, json=payload_small, timeout=60)
if resp3.status_code == 200:
data3 = resp3.json()
return data3.get("choices", [{}])[0].get("message", {})
except:
pass
return {"content": "AI request timed out. Please try again."}
except Exception as e:
logger.error(f"call_ai error: {e}")
if attempt < retries:
continue
return {"content": f"AI error. Please try again."}
return {"content": "AI แแแฏแกแแฏแถแธแแแผแฏแแญแฏแแบแแซแ แแแแผแแบแ
แแบแธแแซแ"}
def _trim_messages(messages):
"""Trim message history to prevent oversized payloads"""
if not messages:
return messages
MAX_CHARS = 30000
result = []
system_msgs = [m for m in messages if m.get("role") == "system"]
other_msgs = [m for m in messages if m.get("role") != "system"]
total = sum(len(json.dumps(m)) for m in system_msgs)
# Add messages from most recent, keeping under limit
for msg in reversed(other_msgs):
msg_size = len(json.dumps(msg))
if total + msg_size > MAX_CHARS:
break
result.insert(0, msg)
total += msg_size
return system_msgs + result
def get_text_content(msg):
content = msg.get("content")
if content and isinstance(content, str) and content.strip():
return content
reasoning = msg.get("reasoning")
if reasoning and isinstance(reasoning, str) and reasoning.strip():
return reasoning
return ""
# ============================================================
# TOOL CALL EXTRACTION
# ============================================================
def extract_tool_call(msg):
tool_calls = msg.get("tool_calls", [])
if tool_calls:
tc = tool_calls[0]
fn = tc.get("function", {})
name = fn.get("name", "")
args_str = fn.get("arguments", "{}")
try:
args = json.loads(args_str)
return {"tool": name, "args": args}
except json.JSONDecodeError:
try:
fixed = _fix_json_strings(args_str)
args = json.loads(fixed)
for k, v in args.items():
if isinstance(v, str):
args[k] = v.replace('\\n', '\n').replace('\\t', '\t')
return {"tool": name, "args": args}
except:
pass
content = get_text_content(msg)
if not content:
return None
for block in re.findall(r'```json\s*\n(.*?)\n```', content, re.DOTALL):
try:
data = json.loads(block.strip())
if isinstance(data, dict) and "tool" in data and data["tool"] in TOOLS:
return {"tool": data["tool"], "args": data.get("args", {})}
except:
pass
for tm in re.finditer(r'"tool"\s*:\s*"(\w+)"', content):
tool_name = tm.group(1)
if tool_name not in TOOLS:
continue
start = content.rfind('{', 0, tm.start())
if start == -1: continue
depth = 0; end = -1
for i in range(start, min(start + 100000, len(content))):
if content[i] == '{': depth += 1
elif content[i] == '}':
depth -= 1
if depth == 0: end = i + 1; break
if end == -1: continue
json_str = content[start:end]
try:
data = json.loads(json_str)
if "tool" in data:
return {"tool": data["tool"], "args": data.get("args", {})}
except:
try:
fixed = _fix_json_strings(json_str)
data = json.loads(fixed)
if "tool" in data:
args = data.get("args", {})
for k, v in args.items():
if isinstance(v, str):
args[k] = v.replace('\\n', '\n').replace('\\t', '\t')
return {"tool": data["tool"], "args": args}
except:
pass
return None
def _fix_json_strings(json_str):
result = []; in_string = False; escape_next = False
for ch in json_str:
if escape_next: result.append(ch); escape_next = False; continue
if ch == '\\' and in_string: result.append(ch); escape_next = True; continue
if ch == '"': in_string = not in_string; result.append(ch); continue
if in_string:
if ch == '\n': result.append('\\n'); continue
elif ch == '\t': result.append('\\t'); continue
elif ch == '\r': result.append('\\r'); continue
result.append(ch)
return ''.join(result)
def execute_tool(tool_call):
name = tool_call.get("tool", "")
args = tool_call.get("args", {})
if name not in TOOLS:
return f"ERROR: Unknown tool '{name}'. Available: {list(TOOLS.keys())}"
fn = TOOLS[name]
try:
result = fn(**args)
logger.info(f"Tool {name}: {result[:200]}")
return result
except Exception as e:
logger.error(f"Tool {name} error: {e}")
return f"ERROR: {e}"
# ============================================================
# HISTORY HELPERS
# ============================================================
def history_to_messages(history):
messages = []
if not history: return messages
for item in history:
# Handle Gradio 6.x messages format (dict with role/content)
if isinstance(item, dict) and "role" in item:
role = item.get("role", "")
if role not in ("user", "assistant"): continue
content = item.get("content", "")
if isinstance(content, list):
texts = []
for sub in content:
if isinstance(sub, dict) and sub.get("type") == "text": texts.append(sub.get("text", ""))
elif isinstance(sub, str): texts.append(sub)
content = " ".join(texts)
content = str(content or "").strip()
if not content: continue
if role == "assistant": content = _clean_bot_msg(content)
if content: messages.append({"role": role, "content": content})
# Handle legacy tuple format (user_msg, bot_msg)
elif isinstance(item, (list, tuple)) and len(item) == 2:
user_msg, bot_msg = item
if user_msg and str(user_msg).strip():
messages.append({"role": "user", "content": str(user_msg).strip()})
if bot_msg and str(bot_msg).strip():
cleaned = _clean_bot_msg(str(bot_msg).strip())
if cleaned:
messages.append({"role": "assistant", "content": cleaned})
return messages
def _clean_bot_msg(text):
"""Clean bot messages before sending to AI context - remove UI elements"""
text = re.sub(r'แแฏแแบแแฑแฌแแบแแฑแแซแแผแฎ', '', text)
text = re.sub(r'แแฏแแบแแฑแฌแแบแแปแแบแแปแฌแธ:.*?(?=\n\n|\Z)', '', text, flags=re.DOTALL)
text = re.sub(r'Step \d+/\d+.*?\n', '', text)
text = re.sub(r'Step \d+:.*?\n', '', text)
# Remove markdown formatting
text = re.sub(r'#{1,3}\s*', '', text)
text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text)
text = re.sub(r'\*([^*]+)\*', r'\1', text)
text = re.sub(r'- (โ
|๐)\s*\*\*Step \d+:\*\*', 'Step:', text)
text = re.sub(r'[\U0001F300-\U0001F9FF\U00002702-\U000027B0\U0000FE00-\U0000FE0F\U0001F900-\U0001FAFF\U00002600-\U000026FF]', '', text)
text = re.sub(r'\[.*?\]\s*', '', text)
text = re.sub(r'\n{3,}', '\n\n', text)
return text.strip()
# ============================================================
# AUTONOMOUS AGENT LOOP (12-AGENT UPGRADE)
# ============================================================
def run_agent(user_message, history, ai_model="openai"):
"""Autonomous agent loop - yields updated history in Gradio messages format"""
# Ensure history is in proper format
if history is None:
history = []
# Add user message to history
history.append({"role": "user", "content": user_message})
# Build AI context from history
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
messages.extend(history_to_messages(history))
steps_done = []
current_crew = "planner"
deployed_repo = None
fix_count = 0
# Helper to update the last assistant message in history
def _update_assistant(text):
# Find last assistant message or append new one
for i in range(len(history) - 1, -1, -1):
if isinstance(history[i], dict) and history[i].get("role") == "assistant":
history[i] = {"role": "assistant", "content": text}
return
history.append({"role": "assistant", "content": text})
def _add_assistant(text):
history.append({"role": "assistant", "content": text})
# Initial thinking message
progress_text = _format_progress(steps_done, current_crew, "๐ญ แ
แแบแธแ
แฌแธแแฑแแซ...")
_add_assistant(progress_text)
yield list(history)
for step in range(MAX_STEPS):
ai_msg = call_ai(messages, model=ai_model)
text_content = get_text_content(ai_msg)
tool_call = extract_tool_call(ai_msg)
current_crew = _detect_crew_member(tool_call, steps_done, current_crew)
if not tool_call:
if text_content and _is_planning_text(text_content):
messages.append({"role": "assistant", "content": text_content})
messages.append({"role": "user", "content": "Execute now by calling the appropriate tool. Start with detect_project to find a template, then deploy_template for instant deployment."})
thinking = text_content[:150].strip()
progress_text = _format_progress(steps_done, current_crew, f"๐ญ {thinking}...")
_update_assistant(progress_text)
yield list(history)
continue
if text_content:
final_text = _format_final(steps_done, current_crew, text_content)
else:
final_text = _format_final(steps_done, current_crew, "แแฏแแบแแฑแฌแแบแแปแแบ แแผแฎแธแแผแฑแฌแแบแแซแแผแฎแ")
_update_assistant(final_text)
yield list(history)
return
tool_name = tool_call["tool"]
tool_args = tool_call["args"]
result = execute_tool(tool_call)
step_desc = _describe_step(tool_name, tool_args, result)
steps_done.append(step_desc)
SESSION_MEMORY["total_steps"] += 1
logger.info(f"Step {step+1}/{MAX_STEPS} [{current_crew}]: {step_desc}")
if tool_name == "create_space" and not result.startswith("ERROR"):
deployed_repo = tool_args.get("repo_id", "")
if deployed_repo not in SESSION_MEMORY["spaces_created"]:
SESSION_MEMORY["spaces_created"].append(deployed_repo)
if tool_name == "deploy_template" and not result.startswith("ERROR"):
deployed_repo = tool_args.get("repo_id", "")
if deployed_repo not in SESSION_MEMORY["spaces_created"]:
SESSION_MEMORY["spaces_created"].append(deployed_repo)
progress_text = _format_progress(steps_done, current_crew, f"โณ Step {step+1}/{MAX_STEPS} แแฏแแบแแฑแฌแแบแแฑแแฒ...")
_update_assistant(progress_text)
yield list(history)
if text_content and not text_content.startswith("{"):
messages.append({"role": "assistant", "content": text_content})
else:
messages.append({"role": "assistant", "content": f"Calling {tool_name}"})
if result.startswith("ERROR"):
extra = ""
if deployed_repo:
extra = f"\n\nTry: auto_fix_space(repo_id='{deployed_repo}') for smart auto-fix, or get_space_logs(repo_id='{deployed_repo}') to see error details."
messages.append({"role": "user", "content": f"โ Tool FAILED: {result}{extra}\n\nAnalyze the error and try a different approach. If it's a Space error, use auto_fix_space first."})
fix_count += 1
else:
done = _check_task_complete(tool_name, result, steps_done, deployed_repo)
if done:
# Final verification
if deployed_repo:
verify_result = tool_verify_deployment(deployed_repo)
if "LIVE" in verify_result:
steps_done.append("โ
Space แกแแพแแบแแแแบ แกแแฏแแบแแฏแแบแแฑแแซแแผแฎ!")
progress_text = _format_progress(steps_done, "deployer", "โ
แกแแฏแแบแแฏแแบแแฑแแซแแผแฎ!")
_update_assistant(progress_text)
yield list(history)
messages.append({"role": "user", "content": f"โ
{verify_result}\n\nSpace is LIVE! Provide the final URL and summary. Do NOT call more tools."})
elif "RUNTIME_ERROR" in verify_result and fix_count < MAX_FIX_RETRIES:
fix_count += 1
steps_done.append("๐ง Error แแฝแฑแทแแพแญแ Auto-fix แแฏแแบแแฑ...")
progress_text = _format_progress(steps_done, "debugger", "๐ง Smart Auto-fix แแฏแแบแแฑ...")
_update_assistant(progress_text)
yield list(history)
# Try auto-fix
fix_result = tool_auto_fix_space(deployed_repo)
if fix_result.startswith("OK: Auto-fixed"):
steps_done.append("โ
Auto-fix แกแฑแฌแแบแแผแแบ!")
wait_result = tool_wait_for_space(deployed_repo)
if "RUNNING" in wait_result:
messages.append({"role": "user", "content": f"โ
Auto-fix succeeded! {wait_result}\n\nSpace is running. Provide the final URL. Do NOT call more tools."})
continue
# If auto-fix didn't fully work, let AI figure it out
messages.append({"role": "user", "content": f"Auto-fix attempted. Result: {fix_result}\n\nIf still broken, read logs and fix manually."})
continue
else:
messages.append({"role": "user", "content": f"โ
Result: {result}\n\nVerification: {verify_result}\n\nProvide the final summary and URL to the user."})
else:
messages.append({"role": "user", "content": f"โ
Result: {result}\n\nTask complete. Provide final summary. Do NOT call more tools."})
else:
next_hint = _get_next_hint(tool_name, steps_done, deployed_repo)
messages.append({"role": "user", "content": f"โ
Result: {result}\n\n{next_hint}"})
final_text = _format_final(steps_done, current_crew, "โ ๏ธ แกแแปแฌแธแแฏแถแธ แกแแแทแบแแฑแฌแแบแแซแแผแฎแ แแแบแแฏแแบแแญแฏแแซแ แแแบแแฑแธแแซแ")
_update_assistant(final_text)
yield list(history)
def _detect_crew_member(tool_call, steps_done, current):
"""Detect which crew member is active based on tool call - updated for 12 agents"""
if not tool_call: return current
tool_name = tool_call.get("tool", "")
# Planner: project detection, space creation, web search
if tool_name in ("create_space", "web_search", "list_files", "detect_project"): return "planner"
# Architect: space info, duplication
if tool_name in ("get_space_info", "duplicate_space"): return "architect"
# Coder: file upload (code files)
if tool_name == "upload_file": return "coder" if tool_call.get("args", {}).get("filename", "").endswith(".py") else "deployer"
# Designer: readme generation
if tool_name == "generate_readme": return "designer"
# Reviewer: validation, reading, logs
if tool_name in ("validate_code", "read_file", "get_space_logs", "verify_deployment"): return "reviewer"
# Tester: URL testing
if tool_name == "test_space_url": return "tester"
# Optimizer: check status
if tool_name == "check_status": return "optimizer"
# Security: secrets
if tool_name == "set_secret": return "security"
# Deployer: restart, deploy, wait
if tool_name in ("restart_space", "wait_for_space", "deploy_template", "delete_file"): return "deployer"
# Debugger: auto-fix
if tool_name in ("auto_fix_space",): return "debugger"
# Documenter: (covered by generate_readme -> designer)
# Manager: list spaces, delete space
if tool_name in ("list_my_spaces", "delete_space"): return "manager"
# DevOps: update settings, env files
if tool_name in ("update_space_settings", "create_env_file"): return "devops"
# Analyst: code analysis
if tool_name in ("analyze_code", "refactor_code"): return "analyst"
# QA: health check, verification
if tool_name in ("health_check",): return "qa"
# Integrator: API code generation
if tool_name in ("generate_api_code",): return "integrator"
# Refactorer: code refactoring
if tool_name in ("refactor_code",) and current == "analyst": return "refactorer"
# Monitor: batch deploy, check_status
if tool_name in ("batch_deploy",): return "monitor"
# Scraper: web scraping
if tool_name in ("scrape_webpage",): return "scraper"
# API Builder: API generation
if tool_name in ("generate_api_code",) and current == "integrator": return "api_builder"
return current
def _is_planning_text(text):
planning = ["i'll create", "i will create", "let me create", "i'll upload", "i will upload", "first, i'll", "first i will", "i need to", "i should", "let's start", "here's my plan"]
lower = text.lower()[:500]
return any(p in lower for p in planning)
def _describe_step(tool_name, tool_args, result):
crew_map = {"create_space": "๐ง ", "web_search": "๐ง ", "list_files": "๐ง ", "detect_project": "๐ง ",
"get_space_info": "๐๏ธ", "duplicate_space": "๐๏ธ",
"upload_file": "๐ป",
"generate_readme": "๐จ",
"validate_code": "๐", "read_file": "๐", "get_space_logs": "๐", "verify_deployment": "๐",
"test_space_url": "๐งช",
"check_status": "โก",
"set_secret": "๐",
"restart_space": "๐", "wait_for_space": "๐", "deploy_template": "๐", "delete_file": "๐",
"auto_fix_space": "๐ง",
"list_my_spaces": "๐", "delete_space": "๐"}
emoji = crew_map.get(tool_name, "โ๏ธ")
if tool_name == "create_space":
return f"{emoji} Space แแฑแฌแแบแแแบ: {tool_args.get('repo_id', '?')} ({tool_args.get('sdk', 'gradio')})"
elif tool_name == "upload_file":
return f"{emoji} File แแแบแแแบ: {tool_args.get('filename', '?')} ({len(tool_args.get('content', ''))} chars)"
elif tool_name == "deploy_template":
tmpl_name = tool_args.get('template_name', '?')
status = "โ
" if "OK" in result else "โ"
return f"{emoji} Template Deploy: {tmpl_name} โ {tool_args.get('repo_id', '?')} {status}"
elif tool_name == "detect_project":
found = "Template แแฝแฑแท" if "Found" in result else "Custom แแฏแแบแแแบ"
return f"{emoji} Project แ
แ
แบแแฑแธแแแบ: {found}"
elif tool_name == "auto_fix_space":
status = "โ
แกแฑแฌแแบแแผแแบ" if "Auto-fixed" in result else "โ ๏ธ แแญแฏแกแแบ"
return f"{emoji} Auto-fix แแฏแแบแแแบ: {status}"
elif tool_name == "verify_deployment":
status = "โ
LIVE!" if "LIVE" in result else "โ ๏ธ แ
แ
แบแแฑแธ"
return f"{emoji} Deploy แ
แ
แบแแฑแธแแแบ: {status}"
elif tool_name == "validate_code":
lines = tool_args.get('code', '').count('\n') + 1
status = "โ
" if "OK" in result else "โ"
return f"{emoji} Code แ
แ
แบแแฑแธแแแบ: {tool_args.get('filename', 'app.py')} ({lines} lines) {status}"
elif tool_name == "list_my_spaces":
return f"{emoji} Space แแฝแฑ แแผแแทแบแแแบ"
elif tool_name == "wait_for_space":
status = "โ
RUNNING!" if "RUNNING" in result else "โณ"
return f"{emoji} Space แ
แฑแฌแแทแบแแแบ: {tool_args.get('repo_id', '?')} {status}"
elif tool_name == "restart_space":
return f"{emoji} Space restart แแฏแแบแแแบ: {tool_args.get('repo_id', '?')}"
elif tool_name == "check_status":
return f"{emoji} Status แ
แ
แบแแแบ: {tool_args.get('repo_id', '?')}"
elif tool_name == "get_space_logs":
return f"{emoji} Logs แแแบแแแบ: {tool_args.get('repo_id', '?')}"
elif tool_name == "set_secret":
return f"{emoji} Secret แแแบแแพแแบแแแบ: {tool_args.get('key', '?')}"
elif tool_name == "web_search":
return f"{emoji} Web แแพแฌแแแบ: {tool_args.get('query', '?')[:50]}"
elif tool_name == "delete_space":
return f"{emoji} Space แแปแแบแแแบ: {tool_args.get('repo_id', '?')}"
elif tool_name == "duplicate_space":
return f"{emoji} Space Duplicate แแฏแแบแแแบ: {tool_args.get('repo_id', '?')} โ {tool_args.get('new_repo_id', '?')}"
elif tool_name == "generate_readme":
return f"{emoji} README แแฏแแบแแแบ: {tool_args.get('repo_id', '?')}"
elif tool_name == "test_space_url":
status = "โ
" if "OK" in result else "โ"
return f"{emoji} URL แ
แแบแธแแแบแแแบ: {status}"
elif tool_name == "get_space_info":
return f"{emoji} Space Info แแผแแทแบแแแบ: {tool_args.get('repo_id', '?')}"
elif tool_name == "update_space_settings":
return f"โ๏ธ Settings แแผแแบแแแบ: {tool_args.get('repo_id', '?')}"
elif tool_name == "scrape_webpage":
return f"๐ Webpage แแฐแแแบ: {tool_args.get('url', '?')[:50]}"
elif tool_name == "analyze_code":
return f"๐ Code แแฝแฒแแผแแบแธแแแบ"
elif tool_name == "generate_api_code":
return f"๐ ๏ธ API Code แแฏแแบแแแบ: {tool_args.get('name', '?')}"
elif tool_name == "batch_deploy":
return f"๐ก Batch Deploy แแฏแแบแแแบ"
elif tool_name == "health_check":
return f"โ
Health Check แแฏแแบแแแบ: {tool_args.get('repo_id', '?')}"
elif tool_name == "refactor_code":
return f"โป๏ธ Code Refactor แแฏแแบแแแบ: {tool_args.get('repo_id', '?')}"
elif tool_name == "create_env_file":
return f"โ๏ธ Env File แแฏแแบแแแบ: {tool_args.get('repo_id', '?')}"
return f"{emoji} {tool_name}"
def _check_task_complete(tool_name, result, steps_done, deployed_repo):
deployed = any("Template Deploy" in s for s in steps_done)
uploaded_app = any("app.py" in s and "File แแแบแแแบ" in s for s in steps_done)
restarted = any("restart" in s.lower() for s in steps_done)
verified = any("Deploy แ
แ
แบแแฑแธแแแบ" in s or "LIVE" in s for s in steps_done)
if tool_name == "verify_deployment" and "LIVE" in result:
return True
if tool_name == "wait_for_space" and "RUNNING" in result:
return True
if tool_name == "deploy_template" and deployed:
return True
if tool_name == "check_status" and restarted and (uploaded_app or deployed):
return True
return False
def _get_next_hint(tool_name, steps_done, deployed_repo):
detected = any("Project แ
แ
แบแแฑแธแแแบ" in s for s in steps_done)
created = any("Space แแฑแฌแแบแแแบ" in s for s in steps_done)
deployed = any("Template Deploy" in s for s in steps_done)
uploaded_app = any("app.py" in s and "File แแแบแแแบ" in s for s in steps_done)
uploaded_req = any("requirements.txt" in s and "File แแแบแแแบ" in s for s in steps_done)
validated = any("Code แ
แ
แบแแฑแธแแแบ" in s for s in steps_done)
restarted = any("restart" in s.lower() for s in steps_done)
waited = any("Space แ
แฑแฌแแทแบแแแบ" in s for s in steps_done)
verified = any("Deploy แ
แ
แบแแฑแธแแแบ" in s for s in steps_done)
readme_generated = any("README แแฏแแบแแแบ" in s for s in steps_done)
if tool_name == "detect_project" and not deployed:
return "Template found! Now use deploy_template to deploy instantly, or create_space + upload_file for custom code."
if tool_name == "deploy_template":
return "Template deployed! Now wait_for_space() then verify_deployment(). After success, use generate_readme()."
if tool_name == "create_space" and not uploaded_app:
return "Space created! Now write the app code and upload it."
if tool_name == "validate_code" and not uploaded_app:
return "Code validated! Now upload it with upload_file()."
if tool_name == "upload_file" and uploaded_app and not uploaded_req:
return "app.py uploaded! Now upload requirements.txt too."
if tool_name == "upload_file" and uploaded_app and uploaded_req and not restarted:
return "All files uploaded! Now restart the Space."
if tool_name == "restart_space" and not waited:
return "Space restarted! Now wait_for_space() then verify_deployment()."
if tool_name == "wait_for_space":
return "Verify with verify_deployment() to confirm it's working, then generate_readme() and give user the URL."
if tool_name == "verify_deployment" and not readme_generated and deployed_repo:
return "Space verified! Now generate_readme() to add a professional README."
return "Continue with the next step."
def _format_progress(steps_done, crew_member, status):
crew_info = CREW.get(crew_member, CREW["planner"])
lines = [f"### {crew_info['emoji']} {crew_info['name']} แแฏแแบแแฑแฌแแบแแฑแแซแแผแฎ...\n"]
if steps_done:
lines.append("**๐ แแฏแแบแแฑแฌแแบแแปแแบแแปแฌแธ:**\n")
for i, s in enumerate(steps_done, 1):
icon = "โ
" if i < len(steps_done) else "๐"
lines.append(f"- {icon} **Step {i}:** {s}")
lines.append(f"\n*{status}*")
return "\n".join(lines)
def _format_final(steps_done, crew_member, final_text):
crew_info = CREW.get(crew_member, CREW["deployer"])
lines = []
if steps_done:
lines.append(f"### {crew_info['emoji']} แแฏแแบแแฑแฌแแบแแปแแบแแปแฌแธ [{crew_info['name']} Crew]\n")
for i, s in enumerate(steps_done, 1):
lines.append(f"- โ
**Step {i}:** {s}")
lines.append("")
lines.append(final_text)
return "\n".join(lines)
# ============================================================
# PRO AI CSS - GLASSMORPHISM + MODERN CHAT DESIGN
# ============================================================
GEMINI_CSS = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+Myanmar:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
:root {
--bg-primary: #0f0f11;
--bg-surface: #1a1a2e;
--bg-glass: rgba(255,255,255,0.04);
--bg-glass-hover: rgba(255,255,255,0.07);
--border-subtle: rgba(255,255,255,0.08);
--border-hover: rgba(255,255,255,0.14);
--accent-primary: #6c63ff;
--accent-secondary: #00d4aa;
--user-bubble-bg: rgba(108,99,255,0.15);
--user-bubble-border: rgba(108,99,255,0.25);
--bot-bubble-bg: rgba(255,255,255,0.03);
--bot-bubble-border: rgba(255,255,255,0.06);
--text-primary: #e8eaed;
--text-secondary: #9aa0a6;
--text-muted: #5f6368;
--font-main: 'Inter', 'Noto Sans Myanmar', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', 'Courier New', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg-primary) !important;
font-family: var(--font-main) !important;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
color: var(--text-primary);
}
/* ===== GRADIO CONTAINER ===== */
.gradio-container {
max-width: 860px !important;
margin: 0 auto !important;
font-family: var(--font-main) !important;
background: transparent !important;
padding: 0 16px !important;
}
/* ===== HEADER ===== */
.crew-header {
text-align: center;
padding: 28px 16px 8px;
}
.crew-header-row {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 14px;
margin-bottom: 6px;
}
.crew-logo {
width: 44px; height: 44px;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
border-radius: 14px;
display: flex; align-items: center; justify-content: center;
font-size: 22px; color: #fff;
box-shadow: 0 4px 20px rgba(108,99,255,0.3);
flex-shrink: 0;
}
.crew-title {
font-size: 28px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.5px;
}
.crew-subtitle {
color: var(--text-secondary);
font-size: 13px;
margin-top: 4px;
font-weight: 400;
}
/* ===== STATS BAR ===== */
.crew-stats {
display: flex;
justify-content: center;
align-items: center;
gap: 6px;
padding: 10px 0 4px;
flex-wrap: wrap;
font-size: 13px;
color: var(--text-muted);
}
.crew-stats .stat-sep {
color: var(--border-subtle);
margin: 0 2px;
}
.crew-stats .stat-val {
font-weight: 600;
color: var(--accent-primary);
}
.crew-stats .stat-free {
font-weight: 600;
color: var(--accent-secondary);
}
/* ===== CHATBOT ===== */
.chatbot {
background: var(--bg-glass) !important;
border: 1px solid var(--border-subtle) !important;
border-radius: 16px !important;
min-height: 500px !important;
}
/* User message bubble */
.chatbot .message.user {
background: var(--user-bubble-bg) !important;
border-radius: 18px 18px 4px 18px !important;
padding: 12px 18px !important;
margin-left: 40px !important;
border: 1px solid var(--user-bubble-border) !important;
}
/* Assistant message bubble */
.chatbot .message.assistant,
.chatbot .message.bot {
background: var(--bot-bubble-bg) !important;
border-radius: 18px 18px 18px 4px !important;
padding: 14px 18px !important;
margin-right: 40px !important;
border: 1px solid var(--bot-bubble-border) !important;
}
/* Message text */
.chatbot .message p,
.chatbot .message span,
.chatbot .message div {
font-size: 14.5px !important;
line-height: 1.7 !important;
color: var(--text-primary) !important;
font-family: var(--font-main) !important;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* Myanmar text readability */
.chatbot .message {
font-family: var(--font-main) !important;
font-size: 14.5px !important;
line-height: 1.85 !important;
letter-spacing: 0.1px;
}
/* Code blocks inside messages */
.chatbot .message pre {
background: #0d0d12 !important;
border: 1px solid var(--border-subtle) !important;
border-radius: 10px !important;
padding: 14px 16px !important;
font-size: 13px !important;
line-height: 1.6 !important;
font-family: var(--font-mono) !important;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
}
/* Inline code */
.chatbot .message code {
font-family: var(--font-mono) !important;
font-size: 13px !important;
background: rgba(108,99,255,0.12) !important;
padding: 2px 6px !important;
border-radius: 5px !important;
color: var(--accent-primary) !important;
}
/* Bold text */
.chatbot .message strong {
color: var(--text-primary) !important;
font-weight: 600 !important;
}
/* List items */
.chatbot .message li {
margin-left: 16px !important;
padding-left: 4px !important;
}
/* ===== QUICK START TEMPLATE CARDS ===== */
.quick-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
padding: 6px 0;
max-width: 100%;
}
.quick-card {
background: var(--bg-glass);
border: 1px solid var(--border-subtle);
border-radius: 12px;
padding: 12px 10px;
text-align: center;
transition: all 0.2s ease;
cursor: pointer;
user-select: none;
}
.quick-card:hover {
background: var(--bg-glass-hover);
border-color: var(--accent-primary);
transform: translateY(-1px);
}
.quick-card:active {
transform: translateY(0);
}
.quick-card .qc-icon {
font-size: 20px;
display: block;
margin-bottom: 4px;
}
.quick-card .qc-label {
color: var(--text-secondary);
font-size: 12px;
font-weight: 500;
line-height: 1.35;
display: block;
}
/* ===== INPUT AREA ===== */
.input-container,
.gr-input-container {
background: transparent !important;
border: none !important;
}
textarea {
font-size: 15px !important;
font-family: var(--font-main) !important;
color: var(--text-primary) !important;
background: transparent !important;
line-height: 1.5 !important;
padding: 12px 16px !important;
border: none !important;
resize: none !important;
}
textarea:focus {
outline: none !important;
box-shadow: none !important;
}
textarea::placeholder {
color: var(--text-muted) !important;
font-size: 15px !important;
}
.crew-input-row {
display: flex;
align-items: center;
gap: 8px;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: 24px;
padding: 6px 8px 6px 16px;
transition: border-color 0.2s ease;
}
.crew-input-row:focus-within {
border-color: var(--accent-primary);
}
/* ===== BUTTONS ===== */
button.primary-btn,
button.btn-primary,
.gr-button-primary,
button[variant="primary"] {
background: var(--accent-primary) !important;
border: none !important;
border-radius: 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
padding: 10px 20px !important;
min-height: 42px !important;
transition: all 0.2s ease !important;
color: #fff !important;
box-shadow: 0 2px 12px rgba(108,99,255,0.25) !important;
}
button.primary-btn:hover,
button.btn-primary:hover,
.gr-button-primary:hover {
background: #7b73ff !important;
box-shadow: 0 4px 20px rgba(108,99,255,0.35) !important;
transform: translateY(-1px) !important;
}
button.primary-btn:active,
button.btn-primary:active {
transform: translateY(0) !important;
box-shadow: 0 2px 8px rgba(108,99,255,0.2) !important;
}
/* Secondary/clear button */
.crew-clear-btn {
background: var(--bg-glass) !important;
border: 1px solid var(--border-subtle) !important;
border-radius: 20px !important;
color: var(--text-secondary) !important;
font-weight: 500 !important;
font-size: 13px !important;
padding: 8px 14px !important;
min-height: 42px !important;
transition: all 0.2s ease !important;
}
.crew-clear-btn:hover {
background: var(--bg-glass-hover) !important;
border-color: var(--border-hover) !important;
color: var(--text-primary) !important;
}
/* ===== EXAMPLES ROW ===== */
.examples {
background: transparent !important;
border: none !important;
border-radius: 0 !important;
padding: 0 !important;
margin-top: 8px !important;
}
.examples .example {
background: var(--bg-glass) !important;
border: 1px solid var(--border-subtle) !important;
border-radius: 12px !important;
color: var(--text-secondary) !important;
font-size: 13px !important;
padding: 8px 14px !important;
transition: all 0.2s ease !important;
cursor: pointer;
}
.examples .example:hover {
border-color: var(--accent-primary) !important;
background: rgba(108,99,255,0.08) !important;
color: var(--text-primary) !important;
}
/* ===== ACCORDION ===== */
.gr-accordion {
background: var(--bg-glass) !important;
border: 1px solid var(--border-subtle) !important;
border-radius: 14px !important;
overflow: hidden;
}
.gr-accordion:hover {
border-color: var(--border-hover) !important;
}
/* ===== DROPDOWN ===== */
.gr-dropdown {
background: var(--bg-surface) !important;
border: 1px solid var(--border-subtle) !important;
border-radius: 12px !important;
color: var(--text-primary) !important;
}
.gr-dropdown:hover,
.gr-dropdown:focus {
border-color: var(--accent-primary) !important;
}
/* ===== SCROLLBAR ===== */
::-webkit-scrollbar {
width: 4px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.10);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255,255,255,0.18);
}
/* ===== GLOBAL TEXT ===== */
.gradio-container p, .gradio-container li {
color: var(--text-primary);
font-size: 14px;
line-height: 1.6;
}
.gradio-container h1, .gradio-container h2, .gradio-container h3 {
color: var(--text-primary);
}
/* ===== FOOTER - ALWAYS HIDDEN ===== */
footer { display: none !important; }
/* ===== TEMPLATE GALLERY GRID ===== */
.tmpl-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
gap: 8px;
padding: 4px 0;
}
.tmpl-card {
background: var(--bg-glass);
border: 1px solid var(--border-subtle);
border-radius: 12px;
padding: 10px 12px;
transition: all 0.2s ease;
cursor: default;
}
.tmpl-card:hover {
background: var(--bg-glass-hover);
border-color: var(--border-hover);
transform: translateY(-1px);
}
.tmpl-card b { color: var(--accent-primary); font-size: 13px; }
.tmpl-card span { color: var(--text-muted); font-size: 11px; }
/* ===== RESPONSIVE ===== */
@media (max-width: 768px) {
.crew-title { font-size: 22px; }
.crew-logo { width: 36px; height: 36px; font-size: 18px; border-radius: 10px; }
.chatbot .message.user { margin-left: 12px !important; }
.chatbot .message.assistant,
.chatbot .message.bot { margin-right: 12px !important; }
.quick-grid { grid-template-columns: repeat(2, 1fr); }
.tmpl-grid { grid-template-columns: repeat(2, 1fr); }
.crew-stats { font-size: 12px; }
}
@media (max-width: 480px) {
.crew-title { font-size: 20px; }
.quick-grid { grid-template-columns: 1fr 1fr; gap: 6px; }
.quick-card { padding: 10px 8px; }
.quick-card .qc-icon { font-size: 18px; }
.quick-card .qc-label { font-size: 11px; }
.crew-header { padding: 20px 12px 6px; }
.crew-stats { gap: 4px; font-size: 11px; }
}
/* ===== TEXT INPUT / DROPDOWN CONTAINERS ===== */
.gr-input, .gr-text-input {
background: transparent !important;
border: none !important;
}
.gr-box {
border-radius: 12px !important;
}
label {
color: var(--text-secondary) !important;
font-size: 12px !important;
}
"""
# ============================================================
# GRADIO UI - CLEAN CHAT-STYLE LAYOUT
# ============================================================
with gr.Blocks(title="AI Coding Agent Crew") as demo:
# ---- Simple header ----
gr.HTML(f"""
""")
# ---- Quick-start template cards ----
gr.HTML("""
🚀
chatbot แแฑแฌแแบแแฑแธ
🎨
image generator แแฏแแบแแฑแธ
🎵
TTS app แแฑแฌแแบแแฑแธ
🔒
password gen แแฏแแบแแฑแธ
🌍
translator แแฑแฌแแบแแฑแธ
📊
my spaces แแผแแฑแธ
""")
# ---- Chatbot (main area) ----
chatbot = gr.Chatbot(
height=550,
placeholder="""
✦
แแฌแแฑแฌแแบแแปแแบแแซแแแฒ?
What would you like to build today?
App แแ
แบแแฏแแฑแฌแแบแแปแแบแแแบ แแผแฑแฌแแซ / Just describe what you want
""",
)
# ---- Input row ----
with gr.Row(equal_height=True):
with gr.Column(scale=8, min_width=300):
msg_input = gr.Textbox(
placeholder="แแฌแแฑแฌแแบแแปแแบแแฒ? / What would you like to build?...",
show_label=False,
container=False,
lines=1,
max_lines=4,
)
with gr.Column(scale=4, min_width=180):
with gr.Row(equal_height=True):
model_dropdown = gr.Dropdown(
choices=["openai", "mistral", "deepseek", "llama", "qwen"],
value="openai",
label="Model",
min_width=90,
scale=2,
)
send_btn = gr.Button("Send", variant="primary", min_width=70, scale=1)
clear_btn = gr.Button("Clear", elem_classes=["crew-clear-btn"], min_width=60, scale=1)
# ---- Quick examples row ----
gr.Examples(
examples=[
"chatbot แแฑแฌแแบแแฑแธ",
"image generator แแฏแแบแแฑแธ",
"text to speech app แแฑแฌแแบแแฑแธ",
"password generator แแฏแแบแแฑแธ",
"translator app แแฑแฌแแบแแฑแธ",
"my spaces แแฝแฑแแผแแฑแธ",
],
inputs=msg_input,
label="Quick Start",
)
# ---- Dashboard accordion ----
with gr.Accordion("Dashboard & Tools", open=False):
with gr.Row():
refresh_btn = gr.Button("Refresh Spaces", variant="primary")
export_btn = gr.Button("Export Chat")
spaces_output = gr.Textbox(label="Your Spaces", lines=8, interactive=False)
stats_output = gr.Textbox(label="Session Stats", interactive=False)
export_file = gr.File(label="Download")
# ---- Templates gallery accordion ----
with gr.Accordion("Template Gallery", open=False):
template_html = ''
for tkey, tmpl in PROJECT_TEMPLATES.items():
template_html += f'
{tmpl["name"]}
{tkey}
'
template_html += '
'
gr.HTML(template_html)
# ---- Event handlers ----
msg_input.submit(run_agent, [msg_input, chatbot, model_dropdown], [chatbot]).then(fn=lambda: "", outputs=msg_input)
send_btn.click(run_agent, [msg_input, chatbot, model_dropdown], [chatbot], api_name="send").then(fn=lambda: "", outputs=msg_input)
clear_btn.click(fn=lambda: ([], ""), inputs=None, outputs=[chatbot, msg_input])
def refresh_spaces():
result = tool_list_my_spaces()
stats = f"Spaces Created: {len(SESSION_MEMORY['spaces_created'])} | Total Steps: {SESSION_MEMORY['total_steps']} | Session: {int(time.time() - SESSION_MEMORY['start_time'])}s"
return result, stats
refresh_btn.click(fn=refresh_spaces, inputs=None, outputs=[spaces_output, stats_output])
def export_chat(history):
if not history:
return None
export_data = {"exported_at": time.strftime("%Y-%m-%d %H:%M:%S"), "messages": []}
for msg in history:
if isinstance(msg, dict):
export_data["messages"].append({"role": msg.get("role", ""), "content": msg.get("content", "")})
tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
json.dump(export_data, open(tmp.name, "w"), ensure_ascii=False, indent=2)
return tmp.name
export_btn.click(fn=export_chat, inputs=[chatbot], outputs=[export_file])
demo.launch(css=GEMINI_CSS, theme=gr.themes.Soft(primary_hue="blue", neutral_hue="slate", font=gr.themes.GoogleFont("Inter")))