File size: 5,500 Bytes
971e38a
86b948e
 
 
 
335600f
 
 
 
 
 
971e38a
e3c954b
971e38a
335600f
 
e3c954b
 
86b948e
e3c954b
 
 
 
 
86b948e
 
 
 
 
 
 
 
 
 
 
 
e3c954b
 
86b948e
e3c954b
 
 
 
 
 
 
86b948e
 
971e38a
 
 
 
86b948e
971e38a
335600f
86b948e
 
 
 
 
 
 
 
 
 
 
 
e3c954b
86b948e
971e38a
 
86b948e
 
 
971e38a
 
 
86b948e
 
 
 
 
 
 
 
 
 
 
971e38a
 
86b948e
971e38a
 
 
 
86b948e
 
 
335600f
 
 
 
 
971e38a
 
e3c954b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335600f
 
 
 
 
 
 
 
 
e3c954b
 
971e38a
 
 
 
 
 
 
335600f
e3c954b
 
971e38a
 
e3c954b
335600f
971e38a
 
 
335600f
86b948e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
from __future__ import annotations

import os
import json
import tempfile
import gradio as gr
from dotenv import load_dotenv

load_dotenv()

from genesis.pipeline import research_once
from genesis.providers import postprocess_summary, synthesize_tts
from genesis.graph import build_preview_graph_html
from genesis.graphdb import write_topic_and_papers

APP_TITLE = "GENESIS-AI β€” Synthetic Biology Deep Research (Safety-First)"
APP_DESC = (
    "High-level synthetic biology literature synthesis with citations. "
    "This app NEVER produces operational protocols."
)

DEFAULT_POST = os.getenv("POSTPROCESSOR_DEFAULT", "none").lower()
DEFAULT_RERANK_MODEL = os.getenv("RERANK_MODEL", "mixedbread-ai/mxbai-rerank-large-v1")


async def run_pipeline(
    query: str,
    fast: bool,
    postprocessor: str,
    want_graph: bool,
    state: dict,
) -> tuple[str, str, str, str | None, dict]:
    """
    Orchestrates deep research + optional post-processing + optional graph preview.
    Returns: (final_markdown, citations_markdown, json_blob, graph_html, state)
    """
    out = await research_once(query, fast=fast, rerank_model=DEFAULT_RERANK_MODEL)

    # Optional polish (Gemini/DeepSeek) β€” never add lab steps
    if postprocessor and postprocessor != "none":
        out["final_output"] = await postprocess_summary(
            base_text=out.get("final_output") or "",
            citations=out.get("citations", []),
            engine=postprocessor,
        )

    # Keep state for TTS / graph writer
    state = state or {}
    state["final_text"] = out.get("final_output") or ""
    state["citations"] = out.get("citations", [])
    state["query"] = query

    # Graph preview HTML
    graph_html = build_preview_graph_html(state["citations"]) if want_graph else None

    # Report
    final_md = state["final_text"] if state["final_text"] else "_No output_"

    # Citations list (robust string building)
    cite_lines: list[str] = []
    for c in state["citations"]:
        title = c.get("title") or "link"
        url = c.get("url") or ""
        cite_lines.append(f"- [{title}]({url})")
    cites_md = "\n".join(cite_lines) if cite_lines else "_None detected_"

    # JSON export
    json_blob = json.dumps(out, indent=2)

    return final_md, cites_md, json_blob, graph_html, state


async def do_tts(state: dict) -> tuple[str | None, str]:
    """Narrate the high-level summary via ElevenLabs."""
    text = (state or {}).get("final_text") or ""
    if not text.strip():
        return None, "Nothing to narrate yet β€” run research first."

    audio_bytes, mime = await synthesize_tts(text)
    if not audio_bytes:
        return None, "TTS not configured or failed. Ensure ELEVEN_LABS_API_KEY/VOICE_ID are set."

    suffix = ".mp3" if (mime or "").find("mpeg") >= 0 else ".wav"
    with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as f:
        f.write(audio_bytes)
        path = f.name
    return path, "Narration ready."


async def do_graph_write(state: dict) -> str:
    """Write Topic -> Paper knowledge graph into Neo4j."""
    topic = (state or {}).get("query") or "Untitled Topic"
    citations = (state or {}).get("citations") or []
    if not citations:
        return "No citations present β€” run research first."
    counts = await write_topic_and_papers(topic, citations)
    return f"Wrote to Neo4j: nodes={counts.get('nodes',0)}, rels={counts.get('rels',0)}"


with gr.Blocks(theme=gr.themes.Soft(), fill_height=True) as demo:
    gr.Markdown(f"# {APP_TITLE}")
    gr.Markdown(APP_DESC)

    state = gr.State({"final_text": "", "citations": [], "query": ""})

    with gr.Row():
        query = gr.Textbox(
            label="Your high-level research request",
            lines=5,
            placeholder=(
                "e.g., High-level synthesis of CRISPR base-editing trends in oncology (last 2 years). "
                "Summarize mechanisms, targets, ethics, and provide citations."
            ),
        )
    with gr.Row():
        fast = gr.Checkbox(label="Fast mode (o4-mini-deep-research)", value=False)
        post = gr.Dropdown(
            label="Post-processor",
            choices=["none", "gemini", "deepseek"],
            value=DEFAULT_POST,
            allow_custom_value=False,
        )
        want_graph = gr.Checkbox(label="Build graph preview", value=False)
    go = gr.Button("Run Deep Research", variant="primary")

    with gr.Tabs():
        with gr.Tab("Research Report"):
            report = gr.Markdown()
        with gr.Tab("Citations"):
            citations = gr.Markdown()
        with gr.Tab("JSON Export"):
            json_out = gr.Code(language="json")
        with gr.Tab("Graph Preview"):
            graph_html = gr.HTML()
        with gr.Tab("Graph Writer (Neo4j)"):
            write_btn = gr.Button("Write Topic & Papers to Neo4j", variant="secondary")
            write_status = gr.Markdown()
        with gr.Tab("Narration (ElevenLabs)"):
            tts_btn = gr.Button("Narrate Summary", variant="secondary")
            tts_audio = gr.Audio(label="Narration", autoplay=False)
            tts_status = gr.Markdown()

    go.click(
        fn=run_pipeline,
        inputs=[query, fast, post, want_graph, state],
        outputs=[report, citations, json_out, graph_html, state],
    )

    tts_btn.click(fn=do_tts, inputs=[state], outputs=[tts_audio, tts_status])
    write_btn.click(fn=do_graph_write, inputs=[state], outputs=[write_status])

if __name__ == "__main__":
    demo.launch()