CoolFace
Apppublic

RCaz/Build_small_hackathon_Thousand_tokens_world

sourceHugging Facemitupdated 2mo agoView on Hugging Face
0likes
app.py513 linesDownload Raw Back to root
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 &amp; 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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")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