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("""
""", 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"""
""", 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
""", 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)