Zasha1 commited on
Commit
a616221
·
verified ·
1 Parent(s): 071fde5

Upload 12 files

Browse files
ai-sales-call-assistant-457770399a34.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "service_account",
3
+ "project_id": "ai-sales-call-assistant",
4
+ "private_key_id": "457770399a34b7fd34c166b27b25e0db381d32a8",
5
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCqjFfvqapmJXtt\nG5NXZ8FoyxyCMflUI8EtyGiDjwuFsuHK6sQ6ZoMD/dWObHJH8p4kiiU+OvUSa5gM\nt8zYPnjpgV3FwU1opPL1kGFyip+MZxpbL0aY7rmrROUJKHXULFyjnKiWfMQqlxYk\nFAULWlPfkTM0abAJg6cSMSuZPo2Unb9yGhFdQObBL0Wg4QfulcKGMQGRmOvJsOwf\nCy+tUfZ9dAc0kKCnH5fZu6+/38Mq0Kc8IzJzgJwB+1VWvRfvKdSOI9oVw3nk3Ycm\nCpBMYv/mVybkSaDR1JSM3+7MLRDm53vCucB2HA+0Xui1WbHwbCAsUw+aHORcARLs\nxpRh0OelAgMBAAECggEAI1Tpzsu+cmTnegYRczUae2RAprRFq+mwVpTDGiYjQ/J4\nFnqmZlbgY45NlLDgyAj6PCWma4r5RHSnzlKxjEb885sKWzKdn8U0VC0yEvGm9gZS\nDnvvyzUBn/qo3EnWhzsdggOtZWe5l/0u6BCBrwVqhNFm4z/V6VKt5PXsy1WLLTNe\nROi8E9ezhRGfsn7oYa0uy8UEgddUeEUhLpFoX6WRO13e80hKCGk72tEL2MXAa8lR\nE/LtN9tQl/NlhMGHyB7+OyXkthZxCZaSzVhOV4K31uY4JMPvnKdNBrusjhj1KLGv\njb0QiKuoPFDhqMjIDwFv0xG2AjcCkdjqd5FqaQDdrQKBgQDc9gH6d8iatdW6ipo8\nL3/a93SNGQ9CPBx3+EF7zpbh24dEIaItjUSqFwLBFM84yysB0DU6HPj1MrZ3OtIe\njCjXYHlbn9fRz8gmps54RyoOaEvr9nauOej4MQ4tkn7cDIv2kVvewMRGcEO2DRXJ\nLg9mwxjxMVWSoy+A1P9LhjVqWwKBgQDFl8+9z/9tT0OoFZruWpz+ozKisCoPOp+l\nMz1+rYnB0h4LSY0TBPoqy296WkIDc/WTrHPlnmC5vEgjvT5v3KT3yHd0fIkw9AmM\nF59He6Ihj+FGrRxYO1xadVYdSCIHy4pCGOZLpKInWuZ1sdEQVzw4gYCxcfrMb4mO\nUAuUTy+V/wKBgFiguFRtnWIo00yacZj4eHEs1mwOBCfOEqEwS5vMVorLUitKzlE1\nG7iZuDoYDbI7E8oLaH4hxt4a9ENIraUhFPSmtqbAq4F1tVODjseBy+Wxgdpoplvl\n0INUsdonq4i5454H2fC0I0YZm583CmkCd50BXkzIPAmwOMqVJL13XI+HAoGAf4NB\n0BeLmcoeOjl/GzSkvfspcS3IZr2JSv3vQHHTRZ5IPzZ+8Pg0TSutzEK0+S97Goqe\n3F7BwvsLfuzgfyXf2/ulgynfCxVhl+OiqWnSrmAAnDCY6yObrNCt+wWS2H70wUT6\nUXR0JHuX3/oZlbcGKN0B5QFOPWH5Xjqvzkzvw5cCgYAlsZVJGakTmN6EEjYi/OwM\nUMQfuY6CPLD3kj57sezyW4fHKXwTkQ0y7fmHV4opQmuNbU9o1FBIVvZr03oBgXbm\n60K5qTE6JCKj7mPM9nOGl3327+DPGmIrQus+CsPPi6j8CLu4AXkmOhZ0aR2+Hy4e\nRLzsKUU3oGJ7pa1xFEGwJg==\n-----END PRIVATE KEY-----\n",
6
+ "client_email": "sales-bot-service@ai-sales-call-assistant.iam.gserviceaccount.com",
7
+ "client_id": "107478451989301946230",
8
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
9
+ "token_uri": "https://oauth2.googleapis.com/token",
10
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
11
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/sales-bot-service%40ai-sales-call-assistant.iam.gserviceaccount.com",
12
+ "universe_domain": "googleapis.com"
13
+ }
app.py ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import speech_recognition as sr
2
+ from sentiment_analysis import analyze_sentiment
3
+ from product_recommender import ProductRecommender
4
+ from objection_handler import ObjectionHandler
5
+ from google_sheets import fetch_call_data, store_data_in_sheet
6
+ from sentence_transformers import SentenceTransformer
7
+ from env_setup import config
8
+ import re
9
+ import uuid
10
+ from google.oauth2 import service_account
11
+ from googleapiclient.discovery import build
12
+ import pandas as pd
13
+ import plotly.express as px
14
+ import plotly.graph_objs as go
15
+ import streamlit as st
16
+
17
+ # Initialize components
18
+ product_recommender = ProductRecommender(r"C:\Users\shaik\Downloads\Sales Calls Transcriptions - Sheet2.csv")
19
+ objection_handler = ObjectionHandler(r"C:\Users\shaik\Downloads\Sales Calls Transcriptions - Sheet3.csv")
20
+ model = SentenceTransformer('all-MiniLM-L6-v2')
21
+
22
+ def generate_comprehensive_summary(chunks):
23
+ """
24
+ Generate a comprehensive summary from conversation chunks
25
+ """
26
+ # Extract full text from chunks
27
+ full_text = " ".join([chunk[0] for chunk in chunks])
28
+
29
+ # Perform basic analysis
30
+ total_chunks = len(chunks)
31
+ sentiments = [chunk[1] for chunk in chunks]
32
+
33
+ # Determine overall conversation context
34
+ context_keywords = {
35
+ 'product_inquiry': ['dress', 'product', 'price', 'stock'],
36
+ 'pricing': ['cost', 'price', 'budget'],
37
+ 'negotiation': ['installment', 'payment', 'manage']
38
+ }
39
+
40
+ # Detect conversation themes
41
+ themes = []
42
+ for keyword_type, keywords in context_keywords.items():
43
+ if any(keyword.lower() in full_text.lower() for keyword in keywords):
44
+ themes.append(keyword_type)
45
+
46
+ # Basic sentiment analysis
47
+ positive_count = sentiments.count('POSITIVE')
48
+ negative_count = sentiments.count('NEGATIVE')
49
+ neutral_count = sentiments.count('NEUTRAL')
50
+
51
+ # Key interaction highlights
52
+ key_interactions = []
53
+ for chunk in chunks:
54
+ if any(keyword.lower() in chunk[0].lower() for keyword in ['price', 'dress', 'stock', 'installment']):
55
+ key_interactions.append(chunk[0])
56
+
57
+ # Construct summary
58
+ summary = f"Conversation Summary:\n"
59
+
60
+ # Context and themes
61
+ if 'product_inquiry' in themes:
62
+ summary += "• Customer initiated a product inquiry about items.\n"
63
+
64
+ if 'pricing' in themes:
65
+ summary += "• Price and budget considerations were discussed.\n"
66
+
67
+ if 'negotiation' in themes:
68
+ summary += "• Customer and seller explored flexible payment options.\n"
69
+
70
+ # Sentiment insights
71
+ summary += f"\nConversation Sentiment:\n"
72
+ summary += f"• Positive Interactions: {positive_count}\n"
73
+ summary += f"• Negative Interactions: {negative_count}\n"
74
+ summary += f"• Neutral Interactions: {neutral_count}\n"
75
+
76
+ # Key highlights
77
+ summary += "\nKey Conversation Points:\n"
78
+ for interaction in key_interactions[:3]: # Limit to top 3 key points
79
+ summary += f"• {interaction}\n"
80
+
81
+ # Conversation outcome
82
+ if positive_count > negative_count:
83
+ summary += "\nOutcome: Constructive and potentially successful interaction."
84
+ elif negative_count > positive_count:
85
+ summary += "\nOutcome: Interaction may require further follow-up."
86
+ else:
87
+ summary += "\nOutcome: Neutral interaction with potential for future engagement."
88
+
89
+ return summary
90
+
91
+ def is_valid_input(text):
92
+ text = text.strip().lower()
93
+ if len(text) < 3 or re.match(r'^[a-zA-Z\s]*$', text) is None:
94
+ return False
95
+ return True
96
+
97
+ def is_relevant_sentiment(sentiment_score):
98
+ return sentiment_score > 0.4
99
+
100
+ def calculate_overall_sentiment(sentiment_scores):
101
+ if sentiment_scores:
102
+ average_sentiment = sum(sentiment_scores) / len(sentiment_scores)
103
+ overall_sentiment = (
104
+ "POSITIVE" if average_sentiment > 0 else
105
+ "NEGATIVE" if average_sentiment < 0 else
106
+ "NEUTRAL"
107
+ )
108
+ else:
109
+ overall_sentiment = "NEUTRAL"
110
+ return overall_sentiment
111
+
112
+ def real_time_analysis():
113
+ recognizer = sr.Recognizer()
114
+ mic = sr.Microphone()
115
+
116
+ st.info("Say 'stop' to end the process.")
117
+
118
+ sentiment_scores = []
119
+ transcribed_chunks = []
120
+ total_text = ""
121
+
122
+ try:
123
+ while True:
124
+ with mic as source:
125
+ st.write("Listening...")
126
+ recognizer.adjust_for_ambient_noise(source)
127
+ audio = recognizer.listen(source)
128
+
129
+ try:
130
+ st.write("Recognizing...")
131
+ text = recognizer.recognize_google(audio)
132
+ st.write(f"*Recognized Text:* {text}")
133
+
134
+ if 'stop' in text.lower():
135
+ st.write("Stopping real-time analysis...")
136
+ break
137
+
138
+ # Append to the total conversation
139
+ total_text += text + " "
140
+ sentiment, score = analyze_sentiment(text)
141
+ sentiment_scores.append(score)
142
+
143
+ # Handle objection
144
+ objection_response = handle_objection(text)
145
+
146
+ # Get product recommendation
147
+ recommendations = []
148
+ if is_valid_input(text) and is_relevant_sentiment(score):
149
+ query_embedding = model.encode([text])
150
+ distances, indices = product_recommender.index.search(query_embedding, 1)
151
+
152
+ if distances[0][0] < 1.5: # Similarity threshold
153
+ recommendations = product_recommender.get_recommendations(text)
154
+
155
+ transcribed_chunks.append((text, sentiment, score))
156
+
157
+ st.write(f"*Sentiment:* {sentiment} (Score: {score})")
158
+ st.write(f"*Objection Response:* {objection_response}")
159
+
160
+ if recommendations:
161
+ st.write("*Product Recommendations:*")
162
+ for rec in recommendations:
163
+ st.write(rec)
164
+
165
+ except sr.UnknownValueError:
166
+ st.error("Speech Recognition could not understand the audio.")
167
+ except sr.RequestError as e:
168
+ st.error(f"Error with the Speech Recognition service: {e}")
169
+ except Exception as e:
170
+ st.error(f"Error during processing: {e}")
171
+
172
+ # After conversation ends, calculate and display overall sentiment and summary
173
+ overall_sentiment = calculate_overall_sentiment(sentiment_scores)
174
+ call_summary = generate_comprehensive_summary(transcribed_chunks)
175
+
176
+ st.subheader("Conversation Summary:")
177
+ st.write(total_text.strip())
178
+ st.subheader("Overall Sentiment:")
179
+ st.write(overall_sentiment)
180
+
181
+ # Store data in Google Sheets
182
+ store_data_in_sheet(
183
+ config["google_sheet_id"],
184
+ transcribed_chunks,
185
+ call_summary,
186
+ overall_sentiment
187
+ )
188
+ st.success("Conversation data stored successfully in Google Sheets!")
189
+
190
+ except Exception as e:
191
+ st.error(f"Error in real-time analysis: {e}")
192
+
193
+ def handle_objection(text):
194
+ query_embedding = model.encode([text])
195
+ distances, indices = objection_handler.index.search(query_embedding, 1)
196
+ if distances[0][0] < 1.5: # Adjust similarity threshold as needed
197
+ responses = objection_handler.handle_objection(text)
198
+ return "\n".join(responses) if responses else "No objection response found."
199
+ return "No objection response found."
200
+
201
+ # (Previous imports remain the same)
202
+
203
+ def run_app():
204
+ st.set_page_config(page_title="Sales Call Assistant", layout="wide")
205
+ st.title("AI Sales Call Assistant")
206
+
207
+ st.sidebar.title("Navigation")
208
+ app_mode = st.sidebar.radio("Choose a mode:", ["Real-Time Call Analysis", "Dashboard"])
209
+
210
+ if app_mode == "Real-Time Call Analysis":
211
+ st.header("Real-Time Sales Call Analysis")
212
+ if st.button("Start Listening"):
213
+ real_time_analysis()
214
+
215
+ elif app_mode == "Dashboard":
216
+ st.header("Call Summaries and Sentiment Analysis")
217
+ try:
218
+ data = fetch_call_data(config["google_sheet_id"])
219
+ if data.empty:
220
+ st.warning("No data available in the Google Sheet.")
221
+ else:
222
+ # Sentiment Visualizations
223
+ sentiment_counts = data['Sentiment'].value_counts()
224
+
225
+ # Pie Chart
226
+ col1, col2 = st.columns(2)
227
+ with col1:
228
+ st.subheader("Sentiment Distribution")
229
+ fig_pie = px.pie(
230
+ values=sentiment_counts.values,
231
+ names=sentiment_counts.index,
232
+ title='Call Sentiment Breakdown',
233
+ color_discrete_map={
234
+ 'POSITIVE': 'green',
235
+ 'NEGATIVE': 'red',
236
+ 'NEUTRAL': 'blue'
237
+ }
238
+ )
239
+ st.plotly_chart(fig_pie)
240
+
241
+ # Bar Chart
242
+ with col2:
243
+ st.subheader("Sentiment Counts")
244
+ fig_bar = px.bar(
245
+ x=sentiment_counts.index,
246
+ y=sentiment_counts.values,
247
+ title='Number of Calls by Sentiment',
248
+ labels={'x': 'Sentiment', 'y': 'Number of Calls'},
249
+ color=sentiment_counts.index,
250
+ color_discrete_map={
251
+ 'POSITIVE': 'green',
252
+ 'NEGATIVE': 'red',
253
+ 'NEUTRAL': 'blue'
254
+ }
255
+ )
256
+ st.plotly_chart(fig_bar)
257
+
258
+ # Existing Call Details Section
259
+ st.subheader("All Calls")
260
+ display_data = data.copy()
261
+ display_data['Summary Preview'] = display_data['Summary'].str[:100] + '...'
262
+ st.dataframe(display_data[['Call ID', 'Chunk', 'Sentiment', 'Summary Preview', 'Overall Sentiment']])
263
+
264
+ # Dropdown to select Call ID
265
+ unique_call_ids = data[data['Call ID'] != '']['Call ID'].unique()
266
+ call_id = st.selectbox("Select a Call ID to view details:", unique_call_ids)
267
+
268
+ # Display selected Call ID details
269
+ call_details = data[data['Call ID'] == call_id]
270
+ if not call_details.empty:
271
+ st.subheader("Detailed Call Information")
272
+ st.write(f"**Call ID:** {call_id}")
273
+ st.write(f"**Overall Sentiment:** {call_details.iloc[0]['Overall Sentiment']}")
274
+
275
+ # Expand summary section
276
+ st.subheader("Full Call Summary")
277
+ st.text_area("Summary:",
278
+ value=call_details.iloc[0]['Summary'],
279
+ height=200,
280
+ disabled=True)
281
+
282
+ # Show all chunks for the selected call
283
+ st.subheader("Conversation Chunks")
284
+ for _, row in call_details.iterrows():
285
+ if pd.notna(row['Chunk']):
286
+ st.write(f"**Chunk:** {row['Chunk']}")
287
+ st.write(f"**Sentiment:** {row['Sentiment']}")
288
+ st.write("---") # Separator between chunks
289
+ else:
290
+ st.error("No details available for the selected Call ID.")
291
+ except Exception as e:
292
+ st.error(f"Error loading dashboard: {e}")
293
+
294
+ if __name__ == "__main__":
295
+ run_app()
env_setup.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ load_dotenv()
5
+
6
+ config = {
7
+ "google_creds": os.getenv("google_creds"),
8
+ "huggingface_api_key": os.getenv("huggingface_api_key"),
9
+ "google_sheet_id": os.getenv("google_sheet_id"),
10
+ "vosk_model_path": os.getenv("vosk_model_path"),
11
+ "PRODUCT_DATA_PATH": os.getenv("PRODUCT_DATA_PATH"),
12
+ "OBJECTION_DATA_PATH": os.getenv("OBJECTION_DATA_PATH"),
13
+ "cohere_api_key": os.getenv("cohere_api_key")
14
+ }
google_sheets.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ from google.oauth2 import service_account
3
+ from googleapiclient.discovery import build
4
+ from env_setup import config
5
+ import pandas as pd
6
+
7
+ SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
8
+
9
+ def authenticate_google_account():
10
+ service_account_file = config["google_creds"]
11
+ if not service_account_file:
12
+ raise ValueError("Service account credentials path is missing in env_setup.py.")
13
+ return service_account.Credentials.from_service_account_file(service_account_file, scopes=SCOPES)
14
+
15
+ def store_data_in_sheet(sheet_id, chunks, summary, overall_sentiment):
16
+ creds = authenticate_google_account()
17
+ service = build('sheets', 'v4', credentials=creds)
18
+ sheet = service.spreadsheets()
19
+
20
+ call_id = str(uuid.uuid4())
21
+ print(f"Call ID: {call_id}")
22
+
23
+ values = []
24
+ if chunks:
25
+ first_chunk, first_sentiment, _ = chunks[0]
26
+ values.append([call_id, first_chunk, first_sentiment, summary, overall_sentiment])
27
+ for chunk, sentiment, _ in chunks[1:]:
28
+ values.append(["", chunk, sentiment, "", ""])
29
+
30
+ header = ["Call ID", "Chunk", "Sentiment", "Summary", "Overall Sentiment"]
31
+ all_values = [header] + values
32
+
33
+ body = {'values': all_values}
34
+ try:
35
+ result = sheet.values().append(
36
+ spreadsheetId=sheet_id,
37
+ range="Sheet1!A1",
38
+ valueInputOption="RAW",
39
+ body=body
40
+ ).execute()
41
+ print(f"{result.get('updates').get('updatedCells')} cells updated in Google Sheets.")
42
+ except Exception as e:
43
+ print(f"Error updating Google Sheets: {e}")
44
+
45
+ def fetch_call_data(sheet_id, sheet_range="Sheet1!A1:E"):
46
+ """
47
+ Fetches data from the specified Google Sheet and returns a pandas DataFrame.
48
+
49
+ :param sheet_id: The ID of the Google Sheet to fetch data from.
50
+ :param sheet_range: The range in A1 notation to fetch data from.
51
+ :return: pandas DataFrame with the sheet data.
52
+ """
53
+ try:
54
+ # Authenticate and get credentials
55
+ creds = authenticate_google_account()
56
+ service = build('sheets', 'v4', credentials=creds)
57
+ sheet = service.spreadsheets()
58
+
59
+ # Fetch the data
60
+ result = sheet.values().get(
61
+ spreadsheetId=sheet_id,
62
+ range=sheet_range
63
+ ).execute()
64
+
65
+ # Get the rows
66
+ rows = result.get("values", [])
67
+
68
+ # If rows exist, convert to DataFrame
69
+ if rows:
70
+ # Use the first row as column headers
71
+ headers = rows[0]
72
+ data = rows[1:]
73
+
74
+ # Create DataFrame
75
+ df = pd.DataFrame(data, columns=headers)
76
+
77
+ return df
78
+ else:
79
+ # Return an empty DataFrame if no data
80
+ return pd.DataFrame()
81
+
82
+ except Exception as e:
83
+ print(f"Error fetching data from Google Sheets: {e}")
84
+ # Return an empty DataFrame in case of error
85
+ return pd.DataFrame()
main.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pyaudio
3
+ import json
4
+ from vosk import Model, KaldiRecognizer
5
+ from transformers import pipeline, AutoModelForSequenceClassification, AutoTokenizer
6
+ from sentence_transformers import SentenceTransformer
7
+ import time
8
+ import pandas as pd
9
+ from dotenv import load_dotenv
10
+ import os
11
+ import numpy as np
12
+
13
+ def cosine_similarity(a, b):
14
+ return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
15
+
16
+ class SalesAnalysisApp:
17
+ def __init__(self):
18
+ model_name = "tabularisai/multilingual-sentiment-analysis"
19
+ model = AutoModelForSequenceClassification.from_pretrained(model_name)
20
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
21
+ self.sentiment_analyzer = pipeline("sentiment-analysis", model=model, tokenizer=tokenizer)
22
+
23
+ vosk_model_path = os.getenv("VOSK_MODEL_PATH")
24
+ self.vosk_model = Model(vosk_model_path)
25
+ self.recognizer = KaldiRecognizer(self.vosk_model, 16000)
26
+
27
+ self.audio = pyaudio.PyAudio()
28
+ self.stream = None
29
+
30
+ self.product_data = pd.read_csv(r"C:\Users\shaik\Downloads\Sales Calls Transcriptions - Sheet2.csv")
31
+ self.objection_data = pd.read_csv(r"C:\Users\shaik\Downloads\Sales Calls Transcriptions - Sheet3.csv")
32
+
33
+ self.sentence_model = SentenceTransformer('all-MiniLM-L6-v2')
34
+
35
+ def get_recommendations(self, text):
36
+ text_embedding = self.sentence_model.encode([text])
37
+ product_embeddings = self.sentence_model.encode(self.product_data['Description'].tolist())
38
+
39
+ similarities = [cosine_similarity(text_embedding[0], prod_emb) for prod_emb in product_embeddings]
40
+ top_indices = np.argsort(similarities)[-5:][::-1]
41
+ return self.product_data.iloc[top_indices]['Product'].tolist()
42
+
43
+ def get_objection_response(self, text):
44
+ text_embedding = self.sentence_model.encode([text])
45
+ objection_embeddings = self.sentence_model.encode(self.objection_data['Objection'].tolist())
46
+
47
+ similarities = [cosine_similarity(text_embedding[0], obj_emb) for obj_emb in objection_embeddings]
48
+ max_similarity = max(similarities)
49
+ if max_similarity > 0.5:
50
+ top_idx = np.argmax(similarities)
51
+ return self.objection_data.iloc[top_idx]['Response']
52
+ return None
53
+
54
+ # Rest of the code remains the same...
55
+ def analyze_sentiment(self, text):
56
+ if not text.strip():
57
+ return "NEUTRAL", 0.0
58
+ result = self.sentiment_analyzer(text.strip().lower())[0]
59
+ sentiment_map = {
60
+ 'Very Negative': "NEGATIVE",
61
+ 'Negative': "NEGATIVE",
62
+ 'Neutral': "NEUTRAL",
63
+ 'Positive': "POSITIVE",
64
+ 'Very Positive': "POSITIVE"
65
+ }
66
+ return sentiment_map.get(result['label'], "NEUTRAL"), result['score']
67
+
68
+ def run_app(self):
69
+ st.title("Real-time Sales Call Analysis")
70
+
71
+ if st.button("Start Recording"):
72
+ self.stream = self.audio.open(format=pyaudio.paInt16,
73
+ channels=1,
74
+ rate=16000,
75
+ input=True,
76
+ frames_per_buffer=4000)
77
+
78
+ transcript_placeholder = st.empty()
79
+ sentiment_placeholder = st.empty()
80
+ recommendations_placeholder = st.empty()
81
+ objections_placeholder = st.empty()
82
+
83
+ try:
84
+ while True:
85
+ data = self.stream.read(4000, exception_on_overflow=False)
86
+
87
+ if self.recognizer.AcceptWaveform(data):
88
+ result = json.loads(self.recognizer.Result())
89
+ text = result["text"]
90
+
91
+ if text:
92
+ transcript_placeholder.write(f"Transcription: {text}")
93
+
94
+ sentiment, score = self.analyze_sentiment(text)
95
+ sentiment_placeholder.write(f"Sentiment: {sentiment} (Score: {score:.2f})")
96
+
97
+ recommendations = self.get_recommendations(text)
98
+ if recommendations:
99
+ recommendations_placeholder.write("Product Recommendations:")
100
+ for rec in recommendations:
101
+ recommendations_placeholder.write(f"- {rec}")
102
+
103
+ objection_response = self.get_objection_response(text)
104
+ if objection_response:
105
+ objections_placeholder.write(f"Suggested Response: {objection_response}")
106
+
107
+ time.sleep(0.1)
108
+
109
+ except Exception as e:
110
+ st.error(f"Error: {str(e)}")
111
+ if self.stream:
112
+ self.stream.stop_stream()
113
+ self.stream.close()
114
+
115
+ if __name__ == "__main__":
116
+ app = SalesAnalysisApp()
117
+ app.run_app()
ml_insights.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import seaborn as sns
5
+ from sklearn.linear_model import LinearRegression
6
+ import streamlit as st
7
+
8
+ def generate_insights(call_data):
9
+ """
10
+ Generate ML insights and visualizations from call data
11
+ """
12
+ # Convert call data to DataFrame
13
+ df = pd.DataFrame(call_data)
14
+
15
+ # Sentiment distribution pie chart
16
+ plt.figure(figsize=(10, 6))
17
+ sentiment_counts = df['sentiment'].value_counts()
18
+ plt.pie(sentiment_counts, labels=sentiment_counts.index, autopct='%1.1f%%')
19
+ plt.title('Sentiment Distribution')
20
+ st.pyplot(plt)
21
+ plt.close()
22
+
23
+ # Calculate sentiment trend
24
+ df['sentiment_numeric'] = df['sentiment'].map({'POSITIVE': 1, 'NEGATIVE': -1, 'NEUTRAL': 0})
25
+
26
+ # Simple trend analysis
27
+ X = np.array(range(len(df))).reshape(-1, 1)
28
+ y = df['sentiment_numeric'].values
29
+
30
+ model = LinearRegression()
31
+ model.fit(X, y)
32
+
33
+ # Predict trend
34
+ trend_score = model.coef_[0]
35
+ trend_interpretation = (
36
+ "Improving" if trend_score > 0.1 else
37
+ "Declining" if trend_score < -0.1 else
38
+ "Stable"
39
+ )
40
+
41
+ # Summary metrics
42
+ st.subheader("Call Analysis Summary")
43
+ st.write(f"Total Calls: {len(df)}")
44
+ st.write("Sentiment Breakdown:")
45
+ st.write(sentiment_counts)
46
+ st.write(f"Sentiment Trend: {trend_interpretation}")
47
+
48
+ def main():
49
+ st.title("Sales Call Insights")
50
+
51
+ # Placeholder for loading data mechanism
52
+ st.write("Insights generation ready.")
53
+
54
+ if __name__ == "__main__":
55
+ main()
objection_handler.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from sentence_transformers import SentenceTransformer
3
+ import faiss
4
+
5
+ def load_objections(file_path):
6
+ """Load objections from a CSV file into a dictionary."""
7
+ try:
8
+ objections_df = pd.read_csv(file_path)
9
+ objections_dict = {}
10
+ for index, row in objections_df.iterrows():
11
+ objections_dict[row['Customer Objection']] = row['Salesperson Response']
12
+ return objections_dict
13
+ except Exception as e:
14
+ print(f"Error loading objections: {e}")
15
+ return {}
16
+
17
+ def check_objections(text, objections_dict):
18
+ """Check for objections in the given text and return responses."""
19
+ responses = []
20
+ for objection, response in objections_dict.items():
21
+ if objection.lower() in text.lower():
22
+ responses.append(response)
23
+ return responses
24
+
25
+ class ObjectionHandler:
26
+ def __init__(self, objection_data_path):
27
+ self.data = pd.read_csv(objection_data_path)
28
+ self.model = SentenceTransformer('all-MiniLM-L6-v2')
29
+ self.embeddings = self.model.encode(self.data['Customer Objection'].tolist())
30
+ self.index = faiss.IndexFlatL2(self.embeddings.shape[1])
31
+ self.index.add(self.embeddings)
32
+
33
+ def handle_objection(self, query, top_n=1):
34
+ """Handle objections using embeddings."""
35
+ query_embedding = self.model.encode([query])
36
+ distances, indices = self.index.search(query_embedding, top_n)
37
+ responses = []
38
+ for i in indices[0]:
39
+ responses.append(self.data.iloc[i]['Salesperson Response'])
40
+ return responses
product_recommender.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from sentence_transformers import SentenceTransformer
3
+ import faiss
4
+
5
+ class ProductRecommender:
6
+ def __init__(self, product_data_path):
7
+ self.data = pd.read_csv(product_data_path)
8
+ self.model = SentenceTransformer('all-MiniLM-L6-v2')
9
+ self.embeddings = self.model.encode(self.data['product_description'].tolist())
10
+ self.index = faiss.IndexFlatL2(self.embeddings.shape[1])
11
+ self.index.add(self.embeddings)
12
+
13
+ def get_recommendations(self, query, top_n=5):
14
+ query_embedding = self.model.encode([query])
15
+ distances, indices = self.index.search(query_embedding, top_n)
16
+ recommendations = []
17
+ for i in indices[0]:
18
+ recommendations.append(self.data.iloc[i]['product_title'] + ": " + self.data.iloc[i]['product_description'])
19
+ return recommendations
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ google-api-python-client==2.98.0
2
+ google-auth-httplib2==0.1.0
3
+ google-auth-oauthlib==0.6.0
4
+ huggingface-hub==0.16.4
5
+ transformers==4.32.0
6
+ torch==2.1.0
7
+ speechrecognition==3.8.1
8
+ pyaudio==0.2.11
9
+ python-dotenv==1.0.0
10
+ vosk==0.3.32
11
+ pandas==2.1.4
12
+ plotly==5.18.0
13
+ streamlit==1.30.0
14
+ sentence-transformers==2.2.2
15
+ faiss-cpu==1.7.4
sentiment_analysis.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import time
4
+ import pyaudio
5
+ from vosk import Model, KaldiRecognizer
6
+ from transformers import pipeline, AutoModelForSequenceClassification, AutoTokenizer
7
+ from huggingface_hub import login
8
+ from product_recommender import ProductRecommender
9
+ from objection_handler import load_objections, check_objections # Ensure check_objections is imported
10
+ from objection_handler import ObjectionHandler
11
+ from env_setup import config
12
+ from sentence_transformers import SentenceTransformer
13
+ from dotenv import load_dotenv
14
+
15
+ # Load environment variables
16
+ load_dotenv()
17
+
18
+ # Initialize the ProductRecommender
19
+ product_recommender = ProductRecommender(r"C:\Users\shaik\Downloads\Sales Calls Transcriptions - Sheet2.csv")
20
+
21
+ # Hugging Face API setup
22
+ huggingface_api_key = config["huggingface_api_key"]
23
+ login(token=huggingface_api_key)
24
+
25
+ # Sentiment Analysis Model
26
+ model_name = "tabularisai/multilingual-sentiment-analysis"
27
+ model = AutoModelForSequenceClassification.from_pretrained(model_name)
28
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
29
+ sentiment_analyzer = pipeline("sentiment-analysis", model=model, tokenizer=tokenizer)
30
+
31
+ # Vosk Speech Recognition Model
32
+ vosk_model_path = config["vosk_model_path"]
33
+
34
+ if not vosk_model_path:
35
+ raise ValueError("Error: vosk_model_path is not set in the .env file.")
36
+
37
+ try:
38
+ vosk_model = Model(vosk_model_path)
39
+ print("Vosk model loaded successfully.")
40
+ except Exception as e:
41
+ raise ValueError(f"Failed to load Vosk model: {e}")
42
+
43
+ recognizer = KaldiRecognizer(vosk_model, 16000)
44
+ audio = pyaudio.PyAudio()
45
+
46
+ stream = audio.open(format=pyaudio.paInt16,
47
+ channels=1,
48
+ rate=16000,
49
+ input=True,
50
+ frames_per_buffer=4000)
51
+ stream.start_stream()
52
+
53
+ # Function to analyze sentiment
54
+ def preprocess_text(text):
55
+ """Preprocess text for better sentiment analysis."""
56
+ # Strip whitespace and convert to lowercase
57
+ processed = text.strip().lower()
58
+ return processed
59
+
60
+ def preprocess_text(text):
61
+ """Preprocess text for better sentiment analysis."""
62
+ return text.strip().lower()
63
+
64
+ def analyze_sentiment(text):
65
+ """Analyze sentiment of the text using Hugging Face model."""
66
+ try:
67
+ if not text.strip():
68
+ return "NEUTRAL", 0.0
69
+
70
+ processed_text = preprocess_text(text)
71
+ result = sentiment_analyzer(processed_text)[0]
72
+
73
+ print(f"Sentiment Analysis Result: {result}")
74
+
75
+ # Map raw labels to sentiments
76
+ sentiment_map = {
77
+ 'Very Negative': "NEGATIVE",
78
+ 'Negative': "NEGATIVE",
79
+ 'Neutral': "NEUTRAL",
80
+ 'Positive': "POSITIVE",
81
+ 'Very Positive': "POSITIVE"
82
+ }
83
+
84
+ sentiment = sentiment_map.get(result['label'], "NEUTRAL")
85
+ return sentiment, result['score']
86
+
87
+ except Exception as e:
88
+ print(f"Error in sentiment analysis: {e}")
89
+ return "NEUTRAL", 0.5
90
+
91
+ def transcribe_with_chunks(objections_dict):
92
+ """Perform real-time transcription with sentiment analysis."""
93
+ print("Say 'start listening' to begin transcription. Say 'stop listening' to stop.")
94
+ is_listening = False
95
+ chunks = []
96
+ current_chunk = []
97
+ chunk_start_time = time.time()
98
+
99
+ # Initialize handlers with semantic search capabilities
100
+ objection_handler = ObjectionHandler(r"C:\Users\shaik\Downloads\Sales Calls Transcriptions - Sheet3.csv")
101
+ product_recommender = ProductRecommender(r"C:\Users\shaik\Downloads\Sales Calls Transcriptions - Sheet2.csv")
102
+
103
+ # Load the embeddings model once
104
+ model = SentenceTransformer('all-MiniLM-L6-v2')
105
+
106
+ try:
107
+ while True:
108
+ data = stream.read(4000, exception_on_overflow=False)
109
+
110
+ if recognizer.AcceptWaveform(data):
111
+ result = recognizer.Result()
112
+ text = json.loads(result)["text"]
113
+
114
+ if "start listening" in text.lower():
115
+ is_listening = True
116
+ print("Listening started. Speak into the microphone.")
117
+ continue
118
+ elif "stop listening" in text.lower():
119
+ is_listening = False
120
+ print("Listening stopped.")
121
+ if current_chunk:
122
+ chunk_text = " ".join(current_chunk)
123
+ sentiment, score = analyze_sentiment(chunk_text)
124
+ chunks.append((chunk_text, sentiment, score))
125
+ current_chunk = []
126
+ continue
127
+
128
+ if is_listening and text.strip():
129
+ print(f"Transcription: {text}")
130
+ current_chunk.append(text)
131
+
132
+ if time.time() - chunk_start_time > 3:
133
+ if current_chunk:
134
+ chunk_text = " ".join(current_chunk)
135
+
136
+ # Always process sentiment
137
+ sentiment, score = analyze_sentiment(chunk_text)
138
+ chunks.append((chunk_text, sentiment, score))
139
+
140
+ # Get objection responses and check similarity score
141
+ query_embedding = model.encode([chunk_text])
142
+ distances, indices = objection_handler.index.search(query_embedding, 1)
143
+
144
+ # If similarity is high enough, show objection response
145
+ if distances[0][0] < 1.5: # Threshold for similarity
146
+ responses = objection_handler.handle_objection(chunk_text)
147
+ if responses:
148
+ print("\nSuggested Response:")
149
+ for response in responses:
150
+ print(f"→ {response}")
151
+
152
+ # Get product recommendations and check similarity score
153
+ distances, indices = product_recommender.index.search(query_embedding, 1)
154
+
155
+ # If similarity is high enough, show recommendations
156
+ if distances[0][0] < 1.5: # Threshold for similarity
157
+ recommendations = product_recommender.get_recommendations(chunk_text)
158
+ if recommendations:
159
+ print(f"\nRecommendations for this response:")
160
+ for idx, rec in enumerate(recommendations, 1):
161
+ print(f"{idx}. {rec}")
162
+
163
+ print("\n")
164
+ current_chunk = []
165
+ chunk_start_time = time.time()
166
+
167
+ except KeyboardInterrupt:
168
+ print("\nExiting...")
169
+ stream.stop_stream()
170
+
171
+ return chunks
172
+
173
+ if __name__ == "__main__":
174
+ objections_file_path = r"C:\Users\shaik\Downloads\Sales Calls Transcriptions - Sheet3.csv"
175
+ objections_dict = load_objections(objections_file_path)
176
+ transcribed_chunks = transcribe_with_chunks(objections_dict)
177
+ print("Final transcriptions and sentiments:", transcribed_chunks)
speech_to_text.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import os
3
+ from vosk import Model, KaldiRecognizer
4
+ import pyaudio
5
+ import json
6
+
7
+ # Load environment variables from .env file
8
+ load_dotenv()
9
+
10
+ # Get the Vosk model path from the environment variable
11
+ vosk_model_path = os.getenv("vosk_model_path")
12
+
13
+ if not vosk_model_path:
14
+ print("Error: vosk_model_path is not set in the .env file.")
15
+ exit()
16
+
17
+ # Initialize the Vosk model
18
+ try:
19
+ model = Model(vosk_model_path)
20
+ print("Vosk model loaded successfully.")
21
+ except Exception as e:
22
+ print(f"Failed to load Vosk model: {e}")
23
+ exit()
24
+
25
+ # Initialize recognizer and audio input
26
+ recognizer = KaldiRecognizer(model, 16000)
27
+ audio = pyaudio.PyAudio()
28
+
29
+ # Open audio stream
30
+ stream = audio.open(format=pyaudio.paInt16,
31
+ channels=1,
32
+ rate=16000,
33
+ input=True,
34
+ frames_per_buffer=4000)
35
+ stream.start_stream()
36
+
37
+ print("Say 'start listening' to begin transcription and 'stop listening' to stop.")
38
+
39
+ # State management
40
+ is_listening = False
41
+
42
+ try:
43
+ while True:
44
+ data = stream.read(4000, exception_on_overflow=False)
45
+
46
+ if recognizer.AcceptWaveform(data):
47
+ result = recognizer.Result()
48
+ text = json.loads(result)["text"]
49
+
50
+ # Check for commands to start or stop listening
51
+ if "start listening" in text.lower():
52
+ is_listening = True
53
+ print("Listening started. Speak into the microphone.")
54
+ continue
55
+ elif "stop listening" in text.lower():
56
+ is_listening = False
57
+ print("Listening stopped. Say 'start listening' to resume.")
58
+ continue
59
+
60
+ # Transcribe if actively listening
61
+ if is_listening:
62
+ print(f"Transcription: {text}")
63
+ else:
64
+ # Handle partial results if needed
65
+ chunk_result = recognizer.PartialResult()
66
+ chunk_text = json.loads(chunk_result)["partial"]
67
+
68
+ # Display partial transcription only if actively listening
69
+ if is_listening and chunk_text:
70
+ print(f"chunk: {chunk_text}", end="\r")
71
+
72
+ except KeyboardInterrupt:
73
+ print("\nExiting...")
74
+ stream.stop_stream()
75
+ stream.close()
76
+ audio.terminate()
utils.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ # Function to load environment variables from .env file
5
+ def load_env_variables():
6
+ # Load environment variables from .env file
7
+ load_dotenv()
8
+
9
+ # Return the loaded environment variables as a dictionary
10
+ return {
11
+ 'assemblyai_api_key': os.getenv('ASSEMBLYAI_API_KEY'),
12
+ 'huggingface_api_key': os.getenv('HUGGINGFACE_API_KEY'),
13
+ 'google_creds': os.getenv('GOOGLE_CREDS_PATH'),
14
+ 'google_sheet_id': os.getenv('GOOGLE_SHEET_ID')
15
+ }