RCaz/Build_small_hackathon_Thousand_tokens_world
0
1import io2import os3import tempfile4import uuid5 6import gradio as gr7from PIL import Image8 9from crew2 import (10 run_pipeline,11 generate_image,12 generate_caption,13 generate_voice,14 transcribe_audio,15)16 17# ---------------------------------------------------------------------------18# Custom CSS19# ---------------------------------------------------------------------------20_CSS = """21/* ── Global ─────────────────────────────────────────────── */22@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');23 24* { font-family: 'Inter', system-ui, -apple-system, sans-serif; }25 26.gradio-container { max-width: 1200px !important; margin: 0 auto !important; }27 28/* ── Header ─────────────────────────────────────────────── */29.header-box {30 background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);31 border-radius: 24px;32 padding: 2.5rem 2rem;33 margin-bottom: 2rem;34 text-align: center;35 border: 1px solid rgba(255,255,255,0.06);36 box-shadow: 0 20px 60px rgba(0,0,0,0.5);37}38.header-box h1 {39 font-size: 2.4rem;40 font-weight: 800;41 background: linear-gradient(135deg, #f093fb 0%, #f5576c 50%, #4facfe 100%);42 -webkit-background-clip: text;43 -webkit-text-fill-color: transparent;44 background-clip: text;45 margin-bottom: 0.4rem;46 letter-spacing: -0.02em;47}48.header-box p {49 font-size: 1.05rem;50 color: rgba(255,255,255,0.55);51 font-weight: 300;52 margin: 0;53}54 55/* ── Card containers ────────────────────────────────────── */56.card {57 background: rgba(255,255,255,0.04);58 backdrop-filter: blur(16px);59 -webkit-backdrop-filter: blur(16px);60 border: 1px solid rgba(255,255,255,0.07);61 border-radius: 18px;62 padding: 1.8rem 1.5rem;63 margin-bottom: 1.5rem;64 box-shadow: 0 8px 32px rgba(0,0,0,0.25);65 transition: border-color 0.3s;66}67.card:hover { border-color: rgba(255,255,255,0.12); }68 69.card-label {70 font-size: 0.7rem;71 font-weight: 600;72 letter-spacing: 0.08em;73 text-transform: uppercase;74 color: rgba(255,255,255,0.3);75 margin-bottom: 0.6rem;76}77 78/* ── Input overrides ────────────────────────────────────── */79input, textarea, .gr-box {80 border-radius: 12px !important;81 background: rgba(255,255,255,0.05) !important;82 border: 1px solid rgba(255,255,255,0.1) !important;83 color: #fff !important;84 transition: border-color 0.2s, box-shadow 0.2s;85}86input:focus, textarea:focus, .gr-box:focus-within {87 border-color: #4facfe !important;88 box-shadow: 0 0 0 3px rgba(79,172,254,0.15) !important;89}90 91/* ── Button ─────────────────────────────────────────────── */92.submit-btn {93 background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%) !important;94 border: none !important;95 border-radius: 14px !important;96 color: #fff !important;97 font-weight: 700 !important;98 font-size: 1.05rem !important;99 padding: 0.9rem 2rem !important;100 transition: transform 0.2s, box-shadow 0.2s !important;101 box-shadow: 0 6px 24px rgba(245,87,108,0.3) !important;102 cursor: pointer;103}104.submit-btn:hover {105 transform: translateY(-2px) scale(1.01);106 box-shadow: 0 10px 36px rgba(245,87,108,0.4) !important;107}108.submit-btn:active { transform: translateY(0); }109 110/* ── Progress ───────────────────────────────────────────── */111.progress-text {112 font-size: 0.95rem;113 font-weight: 500;114 color: rgba(255,255,255,0.75);115 margin-top: 0.5rem;116 min-height: 1.6em;117}118.gr-progress-bar {119 --progress-bar-color: linear-gradient(90deg, #f093fb, #f5576c, #4facfe) !important;120 height: 6px !important;121 border-radius: 4px !important;122}123 124/* ── Output image / audio ───────────────────────────────── */125.output-image img {126 border-radius: 16px !important;127 box-shadow: 0 12px 48px rgba(0,0,0,0.4);128 width: 100%;129 height: auto;130 max-height: 500px;131 object-fit: cover;132}133audio { width: 100%; border-radius: 40px; }134 135/* ── Detail accordions ──────────────────────────────────── */136details.card-detail {137 background: rgba(255,255,255,0.03);138 border: 1px solid rgba(255,255,255,0.06);139 border-radius: 14px;140 padding: 0.8rem 1.2rem;141 margin-bottom: 0.8rem;142 transition: border-color 0.2s;143 cursor: pointer;144}145details.card-detail[open] { border-color: rgba(79,172,254,0.25); }146details.card-detail summary {147 font-weight: 600;148 font-size: 0.9rem;149 color: rgba(255,255,255,0.7);150 padding: 0.2rem 0;151 outline: none;152}153details.card-detail summary::-webkit-details-marker { color: rgba(255,255,255,0.3); }154details.card-detail .detail-body {155 margin-top: 0.8rem;156 font-size: 0.85rem;157 line-height: 1.6;158 color: rgba(255,255,255,0.55);159 max-height: 300px;160 overflow-y: auto;161 white-space: pre-wrap;162}163 164/* ── Status badges ──────────────────────────────────────── */165.badge {166 display: inline-block;167 font-size: 0.65rem;168 font-weight: 700;169 letter-spacing: 0.05em;170 padding: 0.2rem 0.6rem;171 border-radius: 20px;172 text-transform: uppercase;173}174.badge-success { background: rgba(52,211,153,0.15); color: #34d399; }175.badge-idle { background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.3); }176 177/* ── Responsive ─────────────────────────────────────────── */178@media (max-width: 640px) {179 .header-box h1 { font-size: 1.6rem; }180 .card { padding: 1.2rem 1rem; }181}182"""183 184 185# ---------------------------------------------------------------------------186# Pipeline runner — exposed as a module-level function for tests187# ---------------------------------------------------------------------------188def process(text_input: str | None, audio_input: str | None) -> tuple:189 """Run the full pipeline (text or audio). Returns (image, audio_path, caption, voice_style)."""190 session_id = str(uuid.uuid4())191 192 if audio_input is not None:193 text = transcribe_audio(audio_input, session_id=session_id)194 else:195 text = text_input196 197 if not text:198 raise ValueError("Please provide text or audio input.")199 200 results = run_pipeline(text, session_id=session_id)201 prompt = results.get("prompt")202 corroborate = results.get("result_corroborate", "")203 opposite = results.get("result_opposite", "")204 205 if not prompt:206 raise RuntimeError("Failed to generate an image prompt.")207 208 image_bytes = generate_image(prompt)209 image = Image.open(io.BytesIO(image_bytes))210 211 caption_result = generate_caption(corroborate, opposite, prompt, session_id=session_id)212 caption = caption_result.get("caption") or "(not found)"213 voice_style = caption_result.get("voice_style") or "(not found)"214 215 audio_bytes = generate_voice(voice_style, caption, session_id=session_id)216 tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)217 tmp.write(audio_bytes)218 audio_path = tmp.name219 tmp.close()220 221 return image, audio_path, caption, voice_style222 223 224# ---------------------------------------------------------------------------225# Build demo226# ---------------------------------------------------------------------------227with gr.Blocks(228 title="CrewAI Research → Image → Voice",229 fill_height=True,230) as demo:231 232 # ---------- header ----------233 gr.HTML(234 """235<div class="header-box">236 <h1>🧠 CrewAI Research Pipeline</h1>237 <p>Enter a statement → AI researches both sides → generates a Flux LoRA image & VoxCPM voiceover</p>238</div>"""239 )240 241 # ---------- input row ----------242 with gr.Column(elem_classes="card"):243 gr.HTML('<div class="card-label">Input</div>')244 with gr.Row(equal_height=True):245 with gr.Column(scale=3):246 text_input = gr.Textbox(247 label="Statement",248 placeholder='e.g. "AI will replace humans at work"',249 lines=2,250 elem_classes="input-field",251 )252 with gr.Column(scale=2):253 audio_input = gr.Audio(254 label="Or speak your statement",255 type="filepath",256 sources=["microphone", "upload"],257 waveform_options=gr.WaveformOptions(258 waveform_color="rgba(79,172,254,0.3)",259 waveform_progress_color="rgba(79,172,254,0.8)",260 ),261 )262 with gr.Row():263 submit_btn = gr.Button(264 "🚀 Run Pipeline",265 variant="primary",266 size="lg",267 elem_classes="submit-btn",268 )269 270 # ---------- progress ----------271 progress_text = gr.HTML(272 '<div class="progress-text">⏳ Ready — enter a statement and press Run</div>'273 )274 275 # ---------- results ----------276 with gr.Row():277 with gr.Column(scale=3):278 # Image279 with gr.Column(elem_classes="card"):280 gr.HTML('<div class="card-label">Generated Image — Flux LoRA</div>')281 image_output = gr.Image(282 show_label=False,283 height=420,284 elem_classes="output-image",285 )286 287 # Audio288 with gr.Column(elem_classes="card"):289 gr.HTML('<div class="card-label">Voiceover — VoxCPM</div>')290 audio_output = gr.Audio(291 show_label=False,292 type="filepath",293 elem_classes="output-audio",294 )295 296 with gr.Column(scale=2):297 # Caption + style298 with gr.Column(elem_classes="card"):299 gr.HTML('<div class="card-label">Narration</div>')300 caption_output = gr.Textbox(301 label="Caption (30 s)",302 lines=4,303 max_lines=6,304 placeholder="The spoken narration will appear here ...",305 )306 style_output = gr.Textbox(307 label="Voice Style",308 lines=1,309 placeholder="e.g. warm thoughtful professor",310 )311 312 # Research details313 with gr.Column(elem_classes="card"):314 gr.HTML('<div class="card-label">📚 Research Details</div>')315 316 with gr.Row():317 corroborate_badge = gr.HTML(318 '<span class="badge badge-idle">⏳ awaiting run</span>'319 )320 opposite_badge = gr.HTML(321 '<span class="badge badge-idle">⏳ awaiting run</span>'322 )323 324 corroborate_html = gr.HTML(325 '<details class="card-detail"><summary>📖 Corroborative Evidence</summary>'326 '<div class="detail-body">Run the pipeline to see findings …</div></details>'327 )328 opposite_html = gr.HTML(329 '<details class="card-detail"><summary>📖 Opposing / Complementary</summary>'330 '<div class="detail-body">Run the pipeline to see findings …</div></details>'331 )332 prompt_html = gr.HTML(333 '<details class="card-detail"><summary>🖼️ Image Prompt</summary>'334 '<div class="detail-body">Run the pipeline to see the prompt …</div></details>'335 )336 337 # ---------- event wiring ----------338 def on_submit(text, audio, progress: gr.Progress = gr.Progress(), request: gr.Request = None):339 session_id = str(uuid.uuid4())340 try:341 yield from _generate(text, audio, session_id, progress)342 except gr.Error:343 raise344 except Exception as exc:345 raise gr.Error(f"Pipeline failed: {exc}")346 347 def _generate(text, audio, session_id, progress):348 # ---- run pipeline ----349 if audio is not None:350 progress(0.02, desc="🎤 Transcribing audio …")351 text = transcribe_audio(audio, session_id=session_id)352 else:353 text = text354 355 if not text:356 raise gr.Error("Please enter a statement or record audio.")357 358 progress(0.05, desc="📡 Crew 1 — Corroborative research …")359 progress(0.18, desc="🔄 Crew 2 — Opposite research …")360 progress(0.32, desc="🧠 Crew 3 — Synthesizing image prompt …")361 362 results = run_pipeline(text, session_id=session_id)363 prompt = results["prompt"]364 corroborate = results["result_corroborate"]365 opposite = results["result_opposite"]366 367 if not prompt:368 raise gr.Error("Failed to generate an image prompt.")369 370 # ---- partial update: research badges + details ----371 yield (372 None, None, None, None, # image, audio, caption, style373 _badge("done", "corroborated"),374 _badge("done", "opposed"),375 _detail("📖 Corroborative Evidence", _shorten(corroborate)),376 _detail("📖 Opposing / Complementary", _shorten(opposite)),377 _detail("🖼️ Image Prompt", prompt),378 _progress("🎨 Generating image …"),379 )380 381 # ---- image ----382 progress(0.50, desc="🎨 Generating image with Flux LoRA …")383 try:384 image_bytes = generate_image(prompt)385 image = Image.open(io.BytesIO(image_bytes))386 except Exception as exc:387 raise gr.Error(f"Image generation failed: {exc}")388 389 yield (390 image, None, None, None,391 _badge("done", "corroborated"),392 _badge("done", "opposed"),393 _detail("📖 Corroborative Evidence", _shorten(corroborate)),394 _detail("📖 Opposing / Complementary", _shorten(opposite)),395 _detail("🖼️ Image Prompt", prompt),396 _progress("📝 Generating narration …"),397 )398 399 # ---- caption ----400 progress(0.70, desc="📝 Generating narration via Gemma 4 …")401 try:402 caption_result = generate_caption(corroborate, opposite, prompt, session_id=session_id)403 except Exception as exc:404 raise gr.Error(f"Caption generation failed: {exc}")405 406 caption = caption_result.get("caption") or "(not found)"407 voice_style = caption_result.get("voice_style") or "(not found)"408 409 yield (410 image, None, caption, voice_style,411 _badge("done", "corroborated"),412 _badge("done", "opposed"),413 _detail("📖 Corroborative Evidence", _shorten(corroborate)),414 _detail("📖 Opposing / Complementary", _shorten(opposite)),415 _detail("🖼️ Image Prompt", prompt),416 _progress("🔊 Synthesizing voiceover …"),417 )418 419 # ---- voice ----420 progress(0.85, desc="🔊 Synthesizing voiceover with VoxCPM …")421 try:422 audio_bytes = generate_voice(voice_style, caption, session_id=session_id)423 tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)424 tmp.write(audio_bytes)425 audio_path = tmp.name426 tmp.close()427 except Exception as exc:428 raise gr.Error(f"Voice synthesis failed: {exc}")429 430 yield (431 image, audio_path, caption, voice_style,432 _badge("done", "corroborated"),433 _badge("done", "opposed"),434 _detail("📖 Corroborative Evidence", _shorten(corroborate)),435 _detail("📖 Opposing / Complementary", _shorten(opposite)),436 _detail("🖼️ Image Prompt", prompt),437 _progress("✅ Complete!"),438 )439 440 # helpers -------------------------------------------------441 def _shorten(text: str, max_chars: int = 2000) -> str:442 """Truncate long text for the accordion detail view."""443 return text if len(text) <= max_chars else text[:max_chars] + "\n\n… (truncated)"444 445 def _badge(state: str, label: str) -> str:446 cls = "badge-success" if state == "done" else "badge-idle"447 icon = "✅" if state == "done" else "⏳"448 return f'<span class="badge {cls}">{icon} {label}</span>'449 450 def _detail(summary: str, body: str) -> str:451 safe = body.replace("&", "&").replace("<", "<").replace(">", ">")452 return (453 f"<details class='card-detail' open>"454 f"<summary>{summary}</summary>"455 f"<div class='detail-body'>{safe}</div>"456 f"</details>"457 )458 459 def _progress(msg: str) -> str:460 return f'<div class="progress-text">{msg}</div>'461 462 # ---------- wire submit ----------463 submit_event = submit_btn.click(464 fn=on_submit,465 inputs=[text_input, audio_input],466 outputs=[467 image_output,468 audio_output,469 caption_output,470 style_output,471 corroborate_badge,472 opposite_badge,473 corroborate_html,474 opposite_html,475 prompt_html,476 progress_text,477 ],478 queue=True,479 )480 481 # ---------- examples ----------482 gr.Markdown(483 """484---485<div style="text-align:center;color:rgba(255,255,255,0.35);font-size:0.85rem;padding:1rem 0;">486 Powered by487 <strong>CrewAI</strong> ·488 <strong>Gemma 4 26B</strong> (vLLM / H200) ·489 <strong>Flux LoRA</strong> (A100) ·490 <strong>VoxCPM</strong> (T4) ·491 <strong>Cohere Transcribe</strong> (T4)492</div>"""493 )494 495# ---------------------------------------------------------------------------496# Launch497# ---------------------------------------------------------------------------498if __name__ == "__main__":499 import os500 is_hf_space = os.environ.get("SPACE_ID") is not None501 demo.launch(502 server_name="0.0.0.0",503 server_port=7860,504 show_error=not is_hf_space,505 theme=gr.themes.Soft(506 primary_hue="violet",507 secondary_hue="blue",508 neutral_hue="slate",509 font=gr.themes.GoogleFont("Inter"),510 ),511 css=_CSS,512 )513 