""" ResearchPilot AI โ€” Single-Page UI All sections on one scrollable page with sticky nav. Fixes: anchor links, SVG via components.html, PDF crash, renamed labels. """ import sys, os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import json import streamlit as st import streamlit.components.v1 as components from graph.graph_builder import graph from tools.pdf_generator import generate_pdf st.set_page_config( page_title="ResearchPilot AI", page_icon="๐Ÿงญ", layout="wide", initial_sidebar_state="expanded", ) # โ”€โ”€ CSS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown("""""", unsafe_allow_html=True) # โ”€โ”€ STICKY NAV โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # Uses JS to inject the nav above Streamlit's own header st.markdown("""
๐Ÿ”ฌ Research ๐Ÿ“Š Data & Charts ๐Ÿ“– References ๐Ÿ“„ Report โฑ๏ธ Trace
""", unsafe_allow_html=True) # โ”€โ”€ HEADER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown("""

๐Ÿงญ ResearchPilot AI

Autonomous Multi-Agent Research ยท Router ยท Statistics ยท Domain Expert ยท Fact Checker ยท Writer


""", unsafe_allow_html=True) # โ”€โ”€ SIDEBAR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ with st.sidebar: st.markdown("### โš™๏ธ Options") generate_thumbnail = st.toggle("Generate cover card", value=True) st.markdown("### ๐Ÿ’ก Try a Topic") st.markdown( "

" "Curated picks that showcase rich charts, expert analysis & fact-checking. " "Click any card to load it instantly.

", unsafe_allow_html=True, ) # Curated examples โ€” each one is chosen to trigger multiple agents and # produce strong chart-worthy statistics, so a first-time user sees the # system at its best. EXAMPLES = [ {"emoji": "๐Ÿš€", "label": "AI in Healthcare", "query": "Impact of AI on healthcare market growth: adoption rates, investment trends and future outlook"}, {"emoji": "๐Ÿ“ˆ", "label": "Renewable Energy", "query": "Global renewable energy adoption trends: solar, wind and investment growth by region"}, {"emoji": "๐Ÿค–", "label": "Multi-Agent AI", "query": "Future of multi-agent AI systems: architecture, real-world adoption and key players"}, {"emoji": "๐Ÿ’ฐ", "label": "AI in Banking", "query": "AI in banking and financial services: fraud detection, market size and adoption statistics"}, {"emoji": "โšก", "label": "Electric Vehicles", "query": "Future of electric vehicles worldwide: sales growth, battery costs and market share by region"}, {"emoji": "๐Ÿ›ก๏ธ", "label": "Cybersecurity 2026", "query": "Cybersecurity threat landscape 2026: emerging attack trends, breach costs and industry response"}, {"emoji": "๐ŸŽ“", "label": "AI in Education", "query": "AI transformation in education: adoption rates, learning outcomes and classroom case studies"}, {"emoji": "๐ŸŒ", "label": "Climate Change", "query": "Climate change: data, risks and emerging solutions across global regions"}, ] # 2-column grid of clickable example cards cols = st.columns(2) for idx, ex in enumerate(EXAMPLES): with cols[idx % 2]: if st.button( f"{ex['emoji']} {ex['label']}", use_container_width=True, key=f"ex_{idx}", help=ex["query"], ): st.session_state["query_input"] = ex["query"] st.rerun() st.markdown( "
", unsafe_allow_html=True ) if st.button("๐ŸŽฒ Surprise me", use_container_width=True, key="ex_random"): import random st.session_state["query_input"] = random.choice(EXAMPLES)["query"] st.rerun() st.markdown("---") st.markdown("### ๐Ÿค– Pipeline") for icon, name, desc in [ ("๐Ÿง ","Planner", "Query โ†’ tasks + domain"), ("๐Ÿ”€","Router", "Decides active agents"), ("๐Ÿ”","Research", "Deep web intelligence"), ("๐Ÿ“Š","Statistics", "Mine quantitative data"), ("๐ŸŽ“","Expert", "Domain expert analysis"), ("โœ…","FactCheck", "Verify key claims"), ("๐Ÿ“–","References", "APA source formatting"), ("๐Ÿ“ˆ","Viz", "6 interactive charts"), ("๐Ÿ–ผ๏ธ","Cover", "SVG cover card"), ("โœ๏ธ","Writer", "Structured report"), ("๐Ÿ†","QualityGate","Score + retry"), ]: st.markdown( f"
" f"{icon} {name}
" f"{desc}
", unsafe_allow_html=True) st.markdown("---") if st.session_state.get("rf_has_run"): if st.button("๐Ÿ”„ New Research", use_container_width=True): for k in list(st.session_state.keys()): if k.startswith("rf_") or k.startswith("pdf_cache_"): del st.session_state[k] st.rerun() st.markdown("### ๐Ÿ”— Links") st.markdown( "[๐Ÿ‘‰ GitHub Repo](https://github.com/vishal815) ยท " "[๐Ÿค LinkedIn](https://www.linkedin.com/in/vishal-lazrus/) ยท " "[๐ŸŒ Portfolio](https://vishal-lazrus-portfolio.vercel.app/)" ) # โ”€โ”€ QUERY INPUT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ col_q, col_btn = st.columns([5, 1]) with col_q: query = st.text_input( "๐Ÿ”Ž Research Topic", value=st.session_state.get("query_input",""), placeholder="e.g. How is generative AI transforming software development in 2025?", key="query_input", label_visibility="collapsed", ) with col_btn: run_btn = st.button("๐Ÿš€ Research", type="primary", use_container_width=True) st.markdown("
", unsafe_allow_html=True) NODE_ORDER = ["planner","router","workers","visualization","cover","writer","quality_gate"] def node_pills(completed, current=None): labels = { "planner":"๐Ÿง  Planner","router":"๐Ÿ”€ Router","workers":"โš™๏ธ Workers", "visualization":"๐Ÿ“ˆ Viz","cover":"๐Ÿ–ผ๏ธ Cover", "writer":"โœ๏ธ Writer","quality_gate":"๐Ÿ† Gate", "synthesizer":"โœ๏ธ Writer","thumbnail":"๐Ÿ–ผ๏ธ Cover", } parts = [] for n in NODE_ORDER: lbl = labels.get(n, n) if n in completed: parts.append(f"โœ… {lbl}") elif n == current: parts.append(f"โšก {lbl}") else: parts.append(f"โ—‹ {lbl}") return "
" + " ".join(parts) + "
" def log_class(l): return "log-ok" if "Done" in l or "โœ…" in l else ( "log-run" if any(x in l for x in ["Starting","Searching","Mining","Building","Applying","Evaluating","Merging","Generating","Verifying","Formatting"]) else "log-info") # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # PIPELINE RUN โ€” cached in session_state so download clicks # (which trigger a Streamlit rerun) don't re-run the agents. # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if run_btn and query.strip(): st.session_state["rf_query"] = query st.session_state["rf_has_run"] = True st.session_state.pop("rf_final", None) # force fresh run should_run_pipeline = ( st.session_state.get("rf_has_run", False) and "rf_final" not in st.session_state ) if should_run_pipeline: initial = { "query":query,"todos":[],"research_plan":"", "active_agents":[],"topic_domain":"general", "virtual_files":{},"structured_data":{},"citations":[], "chart_json":[],"thumbnail_svg":None, "final_report":"","quality_score":0.0, "retry_count":0,"agent_logs":[],"agent_timings":{}, "generate_thumbnail":generate_thumbnail, } completed, final = [], dict(initial) # โ”€โ”€ SECTION 1: RESEARCH (live) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown('', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
๐Ÿ”ฌ Research โ€” Live Pipeline
', unsafe_allow_html=True) status_box = st.empty() graph_box = st.empty() agent_box = st.empty() with st.spinner("ResearchPilot AI is working..."): for update in graph.stream(initial): node_name = list(update.keys())[0] node_data = update[node_name] completed.append(node_name) for k, v in node_data.items(): if v is not None: final[k] = v logs = final.get("agent_logs", []) active = final.get("active_agents", []) recent = logs[-6:] log_html = "".join( f"
โ†’ {l}
" for l in recent ) status_box.markdown( f"
" f"โšก Running: {node_name}" f"
{log_html}
", unsafe_allow_html=True, ) graph_box.markdown(node_pills(completed, node_name), unsafe_allow_html=True) if active: chips = "".join(f"๐Ÿค– {a}" for a in active) agent_box.markdown( f"
" f"Active agents: {chips}
", unsafe_allow_html=True, ) # โ”€โ”€ Save to session_state so download-button reruns reuse it โ”€โ”€ st.session_state["rf_final"] = final st.session_state["rf_completed"] = completed # Metrics row score = final.get("quality_score", 0) active = final.get("active_agents", []) domain = final.get("topic_domain", "general") vfs = final.get("virtual_files", {}) sc_cls = "badge-hi" if score>=7 else ("badge-md" if score>=4 else "badge-lo") st.markdown( f"
" f"
DOMAIN
{domain.upper()}
" f"
AGENTS USED
{len(active)}
" f"
QUALITY SCORE
{score:.1f}/10
" f"
", unsafe_allow_html=True, ) # VFS files if vfs: st.markdown("**๐Ÿ“ Agent Outputs**") for fname, content in vfs.items(): icon = "๐Ÿ“Š" if "csv" in fname else ("๐Ÿ”" if "research" in fname else ("๐Ÿ“–" if "ref" in fname or "cite" in fname else "๐Ÿ“„")) with st.expander(f"{icon} {fname}"): if fname.endswith(".csv"): try: import pandas as pd, io df = pd.read_csv(io.StringIO(content)) st.dataframe(df, use_container_width=True) except Exception: st.text_area("", content, height=150, disabled=True, key=f"vfs_{fname}") else: st.text_area("", content, height=160, disabled=True, key=f"vfs_{fname}") with st.expander("๐Ÿ“‹ Full Agent Log"): for l in final.get("agent_logs", []): st.markdown(f"
โ†’ {l}
", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # close sec-wrap # โ”€โ”€ SECTION 2: DATA & CHARTS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown('', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
๐Ÿ“Š Data & Charts
', unsafe_allow_html=True) chart_list = final.get("chart_json", []) structured = final.get("structured_data", {}) if chart_list: try: import plotly.graph_objects as go # Show charts in 2-column grid where possible c_pairs = [chart_list[i:i+2] for i in range(0, len(chart_list), 2)] for pair in c_pairs: if len(pair) == 2: ca, cb = st.columns(2) with ca: st.plotly_chart(go.Figure(json.loads(pair[0])), use_container_width=True, key=f"c{chart_list.index(pair[0])}") with cb: st.plotly_chart(go.Figure(json.loads(pair[1])), use_container_width=True, key=f"c{chart_list.index(pair[1])}") else: st.plotly_chart(go.Figure(json.loads(pair[0])), use_container_width=True, key=f"c{chart_list.index(pair[0])}") except ImportError: st.warning("Install plotly: `pip install plotly`") else: st.info("No charts โ€” Statistics agent was not activated for this topic.") # CSV download csv_data = vfs.get("statistics_data.csv","") if csv_data: col_csv1, col_csv2 = st.columns([1,4]) with col_csv1: st.download_button("โฌ‡๏ธ Download CSV", data=csv_data, file_name="researchpilot_data.csv", mime="text/csv", use_container_width=True, key="csv_dl_first") with col_csv2: if structured.get("summary"): st.markdown(f"> ๐Ÿ“ {structured['summary']}") if structured.get("metrics"): st.markdown("**๐Ÿ”ข Data Table**") import pandas as pd df = pd.DataFrame(structured["metrics"]) st.dataframe(df, use_container_width=True, hide_index=True) # Cover thumbnail thumb = final.get("thumbnail_svg") if thumb: st.markdown("**๐Ÿ–ผ๏ธ Cover Card**") components.html( f'
{thumb}
', height=460, scrolling=False, ) st.markdown("
", unsafe_allow_html=True) # โ”€โ”€ SECTION 3: REFERENCES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown('', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
๐Ÿ“– References
', unsafe_allow_html=True) citations = final.get("citations", []) if citations: st.markdown(f"

{len(citations)} sources found

", unsafe_allow_html=True) for c in citations: url = c.get("url","") link = f'{url[:70]}โ€ฆ' if url else "" st.markdown( f"
" f"[{c.get('number','')}] " f"{c.get('title','')}
" f"{c.get('apa','')}
{link}" f"
", unsafe_allow_html=True, ) else: ref_text = vfs.get("references.txt","") if ref_text and ref_text != "No references extracted.": st.markdown(ref_text) else: st.info("No references extracted for this topic.") st.markdown("
", unsafe_allow_html=True) # โ”€โ”€ SECTION 4: REPORT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown('', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
๐Ÿ“„ Report
', unsafe_allow_html=True) report = final.get("final_report","") if report: st.markdown(f'
{report}
', unsafe_allow_html=True) st.markdown("---") dl1, dl2 = st.columns(2) with dl1: st.download_button( "โฌ‡๏ธ Download Markdown", data=report, file_name=f"researchpilot_{query[:28].replace(' ','_')}.md", mime="text/markdown", use_container_width=True, key="md_dl_first", ) with dl2: pdf_key = f"pdf_cache_{hash(report) % 100000}" if pdf_key not in st.session_state: with st.spinner("Building PDF..."): st.session_state[pdf_key] = generate_pdf( report=report, query=query, quality_score=final.get("quality_score",0), citations=final.get("citations",[]), chart_json_list=final.get("chart_json",[]), thumbnail_svg=None, active_agents=final.get("active_agents",[]), ) pdf_bytes = st.session_state[pdf_key] if pdf_bytes: st.download_button( "โฌ‡๏ธ Download PDF", data=pdf_bytes, file_name=f"researchpilot_{query[:28].replace(' ','_')}.pdf", mime="application/pdf", use_container_width=True, key="pdf_dl_first", ) else: st.caption("PDF needs fpdf2: `pip install fpdf2`") st.markdown("
", unsafe_allow_html=True) # โ”€โ”€ SECTION 5: TRACE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown('', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
โฑ๏ธ Trace
', unsafe_allow_html=True) timings = final.get("agent_timings",{}) if timings: try: import plotly.graph_objects as go fig_t = go.Figure(go.Bar( x=list(timings.keys()), y=list(timings.values()), marker=dict(color=["#6366f1","#8b5cf6","#34d399","#f59e0b", "#06b6d4","#ef4444","#10b981"][:len(timings)], line=dict(color="#0f172a",width=1)), text=[f"{v:.1f}s" for v in timings.values()], textposition="outside", textfont=dict(color="#e2e8f0"), hovertemplate="%{x}: %{y:.2f}s", )) fig_t.update_layout( title=dict(text="Node Execution Time (seconds)",font=dict(color="#67e8f9",size=15)), plot_bgcolor="#0d1a1a", paper_bgcolor="#0d1a1a", font=dict(color="#e2e8f0"), xaxis=dict(gridcolor="#1e293b"), yaxis=dict(gridcolor="#1e293b", title="Seconds"), margin=dict(l=20,r=20,t=50,b=20), height=300, ) st.plotly_chart(fig_t, use_container_width=True, key="timing_chart") except ImportError: for k,v in timings.items(): st.write(f"`{k}`: {v:.2f}s") total = sum(timings.values()) if timings else 0 c1,c2,c3,c4 = st.columns(4) c1.metric("โฑ Total Time", f"{total:.1f}s") c2.metric("๐ŸŒ Domain", domain.upper()) c3.metric("๐Ÿค– Agents", len(active)) c4.metric("๐Ÿ” Retries", final.get("retry_count",0)) langsmith = os.getenv("LANGCHAIN_API_KEY","") if langsmith: st.markdown("**[๐Ÿ”— View LangSmith Trace โ†’](https://smith.langchain.com)**") else: st.info("Add `LANGCHAIN_API_KEY` to .env to enable LangSmith traces at smith.langchain.com") st.markdown("
", unsafe_allow_html=True) st.divider() st.markdown("### ๐Ÿ”— Links") st.markdown( "[๐Ÿ‘‰ GitHub Repo](https://github.com/vishal815) ยท " "[๐Ÿค LinkedIn](https://www.linkedin.com/in/vishal-lazrus/) ยท " "[๐ŸŒ Portfolio](https://vishal-lazrus-portfolio.vercel.app/)" ) elif run_btn and not query.strip(): st.warning("Please enter a research topic.") elif st.session_state.get("rf_has_run") and "rf_final" in st.session_state: # โ”€โ”€ Rerun triggered by a download button โ€” reuse cached result, skip re-running agents โ”€โ”€ final = st.session_state["rf_final"] completed = st.session_state.get("rf_completed", []) query = st.session_state.get("rf_query", query) score = final.get("quality_score", 0) active = final.get("active_agents", []) domain = final.get("topic_domain", "general") vfs = final.get("virtual_files", {}) sc_cls = "badge-hi" if score>=7 else ("badge-md" if score>=4 else "badge-lo") # โ”€โ”€ SECTION 1: RESEARCH (cached summary, no re-streaming) โ”€ st.markdown('', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
๐Ÿ”ฌ Research โ€” Pipeline Summary
', unsafe_allow_html=True) st.markdown(node_pills(completed, None), unsafe_allow_html=True) if active: chips = "".join(f"๐Ÿค– {a}" for a in active) st.markdown(f"
Active agents: {chips}
", unsafe_allow_html=True) st.markdown( f"
" f"
DOMAIN
{domain.upper()}
" f"
AGENTS USED
{len(active)}
" f"
QUALITY SCORE
{score:.1f}/10
" f"
", unsafe_allow_html=True, ) if vfs: st.markdown("**๐Ÿ“ Agent Outputs**") for fname, content in vfs.items(): icon = "๐Ÿ“Š" if "csv" in fname else ("๐Ÿ”" if "research" in fname else ("๐Ÿ“–" if "ref" in fname or "cite" in fname else "๐Ÿ“„")) with st.expander(f"{icon} {fname}"): if fname.endswith(".csv"): try: import pandas as pd, io df = pd.read_csv(io.StringIO(content)) st.dataframe(df, use_container_width=True) except Exception: st.text_area("", content, height=150, disabled=True, key=f"vfs_cached_{fname}") else: st.text_area("", content, height=160, disabled=True, key=f"vfs_cached_{fname}") with st.expander("๐Ÿ“‹ Full Agent Log"): for l in final.get("agent_logs", []): st.markdown(f"
โ†’ {l}
", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # โ”€โ”€ SECTION 2: DATA & CHARTS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown('', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
๐Ÿ“Š Data & Charts
', unsafe_allow_html=True) chart_list = final.get("chart_json", []) structured = final.get("structured_data", {}) if chart_list: try: import plotly.graph_objects as go c_pairs = [chart_list[i:i+2] for i in range(0, len(chart_list), 2)] for pair in c_pairs: if len(pair) == 2: ca, cb = st.columns(2) with ca: st.plotly_chart(go.Figure(json.loads(pair[0])), use_container_width=True, key=f"cc{chart_list.index(pair[0])}") with cb: st.plotly_chart(go.Figure(json.loads(pair[1])), use_container_width=True, key=f"cc{chart_list.index(pair[1])}") else: st.plotly_chart(go.Figure(json.loads(pair[0])), use_container_width=True, key=f"cc{chart_list.index(pair[0])}") except ImportError: st.warning("Install plotly: `pip install plotly`") else: st.info("No charts โ€” Statistics agent was not activated for this topic.") csv_data = vfs.get("statistics_data.csv","") if csv_data: col_csv1, col_csv2 = st.columns([1,4]) with col_csv1: st.download_button("โฌ‡๏ธ Download CSV", data=csv_data, file_name="researchpilot_data.csv", mime="text/csv", use_container_width=True, key="csv_dl_cached") with col_csv2: if structured.get("summary"): st.markdown(f"> ๐Ÿ“ {structured['summary']}") if structured.get("metrics"): st.markdown("**๐Ÿ”ข Data Table**") import pandas as pd df = pd.DataFrame(structured["metrics"]) st.dataframe(df, use_container_width=True, hide_index=True) thumb = final.get("thumbnail_svg") if thumb: st.markdown("**๐Ÿ–ผ๏ธ Cover Card**") components.html( f'
{thumb}
', height=460, scrolling=False, ) st.markdown("
", unsafe_allow_html=True) # โ”€โ”€ SECTION 3: REFERENCES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown('', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
๐Ÿ“– References
', unsafe_allow_html=True) citations = final.get("citations", []) if citations: st.markdown(f"

{len(citations)} sources found

", unsafe_allow_html=True) for c in citations: url = c.get("url","") link = f'{url[:70]}โ€ฆ' if url else "" st.markdown( f"
" f"[{c.get('number','')}] " f"{c.get('title','')}
" f"{c.get('apa','')}
{link}" f"
", unsafe_allow_html=True, ) else: ref_text = vfs.get("references.txt","") if ref_text and ref_text != "No references extracted.": st.markdown(ref_text) else: st.info("No references extracted for this topic.") st.markdown("
", unsafe_allow_html=True) # โ”€โ”€ SECTION 4: REPORT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown('', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
๐Ÿ“„ Report
', unsafe_allow_html=True) report = final.get("final_report","") if report: st.markdown(f'
{report}
', unsafe_allow_html=True) st.markdown("---") dl1, dl2 = st.columns(2) with dl1: st.download_button( "โฌ‡๏ธ Download Markdown", data=report, file_name=f"researchpilot_{query[:28].replace(' ','_')}.md", mime="text/markdown", use_container_width=True, key="md_dl_cached", ) with dl2: pdf_key = f"pdf_cache_{hash(report) % 100000}" if pdf_key not in st.session_state: with st.spinner("Building PDF..."): st.session_state[pdf_key] = generate_pdf( report=report, query=query, quality_score=final.get("quality_score",0), citations=final.get("citations",[]), chart_json_list=final.get("chart_json",[]), thumbnail_svg=None, active_agents=final.get("active_agents",[]), ) pdf_bytes = st.session_state[pdf_key] if pdf_bytes: st.download_button( "โฌ‡๏ธ Download PDF", data=pdf_bytes, file_name=f"researchpilot_{query[:28].replace(' ','_')}.pdf", mime="application/pdf", use_container_width=True, key="pdf_dl_cached", ) else: st.caption("PDF needs fpdf2: `pip install fpdf2`") st.markdown("
", unsafe_allow_html=True) # โ”€โ”€ SECTION 5: TRACE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown('', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) st.markdown('
โฑ๏ธ Trace
', unsafe_allow_html=True) timings = final.get("agent_timings",{}) if timings: try: import plotly.graph_objects as go fig_t = go.Figure(go.Bar( x=list(timings.keys()), y=list(timings.values()), marker=dict(color=["#6366f1","#8b5cf6","#34d399","#f59e0b","#06b6d4","#ef4444","#10b981"][:len(timings)], line=dict(color="#0f172a",width=1)), text=[f"{v:.1f}s" for v in timings.values()], textposition="outside", textfont=dict(color="#e2e8f0"), hovertemplate="%{x}: %{y:.2f}s", )) fig_t.update_layout( title=dict(text="Node Execution Time (seconds)",font=dict(color="#67e8f9",size=15)), plot_bgcolor="#0d1a1a", paper_bgcolor="#0d1a1a", font=dict(color="#e2e8f0"), xaxis=dict(gridcolor="#1e293b"), yaxis=dict(gridcolor="#1e293b", title="Seconds"), margin=dict(l=20,r=20,t=50,b=20), height=300, ) st.plotly_chart(fig_t, use_container_width=True, key="timing_chart_cached") except ImportError: for k,v in timings.items(): st.write(f"`{k}`: {v:.2f}s") total = sum(timings.values()) if timings else 0 c1,c2,c3,c4 = st.columns(4) c1.metric("โฑ Total Time", f"{total:.1f}s") c2.metric("๐ŸŒ Domain", domain.upper()) c3.metric("๐Ÿค– Agents", len(active)) c4.metric("๐Ÿ” Retries", final.get("retry_count",0)) langsmith = os.getenv("LANGCHAIN_API_KEY","") if langsmith: st.markdown("**[๐Ÿ”— View LangSmith Trace โ†’](https://smith.langchain.com)**") else: st.info("Add `LANGCHAIN_API_KEY` to .env to enable LangSmith traces at smith.langchain.com") st.markdown("
", unsafe_allow_html=True) st.divider() st.markdown("### ๐Ÿ”— Links") st.markdown( "[๐Ÿ‘‰ GitHub Repo](https://github.com/vishal815) ยท " "[๐Ÿค LinkedIn](https://www.linkedin.com/in/vishal-lazrus/) ยท " "[๐ŸŒ Portfolio](https://vishal-lazrus-portfolio.vercel.app/)" ) else: # Landing placeholders โ€” one per section, each styled distinctly for anchor, sec_cls, title_cls, icon, title, desc in [ ("sec-research", "sec-research", "t-indigo", "๐Ÿ”ฌ", "Research", "Live agent pipeline, domain detection, VFS file browser"), ("sec-charts", "sec-charts", "t-green", "๐Ÿ“Š", "Data & Charts", "6 interactive Plotly charts + CSV export + cover card"), ("sec-references","sec-references", "t-purple", "๐Ÿ“–", "References", "APA-formatted sources extracted from the web"), ("sec-report", "sec-report", "t-amber", "๐Ÿ“„", "Report", "Full structured report โ€” Markdown + PDF download"), ("sec-trace", "sec-trace", "t-cyan", "โฑ๏ธ", "Trace", "Per-node timing chart + LangSmith integration"), ]: st.markdown(f'', unsafe_allow_html=True) st.markdown( f'
' f'
{icon} {title}
' f'

{desc}

' f'
', unsafe_allow_html=True, ) st.markdown("---") st.markdown("### ๐Ÿ”— Links") st.markdown( "[๐Ÿ‘‰ GitHub Repo](https://github.com/vishal815) ยท " "[๐Ÿค LinkedIn](https://www.linkedin.com/in/vishal-lazrus/) ยท " "[๐ŸŒ Portfolio](https://vishal-lazrus-portfolio.vercel.app/)" )