Spaces:
Running on Zero
Running on Zero
| """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: | |
| 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""" | |
| <div style="background: #12121a; border-right: 1px solid rgba(255,255,255,0.08); | |
| height: 100vh; padding: 16px; font-family: system-ui;"> | |
| <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 20px;"> | |
| <div style="width: 32px; height: 32px; border-radius: 8px; | |
| background: linear-gradient(135deg, #6366f1, #8b5cf6); | |
| display: flex; align-items: center; justify-content: center; | |
| color: white; font-weight: 700; font-size: 14px;">S</div> | |
| <span style="font-size: 16px; font-weight: 600; | |
| background: linear-gradient(135deg, #6366f1, #8b5cf6); | |
| -webkit-background-clip: text; -webkit-text-fill-color: transparent;"> | |
| Synapse V8 | |
| </span> | |
| </div> | |
| <div style="color: #606080; font-size: 12px; margin-bottom: 12px; | |
| text-transform: uppercase; letter-spacing: 0.5px;"> | |
| v{config.version} | |
| </div> | |
| </div> | |
| """ | |
| def _build_header_html() -> str: | |
| return f""" | |
| <div style="display: flex; align-items: center; justify-content: space-between; | |
| padding: 12px 20px; border-bottom: 1px solid rgba(255,255,255,0.08); | |
| background: rgba(255,255,255,0.03); backdrop-filter: blur(20px);"> | |
| <div style="font-size: 15px; font-weight: 600; color: #f0f0f5;">Synapse V8</div> | |
| <div style="display: flex; gap: 8px; align-items: center;"> | |
| <span style="font-size: 11px; color: #606080; padding: 4px 8px; | |
| border: 1px solid rgba(255,235,59,0.2); border-radius: 4px; | |
| background: rgba(255,235,59,0.05);"> | |
| {'🟢 ZeroGPU' if GPU_AVAILABLE else '⚪ CPU Mode'} | |
| </span> | |
| </div> | |
| </div> | |
| """ | |
| app = build_synapse_ui() | |
| if __name__ == "__main__": | |
| app.launch( | |
| server_name=config.host, | |
| server_port=config.port, | |
| share=False, | |
| show_error=True, | |
| theme=SYNAPSE_THEME, | |
| css=CUSTOM_CSS, | |
| ssr_mode=False, | |
| ) | |