V8 / app.py
Drizzy0101's picture
update
c346152
Raw
History Blame Contribute Delete
24.5 kB
"""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"""
<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,
)