import time import streamlit as st from analyzer import analyze_visual, chat_followup from parser import parse_response, to_dataframe from charts import draw_chart from report import generate_report # ── page config ─────────────────────────────────────────────────── st.set_page_config( page_title="Visual Analyst Agent", page_icon="📊", layout="wide" ) # ── custom CSS — full dark theme ────────────────────────────────── st.markdown(""" """, unsafe_allow_html=True) # ── session state ───────────────────────────────────────────────── for key in ["result", "parsed", "image_bytes"]: if key not in st.session_state: st.session_state[key] = None if "chat_history" not in st.session_state: st.session_state.chat_history = [] # ── sidebar ─────────────────────────────────────────────────────── with st.sidebar: st.markdown("""
📊 Visual Analyst
powered by Gemini
""", unsafe_allow_html=True) st.markdown("", unsafe_allow_html=True) uploaded = st.file_uploader( "Drop chart or table", type=["png", "jpg", "jpeg", "webp"], label_visibility="collapsed" ) analyze_btn = st.button( "Analyze visual", disabled=uploaded is None, use_container_width=True ) st.markdown("", unsafe_allow_html=True) st.markdown("""
Bar Line Pie Table Dashboard Scatter
""", unsafe_allow_html=True) # ── header ──────────────────────────────────────────────────────── st.markdown("""
Visual Analyst Agent
powered by Gemini
""", unsafe_allow_html=True) def render_steps(active_idx, steps): html = "
" for i, step in enumerate(steps): if i < active_idx: circle = f"
" color = "#a89fcc" elif i == active_idx: circle = f"
{i+1}
" color = "#6c63ff" else: circle = f"
{i+1}
" color = "#3a3a5a" html += f"
{circle}{step}
" if i < len(steps) - 1: html += "
" html += "
" return html # ── run analysis ────────────────────────────────────────────────── if analyze_btn and uploaded: image_bytes = uploaded.read() st.session_state.image_bytes = image_bytes st.session_state.chat_history = [] # animated step indicator steps_placeholder = st.empty() steps = ["Detecting visual type", "Extracting data", "Analyzing trends", "Building report"] for i in range(4): steps_placeholder.markdown(render_steps(i, steps), unsafe_allow_html=True) time.sleep(0.6) with st.spinner(""): result = analyze_visual(image_bytes) parsed = parse_response(result["raw"]) st.session_state.result = result st.session_state.parsed = parsed steps_placeholder.markdown(render_steps(4, steps), unsafe_allow_html=True) time.sleep(0.3) steps_placeholder.empty() # ── display results ─────────────────────────────────────────────── if st.session_state.parsed: parsed = st.session_state.parsed df = to_dataframe(parsed["data"]) tab1, tab2, tab3, tab4, tab5 = st.tabs([ "Analysis", "Extracted Data", "Redrawn Chart", "Chat", "Export" ]) # ── TAB 1: ANALYSIS ────────────────────────────────────────── with tab1: st.markdown(f"""
{parsed['visual_type']}
""", unsafe_allow_html=True) col1, col2, col3 = st.columns(3) if df is not None and "values" in parsed["data"]: values = parsed["data"]["values"] labels = parsed["data"].get("labels", []) peak_idx = values.index(max(values)) with col1: st.markdown(f"""
{labels[peak_idx] if labels else max(values)}
Peak
""", unsafe_allow_html=True) with col2: st.markdown(f"""
{len(values)}
Data points
""", unsafe_allow_html=True) with col3: if len(values) > 1: growth = round(((values[-1] - values[0]) / values[0]) * 100) color = "#34d399" if growth >= 0 else "#f87171" sign = "+" if growth >= 0 else "" st.markdown(f"""
{sign}{growth}%
Growth
""", unsafe_allow_html=True) col_img, col_analysis = st.columns([1, 1]) with col_img: st.markdown("
Original image
", unsafe_allow_html=True) st.image(st.session_state.image_bytes, use_container_width=True) st.markdown("
", unsafe_allow_html=True) with col_analysis: st.markdown(f"""
Trend analysis
{parsed['analysis']}
""", unsafe_allow_html=True) if parsed["parse_error"]: st.warning(f"Note: {parsed['parse_error']}") # ── TAB 2: EXTRACTED DATA ───────────────────────────────────── with tab2: st.markdown("
Extracted data
", unsafe_allow_html=True) if df is not None: st.dataframe(df, use_container_width=True, hide_index=True) csv = df.to_csv(index=False) st.download_button( "Download CSV", csv, "extracted_data.csv", "text/csv", use_container_width=True ) else: st.markdown("
Could not extract structured data from this visual.
", unsafe_allow_html=True) if parsed["data"]: st.json(parsed["data"]) st.markdown("
", unsafe_allow_html=True) # ── TAB 3: REDRAWN CHART ───────────────────────────────────── with tab3: fig = draw_chart(parsed["visual_type"], parsed["data"]) if fig: st.markdown("
Redrawn with Plotly — hover to explore
", unsafe_allow_html=True) st.plotly_chart(fig, use_container_width=True) st.markdown("
", unsafe_allow_html=True) else: st.markdown("""
Chart could not be redrawn — the visual may be a table or complex dashboard without extractable chart data.
""", unsafe_allow_html=True) # ── TAB 4: CHAT ─────────────────────────────────────────────── with tab4: if st.session_state.image_bytes is None: st.markdown("
Please analyze an image first before using chat.
", unsafe_allow_html=True) else: st.markdown("
Ask follow-up questions about your visual
", unsafe_allow_html=True) for msg in st.session_state.chat_history: if msg["role"] == "user": st.markdown(f"""
You
{msg['content']}
""", unsafe_allow_html=True) else: st.markdown(f"""
Analyst
{msg['content']}
""", unsafe_allow_html=True) user_input = st.chat_input("Ask anything about this visual...") if user_input: st.session_state.chat_history.append({"role": "user", "content": user_input}) with st.spinner("Thinking..."): reply = chat_followup( st.session_state.image_bytes, st.session_state.chat_history[:-1], user_input ) st.session_state.chat_history.append({"role": "assistant", "content": reply}) st.rerun() # ── TAB 5: EXPORT ───────────────────────────────────────────── with tab5: st.markdown("""
Export report
Download a complete HTML report with the visual type, trend analysis, and extracted data table. Opens in any browser. Fully shareable.
""", unsafe_allow_html=True) html_report = generate_report(parsed["visual_type"], parsed["analysis"], df) st.download_button( label="Download HTML Report", data=html_report, file_name="visual_analysis_report.html", mime="text/html", use_container_width=True ) else: # ── IDLE STATE ──────────────────────────────────────────────── st.markdown("""
📊
Upload a visual to get started
Charts, tables, dashboards, screenshots
Bar chart Line chart Pie chart Data table Dashboard
Upload an image in the sidebar and click Analyze
""", unsafe_allow_html=True)