CoolFace
Apppublic

Fade0510/CallCenterSummarizationAgent

sourceHugging Facemitupdated 5mo agoView on Hugging Face
0likes
streamlit_app.py376 linesDownload Raw Back to src
1import streamlit as st2import sys3import os4import json5from pathlib import Path6from dotenv import load_dotenv7 8# Load environment variables from .env file9load_dotenv()10 11sys.path.append(os.path.dirname(os.path.dirname(__file__)))12 13PROCESSED_DIR = "data/processed_results"14 15 16def apply_material_css():17    st.markdown("""18        <style>19        /* Material Design CSS Overrides */20 21        .stApp {22            font-family: 'Roboto', 'Inter', sans-serif;23            background-color: #121212;24            color: #FFFFFF;25        }26 27        /* Material Cards for metrics and sections */28        .material-card {29            background-color: #1E1E1E;30            border-radius: 8px;31            padding: 20px;32            box-shadow: 0 4px 6px rgba(0,0,0,0.3);33            margin-bottom: 20px;34        }35 36        h1, h2, h3, h4 {37            font-weight: 500;38        }39 40        /* Subtle styling for Streamlit columns to look like cards */41        [data-testid="column"] {42            background-color: #1E1E1E;43            border-radius: 8px;44            padding: 20px;45            box-shadow: 0 4px 6px rgba(0,0,0,0.3);46        }47 48        /* Expander (Transcript) - keep dark theme even on hover/focus */49        div[data-testid="stExpander"] details,50        div[data-testid="stExpander"] summary,51        div[data-testid="stExpander"] summary:hover,52        div[data-testid="stExpander"] summary:focus,53        div[data-testid="stExpander"] summary:active,54        div[data-testid="stExpander"] summary:focus-visible {55            background-color: #1E1E1E !important;56            color: #FFFFFF !important;57        }58 59        div[data-testid="stExpander"] details {60            border: 1px solid #333333 !important;61            border-radius: 8px !important;62        }63 64        /* Transcript text area */65        div[data-testid="stExpander"] textarea,66        div[data-testid="stExpander"] textarea:hover,67        div[data-testid="stExpander"] textarea:focus,68        div[data-testid="stExpander"] textarea:active {69            background-color: #121212 !important;70            color: #FFFFFF !important;71            border-color: #333333 !important;72        }73 74        </style>75    """, unsafe_allow_html=True)76 77 78def display_results(final_state):79    st.subheader("Workflow Results")80 81    metadata = final_state.get("metadata", {})82    if isinstance(metadata, dict) and metadata.get("intake_error"):83        st.error(f"CSV validation failed: {metadata['intake_error']}")84        st.info("This CSV was not accepted for processing. Please fix the headers and re-upload.")85        st.markdown("### Metadata")86        for k, v in metadata.items():87            st.markdown(f"- **{k.replace('_', ' ').title()}**: {v}")88        return89 90    st.markdown("### Transcript")91    transcript_text = final_state.get("clean_content") or final_state.get("content") or ""92    if transcript_text:93        with st.expander("View transcript", expanded=False):94            st.text_area("Transcript", transcript_text, height=220)95    else:96        st.write("No transcript available.")97 98    col1, col2 = st.columns(2)99    with col1:100        st.markdown("### Summary")101        st.write(final_state.get("summary", "No summary generated."))102 103        st.markdown("### Key Points")104        key_points = final_state.get("key_points", "No key points generated.")105        if isinstance(key_points, list):106            for point in key_points:107                st.markdown(f"- {point}")108        else:109            st.write(key_points)110 111        st.markdown("### Action Items")112        action_items = final_state.get("action_items", "No action items generated.")113        if isinstance(action_items, list):114            if action_items:115                for item in action_items:116                    st.markdown(f"- {item}")117            else:118                st.write("No action items generated.")119        else:120            st.write(action_items)121 122        st.markdown("### Tags / Highlights")123        tags = final_state.get("tags") or []124        highlights = final_state.get("highlights") or []125        if tags:126            st.write("**Tags:** " + ", ".join([str(t) for t in tags]))127        else:128            st.write("**Tags:** None")129        if highlights:130            st.write("**Highlights:**")131            for h in highlights:132                st.markdown(f"- {h}")133        else:134            st.write("**Highlights:** None")135 136    with col2:137        st.markdown("### Scoring Rubric")138        quality_scores = final_state.get("quality_scores", {})139        if isinstance(quality_scores, dict) and quality_scores:140            import plotly.graph_objects as go141            for metric in ['tone', 'professionalism', 'structured_resolution']:142                if metric in quality_scores:143                    try:144                        val = float(quality_scores[metric])145                        fig = go.Figure(go.Indicator(146                            mode="gauge+number",147                            value=val,148                            title={'text': metric.replace('_', ' ').title(), 'font': {'size': 16, 'color': '#FFFFFF'}},149                            gauge={150                                'axis': {'range': [None, 10], 'tickwidth': 1, 'tickcolor': "#BB86FC"},151                                'bar': {'color': "#BB86FC"},152                                'bgcolor': "#1E1E1E",153                                'borderwidth': 2,154                                'bordercolor': "#333333",155                                'steps': [156                                    {'range': [0, 4], 'color': '#cf6679'},157                                    {'range': [4, 7], 'color': '#ffb74d'},158                                    {'range': [7, 10], 'color': '#81c784'}],159                            }160                        ))161                        # Adjust colors for dark theme162                        fig.update_layout(163                            height=180,164                            margin=dict(l=20, r=20, t=40, b=20),165                            paper_bgcolor='#1E1E1E',166                            plot_bgcolor='#1E1E1E',167                            font={'color': '#FFFFFF'}168                        )169                        st.plotly_chart(fig, width="stretch")170                    except (ValueError, TypeError):171                        st.write(f"**{metric.replace('_', ' ').title()}**: {quality_scores[metric]}")172 173            if "notes" in quality_scores:174                st.write("**Notes:**", quality_scores["notes"])175 176            if "rubric" in quality_scores and quality_scores["rubric"]:177                with st.expander("View scoring rubric", expanded=False):178                    rubric_rows = [179                        {180                            "Dimension": "Tone",181                            "0": "Hostile/arguing",182                            "3": "Curt/tense",183                            "5": "Neutral",184                            "7": "Friendly/empathic",185                            "10": "Consistently calm, respectful, de-escalating",186                        },187                        {188                            "Dimension": "Professionalism",189                            "0": "Rude/unprofessional",190                            "3": "Unclear or dismissive",191                            "5": "Acceptable",192                            "7": "Clear, courteous, policy-aligned",193                            "10": "Excellent clarity, appropriate boundaries, ownership",194                        },195                        {196                            "Dimension": "Structured resolution",197                            "0": "No attempt",198                            "3": "Vague / no next steps",199                            "5": "Partial (some questions/steps)",200                            "7": "Clear diagnosis + next steps + confirmation",201                            "10": "Fully structured (issue, actions, timelines, confirmation, closure)",202                        },203                    ]204 205                    st.dataframe(206                        rubric_rows,207                        hide_index=True,208                        use_container_width=True,209                    )210                    st.caption(211                        "Notes must cite 1–3 specific behaviors from the transcript (avoid long quotes)."212                    )213        else:214            st.write("No scoring rubric results generated.", quality_scores)215 216        st.markdown("### Metadata")217        if isinstance(metadata, dict) and metadata:218            for k, v in metadata.items():219                st.markdown(f"- **{k.replace('_', ' ').title()}**: {v}")220        else:221            st.write("No metadata available.")222 223 224def main():225    st.set_page_config(page_title="Call Center Data Analysis", layout="wide")226    apply_material_css()227 228    st.title("Call Center Data Analysis Dashboard")229 230    os.makedirs(PROCESSED_DIR, exist_ok=True)231    os.makedirs("tmp", exist_ok=True)232 233    if "view_mode" not in st.session_state:234        st.session_state.view_mode = "none"235 236    def set_upload_mode():237        st.session_state.view_mode = "upload"238 239    def set_dropdown_mode():240        st.session_state.view_mode = "dropdown"241 242    st.sidebar.header("Upload New File")243    uploaded_file = st.sidebar.file_uploader(244        "Upload a file",245        type=["mp3", "wav", "csv", "json"],246        on_change=set_upload_mode247    )248 249    st.sidebar.markdown("---")250    st.sidebar.header("Processed Files")251 252    # Get list of processed files253    processed_files = [f for f in os.listdir(PROCESSED_DIR) if f.endswith(".json")]254    processed_files.sort(reverse=True)  # Show newest (or reverse alphabetical) first255 256    selected_file = None257    if processed_files:258        options = ["-- Select a file --"] + processed_files259        selected_dropdown = st.sidebar.selectbox(260            "View cached results:",261            options,262            format_func=lambda x: x.replace(".json", "") if x != "-- Select a file --" else x,263            on_change=set_dropdown_mode264        )265        if selected_dropdown != "-- Select a file --":266            selected_file = selected_dropdown267    else:268        st.sidebar.info("No files processed yet.")269 270    st.sidebar.markdown("---")271    st.sidebar.header("Notes")272    st.sidebar.markdown("""273    Sample data:274    - [Customer Call Center Dataset Analysis](https://www.kaggle.com/datasets/rafaqatkhan608/customer-call-center-dataset-analysis/code/data)275    - [E-commerce Customer Support English Audio](https://huggingface.co/datasets/HumynLabs/e-commerce-customersupport-english-audio/tree/main)276    """)277 278    # Prioritize based on view_mode279    if st.session_state.view_mode == "upload" and uploaded_file is not None:280        st.success(f"File '{uploaded_file.name}' uploaded successfully!")281 282        from src.workflow import build_workflow283 284        file_path = os.path.join("tmp", uploaded_file.name)285        with open(file_path, "wb") as f:286            f.write(uploaded_file.getbuffer())287 288        file_extension = uploaded_file.name.split('.')[-1].lower()289 290        # Check if already processed to avoid reprocessing on rerun if same file is in uploader291        cached_json_path = os.path.join(PROCESSED_DIR, f"{uploaded_file.name}.json")292 293        if os.path.exists(cached_json_path):294            st.info("Loading cached results for this file...")295            with open(cached_json_path, 'r') as f:296                final_state = json.load(f)297            display_results(final_state)298        else:299            st.write("Processing file through LangGraph Workflow...")300            with st.spinner("Agents are analyzing the data..."):301                workflow = build_workflow()302                initial_state = {303                    "file_path": file_path,304                    "file_type": file_extension305                }306 307                thread_id = Path(file_path).name308                final_state = workflow.invoke(309                    initial_state, config={"configurable": {"thread_id": thread_id}}310                )311 312                if final_state.get("metadata", {}).get("intake_error"):313                    display_results(final_state)314                else:315                    # Cache the results316                    with open(cached_json_path, 'w') as f:317                        json.dump(final_state, f)318                    st.rerun()319 320            if not final_state.get("metadata", {}).get("intake_error"):321                display_results(final_state)322 323    elif (324            st.session_state.view_mode == "dropdown" or st.session_state.view_mode == "none") and selected_file is not None:325        st.info(f"Loading cached results for '{selected_file.replace('.json', '')}'")326        cached_json_path = os.path.join(PROCESSED_DIR, selected_file)327        with open(cached_json_path, 'r') as f:328            final_state = json.load(f)329        display_results(final_state)330 331    elif st.session_state.view_mode != "dropdown" and uploaded_file is not None:332        st.success(f"File '{uploaded_file.name}' uploaded successfully!")333 334        from src.workflow import build_workflow335 336        file_path = os.path.join("tmp", uploaded_file.name)337        with open(file_path, "wb") as f:338            f.write(uploaded_file.getbuffer())339 340        file_extension = uploaded_file.name.split('.')[-1].lower()341 342        cached_json_path = os.path.join(PROCESSED_DIR, f"{uploaded_file.name}.json")343 344        if os.path.exists(cached_json_path):345            st.info("Loading cached results for this file...")346            with open(cached_json_path, 'r') as f:347                final_state = json.load(f)348            display_results(final_state)349        else:350            st.write("Processing file through LangGraph Workflow...")351            with st.spinner("Agents are analyzing the data..."):352                workflow = build_workflow()353                initial_state = {354                    "file_path": file_path,355                    "file_type": file_extension356                }357                thread_id = Path(file_path).name358                final_state = workflow.invoke(359                    initial_state, config={"configurable": {"thread_id": thread_id}}360                )361                if final_state.get("metadata", {}).get("intake_error"):362                    display_results(final_state)363                else:364                    with open(cached_json_path, 'w') as f:365                        json.dump(final_state, f)366                    st.rerun()367            if not final_state.get("metadata", {}).get("intake_error"):368                display_results(final_state)369 370    else:371        st.info("Please upload a file or select a previously processed file.")372 373 374if __name__ == "__main__":375    main()376