Adil51 commited on
Commit
d5eb838
·
verified ·
1 Parent(s): f10dd27

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +716 -0
app.py ADDED
@@ -0,0 +1,716 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ from pymongo import MongoClient
4
+ from datetime import datetime
5
+ import requests
6
+ from openai import OpenAI
7
+ from bs4 import BeautifulSoup
8
+ import asyncio
9
+ import smtplib
10
+ from email.mime.text import MIMEText
11
+ from email.mime.multipart import MIMEMultipart
12
+ from selenium import webdriver
13
+ from selenium.webdriver.common.by import By
14
+ from selenium.webdriver.common.keys import Keys
15
+ from selenium.webdriver.support.ui import WebDriverWait
16
+ from selenium.webdriver.support import expected_conditions as EC
17
+ from selenium.common.exceptions import TimeoutException, NoSuchElementException
18
+ import time
19
+
20
+ # Initialize OpenAI client
21
+ @st.cache_resource
22
+ def init_openai():
23
+ client = OpenAI(api_key=('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'))
24
+ return client
25
+
26
+ # Initialize MongoDB connection
27
+ @st.cache_resource
28
+ def init_mongodb():
29
+ client = MongoClient("mongodb://linkedin_user:P4XnKOjkOaTg@18.235.17.44:27017/?authMechanism=DEFAULT")
30
+ return client['linkedin_db']
31
+
32
+ # Get detailed address using Selenium and Google Maps
33
+ @st.cache_data(ttl=3600)
34
+ def get_location_info(company_name):
35
+ options = webdriver.ChromeOptions()
36
+ options.add_argument("--headless")
37
+ options.add_argument("--no-sandbox")
38
+ options.add_argument("--disable-dev-shm-usage")
39
+
40
+ result = {
41
+ "status": "failed",
42
+ "address": None,
43
+ "error": None
44
+ }
45
+
46
+ try:
47
+ driver = webdriver.Chrome(options=options)
48
+ driver.implicitly_wait(10)
49
+
50
+ # Open Google Maps
51
+ driver.get("https://www.google.com/maps")
52
+
53
+ # Accept cookies if prompted (common in some regions)
54
+ try:
55
+ cookie_button = WebDriverWait(driver, 3).until(
56
+ EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Accept all')]"))
57
+ )
58
+ cookie_button.click()
59
+ except:
60
+ pass # No cookie prompt or different format
61
+
62
+ # Find and use the search box
63
+ search_box = driver.find_element(By.NAME, "q")
64
+ search_box.clear()
65
+ search_box.send_keys(company_name)
66
+ search_box.send_keys(Keys.RETURN)
67
+
68
+ # Wait for results and get the address
69
+ wait = WebDriverWait(driver, 10)
70
+
71
+ # First attempt with Io6YTe class
72
+ try:
73
+ address_element = wait.until(
74
+ EC.presence_of_element_located((By.CLASS_NAME, "Io6YTe"))
75
+ )
76
+ address = address_element.text
77
+ result["status"] = "success"
78
+ result["address"] = address
79
+ except:
80
+ # Fallback to alternative selectors
81
+ try:
82
+ # Try looking for the address in a different format
83
+ address_container = wait.until(
84
+ EC.presence_of_element_located((By.CSS_SELECTOR, "[data-section-id='addr']"))
85
+ )
86
+ address = address_container.text.replace("Address: ", "")
87
+ result["status"] = "success"
88
+ result["address"] = address
89
+ except:
90
+ result["error"] = "Could not find address element"
91
+
92
+ except TimeoutException:
93
+ result["error"] = "Timeout waiting for Google Maps to load"
94
+ except NoSuchElementException:
95
+ result["error"] = "Could not find the required elements on the page"
96
+ except Exception as e:
97
+ result["error"] = f"An error occurred: {str(e)}"
98
+ finally:
99
+ # Always close the browser
100
+ if 'driver' in locals():
101
+ driver.quit()
102
+
103
+ return result
104
+
105
+
106
+ def search_profiles(db, search_terms, location=None, limit=100):
107
+ # Your existing function code here
108
+ query = {
109
+ '$and': [
110
+ {'search_query': {'$regex': search_terms, '$options': 'i'}},
111
+ ]
112
+ }
113
+
114
+ if location:
115
+ query['$and'].append({'location': {'$regex': location, '$options': 'i'}})
116
+
117
+ profiles = list(db.LInkedinProfiles.find(query).limit(limit))
118
+
119
+ # Get all sent emails for this campaign
120
+ sent_emails = list(db.sent_emails.find({}, {
121
+ 'recipient_first_name': 1,
122
+ 'recipient_last_name': 1,
123
+ 'recipient_company': 1,
124
+ 'sent_date': 1
125
+ }))
126
+
127
+ # Create a lookup dictionary for sent emails
128
+ sent_email_lookup = {
129
+ f"{email['recipient_first_name']}_{email['recipient_last_name']}_{email['recipient_company']}": email['sent_date']
130
+ for email in sent_emails
131
+ }
132
+
133
+ # Add email status to each profile
134
+ for profile in profiles:
135
+ key = f"{profile['first_name']}_{profile['last_name']}_{profile['company']}"
136
+ if key in sent_email_lookup:
137
+ profile['email_status'] = 'Sent'
138
+ profile['sent_date'] = sent_email_lookup[key]
139
+ else:
140
+ profile['email_status'] = 'Not Sent'
141
+ profile['sent_date'] = None
142
+
143
+ return profiles
144
+
145
+
146
+ async def get_coffee_shops(company_address):
147
+ if not company_address:
148
+ return []
149
+
150
+ url = f"https://www.google.com/search?q=coffee%20shops%20near%20{company_address}&sca_esv=2621f7b39c394d4e&tbm=lcl"
151
+
152
+ headers = {
153
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
154
+ }
155
+
156
+ try:
157
+ response = requests.get(url, headers=headers)
158
+ soup = BeautifulSoup(response.text, "html.parser")
159
+ shops = soup.find_all("div", class_="VkpGBb")
160
+
161
+ rated_shops = []
162
+
163
+ for shop in shops:
164
+ name = shop.find("div", class_="dbg0pd").text if shop.find("div", class_="dbg0pd") else "N/A"
165
+ rating_elem = shop.find("span", class_="yi40Hd YrbPuc")
166
+ rating = rating_elem.text if rating_elem else "0"
167
+ address_divs = shop.find_all("div")
168
+ address = address_divs[-4].text.strip() if len(address_divs) > 2 else "N/A"
169
+
170
+ try:
171
+ rating_value = float(rating.replace("/5", ""))
172
+ rated_shops.append({
173
+ "name": name,
174
+ "rating": rating,
175
+ "rating_value": rating_value,
176
+ "address": address
177
+ })
178
+ except ValueError:
179
+ continue
180
+
181
+ top_shops = sorted(rated_shops, key=lambda x: x["rating_value"], reverse=True)[:3]
182
+
183
+ for shop in top_shops:
184
+ del shop["rating_value"]
185
+
186
+ return top_shops
187
+ except Exception as e:
188
+ print(f"Error fetching shops for {company_address}: {str(e)}")
189
+ return []
190
+
191
+ def get_email_for_profile(profile_url):
192
+ try:
193
+ response = requests.get(
194
+ "http://127.0.0.1:8000/get_emails",
195
+ params={"profile_url": profile_url},
196
+ timeout=10
197
+ )
198
+
199
+ if response.status_code == 200:
200
+ data = response.json()
201
+ return data.get('email')
202
+ else:
203
+ st.error(f"API Error: Status {response.status_code}")
204
+ return None
205
+
206
+ except requests.exceptions.Timeout:
207
+ st.error("Request timed out. Please try again.")
208
+ return None
209
+ except requests.exceptions.ConnectionError:
210
+ st.error("Could not connect to the email service. Please check if the API server is running.")
211
+ return None
212
+ except Exception as e:
213
+ st.error(f"Error fetching email: {str(e)}")
214
+ return None
215
+
216
+ def update_template_with_coffee_shop(template, shop_name, shop_address):
217
+ paragraphs = template.split('\n\n')
218
+
219
+ meeting_loc_idx = -1
220
+ for i, para in enumerate(paragraphs):
221
+ if any(keyword in para.lower() for keyword in ['meet', 'coffee', 'discuss']):
222
+ meeting_loc_idx = i
223
+ break
224
+
225
+ meeting_text = f"I would love to meet you at {shop_name} ({shop_address}) to discuss this further."
226
+
227
+ if meeting_loc_idx >= 0:
228
+ paragraphs[meeting_loc_idx] = meeting_text
229
+ else:
230
+ paragraphs.insert(-1, meeting_text)
231
+
232
+ return '\n\n'.join(paragraphs)
233
+
234
+ def generate_email_template(openai_client, profile_data, coffee_shops):
235
+ try:
236
+ shops_text = ""
237
+ if coffee_shops:
238
+ shops_text = "Nearby recommended meeting spots:\n"
239
+ for i, shop in enumerate(coffee_shops, 1):
240
+ shops_text += f"{i}. {shop['name']} (Rating: {shop['rating']}) - {shop['address']}\n"
241
+
242
+ first_name = profile_data.get('first_name', '')
243
+ company = profile_data.get('company', '')
244
+ location = profile_data.get('location', '')
245
+ description = profile_data.get('description', '')
246
+ company_address = profile_data.get('company_address', '')
247
+
248
+ # First, detect the actual company name
249
+ company_detection_prompt = f"""
250
+ I need to identify the most likely company name from the following LinkedIn profile data. The company name might be in any of these fields:
251
+
252
+ Company field: "{company}"
253
+ Description field: "{description}"
254
+ Location field: "{location}"
255
+
256
+ Analyze all three fields and identify the most likely company name. Return ONLY the company name, nothing else.
257
+ """
258
+
259
+ company_detection_response = openai_client.chat.completions.create(
260
+ model="gpt-4-turbo-preview",
261
+ messages=[
262
+ {"role": "system", "content": "You extract the most likely company name from LinkedIn profile data."},
263
+ {"role": "user", "content": company_detection_prompt}
264
+ ],
265
+ temperature=0.3
266
+ )
267
+
268
+ detected_company = company_detection_response.choices[0].message.content.strip()
269
+
270
+ # Print the detected company name and address
271
+ print(f"Original company field: {company}")
272
+ print(f"Detected company name: {detected_company}")
273
+ print(f"Company address: {company_address}")
274
+
275
+ system_message = """
276
+ You are a professional email writer crafting meeting request templates.
277
+ IMPORTANT:
278
+ 1. Always use the recipient's actual first name in the greeting (e.g., "Dear John," not "Dear [Name]")
279
+ 2. Always specifically mention their company name in the first paragraph
280
+ 3. Always sign the email with "Best regards,\nAdil"
281
+ 4. Never use placeholders like [Name] or [CEO's Name]
282
+ """
283
+
284
+ prompt = f"""
285
+ Based on the following professional's information and nearby coffee shops, write a formal email template requesting a meeting:
286
+
287
+ First Name: {first_name}
288
+ Company: {detected_company}
289
+ Location: {location}
290
+ Company Address: {company_address}
291
+ Description: {description}
292
+
293
+ {shops_text}
294
+
295
+ The email should:
296
+ 1. Begin with "Dear {first_name},"
297
+ 2. Be professional and formal
298
+ 3. Reference their current role at {detected_company} specifically in the first paragraph
299
+ 4. Suggest meeting at one of the nearby coffee shops (if available)
300
+ 5. Be concise but personal
301
+ 6. Include a clear call to action for a meeting
302
+ 7. End with "Best regards,\nAdil"
303
+
304
+ Write only the email body without additional subject line or formatting.
305
+ """
306
+
307
+ response = openai_client.chat.completions.create(
308
+ model="gpt-4-turbo-preview",
309
+ messages=[
310
+ {"role": "system", "content": system_message},
311
+ {"role": "user", "content": prompt}
312
+ ],
313
+ temperature=0.7
314
+ )
315
+
316
+ template = response.choices[0].message.content.strip()
317
+
318
+ if "Dear " + first_name not in template:
319
+ template = f"Dear {first_name},\n\n" + template.split('\n', 1)[1] if '\n' in template else template
320
+
321
+ if "Best regards,\nAdil" not in template:
322
+ template = template.rsplit('\n', 2)[0] + "\n\nBest regards,\nAdil"
323
+
324
+ # Add the detected company name and address at the top of the email for your reference
325
+ template = f"[Detected Company: {detected_company}]\n[Company Address: {company_address}]\n\n" + template
326
+
327
+ return template
328
+ except Exception as e:
329
+ st.error(f"Error generating email template: {str(e)}")
330
+ return None
331
+
332
+
333
+ def save_email_record(db, profile_data, template):
334
+ try:
335
+ email_record = {
336
+ 'recipient_first_name': profile_data['First Name'],
337
+ 'recipient_last_name': profile_data['Last Name'],
338
+ 'recipient_company': profile_data['Company'],
339
+ 'email_template': template,
340
+ 'sent_date': datetime.now(),
341
+ 'status': 'sent'
342
+ }
343
+
344
+ result = db.sent_emails.insert_one(email_record)
345
+ return result.inserted_id
346
+ except Exception as e:
347
+ st.error(f"Error saving email record: {str(e)}")
348
+ return None
349
+
350
+ def send_email(template, db, profile_data):
351
+ try:
352
+ sender_email = "adilinbox4@gmail.com"
353
+ sender_password = "pulv dnov zzfg etcg"
354
+ recipient_email = "adilinbox4@gmail.com"
355
+
356
+ msg = MIMEMultipart()
357
+ msg['From'] = sender_email
358
+ msg['To'] = recipient_email
359
+ msg['Subject'] = "Meeting Request"
360
+
361
+ msg.attach(MIMEText(template, 'plain'))
362
+
363
+ server = smtplib.SMTP('smtp.gmail.com', 587)
364
+ server.starttls()
365
+ server.login(sender_email, sender_password)
366
+ text = msg.as_string()
367
+ server.sendmail(sender_email, recipient_email, text)
368
+ server.quit()
369
+
370
+ # Save record to database after successful send
371
+ record_id = save_email_record(db, profile_data, template)
372
+
373
+ if record_id:
374
+ # Update profile status in session state
375
+ for profile in st.session_state.search_results:
376
+ if (profile['first_name'] == profile_data['First Name'] and
377
+ profile['last_name'] == profile_data['Last Name'] and
378
+ profile['company'] == profile_data['Company']):
379
+ profile['email_status'] = 'Sent'
380
+ profile['sent_date'] = datetime.now()
381
+ return True
382
+
383
+ return False
384
+ except Exception as e:
385
+ st.error(f"Failed to send email: {str(e)}")
386
+ return False
387
+
388
+ def main():
389
+ st.set_page_config(page_title="LinkedIn Profile Explorer", layout="wide")
390
+
391
+ # Initialize session state variables
392
+ if 'search_results' not in st.session_state:
393
+ st.session_state.search_results = None
394
+ if 'edited_df' not in st.session_state:
395
+ st.session_state.edited_df = None
396
+ if 'email_results' not in st.session_state:
397
+ st.session_state.email_results = []
398
+ if 'selected_templates' not in st.session_state:
399
+ st.session_state.selected_templates = {}
400
+ if 'templates_generated' not in st.session_state:
401
+ st.session_state.templates_generated = False
402
+ if 'edited_templates' not in st.session_state:
403
+ st.session_state.edited_templates = {}
404
+ if 'deleted_templates' not in st.session_state:
405
+ st.session_state.deleted_templates = set()
406
+ if 'selected_coffee_shops' not in st.session_state:
407
+ st.session_state.selected_coffee_shops = {}
408
+ if 'show_templates' not in st.session_state:
409
+ st.session_state.show_templates = False
410
+ if 'company_addresses' not in st.session_state:
411
+ st.session_state.company_addresses = {}
412
+
413
+ # Initialize OpenAI and MongoDB clients
414
+ openai_client = init_openai()
415
+ db = init_mongodb()
416
+
417
+ if 'coffee_shop_selections' not in st.session_state:
418
+ st.session_state.coffee_shop_selections = {}
419
+
420
+ # Add callback function for radio button changes
421
+ def on_coffee_shop_change(template_key, selected_shop, result):
422
+ st.session_state.coffee_shop_selections[template_key] = selected_shop
423
+ current_template = st.session_state.edited_templates.get(template_key, result['Email Template'])
424
+
425
+ if selected_shop != "No specific coffee shop":
426
+ shop_name = selected_shop.split(" (Rating")[0]
427
+ shop_address = selected_shop.split(" - ")[-1]
428
+ current_template = update_template_with_coffee_shop(current_template, shop_name, shop_address)
429
+ st.session_state.edited_templates[template_key] = current_template
430
+
431
+ # Sidebar
432
+ st.sidebar.title("LinkedIn Profile Explorer")
433
+
434
+ # Search Interface
435
+ st.sidebar.header("Search Profiles")
436
+
437
+ search_terms = st.sidebar.text_input("Search Keywords (e.g., Healthcare CEO)")
438
+ location = st.sidebar.text_input("Location (Optional)")
439
+ limit = st.sidebar.slider("Number of results", 10, 500, 100)
440
+
441
+ if st.sidebar.button("Search"):
442
+ with st.spinner("Searching profiles..."):
443
+ profiles = search_profiles(db, search_terms, location, limit)
444
+ if profiles:
445
+ st.session_state.search_results = profiles
446
+ st.session_state.email_results = []
447
+ st.session_state.selected_templates = {}
448
+ st.session_state.templates_generated = False
449
+ st.session_state.edited_templates = {}
450
+ st.session_state.deleted_templates = set()
451
+ st.session_state.show_templates = False
452
+ st.session_state.company_addresses = {}
453
+ else:
454
+ st.session_state.search_results = None
455
+ st.warning("No profiles found matching your search criteria.")
456
+
457
+ if st.session_state.search_results:
458
+ st.title("Search Results")
459
+ st.success(f"Found {len(st.session_state.search_results)} matching profiles")
460
+
461
+ df = pd.DataFrame(st.session_state.search_results)
462
+
463
+ if not df.empty:
464
+ display_cols = {
465
+ 'first_name': 'First Name',
466
+ 'last_name': 'Last Name',
467
+ 'description': 'Description',
468
+ 'company': 'Company',
469
+ 'location': 'Location',
470
+ 'url': 'Profile URL',
471
+ 'email_status': 'Email Status',
472
+ 'sent_date': 'Sent Date'
473
+ }
474
+
475
+ df_display = df[display_cols.keys()].rename(columns=display_cols)
476
+ df_display['Description'] = df_display['Description'].apply(
477
+ lambda x: x[:100] + '...' if isinstance(x, str) and len(x) > 100 else x
478
+ )
479
+
480
+ def format_date(x):
481
+ try:
482
+ return x.strftime("%Y-%m-%d %H:%M:%S") if pd.notnull(x) and hasattr(x, 'strftime') else ''
483
+ except:
484
+ return ''
485
+
486
+ df_display['Sent Date'] = df_display['Sent Date'].apply(format_date)
487
+
488
+ # Add Select column and set initial values
489
+ df_display.insert(0, 'Select', False)
490
+ mask = df_display['Email Status'].fillna('').astype(str) == 'Sent'
491
+ df_display.loc[mask, 'Select'] = False
492
+
493
+ # Apply conditional styling
494
+ def highlight_sent_rows(row):
495
+ if row['Email Status'] == 'Sent':
496
+ return ['background-color: #4CAF50; color: black'] * len(row)
497
+ else:
498
+ return [''] * len(row)
499
+
500
+ styled_df = df_display.style.apply(highlight_sent_rows, axis=1)
501
+
502
+ st.session_state.edited_df = st.data_editor(
503
+ styled_df,
504
+ hide_index=True,
505
+ disabled=list(display_cols.values()),
506
+ column_config={
507
+ "Select": st.column_config.CheckboxColumn(
508
+ "Select",
509
+ help="Select profiles to fetch emails",
510
+ default=False,
511
+ ),
512
+ "Email Status": st.column_config.Column(
513
+ "Email Status",
514
+ help="Shows if an email has been sent to this profile",
515
+ width="medium"
516
+ ),
517
+ "Sent Date": st.column_config.Column(
518
+ "Sent Date",
519
+ help="When the email was sent",
520
+ width="medium"
521
+ )
522
+ }
523
+ )
524
+
525
+ # Generate templates button
526
+ if st.button("Get Emails and Generate Templates"):
527
+ selected_profiles = st.session_state.edited_df[st.session_state.edited_df['Select'] == True]
528
+
529
+ if selected_profiles.empty:
530
+ st.warning("Please select at least one profile")
531
+ else:
532
+ st.session_state.show_templates = True
533
+ progress_placeholder = st.empty()
534
+ email_results = []
535
+
536
+ total_profiles = len(selected_profiles)
537
+
538
+ for idx, (i, row) in enumerate(selected_profiles.iterrows()):
539
+ progress = min(idx / (total_profiles - 1) if total_profiles > 1 else 1.0, 1.0)
540
+ progress_placeholder.progress(progress)
541
+
542
+ # First, use GPT to detect the company name
543
+ company_detection_prompt = f"""
544
+ I need to identify the most likely company name from the following LinkedIn profile data:
545
+
546
+ Company field: "{row['Company']}"
547
+ Description field: "{row['Description']}"
548
+ Location field: "{row['Location']}"
549
+
550
+ Analyze all fields and identify the most likely company name. Return ONLY the company name, nothing else.
551
+ """
552
+
553
+ company_detection_response = openai_client.chat.completions.create(
554
+ model="gpt-4-turbo-preview",
555
+ messages=[
556
+ {"role": "system", "content": "You extract the most likely company name from LinkedIn profile data."},
557
+ {"role": "user", "content": company_detection_prompt}
558
+ ],
559
+ temperature=0.3
560
+ )
561
+
562
+ detected_company = company_detection_response.choices[0].message.content.strip()
563
+
564
+ # Use Selenium to get the company's detailed address
565
+ with st.spinner(f"Looking up address for {detected_company}..."):
566
+ location_info = get_location_info(detected_company)
567
+ company_address = location_info.get("address", "")
568
+
569
+ # Store in session state for reuse
570
+ key = f"{row['First Name']}_{row['Last Name']}_{row['Company']}"
571
+ st.session_state.company_addresses[key] = company_address
572
+
573
+ # Get coffee shops near the company address
574
+ try:
575
+ with st.spinner(f"Finding coffee shops near {detected_company}..."):
576
+ coffee_shops = asyncio.run(get_coffee_shops(company_address))
577
+ except Exception as e:
578
+ coffee_shops = []
579
+ st.warning(f"Could not fetch coffee shops: {str(e)}")
580
+
581
+ # Generate email template
582
+ template = generate_email_template(
583
+ openai_client,
584
+ {
585
+ 'first_name': row['First Name'],
586
+ 'company': row['Company'],
587
+ 'location': row['Location'],
588
+ 'description': row['Description'],
589
+ 'company_address': company_address
590
+ },
591
+ coffee_shops
592
+ )
593
+
594
+ result = {
595
+ 'First Name': row['First Name'],
596
+ 'Last Name': row['Last Name'],
597
+ 'Company': row['Company'],
598
+ 'Detected Company': detected_company,
599
+ 'Company Address': company_address,
600
+ 'Email Template': template if template else 'Template generation failed',
601
+ 'Nearby Coffee Shops': coffee_shops
602
+ }
603
+
604
+ email_results.append(result)
605
+
606
+ progress_placeholder.empty()
607
+ st.session_state.email_results = email_results
608
+ st.session_state.templates_generated = True
609
+
610
+ # Initialize edited templates with generated content
611
+ for idx, result in enumerate(email_results):
612
+ template_key = f"template_{idx}"
613
+ if template_key not in st.session_state.edited_templates:
614
+ st.session_state.edited_templates[template_key] = result['Email Template']
615
+
616
+ # Display templates section
617
+ if st.session_state.show_templates and st.session_state.email_results:
618
+ st.write("### Select Templates to Send")
619
+
620
+ templates_to_display = [
621
+ (idx, result) for idx, result in enumerate(st.session_state.email_results)
622
+ if f"template_{idx}" not in st.session_state.deleted_templates
623
+ ]
624
+
625
+ for idx, result in templates_to_display:
626
+ template_key = f"template_{idx}"
627
+
628
+ with st.expander(f"📧 {result['First Name']} {result['Last Name']} - {result['Company']}", expanded=True):
629
+ col1, col2, col3 = st.columns([0.2, 1.6, 0.2])
630
+
631
+ with col1:
632
+ st.session_state.selected_templates[template_key] = True
633
+
634
+ if result.get('Company Address'):
635
+ st.info(f"**Company Address:** {result['Company Address']}")
636
+
637
+ with col2:
638
+ if result.get('Nearby Coffee Shops'):
639
+ st.write("**Select Coffee Shop for Meeting:**")
640
+
641
+ coffee_shops = result['Nearby Coffee Shops']
642
+ coffee_shop_options = [
643
+ f"{shop['name']} (Rating: {shop['rating']}) - {shop['address']}"
644
+ for shop in coffee_shops
645
+ ]
646
+ coffee_shop_options.insert(0, "No specific coffee shop")
647
+
648
+ if template_key not in st.session_state.coffee_shop_selections and coffee_shop_options:
649
+ st.session_state.coffee_shop_selections[template_key] = coffee_shop_options[1] if len(coffee_shop_options) > 1 else coffee_shop_options[0]
650
+
651
+ if coffee_shop_options:
652
+ selected_shop = st.radio(
653
+ "Choose a coffee shop:",
654
+ options=coffee_shop_options,
655
+ key=f"coffee_shop_{template_key}",
656
+ index=coffee_shop_options.index(st.session_state.coffee_shop_selections.get(template_key, coffee_shop_options[0]))
657
+ )
658
+
659
+ if selected_shop != st.session_state.coffee_shop_selections.get(template_key):
660
+ on_coffee_shop_change(template_key, selected_shop, result)
661
+
662
+ st.write("**Generated Email Template:**")
663
+ current_template = st.session_state.edited_templates.get(template_key, result['Email Template'])
664
+ edited_template = st.text_area(
665
+ "",
666
+ value=current_template,
667
+ height=300,
668
+ key=f"edit_{template_key}"
669
+ )
670
+ st.session_state.edited_templates[template_key] = edited_template
671
+
672
+ with col3:
673
+ if st.button("🗑️", key=f"delete_{template_key}"):
674
+ st.session_state.deleted_templates.add(template_key)
675
+ st.rerun()
676
+
677
+
678
+ if templates_to_display and st.button("Send Selected Templates"):
679
+ success_count = 0
680
+ send_progress = st.progress(0)
681
+ status_text = st.empty()
682
+
683
+ total_selected = len(templates_to_display)
684
+
685
+ for i, (idx, result) in enumerate(templates_to_display):
686
+ template_key = f"template_{idx}"
687
+ template = st.session_state.edited_templates[template_key]
688
+ profile_data = st.session_state.email_results[idx]
689
+
690
+ if send_email(template, db, profile_data):
691
+ success_count += 1
692
+ st.session_state.deleted_templates.add(template_key)
693
+
694
+ progress = (i + 1) / total_selected
695
+ send_progress.progress(progress)
696
+ status_text.text(f"Sending emails: {i + 1}/{total_selected}")
697
+
698
+ send_progress.empty()
699
+ status_text.empty()
700
+
701
+ if success_count > 0:
702
+ st.success(f"Successfully sent {success_count} out of {total_selected} templates!")
703
+ st.rerun()
704
+ if success_count < total_selected:
705
+ st.warning(f"Failed to send {total_selected - success_count} templates. Please check the errors above.")
706
+
707
+ st.sidebar.markdown("---")
708
+ st.sidebar.markdown("### About")
709
+ st.sidebar.info(
710
+ "This application allows you to search LinkedIn profiles and generate meeting request templates."
711
+ )
712
+
713
+
714
+
715
+ if __name__ == "__main__":
716
+ main()