SalesAI / app.py
Zasha1's picture
Update app.py
6495c45 verified
raw
history blame
14.6 kB
from streamlit_webrtc import webrtc_streamer, WebRtcMode
from sentiment_analysis import analyze_sentiment
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
from io import BytesIO
import wave
# Initialize components
objection_handler = ObjectionHandler('objections.csv')
product_recommender = ProductRecommender('recommendations.csv')
model = SentenceTransformer('all-MiniLM-L6-v2')
def real_time_analysis():
st.info("Listening... Say 'stop' to end the process.")
def audio_frame_callback(audio_frame):
# Convert audio frame to bytes
audio_bytes = audio_frame.to_ndarray().tobytes()
# Save audio bytes to a temporary WAV file
with BytesIO() as wav_buffer:
with wave.open(wav_buffer, 'wb') as wav_file:
wav_file.setnchannels(1) # Mono audio
wav_file.setsampwidth(2) # 2 bytes for int16
wav_file.setframerate(16000) # Sample rate
wav_file.writeframes(audio_bytes)
# Transcribe the audio
text = transcribe_audio(wav_buffer.getvalue())
if text:
st.write(f"*Recognized Text:* {text}")
# Analyze sentiment
sentiment, score = analyze_sentiment(text)
st.write(f"*Sentiment:* {sentiment} (Score: {score})")
# Handle objection
objection_response = handle_objection(text)
st.write(f"*Objection Response:* {objection_response}")
# Get product recommendation
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: # Similarity threshold
recommendations = product_recommender.get_recommendations(text)
if recommendations:
st.write("*Product Recommendations:*")
for rec in recommendations:
st.write(rec)
return audio_frame
# Start WebRTC audio stream
webrtc_ctx = webrtc_streamer(
key="real-time-audio",
mode=WebRtcMode.SENDONLY,
audio_frame_callback=audio_frame_callback,
media_stream_constraints={"audio": True, "video": False},
)
def transcribe_audio(audio_bytes):
"""Transcribe audio using a speech-to-text model or API."""
# Replace this with your actual speech-to-text implementation
# For now, we'll just return a dummy text
return "This is a placeholder transcription."
def handle_objection(text):
query_embedding = model.encode([text])
distances, indices = objection_handler.index.search(query_embedding, 1)
if distances[0][0] < 1.5: # Adjust similarity threshold as needed
responses = objection_handler.handle_objection(text)
return "\n".join(responses) if responses else "No objection response found."
return "No objection response found."
def is_valid_input(text):
text = text.strip().lower()
if len(text) < 3 or re.match(r'^[a-zA-Z\s]*$', text) is None:
return False
return True
def is_relevant_sentiment(sentiment_score):
return sentiment_score > 0.4
def run_app():
st.set_page_config(page_title="Sales Call Assistant", layout="wide")
st.title("AI Sales Call Assistant")
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")
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()
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}")
if __name__ == "__main__":
run_app()from streamlit_webrtc import webrtc_streamer, WebRtcMode
from sentiment_analysis import analyze_sentiment
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
from io import BytesIO
import wave
# Initialize components
objection_handler = ObjectionHandler('objections.csv')
product_recommender = ProductRecommender('recommendations.csv')
model = SentenceTransformer('all-MiniLM-L6-v2')
def real_time_analysis():
st.info("Listening... Say 'stop' to end the process.")
def audio_frame_callback(audio_frame):
# Convert audio frame to bytes
audio_bytes = audio_frame.to_ndarray().tobytes()
# Save audio bytes to a temporary WAV file
with BytesIO() as wav_buffer:
with wave.open(wav_buffer, 'wb') as wav_file:
wav_file.setnchannels(1) # Mono audio
wav_file.setsampwidth(2) # 2 bytes for int16
wav_file.setframerate(16000) # Sample rate
wav_file.writeframes(audio_bytes)
# Transcribe the audio
text = transcribe_audio(wav_buffer.getvalue())
if text:
st.write(f"*Recognized Text:* {text}")
# Analyze sentiment
sentiment, score = analyze_sentiment(text)
st.write(f"*Sentiment:* {sentiment} (Score: {score})")
# Handle objection
objection_response = handle_objection(text)
st.write(f"*Objection Response:* {objection_response}")
# Get product recommendation
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: # Similarity threshold
recommendations = product_recommender.get_recommendations(text)
if recommendations:
st.write("*Product Recommendations:*")
for rec in recommendations:
st.write(rec)
return audio_frame
# Start WebRTC audio stream
webrtc_ctx = webrtc_streamer(
key="real-time-audio",
mode=WebRtcMode.SENDONLY,
audio_frame_callback=audio_frame_callback,
media_stream_constraints={"audio": True, "video": False},
)
def transcribe_audio(audio_bytes):
"""Transcribe audio using a speech-to-text model or API."""
# Replace this with your actual speech-to-text implementation
# For now, we'll just return a dummy text
return "This is a placeholder transcription."
def handle_objection(text):
query_embedding = model.encode([text])
distances, indices = objection_handler.index.search(query_embedding, 1)
if distances[0][0] < 1.5: # Adjust similarity threshold as needed
responses = objection_handler.handle_objection(text)
return "\n".join(responses) if responses else "No objection response found."
return "No objection response found."
def is_valid_input(text):
text = text.strip().lower()
if len(text) < 3 or re.match(r'^[a-zA-Z\s]*$', text) is None:
return False
return True
def is_relevant_sentiment(sentiment_score):
return sentiment_score > 0.4
def run_app():
st.set_page_config(page_title="Sales Call Assistant", layout="wide")
st.title("AI Sales Call Assistant")
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")
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()
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}")
if __name__ == "__main__":
run_app()