Spaces:
Sleeping
Sleeping
| """ | |
| 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 = "<div style='display:flex;gap:8px'>" | |
| for c in colors: | |
| html += f"<div style='width:80px;height:80px;background:{c};border-radius:12px;display:flex;align-items:flex-end;justify-content:center;padding:8px;font-size:11px;color:#fff;text-shadow:1px 1px #000'>{c}</div>" | |
| html += "</div>" | |
| 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'<script[^>]*>.*?</script>', '', resp.text, flags=re.DOTALL) | |
| text = re.sub(r'<style[^>]*>.*?</style>', '', 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""" | |
| <div class="crew-header"> | |
| <div class="crew-header-row"> | |
| <div class="crew-logo">✦</div> | |
| <span class="crew-title">AI Coding Crew</span> | |
| </div> | |
| <div class="crew-subtitle">Autonomous AI Agent Crew for Hugging Face Spaces</div> | |
| <div class="crew-stats"> | |
| <span class="stat-val">{len(CREW)}</span> Agents | |
| <span class="stat-sep">|</span> | |
| <span class="stat-val">{len(TOOLS)}</span> Tools | |
| <span class="stat-sep">|</span> | |
| <span class="stat-val">{len(PROJECT_TEMPLATES)}</span> Templates | |
| <span class="stat-sep">|</span> | |
| <span class="stat-free">Free</span> | |
| </div> | |
| </div> | |
| """) | |
| # ---- Quick-start template cards ---- | |
| gr.HTML(""" | |
| <div class="quick-grid"> | |
| <div class="quick-card" onclick="document.querySelector('textarea').value='chatbot ဆောက်ပေး'; document.querySelector('textarea').dispatchEvent(new Event('input',{bubbles:true}))"> | |
| <span class="qc-icon">🚀</span> | |
| <span class="qc-label">chatbot ဆောက်ပေး</span> | |
| </div> | |
| <div class="quick-card" onclick="document.querySelector('textarea').value='image generator လုပ်ပေး'; document.querySelector('textarea').dispatchEvent(new Event('input',{bubbles:true}))"> | |
| <span class="qc-icon">🎨</span> | |
| <span class="qc-label">image generator လုပ်ပေး</span> | |
| </div> | |
| <div class="quick-card" onclick="document.querySelector('textarea').value='text to speech app ဆောက်ပေး'; document.querySelector('textarea').dispatchEvent(new Event('input',{bubbles:true}))"> | |
| <span class="qc-icon">🎵</span> | |
| <span class="qc-label">TTS app ဆောက်ပေး</span> | |
| </div> | |
| <div class="quick-card" onclick="document.querySelector('textarea').value='password generator လုပ်ပေး'; document.querySelector('textarea').dispatchEvent(new Event('input',{bubbles:true}))"> | |
| <span class="qc-icon">🔒</span> | |
| <span class="qc-label">password gen လုပ်ပေး</span> | |
| </div> | |
| <div class="quick-card" onclick="document.querySelector('textarea').value='translator app ဆောက်ပေး'; document.querySelector('textarea').dispatchEvent(new Event('input',{bubbles:true}))"> | |
| <span class="qc-icon">🌍</span> | |
| <span class="qc-label">translator ဆောက်ပေး</span> | |
| </div> | |
| <div class="quick-card" onclick="document.querySelector('textarea').value='my spaces တွေပြပေး'; document.querySelector('textarea').dispatchEvent(new Event('input',{bubbles:true}))"> | |
| <span class="qc-icon">📊</span> | |
| <span class="qc-label">my spaces ပြပေး</span> | |
| </div> | |
| </div> | |
| """) | |
| # ---- Chatbot (main area) ---- | |
| chatbot = gr.Chatbot( | |
| height=550, | |
| placeholder=""" | |
| <div style='text-align:center;padding:60px 20px;color:#9aa0a6;font-family:Inter,Noto Sans Myanmar,sans-serif'> | |
| <div style='font-size:40px;margin-bottom:12px'>✦</div> | |
| <div style='font-size:20px;font-weight:600;color:#e8eaed;margin-bottom:6px'>ဘာဆောက်ချင်ပါသလဲ?</div> | |
| <div style='font-size:14px;color:#5f6368'>What would you like to build today?</div> | |
| <div style='font-size:13px;color:#5f6368;margin-top:10px'>App တစ်ခုဆောက်ချင်ရင် ပြောပါ / Just describe what you want</div> | |
| </div> | |
| """, | |
| ) | |
| # ---- 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 = '<div class="tmpl-grid">' | |
| for tkey, tmpl in PROJECT_TEMPLATES.items(): | |
| template_html += f'<div class="tmpl-card"><b>{tmpl["name"]}</b><br><span>{tkey}</span></div>' | |
| template_html += '</div>' | |
| 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"))) | |