"""Synapse V8 — Main Gradio Application. Advanced multimodal AI assistant for Hugging Face Spaces with ZeroGPU. """ import os import sys import json import asyncio import logging import tempfile from pathlib import Path from typing import Optional import gradio as gr sys.path.insert(0, str(Path(__file__).resolve().parent)) from config import config try: import spaces GPU_AVAILABLE = True except ImportError: GPU_AVAILABLE = False if GPU_AVAILABLE: @spaces.GPU(duration=60) def _gpu_keepalive(): pass from utils.logging import setup_logging logger = setup_logging() from database.engine import init_db, close_db from chat.engine import chat_engine from memory.manager import MemoryManager from agent.synapse_agent import SynapseAgent from history.manager import history_manager from personalities.definitions import BUILTIN_PERSONALITIES, list_personalities, get_personality from workspace.manager import workspace async def startup(): await init_db() logger.info("Synapse V8 database initialized") def init_sync(): loop = None try: loop = asyncio.get_event_loop() except RuntimeError: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(startup()) init_sync() CUSTOM_CSS = """ /* Synapse V8 — Gradio Theme Overrides */ .gradio-container { background: transparent !important; } .gr-block { background: transparent !important; border: none !important; } .gr-textbox textarea { background: rgba(255,255,255,0.03) !important; border: 1px solid rgba(255,255,255,0.08) !important; border-radius: 12px !important; color: #f0f0f5 !important; font-size: 14px !important; padding: 14px 16px !important; } .gr-textbox textarea:focus { border-color: #6366f1 !important; box-shadow: 0 0 0 3px rgba(99,102,241,0.15) !important; } .gr-button-primary { background: linear-gradient(135deg, #6366f1, #8b5cf6) !important; border: none !important; border-radius: 12px !important; font-weight: 600 !important; transition: all 0.2s ease !important; } .gr-button-primary:hover { transform: scale(1.02); box-shadow: 0 0 20px rgba(99,102,241,0.3) !important; } .gr-button-secondary { background: rgba(255,255,255,0.05) !important; border: 1px solid rgba(255,255,255,0.08) !important; border-radius: 12px !important; color: #a0a0b8 !important; } .gr-button-secondary:hover { background: rgba(255,255,255,0.08) !important; color: #f0f0f5 !important; } .gr-dropdown { background: rgba(255,255,255,0.03) !important; border: 1px solid rgba(255,255,255,0.08) !important; border-radius: 12px !important; color: #f0f0f5 !important; } .gr-chatbot { background: transparent !important; border: none !important; } .gr-chatbot .message { background: rgba(255,255,255,0.03) !important; border: 1px solid rgba(255,255,255,0.08) !important; border-radius: 16px !important; backdrop-filter: blur(10px); } .gr-chatbot .user { background: linear-gradient(135deg, #6366f1, #8b5cf6) !important; border-color: transparent !important; } .gr-tab-nav { background: rgba(255,255,255,0.03) !important; } .gr-tab { background: transparent !important; border-color: rgba(255,255,255,0.08) !important; } .gr-html { color: #f0f0f5 !important; } .gr-label { color: #a0a0b8 !important; } .gr-box { background: rgba(255,255,255,0.03) !important; border-color: rgba(255,255,255,0.08) !important; border-radius: 12px !important; } .gr-accordion { background: rgba(255,255,255,0.03) !important; border-color: rgba(255,255,255,0.08) !important; border-radius: 12px !important; } footer { display: none !important; } """ SYNAPSE_THEME = gr.themes.Base( primary_hue="indigo", secondary_hue="purple", neutral_hue="slate", font=["SF Pro Display", "system-ui", "sans-serif"], ).set( body_background_fill="*neutral_950", body_background_fill_dark="*neutral_950", block_background_fill="*neutral_900", block_background_fill_dark="*neutral_900", block_border_color="*neutral_800", block_label_text_color="*neutral_400", block_title_text_color="*neutral_200", input_background_fill="*neutral_900", input_background_fill_dark="*neutral_900", input_border_color="*neutral_700", input_border_color_dark="*neutral_700", button_primary_background_fill="indigo_600", button_primary_background_fill_dark="indigo_600", button_primary_text_color="white", button_secondary_background_fill="*neutral_800", button_secondary_text_color="*neutral_200", checkbox_background_color="*neutral_800", slider_color="indigo_500", body_text_color="*neutral_100", body_text_color_subdued="*neutral_400", ) def build_synapse_ui(): with gr.Blocks( analytics_enabled=False, ) as app: with gr.Row(): with gr.Column(scale=0, min_width=280): sidebar_html = gr.HTML(_build_sidebar_html(), elem_id="sidebar") with gr.Column(scale=1): with gr.Row(elem_id="header"): gr.HTML(_build_header_html()) with gr.Tabs() as main_tabs: with gr.Tab("💬 Chat", id="chat"): chatbot = gr.Chatbot( value=[], height=520, render_markdown=True, avatar_images=(None, None), ) with gr.Row(): mode_select = gr.Radio( choices=["agent", "fast", "reasoning", "deep_think"], value="agent", label="Mode", scale=3, ) personality_select = gr.Dropdown( choices=[p["name"] for p in list_personalities()], value="Synapse", label="Personality", scale=2, ) with gr.Row(): chat_input = gr.Textbox( placeholder="Message Synapse...", label="", lines=1, max_lines=5, scale=5, show_label=False, ) send_btn = gr.Button("→", variant="primary", scale=0, min_width=60) mic_btn = gr.Button("🎤", scale=0, min_width=40) with gr.Row(): upload_btn = gr.UploadButton( "📎 Attach", file_types=["image", "file"], file_count="multiple", scale=0, ) clear_btn = gr.Button("Clear", scale=0) with gr.Tab("🖼 Image", id="image"): with gr.Row(): with gr.Column(scale=2): img_prompt = gr.Textbox( label="Prompt", placeholder="Describe the image to generate...", lines=2, ) img_negative = gr.Textbox( label="Negative Prompt", placeholder="Things to avoid...", lines=1, ) with gr.Row(): img_width = gr.Slider(128, 2048, value=1024, step=64, label="Width") img_height = gr.Slider(128, 2048, value=1024, step=64, label="Height") with gr.Row(): img_steps = gr.Slider(1, 50, value=4, step=1, label="Steps") img_guidance = gr.Slider(1, 20, value=7.5, step=0.5, label="CFG") img_seed = gr.Number(label="Seed", value=-1) img_generate = gr.Button("Generate Image", variant="primary") with gr.Column(scale=1): img_output = gr.Image(label="Generated Image", type="filepath") img_gallery = gr.Gallery(label="Gallery", columns=3, height=300) with gr.Tab("🎥 Video", id="video"): with gr.Row(): with gr.Column(scale=2): vid_prompt = gr.Textbox( label="Prompt", placeholder="Describe the video...", lines=2, ) vid_duration = gr.Slider(1, 10, value=4, step=1, label="Duration (seconds)") vid_seed = gr.Number(label="Seed", value=-1) vid_generate = gr.Button("Generate Video", variant="primary") with gr.Column(scale=1): vid_output = gr.Video(label="Generated Video") with gr.Tab("🎵 Music", id="music"): with gr.Row(): with gr.Column(scale=2): mus_prompt = gr.Textbox( label="Prompt", placeholder="Describe the music...", lines=2, ) mus_genre = gr.Dropdown( choices=["Electronic", "Pop", "Rock", "Jazz", "Classical", "Hip Hop", "Ambient", "Lo-fi", "Cinematic", "Epic"], label="Genre", value="Electronic", ) mus_duration = gr.Slider(5, 120, value=30, step=5, label="Duration (seconds)") mus_seed = gr.Number(label="Seed", value=-1) mus_generate = gr.Button("Generate Music", variant="primary") with gr.Column(scale=1): mus_output = gr.Audio(label="Generated Music", type="filepath") with gr.Tab("👁 Vision", id="vision"): with gr.Row(): with gr.Column(scale=1): vis_image = gr.Image(label="Upload Image", type="filepath") vis_task = gr.Dropdown( choices=["caption", "detailed", "ocr", "objects"], value="caption", label="Task", ) vis_analyze = gr.Button("Analyze", variant="primary") vis_question = gr.Textbox( label="Ask a question about this image", placeholder="What's in this image?", ) vis_ask = gr.Button("Ask") with gr.Column(scale=1): vis_output = gr.Textbox(label="Result", lines=10) with gr.Tab("📄 Documents", id="docs"): with gr.Row(): with gr.Column(): doc_type = gr.Dropdown( choices=["PDF", "DOCX", "Markdown", "TXT", "CSV", "Excel", "PowerPoint", "JSON", "HTML", "Python", "JavaScript", "ZIP"], label="Document Type", value="PDF", ) doc_prompt = gr.Textbox( label="Content / Prompt", placeholder="Describe what to generate...", lines=5, ) doc_generate = gr.Button("Generate Document", variant="primary") with gr.Column(): doc_output = gr.File(label="Download") with gr.Tab("🧠 Memory", id="memory"): with gr.Row(): with gr.Column(scale=1): mem_add = gr.Textbox( label="Add Memory", placeholder="Store something important...", lines=2, ) mem_importance = gr.Slider(0, 1, value=0.5, step=0.1, label="Importance") mem_type = gr.Dropdown( choices=["long_term", "short_term", "pinned", "fact"], value="long_term", label="Type", ) mem_save = gr.Button("Save Memory", variant="primary") mem_search = gr.Textbox(label="Search Memories", placeholder="Search...") mem_search_btn = gr.Button("Search") with gr.Column(scale=2): mem_list = gr.Textbox(label="Memories", lines=15, interactive=False) with gr.Tab("⚙️ Settings", id="settings"): with gr.Row(): with gr.Column(): gr.Markdown("### Model Settings") model_provider = gr.Dropdown( choices=["groq", "hf_inference", "openrouter", "auto"], value="auto", label="Provider", ) temperature = gr.Slider(0, 2, value=0.7, step=0.1, label="Temperature") max_tokens = gr.Slider(256, 8192, value=4096, step=256, label="Max Tokens") stream_toggle = gr.Checkbox(label="Enable Streaming", value=True) with gr.Column(): gr.Markdown("### Appearance") theme_toggle = gr.Radio( choices=["dark", "light"], value="dark", label="Theme", ) gr.Markdown("### Voice") voice_speed = gr.Slider(0.5, 2.0, value=1.0, step=0.1, label="Speech Speed") voice_select = gr.Dropdown( choices=["default", "af_heart", "af_bella", "am_adam", "am_michael"], value="default", label="Voice", ) def respond(message, history, mode, personality): if not message.strip(): return history, "" loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: personality_data = get_personality(personality.lower()) system_prompt = personality_data["system_prompt"] result = loop.run_until_complete( chat_engine.send_message( content=message, user_id="gradio_user", mode=mode, personality_prompt=system_prompt, ) ) history = history or [] history.append({"role": "user", "content": message}) history.append({"role": "assistant", "content": result.get("content", "No response")}) return history, "" except Exception as e: logger.error(f"Chat error: {e}") history = history or [] history.append({"role": "user", "content": message}) history.append({"role": "assistant", "content": f"Error: {e}"}) return history, "" finally: loop.close() def generate_image_fn(prompt, negative, width, height, steps, guidance, seed): try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: from image.generator import image_generator s = None if seed == -1 else int(seed) result = image_generator.generate( prompt=prompt, negative_prompt=negative or "", width=int(width), height=int(height), steps=int(steps), guidance_scale=float(guidance), seed=s, ) return result finally: loop.close() except Exception as e: logger.error(f"Image gen error: {e}") return None def generate_video_fn(prompt, duration, seed): try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: from video.generator import video_generator s = None if seed == -1 else int(seed) result = video_generator.generate( prompt=prompt, seed=s, ) return result finally: loop.close() except Exception as e: logger.error(f"Video gen error: {e}") return None def generate_music_fn(prompt, genre, duration, seed): try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: from music.generator import music_generator result = music_generator.generate( prompt=prompt, genre=genre, duration=int(duration), seed=int(seed) if seed != -1 else None, ) return result finally: loop.close() except Exception as e: logger.error(f"Music gen error: {e}") return None def analyze_vision_fn(image, task): if image is None: return "No image provided." try: from vision.engine import vision_engine return vision_engine.describe_image(image, task=task) except Exception as e: return f"Error: {e}" def ask_vision_fn(image, question): if image is None: return "No image provided." if not question.strip(): return "Please ask a question." try: from vision.engine import vision_engine return vision_engine.analyze_for_question(image, question) except Exception as e: return f"Error: {e}" def save_memory_fn(content, importance, mem_type): if not content.strip(): return "Please enter a memory." try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: memory = MemoryManager("gradio_user") loop.run_until_complete( memory.add_memory(content, mem_type, importance) ) return f"Saved: {content[:50]}..." finally: loop.close() except Exception as e: return f"Error: {e}" def search_memory_fn(query): if not query.strip(): return "Enter a search query." try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: memory = MemoryManager("gradio_user") results = loop.run_until_complete(memory.search_long_term(query)) if not results: return "No matching memories found." parts = [] for m in results: parts.append(f"• {m['content'][:200]}") return "\n\n".join(parts) finally: loop.close() except Exception as e: return f"Error: {e}" send_btn.click( respond, [chat_input, chatbot, mode_select, personality_select], [chatbot, chat_input], ) chat_input.submit( respond, [chat_input, chatbot, mode_select, personality_select], [chatbot, chat_input], ) img_generate.click( generate_image_fn, [img_prompt, img_negative, img_width, img_height, img_steps, img_guidance, img_seed], [img_output], ) vid_generate.click( generate_video_fn, [vid_prompt, vid_duration, vid_seed], [vid_output], ) mus_generate.click( generate_music_fn, [mus_prompt, mus_genre, mus_duration, mus_seed], [mus_output], ) vis_analyze.click(analyze_vision_fn, [vis_image, vis_task], [vis_output]) vis_ask.click(ask_vision_fn, [vis_image, vis_question], [vis_output]) mem_save.click(save_memory_fn, [mem_add, mem_importance, mem_type], [mem_list]) mem_search_btn.click(search_memory_fn, [mem_search], [mem_list]) mem_search.submit(search_memory_fn, [mem_search], [mem_list]) return app def _build_sidebar_html() -> str: return f"""