Download app.py from Zasha1/SalesAI: direct link, hf CLI and curl.
- Browser
- Download file 15.9 kB
-
https://huggingface.co/spaces/Zasha1/SalesAI/resolve/main/app.py
- Command line
-
hf download hf://spaces/Zasha1/SalesAI/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Zasha1/SalesAI/resolve/main/app.py
15.9 kB
| from streamlit_webrtc import webrtc_streamer, WebRtcMode, AudioProcessorBase | |
| from sentiment_analysis import analyze_sentiment, transcribe_with_chunks | |
| from product_recommender import ProductRecommender | |
| from objection_handler import ObjectionHandler | |
| from google_sheets import fetch_call_data, store_data_in_sheet | |
| from sentence_transformers import SentenceTransformer | |
| from env_setup import config | |
| import re | |
| import uuid | |
| import pandas as pd | |
| import plotly.express as px | |
| import streamlit as st | |
| import numpy as np | |
| import queue | |
| import threading | |
| # Initialize components | |
| objection_handler = ObjectionHandler("objections.csv") | |
| product_recommender = ProductRecommender("recommendations.csv") | |
| model = SentenceTransformer('all-MiniLM-L6-v2') | |
| # Queue to hold transcribed text | |
| transcription_queue = queue.Queue() | |
| def generate_comprehensive_summary(chunks): | |
| # Your existing function implementation | |
| pass | |
| def is_valid_input(text): | |
| # Your existing function implementation | |
| pass | |
| def is_relevant_sentiment(sentiment_score): | |
| # Your existing function implementation | |
| pass | |
| def calculate_overall_sentiment(sentiment_scores): | |
| # Your existing function implementation | |
| pass | |
| def handle_objection(text): | |
| query_embedding = model.encode([text]) | |
| distances, indices = objection_handler.index.search(query_embedding, 1) | |
| if distances[0][0] < 1.5: | |
| responses = objection_handler.handle_objection(text) | |
| return "\n".join(responses) if responses else "No objection response found." | |
| return "No objection response found." | |
| class AudioProcessor(AudioProcessorBase): | |
| def __init__(self): | |
| self.sr = 16000 # Sample rate | |
| self.q = transcription_queue | |
| def recv(self, frame): | |
| audio_data = frame.to_ndarray() | |
| audio_bytes = (audio_data * 32767).astype(np.int16).tobytes() # Convert to int16 format | |
| print(f"Audio data shape: {audio_data.shape}") | |
| print(f"Audio data sample: {audio_data[:10]}") | |
| text = self.transcribe_audio(audio_bytes) | |
| if text: | |
| self.q.put(text) | |
| return frame | |
| def transcribe_audio(self, audio_bytes): | |
| try: | |
| chunks = transcribe_with_chunks({}) | |
| if chunks: | |
| return chunks[-1][0] | |
| except Exception as e: | |
| print(f"Error transcribing audio: {e}") | |
| return None | |
| def real_time_analysis(): | |
| st.info("Listening... Say 'stop' to end the process.") | |
| webrtc_ctx = webrtc_streamer( | |
| key="real-time-audio", | |
| mode=WebRtcMode.SENDONLY, | |
| audio_processor_factory=AudioProcessor, | |
| media_stream_constraints={"audio": True, "video": False}, | |
| ) | |
| if webrtc_ctx.state.playing: | |
| while not transcription_queue.empty(): | |
| text = transcription_queue.get() | |
| st.write(f"*Recognized Text:* {text}") | |
| sentiment, score = analyze_sentiment(text) | |
| st.write(f"*Sentiment:* {sentiment} (Score: {score})") | |
| objection_response = handle_objection(text) | |
| st.write(f"*Objection Response:* {objection_response}") | |
| recommendations = [] | |
| if is_valid_input(text) and is_relevant_sentiment(score): | |
| query_embedding = model.encode([text]) | |
| distances, indices = product_recommender.index.search(query_embedding, 1) | |
| if distances[0][0] < 1.5: | |
| recommendations = product_recommender.get_recommendations(text) | |
| if recommendations: | |
| st.write("*Product Recommendations:*") | |
| for rec in recommendations: | |
| st.write(rec) | |
| def fetch_data_and_display(): | |
| try: | |
| st.header("Call Summaries and Sentiment Analysis") | |
| data = fetch_call_data(config["google_sheet_id"]) | |
| print(f"Fetched data: {data}") # Log fetched data | |
| if data.empty: | |
| st.warning("No data available in the Google Sheet.") | |
| else: | |
| sentiment_counts = data['Sentiment'].value_counts() | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.subheader("Sentiment Distribution") | |
| fig_pie = px.pie( | |
| values=sentiment_counts.values, | |
| names=sentiment_counts.index, | |
| title='Call Sentiment Breakdown', | |
| color_discrete_map={ | |
| 'POSITIVE': 'green', | |
| 'NEGATIVE': 'red', | |
| 'NEUTRAL': 'blue' | |
| } | |
| ) | |
| st.plotly_chart(fig_pie) | |
| with col2: | |
| st.subheader("Sentiment Counts") | |
| fig_bar = px.bar( | |
| x=sentiment_counts.index, | |
| y=sentiment_counts.values, | |
| title='Number of Calls by Sentiment', | |
| labels={'x': 'Sentiment', 'y': 'Number of Calls'}, | |
| color=sentiment_counts.index, | |
| color_discrete_map={ | |
| 'POSITIVE': 'green', | |
| 'NEGATIVE': 'red', | |
| 'NEUTRAL': 'blue' | |
| } | |
| ) | |
| st.plotly_chart(fig_bar) | |
| st.subheader("All Calls") | |
| display_data = data.copy() | |
| display_data['Summary Preview'] = display_data['Summary'].str[:100] + '...' | |
| st.dataframe(display_data[['Call ID', 'Chunk', 'Sentiment', 'Summary Preview', 'Overall Sentiment']]) | |
| unique_call_ids = data[data['Call ID'] != '']['Call ID'].unique() | |
| call_id = st.selectbox("Select a Call ID to view details:", unique_call_ids) | |
| call_details = data[data['Call ID'] == call_id] | |
| if not call_details.empty: | |
| st.subheader("Detailed Call Information") | |
| st.write(f"**Call ID:** {call_id}") | |
| st.write(f"**Overall Sentiment:** {call_details.iloc[0]['Overall Sentiment']}") | |
| st.subheader("Full Call Summary") | |
| st.text_area("Summary:", | |
| value=call_details.iloc[0]['Summary'], | |
| height=200, | |
| disabled=True) | |
| st.subheader("Conversation Chunks") | |
| for _, row in call_details.iterrows(): | |
| if pd.notna(row['Chunk']): | |
| st.write(f"**Chunk:** {row['Chunk']}") | |
| st.write(f"**Sentiment:** {row['Sentiment']}") | |
| st.write("---") | |
| else: | |
| st.error("No details available for the selected Call ID.") | |
| except Exception as e: | |
| st.error(f"Error loading dashboard: {e}") | |
| def run_app(): | |
| st.set_page_config(page_title="Sales Call Assistant", layout="wide") | |
| st.title("AI Sales Call Assistant") | |
| st.warning("The space is currently not working due to issues with real-time transcription and data fetching from Google Sheets. We are working on resolving these issues.") | |
| st.markdown(""" | |
| <style> | |
| /* Header Container Styling */ | |
| .header-container { | |
| background: linear-gradient(135deg, #F8F9FA 0%, #E9ECEF 100%); | |
| padding: 20px; | |
| border-radius: 15px; | |
| margin-bottom: 30px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| /* Section Container Styling */ | |
| .section { | |
| background: linear-gradient(135deg, #FFFFFF 0%, #F8F9FA 100%); | |
| padding: 25px; | |
| border-radius: 15px; | |
| margin-bottom: 30px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| /* Header Text Styling */ | |
| .header { | |
| font-size: 2.5em; | |
| font-weight: 800; | |
| text-align: center; | |
| background: linear-gradient(120deg, #0D6EFD 0%, #0B5ED7 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| margin: 0; | |
| padding: 10px; | |
| letter-spacing: 1px; | |
| } | |
| /* Subheader Styling */ | |
| .subheader { | |
| font-size: 1.8em; | |
| font-weight: 600; | |
| background: linear-gradient(120deg, #0D6EFD 0%, #0B5ED7 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| margin-top: 20px; | |
| margin-bottom: 10px; | |
| text-align: left; | |
| } | |
| /* Table Container Styling */ | |
| .table-container { | |
| background: linear-gradient(135deg, #FFFFFF 0%, #F8F9FA 100%); | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin: 20px 0; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| /* Dark mode adjustments */ | |
| @media (prefers-color-scheme: dark) { | |
| .header-container { | |
| background: linear-gradient(135deg, #212529 0%, #343A40 100%); | |
| } | |
| .section { | |
| background: linear-gradient(135deg, #212529 0%, #2B3035 100%); | |
| } | |
| .table-container { | |
| background: linear-gradient(135deg, #212529 0%, #2B3035 100%); | |
| } | |
| .header { | |
| background: linear-gradient(120deg, #6EA8FE 0%, #9EC5FE 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .subheader { | |
| background: linear-gradient(120deg, #6EA8FE 0%, #9EC5FE 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| } | |
| /* Button Styling */ | |
| .stButton > button { | |
| background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%); | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 5px; | |
| transition: all 0.3s ease; | |
| } | |
| .stButton > button:hover { | |
| background: linear-gradient(135deg, #1976D2 0%, #1565C0 100%); | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); | |
| } | |
| /* Tab Styling */ | |
| .stTabs [data-baseweb="tab-list"] { | |
| gap: 24px; | |
| background: linear-gradient(135deg, #F8F9FA 0%, #E9ECEF 100%); | |
| padding: 10px; | |
| border-radius: 10px; | |
| } | |
| .stTabs [data-baseweb="tab"] { | |
| background-color: transparent; | |
| border-radius: 4px; | |
| color: #1976D2; | |
| font-weight: 600; | |
| padding: 10px 16px; | |
| } | |
| .stTabs [aria-selected="true"] { | |
| background: linear-gradient(120deg, #2196F3 0%, #1976D2 100%); | |
| color: white; | |
| } | |
| /* Dark mode tab adjustments */ | |
| @media (prefers-color-scheme: dark) { | |
| .stTabs [data-baseweb="tab-list"] { | |
| background: linear-gradient(135deg, #212529 0%, #343A40 100%); | |
| } | |
| .stTabs [data-baseweb="tab"] { | |
| color: #82B1FF; | |
| } | |
| .stTabs [aria-selected="true"] { | |
| background: linear-gradient(120deg, #448AFF 0%, #2979FF 100%); | |
| } | |
| } | |
| /* Message Styling */ | |
| .success { | |
| background: linear-gradient(135deg, #43A047 0%, #2E7D32 100%); | |
| color: white; | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin: 10px 0; | |
| } | |
| .error { | |
| background: linear-gradient(135deg, #E53935 0%, #C62828 100%); | |
| color: white; | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin: 10px 0; | |
| } | |
| .warning { | |
| background: linear-gradient(135deg, #FB8C00 0%, #F57C00 100%); | |
| color: white; | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin: 10px 0; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| st.sidebar.title("Navigation") | |
| app_mode = st.sidebar.radio("Choose a mode:", ["Real-Time Call Analysis", "Dashboard"]) | |
| if app_mode == "Real-Time Call Analysis": | |
| st.header("Real-Time Sales Call Analysis") | |
| if st.button("Start Listening"): | |
| real_time_analysis() | |
| elif app_mode == "Dashboard": | |
| st.header("Call Summaries and Sentiment Analysis") | |
| try: | |
| data = fetch_call_data(config["google_sheet_id"]) | |
| if data.empty: | |
| st.warning("No data available in the Google Sheet.") | |
| else: | |
| sentiment_counts = data['Sentiment'].value_counts() | |
| product_mentions = filter_product_mentions(data[['Chunk']].values.tolist(), product_titles) | |
| product_mentions_df = pd.DataFrame(list(product_mentions.items()), columns=['Product', 'Count']) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.subheader("Sentiment Distribution") | |
| fig_bar = px.bar( | |
| x=sentiment_counts.index, | |
| y=sentiment_counts.values, | |
| title='Number of Calls by Sentiment', | |
| labels={'x': 'Sentiment', 'y': 'Number of Calls'}, | |
| color=sentiment_counts.index, | |
| color_discrete_map={ | |
| 'POSITIVE': 'green', | |
| 'NEGATIVE': 'red', | |
| 'NEUTRAL': 'blue' | |
| } | |
| ) | |
| st.plotly_chart(fig_bar) | |
| with col2: | |
| st.subheader("Most Mentioned Products") | |
| fig_products = px.pie( | |
| values=product_mentions_df['Count'], | |
| names=product_mentions_df['Product'], | |
| title='Most Mentioned Products' | |
| ) | |
| st.plotly_chart(fig_products) | |
| st.subheader("All Calls") | |
| display_data = data.copy() | |
| display_data['Summary Preview'] = display_data['Summary'].str[:100] + '...' | |
| st.dataframe(display_data[['Call ID', 'Chunk', 'Sentiment', 'Summary Preview', 'Overall Sentiment']]) | |
| unique_call_ids = data[data['Call ID'] != '']['Call ID'].unique() | |
| call_id = st.selectbox("Select a Call ID to view details:", unique_call_ids) | |
| call_details = data[data['Call ID'] == call_id] | |
| if not call_details.empty: | |
| st.subheader("Detailed Call Information") | |
| st.write(f"**Call ID:** {call_id}") | |
| st.write(f"**Overall Sentiment:** {call_details.iloc[0]['Overall Sentiment']}") | |
| st.subheader("Full Call Summary") | |
| st.text_area("Summary:", | |
| value=call_details.iloc[0]['Summary'], | |
| height=200, | |
| disabled=True) | |
| else: | |
| st.error("No details available for the selected Call ID.") | |
| except Exception as e: | |
| st.error(f"Error loading dashboard: {e}") | |
| if __name__ == "__main__": | |
| run_app() |