import streamlit as st import sys import os import json from pathlib import Path from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() sys.path.append(os.path.dirname(os.path.dirname(__file__))) PROCESSED_DIR = "data/processed_results" def apply_material_css(): st.markdown(""" """, unsafe_allow_html=True) def display_results(final_state): st.subheader("Workflow Results") metadata = final_state.get("metadata", {}) if isinstance(metadata, dict) and metadata.get("intake_error"): st.error(f"CSV validation failed: {metadata['intake_error']}") st.info("This CSV was not accepted for processing. Please fix the headers and re-upload.") st.markdown("### Metadata") for k, v in metadata.items(): st.markdown(f"- **{k.replace('_', ' ').title()}**: {v}") return st.markdown("### Transcript") transcript_text = final_state.get("clean_content") or final_state.get("content") or "" if transcript_text: with st.expander("View transcript", expanded=False): st.text_area("Transcript", transcript_text, height=220) else: st.write("No transcript available.") col1, col2 = st.columns(2) with col1: st.markdown("### Summary") st.write(final_state.get("summary", "No summary generated.")) st.markdown("### Key Points") key_points = final_state.get("key_points", "No key points generated.") if isinstance(key_points, list): for point in key_points: st.markdown(f"- {point}") else: st.write(key_points) st.markdown("### Action Items") action_items = final_state.get("action_items", "No action items generated.") if isinstance(action_items, list): if action_items: for item in action_items: st.markdown(f"- {item}") else: st.write("No action items generated.") else: st.write(action_items) st.markdown("### Tags / Highlights") tags = final_state.get("tags") or [] highlights = final_state.get("highlights") or [] if tags: st.write("**Tags:** " + ", ".join([str(t) for t in tags])) else: st.write("**Tags:** None") if highlights: st.write("**Highlights:**") for h in highlights: st.markdown(f"- {h}") else: st.write("**Highlights:** None") with col2: st.markdown("### Scoring Rubric") quality_scores = final_state.get("quality_scores", {}) if isinstance(quality_scores, dict) and quality_scores: import plotly.graph_objects as go for metric in ['tone', 'professionalism', 'structured_resolution']: if metric in quality_scores: try: val = float(quality_scores[metric]) fig = go.Figure(go.Indicator( mode="gauge+number", value=val, title={'text': metric.replace('_', ' ').title(), 'font': {'size': 16, 'color': '#FFFFFF'}}, gauge={ 'axis': {'range': [None, 10], 'tickwidth': 1, 'tickcolor': "#BB86FC"}, 'bar': {'color': "#BB86FC"}, 'bgcolor': "#1E1E1E", 'borderwidth': 2, 'bordercolor': "#333333", 'steps': [ {'range': [0, 4], 'color': '#cf6679'}, {'range': [4, 7], 'color': '#ffb74d'}, {'range': [7, 10], 'color': '#81c784'}], } )) # Adjust colors for dark theme fig.update_layout( height=180, margin=dict(l=20, r=20, t=40, b=20), paper_bgcolor='#1E1E1E', plot_bgcolor='#1E1E1E', font={'color': '#FFFFFF'} ) st.plotly_chart(fig, width="stretch") except (ValueError, TypeError): st.write(f"**{metric.replace('_', ' ').title()}**: {quality_scores[metric]}") if "notes" in quality_scores: st.write("**Notes:**", quality_scores["notes"]) if "rubric" in quality_scores and quality_scores["rubric"]: with st.expander("View scoring rubric", expanded=False): rubric_rows = [ { "Dimension": "Tone", "0": "Hostile/arguing", "3": "Curt/tense", "5": "Neutral", "7": "Friendly/empathic", "10": "Consistently calm, respectful, de-escalating", }, { "Dimension": "Professionalism", "0": "Rude/unprofessional", "3": "Unclear or dismissive", "5": "Acceptable", "7": "Clear, courteous, policy-aligned", "10": "Excellent clarity, appropriate boundaries, ownership", }, { "Dimension": "Structured resolution", "0": "No attempt", "3": "Vague / no next steps", "5": "Partial (some questions/steps)", "7": "Clear diagnosis + next steps + confirmation", "10": "Fully structured (issue, actions, timelines, confirmation, closure)", }, ] st.dataframe( rubric_rows, hide_index=True, use_container_width=True, ) st.caption( "Notes must cite 1–3 specific behaviors from the transcript (avoid long quotes)." ) else: st.write("No scoring rubric results generated.", quality_scores) st.markdown("### Metadata") if isinstance(metadata, dict) and metadata: for k, v in metadata.items(): st.markdown(f"- **{k.replace('_', ' ').title()}**: {v}") else: st.write("No metadata available.") def main(): st.set_page_config(page_title="Call Center Data Analysis", layout="wide") apply_material_css() st.title("Call Center Data Analysis Dashboard") os.makedirs(PROCESSED_DIR, exist_ok=True) os.makedirs("tmp", exist_ok=True) if "view_mode" not in st.session_state: st.session_state.view_mode = "none" def set_upload_mode(): st.session_state.view_mode = "upload" def set_dropdown_mode(): st.session_state.view_mode = "dropdown" st.sidebar.header("Upload New File") uploaded_file = st.sidebar.file_uploader( "Upload a file", type=["mp3", "wav", "csv", "json"], on_change=set_upload_mode ) st.sidebar.markdown("---") st.sidebar.header("Processed Files") # Get list of processed files processed_files = [f for f in os.listdir(PROCESSED_DIR) if f.endswith(".json")] processed_files.sort(reverse=True) # Show newest (or reverse alphabetical) first selected_file = None if processed_files: options = ["-- Select a file --"] + processed_files selected_dropdown = st.sidebar.selectbox( "View cached results:", options, format_func=lambda x: x.replace(".json", "") if x != "-- Select a file --" else x, on_change=set_dropdown_mode ) if selected_dropdown != "-- Select a file --": selected_file = selected_dropdown else: st.sidebar.info("No files processed yet.") st.sidebar.markdown("---") st.sidebar.header("Notes") st.sidebar.markdown(""" Sample data: - [Customer Call Center Dataset Analysis](https://www.kaggle.com/datasets/rafaqatkhan608/customer-call-center-dataset-analysis/code/data) - [E-commerce Customer Support English Audio](https://huggingface.co/datasets/HumynLabs/e-commerce-customersupport-english-audio/tree/main) """) # Prioritize based on view_mode if st.session_state.view_mode == "upload" and uploaded_file is not None: st.success(f"File '{uploaded_file.name}' uploaded successfully!") from src.workflow import build_workflow file_path = os.path.join("tmp", uploaded_file.name) with open(file_path, "wb") as f: f.write(uploaded_file.getbuffer()) file_extension = uploaded_file.name.split('.')[-1].lower() # Check if already processed to avoid reprocessing on rerun if same file is in uploader cached_json_path = os.path.join(PROCESSED_DIR, f"{uploaded_file.name}.json") if os.path.exists(cached_json_path): st.info("Loading cached results for this file...") with open(cached_json_path, 'r') as f: final_state = json.load(f) display_results(final_state) else: st.write("Processing file through LangGraph Workflow...") with st.spinner("Agents are analyzing the data..."): workflow = build_workflow() initial_state = { "file_path": file_path, "file_type": file_extension } thread_id = Path(file_path).name final_state = workflow.invoke( initial_state, config={"configurable": {"thread_id": thread_id}} ) if final_state.get("metadata", {}).get("intake_error"): display_results(final_state) else: # Cache the results with open(cached_json_path, 'w') as f: json.dump(final_state, f) st.rerun() if not final_state.get("metadata", {}).get("intake_error"): display_results(final_state) elif ( st.session_state.view_mode == "dropdown" or st.session_state.view_mode == "none") and selected_file is not None: st.info(f"Loading cached results for '{selected_file.replace('.json', '')}'") cached_json_path = os.path.join(PROCESSED_DIR, selected_file) with open(cached_json_path, 'r') as f: final_state = json.load(f) display_results(final_state) elif st.session_state.view_mode != "dropdown" and uploaded_file is not None: st.success(f"File '{uploaded_file.name}' uploaded successfully!") from src.workflow import build_workflow file_path = os.path.join("tmp", uploaded_file.name) with open(file_path, "wb") as f: f.write(uploaded_file.getbuffer()) file_extension = uploaded_file.name.split('.')[-1].lower() cached_json_path = os.path.join(PROCESSED_DIR, f"{uploaded_file.name}.json") if os.path.exists(cached_json_path): st.info("Loading cached results for this file...") with open(cached_json_path, 'r') as f: final_state = json.load(f) display_results(final_state) else: st.write("Processing file through LangGraph Workflow...") with st.spinner("Agents are analyzing the data..."): workflow = build_workflow() initial_state = { "file_path": file_path, "file_type": file_extension } thread_id = Path(file_path).name final_state = workflow.invoke( initial_state, config={"configurable": {"thread_id": thread_id}} ) if final_state.get("metadata", {}).get("intake_error"): display_results(final_state) else: with open(cached_json_path, 'w') as f: json.dump(final_state, f) st.rerun() if not final_state.get("metadata", {}).get("intake_error"): display_results(final_state) else: st.info("Please upload a file or select a previously processed file.") if __name__ == "__main__": main()