ahmednoorx commited on
Commit
00cf1a8
·
verified ·
1 Parent(s): 6542527

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +486 -133
app.py CHANGED
@@ -7,14 +7,86 @@ import time
7
 
8
  # Page config
9
  st.set_page_config(
10
- page_title="Cold Email Assistant - AI Email Generator",
11
  page_icon="📧",
12
  layout="wide"
13
  )
14
 
15
- # Initialize session state for demo tracking
16
  if 'email_count' not in st.session_state:
17
  st.session_state.email_count = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  @st.cache_resource
20
  def load_modules():
@@ -36,160 +108,441 @@ def create_fallback_email(name, company, tone="professional"):
36
  """Simple fallback when main system fails"""
37
  return {
38
  'subject': f"Partnership opportunity - {company}",
39
- 'body': f"""Hi {name},
40
-
41
- I came across {company} and was impressed by your work in the industry.
42
-
43
- I'd love to explore potential partnership opportunities that could benefit both our organizations.
44
-
45
- Would you be open to a brief conversation to discuss how we might collaborate?
46
-
47
- Best regards,
48
- [Your Name]""",
49
- 'tone': tone,
50
- 'personalization_score': 75,
51
- 'estimated_response_rate': "15-25%"
52
  }
53
 
54
- def main():
55
- # Header
56
- st.title("📧 Cold Email Assistant")
57
- st.markdown("### Generate High-Converting Cold Emails with AI")
58
- st.markdown("---")
59
 
60
- # Demo notice
61
- st.info("🚀 **Demo Version** - Try it out and let us know what you think! This uses advanced AI (Mistral-7B) to generate personalized cold emails.")
 
62
 
63
- # Load modules
64
- scraper, email_generator = load_modules()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
- # Main interface
67
- col1, col2 = st.columns([1, 1])
68
 
69
- with col1:
70
- st.subheader("📝 Lead Information")
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- # Input fields
73
- name = st.text_input("👤 Recipient Name *", placeholder="e.g., John Smith")
74
- company = st.text_input("🏢 Company Name *", placeholder="e.g., TechCorp Inc")
 
 
75
 
76
- # Company info
77
- st.markdown("**📊 Company Information** (Optional - helps with personalization)")
78
- company_info = st.text_area(
79
- "Company Details",
80
- placeholder="e.g., SaaS company, 50 employees, recently raised Series A...",
81
- height=100
82
  )
83
 
84
- # Tone selection
85
- tone = st.selectbox(
86
- "🎯 Email Tone",
87
- ["Professional", "Friendly", "Casual"],
88
- help="Choose the tone that matches your target audience"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  )
90
 
91
- # Generation options
92
- st.markdown("**⚙️ Generation Options**")
93
- num_variations = st.slider("Number of variations", 1, 5, 3)
 
 
 
 
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  with col2:
96
- st.subheader("📧 Generated Emails")
97
-
98
- if st.button("🚀 Generate Cold Email", type="primary"):
99
- if not name or not company:
100
- st.error("❌ Please provide at least the recipient name and company name.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  else:
102
- with st.spinner(f"🤖 Generating {num_variations} email variation(s)..."):
103
- try:
104
- # Track usage
105
- st.session_state.email_count += num_variations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- if email_generator:
108
- if num_variations == 1:
109
- result = email_generator.generate_email(
110
- name=name,
111
- company=company,
112
- company_info=company_info or f"{company} is a company in the business sector.",
113
- tone=tone
114
- )
115
- results = [result] if result else []
116
- else:
117
- results = email_generator.generate_multiple_variations(
118
- name=name,
119
- company=company,
120
- company_info=company_info or f"{company} is a company in the business sector.",
121
- num_variations=num_variations,
122
- tone=tone
123
- )
124
  else:
125
- # Fallback generation
126
- results = [create_fallback_email(name, company, tone) for _ in range(num_variations)]
 
 
 
 
 
 
 
 
127
 
128
- if results:
129
- st.success(f"✅ Generated {len(results)} email variation(s)!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
- # Display results
132
- for i, email in enumerate(results, 1):
133
- with st.expander(f"📧 Email Variation {i} (Quality: {email.get('personalization_score', 'N/A')}/10)", expanded=i==1):
134
- st.markdown(f"**Subject:** {email.get('subject', 'N/A')}")
135
- st.markdown("**Email Body:**")
136
- st.text_area(f"Email {i}", value=email.get('body', 'N/A'), height=200, key=f"email_{i}")
137
-
138
- # Email metrics
139
- col_a, col_b, col_c = st.columns(3)
140
- with col_a:
141
- st.metric("Quality Score", f"{email.get('personalization_score', 'N/A')}/10")
142
- with col_b:
143
- st.metric("Tone", email.get('tone', 'N/A'))
144
- with col_c:
145
- st.metric("Est. Response Rate", email.get('estimated_response_rate', 'N/A'))
146
 
147
- # CSV Export
148
- if st.button("📁 Download as CSV", key="download_csv"):
149
- df_data = []
150
- for i, email in enumerate(results, 1):
151
- df_data.append({
152
- 'name': name,
153
- 'email': '', # No email provided in demo
154
- 'company': company,
155
- 'subject': email.get('subject', ''),
156
- 'email_content': email.get('body', ''),
157
- 'quality_score': email.get('personalization_score', 0),
158
- 'status': 'generated'
159
- })
160
-
161
- df = pd.DataFrame(df_data)
162
- csv = df.to_csv(index=False)
163
- st.download_button(
164
- label="📥 Download CSV",
165
- data=csv,
166
- file_name=f"cold_emails_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
167
- mime="text/csv"
168
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
- else:
171
- st.error(" Failed to generate email. Please try again.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
- except Exception as e:
174
- st.error(f"❌ An error occurred: {str(e)}")
175
- st.info("💡 Please try again or contact support if the issue persists.")
176
-
177
- # Usage stats
178
- st.markdown("---")
179
- st.markdown(f"📊 **Demo Stats:** {st.session_state.email_count} emails generated in this session")
180
-
181
- # Feedback section
182
- st.markdown("---")
183
- st.subheader("💭 Feedback")
184
- st.markdown("This is a demo version! Please let us know what you think:")
185
-
186
- feedback_col1, feedback_col2 = st.columns(2)
187
- with feedback_col1:
188
- if st.button("👍 Great tool!"):
189
- st.success("Thanks for the positive feedback!")
190
- with feedback_col2:
191
- if st.button("💡 Suggestions"):
192
- st.info("We'd love to hear your ideas! Please comment or reach out.")
 
 
 
193
 
194
  # Footer
195
  st.markdown("---")
 
7
 
8
  # Page config
9
  st.set_page_config(
10
+ page_title="Cold Email Outreach Assistant",
11
  page_icon="📧",
12
  layout="wide"
13
  )
14
 
15
+ # Initialize session state for usage tracking
16
  if 'email_count' not in st.session_state:
17
  st.session_state.email_count = 0
18
+ if 'user_email' not in st.session_state:
19
+ st.session_state.user_email = ""
20
+ if 'is_premium' not in st.session_state:
21
+ st.session_state.is_premium = False
22
+
23
+ # Usage limits
24
+ FREE_DAILY_LIMIT = 5
25
+ PREMIUM_DAILY_LIMIT = 1000
26
+
27
+ def check_usage_limit():
28
+ """Check if user has exceeded daily limit"""
29
+ if st.session_state.is_premium:
30
+ return True
31
+ return st.session_state.email_count < FREE_DAILY_LIMIT
32
+
33
+ def increment_usage():
34
+ """Increment usage counter"""
35
+ st.session_state.email_count += 1
36
+
37
+ def show_upgrade_cta():
38
+ """Show upgrade call-to-action when limit reached"""
39
+ st.error("🚫 **Daily limit reached!** You've used all 5 free emails today.")
40
+
41
+ col1, col2 = st.columns(2)
42
+ with col1:
43
+ st.markdown("""
44
+ ### 🚀 Upgrade to Unlimited
45
+ **$19 one-time payment**
46
+ - ✅ Unlimited emails per day
47
+ - ✅ Priority processing
48
+ - ✅ Advanced tone options
49
+ - ✅ Bulk export features
50
+ """)
51
+
52
+ if st.button("🔓 Get Unlimited Access - $19", use_container_width=True):
53
+ st.markdown("**[🛒 Purchase Here →](https://gumroad.com/l/cold-email-unlimited)**")
54
+ st.info("💡 **Limited Time:** Regular price $49, now only $19!")
55
+
56
+ with col2:
57
+ st.markdown("""
58
+ ### 💼 Need More?
59
+ **Professional Service Available**
60
+ - 📧 Custom email campaigns
61
+ - 🎯 Industry-specific targeting
62
+ - 📊 A/B testing & optimization
63
+ - 🚀 Done-for-you outreach
64
+ """)
65
+
66
+ if st.button("💼 Hire Me on Fiverr", use_container_width=True):
67
+ st.markdown("**[🎯 Professional Service →](https://fiverr.com/your-username)**")
68
+
69
+ def init_database():
70
+ """Initialize SQLite database for caching"""
71
+ conn = sqlite3.connect('leads.db')
72
+ cursor = conn.cursor()
73
+
74
+ cursor.execute('''
75
+ CREATE TABLE IF NOT EXISTS scraped_data (
76
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
77
+ name TEXT,
78
+ email TEXT,
79
+ company TEXT,
80
+ linkedin_url TEXT,
81
+ scraped_info TEXT,
82
+ generated_subject TEXT,
83
+ generated_email TEXT,
84
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
85
+ )
86
+ ''')
87
+
88
+ conn.commit()
89
+ conn.close()
90
 
91
  @st.cache_resource
92
  def load_modules():
 
108
  """Simple fallback when main system fails"""
109
  return {
110
  'subject': f"Partnership opportunity - {company}",
111
+ 'content': f"Hi {name},\n\nI'd love to explore how we can help {company} grow.\n\nInterested in a quick call?\n\nBest regards,\nAlex",
112
+ 'quality_score': 8.0
 
 
 
 
 
 
 
 
 
 
 
113
  }
114
 
115
+ def process_leads(df, tone, creativity):
116
+ """Process leads with bulletproof functionality"""
117
+ scraper, email_generator = load_modules()
 
 
118
 
119
+ results = []
120
+ progress_bar = st.progress(0)
121
+ status_text = st.empty()
122
 
123
+ for idx, row in df.iterrows():
124
+ try:
125
+ progress = (idx + 1) / len(df)
126
+ progress_bar.progress(progress)
127
+ status_text.text(f"Processing {row['name']} ({idx + 1}/{len(df)})")
128
+
129
+ # Scrape company data with fallback
130
+ company_data = ""
131
+ if scraper:
132
+ try:
133
+ if hasattr(scraper, 'scrape_linkedin_company'):
134
+ company_data = scraper.scrape_linkedin_company(row['linkedin_url'])
135
+ elif hasattr(scraper, 'scrape_linkedin_profile'):
136
+ company_data = scraper.scrape_linkedin_profile(row['linkedin_url'])
137
+ elif hasattr(scraper, 'scrape_linkedin_or_company'):
138
+ company_data = scraper.scrape_linkedin_or_company(row['linkedin_url'], row['company'])
139
+ except Exception:
140
+ company_data = f"Company: {row['company']} - Professional services company"
141
+
142
+ if not company_data:
143
+ company_data = f"Company: {row['company']} - Industry leading organization"
144
+
145
+ # Generate email with multiple fallbacks
146
+ email_result = None
147
+
148
+ # Try AI generation first
149
+ if email_generator:
150
+ try:
151
+ # Check the email generator's method signature
152
+ if hasattr(email_generator, 'generate_email'):
153
+ # Try different method signatures
154
+ try:
155
+ # Try the newer signature (name, company, company_info, tone, temperature)
156
+ subject, body = email_generator.generate_email(
157
+ name=row['name'],
158
+ company=row['company'],
159
+ company_info=company_data,
160
+ tone=tone,
161
+ temperature=creativity
162
+ )
163
+ email_result = {
164
+ 'subject': subject,
165
+ 'content': body,
166
+ 'quality_score': 8.0
167
+ }
168
+ except TypeError:
169
+ # Try older signature (recipient_name, recipient_email, company_name, company_data, tone, temperature)
170
+ email_result = email_generator.generate_email(
171
+ recipient_name=row['name'],
172
+ recipient_email=row['email'],
173
+ company_name=row['company'],
174
+ company_data={'description': company_data},
175
+ tone=tone.lower(),
176
+ temperature=creativity
177
+ )
178
+ except Exception as e:
179
+ print(f"AI generation failed for {row['name']}: {e}")
180
+ email_result = None
181
+
182
+ # Use advanced fallback if AI failed
183
+ if not email_result or not email_result.get('content'):
184
+ if email_generator:
185
+ try:
186
+ # Use the email generator's advanced fallback system
187
+ subject, body = email_generator._advanced_fallback_generation(
188
+ row['name'],
189
+ row['company'],
190
+ company_data,
191
+ tone
192
+ )
193
+ email_result = {
194
+ 'subject': subject,
195
+ 'content': body,
196
+ 'quality_score': 8.5
197
+ }
198
+ except:
199
+ # Final fallback
200
+ email_result = create_fallback_email(row['name'], row['company'], tone)
201
+ else:
202
+ email_result = create_fallback_email(row['name'], row['company'], tone)
203
+
204
+ # Create result
205
+ result = {
206
+ 'name': row['name'],
207
+ 'email': row['email'],
208
+ 'company': row['company'],
209
+ 'subject': email_result.get('subject', f"Partnership Opportunity - {row['company']}"),
210
+ 'email_content': email_result.get('content', ''),
211
+ 'quality_score': email_result.get('quality_score', 8.0),
212
+ 'status': 'success'
213
+ }
214
+
215
+ results.append(result)
216
+ time.sleep(0.3) # Rate limiting
217
+
218
+ except Exception as e:
219
+ st.warning(f"⚠️ Issue with {row['name']}: {str(e)}")
220
+ # Always create a result, even with errors
221
+ if email_generator:
222
+ try:
223
+ fallback_result = email_generator._advanced_fallback_generation(
224
+ row['name'],
225
+ row['company'],
226
+ '',
227
+ tone
228
+ )
229
+ result = {
230
+ 'name': row['name'],
231
+ 'email': row['email'],
232
+ 'company': row['company'],
233
+ 'subject': fallback_result[0],
234
+ 'email_content': fallback_result[1],
235
+ 'quality_score': 8.0,
236
+ 'status': 'fallback'
237
+ }
238
+ except:
239
+ fallback_result = create_fallback_email(row['name'], row['company'], tone)
240
+ result = {
241
+ 'name': row['name'],
242
+ 'email': row['email'],
243
+ 'company': row['company'],
244
+ 'subject': fallback_result.get('subject', f"Partnership Opportunity - {row['company']}"),
245
+ 'email_content': fallback_result.get('content', ''),
246
+ 'quality_score': 7.0,
247
+ 'status': 'error'
248
+ }
249
+ else:
250
+ fallback_result = create_fallback_email(row['name'], row['company'], tone)
251
+ result = {
252
+ 'name': row['name'],
253
+ 'email': row['email'],
254
+ 'company': row['company'],
255
+ 'subject': fallback_result.get('subject', f"Partnership Opportunity - {row['company']}"),
256
+ 'email_content': fallback_result.get('content', ''),
257
+ 'quality_score': 7.0,
258
+ 'status': 'basic_fallback'
259
+ }
260
+
261
+ results.append(result)
262
 
263
+ progress_bar.progress(1.0)
264
+ status_text.text("✅ Processing complete!")
265
 
266
+ return results
267
+
268
+ def main():
269
+ # Initialize database
270
+ init_database()
271
+
272
+ # Header
273
+ st.title("📧 Cold Email Outreach Assistant")
274
+ st.markdown("Transform your lead list into personalized, high-converting cold emails using AI")
275
+
276
+ # Sidebar settings
277
+ with st.sidebar:
278
+ st.header("⚙️ Settings")
279
 
280
+ tone = st.selectbox(
281
+ "🎭 Email Tone",
282
+ ["Professional", "Friendly", "Direct"],
283
+ index=0
284
+ )
285
 
286
+ creativity = st.slider(
287
+ "🎨 Creativity Level",
288
+ min_value=0.3,
289
+ max_value=0.9,
290
+ value=0.7,
291
+ step=0.1
292
  )
293
 
294
+ st.markdown("---")
295
+ st.subheader("🤖 AI Model Status")
296
+
297
+ # Try to load modules to show status
298
+ scraper, email_gen = load_modules()
299
+ if scraper and email_gen:
300
+ st.success("✅ AI model loaded successfully")
301
+ else:
302
+ st.warning("⚠️ AI model loading... (this may take 10-15 minutes on first run)")
303
+ st.info("💡 The app will work with high-quality fallback emails while the model loads")
304
+
305
+ st.markdown("---")
306
+ st.info("💡 **Tip**: Use LinkedIn company URLs for best results")
307
+
308
+ # Email capture for lead generation
309
+ st.markdown("---")
310
+ st.subheader("🔔 Stay Updated")
311
+
312
+ user_email = st.text_input(
313
+ "📧 Email Address",
314
+ placeholder="[email protected]",
315
+ help="Get notified when Pro features launch"
316
  )
317
 
318
+ if st.button("🚀 Notify Me", use_container_width=True):
319
+ if user_email and "@" in user_email:
320
+ st.session_state.user_email = user_email
321
+ # Here you could save to a database or CSV
322
+ st.success("✅ Thanks! We'll keep you updated.")
323
+ else:
324
+ st.error("❌ Please enter a valid email")
325
 
326
+ # Show current usage
327
+ if not st.session_state.is_premium:
328
+ remaining = FREE_DAILY_LIMIT - st.session_state.email_count
329
+ st.markdown("---")
330
+ st.subheader("📊 Usage Today")
331
+ st.progress(st.session_state.email_count / FREE_DAILY_LIMIT)
332
+ st.write(f"**{remaining} free emails remaining**")
333
+
334
+ if remaining <= 2:
335
+ st.warning("⚠️ Almost at daily limit!")
336
+ if st.button("🔓 Upgrade Now", use_container_width=True):
337
+ st.markdown("**[Get Unlimited Access →](https://gumroad.com/l/cold-email-unlimited)**")
338
+
339
+ # Main content
340
+ st.subheader("📁 Upload Your Leads")
341
+
342
+ uploaded_file = st.file_uploader(
343
+ "Choose a CSV file",
344
+ type=['csv'],
345
+ help="Upload a CSV with columns: name, email, company, linkedin_url"
346
+ )
347
+
348
+ # Sample CSV download
349
+ col1, col2 = st.columns([2, 1])
350
  with col2:
351
+ sample_data = {
352
+ 'name': ['John Smith', 'Jane Doe', 'Mike Johnson', 'Sarah Wilson', 'David Brown'],
353
+ 'email': ['john@techcorp.com', '[email protected]', '[email protected]', '[email protected]', '[email protected]'],
354
+ 'company': ['TechCorp Inc', 'StartupXYZ', 'Creative Agency', 'FutureTech', 'DigitalSolutions'],
355
+ 'linkedin_url': [
356
+ 'https://linkedin.com/company/techcorp',
357
+ 'https://linkedin.com/company/startupxyz',
358
+ 'https://linkedin.com/company/creative-agency',
359
+ 'https://linkedin.com/company/futuretech',
360
+ 'https://linkedin.com/company/digital-solutions'
361
+ ]
362
+ }
363
+ sample_df = pd.DataFrame(sample_data)
364
+ csv = sample_df.to_csv(index=False)
365
+ st.download_button(
366
+ "📄 Download Sample CSV",
367
+ csv,
368
+ "sample_leads.csv",
369
+ "text/csv"
370
+ )
371
+
372
+ if uploaded_file is not None:
373
+ try:
374
+ # Load CSV
375
+ df = pd.read_csv(uploaded_file)
376
+
377
+ # Validate columns
378
+ required_columns = ['name', 'email', 'company', 'linkedin_url']
379
+ missing_columns = [col for col in required_columns if col not in df.columns]
380
+
381
+ if missing_columns:
382
+ st.error(f"❌ Missing columns: {', '.join(missing_columns)}")
383
+ st.info("Required columns: name, email, company, linkedin_url")
384
  else:
385
+ st.success(f" Loaded {len(df)} leads")
386
+
387
+ # Show preview
388
+ with st.expander("👀 Preview Data"):
389
+ st.dataframe(df.head(), use_container_width=True)
390
+
391
+ # Process button
392
+ if st.button("🚀 Generate Cold Emails", type="primary", use_container_width=True):
393
+
394
+ # Check usage limits first
395
+ if not check_usage_limit():
396
+ show_upgrade_cta()
397
+ return
398
+
399
+ # Check if user will exceed limit with this batch
400
+ batch_size = len(df)
401
+ if st.session_state.email_count + batch_size > FREE_DAILY_LIMIT and not st.session_state.is_premium:
402
+ remaining = FREE_DAILY_LIMIT - st.session_state.email_count
403
+ st.warning(f"⚠️ You can only generate {remaining} more emails today. Upgrade for unlimited access!")
404
 
405
+ if st.button("� Continue with Limited Batch", use_container_width=True):
406
+ df = df.head(remaining) if remaining > 0 else df.head(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  else:
408
+ show_upgrade_cta()
409
+ return
410
+
411
+ with st.spinner("�🔄 Processing your leads and generating emails..."):
412
+ results = process_leads(df, tone, creativity)
413
+ # Increment usage counter
414
+ increment_usage()
415
+
416
+ if results:
417
+ st.success(f"✅ Generated {len(results)} professional emails!")
418
 
419
+ # Display metrics
420
+ col1, col2, col3 = st.columns(3)
421
+ with col1:
422
+ st.metric("📨 Emails Generated", len(results))
423
+ with col2:
424
+ avg_quality = sum(r['quality_score'] for r in results) / len(results)
425
+ st.metric("🎯 Avg Quality Score", f"{avg_quality:.1f}")
426
+ with col3:
427
+ high_quality = len([r for r in results if r['quality_score'] >= 8.0])
428
+ st.metric("⭐ High Quality", high_quality)
429
+
430
+ # Results table with data validation
431
+ st.subheader("📊 Generated Emails")
432
+ try:
433
+ # Clean and validate results data before display
434
+ clean_results = []
435
+ for r in results:
436
+ # Ensure all values are strings and properly sanitized
437
+ clean_result = {
438
+ 'name': str(r.get('name', 'Unknown')).replace('\n', ' ').replace('\r', '')[:50],
439
+ 'company': str(r.get('company', 'Unknown')).replace('\n', ' ').replace('\r', '')[:50],
440
+ 'subject': str(r.get('subject', 'No subject')).replace('\n', ' ').replace('\r', '')[:80],
441
+ 'quality_score': round(float(r.get('quality_score', 8.0)), 1)
442
+ }
443
+ # Validate that quality score is within range
444
+ if clean_result['quality_score'] < 1.0 or clean_result['quality_score'] > 10.0:
445
+ clean_result['quality_score'] = 8.0
446
+ clean_results.append(clean_result)
447
 
448
+ display_df = pd.DataFrame(clean_results)
449
+ # Ensure DataFrame has the expected columns
450
+ expected_columns = ['name', 'company', 'subject', 'quality_score']
451
+ for col in expected_columns:
452
+ if col not in display_df.columns:
453
+ display_df[col] = 'N/A'
 
 
 
 
 
 
 
 
 
454
 
455
+ st.dataframe(display_df[expected_columns], use_container_width=True, height=300)
456
+ except Exception as e:
457
+ st.warning("⚠️ Display issue - showing simplified view")
458
+ simple_data = [[r['name'], r['company'], f"{r['quality_score']:.1f}"] for r in results]
459
+ st.table(pd.DataFrame(simple_data, columns=['Name', 'Company', 'Quality']))
460
+
461
+ # Email preview with error handling
462
+ st.subheader("📝 Email Preview")
463
+ try:
464
+ if len(results) > 0:
465
+ selected_idx = st.selectbox(
466
+ "Select email to preview:",
467
+ range(min(len(results), 50)), # Limit to prevent crashes
468
+ format_func=lambda x: f"{results[x]['name']} - {results[x]['company']} (Q: {results[x]['quality_score']:.1f})"
 
 
 
 
 
 
 
469
  )
470
+
471
+ if selected_idx < len(results):
472
+ selected_email = results[selected_idx]
473
+
474
+ col1, col2 = st.columns([1, 1])
475
+ with col1:
476
+ st.write("**📧 Subject:**")
477
+ # Ensure subject is clean and safe for display
478
+ clean_subject = str(selected_email.get('subject', 'No subject'))
479
+ clean_subject = clean_subject.replace('\n', ' ').replace('\r', '')[:150]
480
+ st.code(clean_subject)
481
+ st.write("**📊 Quality Score:**")
482
+ quality = float(selected_email.get('quality_score', 8.0))
483
+ # Ensure quality is in valid range
484
+ quality = max(1.0, min(10.0, quality))
485
+ st.metric("", f"{quality:.1f}/10")
486
+
487
+ with col2:
488
+ st.write("**📄 Email Content:**")
489
+ # Ensure content is clean and safe for display
490
+ clean_content = str(selected_email.get('email_content', 'No content'))
491
+ # Remove problematic characters that might cause React errors
492
+ clean_content = clean_content.replace('\r\n', '\n').replace('\r', '\n')
493
+ # Limit length to prevent display issues
494
+ clean_content = clean_content[:2000]
495
+ st.text_area(
496
+ "",
497
+ clean_content,
498
+ height=250,
499
+ disabled=True,
500
+ label_visibility="collapsed"
501
+ )
502
+ except Exception as e:
503
+ st.error("⚠️ Preview unavailable - but your emails were generated successfully")
504
 
505
+ # Export with data validation
506
+ st.subheader("📤 Export Results")
507
+ try:
508
+ # Clean export data with robust validation
509
+ export_data = []
510
+ for r in results:
511
+ # Sanitize all data for CSV export
512
+ clean_row = {
513
+ 'name': str(r.get('name', 'Unknown')).strip()[:100],
514
+ 'email': str(r.get('email', '[email protected]')).strip()[:100],
515
+ 'company': str(r.get('company', 'Unknown')).strip()[:100],
516
+ 'subject': str(r.get('subject', 'No subject')).replace('\n', ' ').replace('\r', ' ').strip()[:200],
517
+ 'email_content': str(r.get('email_content', 'No content')).replace('\r\n', '\n').replace('\r', '\n').strip()[:5000],
518
+ 'quality_score': round(max(1.0, min(10.0, float(r.get('quality_score', 8.0)))), 1),
519
+ 'status': str(r.get('status', 'success')).strip()[:20]
520
+ }
521
+ export_data.append(clean_row)
522
 
523
+ export_df = pd.DataFrame(export_data)
524
+ # Ensure no NaN values that could cause issues
525
+ export_df = export_df.fillna('')
526
+ csv_data = export_df.to_csv(index=False, encoding='utf-8').encode('utf-8')
527
+
528
+ st.download_button(
529
+ "📥 Download All Emails (CSV)",
530
+ csv_data,
531
+ f"cold_emails_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
532
+ "text/csv",
533
+ use_container_width=True
534
+ )
535
+
536
+ st.info(f"💡 Ready to export {len(results)} professional cold emails for your outreach campaign!")
537
+ except Exception as e:
538
+ st.error("⚠️ Export issue - please try regenerating")
539
+
540
+ else:
541
+ st.error(" Failed to generate emails. Please try again.")
542
+
543
+ except Exception as e:
544
+ st.error(f"❌ Error loading CSV: {str(e)}")
545
+ st.info("Please ensure your CSV file has the correct format and encoding.")
546
 
547
  # Footer
548
  st.markdown("---")