""" 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"""
AI Coding Crew
Autonomous AI Agent Crew for Hugging Face Spaces
{len(CREW)} Agents | {len(TOOLS)} Tools | {len(PROJECT_TEMPLATES)} Templates | Free
""") # ---- 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")))